Sync libsvn_diff from subversion r876937
[TortoiseGit.git] / src / TortoiseMerge / libsvn_diff / diff_file.c
blob104d4c9a1f076800cef328a8aecc1135f7739342
1 /*
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 * ====================================================================
20 #include <apr.h>
21 #include <apr_pools.h>
22 #include <apr_general.h>
23 #include <apr_file_io.h>
24 #include <apr_file_info.h>
25 #include <apr_time.h>
26 #include <apr_mmap.h>
27 #include <apr_getopt.h>
29 #include <apr_general.h>
30 #include "svn_version.h"
31 #include "svn_io.h"
32 #include "svn_ctype.h"
34 #include "svn_error.h"
35 #include "svn_diff.h"
36 #include "svn_types.h"
37 #include "svn_string.h"
38 #include "svn_io.h"
39 #include "svn_utf.h"
40 #include "svn_pools.h"
41 #include "diff.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. */
50 apr_off_t offset;
51 /* Offset of the normalized token (may skip leading whitespace) */
52 apr_off_t norm_offset;
53 /* Total length - before normalization. */
54 apr_off_t raw_length;
55 /* Total length - after normalization. */
56 apr_off_t length;
57 } svn_diff__file_token_t;
60 typedef struct svn_diff__file_baton_t
62 const svn_diff_file_options_t *options;
63 const char *path[4];
65 apr_file_t *file[4];
66 apr_off_t size[4];
68 int chunk[4];
69 char *buffer[4];
70 char *curp[4];
71 char *endp[4];
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];
78 apr_pool_t *pool;
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.
86 static char *
87 find_eol_start(char *buf, apr_size_t len)
89 for (; len > 0; ++buf, --len)
91 if (*buf == '\n' || *buf == '\r')
92 return buf;
94 return NULL;
97 static int
98 datasource_to_index(svn_diff_datasource_e datasource)
100 switch (datasource)
102 case svn_diff_datasource_original:
103 return 0;
105 case svn_diff_datasource_modified:
106 return 1;
108 case svn_diff_datasource_latest:
109 return 2;
111 case svn_diff_datasource_ancestor:
112 return 3;
115 return -1;
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.
139 * XXX: Check.
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.
151 #if APR_HAS_MMAP
152 #define MMAP_T_PARAM(NAME) apr_mmap_t **NAME,
153 #define MMAP_T_ARG(NAME) &(NAME),
154 #else
155 #define MMAP_T_PARAM(NAME)
156 #define MMAP_T_ARG(NAME)
157 #endif
159 static svn_error_t *
160 map_or_read_file(apr_file_t **file,
161 MMAP_T_PARAM(mm)
162 char **buffer, apr_off_t *size,
163 const char *path, apr_pool_t *pool)
165 apr_finfo_t finfo;
166 apr_status_t rv;
168 *buffer = NULL;
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));
173 #if APR_HAS_MMAP
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)
180 *buffer = (*mm)->mm;
183 /* On failure we just fall through and try reading the file into
184 * memory instead.
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,
194 NULL, pool));
196 /* Since we have the entire contents of the file we can
197 * close it now.
199 SVN_ERR(svn_io_file_close(*file, pool));
201 *file = NULL;
204 *size = finfo.size;
206 return SVN_NO_ERROR;
210 /* Implements svn_diff_fns_t::datasource_open */
211 static svn_error_t *
212 datasource_open(void *baton, svn_diff_datasource_e datasource)
214 svn_diff__file_baton_t *file_baton = baton;
215 int idx;
216 apr_finfo_t finfo;
217 apr_off_t length;
218 char *curp;
219 char *endp;
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));
230 }else
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;
239 if (length == 0)
240 return SVN_NO_ERROR;
242 endp = curp = apr_palloc(file_baton->pool, (apr_size_t) length);
243 endp += 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 */
254 static svn_error_t *
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.
261 return SVN_NO_ERROR;
264 /* Implements svn_diff_fns_t::datasource_get_next_token */
265 static svn_error_t *
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;
271 int idx;
272 char *endp;
273 char *curp;
274 char *eol;
275 apr_off_t last_chunk;
276 apr_off_t length;
277 apr_uint32_t h = 0;
278 /* Did the last chunk end in a CR character? */
279 svn_boolean_t had_cr = FALSE;
281 *token = NULL;
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]);
290 if (curp == endp
291 && last_chunk == file_baton->chunk[idx])
293 return SVN_NO_ERROR;
296 /* Get a new token */
297 file_token = file_baton->tokens;
298 if (file_token)
300 file_baton->tokens = file_token->next;
302 else
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;
313 while (1)
315 eol = find_eol_start(curp, endp - curp);
316 if (eol)
318 had_cr = (*eol == '\r');
319 eol++;
320 /* If we have the whole eol sequence in the chunk... */
321 if (!had_cr || eol != endp)
323 if (had_cr && *eol == '\n')
324 ++eol;
325 break;
329 if (file_baton->chunk[idx] == last_chunk)
331 eol = endp;
332 break;
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;
347 endp += length;
348 file_baton->endp[idx] = endp;
350 SVN_ERR(read_chunk(file_baton->file[idx], file_baton->path[idx],
351 curp, length,
352 chunk_to_offset(file_baton->chunk[idx]),
353 file_baton->pool));
355 /* If the last chunk ended in a CR, we're done. */
356 if (had_cr)
358 eol = curp;
359 if (*curp == '\n')
360 ++eol;
361 break;
365 length = eol - curp;
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
373 * line. */
374 if (file_token->raw_length > 0)
376 char *c = curp;
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);
389 *token = file_token;
392 return SVN_NO_ERROR;
395 #define COMPARE_CHUNK_SIZE 4096
397 /* Implements svn_diff_fns_t::token_compare */
398 static svn_error_t *
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];
404 char *bufp[2];
405 apr_off_t offset[2];
406 int idx[2];
407 apr_off_t length[2];
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];
411 int i;
412 int chunk[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)
419 *compare = -1;
420 return SVN_NO_ERROR;
423 if (file_token[0]->length > file_token[1]->length)
425 *compare = 1;
426 return SVN_NO_ERROR;
429 total_length = file_token[0]->length;
430 if (total_length == 0)
432 *compare = 0;
433 return SVN_NO_ERROR;
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
446 * in memory.
448 bufp[i] = file_baton->buffer[idx[i]];
449 bufp[i] += offset_in_chunk(offset[i]);
451 length[i] = total_length;
452 raw_length[i] = 0;
454 else
456 length[i] = 0;
457 raw_length[i] = file_token[i]->raw_length;
463 apr_off_t len;
464 for (i = 0; i < 2; i++)
466 if (length[i] == 0)
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,
473 NULL,
474 _("The file '%s' changed unexpectedly"
475 " during diff"),
476 file_baton->path[idx[i]]);
478 /* Read a chunk from disk into a buffer */
479 bufp[i] = buffer[i];
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],
486 file_baton->pool));
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
499 * in memory).
501 *compare = memcmp(bufp[0], bufp[1], (size_t) len);
502 if (*compare != 0)
503 return SVN_NO_ERROR;
505 total_length -= len;
506 length[0] -= len;
507 length[1] -= len;
508 bufp[0] += len;
509 bufp[1] += len;
511 while(total_length > 0);
513 *compare = 0;
514 return SVN_NO_ERROR;
518 /* Implements svn_diff_fns_t::token_discard */
519 static void
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 */
531 static void
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 =
543 datasource_open,
544 datasource_close,
545 datasource_get_next_token,
546 token_compare,
547 token_discard,
548 token_discard_all
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 },
564 { NULL, 0, 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));
573 svn_error_t *
574 svn_diff_file_options_parse(svn_diff_file_options_t *options,
575 const apr_array_header_t *args,
576 apr_pool_t *pool)
578 apr_getopt_t *os;
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));
582 argv[0] = "";
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! */
588 os->errfn = NULL;
589 while (1)
591 const char *opt_arg;
592 int opt_id;
593 apr_status_t err = apr_getopt_long(os, diff_options, &opt_id, &opt_arg);
595 if (APR_STATUS_IS_EOF(err))
596 break;
597 if (err)
598 return svn_error_wrap_apr(err, _("Error parsing diff options"));
600 switch (opt_id)
602 case 'b':
603 /* -w takes precedence over -b. */
604 if (! options->ignore_space)
605 options->ignore_space = svn_diff_file_ignore_space_change;
606 break;
607 case 'w':
608 options->ignore_space = svn_diff_file_ignore_space_all;
609 break;
610 case SVN_DIFF__OPT_IGNORE_EOL_STYLE:
611 options->ignore_eol_style = TRUE;
612 break;
613 case 'p':
614 options->show_c_function = TRUE;
615 break;
616 default:
617 break;
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"),
625 os->argv[os->ind]);
627 return SVN_NO_ERROR;
630 svn_error_t *
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,
635 apr_pool_t *pool)
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);
648 return SVN_NO_ERROR;
651 svn_error_t *
652 svn_diff_file_diff(svn_diff_t **diff,
653 const char *original,
654 const char *modified,
655 apr_pool_t *pool)
657 return svn_diff_file_diff_2(diff, original, modified,
658 svn_diff_file_options_create(pool), pool);
661 svn_error_t *
662 svn_diff_file_diff3_2(svn_diff_t **diff,
663 const char *original,
664 const char *modified,
665 const char *latest,
666 const svn_diff_file_options_t *options,
667 apr_pool_t *pool)
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);
681 return SVN_NO_ERROR;
684 svn_error_t *
685 svn_diff_file_diff3(svn_diff_t **diff,
686 const char *original,
687 const char *modified,
688 const char *latest,
689 apr_pool_t *pool)
691 return svn_diff_file_diff3_2(diff, original, modified, latest,
692 svn_diff_file_options_create(pool), pool);
695 svn_error_t *
696 svn_diff_file_diff4_2(svn_diff_t **diff,
697 const char *original,
698 const char *modified,
699 const char *latest,
700 const char *ancestor,
701 const svn_diff_file_options_t *options,
702 apr_pool_t *pool)
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);
717 return SVN_NO_ERROR;
720 svn_error_t *
721 svn_diff_file_diff4(svn_diff_t **diff,
722 const char *original,
723 const char *modified,
724 const char *latest,
725 const char *ancestor,
726 apr_pool_t *pool)
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;
747 const char *path[2];
748 apr_file_t *file[2];
750 apr_off_t current_line[2];
752 char buffer[2][4096];
753 apr_size_t length[2];
754 char *curp[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
765 * is set. */
766 svn_stringbuf_t *extra_context;
767 /* Extra context for the current hunk. */
768 char hunk_extra_context[SVN_DIFF__EXTRA_CONTEXT_LENGTH + 1];
770 apr_pool_t *pool;
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;
782 static svn_error_t *
783 output_unified_line(svn_diff__file_output_baton_t *baton,
784 svn_diff__file_output_unified_type_e type, int idx)
786 char *curp;
787 char *eol;
788 apr_size_t length;
789 svn_error_t *err;
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]))
805 return SVN_NO_ERROR;
810 if (length > 0)
812 if (!bytes_processed)
814 switch (type)
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]++;
820 break;
821 case svn_diff__file_output_unified_delete:
822 svn_stringbuf_appendcstr(baton->hunk, baton->delete_str);
823 baton->hunk_length[0]++;
824 break;
825 case svn_diff__file_output_unified_insert:
826 svn_stringbuf_appendcstr(baton->hunk, baton->insert_str);
827 baton->hunk_length[1]++;
828 break;
829 default:
830 break;
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);
847 if (eol != NULL)
849 apr_size_t len;
851 had_cr = (*eol == '\r');
852 eol++;
853 len = (apr_size_t)(eol - curp);
855 if (! had_cr || len < length)
857 if (had_cr && *eol == '\n')
859 ++eol;
860 ++len;
863 length -= len;
865 if (type != svn_diff__file_output_unified_skip)
867 svn_stringbuf_appendbytes(baton->hunk, curp, len);
869 if (collect_extra)
871 svn_stringbuf_appendbytes(baton->extra_context,
872 curp, len);
875 baton->curp[idx] = eol;
876 baton->length[idx] = length;
878 err = SVN_NO_ERROR;
880 break;
884 if (type != svn_diff__file_output_unified_skip)
886 svn_stringbuf_appendbytes(baton->hunk, curp, length);
889 if (collect_extra)
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
903 of this chunk. */
904 if (had_cr)
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. */
914 ++curp;
915 --length;
918 baton->curp[idx] = curp;
919 baton->length[idx] = length;
921 break;
924 while (! err);
926 if (err && ! APR_STATUS_IS_EOF(err->apr_err))
927 return 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)
935 && ! had_cr)
937 const char *out_str;
938 SVN_ERR(svn_utf_cstring_from_utf8_ex2
939 (&out_str,
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;
951 return SVN_NO_ERROR;
954 static svn_error_t *
955 output_unified_flush_hunk(svn_diff__file_output_baton_t *baton)
957 apr_off_t target_line;
958 apr_size_t hunk_len;
959 int i;
961 if (svn_stringbuf_isempty(baton->hunk))
963 /* Nothing to flush */
964 return SVN_NO_ERROR;
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
987 1 implicitly)
989 SVN_ERR(svn_stream_printf_from_utf8(baton->output_stream,
990 baton->header_encoding,
991 baton->pool,
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]
1018 ? " " : "",
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,
1024 &hunk_len));
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];
1042 int i;
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]
1061 - original_start;
1063 /* Skip lines until we are at the beginning of the context we want to
1064 display */
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)
1073 int p;
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
1079 * 0-terminated. */
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);
1086 while (p > 0
1087 && svn_ctype_isspace(output_baton->hunk_extra_context[p - 1]))
1089 output_baton->hunk_extra_context[--p] = '\0';
1091 invalid_character =
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
1125 (output_baton,
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,
1137 apr_pool_t *pool)
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 */
1168 svn_error_t *
1169 svn_diff_file_output_unified3(svn_stream_t *output_stream,
1170 svn_diff_t *diff,
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,
1178 apr_pool_t *pool)
1180 svn_diff__file_output_baton_t baton;
1181 int i;
1183 if (svn_diff_contains_diffs(diff))
1185 const char **c;
1187 memset(&baton, 0, sizeof(baton));
1188 baton.output_stream = output_stream;
1189 baton.pool = pool;
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);
1199 *c = "public:*";
1200 c = apr_array_push(baton.extra_skip_match);
1201 *c = "private:*";
1202 c = apr_array_push(baton.extra_skip_match);
1203 *c = "protected:*";
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);
1222 if (child_path)
1223 original_path = child_path;
1224 else
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);
1234 if (child_path)
1235 modified_path = child_path;
1236 else
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
1285 *pointers! */
1286 typedef struct {
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;
1292 } context_saver_t;
1295 static svn_error_t *
1296 context_saver_stream_write(void *baton,
1297 const char *data,
1298 apr_size_t *len)
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];
1316 char *buffer[3];
1317 char *endp[3];
1318 char *curp[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. */
1340 apr_pool_t *pool;
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)
1347 int i;
1348 for (i = 0; i < SVN_DIFF__UNIFIED_CONTEXT_SIZE; i++)
1350 int slot = (i + cs->next_slot) % SVN_DIFF__UNIFIED_CONTEXT_SIZE;
1351 if (cs->data[slot])
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;
1360 static void
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,
1388 const char *data,
1389 apr_size_t *len)
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;
1401 static void
1402 make_trailing_context_printer(svn_diff3__file_output_baton_t *btn)
1404 struct trailing_context_printer *tcp;
1405 svn_stream_t *s;
1407 svn_pool_clear(btn->pool);
1409 tcp = apr_pcalloc(btn->pool, sizeof(*tcp));
1410 tcp->lines_to_print = SVN_DIFF__UNIFIED_CONTEXT_SIZE;
1411 tcp->fob = btn;
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)
1431 char *curp;
1432 char *endp;
1433 char *eol;
1434 apr_size_t len;
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]++;
1443 if (curp == endp)
1444 return SVN_NO_ERROR;
1446 eol = find_eol_start(curp, endp - curp);
1447 if (!eol)
1448 eol = endp;
1449 else
1451 svn_boolean_t had_cr = (*eol == '\r');
1452 eol++;
1453 if (had_cr && eol != endp && *eol == '\n')
1454 eol++;
1457 if (type != svn_diff3__file_output_skip)
1459 len = eol - curp;
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,
1530 svn_diff_t *diff);
1532 static const svn_diff_output_fns_t svn_diff3__file_output_vtable =
1534 output_common,
1535 output_diff_modified,
1536 output_diff_latest,
1537 output_diff_modified, /* output_diff_common */
1538 output_conflict
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,
1587 (latest_length == 1
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,
1606 svn_diff_t *diff)
1608 svn_diff3__file_output_baton_t *file_baton = baton;
1609 apr_size_t len;
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)
1621 if (diff)
1622 return svn_diff_output(diff, baton,
1623 &svn_diff3__file_output_vtable);
1624 else
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,
1634 &len));
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.
1680 static const char *
1681 detect_eol(char *buf, char *endp)
1683 const char *eol = find_eol_start(buf, endp - buf);
1684 if (eol)
1686 if (*eol == '\n')
1687 return "\n";
1689 /* We found a CR. */
1690 ++eol;
1691 if (eol == endp || *eol != '\n')
1692 return "\r";
1693 return "\r\n";
1696 return NULL;
1699 svn_error_t *
1700 svn_diff_file_output_merge2(svn_stream_t *output_stream,
1701 svn_diff_t *diff,
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,
1710 apr_pool_t *pool)
1712 svn_diff3__file_output_baton_t baton;
1713 apr_file_t *file[3];
1714 apr_off_t size;
1715 int idx;
1716 #if APR_HAS_MMAP
1717 apr_mmap_t *mm[3] = { 0 };
1718 #endif /* APR_HAS_MMAP */
1719 const char *eol;
1720 svn_boolean_t conflicts_only =
1721 (style == svn_diff_conflict_display_only_conflicts);
1723 memset(&baton, 0, sizeof(baton));
1724 if (conflicts_only)
1726 baton.pool = svn_pool_create(pool);
1727 make_context_saver(&baton);
1728 baton.real_output_stream = output_stream;
1730 else
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",
1738 modified_path),
1739 pool));
1740 SVN_ERR(svn_utf_cstring_from_utf8(&baton.conflict_original,
1741 conflict_original ? conflict_original
1742 : apr_psprintf(pool, "||||||| %s",
1743 original_path),
1744 pool));
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",
1751 latest_path),
1752 pool));
1754 baton.conflict_style = style;
1756 for (idx = 0; idx < 3; idx++)
1758 SVN_ERR(map_or_read_file(&file[idx],
1759 MMAP_T_ARG(mm[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]);
1774 if (! eol)
1775 eol = APR_EOL_STR;
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++)
1783 #if APR_HAS_MMAP
1784 if (mm[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'"),
1790 baton.path[idx]);
1793 #endif /* APR_HAS_MMAP */
1795 if (file[idx])
1797 SVN_ERR(svn_io_file_close(file[idx], pool));
1801 if (conflicts_only)
1802 svn_pool_destroy(baton.pool);
1804 return SVN_NO_ERROR;
1808 svn_error_t *
1809 svn_diff_file_output_merge(svn_stream_t *output_stream,
1810 svn_diff_t *diff,
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,
1820 apr_pool_t *pool)
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,
1832 diff,
1833 original_path,
1834 modified_path,
1835 latest_path,
1836 conflict_original,
1837 conflict_modified,
1838 conflict_latest,
1839 conflict_separator,
1840 style,
1841 pool);