2 * diff_file.c : routines for doing diffs on files
4 * ====================================================================
5 * Licensed to the Apache Software Foundation (ASF) under one
6 * or more contributor license agreements. See the NOTICE file
7 * distributed with this work for additional information
8 * regarding copyright ownership. The ASF licenses this file
9 * to you under the Apache License, Version 2.0 (the
10 * "License"); you may not use this file except in compliance
11 * with the License. You may obtain a copy of the License at
13 * http://www.apache.org/licenses/LICENSE-2.0
15 * Unless required by applicable law or agreed to in writing,
16 * software distributed under the License is distributed on an
17 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
18 * KIND, either express or implied. See the License for the
19 * specific language governing permissions and limitations
21 * ====================================================================
26 #include <apr_pools.h>
27 #include <apr_general.h>
28 #include <apr_file_io.h>
29 #include <apr_file_info.h>
32 #include <apr_getopt.h>
34 #include "svn_error.h"
36 #include "svn_types.h"
37 #include "svn_string.h"
38 #include "svn_subst.h"
41 #include "svn_pools.h"
43 #include "svn_private_config.h"
45 #include "svn_ctype.h"
47 #include "private/svn_utf_private.h"
48 #include "private/svn_eol_private.h"
49 #include "private/svn_dep_compat.h"
50 #include "private/svn_adler32.h"
51 #include "private/svn_diff_private.h"
53 /* A token, i.e. a line read from a file. */
54 typedef struct svn_diff__file_token_t
56 /* Next token in free list. */
57 struct svn_diff__file_token_t
*next
;
58 svn_diff_datasource_e datasource
;
59 /* Offset in the datasource. */
61 /* Offset of the normalized token (may skip leading whitespace) */
62 apr_off_t norm_offset
;
63 /* Total length - before normalization. */
65 /* Total length - after normalization. */
67 } svn_diff__file_token_t
;
70 typedef struct svn_diff__file_baton_t
72 const svn_diff_file_options_t
*options
;
75 const char *path
; /* path to this file, absolute or relative to CWD */
77 /* All the following fields are active while this datasource is open */
78 apr_file_t
*file
; /* handle of this file */
79 apr_off_t size
; /* total raw size in bytes of this file */
81 /* The current chunk: CHUNK_SIZE bytes except for the last chunk. */
82 int chunk
; /* the current chunk number, zero-based */
83 char *buffer
; /* a buffer containing the current chunk */
84 char *curp
; /* current position in the current chunk */
85 char *endp
; /* next memory address after the current chunk */
87 svn_diff__normalize_state_t normalize_state
;
89 /* Where the identical suffix starts in this datasource */
90 int suffix_start_chunk
;
91 apr_off_t suffix_offset_in_chunk
;
94 /* List of free tokens that may be reused. */
95 svn_diff__file_token_t
*tokens
;
98 } svn_diff__file_baton_t
;
101 datasource_to_index(svn_diff_datasource_e datasource
)
105 case svn_diff_datasource_original
:
108 case svn_diff_datasource_modified
:
111 case svn_diff_datasource_latest
:
114 case svn_diff_datasource_ancestor
:
121 /* Files are read in chunks of 128k. There is no support for this number
122 * whatsoever. If there is a number someone comes up with that has some
123 * argumentation, let's use that.
125 /* If you change this number, update test_norm_offset(),
126 * test_identical_suffix() and and test_token_compare() in diff-diff3-test.c.
128 #define CHUNK_SHIFT 17
129 #define CHUNK_SIZE (1 << CHUNK_SHIFT)
131 #define chunk_to_offset(chunk) ((chunk) << CHUNK_SHIFT)
132 #define offset_to_chunk(offset) ((offset) >> CHUNK_SHIFT)
133 #define offset_in_chunk(offset) ((offset) & (CHUNK_SIZE - 1))
136 /* Read a chunk from a FILE into BUFFER, starting from OFFSET, going for
137 * *LENGTH. The actual bytes read are stored in *LENGTH on return.
139 static APR_INLINE svn_error_t
*
140 read_chunk(apr_file_t
*file
, const char *path
,
141 char *buffer
, apr_off_t length
,
142 apr_off_t offset
, apr_pool_t
*pool
)
144 /* XXX: The final offset may not be the one we asked for.
147 SVN_ERR(svn_io_file_seek(file
, APR_SET
, &offset
, pool
));
148 return svn_io_file_read_full2(file
, buffer
, (apr_size_t
) length
,
153 /* Map or read a file at PATH. *BUFFER will point to the file
154 * contents; if the file was mapped, *FILE and *MM will contain the
155 * mmap context; otherwise they will be NULL. SIZE will contain the
156 * file size. Allocate from POOL.
159 #define MMAP_T_PARAM(NAME) apr_mmap_t **NAME,
160 #define MMAP_T_ARG(NAME) &(NAME),
162 #define MMAP_T_PARAM(NAME)
163 #define MMAP_T_ARG(NAME)
167 map_or_read_file(apr_file_t
**file
,
169 char **buffer
, apr_off_t
*size
,
170 const char *path
, apr_pool_t
*pool
)
177 SVN_ERR(svn_io_file_open(file
, path
, APR_READ
, APR_OS_DEFAULT
, pool
));
178 SVN_ERR(svn_io_file_info_get(&finfo
, APR_FINFO_SIZE
, *file
, pool
));
181 if (finfo
.size
> APR_MMAP_THRESHOLD
)
183 rv
= apr_mmap_create(mm
, *file
, 0, (apr_size_t
) finfo
.size
,
184 APR_MMAP_READ
, pool
);
185 if (rv
== APR_SUCCESS
)
190 /* On failure we just fall through and try reading the file into
194 #endif /* APR_HAS_MMAP */
196 if (*buffer
== NULL
&& finfo
.size
> 0)
198 *buffer
= apr_palloc(pool
, (apr_size_t
) finfo
.size
);
200 SVN_ERR(svn_io_file_read_full2(*file
, *buffer
, (apr_size_t
) finfo
.size
,
203 /* Since we have the entire contents of the file we can
206 SVN_ERR(svn_io_file_close(*file
, pool
));
217 /* For all files in the FILE array, increment the curp pointer. If a file
218 * points before the beginning of file, let it point at the first byte again.
219 * If the end of the current chunk is reached, read the next chunk in the
220 * buffer and point curp to the start of the chunk. If EOF is reached, set
221 * curp equal to endp to indicate EOF. */
222 #define INCREMENT_POINTERS(all_files, files_len, pool) \
224 apr_size_t svn_macro__i; \
226 for (svn_macro__i = 0; svn_macro__i < (files_len); svn_macro__i++) \
228 if ((all_files)[svn_macro__i].curp < (all_files)[svn_macro__i].endp - 1)\
229 (all_files)[svn_macro__i].curp++; \
231 SVN_ERR(increment_chunk(&(all_files)[svn_macro__i], (pool))); \
236 /* For all files in the FILE array, decrement the curp pointer. If the
237 * start of a chunk is reached, read the previous chunk in the buffer and
238 * point curp to the last byte of the chunk. If the beginning of a FILE is
239 * reached, set chunk to -1 to indicate BOF. */
240 #define DECREMENT_POINTERS(all_files, files_len, pool) \
242 apr_size_t svn_macro__i; \
244 for (svn_macro__i = 0; svn_macro__i < (files_len); svn_macro__i++) \
246 if ((all_files)[svn_macro__i].curp > (all_files)[svn_macro__i].buffer) \
247 (all_files)[svn_macro__i].curp--; \
249 SVN_ERR(decrement_chunk(&(all_files)[svn_macro__i], (pool))); \
255 increment_chunk(struct file_info
*file
, apr_pool_t
*pool
)
258 apr_off_t last_chunk
= offset_to_chunk(file
->size
);
260 if (file
->chunk
== -1)
262 /* We are at BOF (Beginning Of File). Point to first chunk/byte again. */
264 file
->curp
= file
->buffer
;
266 else if (file
->chunk
== last_chunk
)
268 /* We are at the last chunk. Indicate EOF by setting curp == endp. */
269 file
->curp
= file
->endp
;
273 /* There are still chunks left. Read next chunk and reset pointers. */
275 length
= file
->chunk
== last_chunk
?
276 offset_in_chunk(file
->size
) : CHUNK_SIZE
;
277 SVN_ERR(read_chunk(file
->file
, file
->path
, file
->buffer
,
278 length
, chunk_to_offset(file
->chunk
),
280 file
->endp
= file
->buffer
+ length
;
281 file
->curp
= file
->buffer
;
289 decrement_chunk(struct file_info
*file
, apr_pool_t
*pool
)
291 if (file
->chunk
== 0)
293 /* We are already at the first chunk. Indicate BOF (Beginning Of File)
294 by setting chunk = -1 and curp = endp - 1. Both conditions are
295 important. They help the increment step to catch the BOF situation
296 in an efficient way. */
298 file
->curp
= file
->endp
- 1;
302 /* Read previous chunk and reset pointers. */
304 SVN_ERR(read_chunk(file
->file
, file
->path
, file
->buffer
,
305 CHUNK_SIZE
, chunk_to_offset(file
->chunk
),
307 file
->endp
= file
->buffer
+ CHUNK_SIZE
;
308 file
->curp
= file
->endp
- 1;
315 /* Check whether one of the FILEs has its pointers 'before' the beginning of
316 * the file (this can happen while scanning backwards). This is the case if
317 * one of them has chunk == -1. */
319 is_one_at_bof(struct file_info file
[], apr_size_t file_len
)
323 for (i
= 0; i
< file_len
; i
++)
324 if (file
[i
].chunk
== -1)
330 /* Check whether one of the FILEs has its pointers at EOF (this is the case if
331 * one of them has curp == endp (this can only happen at the last chunk)) */
333 is_one_at_eof(struct file_info file
[], apr_size_t file_len
)
337 for (i
= 0; i
< file_len
; i
++)
338 if (file
[i
].curp
== file
[i
].endp
)
344 /* Quickly determine whether there is a eol char in CHUNK.
345 * (mainly copy-n-paste from eol.c#svn_eol__find_eol_start).
348 #if SVN_UNALIGNED_ACCESS_IS_OK
349 static svn_boolean_t
contains_eol(apr_uintptr_t chunk
)
351 apr_uintptr_t r_test
= chunk
^ SVN__R_MASK
;
352 apr_uintptr_t n_test
= chunk
^ SVN__N_MASK
;
354 r_test
|= (r_test
& SVN__LOWER_7BITS_SET
) + SVN__LOWER_7BITS_SET
;
355 n_test
|= (n_test
& SVN__LOWER_7BITS_SET
) + SVN__LOWER_7BITS_SET
;
357 return (r_test
& n_test
& SVN__BIT_7_SET
) != SVN__BIT_7_SET
;
361 /* Find the prefix which is identical between all elements of the FILE array.
362 * Return the number of prefix lines in PREFIX_LINES. REACHED_ONE_EOF will be
363 * set to TRUE if one of the FILEs reached its end while scanning prefix,
364 * i.e. at least one file consisted entirely of prefix. Otherwise,
365 * REACHED_ONE_EOF is set to FALSE.
367 * After this function is finished, the buffers, chunks, curp's and endp's
368 * of the FILEs are set to point at the first byte after the prefix. */
370 find_identical_prefix(svn_boolean_t
*reached_one_eof
, apr_off_t
*prefix_lines
,
371 struct file_info file
[], apr_size_t file_len
,
374 svn_boolean_t had_cr
= FALSE
;
375 svn_boolean_t is_match
;
379 *reached_one_eof
= FALSE
;
381 for (i
= 1, is_match
= TRUE
; i
< file_len
; i
++)
382 is_match
= is_match
&& *file
[0].curp
== *file
[i
].curp
;
385 #if SVN_UNALIGNED_ACCESS_IS_OK
386 apr_ssize_t max_delta
, delta
;
387 #endif /* SVN_UNALIGNED_ACCESS_IS_OK */
389 /* ### TODO: see if we can take advantage of
390 diff options like ignore_eol_style or ignore_space. */
391 /* check for eol, and count */
392 if (*file
[0].curp
== '\r')
397 else if (*file
[0].curp
== '\n' && !had_cr
)
406 INCREMENT_POINTERS(file
, file_len
, pool
);
408 #if SVN_UNALIGNED_ACCESS_IS_OK
410 /* Try to advance as far as possible with machine-word granularity.
411 * Determine how far we may advance with chunky ops without reaching
412 * endp for any of the files.
413 * Signedness is important here if curp gets close to endp.
415 max_delta
= file
[0].endp
- file
[0].curp
- sizeof(apr_uintptr_t
);
416 for (i
= 1; i
< file_len
; i
++)
418 delta
= file
[i
].endp
- file
[i
].curp
- sizeof(apr_uintptr_t
);
419 if (delta
< max_delta
)
424 for (delta
= 0; delta
< max_delta
; delta
+= sizeof(apr_uintptr_t
))
426 apr_uintptr_t chunk
= *(const apr_uintptr_t
*)(file
[0].curp
+ delta
);
427 if (contains_eol(chunk
))
430 for (i
= 1; i
< file_len
; i
++)
431 if (chunk
!= *(const apr_uintptr_t
*)(file
[i
].curp
+ delta
))
443 /* We either found a mismatch or an EOL at or shortly behind curp+delta
444 * or we cannot proceed with chunky ops without exceeding endp.
445 * In any way, everything up to curp + delta is equal and not an EOL.
447 for (i
= 0; i
< file_len
; i
++)
448 file
[i
].curp
+= delta
;
450 /* Skipped data without EOL markers, so last char was not a CR. */
455 *reached_one_eof
= is_one_at_eof(file
, file_len
);
456 if (*reached_one_eof
)
459 for (i
= 1, is_match
= TRUE
; i
< file_len
; i
++)
460 is_match
= is_match
&& *file
[0].curp
== *file
[i
].curp
;
465 /* Check if we ended in the middle of a \r\n for one file, but \r for
466 another. If so, back up one byte, so the next loop will back up
467 the entire line. Also decrement lines, since we counted one
468 too many for the \r. */
469 svn_boolean_t ended_at_nonmatching_newline
= FALSE
;
470 for (i
= 0; i
< file_len
; i
++)
471 if (file
[i
].curp
< file
[i
].endp
)
472 ended_at_nonmatching_newline
= ended_at_nonmatching_newline
473 || *file
[i
].curp
== '\n';
474 if (ended_at_nonmatching_newline
)
477 DECREMENT_POINTERS(file
, file_len
, pool
);
481 /* Back up one byte, so we point at the last identical byte */
482 DECREMENT_POINTERS(file
, file_len
, pool
);
484 /* Back up to the last eol sequence (\n, \r\n or \r) */
485 while (!is_one_at_bof(file
, file_len
) &&
486 *file
[0].curp
!= '\n' && *file
[0].curp
!= '\r')
487 DECREMENT_POINTERS(file
, file_len
, pool
);
489 /* Slide one byte forward, to point past the eol sequence */
490 INCREMENT_POINTERS(file
, file_len
, pool
);
492 *prefix_lines
= lines
;
498 /* The number of identical suffix lines to keep with the middle section. These
499 * lines are not eliminated as suffix, and can be picked up by the token
500 * parsing and lcs steps. This is mainly for backward compatibility with
501 * the previous diff (and blame) output (if there are multiple diff solutions,
502 * our lcs algorithm prefers taking common lines from the start, rather than
503 * from the end. By giving it back some suffix lines, we give it some wiggle
504 * room to find the exact same diff as before).
506 * The number 50 is more or less arbitrary, based on some real-world tests
507 * with big files (and then doubling the required number to be on the safe
508 * side). This has a negligible effect on the power of the optimization. */
509 /* If you change this number, update test_identical_suffix() in diff-diff3-test.c */
510 #ifndef SUFFIX_LINES_TO_KEEP
511 #define SUFFIX_LINES_TO_KEEP 50
514 /* Find the suffix which is identical between all elements of the FILE array.
515 * Return the number of suffix lines in SUFFIX_LINES.
517 * Before this function is called the FILEs' pointers and chunks should be
518 * positioned right after the identical prefix (which is the case after
519 * find_identical_prefix), so we can determine where suffix scanning should
520 * ultimately stop. */
522 find_identical_suffix(apr_off_t
*suffix_lines
, struct file_info file
[],
523 apr_size_t file_len
, apr_pool_t
*pool
)
525 struct file_info file_for_suffix
[4] = { { 0 } };
527 apr_off_t suffix_min_chunk0
;
528 apr_off_t suffix_min_offset0
;
529 apr_off_t min_file_size
;
530 int suffix_lines_to_keep
= SUFFIX_LINES_TO_KEEP
;
531 svn_boolean_t is_match
;
533 svn_boolean_t had_cr
;
534 svn_boolean_t had_nl
;
537 /* Initialize file_for_suffix[].
538 Read last chunk, position curp at last byte. */
539 for (i
= 0; i
< file_len
; i
++)
541 file_for_suffix
[i
].path
= file
[i
].path
;
542 file_for_suffix
[i
].file
= file
[i
].file
;
543 file_for_suffix
[i
].size
= file
[i
].size
;
544 file_for_suffix
[i
].chunk
=
545 (int) offset_to_chunk(file_for_suffix
[i
].size
); /* last chunk */
546 length
[i
] = offset_in_chunk(file_for_suffix
[i
].size
);
549 /* last chunk is an empty chunk -> start at next-to-last chunk */
550 file_for_suffix
[i
].chunk
= file_for_suffix
[i
].chunk
- 1;
551 length
[i
] = CHUNK_SIZE
;
554 if (file_for_suffix
[i
].chunk
== file
[i
].chunk
)
556 /* Prefix ended in last chunk, so we can reuse the prefix buffer */
557 file_for_suffix
[i
].buffer
= file
[i
].buffer
;
561 /* There is at least more than 1 chunk,
562 so allocate full chunk size buffer */
563 file_for_suffix
[i
].buffer
= apr_palloc(pool
, CHUNK_SIZE
);
564 SVN_ERR(read_chunk(file_for_suffix
[i
].file
, file_for_suffix
[i
].path
,
565 file_for_suffix
[i
].buffer
, length
[i
],
566 chunk_to_offset(file_for_suffix
[i
].chunk
),
569 file_for_suffix
[i
].endp
= file_for_suffix
[i
].buffer
+ length
[i
];
570 file_for_suffix
[i
].curp
= file_for_suffix
[i
].endp
- 1;
573 /* Get the chunk and pointer offset (for file[0]) at which we should stop
574 scanning backward for the identical suffix, i.e. when we reach prefix. */
575 suffix_min_chunk0
= file
[0].chunk
;
576 suffix_min_offset0
= file
[0].curp
- file
[0].buffer
;
578 /* Compensate if other files are smaller than file[0] */
579 for (i
= 1, min_file_size
= file
[0].size
; i
< file_len
; i
++)
580 if (file
[i
].size
< min_file_size
)
581 min_file_size
= file
[i
].size
;
582 if (file
[0].size
> min_file_size
)
584 suffix_min_chunk0
+= (file
[0].size
- min_file_size
) / CHUNK_SIZE
;
585 suffix_min_offset0
+= (file
[0].size
- min_file_size
) % CHUNK_SIZE
;
588 /* Scan backwards until mismatch or until we reach the prefix. */
589 for (i
= 1, is_match
= TRUE
; i
< file_len
; i
++)
591 && *file_for_suffix
[0].curp
== *file_for_suffix
[i
].curp
;
592 if (is_match
&& *file_for_suffix
[0].curp
!= '\r'
593 && *file_for_suffix
[0].curp
!= '\n')
594 /* Count an extra line for the last line not ending in an eol. */
600 svn_boolean_t reached_prefix
;
601 #if SVN_UNALIGNED_ACCESS_IS_OK
602 /* Initialize the minimum pointer positions. */
603 const char *min_curp
[4];
604 svn_boolean_t can_read_word
;
605 #endif /* SVN_UNALIGNED_ACCESS_IS_OK */
607 /* ### TODO: see if we can take advantage of
608 diff options like ignore_eol_style or ignore_space. */
609 /* check for eol, and count */
610 if (*file_for_suffix
[0].curp
== '\n')
615 else if (*file_for_suffix
[0].curp
== '\r' && !had_nl
)
624 DECREMENT_POINTERS(file_for_suffix
, file_len
, pool
);
626 #if SVN_UNALIGNED_ACCESS_IS_OK
627 for (i
= 0; i
< file_len
; i
++)
628 min_curp
[i
] = file_for_suffix
[i
].buffer
;
630 /* If we are in the same chunk that contains the last part of the common
631 prefix, use the min_curp[0] pointer to make sure we don't get a
632 suffix that overlaps the already determined common prefix. */
633 if (file_for_suffix
[0].chunk
== suffix_min_chunk0
)
634 min_curp
[0] += suffix_min_offset0
;
636 /* Scan quickly by reading with machine-word granularity. */
637 for (i
= 0, can_read_word
= TRUE
; i
< file_len
; i
++)
638 can_read_word
= can_read_word
639 && ( (file_for_suffix
[i
].curp
+ 1
640 - sizeof(apr_uintptr_t
))
642 while (can_read_word
)
646 /* For each file curp is positioned at the current byte, but we
647 want to examine the current byte and the ones before the current
648 location as one machine word. */
650 chunk
= *(const apr_uintptr_t
*)(file_for_suffix
[0].curp
+ 1
651 - sizeof(apr_uintptr_t
));
652 if (contains_eol(chunk
))
655 for (i
= 1, is_match
= TRUE
; i
< file_len
; i
++)
658 == *(const apr_uintptr_t
*)
659 (file_for_suffix
[i
].curp
+ 1
660 - sizeof(apr_uintptr_t
)));
665 for (i
= 0; i
< file_len
; i
++)
667 file_for_suffix
[i
].curp
-= sizeof(apr_uintptr_t
);
668 can_read_word
= can_read_word
669 && ( (file_for_suffix
[i
].curp
+ 1
670 - sizeof(apr_uintptr_t
))
674 /* We skipped some bytes, so there are no closing EOLs */
679 /* The > min_curp[i] check leaves at least one final byte for checking
680 in the non block optimized case below. */
683 reached_prefix
= file_for_suffix
[0].chunk
== suffix_min_chunk0
684 && (file_for_suffix
[0].curp
- file_for_suffix
[0].buffer
)
685 == suffix_min_offset0
;
686 if (reached_prefix
|| is_one_at_bof(file_for_suffix
, file_len
))
690 for (i
= 1; i
< file_len
; i
++)
692 && *file_for_suffix
[0].curp
== *file_for_suffix
[i
].curp
;
695 /* Slide one byte forward, to point at the first byte of identical suffix */
696 INCREMENT_POINTERS(file_for_suffix
, file_len
, pool
);
698 /* Slide forward until we find an eol sequence to add the rest of the line
699 we're in. Then add SUFFIX_LINES_TO_KEEP more lines. Stop if at least
700 one file reaches its end. */
704 while (!is_one_at_eof(file_for_suffix
, file_len
)
705 && *file_for_suffix
[0].curp
!= '\n'
706 && *file_for_suffix
[0].curp
!= '\r')
707 INCREMENT_POINTERS(file_for_suffix
, file_len
, pool
);
709 /* Slide one or two more bytes, to point past the eol. */
710 if (!is_one_at_eof(file_for_suffix
, file_len
)
711 && *file_for_suffix
[0].curp
== '\r')
715 INCREMENT_POINTERS(file_for_suffix
, file_len
, pool
);
717 if (!is_one_at_eof(file_for_suffix
, file_len
)
718 && *file_for_suffix
[0].curp
== '\n')
722 INCREMENT_POINTERS(file_for_suffix
, file_len
, pool
);
725 while (!is_one_at_eof(file_for_suffix
, file_len
)
726 && suffix_lines_to_keep
--);
728 if (is_one_at_eof(file_for_suffix
, file_len
))
731 /* Save the final suffix information in the original file_info */
732 for (i
= 0; i
< file_len
; i
++)
734 file
[i
].suffix_start_chunk
= file_for_suffix
[i
].chunk
;
735 file
[i
].suffix_offset_in_chunk
=
736 file_for_suffix
[i
].curp
- file_for_suffix
[i
].buffer
;
739 *suffix_lines
= lines
;
745 /* Let FILE stand for the array of file_info struct elements of BATON->files
746 * that are indexed by the elements of the DATASOURCE array.
747 * BATON's type is (svn_diff__file_baton_t *).
749 * For each file in the FILE array, open the file at FILE.path; initialize
750 * FILE.file, FILE.size, FILE.buffer, FILE.curp and FILE.endp; allocate a
751 * buffer and read the first chunk. Then find the prefix and suffix lines
752 * which are identical between all the files. Return the number of identical
753 * prefix lines in PREFIX_LINES, and the number of identical suffix lines in
756 * Finding the identical prefix and suffix allows us to exclude those from the
757 * rest of the diff algorithm, which increases performance by reducing the
760 * Implements svn_diff_fns2_t::datasources_open. */
762 datasources_open(void *baton
,
763 apr_off_t
*prefix_lines
,
764 apr_off_t
*suffix_lines
,
765 const svn_diff_datasource_e
*datasources
,
766 apr_size_t datasources_len
)
768 svn_diff__file_baton_t
*file_baton
= baton
;
769 struct file_info files
[4];
770 apr_finfo_t finfo
[4];
772 #ifndef SVN_DISABLE_PREFIX_SUFFIX_SCANNING
773 svn_boolean_t reached_one_eof
;
777 /* Make sure prefix_lines and suffix_lines are set correctly, even if we
778 * exit early because one of the files is empty. */
782 /* Open datasources and read first chunk */
783 for (i
= 0; i
< datasources_len
; i
++)
785 struct file_info
*file
786 = &file_baton
->files
[datasource_to_index(datasources
[i
])];
787 SVN_ERR(svn_io_file_open(&file
->file
, file
->path
,
788 APR_READ
, APR_OS_DEFAULT
, file_baton
->pool
));
789 SVN_ERR(svn_io_file_info_get(&finfo
[i
], APR_FINFO_SIZE
,
790 file
->file
, file_baton
->pool
));
791 file
->size
= finfo
[i
].size
;
792 length
[i
] = finfo
[i
].size
> CHUNK_SIZE
? CHUNK_SIZE
: finfo
[i
].size
;
793 file
->buffer
= apr_palloc(file_baton
->pool
, (apr_size_t
) length
[i
]);
794 SVN_ERR(read_chunk(file
->file
, file
->path
, file
->buffer
,
795 length
[i
], 0, file_baton
->pool
));
796 file
->endp
= file
->buffer
+ length
[i
];
797 file
->curp
= file
->buffer
;
798 /* Set suffix_start_chunk to a guard value, so if suffix scanning is
799 * skipped because one of the files is empty, or because of
800 * reached_one_eof, we can still easily check for the suffix during
801 * token reading (datasource_get_next_token). */
802 file
->suffix_start_chunk
= -1;
807 for (i
= 0; i
< datasources_len
; i
++)
809 /* There will not be any identical prefix/suffix, so we're done. */
812 #ifndef SVN_DISABLE_PREFIX_SUFFIX_SCANNING
814 SVN_ERR(find_identical_prefix(&reached_one_eof
, prefix_lines
,
815 files
, datasources_len
, file_baton
->pool
));
817 if (!reached_one_eof
)
818 /* No file consisted totally of identical prefix,
819 * so there may be some identical suffix. */
820 SVN_ERR(find_identical_suffix(suffix_lines
, files
, datasources_len
,
825 /* Copy local results back to baton. */
826 for (i
= 0; i
< datasources_len
; i
++)
827 file_baton
->files
[datasource_to_index(datasources
[i
])] = files
[i
];
833 /* Implements svn_diff_fns2_t::datasource_close */
835 datasource_close(void *baton
, svn_diff_datasource_e datasource
)
837 /* Do nothing. The compare_token function needs previous datasources
838 * to stay available until all datasources are processed.
844 /* Implements svn_diff_fns2_t::datasource_get_next_token */
846 datasource_get_next_token(apr_uint32_t
*hash
, void **token
, void *baton
,
847 svn_diff_datasource_e datasource
)
849 svn_diff__file_baton_t
*file_baton
= baton
;
850 svn_diff__file_token_t
*file_token
;
851 struct file_info
*file
= &file_baton
->files
[datasource_to_index(datasource
)];
855 apr_off_t last_chunk
;
858 /* Did the last chunk end in a CR character? */
859 svn_boolean_t had_cr
= FALSE
;
866 last_chunk
= offset_to_chunk(file
->size
);
868 /* Are we already at the end of a chunk? */
872 if (last_chunk
== file
->chunk
)
873 return SVN_NO_ERROR
; /* EOF */
875 /* Or right before an identical suffix in the next chunk? */
876 if (file
->chunk
+ 1 == file
->suffix_start_chunk
877 && file
->suffix_offset_in_chunk
== 0)
881 /* Stop when we encounter the identical suffix. If suffix scanning was not
882 * performed, suffix_start_chunk will be -1, so this condition will never
884 if (file
->chunk
== file
->suffix_start_chunk
885 && (curp
- file
->buffer
) == file
->suffix_offset_in_chunk
)
888 /* Allocate a new token, or fetch one from the "reusable tokens" list. */
889 file_token
= file_baton
->tokens
;
892 file_baton
->tokens
= file_token
->next
;
896 file_token
= apr_palloc(file_baton
->pool
, sizeof(*file_token
));
899 file_token
->datasource
= datasource
;
900 file_token
->offset
= chunk_to_offset(file
->chunk
)
901 + (curp
- file
->buffer
);
902 file_token
->norm_offset
= file_token
->offset
;
903 file_token
->raw_length
= 0;
904 file_token
->length
= 0;
908 eol
= svn_eol__find_eol_start(curp
, endp
- curp
);
911 had_cr
= (*eol
== '\r');
913 /* If we have the whole eol sequence in the chunk... */
914 if (!(had_cr
&& eol
== endp
))
916 /* Also skip past the '\n' in an '\r\n' sequence. */
917 if (had_cr
&& *eol
== '\n')
923 if (file
->chunk
== last_chunk
)
929 length
= endp
- curp
;
930 file_token
->raw_length
+= length
;
934 svn_diff__normalize_buffer(&c
, &length
,
935 &file
->normalize_state
,
936 curp
, file_baton
->options
);
937 if (file_token
->length
== 0)
939 /* When we are reading the first part of the token, move the
940 normalized offset past leading ignored characters, if any. */
941 file_token
->norm_offset
+= (c
- curp
);
943 file_token
->length
+= length
;
944 h
= svn__adler32(h
, c
, length
);
947 curp
= endp
= file
->buffer
;
949 length
= file
->chunk
== last_chunk
?
950 offset_in_chunk(file
->size
) : CHUNK_SIZE
;
954 /* Issue #4283: Normally we should have checked for reaching the skipped
955 suffix here, but because we assume that a suffix always starts on a
956 line and token boundary we rely on catching the suffix earlier in this
959 When changing things here, make sure the whitespace settings are
960 applied, or we mught not reach the exact suffix boundary as token
962 SVN_ERR(read_chunk(file
->file
, file
->path
,
964 chunk_to_offset(file
->chunk
),
967 /* If the last chunk ended in a CR, we're done. */
978 file_token
->raw_length
+= length
;
981 /* If the file length is exactly a multiple of CHUNK_SIZE, we will end up
982 * with a spurious empty token. Avoid returning it.
983 * Note that we use the unnormalized length; we don't want a line containing
984 * only spaces (and no trailing newline) to appear like a non-existent
986 if (file_token
->raw_length
> 0)
989 svn_diff__normalize_buffer(&c
, &length
,
990 &file
->normalize_state
,
991 curp
, file_baton
->options
);
992 if (file_token
->length
== 0)
994 /* When we are reading the first part of the token, move the
995 normalized offset past leading ignored characters, if any. */
996 file_token
->norm_offset
+= (c
- curp
);
999 file_token
->length
+= length
;
1001 *hash
= svn__adler32(h
, c
, length
);
1002 *token
= file_token
;
1005 return SVN_NO_ERROR
;
1008 #define COMPARE_CHUNK_SIZE 4096
1010 /* Implements svn_diff_fns2_t::token_compare */
1011 static svn_error_t
*
1012 token_compare(void *baton
, void *token1
, void *token2
, int *compare
)
1014 svn_diff__file_baton_t
*file_baton
= baton
;
1015 svn_diff__file_token_t
*file_token
[2];
1016 char buffer
[2][COMPARE_CHUNK_SIZE
];
1018 apr_off_t offset
[2];
1019 struct file_info
*file
[2];
1020 apr_off_t length
[2];
1021 apr_off_t total_length
;
1022 /* How much is left to read of each token from the file. */
1023 apr_off_t raw_length
[2];
1025 svn_diff__normalize_state_t state
[2];
1027 file_token
[0] = token1
;
1028 file_token
[1] = token2
;
1029 if (file_token
[0]->length
< file_token
[1]->length
)
1032 return SVN_NO_ERROR
;
1035 if (file_token
[0]->length
> file_token
[1]->length
)
1038 return SVN_NO_ERROR
;
1041 total_length
= file_token
[0]->length
;
1042 if (total_length
== 0)
1045 return SVN_NO_ERROR
;
1048 for (i
= 0; i
< 2; ++i
)
1050 int idx
= datasource_to_index(file_token
[i
]->datasource
);
1052 file
[i
] = &file_baton
->files
[idx
];
1053 offset
[i
] = file_token
[i
]->norm_offset
;
1054 state
[i
] = svn_diff__normalize_state_normal
;
1056 if (offset_to_chunk(offset
[i
]) == file
[i
]->chunk
)
1058 /* If the start of the token is in memory, the entire token is
1061 bufp
[i
] = file
[i
]->buffer
;
1062 bufp
[i
] += offset_in_chunk(offset
[i
]);
1064 length
[i
] = total_length
;
1073 /* When we skipped the first part of the token via the whitespace
1074 normalization we must reduce the raw length of the token */
1075 skipped
= (file_token
[i
]->norm_offset
- file_token
[i
]->offset
);
1077 raw_length
[i
] = file_token
[i
]->raw_length
- skipped
;
1084 for (i
= 0; i
< 2; i
++)
1088 /* Error if raw_length is 0, that's an unexpected change
1089 * of the file that can happen when ingoring whitespace
1090 * and that can lead to an infinite loop. */
1091 if (raw_length
[i
] == 0)
1092 return svn_error_createf(SVN_ERR_DIFF_DATASOURCE_MODIFIED
,
1094 _("The file '%s' changed unexpectedly"
1098 /* Read a chunk from disk into a buffer */
1099 bufp
[i
] = buffer
[i
];
1100 length
[i
] = raw_length
[i
] > COMPARE_CHUNK_SIZE
?
1101 COMPARE_CHUNK_SIZE
: raw_length
[i
];
1103 SVN_ERR(read_chunk(file
[i
]->file
,
1105 bufp
[i
], length
[i
], offset
[i
],
1107 offset
[i
] += length
[i
];
1108 raw_length
[i
] -= length
[i
];
1109 /* bufp[i] gets reset to buffer[i] before reading each chunk,
1110 so, overwriting it isn't a problem */
1111 svn_diff__normalize_buffer(&bufp
[i
], &length
[i
], &state
[i
],
1112 bufp
[i
], file_baton
->options
);
1114 /* assert(length[i] == file_token[i]->length); */
1118 len
= length
[0] > length
[1] ? length
[1] : length
[0];
1120 /* Compare two chunks (that could be entire tokens if they both reside
1123 *compare
= memcmp(bufp
[0], bufp
[1], (size_t) len
);
1125 return SVN_NO_ERROR
;
1127 total_length
-= len
;
1133 while(total_length
> 0);
1136 return SVN_NO_ERROR
;
1140 /* Implements svn_diff_fns2_t::token_discard */
1142 token_discard(void *baton
, void *token
)
1144 svn_diff__file_baton_t
*file_baton
= baton
;
1145 svn_diff__file_token_t
*file_token
= token
;
1147 /* Prepend FILE_TOKEN to FILE_BATON->TOKENS, for reuse. */
1148 file_token
->next
= file_baton
->tokens
;
1149 file_baton
->tokens
= file_token
;
1153 /* Implements svn_diff_fns2_t::token_discard_all */
1155 token_discard_all(void *baton
)
1157 svn_diff__file_baton_t
*file_baton
= baton
;
1159 /* Discard all memory in use by the tokens, and close all open files. */
1160 svn_pool_clear(file_baton
->pool
);
1164 static const svn_diff_fns2_t svn_diff__file_vtable
=
1168 datasource_get_next_token
,
1174 /* Id for the --ignore-eol-style option, which doesn't have a short name. */
1175 #define SVN_DIFF__OPT_IGNORE_EOL_STYLE 256
1177 /* Options supported by svn_diff_file_options_parse(). */
1178 static const apr_getopt_option_t diff_options
[] =
1180 { "ignore-space-change", 'b', 0, NULL
},
1181 { "ignore-all-space", 'w', 0, NULL
},
1182 { "ignore-eol-style", SVN_DIFF__OPT_IGNORE_EOL_STYLE
, 0, NULL
},
1183 { "show-c-function", 'p', 0, NULL
},
1184 /* ### For compatibility; we don't support the argument to -u, because
1185 * ### we don't have optional argument support. */
1186 { "unified", 'u', 0, NULL
},
1187 { NULL
, 0, 0, NULL
}
1190 svn_diff_file_options_t
*
1191 svn_diff_file_options_create(apr_pool_t
*pool
)
1193 return apr_pcalloc(pool
, sizeof(svn_diff_file_options_t
));
1196 /* A baton for use with opt_parsing_error_func(). */
1197 struct opt_parsing_error_baton_t
1203 /* Store an error message from apr_getopt_long(). Set BATON->err to a new
1204 * error with a message generated from FMT and the remaining arguments.
1205 * Implements apr_getopt_err_fn_t. */
1207 opt_parsing_error_func(void *baton
,
1208 const char *fmt
, ...)
1210 struct opt_parsing_error_baton_t
*b
= baton
;
1211 const char *message
;
1215 message
= apr_pvsprintf(b
->pool
, fmt
, ap
);
1218 /* Skip leading ": " (if present, which it always is in known cases). */
1219 if (strncmp(message
, ": ", 2) == 0)
1222 b
->err
= svn_error_create(SVN_ERR_INVALID_DIFF_OPTION
, NULL
, message
);
1226 svn_diff_file_options_parse(svn_diff_file_options_t
*options
,
1227 const apr_array_header_t
*args
,
1231 struct opt_parsing_error_baton_t opt_parsing_error_baton
;
1232 /* Make room for each option (starting at index 1) plus trailing NULL. */
1233 const char **argv
= apr_palloc(pool
, sizeof(char*) * (args
->nelts
+ 2));
1235 opt_parsing_error_baton
.err
= NULL
;
1236 opt_parsing_error_baton
.pool
= pool
;
1239 memcpy((void *) (argv
+ 1), args
->elts
, sizeof(char*) * args
->nelts
);
1240 argv
[args
->nelts
+ 1] = NULL
;
1242 apr_getopt_init(&os
, pool
, args
->nelts
+ 1, argv
);
1244 /* Capture any error message from apr_getopt_long(). This will typically
1245 * say which option is wrong, which we would not otherwise know. */
1246 os
->errfn
= opt_parsing_error_func
;
1247 os
->errarg
= &opt_parsing_error_baton
;
1251 const char *opt_arg
;
1253 apr_status_t err
= apr_getopt_long(os
, diff_options
, &opt_id
, &opt_arg
);
1255 if (APR_STATUS_IS_EOF(err
))
1258 /* Wrap apr_getopt_long()'s error message. Its doc string implies
1259 * it always will produce one, but never mind if it doesn't. Avoid
1260 * using the message associated with the return code ERR, because
1261 * it refers to the "command line" which may be misleading here. */
1262 return svn_error_create(SVN_ERR_INVALID_DIFF_OPTION
,
1263 opt_parsing_error_baton
.err
,
1264 _("Error in options to internal diff"));
1269 /* -w takes precedence over -b. */
1270 if (! options
->ignore_space
)
1271 options
->ignore_space
= svn_diff_file_ignore_space_change
;
1274 options
->ignore_space
= svn_diff_file_ignore_space_all
;
1276 case SVN_DIFF__OPT_IGNORE_EOL_STYLE
:
1277 options
->ignore_eol_style
= TRUE
;
1280 options
->show_c_function
= TRUE
;
1287 /* Check for spurious arguments. */
1288 if (os
->ind
< os
->argc
)
1289 return svn_error_createf(SVN_ERR_INVALID_DIFF_OPTION
, NULL
,
1290 _("Invalid argument '%s' in diff options"),
1293 return SVN_NO_ERROR
;
1297 svn_diff_file_diff_2(svn_diff_t
**diff
,
1298 const char *original
,
1299 const char *modified
,
1300 const svn_diff_file_options_t
*options
,
1303 svn_diff__file_baton_t baton
= { 0 };
1305 baton
.options
= options
;
1306 baton
.files
[0].path
= original
;
1307 baton
.files
[1].path
= modified
;
1308 baton
.pool
= svn_pool_create(pool
);
1310 SVN_ERR(svn_diff_diff_2(diff
, &baton
, &svn_diff__file_vtable
, pool
));
1312 svn_pool_destroy(baton
.pool
);
1313 return SVN_NO_ERROR
;
1317 svn_diff_file_diff3_2(svn_diff_t
**diff
,
1318 const char *original
,
1319 const char *modified
,
1321 const svn_diff_file_options_t
*options
,
1324 svn_diff__file_baton_t baton
= { 0 };
1326 baton
.options
= options
;
1327 baton
.files
[0].path
= original
;
1328 baton
.files
[1].path
= modified
;
1329 baton
.files
[2].path
= latest
;
1330 baton
.pool
= svn_pool_create(pool
);
1332 SVN_ERR(svn_diff_diff3_2(diff
, &baton
, &svn_diff__file_vtable
, pool
));
1334 svn_pool_destroy(baton
.pool
);
1335 return SVN_NO_ERROR
;
1339 svn_diff_file_diff4_2(svn_diff_t
**diff
,
1340 const char *original
,
1341 const char *modified
,
1343 const char *ancestor
,
1344 const svn_diff_file_options_t
*options
,
1347 svn_diff__file_baton_t baton
= { 0 };
1349 baton
.options
= options
;
1350 baton
.files
[0].path
= original
;
1351 baton
.files
[1].path
= modified
;
1352 baton
.files
[2].path
= latest
;
1353 baton
.files
[3].path
= ancestor
;
1354 baton
.pool
= svn_pool_create(pool
);
1356 SVN_ERR(svn_diff_diff4_2(diff
, &baton
, &svn_diff__file_vtable
, pool
));
1358 svn_pool_destroy(baton
.pool
);
1359 return SVN_NO_ERROR
;
1363 /** Display unified context diffs **/
1365 /* Maximum length of the extra context to show when show_c_function is set.
1366 * GNU diff uses 40, let's be brave and use 50 instead. */
1367 #define SVN_DIFF__EXTRA_CONTEXT_LENGTH 50
1368 typedef struct svn_diff__file_output_baton_t
1370 svn_stream_t
*output_stream
;
1371 const char *header_encoding
;
1373 /* Cached markers, in header_encoding. */
1374 const char *context_str
;
1375 const char *delete_str
;
1376 const char *insert_str
;
1378 const char *path
[2];
1379 apr_file_t
*file
[2];
1381 apr_off_t current_line
[2];
1383 char buffer
[2][4096];
1384 apr_size_t length
[2];
1387 apr_off_t hunk_start
[2];
1388 apr_off_t hunk_length
[2];
1389 svn_stringbuf_t
*hunk
;
1391 /* Should we emit C functions in the unified diff header */
1392 svn_boolean_t show_c_function
;
1393 /* Extra strings to skip over if we match. */
1394 apr_array_header_t
*extra_skip_match
;
1395 /* "Context" to append to the @@ line when the show_c_function option
1397 svn_stringbuf_t
*extra_context
;
1398 /* Extra context for the current hunk. */
1399 char hunk_extra_context
[SVN_DIFF__EXTRA_CONTEXT_LENGTH
+ 1];
1402 } svn_diff__file_output_baton_t
;
1404 typedef enum svn_diff__file_output_unified_type_e
1406 svn_diff__file_output_unified_skip
,
1407 svn_diff__file_output_unified_context
,
1408 svn_diff__file_output_unified_delete
,
1409 svn_diff__file_output_unified_insert
1410 } svn_diff__file_output_unified_type_e
;
1413 static svn_error_t
*
1414 output_unified_line(svn_diff__file_output_baton_t
*baton
,
1415 svn_diff__file_output_unified_type_e type
, int idx
)
1421 svn_boolean_t bytes_processed
= FALSE
;
1422 svn_boolean_t had_cr
= FALSE
;
1423 /* Are we collecting extra context? */
1424 svn_boolean_t collect_extra
= FALSE
;
1426 length
= baton
->length
[idx
];
1427 curp
= baton
->curp
[idx
];
1429 /* Lazily update the current line even if we're at EOF.
1430 * This way we fake output of context at EOF
1432 baton
->current_line
[idx
]++;
1434 if (length
== 0 && apr_file_eof(baton
->file
[idx
]))
1436 return SVN_NO_ERROR
;
1443 if (!bytes_processed
)
1447 case svn_diff__file_output_unified_context
:
1448 svn_stringbuf_appendcstr(baton
->hunk
, baton
->context_str
);
1449 baton
->hunk_length
[0]++;
1450 baton
->hunk_length
[1]++;
1452 case svn_diff__file_output_unified_delete
:
1453 svn_stringbuf_appendcstr(baton
->hunk
, baton
->delete_str
);
1454 baton
->hunk_length
[0]++;
1456 case svn_diff__file_output_unified_insert
:
1457 svn_stringbuf_appendcstr(baton
->hunk
, baton
->insert_str
);
1458 baton
->hunk_length
[1]++;
1464 if (baton
->show_c_function
1465 && (type
== svn_diff__file_output_unified_skip
1466 || type
== svn_diff__file_output_unified_context
)
1467 && (svn_ctype_isalpha(*curp
) || *curp
== '$' || *curp
== '_')
1468 && !svn_cstring_match_glob_list(curp
,
1469 baton
->extra_skip_match
))
1471 svn_stringbuf_setempty(baton
->extra_context
);
1472 collect_extra
= TRUE
;
1476 eol
= svn_eol__find_eol_start(curp
, length
);
1482 had_cr
= (*eol
== '\r');
1484 len
= (apr_size_t
)(eol
- curp
);
1486 if (! had_cr
|| len
< length
)
1488 if (had_cr
&& *eol
== '\n')
1496 if (type
!= svn_diff__file_output_unified_skip
)
1498 svn_stringbuf_appendbytes(baton
->hunk
, curp
, len
);
1502 svn_stringbuf_appendbytes(baton
->extra_context
,
1506 baton
->curp
[idx
] = eol
;
1507 baton
->length
[idx
] = length
;
1515 if (type
!= svn_diff__file_output_unified_skip
)
1517 svn_stringbuf_appendbytes(baton
->hunk
, curp
, length
);
1522 svn_stringbuf_appendbytes(baton
->extra_context
, curp
, length
);
1525 bytes_processed
= TRUE
;
1528 curp
= baton
->buffer
[idx
];
1529 length
= sizeof(baton
->buffer
[idx
]);
1531 err
= svn_io_file_read(baton
->file
[idx
], curp
, &length
, baton
->pool
);
1533 /* If the last chunk ended with a CR, we look for an LF at the start
1537 if (! err
&& length
> 0 && *curp
== '\n')
1539 if (type
!= svn_diff__file_output_unified_skip
)
1541 svn_stringbuf_appendbyte(baton
->hunk
, *curp
);
1543 /* We don't append the LF to extra_context, since it would
1544 * just be stripped anyway. */
1549 baton
->curp
[idx
] = curp
;
1550 baton
->length
[idx
] = length
;
1557 if (err
&& ! APR_STATUS_IS_EOF(err
->apr_err
))
1560 if (err
&& APR_STATUS_IS_EOF(err
->apr_err
))
1562 svn_error_clear(err
);
1563 /* Special case if we reach the end of file AND the last line is in the
1564 changed range AND the file doesn't end with a newline */
1565 if (bytes_processed
&& (type
!= svn_diff__file_output_unified_skip
)
1568 SVN_ERR(svn_diff__unified_append_no_newline_msg(
1569 baton
->hunk
, baton
->header_encoding
, baton
->pool
));
1572 baton
->length
[idx
] = 0;
1575 return SVN_NO_ERROR
;
1578 static APR_INLINE svn_error_t
*
1579 output_unified_diff_range(svn_diff__file_output_baton_t
*output_baton
,
1581 svn_diff__file_output_unified_type_e type
,
1584 while (output_baton
->current_line
[source
] < until
)
1586 SVN_ERR(output_unified_line(output_baton
, type
, source
));
1588 return SVN_NO_ERROR
;
1591 static svn_error_t
*
1592 output_unified_flush_hunk(svn_diff__file_output_baton_t
*baton
)
1594 apr_off_t target_line
;
1595 apr_size_t hunk_len
;
1596 apr_off_t old_start
;
1597 apr_off_t new_start
;
1599 if (svn_stringbuf_isempty(baton
->hunk
))
1601 /* Nothing to flush */
1602 return SVN_NO_ERROR
;
1605 target_line
= baton
->hunk_start
[0] + baton
->hunk_length
[0]
1606 + SVN_DIFF__UNIFIED_CONTEXT_SIZE
;
1608 /* Add trailing context to the hunk */
1609 SVN_ERR(output_unified_diff_range(baton
, 0 /* original */,
1610 svn_diff__file_output_unified_context
,
1613 old_start
= baton
->hunk_start
[0];
1614 new_start
= baton
->hunk_start
[1];
1616 /* If the file is non-empty, convert the line indexes from
1617 zero based to one based */
1618 if (baton
->hunk_length
[0])
1620 if (baton
->hunk_length
[1])
1623 /* Write the hunk header */
1624 SVN_ERR(svn_diff__unified_write_hunk_header(
1625 baton
->output_stream
, baton
->header_encoding
, "@@",
1626 old_start
, baton
->hunk_length
[0],
1627 new_start
, baton
->hunk_length
[1],
1628 baton
->hunk_extra_context
,
1631 /* Output the hunk content */
1632 hunk_len
= baton
->hunk
->len
;
1633 SVN_ERR(svn_stream_write(baton
->output_stream
, baton
->hunk
->data
,
1636 /* Prepare for the next hunk */
1637 baton
->hunk_length
[0] = 0;
1638 baton
->hunk_length
[1] = 0;
1639 baton
->hunk_start
[0] = 0;
1640 baton
->hunk_start
[1] = 0;
1641 svn_stringbuf_setempty(baton
->hunk
);
1643 return SVN_NO_ERROR
;
1646 static svn_error_t
*
1647 output_unified_diff_modified(void *baton
,
1648 apr_off_t original_start
, apr_off_t original_length
,
1649 apr_off_t modified_start
, apr_off_t modified_length
,
1650 apr_off_t latest_start
, apr_off_t latest_length
)
1652 svn_diff__file_output_baton_t
*output_baton
= baton
;
1653 apr_off_t context_prefix_length
;
1654 apr_off_t prev_context_end
;
1655 svn_boolean_t init_hunk
= FALSE
;
1657 if (original_start
> SVN_DIFF__UNIFIED_CONTEXT_SIZE
)
1658 context_prefix_length
= SVN_DIFF__UNIFIED_CONTEXT_SIZE
;
1660 context_prefix_length
= original_start
;
1662 /* Calculate where the previous hunk will end if we would write it now
1663 (including the necessary context at the end) */
1664 if (output_baton
->hunk_length
[0] > 0 || output_baton
->hunk_length
[1] > 0)
1666 prev_context_end
= output_baton
->hunk_start
[0]
1667 + output_baton
->hunk_length
[0]
1668 + SVN_DIFF__UNIFIED_CONTEXT_SIZE
;
1672 prev_context_end
= -1;
1674 if (output_baton
->hunk_start
[0] == 0
1675 && (original_length
> 0 || modified_length
> 0))
1679 /* If the changed range is far enough from the previous range, flush the current
1682 apr_off_t new_hunk_start
= (original_start
- context_prefix_length
);
1684 if (output_baton
->current_line
[0] < new_hunk_start
1685 && prev_context_end
<= new_hunk_start
)
1687 SVN_ERR(output_unified_flush_hunk(output_baton
));
1690 else if (output_baton
->hunk_length
[0] > 0
1691 || output_baton
->hunk_length
[1] > 0)
1693 /* We extend the current hunk */
1696 /* Original: Output the context preceding the changed range */
1697 SVN_ERR(output_unified_diff_range(output_baton
, 0 /* original */,
1698 svn_diff__file_output_unified_context
,
1703 /* Original: Skip lines until we are at the beginning of the context we want
1705 SVN_ERR(output_unified_diff_range(output_baton
, 0 /* original */,
1706 svn_diff__file_output_unified_skip
,
1707 original_start
- context_prefix_length
));
1709 /* Note that the above skip stores data for the show_c_function support below */
1713 SVN_ERR_ASSERT(output_baton
->hunk_length
[0] == 0
1714 && output_baton
->hunk_length
[1] == 0);
1716 output_baton
->hunk_start
[0] = original_start
- context_prefix_length
;
1717 output_baton
->hunk_start
[1] = modified_start
- context_prefix_length
;
1720 if (init_hunk
&& output_baton
->show_c_function
)
1723 const char *invalid_character
;
1725 /* Save the extra context for later use.
1726 * Note that the last byte of the hunk_extra_context array is never
1727 * touched after it is zero-initialized, so the array is always
1729 strncpy(output_baton
->hunk_extra_context
,
1730 output_baton
->extra_context
->data
,
1731 SVN_DIFF__EXTRA_CONTEXT_LENGTH
);
1732 /* Trim whitespace at the end, most notably to get rid of any
1733 * newline characters. */
1734 p
= strlen(output_baton
->hunk_extra_context
);
1736 && svn_ctype_isspace(output_baton
->hunk_extra_context
[p
- 1]))
1738 output_baton
->hunk_extra_context
[--p
] = '\0';
1741 svn_utf__last_valid(output_baton
->hunk_extra_context
,
1742 SVN_DIFF__EXTRA_CONTEXT_LENGTH
);
1743 for (p
= invalid_character
- output_baton
->hunk_extra_context
;
1744 p
< SVN_DIFF__EXTRA_CONTEXT_LENGTH
; p
++)
1746 output_baton
->hunk_extra_context
[p
] = '\0';
1750 /* Modified: Skip lines until we are at the start of the changed range */
1751 SVN_ERR(output_unified_diff_range(output_baton
, 1 /* modified */,
1752 svn_diff__file_output_unified_skip
,
1755 /* Original: Output the context preceding the changed range */
1756 SVN_ERR(output_unified_diff_range(output_baton
, 0 /* original */,
1757 svn_diff__file_output_unified_context
,
1760 /* Both: Output the changed range */
1761 SVN_ERR(output_unified_diff_range(output_baton
, 0 /* original */,
1762 svn_diff__file_output_unified_delete
,
1763 original_start
+ original_length
));
1764 SVN_ERR(output_unified_diff_range(output_baton
, 1 /* modified */,
1765 svn_diff__file_output_unified_insert
,
1766 modified_start
+ modified_length
));
1768 return SVN_NO_ERROR
;
1771 /* Set *HEADER to a new string consisting of PATH, a tab, and PATH's mtime. */
1772 static svn_error_t
*
1773 output_unified_default_hdr(const char **header
, const char *path
,
1776 apr_finfo_t file_info
;
1777 apr_time_exp_t exploded_time
;
1778 char time_buffer
[64];
1779 apr_size_t time_len
;
1780 const char *utf8_timestr
;
1782 SVN_ERR(svn_io_stat(&file_info
, path
, APR_FINFO_MTIME
, pool
));
1783 apr_time_exp_lt(&exploded_time
, file_info
.mtime
);
1785 apr_strftime(time_buffer
, &time_len
, sizeof(time_buffer
) - 1,
1786 /* Order of date components can be different in different languages */
1787 _("%a %b %e %H:%M:%S %Y"), &exploded_time
);
1789 SVN_ERR(svn_utf_cstring_to_utf8(&utf8_timestr
, time_buffer
, pool
));
1791 *header
= apr_psprintf(pool
, "%s\t%s", path
, utf8_timestr
);
1793 return SVN_NO_ERROR
;
1796 static const svn_diff_output_fns_t svn_diff__file_output_unified_vtable
=
1798 NULL
, /* output_common */
1799 output_unified_diff_modified
,
1800 NULL
, /* output_diff_latest */
1801 NULL
, /* output_diff_common */
1802 NULL
/* output_conflict */
1806 svn_diff_file_output_unified3(svn_stream_t
*output_stream
,
1808 const char *original_path
,
1809 const char *modified_path
,
1810 const char *original_header
,
1811 const char *modified_header
,
1812 const char *header_encoding
,
1813 const char *relative_to_dir
,
1814 svn_boolean_t show_c_function
,
1817 if (svn_diff_contains_diffs(diff
))
1819 svn_diff__file_output_baton_t baton
;
1822 memset(&baton
, 0, sizeof(baton
));
1823 baton
.output_stream
= output_stream
;
1825 baton
.header_encoding
= header_encoding
;
1826 baton
.path
[0] = original_path
;
1827 baton
.path
[1] = modified_path
;
1828 baton
.hunk
= svn_stringbuf_create_empty(pool
);
1829 baton
.show_c_function
= show_c_function
;
1830 baton
.extra_context
= svn_stringbuf_create_empty(pool
);
1832 if (show_c_function
)
1834 baton
.extra_skip_match
= apr_array_make(pool
, 3, sizeof(char **));
1836 APR_ARRAY_PUSH(baton
.extra_skip_match
, const char *) = "public:*";
1837 APR_ARRAY_PUSH(baton
.extra_skip_match
, const char *) = "private:*";
1838 APR_ARRAY_PUSH(baton
.extra_skip_match
, const char *) = "protected:*";
1841 SVN_ERR(svn_utf_cstring_from_utf8_ex2(&baton
.context_str
, " ",
1842 header_encoding
, pool
));
1843 SVN_ERR(svn_utf_cstring_from_utf8_ex2(&baton
.delete_str
, "-",
1844 header_encoding
, pool
));
1845 SVN_ERR(svn_utf_cstring_from_utf8_ex2(&baton
.insert_str
, "+",
1846 header_encoding
, pool
));
1848 if (relative_to_dir
)
1850 /* Possibly adjust the "original" and "modified" paths shown in
1851 the output (see issue #2723). */
1852 const char *child_path
;
1854 if (! original_header
)
1856 child_path
= svn_dirent_is_child(relative_to_dir
,
1857 original_path
, pool
);
1859 original_path
= child_path
;
1861 return svn_error_createf(
1862 SVN_ERR_BAD_RELATIVE_PATH
, NULL
,
1863 _("Path '%s' must be inside "
1864 "the directory '%s'"),
1865 svn_dirent_local_style(original_path
, pool
),
1866 svn_dirent_local_style(relative_to_dir
,
1870 if (! modified_header
)
1872 child_path
= svn_dirent_is_child(relative_to_dir
,
1873 modified_path
, pool
);
1875 modified_path
= child_path
;
1877 return svn_error_createf(
1878 SVN_ERR_BAD_RELATIVE_PATH
, NULL
,
1879 _("Path '%s' must be inside "
1880 "the directory '%s'"),
1881 svn_dirent_local_style(modified_path
, pool
),
1882 svn_dirent_local_style(relative_to_dir
,
1887 for (i
= 0; i
< 2; i
++)
1889 SVN_ERR(svn_io_file_open(&baton
.file
[i
], baton
.path
[i
],
1890 APR_READ
, APR_OS_DEFAULT
, pool
));
1893 if (original_header
== NULL
)
1895 SVN_ERR(output_unified_default_hdr(&original_header
, original_path
,
1899 if (modified_header
== NULL
)
1901 SVN_ERR(output_unified_default_hdr(&modified_header
, modified_path
,
1905 SVN_ERR(svn_diff__unidiff_write_header(output_stream
, header_encoding
,
1906 original_header
, modified_header
,
1909 SVN_ERR(svn_diff_output(diff
, &baton
,
1910 &svn_diff__file_output_unified_vtable
));
1911 SVN_ERR(output_unified_flush_hunk(&baton
));
1913 for (i
= 0; i
< 2; i
++)
1915 SVN_ERR(svn_io_file_close(baton
.file
[i
], pool
));
1919 return SVN_NO_ERROR
;
1923 /** Display diff3 **/
1925 /* A stream to remember *leading* context. Note that this stream does
1926 *not* copy the data that it is remembering; it just saves
1928 typedef struct context_saver_t
{
1929 svn_stream_t
*stream
;
1930 const char *data
[SVN_DIFF__UNIFIED_CONTEXT_SIZE
];
1931 apr_size_t len
[SVN_DIFF__UNIFIED_CONTEXT_SIZE
];
1932 apr_size_t next_slot
;
1933 apr_size_t total_written
;
1937 static svn_error_t
*
1938 context_saver_stream_write(void *baton
,
1942 context_saver_t
*cs
= baton
;
1943 cs
->data
[cs
->next_slot
] = data
;
1944 cs
->len
[cs
->next_slot
] = *len
;
1945 cs
->next_slot
= (cs
->next_slot
+ 1) % SVN_DIFF__UNIFIED_CONTEXT_SIZE
;
1946 cs
->total_written
++;
1947 return SVN_NO_ERROR
;
1950 typedef struct svn_diff3__file_output_baton_t
1952 svn_stream_t
*output_stream
;
1954 const char *path
[3];
1956 apr_off_t current_line
[3];
1962 /* The following four members are in the encoding used for the output. */
1963 const char *conflict_modified
;
1964 const char *conflict_original
;
1965 const char *conflict_separator
;
1966 const char *conflict_latest
;
1968 const char *marker_eol
;
1970 svn_diff_conflict_display_style_t conflict_style
;
1972 /* The rest of the fields are for
1973 svn_diff_conflict_display_only_conflicts only. Note that for
1974 these batons, OUTPUT_STREAM is either CONTEXT_SAVER->STREAM or
1975 (soon after a conflict) a "trailing context stream", never the
1976 actual output stream.*/
1977 /* The actual output stream. */
1978 svn_stream_t
*real_output_stream
;
1979 context_saver_t
*context_saver
;
1980 /* Used to allocate context_saver and trailing context streams, and
1981 for some printfs. */
1983 } svn_diff3__file_output_baton_t
;
1985 static svn_error_t
*
1986 flush_context_saver(context_saver_t
*cs
,
1987 svn_stream_t
*output_stream
)
1990 for (i
= 0; i
< SVN_DIFF__UNIFIED_CONTEXT_SIZE
; i
++)
1992 apr_size_t slot
= (i
+ cs
->next_slot
) % SVN_DIFF__UNIFIED_CONTEXT_SIZE
;
1995 apr_size_t len
= cs
->len
[slot
];
1996 SVN_ERR(svn_stream_write(output_stream
, cs
->data
[slot
], &len
));
1999 return SVN_NO_ERROR
;
2003 make_context_saver(svn_diff3__file_output_baton_t
*fob
)
2005 context_saver_t
*cs
;
2007 svn_pool_clear(fob
->pool
);
2008 cs
= apr_pcalloc(fob
->pool
, sizeof(*cs
));
2009 cs
->stream
= svn_stream_empty(fob
->pool
);
2010 svn_stream_set_baton(cs
->stream
, cs
);
2011 svn_stream_set_write(cs
->stream
, context_saver_stream_write
);
2012 fob
->context_saver
= cs
;
2013 fob
->output_stream
= cs
->stream
;
2017 /* A stream which prints SVN_DIFF__UNIFIED_CONTEXT_SIZE lines to
2018 BATON->REAL_OUTPUT_STREAM, and then changes BATON->OUTPUT_STREAM to
2019 a context_saver; used for *trailing* context. */
2021 struct trailing_context_printer
{
2022 apr_size_t lines_to_print
;
2023 svn_diff3__file_output_baton_t
*fob
;
2028 static svn_error_t
*
2029 trailing_context_printer_write(void *baton
,
2033 struct trailing_context_printer
*tcp
= baton
;
2034 SVN_ERR_ASSERT(tcp
->lines_to_print
> 0);
2035 SVN_ERR(svn_stream_write(tcp
->fob
->real_output_stream
, data
, len
));
2036 tcp
->lines_to_print
--;
2037 if (tcp
->lines_to_print
== 0)
2038 make_context_saver(tcp
->fob
);
2039 return SVN_NO_ERROR
;
2044 make_trailing_context_printer(svn_diff3__file_output_baton_t
*btn
)
2046 struct trailing_context_printer
*tcp
;
2049 svn_pool_clear(btn
->pool
);
2051 tcp
= apr_pcalloc(btn
->pool
, sizeof(*tcp
));
2052 tcp
->lines_to_print
= SVN_DIFF__UNIFIED_CONTEXT_SIZE
;
2054 s
= svn_stream_empty(btn
->pool
);
2055 svn_stream_set_baton(s
, tcp
);
2056 svn_stream_set_write(s
, trailing_context_printer_write
);
2057 btn
->output_stream
= s
;
2062 typedef enum svn_diff3__file_output_type_e
2064 svn_diff3__file_output_skip
,
2065 svn_diff3__file_output_normal
2066 } svn_diff3__file_output_type_e
;
2069 static svn_error_t
*
2070 output_line(svn_diff3__file_output_baton_t
*baton
,
2071 svn_diff3__file_output_type_e type
, int idx
)
2078 curp
= baton
->curp
[idx
];
2079 endp
= baton
->endp
[idx
];
2081 /* Lazily update the current line even if we're at EOF.
2083 baton
->current_line
[idx
]++;
2086 return SVN_NO_ERROR
;
2088 eol
= svn_eol__find_eol_start(curp
, endp
- curp
);
2093 svn_boolean_t had_cr
= (*eol
== '\r');
2095 if (had_cr
&& eol
!= endp
&& *eol
== '\n')
2099 if (type
!= svn_diff3__file_output_skip
)
2102 /* Note that the trailing context printer assumes that
2103 svn_stream_write is called exactly once per line. */
2104 SVN_ERR(svn_stream_write(baton
->output_stream
, curp
, &len
));
2107 baton
->curp
[idx
] = eol
;
2109 return SVN_NO_ERROR
;
2112 static svn_error_t
*
2113 output_marker_eol(svn_diff3__file_output_baton_t
*btn
)
2115 return svn_stream_puts(btn
->output_stream
, btn
->marker_eol
);
2118 static svn_error_t
*
2119 output_hunk(void *baton
, int idx
, apr_off_t target_line
,
2120 apr_off_t target_length
)
2122 svn_diff3__file_output_baton_t
*output_baton
= baton
;
2124 /* Skip lines until we are at the start of the changed range */
2125 while (output_baton
->current_line
[idx
] < target_line
)
2127 SVN_ERR(output_line(output_baton
, svn_diff3__file_output_skip
, idx
));
2130 target_line
+= target_length
;
2132 while (output_baton
->current_line
[idx
] < target_line
)
2134 SVN_ERR(output_line(output_baton
, svn_diff3__file_output_normal
, idx
));
2137 return SVN_NO_ERROR
;
2140 static svn_error_t
*
2141 output_common(void *baton
, apr_off_t original_start
, apr_off_t original_length
,
2142 apr_off_t modified_start
, apr_off_t modified_length
,
2143 apr_off_t latest_start
, apr_off_t latest_length
)
2145 return output_hunk(baton
, 1, modified_start
, modified_length
);
2148 static svn_error_t
*
2149 output_diff_modified(void *baton
,
2150 apr_off_t original_start
, apr_off_t original_length
,
2151 apr_off_t modified_start
, apr_off_t modified_length
,
2152 apr_off_t latest_start
, apr_off_t latest_length
)
2154 return output_hunk(baton
, 1, modified_start
, modified_length
);
2157 static svn_error_t
*
2158 output_diff_latest(void *baton
,
2159 apr_off_t original_start
, apr_off_t original_length
,
2160 apr_off_t modified_start
, apr_off_t modified_length
,
2161 apr_off_t latest_start
, apr_off_t latest_length
)
2163 return output_hunk(baton
, 2, latest_start
, latest_length
);
2166 static svn_error_t
*
2167 output_conflict(void *baton
,
2168 apr_off_t original_start
, apr_off_t original_length
,
2169 apr_off_t modified_start
, apr_off_t modified_length
,
2170 apr_off_t latest_start
, apr_off_t latest_length
,
2173 static const svn_diff_output_fns_t svn_diff3__file_output_vtable
=
2176 output_diff_modified
,
2178 output_diff_modified
, /* output_diff_common */
2184 static svn_error_t
*
2185 output_conflict_with_context(svn_diff3__file_output_baton_t
*btn
,
2186 apr_off_t original_start
,
2187 apr_off_t original_length
,
2188 apr_off_t modified_start
,
2189 apr_off_t modified_length
,
2190 apr_off_t latest_start
,
2191 apr_off_t latest_length
)
2193 /* Are we currently saving starting context (as opposed to printing
2194 trailing context)? If so, flush it. */
2195 if (btn
->output_stream
== btn
->context_saver
->stream
)
2197 if (btn
->context_saver
->total_written
> SVN_DIFF__UNIFIED_CONTEXT_SIZE
)
2198 SVN_ERR(svn_stream_puts(btn
->real_output_stream
, "@@\n"));
2199 SVN_ERR(flush_context_saver(btn
->context_saver
, btn
->real_output_stream
));
2202 /* Print to the real output stream. */
2203 btn
->output_stream
= btn
->real_output_stream
;
2205 /* Output the conflict itself. */
2206 SVN_ERR(svn_stream_printf(btn
->output_stream
, btn
->pool
,
2207 (modified_length
== 1
2208 ? "%s (%" APR_OFF_T_FMT
")"
2209 : "%s (%" APR_OFF_T_FMT
",%" APR_OFF_T_FMT
")"),
2210 btn
->conflict_modified
,
2211 modified_start
+ 1, modified_length
));
2212 SVN_ERR(output_marker_eol(btn
));
2213 SVN_ERR(output_hunk(btn
, 1/*modified*/, modified_start
, modified_length
));
2215 SVN_ERR(svn_stream_printf(btn
->output_stream
, btn
->pool
,
2216 (original_length
== 1
2217 ? "%s (%" APR_OFF_T_FMT
")"
2218 : "%s (%" APR_OFF_T_FMT
",%" APR_OFF_T_FMT
")"),
2219 btn
->conflict_original
,
2220 original_start
+ 1, original_length
));
2221 SVN_ERR(output_marker_eol(btn
));
2222 SVN_ERR(output_hunk(btn
, 0/*original*/, original_start
, original_length
));
2224 SVN_ERR(svn_stream_printf(btn
->output_stream
, btn
->pool
,
2225 "%s%s", btn
->conflict_separator
, btn
->marker_eol
));
2226 SVN_ERR(output_hunk(btn
, 2/*latest*/, latest_start
, latest_length
));
2227 SVN_ERR(svn_stream_printf(btn
->output_stream
, btn
->pool
,
2229 ? "%s (%" APR_OFF_T_FMT
")"
2230 : "%s (%" APR_OFF_T_FMT
",%" APR_OFF_T_FMT
")"),
2231 btn
->conflict_latest
,
2232 latest_start
+ 1, latest_length
));
2233 SVN_ERR(output_marker_eol(btn
));
2235 /* Go into print-trailing-context mode instead. */
2236 make_trailing_context_printer(btn
);
2238 return SVN_NO_ERROR
;
2242 static svn_error_t
*
2243 output_conflict(void *baton
,
2244 apr_off_t original_start
, apr_off_t original_length
,
2245 apr_off_t modified_start
, apr_off_t modified_length
,
2246 apr_off_t latest_start
, apr_off_t latest_length
,
2249 svn_diff3__file_output_baton_t
*file_baton
= baton
;
2251 svn_diff_conflict_display_style_t style
= file_baton
->conflict_style
;
2253 if (style
== svn_diff_conflict_display_only_conflicts
)
2254 return output_conflict_with_context(file_baton
,
2255 original_start
, original_length
,
2256 modified_start
, modified_length
,
2257 latest_start
, latest_length
);
2259 if (style
== svn_diff_conflict_display_resolved_modified_latest
)
2262 return svn_diff_output(diff
, baton
,
2263 &svn_diff3__file_output_vtable
);
2265 style
= svn_diff_conflict_display_modified_latest
;
2268 if (style
== svn_diff_conflict_display_modified_latest
||
2269 style
== svn_diff_conflict_display_modified_original_latest
)
2271 SVN_ERR(svn_stream_puts(file_baton
->output_stream
,
2272 file_baton
->conflict_modified
));
2273 SVN_ERR(output_marker_eol(file_baton
));
2275 SVN_ERR(output_hunk(baton
, 1, modified_start
, modified_length
));
2277 if (style
== svn_diff_conflict_display_modified_original_latest
)
2279 SVN_ERR(svn_stream_puts(file_baton
->output_stream
,
2280 file_baton
->conflict_original
));
2281 SVN_ERR(output_marker_eol(file_baton
));
2282 SVN_ERR(output_hunk(baton
, 0, original_start
, original_length
));
2285 SVN_ERR(svn_stream_puts(file_baton
->output_stream
,
2286 file_baton
->conflict_separator
));
2287 SVN_ERR(output_marker_eol(file_baton
));
2289 SVN_ERR(output_hunk(baton
, 2, latest_start
, latest_length
));
2291 SVN_ERR(svn_stream_puts(file_baton
->output_stream
,
2292 file_baton
->conflict_latest
));
2293 SVN_ERR(output_marker_eol(file_baton
));
2295 else if (style
== svn_diff_conflict_display_modified
)
2296 SVN_ERR(output_hunk(baton
, 1, modified_start
, modified_length
));
2297 else if (style
== svn_diff_conflict_display_latest
)
2298 SVN_ERR(output_hunk(baton
, 2, latest_start
, latest_length
));
2299 else /* unknown style */
2300 SVN_ERR_MALFUNCTION();
2302 return SVN_NO_ERROR
;
2306 svn_diff_file_output_merge2(svn_stream_t
*output_stream
,
2308 const char *original_path
,
2309 const char *modified_path
,
2310 const char *latest_path
,
2311 const char *conflict_original
,
2312 const char *conflict_modified
,
2313 const char *conflict_latest
,
2314 const char *conflict_separator
,
2315 svn_diff_conflict_display_style_t style
,
2318 svn_diff3__file_output_baton_t baton
;
2319 apr_file_t
*file
[3];
2322 apr_mmap_t
*mm
[3] = { 0 };
2323 #endif /* APR_HAS_MMAP */
2325 svn_boolean_t conflicts_only
=
2326 (style
== svn_diff_conflict_display_only_conflicts
);
2328 memset(&baton
, 0, sizeof(baton
));
2331 baton
.pool
= svn_pool_create(pool
);
2332 make_context_saver(&baton
);
2333 baton
.real_output_stream
= output_stream
;
2336 baton
.output_stream
= output_stream
;
2337 baton
.path
[0] = original_path
;
2338 baton
.path
[1] = modified_path
;
2339 baton
.path
[2] = latest_path
;
2340 SVN_ERR(svn_utf_cstring_from_utf8(&baton
.conflict_modified
,
2341 conflict_modified
? conflict_modified
2342 : apr_psprintf(pool
, "<<<<<<< %s",
2345 SVN_ERR(svn_utf_cstring_from_utf8(&baton
.conflict_original
,
2346 conflict_original
? conflict_original
2347 : apr_psprintf(pool
, "||||||| %s",
2350 SVN_ERR(svn_utf_cstring_from_utf8(&baton
.conflict_separator
,
2351 conflict_separator
? conflict_separator
2352 : "=======", pool
));
2353 SVN_ERR(svn_utf_cstring_from_utf8(&baton
.conflict_latest
,
2354 conflict_latest
? conflict_latest
2355 : apr_psprintf(pool
, ">>>>>>> %s",
2359 baton
.conflict_style
= style
;
2361 for (idx
= 0; idx
< 3; idx
++)
2365 SVN_ERR(map_or_read_file(&file
[idx
],
2367 &baton
.buffer
[idx
], &size
,
2368 baton
.path
[idx
], pool
));
2370 baton
.curp
[idx
] = baton
.buffer
[idx
];
2371 baton
.endp
[idx
] = baton
.buffer
[idx
];
2373 if (baton
.endp
[idx
])
2374 baton
.endp
[idx
] += size
;
2377 /* Check what eol marker we should use for conflict markers.
2378 We use the eol marker of the modified file and fall back on the
2379 platform's eol marker if that file doesn't contain any newlines. */
2380 eol
= svn_eol__detect_eol(baton
.buffer
[1], baton
.endp
[1] - baton
.buffer
[1],
2384 baton
.marker_eol
= eol
;
2386 SVN_ERR(svn_diff_output(diff
, &baton
,
2387 &svn_diff3__file_output_vtable
));
2389 for (idx
= 0; idx
< 3; idx
++)
2394 apr_status_t rv
= apr_mmap_delete(mm
[idx
]);
2395 if (rv
!= APR_SUCCESS
)
2397 return svn_error_wrap_apr(rv
, _("Failed to delete mmap '%s'"),
2401 #endif /* APR_HAS_MMAP */
2405 SVN_ERR(svn_io_file_close(file
[idx
], pool
));
2410 svn_pool_destroy(baton
.pool
);
2412 return SVN_NO_ERROR
;