2 * diff_file.c : routines for doing diffs on files
4 * ====================================================================
5 * Copyright (c) 2002-2009 CollabNet. All rights reserved.
7 * This software is licensed as described in the file COPYING, which
8 * you should have received as part of this distribution. The terms
9 * are also available at http://subversion.tigris.org/license-1.html.
10 * If newer versions of this license are posted there, you may use a
11 * newer version instead, at your option.
13 * This software consists of voluntary contributions made by many
14 * individuals. For exact contribution history, see the revision
15 * history and logs, available at http://subversion.tigris.org/.
16 * ====================================================================
21 #include <apr_pools.h>
22 #include <apr_general.h>
23 #include <apr_file_io.h>
24 #include <apr_file_info.h>
27 #include <apr_getopt.h>
29 #include <apr_general.h>
30 #include "svn_version.h"
32 #include "svn_ctype.h"
34 #include "svn_error.h"
36 #include "svn_types.h"
37 #include "svn_string.h"
40 #include "svn_pools.h"
43 /* A token, i.e. a line read from a file. */
44 typedef struct svn_diff__file_token_t
46 /* Next token in free list. */
47 struct svn_diff__file_token_t
*next
;
48 svn_diff_datasource_e datasource
;
49 /* Offset in the datasource. */
51 /* Offset of the normalized token (may skip leading whitespace) */
52 apr_off_t norm_offset
;
53 /* Total length - before normalization. */
55 /* Total length - after normalization. */
57 } svn_diff__file_token_t
;
60 typedef struct svn_diff__file_baton_t
62 const svn_diff_file_options_t
*options
;
73 /* List of free tokens that may be reused. */
74 svn_diff__file_token_t
*tokens
;
76 svn_diff__normalize_state_t normalize_state
[4];
79 } svn_diff__file_baton_t
;
82 /* Look for the start of an end-of-line sequence (i.e. CR or LF)
83 * in the array pointed to by BUF, of length LEN.
84 * If such a byte is found, return the pointer to it, else return NULL.
87 find_eol_start(char *buf
, apr_size_t len
)
89 for (; len
> 0; ++buf
, --len
)
91 if (*buf
== '\n' || *buf
== '\r')
98 datasource_to_index(svn_diff_datasource_e datasource
)
102 case svn_diff_datasource_original
:
105 case svn_diff_datasource_modified
:
108 case svn_diff_datasource_latest
:
111 case svn_diff_datasource_ancestor
:
118 /* Files are read in chunks of 128k. There is no support for this number
119 * whatsoever. If there is a number someone comes up with that has some
120 * argumentation, let's use that.
122 #define CHUNK_SHIFT 17
123 #define CHUNK_SIZE (1 << CHUNK_SHIFT)
125 #define chunk_to_offset(chunk) ((chunk) << CHUNK_SHIFT)
126 #define offset_to_chunk(offset) ((offset) >> CHUNK_SHIFT)
127 #define offset_in_chunk(offset) ((offset) & (CHUNK_SIZE - 1))
130 /* Read a chunk from a FILE into BUFFER, starting from OFFSET, going for
131 * *LENGTH. The actual bytes read are stored in *LENGTH on return.
133 static APR_INLINE svn_error_t
*
134 read_chunk(apr_file_t
*file
, const char *path
,
135 char *buffer
, apr_off_t length
,
136 apr_off_t offset
, apr_pool_t
*pool
)
138 /* XXX: The final offset may not be the one we asked for.
141 SVN_ERR(svn_io_file_seek(file
, APR_SET
, &offset
, pool
));
142 return svn_io_file_read_full(file
, buffer
, (apr_size_t
) length
, NULL
, pool
);
146 /* Map or read a file at PATH. *BUFFER will point to the file
147 * contents; if the file was mapped, *FILE and *MM will contain the
148 * mmap context; otherwise they will be NULL. SIZE will contain the
149 * file size. Allocate from POOL.
152 #define MMAP_T_PARAM(NAME) apr_mmap_t **NAME,
153 #define MMAP_T_ARG(NAME) &(NAME),
155 #define MMAP_T_PARAM(NAME)
156 #define MMAP_T_ARG(NAME)
160 map_or_read_file(apr_file_t
**file
,
162 char **buffer
, apr_off_t
*size
,
163 const char *path
, apr_pool_t
*pool
)
170 SVN_ERR(svn_io_file_open(file
, path
, APR_READ
, APR_OS_DEFAULT
, pool
));
171 SVN_ERR(svn_io_file_info_get(&finfo
, APR_FINFO_SIZE
, *file
, pool
));
174 if (finfo
.size
> APR_MMAP_THRESHOLD
)
176 rv
= apr_mmap_create(mm
, *file
, 0, (apr_size_t
) finfo
.size
,
177 APR_MMAP_READ
, pool
);
178 if (rv
== APR_SUCCESS
)
183 /* On failure we just fall through and try reading the file into
187 #endif /* APR_HAS_MMAP */
189 if (*buffer
== NULL
&& finfo
.size
> 0)
191 *buffer
= apr_palloc(pool
, (apr_size_t
) finfo
.size
);
193 SVN_ERR(svn_io_file_read_full(*file
, *buffer
, (apr_size_t
) finfo
.size
,
196 /* Since we have the entire contents of the file we can
199 SVN_ERR(svn_io_file_close(*file
, pool
));
210 /* Implements svn_diff_fns_t::datasource_open */
212 datasource_open(void *baton
, svn_diff_datasource_e datasource
)
214 svn_diff__file_baton_t
*file_baton
= baton
;
221 idx
= datasource_to_index(datasource
);
223 SVN_ERR(svn_io_file_open(&file_baton
->file
[idx
], file_baton
->path
[idx
],
224 APR_READ
, APR_OS_DEFAULT
, file_baton
->pool
));
226 if( strcmp(file_baton
->path
[idx
], "NUL") == 0)
228 memset(&finfo
, 0, sizeof(apr_finfo_t
));
232 SVN_ERR(svn_io_file_info_get(&finfo
, APR_FINFO_SIZE
,
233 file_baton
->file
[idx
], file_baton
->pool
));
236 file_baton
->size
[idx
] = finfo
.size
;
237 length
= finfo
.size
> CHUNK_SIZE
? CHUNK_SIZE
: finfo
.size
;
242 endp
= curp
= apr_palloc(file_baton
->pool
, (apr_size_t
) length
);
245 file_baton
->buffer
[idx
] = file_baton
->curp
[idx
] = curp
;
246 file_baton
->endp
[idx
] = endp
;
248 return read_chunk(file_baton
->file
[idx
], file_baton
->path
[idx
],
249 curp
, length
, 0, file_baton
->pool
);
253 /* Implements svn_diff_fns_t::datasource_close */
255 datasource_close(void *baton
, svn_diff_datasource_e datasource
)
257 /* Do nothing. The compare_token function needs previous datasources
258 * to stay available until all datasources are processed.
264 /* Implements svn_diff_fns_t::datasource_get_next_token */
266 datasource_get_next_token(apr_uint32_t
*hash
, void **token
, void *baton
,
267 svn_diff_datasource_e datasource
)
269 svn_diff__file_baton_t
*file_baton
= baton
;
270 svn_diff__file_token_t
*file_token
;
275 apr_off_t last_chunk
;
278 /* Did the last chunk end in a CR character? */
279 svn_boolean_t had_cr
= FALSE
;
283 idx
= datasource_to_index(datasource
);
285 curp
= file_baton
->curp
[idx
];
286 endp
= file_baton
->endp
[idx
];
288 last_chunk
= offset_to_chunk(file_baton
->size
[idx
]);
291 && last_chunk
== file_baton
->chunk
[idx
])
296 /* Get a new token */
297 file_token
= file_baton
->tokens
;
300 file_baton
->tokens
= file_token
->next
;
304 file_token
= apr_palloc(file_baton
->pool
, sizeof(*file_token
));
307 file_token
->datasource
= datasource
;
308 file_token
->offset
= chunk_to_offset(file_baton
->chunk
[idx
])
309 + (curp
- file_baton
->buffer
[idx
]);
310 file_token
->raw_length
= 0;
311 file_token
->length
= 0;
315 eol
= find_eol_start(curp
, endp
- curp
);
318 had_cr
= (*eol
== '\r');
320 /* If we have the whole eol sequence in the chunk... */
321 if (!had_cr
|| eol
!= endp
)
323 if (had_cr
&& *eol
== '\n')
329 if (file_baton
->chunk
[idx
] == last_chunk
)
335 length
= endp
- curp
;
336 file_token
->raw_length
+= length
;
337 svn_diff__normalize_buffer(&curp
, &length
,
338 &file_baton
->normalize_state
[idx
],
339 curp
, file_baton
->options
);
340 file_token
->length
+= length
;
341 h
= svn_diff__adler32(h
, curp
, length
);
343 curp
= endp
= file_baton
->buffer
[idx
];
344 file_baton
->chunk
[idx
]++;
345 length
= file_baton
->chunk
[idx
] == last_chunk
?
346 offset_in_chunk(file_baton
->size
[idx
]) : CHUNK_SIZE
;
348 file_baton
->endp
[idx
] = endp
;
350 SVN_ERR(read_chunk(file_baton
->file
[idx
], file_baton
->path
[idx
],
352 chunk_to_offset(file_baton
->chunk
[idx
]),
355 /* If the last chunk ended in a CR, we're done. */
366 file_token
->raw_length
+= length
;
367 file_baton
->curp
[idx
] = eol
;
369 /* If the file length is exactly a multiple of CHUNK_SIZE, we will end up
370 * with a spurious empty token. Avoid returning it.
371 * Note that we use the unnormalized length; we don't want a line containing
372 * only spaces (and no trailing newline) to appear like a non-existent
374 if (file_token
->raw_length
> 0)
377 svn_diff__normalize_buffer(&c
, &length
,
378 &file_baton
->normalize_state
[idx
],
379 curp
, file_baton
->options
);
381 file_token
->norm_offset
= file_token
->offset
;
382 if (file_token
->length
== 0)
383 /* move past leading ignored characters */
384 file_token
->norm_offset
+= (c
- curp
);
386 file_token
->length
+= length
;
388 *hash
= svn_diff__adler32(h
, c
, length
);
395 #define COMPARE_CHUNK_SIZE 4096
397 /* Implements svn_diff_fns_t::token_compare */
399 token_compare(void *baton
, void *token1
, void *token2
, int *compare
)
401 svn_diff__file_baton_t
*file_baton
= baton
;
402 svn_diff__file_token_t
*file_token
[2];
403 char buffer
[2][COMPARE_CHUNK_SIZE
];
408 apr_off_t total_length
;
409 /* How much is left to read of each token from the file. */
410 apr_off_t raw_length
[2];
413 svn_diff__normalize_state_t state
[2];
415 file_token
[0] = token1
;
416 file_token
[1] = token2
;
417 if (file_token
[0]->length
< file_token
[1]->length
)
423 if (file_token
[0]->length
> file_token
[1]->length
)
429 total_length
= file_token
[0]->length
;
430 if (total_length
== 0)
436 for (i
= 0; i
< 2; ++i
)
438 idx
[i
] = datasource_to_index(file_token
[i
]->datasource
);
439 offset
[i
] = file_token
[i
]->norm_offset
;
440 chunk
[i
] = file_baton
->chunk
[idx
[i
]];
441 state
[i
] = svn_diff__normalize_state_normal
;
443 if (offset_to_chunk(offset
[i
]) == chunk
[i
])
445 /* If the start of the token is in memory, the entire token is
448 bufp
[i
] = file_baton
->buffer
[idx
[i
]];
449 bufp
[i
] += offset_in_chunk(offset
[i
]);
451 length
[i
] = total_length
;
457 raw_length
[i
] = file_token
[i
]->raw_length
;
464 for (i
= 0; i
< 2; i
++)
468 /* Error if raw_length is 0, that's an unexpected change
469 * of the file that can happen when ingoring whitespace
470 * and that can lead to an infinite loop. */
471 if (raw_length
[i
] == 0)
472 return svn_error_createf(SVN_ERR_DIFF_DATASOURCE_MODIFIED
,
474 _("The file '%s' changed unexpectedly"
476 file_baton
->path
[idx
[i
]]);
478 /* Read a chunk from disk into a buffer */
480 length
[i
] = raw_length
[i
] > COMPARE_CHUNK_SIZE
?
481 COMPARE_CHUNK_SIZE
: raw_length
[i
];
483 SVN_ERR(read_chunk(file_baton
->file
[idx
[i
]],
484 file_baton
->path
[idx
[i
]],
485 bufp
[i
], length
[i
], offset
[i
],
487 offset
[i
] += length
[i
];
488 raw_length
[i
] -= length
[i
];
489 /* bufp[i] gets reset to buffer[i] before reading each chunk,
490 so, overwriting it isn't a problem */
491 svn_diff__normalize_buffer(&bufp
[i
], &length
[i
], &state
[i
],
492 bufp
[i
], file_baton
->options
);
496 len
= length
[0] > length
[1] ? length
[1] : length
[0];
498 /* Compare two chunks (that could be entire tokens if they both reside
501 *compare
= memcmp(bufp
[0], bufp
[1], (size_t) len
);
511 while(total_length
> 0);
518 /* Implements svn_diff_fns_t::token_discard */
520 token_discard(void *baton
, void *token
)
522 svn_diff__file_baton_t
*file_baton
= baton
;
523 svn_diff__file_token_t
*file_token
= token
;
525 file_token
->next
= file_baton
->tokens
;
526 file_baton
->tokens
= file_token
;
530 /* Implements svn_diff_fns_t::token_discard_all */
532 token_discard_all(void *baton
)
534 svn_diff__file_baton_t
*file_baton
= baton
;
536 /* Discard all memory in use by the tokens, and close all open files. */
537 svn_pool_clear(file_baton
->pool
);
541 static const svn_diff_fns_t svn_diff__file_vtable
=
545 datasource_get_next_token
,
551 /* Id for the --ignore-eol-style option, which doesn't have a short name. */
552 #define SVN_DIFF__OPT_IGNORE_EOL_STYLE 256
554 /* Options supported by svn_diff_file_options_parse(). */
555 static const apr_getopt_option_t diff_options
[] =
557 { "ignore-space-change", 'b', 0, NULL
},
558 { "ignore-all-space", 'w', 0, NULL
},
559 { "ignore-eol-style", SVN_DIFF__OPT_IGNORE_EOL_STYLE
, 0, NULL
},
560 { "show-c-function", 'p', 0, NULL
},
561 /* ### For compatibility; we don't support the argument to -u, because
562 * ### we don't have optional argument support. */
563 { "unified", 'u', 0, NULL
},
567 svn_diff_file_options_t
*
568 svn_diff_file_options_create(apr_pool_t
*pool
)
570 return apr_pcalloc(pool
, sizeof(svn_diff_file_options_t
));
574 svn_diff_file_options_parse(svn_diff_file_options_t
*options
,
575 const apr_array_header_t
*args
,
579 /* Make room for each option (starting at index 1) plus trailing NULL. */
580 const char **argv
= apr_palloc(pool
, sizeof(char*) * (args
->nelts
+ 2));
583 memcpy((void *) (argv
+ 1), args
->elts
, sizeof(char*) * args
->nelts
);
584 argv
[args
->nelts
+ 1] = NULL
;
586 apr_getopt_init(&os
, pool
, args
->nelts
+ 1, argv
);
587 /* No printing of error messages, please! */
593 apr_status_t err
= apr_getopt_long(os
, diff_options
, &opt_id
, &opt_arg
);
595 if (APR_STATUS_IS_EOF(err
))
598 return svn_error_wrap_apr(err
, _("Error parsing diff options"));
603 /* -w takes precedence over -b. */
604 if (! options
->ignore_space
)
605 options
->ignore_space
= svn_diff_file_ignore_space_change
;
608 options
->ignore_space
= svn_diff_file_ignore_space_all
;
610 case SVN_DIFF__OPT_IGNORE_EOL_STYLE
:
611 options
->ignore_eol_style
= TRUE
;
614 options
->show_c_function
= TRUE
;
621 /* Check for spurious arguments. */
622 if (os
->ind
< os
->argc
)
623 return svn_error_createf(SVN_ERR_INVALID_DIFF_OPTION
, NULL
,
624 _("Invalid argument '%s' in diff options"),
631 svn_diff_file_diff_2(svn_diff_t
**diff
,
632 const char *original
,
633 const char *modified
,
634 const svn_diff_file_options_t
*options
,
637 svn_diff__file_baton_t baton
;
639 memset(&baton
, 0, sizeof(baton
));
640 baton
.options
= options
;
641 baton
.path
[0] = original
;
642 baton
.path
[1] = modified
;
643 baton
.pool
= svn_pool_create(pool
);
645 SVN_ERR(svn_diff_diff(diff
, &baton
, &svn_diff__file_vtable
, pool
));
647 svn_pool_destroy(baton
.pool
);
652 svn_diff_file_diff(svn_diff_t
**diff
,
653 const char *original
,
654 const char *modified
,
657 return svn_diff_file_diff_2(diff
, original
, modified
,
658 svn_diff_file_options_create(pool
), pool
);
662 svn_diff_file_diff3_2(svn_diff_t
**diff
,
663 const char *original
,
664 const char *modified
,
666 const svn_diff_file_options_t
*options
,
669 svn_diff__file_baton_t baton
;
671 memset(&baton
, 0, sizeof(baton
));
672 baton
.options
= options
;
673 baton
.path
[0] = original
;
674 baton
.path
[1] = modified
;
675 baton
.path
[2] = latest
;
676 baton
.pool
= svn_pool_create(pool
);
678 SVN_ERR(svn_diff_diff3(diff
, &baton
, &svn_diff__file_vtable
, pool
));
680 svn_pool_destroy(baton
.pool
);
685 svn_diff_file_diff3(svn_diff_t
**diff
,
686 const char *original
,
687 const char *modified
,
691 return svn_diff_file_diff3_2(diff
, original
, modified
, latest
,
692 svn_diff_file_options_create(pool
), pool
);
696 svn_diff_file_diff4_2(svn_diff_t
**diff
,
697 const char *original
,
698 const char *modified
,
700 const char *ancestor
,
701 const svn_diff_file_options_t
*options
,
704 svn_diff__file_baton_t baton
;
706 memset(&baton
, 0, sizeof(baton
));
707 baton
.options
= options
;
708 baton
.path
[0] = original
;
709 baton
.path
[1] = modified
;
710 baton
.path
[2] = latest
;
711 baton
.path
[3] = ancestor
;
712 baton
.pool
= svn_pool_create(pool
);
714 SVN_ERR(svn_diff_diff4(diff
, &baton
, &svn_diff__file_vtable
, pool
));
716 svn_pool_destroy(baton
.pool
);
721 svn_diff_file_diff4(svn_diff_t
**diff
,
722 const char *original
,
723 const char *modified
,
725 const char *ancestor
,
728 return svn_diff_file_diff4_2(diff
, original
, modified
, latest
, ancestor
,
729 svn_diff_file_options_create(pool
), pool
);
732 /** Display unified context diffs **/
734 /* Maximum length of the extra context to show when show_c_function is set.
735 * GNU diff uses 40, let's be brave and use 50 instead. */
736 #define SVN_DIFF__EXTRA_CONTEXT_LENGTH 50
737 typedef struct svn_diff__file_output_baton_t
739 svn_stream_t
*output_stream
;
740 const char *header_encoding
;
742 /* Cached markers, in header_encoding. */
743 const char *context_str
;
744 const char *delete_str
;
745 const char *insert_str
;
750 apr_off_t current_line
[2];
752 char buffer
[2][4096];
753 apr_size_t length
[2];
756 apr_off_t hunk_start
[2];
757 apr_off_t hunk_length
[2];
758 svn_stringbuf_t
*hunk
;
760 /* Should we emit C functions in the unified diff header */
761 svn_boolean_t show_c_function
;
762 /* Extra strings to skip over if we match. */
763 apr_array_header_t
*extra_skip_match
;
764 /* "Context" to append to the @@ line when the show_c_function option
766 svn_stringbuf_t
*extra_context
;
767 /* Extra context for the current hunk. */
768 char hunk_extra_context
[SVN_DIFF__EXTRA_CONTEXT_LENGTH
+ 1];
771 } svn_diff__file_output_baton_t
;
773 typedef enum svn_diff__file_output_unified_type_e
775 svn_diff__file_output_unified_skip
,
776 svn_diff__file_output_unified_context
,
777 svn_diff__file_output_unified_delete
,
778 svn_diff__file_output_unified_insert
779 } svn_diff__file_output_unified_type_e
;
783 output_unified_line(svn_diff__file_output_baton_t
*baton
,
784 svn_diff__file_output_unified_type_e type
, int idx
)
790 svn_boolean_t bytes_processed
= FALSE
;
791 svn_boolean_t had_cr
= FALSE
;
792 /* Are we collecting extra context? */
793 svn_boolean_t collect_extra
= FALSE
;
795 length
= baton
->length
[idx
];
796 curp
= baton
->curp
[idx
];
798 /* Lazily update the current line even if we're at EOF.
799 * This way we fake output of context at EOF
801 baton
->current_line
[idx
]++;
803 if (length
== 0 && apr_file_eof(baton
->file
[idx
]))
812 if (!bytes_processed
)
816 case svn_diff__file_output_unified_context
:
817 svn_stringbuf_appendcstr(baton
->hunk
, baton
->context_str
);
818 baton
->hunk_length
[0]++;
819 baton
->hunk_length
[1]++;
821 case svn_diff__file_output_unified_delete
:
822 svn_stringbuf_appendcstr(baton
->hunk
, baton
->delete_str
);
823 baton
->hunk_length
[0]++;
825 case svn_diff__file_output_unified_insert
:
826 svn_stringbuf_appendcstr(baton
->hunk
, baton
->insert_str
);
827 baton
->hunk_length
[1]++;
833 if (baton
->show_c_function
834 && (type
== svn_diff__file_output_unified_skip
835 || type
== svn_diff__file_output_unified_context
)
836 && (svn_ctype_isalpha(*curp
) || *curp
== '$' || *curp
== '_')
837 && !svn_cstring_match_glob_list(curp
,
838 baton
->extra_skip_match
))
840 svn_stringbuf_setempty(baton
->extra_context
);
841 collect_extra
= TRUE
;
845 eol
= find_eol_start(curp
, length
);
851 had_cr
= (*eol
== '\r');
853 len
= (apr_size_t
)(eol
- curp
);
855 if (! had_cr
|| len
< length
)
857 if (had_cr
&& *eol
== '\n')
865 if (type
!= svn_diff__file_output_unified_skip
)
867 svn_stringbuf_appendbytes(baton
->hunk
, curp
, len
);
871 svn_stringbuf_appendbytes(baton
->extra_context
,
875 baton
->curp
[idx
] = eol
;
876 baton
->length
[idx
] = length
;
884 if (type
!= svn_diff__file_output_unified_skip
)
886 svn_stringbuf_appendbytes(baton
->hunk
, curp
, length
);
891 svn_stringbuf_appendbytes(baton
->extra_context
, curp
, length
);
894 bytes_processed
= TRUE
;
897 curp
= baton
->buffer
[idx
];
898 length
= sizeof(baton
->buffer
[idx
]);
900 err
= svn_io_file_read(baton
->file
[idx
], curp
, &length
, baton
->pool
);
902 /* If the last chunk ended with a CR, we look for an LF at the start
906 if (! err
&& length
> 0 && *curp
== '\n')
908 if (type
!= svn_diff__file_output_unified_skip
)
910 svn_stringbuf_appendbytes(baton
->hunk
, curp
, 1);
912 /* We don't append the LF to extra_context, since it would
913 * just be stripped anyway. */
918 baton
->curp
[idx
] = curp
;
919 baton
->length
[idx
] = length
;
926 if (err
&& ! APR_STATUS_IS_EOF(err
->apr_err
))
929 if (err
&& APR_STATUS_IS_EOF(err
->apr_err
))
931 svn_error_clear(err
);
932 /* Special case if we reach the end of file AND the last line is in the
933 changed range AND the file doesn't end with a newline */
934 if (bytes_processed
&& (type
!= svn_diff__file_output_unified_skip
)
938 SVN_ERR(svn_utf_cstring_from_utf8_ex2
940 /* The string below is intentionally not marked for
941 translation: it's vital to correct operation of
942 the diff(1)/patch(1) program pair. */
943 APR_EOL_STR
"\\ No newline at end of file" APR_EOL_STR
,
944 baton
->header_encoding
, baton
->pool
));
945 svn_stringbuf_appendcstr(baton
->hunk
, out_str
);
948 baton
->length
[idx
] = 0;
955 output_unified_flush_hunk(svn_diff__file_output_baton_t
*baton
)
957 apr_off_t target_line
;
961 if (svn_stringbuf_isempty(baton
->hunk
))
963 /* Nothing to flush */
967 target_line
= baton
->hunk_start
[0] + baton
->hunk_length
[0]
968 + SVN_DIFF__UNIFIED_CONTEXT_SIZE
;
970 /* Add trailing context to the hunk */
971 while (baton
->current_line
[0] < target_line
)
973 SVN_ERR(output_unified_line
974 (baton
, svn_diff__file_output_unified_context
, 0));
977 /* If the file is non-empty, convert the line indexes from
978 zero based to one based */
979 for (i
= 0; i
< 2; i
++)
981 if (baton
->hunk_length
[i
] > 0)
982 baton
->hunk_start
[i
]++;
985 /* Output the hunk header. If the hunk length is 1, the file is a one line
986 file. In this case, surpress the number of lines in the hunk (it is
989 SVN_ERR(svn_stream_printf_from_utf8(baton
->output_stream
,
990 baton
->header_encoding
,
992 "@@ -%" APR_OFF_T_FMT
,
993 baton
->hunk_start
[0]));
994 if (baton
->hunk_length
[0] != 1)
996 SVN_ERR(svn_stream_printf_from_utf8(baton
->output_stream
,
997 baton
->header_encoding
,
998 baton
->pool
, ",%" APR_OFF_T_FMT
,
999 baton
->hunk_length
[0]));
1002 SVN_ERR(svn_stream_printf_from_utf8(baton
->output_stream
,
1003 baton
->header_encoding
,
1004 baton
->pool
, " +%" APR_OFF_T_FMT
,
1005 baton
->hunk_start
[1]));
1006 if (baton
->hunk_length
[1] != 1)
1008 SVN_ERR(svn_stream_printf_from_utf8(baton
->output_stream
,
1009 baton
->header_encoding
,
1010 baton
->pool
, ",%" APR_OFF_T_FMT
,
1011 baton
->hunk_length
[1]));
1014 SVN_ERR(svn_stream_printf_from_utf8(baton
->output_stream
,
1015 baton
->header_encoding
,
1016 baton
->pool
, " @@%s%s" APR_EOL_STR
,
1017 baton
->hunk_extra_context
[0]
1019 baton
->hunk_extra_context
));
1021 /* Output the hunk content */
1022 hunk_len
= baton
->hunk
->len
;
1023 SVN_ERR(svn_stream_write(baton
->output_stream
, baton
->hunk
->data
,
1026 /* Prepare for the next hunk */
1027 baton
->hunk_length
[0] = 0;
1028 baton
->hunk_length
[1] = 0;
1029 svn_stringbuf_setempty(baton
->hunk
);
1031 return SVN_NO_ERROR
;
1034 static svn_error_t
*
1035 output_unified_diff_modified(void *baton
,
1036 apr_off_t original_start
, apr_off_t original_length
,
1037 apr_off_t modified_start
, apr_off_t modified_length
,
1038 apr_off_t latest_start
, apr_off_t latest_length
)
1040 svn_diff__file_output_baton_t
*output_baton
= baton
;
1041 apr_off_t target_line
[2];
1044 target_line
[0] = original_start
>= SVN_DIFF__UNIFIED_CONTEXT_SIZE
1045 ? original_start
- SVN_DIFF__UNIFIED_CONTEXT_SIZE
: 0;
1046 target_line
[1] = modified_start
;
1048 /* If the changed ranges are far enough apart (no overlapping or connecting
1049 context), flush the current hunk, initialize the next hunk and skip the
1050 lines not in context. Also do this when this is the first hunk.
1052 if (output_baton
->current_line
[0] < target_line
[0]
1053 && (output_baton
->hunk_start
[0] + output_baton
->hunk_length
[0]
1054 + SVN_DIFF__UNIFIED_CONTEXT_SIZE
< target_line
[0]
1055 || output_baton
->hunk_length
[0] == 0))
1057 SVN_ERR(output_unified_flush_hunk(output_baton
));
1059 output_baton
->hunk_start
[0] = target_line
[0];
1060 output_baton
->hunk_start
[1] = target_line
[1] + target_line
[0]
1063 /* Skip lines until we are at the beginning of the context we want to
1065 while (output_baton
->current_line
[0] < target_line
[0])
1067 SVN_ERR(output_unified_line(output_baton
,
1068 svn_diff__file_output_unified_skip
, 0));
1071 if (output_baton
->show_c_function
)
1074 const char *invalid_character
;
1076 /* Save the extra context for later use.
1077 * Note that the last byte of the hunk_extra_context array is never
1078 * touched after it is zero-initialized, so the array is always
1080 strncpy(output_baton
->hunk_extra_context
,
1081 output_baton
->extra_context
->data
,
1082 SVN_DIFF__EXTRA_CONTEXT_LENGTH
);
1083 /* Trim whitespace at the end, most notably to get rid of any
1084 * newline characters. */
1085 p
= strlen(output_baton
->hunk_extra_context
);
1087 && svn_ctype_isspace(output_baton
->hunk_extra_context
[p
- 1]))
1089 output_baton
->hunk_extra_context
[--p
] = '\0';
1092 svn_utf__last_valid(output_baton
->hunk_extra_context
,
1093 SVN_DIFF__EXTRA_CONTEXT_LENGTH
);
1094 for (p
= invalid_character
- output_baton
->hunk_extra_context
;
1095 p
< SVN_DIFF__EXTRA_CONTEXT_LENGTH
; p
++)
1097 output_baton
->hunk_extra_context
[p
] = '\0';
1102 /* Skip lines until we are at the start of the changed range */
1103 while (output_baton
->current_line
[1] < target_line
[1])
1105 SVN_ERR(output_unified_line(output_baton
,
1106 svn_diff__file_output_unified_skip
, 1));
1109 /* Output the context preceding the changed range */
1110 while (output_baton
->current_line
[0] < original_start
)
1112 SVN_ERR(output_unified_line(output_baton
,
1113 svn_diff__file_output_unified_context
, 0));
1116 target_line
[0] = original_start
+ original_length
;
1117 target_line
[1] = modified_start
+ modified_length
;
1119 /* Output the changed range */
1120 for (i
= 0; i
< 2; i
++)
1122 while (output_baton
->current_line
[i
] < target_line
[i
])
1124 SVN_ERR(output_unified_line
1126 i
== 0 ? svn_diff__file_output_unified_delete
1127 : svn_diff__file_output_unified_insert
, i
));
1131 return SVN_NO_ERROR
;
1134 /* Set *HEADER to a new string consisting of PATH, a tab, and PATH's mtime. */
1135 static svn_error_t
*
1136 output_unified_default_hdr(const char **header
, const char *path
,
1139 apr_finfo_t file_info
;
1140 apr_time_exp_t exploded_time
;
1141 char time_buffer
[64];
1142 apr_size_t time_len
;
1143 const char *utf8_timestr
;
1145 SVN_ERR(svn_io_stat(&file_info
, path
, APR_FINFO_MTIME
, pool
));
1146 apr_time_exp_lt(&exploded_time
, file_info
.mtime
);
1148 apr_strftime(time_buffer
, &time_len
, sizeof(time_buffer
) - 1,
1149 /* Order of date components can be different in different languages */
1150 _("%a %b %e %H:%M:%S %Y"), &exploded_time
);
1152 SVN_ERR(svn_utf_cstring_to_utf8(&utf8_timestr
, time_buffer
, pool
));
1154 *header
= apr_psprintf(pool
, "%s\t%s", path
, utf8_timestr
);
1156 return SVN_NO_ERROR
;
1159 static const svn_diff_output_fns_t svn_diff__file_output_unified_vtable
=
1161 NULL
, /* output_common */
1162 output_unified_diff_modified
,
1163 NULL
, /* output_diff_latest */
1164 NULL
, /* output_diff_common */
1165 NULL
/* output_conflict */
1169 svn_diff_file_output_unified3(svn_stream_t
*output_stream
,
1171 const char *original_path
,
1172 const char *modified_path
,
1173 const char *original_header
,
1174 const char *modified_header
,
1175 const char *header_encoding
,
1176 const char *relative_to_dir
,
1177 svn_boolean_t show_c_function
,
1180 svn_diff__file_output_baton_t baton
;
1183 if (svn_diff_contains_diffs(diff
))
1187 memset(&baton
, 0, sizeof(baton
));
1188 baton
.output_stream
= output_stream
;
1190 baton
.header_encoding
= header_encoding
;
1191 baton
.path
[0] = original_path
;
1192 baton
.path
[1] = modified_path
;
1193 baton
.hunk
= svn_stringbuf_create("", pool
);
1194 baton
.show_c_function
= show_c_function
;
1195 baton
.extra_context
= svn_stringbuf_create("", pool
);
1196 baton
.extra_skip_match
= apr_array_make(pool
, 3, sizeof(char **));
1198 c
= apr_array_push(baton
.extra_skip_match
);
1200 c
= apr_array_push(baton
.extra_skip_match
);
1202 c
= apr_array_push(baton
.extra_skip_match
);
1205 SVN_ERR(svn_utf_cstring_from_utf8_ex2(&baton
.context_str
, " ",
1206 header_encoding
, pool
));
1207 SVN_ERR(svn_utf_cstring_from_utf8_ex2(&baton
.delete_str
, "-",
1208 header_encoding
, pool
));
1209 SVN_ERR(svn_utf_cstring_from_utf8_ex2(&baton
.insert_str
, "+",
1210 header_encoding
, pool
));
1212 if (relative_to_dir
)
1214 /* Possibly adjust the "original" and "modified" paths shown in
1215 the output (see issue #2723). */
1216 const char *child_path
;
1218 if (! original_header
)
1220 child_path
= svn_path_is_child(relative_to_dir
,
1221 original_path
, pool
);
1223 original_path
= child_path
;
1225 return svn_error_createf(SVN_ERR_BAD_RELATIVE_PATH
, NULL
,
1226 _("Path '%s' must be an immediate child of "
1227 "the directory '%s'"),
1228 original_path
, relative_to_dir
);
1231 if (! modified_header
)
1233 child_path
= svn_path_is_child(relative_to_dir
, modified_path
, pool
);
1235 modified_path
= child_path
;
1237 return svn_error_createf(SVN_ERR_BAD_RELATIVE_PATH
, NULL
,
1238 _("Path '%s' must be an immediate child of "
1239 "the directory '%s'"),
1240 modified_path
, relative_to_dir
);
1244 for (i
= 0; i
< 2; i
++)
1246 SVN_ERR(svn_io_file_open(&baton
.file
[i
], baton
.path
[i
],
1247 APR_READ
, APR_OS_DEFAULT
, pool
));
1250 if (original_header
== NULL
)
1252 SVN_ERR(output_unified_default_hdr
1253 (&original_header
, original_path
, pool
));
1256 if (modified_header
== NULL
)
1258 SVN_ERR(output_unified_default_hdr
1259 (&modified_header
, modified_path
, pool
));
1262 SVN_ERR(svn_stream_printf_from_utf8(output_stream
, header_encoding
, pool
,
1263 "--- %s" APR_EOL_STR
1264 "+++ %s" APR_EOL_STR
,
1265 original_header
, modified_header
));
1267 SVN_ERR(svn_diff_output(diff
, &baton
,
1268 &svn_diff__file_output_unified_vtable
));
1269 SVN_ERR(output_unified_flush_hunk(&baton
));
1271 for (i
= 0; i
< 2; i
++)
1273 SVN_ERR(svn_io_file_close(baton
.file
[i
], pool
));
1277 return SVN_NO_ERROR
;
1281 /** Display diff3 **/
1283 /* A stream to remember *leading* context. Note that this stream does
1284 *not* copy the data that it is remembering; it just saves
1287 svn_stream_t
*stream
;
1288 const char *data
[SVN_DIFF__UNIFIED_CONTEXT_SIZE
];
1289 apr_size_t len
[SVN_DIFF__UNIFIED_CONTEXT_SIZE
];
1290 apr_size_t next_slot
;
1291 apr_size_t total_written
;
1295 static svn_error_t
*
1296 context_saver_stream_write(void *baton
,
1300 context_saver_t
*cs
= baton
;
1301 cs
->data
[cs
->next_slot
] = data
;
1302 cs
->len
[cs
->next_slot
] = *len
;
1303 cs
->next_slot
= (cs
->next_slot
+ 1) % SVN_DIFF__UNIFIED_CONTEXT_SIZE
;
1304 cs
->total_written
++;
1305 return SVN_NO_ERROR
;
1308 typedef struct svn_diff3__file_output_baton_t
1310 svn_stream_t
*output_stream
;
1312 const char *path
[3];
1314 apr_off_t current_line
[3];
1320 /* The following four members are in the encoding used for the output. */
1321 const char *conflict_modified
;
1322 const char *conflict_original
;
1323 const char *conflict_separator
;
1324 const char *conflict_latest
;
1326 const char *marker_eol
;
1328 svn_diff_conflict_display_style_t conflict_style
;
1330 /* The rest of the fields are for
1331 svn_diff_conflict_display_only_conflicts only. Note that for
1332 these batons, OUTPUT_STREAM is either CONTEXT_SAVER->STREAM or
1333 (soon after a conflict) a "trailing context stream", never the
1334 actual output stream.*/
1335 /* The actual output stream. */
1336 svn_stream_t
*real_output_stream
;
1337 context_saver_t
*context_saver
;
1338 /* Used to allocate context_saver and trailing context streams, and
1339 for some printfs. */
1341 } svn_diff3__file_output_baton_t
;
1343 static svn_error_t
*
1344 flush_context_saver(context_saver_t
*cs
,
1345 svn_stream_t
*output_stream
)
1348 for (i
= 0; i
< SVN_DIFF__UNIFIED_CONTEXT_SIZE
; i
++)
1350 int slot
= (i
+ cs
->next_slot
) % SVN_DIFF__UNIFIED_CONTEXT_SIZE
;
1353 apr_size_t len
= cs
->len
[slot
];
1354 SVN_ERR(svn_stream_write(output_stream
, cs
->data
[slot
], &len
));
1357 return SVN_NO_ERROR
;
1361 make_context_saver(svn_diff3__file_output_baton_t
*fob
)
1363 context_saver_t
*cs
;
1365 svn_pool_clear(fob
->pool
);
1366 cs
= apr_pcalloc(fob
->pool
, sizeof(*cs
));
1367 cs
->stream
= svn_stream_empty(fob
->pool
);
1368 svn_stream_set_baton(cs
->stream
, cs
);
1369 svn_stream_set_write(cs
->stream
, context_saver_stream_write
);
1370 fob
->context_saver
= cs
;
1371 fob
->output_stream
= cs
->stream
;
1375 /* A stream which prints SVN_DIFF__UNIFIED_CONTEXT_SIZE lines to
1376 BATON->REAL_OUTPUT_STREAM, and then changes BATON->OUTPUT_STREAM to
1377 a context_saver; used for *trailing* context. */
1379 struct trailing_context_printer
{
1380 apr_size_t lines_to_print
;
1381 svn_diff3__file_output_baton_t
*fob
;
1386 static svn_error_t
*
1387 trailing_context_printer_write(void *baton
,
1391 struct trailing_context_printer
*tcp
= baton
;
1392 SVN_ERR_ASSERT(tcp
->lines_to_print
> 0);
1393 SVN_ERR(svn_stream_write(tcp
->fob
->real_output_stream
, data
, len
));
1394 tcp
->lines_to_print
--;
1395 if (tcp
->lines_to_print
== 0)
1396 make_context_saver(tcp
->fob
);
1397 return SVN_NO_ERROR
;
1402 make_trailing_context_printer(svn_diff3__file_output_baton_t
*btn
)
1404 struct trailing_context_printer
*tcp
;
1407 svn_pool_clear(btn
->pool
);
1409 tcp
= apr_pcalloc(btn
->pool
, sizeof(*tcp
));
1410 tcp
->lines_to_print
= SVN_DIFF__UNIFIED_CONTEXT_SIZE
;
1412 s
= svn_stream_empty(btn
->pool
);
1413 svn_stream_set_baton(s
, tcp
);
1414 svn_stream_set_write(s
, trailing_context_printer_write
);
1415 btn
->output_stream
= s
;
1420 typedef enum svn_diff3__file_output_type_e
1422 svn_diff3__file_output_skip
,
1423 svn_diff3__file_output_normal
1424 } svn_diff3__file_output_type_e
;
1427 static svn_error_t
*
1428 output_line(svn_diff3__file_output_baton_t
*baton
,
1429 svn_diff3__file_output_type_e type
, int idx
)
1436 curp
= baton
->curp
[idx
];
1437 endp
= baton
->endp
[idx
];
1439 /* Lazily update the current line even if we're at EOF.
1441 baton
->current_line
[idx
]++;
1444 return SVN_NO_ERROR
;
1446 eol
= find_eol_start(curp
, endp
- curp
);
1451 svn_boolean_t had_cr
= (*eol
== '\r');
1453 if (had_cr
&& eol
!= endp
&& *eol
== '\n')
1457 if (type
!= svn_diff3__file_output_skip
)
1460 /* Note that the trailing context printer assumes that
1461 svn_stream_write is called exactly once per line. */
1462 SVN_ERR(svn_stream_write(baton
->output_stream
, curp
, &len
));
1465 baton
->curp
[idx
] = eol
;
1467 return SVN_NO_ERROR
;
1470 static svn_error_t
*
1471 output_marker_eol(svn_diff3__file_output_baton_t
*btn
)
1473 apr_size_t len
= strlen(btn
->marker_eol
);
1474 return svn_stream_write(btn
->output_stream
, btn
->marker_eol
, &len
);
1477 static svn_error_t
*
1478 output_hunk(void *baton
, int idx
, apr_off_t target_line
,
1479 apr_off_t target_length
)
1481 svn_diff3__file_output_baton_t
*output_baton
= baton
;
1483 /* Skip lines until we are at the start of the changed range */
1484 while (output_baton
->current_line
[idx
] < target_line
)
1486 SVN_ERR(output_line(output_baton
, svn_diff3__file_output_skip
, idx
));
1489 target_line
+= target_length
;
1491 while (output_baton
->current_line
[idx
] < target_line
)
1493 SVN_ERR(output_line(output_baton
, svn_diff3__file_output_normal
, idx
));
1496 return SVN_NO_ERROR
;
1499 static svn_error_t
*
1500 output_common(void *baton
, apr_off_t original_start
, apr_off_t original_length
,
1501 apr_off_t modified_start
, apr_off_t modified_length
,
1502 apr_off_t latest_start
, apr_off_t latest_length
)
1504 return output_hunk(baton
, 1, modified_start
, modified_length
);
1507 static svn_error_t
*
1508 output_diff_modified(void *baton
,
1509 apr_off_t original_start
, apr_off_t original_length
,
1510 apr_off_t modified_start
, apr_off_t modified_length
,
1511 apr_off_t latest_start
, apr_off_t latest_length
)
1513 return output_hunk(baton
, 1, modified_start
, modified_length
);
1516 static svn_error_t
*
1517 output_diff_latest(void *baton
,
1518 apr_off_t original_start
, apr_off_t original_length
,
1519 apr_off_t modified_start
, apr_off_t modified_length
,
1520 apr_off_t latest_start
, apr_off_t latest_length
)
1522 return output_hunk(baton
, 2, latest_start
, latest_length
);
1525 static svn_error_t
*
1526 output_conflict(void *baton
,
1527 apr_off_t original_start
, apr_off_t original_length
,
1528 apr_off_t modified_start
, apr_off_t modified_length
,
1529 apr_off_t latest_start
, apr_off_t latest_length
,
1532 static const svn_diff_output_fns_t svn_diff3__file_output_vtable
=
1535 output_diff_modified
,
1537 output_diff_modified
, /* output_diff_common */
1543 static svn_error_t
*
1544 output_conflict_with_context(svn_diff3__file_output_baton_t
*btn
,
1545 apr_off_t original_start
,
1546 apr_off_t original_length
,
1547 apr_off_t modified_start
,
1548 apr_off_t modified_length
,
1549 apr_off_t latest_start
,
1550 apr_off_t latest_length
)
1552 /* Are we currently saving starting context (as opposed to printing
1553 trailing context)? If so, flush it. */
1554 if (btn
->output_stream
== btn
->context_saver
->stream
)
1556 if (btn
->context_saver
->total_written
> SVN_DIFF__UNIFIED_CONTEXT_SIZE
)
1557 SVN_ERR(svn_stream_printf(btn
->real_output_stream
, btn
->pool
, "@@\n"));
1558 SVN_ERR(flush_context_saver(btn
->context_saver
, btn
->real_output_stream
));
1561 /* Print to the real output stream. */
1562 btn
->output_stream
= btn
->real_output_stream
;
1564 /* Output the conflict itself. */
1565 SVN_ERR(svn_stream_printf(btn
->output_stream
, btn
->pool
,
1566 (modified_length
== 1
1567 ? "%s (%" APR_OFF_T_FMT
")"
1568 : "%s (%" APR_OFF_T_FMT
",%" APR_OFF_T_FMT
")"),
1569 btn
->conflict_modified
,
1570 modified_start
+ 1, modified_length
));
1571 SVN_ERR(output_marker_eol(btn
));
1572 SVN_ERR(output_hunk(btn
, 1/*modified*/, modified_start
, modified_length
));
1574 SVN_ERR(svn_stream_printf(btn
->output_stream
, btn
->pool
,
1575 (original_length
== 1
1576 ? "%s (%" APR_OFF_T_FMT
")"
1577 : "%s (%" APR_OFF_T_FMT
",%" APR_OFF_T_FMT
")"),
1578 btn
->conflict_original
,
1579 original_start
+ 1, original_length
));
1580 SVN_ERR(output_marker_eol(btn
));
1581 SVN_ERR(output_hunk(btn
, 0/*original*/, original_start
, original_length
));
1583 SVN_ERR(svn_stream_printf(btn
->output_stream
, btn
->pool
,
1584 "%s%s", btn
->conflict_separator
, btn
->marker_eol
));
1585 SVN_ERR(output_hunk(btn
, 2/*latest*/, latest_start
, latest_length
));
1586 SVN_ERR(svn_stream_printf(btn
->output_stream
, btn
->pool
,
1588 ? "%s (%" APR_OFF_T_FMT
")"
1589 : "%s (%" APR_OFF_T_FMT
",%" APR_OFF_T_FMT
")"),
1590 btn
->conflict_latest
,
1591 latest_start
+ 1, latest_length
));
1592 SVN_ERR(output_marker_eol(btn
));
1594 /* Go into print-trailing-context mode instead. */
1595 make_trailing_context_printer(btn
);
1597 return SVN_NO_ERROR
;
1601 static svn_error_t
*
1602 output_conflict(void *baton
,
1603 apr_off_t original_start
, apr_off_t original_length
,
1604 apr_off_t modified_start
, apr_off_t modified_length
,
1605 apr_off_t latest_start
, apr_off_t latest_length
,
1608 svn_diff3__file_output_baton_t
*file_baton
= baton
;
1611 svn_diff_conflict_display_style_t style
= file_baton
->conflict_style
;
1613 if (style
== svn_diff_conflict_display_only_conflicts
)
1614 return output_conflict_with_context(file_baton
,
1615 original_start
, original_length
,
1616 modified_start
, modified_length
,
1617 latest_start
, latest_length
);
1619 if (style
== svn_diff_conflict_display_resolved_modified_latest
)
1622 return svn_diff_output(diff
, baton
,
1623 &svn_diff3__file_output_vtable
);
1625 style
= svn_diff_conflict_display_modified_latest
;
1628 if (style
== svn_diff_conflict_display_modified_latest
||
1629 style
== svn_diff_conflict_display_modified_original_latest
)
1631 len
= strlen(file_baton
->conflict_modified
);
1632 SVN_ERR(svn_stream_write(file_baton
->output_stream
,
1633 file_baton
->conflict_modified
,
1635 SVN_ERR(output_marker_eol(file_baton
));
1637 SVN_ERR(output_hunk(baton
, 1, modified_start
, modified_length
));
1639 if (style
== svn_diff_conflict_display_modified_original_latest
)
1641 len
= strlen(file_baton
->conflict_original
);
1642 SVN_ERR(svn_stream_write(file_baton
->output_stream
,
1643 file_baton
->conflict_original
, &len
));
1644 SVN_ERR(output_marker_eol(file_baton
));
1645 SVN_ERR(output_hunk(baton
, 0, original_start
, original_length
));
1648 len
= strlen(file_baton
->conflict_separator
);
1649 SVN_ERR(svn_stream_write(file_baton
->output_stream
,
1650 file_baton
->conflict_separator
, &len
));
1651 SVN_ERR(output_marker_eol(file_baton
));
1653 SVN_ERR(output_hunk(baton
, 2, latest_start
, latest_length
));
1655 len
= strlen(file_baton
->conflict_latest
);
1656 SVN_ERR(svn_stream_write(file_baton
->output_stream
,
1657 file_baton
->conflict_latest
, &len
));
1658 SVN_ERR(output_marker_eol(file_baton
));
1660 else if (style
== svn_diff_conflict_display_modified
)
1661 SVN_ERR(output_hunk(baton
, 1, modified_start
, modified_length
));
1662 else if (style
== svn_diff_conflict_display_latest
)
1663 SVN_ERR(output_hunk(baton
, 2, latest_start
, latest_length
));
1664 else /* unknown style */
1665 SVN_ERR_MALFUNCTION();
1667 return SVN_NO_ERROR
;
1671 /* Return the first eol marker found in [BUF, ENDP) as a
1672 * NUL-terminated string, or NULL if no eol marker is found.
1674 * If the last valid character of BUF is the first byte of a
1675 * potentially two-byte eol sequence, just return "\r", that is,
1676 * assume BUF represents a CR-only file. This is correct for callers
1677 * that pass an entire file at once, and is no more likely to be
1678 * incorrect than correct for any caller that doesn't.
1681 detect_eol(char *buf
, char *endp
)
1683 const char *eol
= find_eol_start(buf
, endp
- buf
);
1689 /* We found a CR. */
1691 if (eol
== endp
|| *eol
!= '\n')
1700 svn_diff_file_output_merge2(svn_stream_t
*output_stream
,
1702 const char *original_path
,
1703 const char *modified_path
,
1704 const char *latest_path
,
1705 const char *conflict_original
,
1706 const char *conflict_modified
,
1707 const char *conflict_latest
,
1708 const char *conflict_separator
,
1709 svn_diff_conflict_display_style_t style
,
1712 svn_diff3__file_output_baton_t baton
;
1713 apr_file_t
*file
[3];
1717 apr_mmap_t
*mm
[3] = { 0 };
1718 #endif /* APR_HAS_MMAP */
1720 svn_boolean_t conflicts_only
=
1721 (style
== svn_diff_conflict_display_only_conflicts
);
1723 memset(&baton
, 0, sizeof(baton
));
1726 baton
.pool
= svn_pool_create(pool
);
1727 make_context_saver(&baton
);
1728 baton
.real_output_stream
= output_stream
;
1731 baton
.output_stream
= output_stream
;
1732 baton
.path
[0] = original_path
;
1733 baton
.path
[1] = modified_path
;
1734 baton
.path
[2] = latest_path
;
1735 SVN_ERR(svn_utf_cstring_from_utf8(&baton
.conflict_modified
,
1736 conflict_modified
? conflict_modified
1737 : apr_psprintf(pool
, "<<<<<<< %s",
1740 SVN_ERR(svn_utf_cstring_from_utf8(&baton
.conflict_original
,
1741 conflict_original
? conflict_original
1742 : apr_psprintf(pool
, "||||||| %s",
1745 SVN_ERR(svn_utf_cstring_from_utf8(&baton
.conflict_separator
,
1746 conflict_separator
? conflict_separator
1747 : "=======", pool
));
1748 SVN_ERR(svn_utf_cstring_from_utf8(&baton
.conflict_latest
,
1749 conflict_latest
? conflict_latest
1750 : apr_psprintf(pool
, ">>>>>>> %s",
1754 baton
.conflict_style
= style
;
1756 for (idx
= 0; idx
< 3; idx
++)
1758 SVN_ERR(map_or_read_file(&file
[idx
],
1760 &baton
.buffer
[idx
], &size
,
1761 baton
.path
[idx
], pool
));
1763 baton
.curp
[idx
] = baton
.buffer
[idx
];
1764 baton
.endp
[idx
] = baton
.buffer
[idx
];
1766 if (baton
.endp
[idx
])
1767 baton
.endp
[idx
] += size
;
1770 /* Check what eol marker we should use for conflict markers.
1771 We use the eol marker of the modified file and fall back on the
1772 platform's eol marker if that file doesn't contain any newlines. */
1773 eol
= detect_eol(baton
.buffer
[1], baton
.endp
[1]);
1776 baton
.marker_eol
= eol
;
1778 SVN_ERR(svn_diff_output(diff
, &baton
,
1779 &svn_diff3__file_output_vtable
));
1781 for (idx
= 0; idx
< 3; idx
++)
1786 apr_status_t rv
= apr_mmap_delete(mm
[idx
]);
1787 if (rv
!= APR_SUCCESS
)
1789 return svn_error_wrap_apr(rv
, _("Failed to delete mmap '%s'"),
1793 #endif /* APR_HAS_MMAP */
1797 SVN_ERR(svn_io_file_close(file
[idx
], pool
));
1802 svn_pool_destroy(baton
.pool
);
1804 return SVN_NO_ERROR
;
1809 svn_diff_file_output_merge(svn_stream_t
*output_stream
,
1811 const char *original_path
,
1812 const char *modified_path
,
1813 const char *latest_path
,
1814 const char *conflict_original
,
1815 const char *conflict_modified
,
1816 const char *conflict_latest
,
1817 const char *conflict_separator
,
1818 svn_boolean_t display_original_in_conflict
,
1819 svn_boolean_t display_resolved_conflicts
,
1822 svn_diff_conflict_display_style_t style
=
1823 svn_diff_conflict_display_modified_latest
;
1825 if (display_resolved_conflicts
)
1826 style
= svn_diff_conflict_display_resolved_modified_latest
;
1828 if (display_original_in_conflict
)
1829 style
= svn_diff_conflict_display_modified_original_latest
;
1831 return svn_diff_file_output_merge2(output_stream
,