Success build TortoiseMerge.
[TortoiseGit.git] / src / TortoiseMerge / libsvn_diff / diff_file.c
blob8d097037612353c2f037654e4a5a419e71d10aa0
1 /*
2 * diff_file.c : routines for doing diffs on files
4 * ====================================================================
5 * Copyright (c) 2000-2006 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_error.h"
31 #include "svn_version.h"
32 #include "svn_io.h"
33 #include "svn_ctype.h"
35 #include "svn_error.h"
36 #include "svn_types.h"
38 #include "svn_string.h"
39 //#include "svn_io.h"
40 //#include "svn_utf.h"
41 #include "svn_pools.h"
42 #include "diff.h"
43 //#include "svn_private_config.h"
44 //#include "svn_path.h"
45 //#include "svn_ctype.h"
46 #include "svn_diff.h"
49 /* A token, i.e. a line read from a
50 file. */
51 typedef struct svn_diff__file_token_t
53 /* Next token in free list. */
54 struct svn_diff__file_token_t *next;
55 svn_diff_datasource_e datasource;
56 /* Offset in the datasource. */
57 apr_off_t offset;
58 /* Offset of the normalized token (may skip leading whitespace) */
59 apr_off_t norm_offset;
60 /* Total length - before normalization. */
61 apr_off_t raw_length;
62 /* Total length - after normalization. */
63 apr_off_t length;
64 } svn_diff__file_token_t;
67 typedef struct svn_diff__file_baton_t
69 const svn_diff_file_options_t *options;
70 const char *path[4];
72 apr_file_t *file[4];
73 apr_off_t size[4];
75 int chunk[4];
76 char *buffer[4];
77 char *curp[4];
78 char *endp[4];
80 /* List of free tokens that may be reused. */
81 svn_diff__file_token_t *tokens;
83 svn_diff__normalize_state_t normalize_state[4];
85 apr_pool_t *pool;
86 } svn_diff__file_baton_t;
89 /* Look for the start of an end-of-line sequence (i.e. CR or LF)
90 * in the array pointed to by BUF, of length LEN.
91 * If such a byte is found, return the pointer to it, else return NULL.
93 static char *
94 find_eol_start(char *buf, apr_size_t len)
96 for (; len > 0; ++buf, --len)
98 if (*buf == '\n' || *buf == '\r')
99 return buf;
101 return NULL;
104 static int
105 datasource_to_index(svn_diff_datasource_e datasource)
107 switch (datasource)
109 case svn_diff_datasource_original:
110 return 0;
112 case svn_diff_datasource_modified:
113 return 1;
115 case svn_diff_datasource_latest:
116 return 2;
118 case svn_diff_datasource_ancestor:
119 return 3;
122 return -1;
125 /* Files are read in chunks of 128k. There is no support for this number
126 * whatsoever. If there is a number someone comes up with that has some
127 * argumentation, let's use that.
129 #define CHUNK_SHIFT 17
130 #define CHUNK_SIZE (1 << CHUNK_SHIFT)
132 #define chunk_to_offset(chunk) ((chunk) << CHUNK_SHIFT)
133 #define offset_to_chunk(offset) ((offset) >> CHUNK_SHIFT)
134 #define offset_in_chunk(offset) ((offset) & (CHUNK_SIZE - 1))
137 /* Read a chunk from a FILE into BUFFER, starting from OFFSET, going for
138 * *LENGTH. The actual bytes read are stored in *LENGTH on return.
140 static APR_INLINE svn_error_t *
141 read_chunk(apr_file_t *file, const char *path,
142 char *buffer, apr_size_t length,
143 apr_off_t offset, apr_pool_t *pool)
145 /* XXX: The final offset may not be the one we asked for.
146 * XXX: Check.
148 SVN_ERR(svn_io_file_seek(file, APR_SET, &offset, pool));
149 return svn_io_file_read_full(file, buffer, length, NULL, pool);
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.
158 #if APR_HAS_MMAP
159 #define MMAP_T_PARAM(NAME) apr_mmap_t **NAME,
160 #define MMAP_T_ARG(NAME) &(NAME),
161 #else
162 #define MMAP_T_PARAM(NAME)
163 #define MMAP_T_ARG(NAME)
164 #endif
166 static svn_error_t *
167 map_or_read_file(apr_file_t **file,
168 MMAP_T_PARAM(mm)
169 char **buffer, apr_off_t *size,
170 const char *path, apr_pool_t *pool)
172 apr_finfo_t finfo;
173 apr_status_t rv;
175 *buffer = NULL;
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));
180 #if APR_HAS_MMAP
181 if (finfo.size > APR_MMAP_THRESHOLD)
183 rv = apr_mmap_create(mm, *file, 0, finfo.size, APR_MMAP_READ, pool);
184 if (rv == APR_SUCCESS)
186 *buffer = (*mm)->mm;
189 /* On failure we just fall through and try reading the file into
190 * memory instead.
193 #endif /* APR_HAS_MMAP */
195 if (*buffer == NULL && finfo.size > 0)
197 *buffer = apr_palloc(pool, finfo.size);
199 SVN_ERR(svn_io_file_read_full(*file, *buffer, finfo.size, NULL, pool));
201 /* Since we have the entire contents of the file we can
202 * close it now.
204 SVN_ERR(svn_io_file_close(*file, pool));
206 *file = NULL;
209 *size = finfo.size;
211 return SVN_NO_ERROR;
215 /* Implements svn_diff_fns_t::datasource_open */
216 static svn_error_t *
217 datasource_open(void *baton, svn_diff_datasource_e datasource)
219 svn_diff__file_baton_t *file_baton = baton;
220 int idx;
221 apr_finfo_t finfo;
222 apr_size_t length;
223 char *curp;
224 char *endp;
226 idx = datasource_to_index(datasource);
228 SVN_ERR(svn_io_file_open(&file_baton->file[idx], file_baton->path[idx],
229 APR_READ, APR_OS_DEFAULT, file_baton->pool));
231 SVN_ERR(svn_io_file_info_get(&finfo, APR_FINFO_SIZE,
232 file_baton->file[idx], file_baton->pool));
234 file_baton->size[idx] = finfo.size;
235 length = finfo.size > CHUNK_SIZE ? CHUNK_SIZE : finfo.size;
237 if (length == 0)
238 return SVN_NO_ERROR;
240 endp = curp = apr_palloc(file_baton->pool, length);
241 endp += length;
243 file_baton->buffer[idx] = file_baton->curp[idx] = curp;
244 file_baton->endp[idx] = endp;
246 return read_chunk(file_baton->file[idx], file_baton->path[idx],
247 curp, length, 0, file_baton->pool);
251 /* Implements svn_diff_fns_t::datasource_close */
252 static svn_error_t *
253 datasource_close(void *baton, svn_diff_datasource_e datasource)
255 /* Do nothing. The compare_token function needs previous datasources
256 * to stay available until all datasources are processed.
259 return SVN_NO_ERROR;
262 /* Implements svn_diff_fns_t::datasource_get_next_token */
263 static svn_error_t *
264 datasource_get_next_token(apr_uint32_t *hash, void **token, void *baton,
265 svn_diff_datasource_e datasource)
267 svn_diff__file_baton_t *file_baton = baton;
268 svn_diff__file_token_t *file_token;
269 int idx;
270 char *endp;
271 char *curp;
272 char *eol;
273 int last_chunk;
274 apr_off_t length;
275 apr_uint32_t h = 0;
276 /* Did the last chunk end in a CR character? */
277 svn_boolean_t had_cr = FALSE;
279 *token = NULL;
281 idx = datasource_to_index(datasource);
283 curp = file_baton->curp[idx];
284 endp = file_baton->endp[idx];
286 last_chunk = offset_to_chunk(file_baton->size[idx]);
288 if (curp == endp
289 && last_chunk == file_baton->chunk[idx])
291 return SVN_NO_ERROR;
294 /* Get a new token */
295 file_token = file_baton->tokens;
296 if (file_token)
298 file_baton->tokens = file_token->next;
300 else
302 file_token = apr_palloc(file_baton->pool, sizeof(*file_token));
305 file_token->datasource = datasource;
306 file_token->offset = chunk_to_offset(file_baton->chunk[idx])
307 + (curp - file_baton->buffer[idx]);
308 file_token->raw_length = 0;
309 file_token->length = 0;
311 while (1)
313 eol = find_eol_start(curp, endp - curp);
314 if (eol)
316 had_cr = (*eol == '\r');
317 eol++;
318 /* If we have the whole eol sequence in the chunk... */
319 if (!had_cr || eol != endp)
321 if (had_cr && *eol == '\n')
322 ++eol;
323 break;
327 if (file_baton->chunk[idx] == last_chunk)
329 eol = endp;
330 break;
333 length = endp - curp;
334 file_token->raw_length += length;
335 svn_diff__normalize_buffer(&curp, &length,
336 &file_baton->normalize_state[idx],
337 curp, file_baton->options);
338 file_token->length += length;
339 h = svn_diff__adler32(h, curp, length);
341 curp = endp = file_baton->buffer[idx];
342 file_baton->chunk[idx]++;
343 length = file_baton->chunk[idx] == last_chunk ?
344 offset_in_chunk(file_baton->size[idx]) : CHUNK_SIZE;
345 endp += length;
346 file_baton->endp[idx] = endp;
348 SVN_ERR(read_chunk(file_baton->file[idx], file_baton->path[idx],
349 curp, length,
350 chunk_to_offset(file_baton->chunk[idx]),
351 file_baton->pool));
353 /* If the last chunk ended in a CR, we're done. */
354 if (had_cr)
356 eol = curp;
357 if (*curp == '\n')
358 ++eol;
359 break;
363 length = eol - curp;
364 file_token->raw_length += length;
365 file_baton->curp[idx] = eol;
367 /* If the file length is exactly a multiple of CHUNK_SIZE, we will end up
368 * with a spurious empty token. Avoid returning it.
369 * Note that we use the unnormalized length; we don't want a line containing
370 * only spaces (and no trailing newline) to appear like a non-existent
371 * line. */
372 if (file_token->raw_length > 0)
374 char *c = curp;
375 svn_diff__normalize_buffer(&c, &length,
376 &file_baton->normalize_state[idx],
377 curp, file_baton->options);
379 file_token->norm_offset = file_token->offset + (c - curp);
380 file_token->length += length;
382 *hash = svn_diff__adler32(h, c, length);
383 *token = file_token;
386 return SVN_NO_ERROR;
389 #define COMPARE_CHUNK_SIZE 4096
391 /* Implements svn_diff_fns_t::token_compare */
392 static svn_error_t *
393 token_compare(void *baton, void *token1, void *token2, int *compare)
395 svn_diff__file_baton_t *file_baton = baton;
396 svn_diff__file_token_t *file_token[2];
397 char buffer[2][COMPARE_CHUNK_SIZE];
398 char *bufp[2];
399 apr_off_t offset[2];
400 int idx[2];
401 apr_off_t length[2];
402 apr_off_t total_length;
403 /* How much is left to read of each token from the file. */
404 apr_off_t raw_length[2];
405 int i;
406 int chunk[2];
407 svn_diff__normalize_state_t state[2];
409 file_token[0] = token1;
410 file_token[1] = token2;
411 if (file_token[0]->length < file_token[1]->length)
413 *compare = -1;
414 return SVN_NO_ERROR;
417 if (file_token[0]->length > file_token[1]->length)
419 *compare = 1;
420 return SVN_NO_ERROR;
423 total_length = file_token[0]->length;
424 if (total_length == 0)
426 *compare = 0;
427 return SVN_NO_ERROR;
430 for (i = 0; i < 2; ++i)
432 idx[i] = datasource_to_index(file_token[i]->datasource);
433 offset[i] = file_token[i]->norm_offset;
434 chunk[i] = file_baton->chunk[idx[i]];
435 state[i] = svn_diff__normalize_state_normal;
437 if (offset_to_chunk(offset[i]) == chunk[i])
439 /* If the start of the token is in memory, the entire token is
440 * in memory.
442 bufp[i] = file_baton->buffer[idx[i]];
443 bufp[i] += offset_in_chunk(offset[i]);
445 length[i] = total_length;
446 raw_length[i] = 0;
448 else
450 length[i] = 0;
451 raw_length[i] = file_token[i]->raw_length;
457 apr_off_t len;
458 for (i = 0; i < 2; i++)
460 if (length[i] == 0)
462 /* Error if raw_length is 0, that's an unexpected change
463 * of the file that can happen when ingoring whitespace
464 * and that can lead to an infinite loop. */
465 if (raw_length[i] == 0)
466 return svn_error_createf(SVN_ERR_DIFF_DATASOURCE_MODIFIED,
467 NULL,
468 _("The file '%s' changed unexpectedly"
469 " during diff"),
470 file_baton->path[idx[i]]);
472 /* Read a chunk from disk into a buffer */
473 bufp[i] = buffer[i];
474 length[i] = raw_length[i] > COMPARE_CHUNK_SIZE ?
475 COMPARE_CHUNK_SIZE : raw_length[i];
477 SVN_ERR(read_chunk(file_baton->file[idx[i]],
478 file_baton->path[idx[i]],
479 bufp[i], length[i], offset[i],
480 file_baton->pool));
481 offset[i] += length[i];
482 raw_length[i] -= length[i];
483 /* bufp[i] gets reset to buffer[i] before reading each chunk,
484 so, overwriting it isn't a problem */
485 svn_diff__normalize_buffer(&bufp[i], &length[i], &state[i],
486 bufp[i], file_baton->options);
490 len = length[0] > length[1] ? length[1] : length[0];
492 /* Compare two chunks (that could be entire tokens if they both reside
493 * in memory).
495 *compare = memcmp(bufp[0], bufp[1], len);
496 if (*compare != 0)
497 return SVN_NO_ERROR;
499 total_length -= len;
500 length[0] -= len;
501 length[1] -= len;
502 bufp[0] += len;
503 bufp[1] += len;
505 while(total_length > 0);
507 *compare = 0;
508 return SVN_NO_ERROR;
512 /* Implements svn_diff_fns_t::token_discard */
513 static void
514 token_discard(void *baton, void *token)
516 svn_diff__file_baton_t *file_baton = baton;
517 svn_diff__file_token_t *file_token = token;
519 file_token->next = file_baton->tokens;
520 file_baton->tokens = file_token;
524 /* Implements svn_diff_fns_t::token_discard_all */
525 static void
526 token_discard_all(void *baton)
528 svn_diff__file_baton_t *file_baton = baton;
530 /* Discard all memory in use by the tokens, and close all open files. */
531 svn_pool_clear(file_baton->pool);
535 static const svn_diff_fns_t svn_diff__file_vtable =
537 datasource_open,
538 datasource_close,
539 datasource_get_next_token,
540 token_compare,
541 token_discard,
542 token_discard_all
545 /* Id for the --ignore-eol-style option, which doesn't have a short name. */
546 #define SVN_DIFF__OPT_IGNORE_EOL_STYLE 256
548 /* Options supported by svn_diff_file_options_parse(). */
549 static const apr_getopt_option_t diff_options[] =
551 { "ignore-space-change", 'b', 0, NULL },
552 { "ignore-all-space", 'w', 0, NULL },
553 { "ignore-eol-style", SVN_DIFF__OPT_IGNORE_EOL_STYLE, 0, NULL },
554 { "show-c-function", 'p', 0, NULL },
555 /* ### For compatibility; we don't support the argument to -u, because
556 * ### we don't have optional argument support. */
557 { "unified", 'u', 0, NULL },
558 { NULL, 0, 0, NULL }
561 svn_diff_file_options_t *
562 svn_diff_file_options_create(apr_pool_t *pool)
564 return apr_pcalloc(pool, sizeof(svn_diff_file_options_t));
567 #if 0
568 svn_error_t *
569 svn_diff_file_options_parse(svn_diff_file_options_t *options,
570 const apr_array_header_t *args,
571 apr_pool_t *pool)
573 apr_getopt_t *os;
574 /* Make room for each option (starting at index 1) plus trailing NULL. */
575 const char **argv = apr_palloc(pool, sizeof(char*) * (args->nelts + 2));
577 argv[0] = "";
578 memcpy((void *) (argv + 1), args->elts, sizeof(char*) * args->nelts);
579 argv[args->nelts + 1] = NULL;
581 apr_getopt_init(&os, pool, args->nelts + 1, argv);
582 /* No printing of error messages, please! */
583 os->errfn = NULL;
584 while (1)
586 const char *opt_arg;
587 int opt_id;
588 apr_status_t err = apr_getopt_long(os, diff_options, &opt_id, &opt_arg);
590 if (APR_STATUS_IS_EOF(err))
591 break;
592 if (err)
593 return svn_error_wrap_apr(err, _("Error parsing diff options"));
595 switch (opt_id)
597 case 'b':
598 /* -w takes precedence over -b. */
599 if (! options->ignore_space)
600 options->ignore_space = svn_diff_file_ignore_space_change;
601 break;
602 case 'w':
603 options->ignore_space = svn_diff_file_ignore_space_all;
604 break;
605 case SVN_DIFF__OPT_IGNORE_EOL_STYLE:
606 options->ignore_eol_style = TRUE;
607 break;
608 case 'p':
609 options->show_c_function = TRUE;
610 break;
611 default:
612 break;
616 /* Check for spurious arguments. */
617 if (os->ind < os->argc)
618 return svn_error_createf(SVN_ERR_INVALID_DIFF_OPTION, NULL,
619 _("Invalid argument '%s' in diff options"),
620 os->argv[os->ind]);
622 return SVN_NO_ERROR;
624 #endif
625 svn_error_t *
626 svn_diff_file_diff_2(svn_diff_t **diff,
627 const char *original,
628 const char *modified,
629 const svn_diff_file_options_t *options,
630 apr_pool_t *pool)
632 svn_diff__file_baton_t baton;
634 memset(&baton, 0, sizeof(baton));
635 baton.options = options;
636 baton.path[0] = original;
637 baton.path[1] = modified;
638 baton.pool = svn_pool_create(pool);
640 SVN_ERR(svn_diff_diff(diff, &baton, &svn_diff__file_vtable, pool));
642 svn_pool_destroy(baton.pool);
643 return SVN_NO_ERROR;
646 svn_error_t *
647 svn_diff_file_diff(svn_diff_t **diff,
648 const char *original,
649 const char *modified,
650 apr_pool_t *pool)
652 return svn_diff_file_diff_2(diff, original, modified,
653 svn_diff_file_options_create(pool), pool);
656 svn_error_t *
657 svn_diff_file_diff3_2(svn_diff_t **diff,
658 const char *original,
659 const char *modified,
660 const char *latest,
661 const svn_diff_file_options_t *options,
662 apr_pool_t *pool)
664 svn_diff__file_baton_t baton;
666 memset(&baton, 0, sizeof(baton));
667 baton.options = options;
668 baton.path[0] = original;
669 baton.path[1] = modified;
670 baton.path[2] = latest;
671 baton.pool = svn_pool_create(pool);
673 SVN_ERR(svn_diff_diff3(diff, &baton, &svn_diff__file_vtable, pool));
675 svn_pool_destroy(baton.pool);
676 return SVN_NO_ERROR;
679 svn_error_t *
680 svn_diff_file_diff3(svn_diff_t **diff,
681 const char *original,
682 const char *modified,
683 const char *latest,
684 apr_pool_t *pool)
686 return svn_diff_file_diff3_2(diff, original, modified, latest,
687 svn_diff_file_options_create(pool), pool);
690 svn_error_t *
691 svn_diff_file_diff4_2(svn_diff_t **diff,
692 const char *original,
693 const char *modified,
694 const char *latest,
695 const char *ancestor,
696 const svn_diff_file_options_t *options,
697 apr_pool_t *pool)
699 svn_diff__file_baton_t baton;
701 memset(&baton, 0, sizeof(baton));
702 baton.options = options;
703 baton.path[0] = original;
704 baton.path[1] = modified;
705 baton.path[2] = latest;
706 baton.path[3] = ancestor;
707 baton.pool = svn_pool_create(pool);
709 SVN_ERR(svn_diff_diff4(diff, &baton, &svn_diff__file_vtable, pool));
711 svn_pool_destroy(baton.pool);
712 return SVN_NO_ERROR;
715 svn_error_t *
716 svn_diff_file_diff4(svn_diff_t **diff,
717 const char *original,
718 const char *modified,
719 const char *latest,
720 const char *ancestor,
721 apr_pool_t *pool)
723 return svn_diff_file_diff4_2(diff, original, modified, latest, ancestor,
724 svn_diff_file_options_create(pool), pool);
727 /** Display unified context diffs **/
729 /* Maximum length of the extra context to show when show_c_function is set.
730 * GNU diff uses 40, let's be brave and use 50 instead. */
731 #define SVN_DIFF__EXTRA_CONTEXT_LENGTH 50
732 typedef struct svn_diff__file_output_baton_t
734 svn_stream_t *output_stream;
735 const char *header_encoding;
737 /* Cached markers, in header_encoding. */
738 const char *context_str;
739 const char *delete_str;
740 const char *insert_str;
742 const char *path[2];
743 apr_file_t *file[2];
745 apr_off_t current_line[2];
747 char buffer[2][4096];
748 apr_size_t length[2];
749 char *curp[2];
751 apr_off_t hunk_start[2];
752 apr_off_t hunk_length[2];
753 svn_stringbuf_t *hunk;
755 /* Should we emit C functions in the unified diff header */
756 svn_boolean_t show_c_function;
757 /* Extra strings to skip over if we match. */
758 apr_array_header_t *extra_skip_match;
759 /* "Context" to append to the @@ line when the show_c_function option
760 * is set. */
761 svn_stringbuf_t *extra_context;
762 /* Extra context for the current hunk. */
763 char hunk_extra_context[SVN_DIFF__EXTRA_CONTEXT_LENGTH + 1];
765 apr_pool_t *pool;
766 } svn_diff__file_output_baton_t;
768 typedef enum svn_diff__file_output_unified_type_e
770 svn_diff__file_output_unified_skip,
771 svn_diff__file_output_unified_context,
772 svn_diff__file_output_unified_delete,
773 svn_diff__file_output_unified_insert
774 } svn_diff__file_output_unified_type_e;
777 static svn_error_t *
778 output_unified_line(svn_diff__file_output_baton_t *baton,
779 svn_diff__file_output_unified_type_e type, int idx)
781 char *curp;
782 char *eol;
783 apr_size_t length;
784 svn_error_t *err;
785 svn_boolean_t bytes_processed = FALSE;
786 svn_boolean_t had_cr = FALSE;
787 /* Are we collecting extra context? */
788 svn_boolean_t collect_extra = FALSE;
790 length = baton->length[idx];
791 curp = baton->curp[idx];
793 /* Lazily update the current line even if we're at EOF.
794 * This way we fake output of context at EOF
796 baton->current_line[idx]++;
798 if (length == 0 && apr_file_eof(baton->file[idx]))
800 return SVN_NO_ERROR;
805 if (length > 0)
807 if (!bytes_processed)
809 switch (type)
811 case svn_diff__file_output_unified_context:
812 svn_stringbuf_appendcstr(baton->hunk, baton->context_str);
813 baton->hunk_length[0]++;
814 baton->hunk_length[1]++;
815 break;
816 case svn_diff__file_output_unified_delete:
817 svn_stringbuf_appendcstr(baton->hunk, baton->delete_str);
818 baton->hunk_length[0]++;
819 break;
820 case svn_diff__file_output_unified_insert:
821 svn_stringbuf_appendcstr(baton->hunk, baton->insert_str);
822 baton->hunk_length[1]++;
823 break;
824 default:
825 break;
828 if (baton->show_c_function
829 && (type == svn_diff__file_output_unified_skip
830 || type == svn_diff__file_output_unified_context)
831 && (svn_ctype_isalpha(*curp) || *curp == '$' || *curp == '_')
832 && !svn_cstring_match_glob_list(curp,
833 baton->extra_skip_match))
835 svn_stringbuf_setempty(baton->extra_context);
836 collect_extra = TRUE;
840 eol = find_eol_start(curp, length);
842 if (eol != NULL)
844 apr_size_t len;
846 had_cr = (*eol == '\r');
847 eol++;
848 len = (apr_size_t)(eol - curp);
850 if (! had_cr || len < length)
852 if (had_cr && *eol == '\n')
854 ++eol;
855 ++len;
858 length -= len;
860 if (type != svn_diff__file_output_unified_skip)
862 svn_stringbuf_appendbytes(baton->hunk, curp, len);
864 if (collect_extra)
866 svn_stringbuf_appendbytes(baton->extra_context,
867 curp, len);
870 baton->curp[idx] = eol;
871 baton->length[idx] = length;
873 err = SVN_NO_ERROR;
875 break;
879 if (type != svn_diff__file_output_unified_skip)
881 svn_stringbuf_appendbytes(baton->hunk, curp, length);
884 if (collect_extra)
886 svn_stringbuf_appendbytes(baton->extra_context, curp, length);
889 bytes_processed = TRUE;
892 curp = baton->buffer[idx];
893 length = sizeof(baton->buffer[idx]);
895 err = svn_io_file_read(baton->file[idx], curp, &length, baton->pool);
897 /* If the last chunk ended with a CR, we look for an LF at the start
898 of this chunk. */
899 if (had_cr)
901 if (! err && length > 0 && *curp == '\n')
903 if (type != svn_diff__file_output_unified_skip)
905 svn_stringbuf_appendbytes(baton->hunk, curp, 1);
907 /* We don't append the LF to extra_context, since it would
908 * just be stripped anyway. */
909 ++curp;
910 --length;
913 baton->curp[idx] = curp;
914 baton->length[idx] = length;
916 break;
919 while (! err);
921 if (err && ! APR_STATUS_IS_EOF(err->apr_err))
922 return err;
924 if (err && APR_STATUS_IS_EOF(err->apr_err))
926 svn_error_clear(err);
927 /* Special case if we reach the end of file AND the last line is in the
928 changed range AND the file doesn't end with a newline */
929 if (bytes_processed && (type != svn_diff__file_output_unified_skip)
930 && ! had_cr)
932 const char *out_str;
933 SVN_ERR(svn_utf_cstring_from_utf8_ex2
934 (&out_str,
935 /* The string below is intentionally not marked for
936 translation: it's vital to correct operation of
937 the diff(1)/patch(1) program pair. */
938 APR_EOL_STR "\\ No newline at end of file" APR_EOL_STR,
939 baton->header_encoding, baton->pool));
940 svn_stringbuf_appendcstr(baton->hunk, out_str);
943 baton->length[idx] = 0;
946 return SVN_NO_ERROR;
949 static svn_error_t *
950 output_unified_flush_hunk(svn_diff__file_output_baton_t *baton)
952 apr_off_t target_line;
953 apr_size_t hunk_len;
954 int i;
956 if (svn_stringbuf_isempty(baton->hunk))
958 /* Nothing to flush */
959 return SVN_NO_ERROR;
962 target_line = baton->hunk_start[0] + baton->hunk_length[0]
963 + SVN_DIFF__UNIFIED_CONTEXT_SIZE;
965 /* Add trailing context to the hunk */
966 while (baton->current_line[0] < target_line)
968 SVN_ERR(output_unified_line
969 (baton, svn_diff__file_output_unified_context, 0));
972 /* If the file is non-empty, convert the line indexes from
973 zero based to one based */
974 for (i = 0; i < 2; i++)
976 if (baton->hunk_length[i] > 0)
977 baton->hunk_start[i]++;
980 /* Output the hunk header. If the hunk length is 1, the file is a one line
981 file. In this case, surpress the number of lines in the hunk (it is
982 1 implicitly)
984 SVN_ERR(svn_stream_printf_from_utf8(baton->output_stream,
985 baton->header_encoding,
986 baton->pool,
987 "@@ -%" APR_OFF_T_FMT,
988 baton->hunk_start[0]));
989 if (baton->hunk_length[0] != 1)
991 SVN_ERR(svn_stream_printf_from_utf8(baton->output_stream,
992 baton->header_encoding,
993 baton->pool, ",%" APR_OFF_T_FMT,
994 baton->hunk_length[0]));
997 SVN_ERR(svn_stream_printf_from_utf8(baton->output_stream,
998 baton->header_encoding,
999 baton->pool, " +%" APR_OFF_T_FMT,
1000 baton->hunk_start[1]));
1001 if (baton->hunk_length[1] != 1)
1003 SVN_ERR(svn_stream_printf_from_utf8(baton->output_stream,
1004 baton->header_encoding,
1005 baton->pool, ",%" APR_OFF_T_FMT,
1006 baton->hunk_length[1]));
1009 SVN_ERR(svn_stream_printf_from_utf8(baton->output_stream,
1010 baton->header_encoding,
1011 baton->pool, " @@%s%s" APR_EOL_STR,
1012 baton->hunk_extra_context[0]
1013 ? " " : "",
1014 baton->hunk_extra_context));
1016 /* Output the hunk content */
1017 hunk_len = baton->hunk->len;
1018 SVN_ERR(svn_stream_write(baton->output_stream, baton->hunk->data,
1019 &hunk_len));
1021 /* Prepare for the next hunk */
1022 baton->hunk_length[0] = 0;
1023 baton->hunk_length[1] = 0;
1024 svn_stringbuf_setempty(baton->hunk);
1026 return SVN_NO_ERROR;
1029 static svn_error_t *
1030 output_unified_diff_modified(void *baton,
1031 apr_off_t original_start, apr_off_t original_length,
1032 apr_off_t modified_start, apr_off_t modified_length,
1033 apr_off_t latest_start, apr_off_t latest_length)
1035 svn_diff__file_output_baton_t *output_baton = baton;
1036 apr_off_t target_line[2];
1037 int i;
1039 target_line[0] = original_start >= SVN_DIFF__UNIFIED_CONTEXT_SIZE
1040 ? original_start - SVN_DIFF__UNIFIED_CONTEXT_SIZE : 0;
1041 target_line[1] = modified_start;
1043 /* If the changed ranges are far enough apart (no overlapping or connecting
1044 context), flush the current hunk, initialize the next hunk and skip the
1045 lines not in context. Also do this when this is the first hunk.
1047 if (output_baton->current_line[0] < target_line[0]
1048 && (output_baton->hunk_start[0] + output_baton->hunk_length[0]
1049 + SVN_DIFF__UNIFIED_CONTEXT_SIZE < target_line[0]
1050 || output_baton->hunk_length[0] == 0))
1052 SVN_ERR(output_unified_flush_hunk(output_baton));
1054 output_baton->hunk_start[0] = target_line[0];
1055 output_baton->hunk_start[1] = target_line[1] + target_line[0]
1056 - original_start;
1058 /* Skip lines until we are at the beginning of the context we want to
1059 display */
1060 while (output_baton->current_line[0] < target_line[0])
1062 SVN_ERR(output_unified_line(output_baton,
1063 svn_diff__file_output_unified_skip, 0));
1066 if (output_baton->show_c_function)
1068 int p;
1070 /* Save the extra context for later use.
1071 * Note that the last byte of the hunk_extra_context array is never
1072 * touched after it is zero-initialized, so the array is always
1073 * 0-terminated. */
1074 strncpy(output_baton->hunk_extra_context,
1075 output_baton->extra_context->data,
1076 SVN_DIFF__EXTRA_CONTEXT_LENGTH);
1077 /* Trim whitespace at the end, most notably to get rid of any
1078 * newline characters. */
1079 p = strlen(output_baton->hunk_extra_context);
1080 while (p > 0
1081 && svn_ctype_isspace(output_baton->hunk_extra_context[p - 1]))
1083 output_baton->hunk_extra_context[--p] = '\0';
1088 /* Skip lines until we are at the start of the changed range */
1089 while (output_baton->current_line[1] < target_line[1])
1091 SVN_ERR(output_unified_line(output_baton,
1092 svn_diff__file_output_unified_skip, 1));
1095 /* Output the context preceding the changed range */
1096 while (output_baton->current_line[0] < original_start)
1098 SVN_ERR(output_unified_line(output_baton,
1099 svn_diff__file_output_unified_context, 0));
1102 target_line[0] = original_start + original_length;
1103 target_line[1] = modified_start + modified_length;
1105 /* Output the changed range */
1106 for (i = 0; i < 2; i++)
1108 while (output_baton->current_line[i] < target_line[i])
1110 SVN_ERR(output_unified_line
1111 (output_baton,
1112 i == 0 ? svn_diff__file_output_unified_delete
1113 : svn_diff__file_output_unified_insert, i));
1117 return SVN_NO_ERROR;
1121 /* Set *HEADER to a new string consisting of PATH, a tab, and PATH's mtime. */
1122 static svn_error_t *
1123 output_unified_default_hdr(const char **header, const char *path,
1124 apr_pool_t *pool)
1126 apr_finfo_t file_info;
1127 apr_time_exp_t exploded_time;
1128 char time_buffer[64];
1129 apr_size_t time_len;
1131 SVN_ERR(svn_io_stat(&file_info, path, APR_FINFO_MTIME, pool));
1132 apr_time_exp_lt(&exploded_time, file_info.mtime);
1134 apr_strftime(time_buffer, &time_len, sizeof(time_buffer) - 1,
1135 "%a %b %e %H:%M:%S %Y", &exploded_time);
1137 *header = apr_psprintf(pool, "%s\t%s", path, time_buffer);
1139 return SVN_NO_ERROR;
1143 static const svn_diff_output_fns_t svn_diff__file_output_unified_vtable =
1145 NULL, /* output_common */
1146 output_unified_diff_modified,
1147 NULL, /* output_diff_latest */
1148 NULL, /* output_diff_common */
1149 NULL /* output_conflict */
1152 svn_error_t *
1153 svn_diff_file_output_unified3(svn_stream_t *output_stream,
1154 svn_diff_t *diff,
1155 const char *original_path,
1156 const char *modified_path,
1157 const char *original_header,
1158 const char *modified_header,
1159 const char *header_encoding,
1160 const char *relative_to_dir,
1161 svn_boolean_t show_c_function,
1162 apr_pool_t *pool)
1164 svn_diff__file_output_baton_t baton;
1165 int i;
1167 if (svn_diff_contains_diffs(diff))
1169 const char **c;
1171 memset(&baton, 0, sizeof(baton));
1172 baton.output_stream = output_stream;
1173 baton.pool = pool;
1174 baton.header_encoding = header_encoding;
1175 baton.path[0] = original_path;
1176 baton.path[1] = modified_path;
1177 baton.hunk = svn_stringbuf_create("", pool);
1178 baton.show_c_function = show_c_function;
1179 baton.extra_context = svn_stringbuf_create("", pool);
1180 baton.extra_skip_match = apr_array_make(pool, 3, sizeof(char **));
1182 c = apr_array_push(baton.extra_skip_match);
1183 *c = "public:*";
1184 c = apr_array_push(baton.extra_skip_match);
1185 *c = "private:*";
1186 c = apr_array_push(baton.extra_skip_match);
1187 *c = "protected:*";
1189 SVN_ERR(svn_utf_cstring_from_utf8_ex2(&baton.context_str, " ",
1190 header_encoding, pool));
1191 SVN_ERR(svn_utf_cstring_from_utf8_ex2(&baton.delete_str, "-",
1192 header_encoding, pool));
1193 SVN_ERR(svn_utf_cstring_from_utf8_ex2(&baton.insert_str, "+",
1194 header_encoding, pool));
1196 if (relative_to_dir)
1198 /* Possibly adjust the "original" and "modified" paths shown in
1199 the output (see issue #2723). */
1200 const char *child_path;
1202 if (! original_header)
1204 child_path = svn_path_is_child(relative_to_dir,
1205 original_path, pool);
1206 if (child_path)
1207 original_path = child_path;
1208 else
1209 return svn_error_createf(SVN_ERR_BAD_RELATIVE_PATH, NULL,
1210 _("Path '%s' must be an immediate child of "
1211 "the directory '%s'"),
1212 original_path, relative_to_dir);
1215 if (! modified_header)
1217 child_path = svn_path_is_child(relative_to_dir, modified_path, pool);
1218 if (child_path)
1219 modified_path = child_path;
1220 else
1221 return svn_error_createf(SVN_ERR_BAD_RELATIVE_PATH, NULL,
1222 _("Path '%s' must be an immediate child of "
1223 "the directory '%s'"),
1224 modified_path, relative_to_dir);
1228 for (i = 0; i < 2; i++)
1230 SVN_ERR(svn_io_file_open(&baton.file[i], baton.path[i],
1231 APR_READ, APR_OS_DEFAULT, pool));
1234 if (original_header == NULL)
1236 SVN_ERR(output_unified_default_hdr
1237 (&original_header, original_path, pool));
1240 if (modified_header == NULL)
1242 SVN_ERR(output_unified_default_hdr
1243 (&modified_header, modified_path, pool));
1246 SVN_ERR(svn_stream_printf_from_utf8(output_stream, header_encoding, pool,
1247 "--- %s" APR_EOL_STR
1248 "+++ %s" APR_EOL_STR,
1249 original_header, modified_header));
1251 SVN_ERR(svn_diff_output(diff, &baton,
1252 &svn_diff__file_output_unified_vtable));
1253 SVN_ERR(output_unified_flush_hunk(&baton));
1255 for (i = 0; i < 2; i++)
1257 SVN_ERR(svn_io_file_close(baton.file[i], pool));
1261 return SVN_NO_ERROR;
1265 /** Display diff3 **/
1267 /* A stream to remember *leading* context. Note that this stream does
1268 *not* copy the data that it is remembering; it just saves
1269 *pointers! */
1270 typedef struct {
1271 svn_stream_t *stream;
1272 const char *data[SVN_DIFF__UNIFIED_CONTEXT_SIZE];
1273 apr_size_t len[SVN_DIFF__UNIFIED_CONTEXT_SIZE];
1274 apr_size_t next_slot;
1275 apr_size_t total_written;
1276 } context_saver_t;
1279 static svn_error_t *
1280 context_saver_stream_write(void *baton,
1281 const char *data,
1282 apr_size_t *len)
1284 context_saver_t *cs = baton;
1285 cs->data[cs->next_slot] = data;
1286 cs->len[cs->next_slot] = *len;
1287 cs->next_slot = (cs->next_slot + 1) % SVN_DIFF__UNIFIED_CONTEXT_SIZE;
1288 cs->total_written++;
1289 return SVN_NO_ERROR;
1292 typedef struct svn_diff3__file_output_baton_t
1294 svn_stream_t *output_stream;
1296 const char *path[3];
1298 apr_off_t current_line[3];
1300 char *buffer[3];
1301 char *endp[3];
1302 char *curp[3];
1304 /* The following four members are in the encoding used for the output. */
1305 const char *conflict_modified;
1306 const char *conflict_original;
1307 const char *conflict_separator;
1308 const char *conflict_latest;
1310 const char *marker_eol;
1312 svn_diff_conflict_display_style_t conflict_style;
1314 /* The rest of the fields are for
1315 svn_diff_conflict_display_only_conflicts only. Note that for
1316 these batons, OUTPUT_STREAM is either CONTEXT_SAVER->STREAM or
1317 (soon after a conflict) a "trailing context stream", never the
1318 actual output stream.*/
1319 /* The actual output stream. */
1320 svn_stream_t *real_output_stream;
1321 context_saver_t *context_saver;
1322 /* Used to allocate context_saver and trailing context streams, and
1323 for some printfs. */
1324 apr_pool_t *pool;
1325 } svn_diff3__file_output_baton_t;
1327 static svn_error_t *
1328 flush_context_saver(context_saver_t *cs,
1329 svn_stream_t *output_stream)
1331 int i;
1332 for (i = 0; i < SVN_DIFF__UNIFIED_CONTEXT_SIZE; i++)
1334 int slot = (i + cs->next_slot) % SVN_DIFF__UNIFIED_CONTEXT_SIZE;
1335 if (cs->data[slot])
1337 apr_size_t len = cs->len[slot];
1338 SVN_ERR(svn_stream_write(output_stream, cs->data[slot], &len));
1341 return SVN_NO_ERROR;
1344 static void
1345 make_context_saver(svn_diff3__file_output_baton_t *fob)
1347 context_saver_t *cs;
1349 svn_pool_clear(fob->pool);
1350 cs = apr_pcalloc(fob->pool, sizeof(*cs));
1351 cs->stream = svn_stream_empty(fob->pool);
1352 svn_stream_set_baton(cs->stream, cs);
1353 svn_stream_set_write(cs->stream, context_saver_stream_write);
1354 fob->context_saver = cs;
1355 fob->output_stream = cs->stream;
1359 /* A stream which prints SVN_DIFF__UNIFIED_CONTEXT_SIZE lines to
1360 BATON->REAL_OUTPUT_STREAM, and then changes BATON->OUTPUT_STREAM to
1361 a context_saver; used for *trailing* context. */
1363 struct trailing_context_printer {
1364 apr_size_t lines_to_print;
1365 svn_diff3__file_output_baton_t *fob;
1370 static svn_error_t *
1371 trailing_context_printer_write(void *baton,
1372 const char *data,
1373 apr_size_t *len)
1375 struct trailing_context_printer *tcp = baton;
1376 SVN_ERR_ASSERT(tcp->lines_to_print > 0);
1377 SVN_ERR(svn_stream_write(tcp->fob->real_output_stream, data, len));
1378 tcp->lines_to_print--;
1379 if (tcp->lines_to_print == 0)
1380 make_context_saver(tcp->fob);
1381 return SVN_NO_ERROR;
1385 static void
1386 make_trailing_context_printer(svn_diff3__file_output_baton_t *btn)
1388 struct trailing_context_printer *tcp;
1389 svn_stream_t *s;
1391 svn_pool_clear(btn->pool);
1393 tcp = apr_pcalloc(btn->pool, sizeof(*tcp));
1394 tcp->lines_to_print = SVN_DIFF__UNIFIED_CONTEXT_SIZE;
1395 tcp->fob = btn;
1396 s = svn_stream_empty(btn->pool);
1397 svn_stream_set_baton(s, tcp);
1398 svn_stream_set_write(s, trailing_context_printer_write);
1399 btn->output_stream = s;
1404 typedef enum svn_diff3__file_output_type_e
1406 svn_diff3__file_output_skip,
1407 svn_diff3__file_output_normal
1408 } svn_diff3__file_output_type_e;
1411 static svn_error_t *
1412 output_line(svn_diff3__file_output_baton_t *baton,
1413 svn_diff3__file_output_type_e type, int idx)
1415 char *curp;
1416 char *endp;
1417 char *eol;
1418 apr_size_t len;
1420 curp = baton->curp[idx];
1421 endp = baton->endp[idx];
1423 /* Lazily update the current line even if we're at EOF.
1425 baton->current_line[idx]++;
1427 if (curp == endp)
1428 return SVN_NO_ERROR;
1430 eol = find_eol_start(curp, endp - curp);
1431 if (!eol)
1432 eol = endp;
1433 else
1435 svn_boolean_t had_cr = (*eol == '\r');
1436 eol++;
1437 if (had_cr && eol != endp && *eol == '\n')
1438 eol++;
1441 if (type != svn_diff3__file_output_skip)
1443 len = eol - curp;
1444 /* Note that the trailing context printer assumes that
1445 svn_stream_write is called exactly once per line. */
1446 SVN_ERR(svn_stream_write(baton->output_stream, curp, &len));
1449 baton->curp[idx] = eol;
1451 return SVN_NO_ERROR;
1454 static svn_error_t *
1455 output_marker_eol(svn_diff3__file_output_baton_t *btn)
1457 apr_size_t len = strlen(btn->marker_eol);
1458 return svn_stream_write(btn->output_stream, btn->marker_eol, &len);
1461 static svn_error_t *
1462 output_hunk(void *baton, int idx, apr_off_t target_line,
1463 apr_off_t target_length)
1465 svn_diff3__file_output_baton_t *output_baton = baton;
1467 /* Skip lines until we are at the start of the changed range */
1468 while (output_baton->current_line[idx] < target_line)
1470 SVN_ERR(output_line(output_baton, svn_diff3__file_output_skip, idx));
1473 target_line += target_length;
1475 while (output_baton->current_line[idx] < target_line)
1477 SVN_ERR(output_line(output_baton, svn_diff3__file_output_normal, idx));
1480 return SVN_NO_ERROR;
1483 static svn_error_t *
1484 output_common(void *baton, apr_off_t original_start, apr_off_t original_length,
1485 apr_off_t modified_start, apr_off_t modified_length,
1486 apr_off_t latest_start, apr_off_t latest_length)
1488 return output_hunk(baton, 1, modified_start, modified_length);
1491 static svn_error_t *
1492 output_diff_modified(void *baton,
1493 apr_off_t original_start, apr_off_t original_length,
1494 apr_off_t modified_start, apr_off_t modified_length,
1495 apr_off_t latest_start, apr_off_t latest_length)
1497 return output_hunk(baton, 1, modified_start, modified_length);
1500 static svn_error_t *
1501 output_diff_latest(void *baton,
1502 apr_off_t original_start, apr_off_t original_length,
1503 apr_off_t modified_start, apr_off_t modified_length,
1504 apr_off_t latest_start, apr_off_t latest_length)
1506 return output_hunk(baton, 2, latest_start, latest_length);
1509 static svn_error_t *
1510 output_conflict(void *baton,
1511 apr_off_t original_start, apr_off_t original_length,
1512 apr_off_t modified_start, apr_off_t modified_length,
1513 apr_off_t latest_start, apr_off_t latest_length,
1514 svn_diff_t *diff);
1516 static const svn_diff_output_fns_t svn_diff3__file_output_vtable =
1518 output_common,
1519 output_diff_modified,
1520 output_diff_latest,
1521 output_diff_modified, /* output_diff_common */
1522 output_conflict
1527 static svn_error_t *
1528 output_conflict_with_context(svn_diff3__file_output_baton_t *btn,
1529 apr_off_t original_start,
1530 apr_off_t original_length,
1531 apr_off_t modified_start,
1532 apr_off_t modified_length,
1533 apr_off_t latest_start,
1534 apr_off_t latest_length)
1536 /* Are we currently saving starting context (as opposed to printing
1537 trailing context)? If so, flush it. */
1538 if (btn->output_stream == btn->context_saver->stream)
1540 if (btn->context_saver->total_written > SVN_DIFF__UNIFIED_CONTEXT_SIZE)
1541 SVN_ERR(svn_stream_printf(btn->real_output_stream, btn->pool, "@@\n"));
1542 SVN_ERR(flush_context_saver(btn->context_saver, btn->real_output_stream));
1545 /* Print to the real output stream. */
1546 btn->output_stream = btn->real_output_stream;
1548 /* Output the conflict itself. */
1549 SVN_ERR(svn_stream_printf(btn->output_stream, btn->pool,
1550 (modified_length == 1
1551 ? "%s (%" APR_OFF_T_FMT ")"
1552 : "%s (%" APR_OFF_T_FMT ",%" APR_OFF_T_FMT ")"),
1553 btn->conflict_modified,
1554 modified_start + 1, modified_length));
1555 SVN_ERR(output_marker_eol(btn));
1556 SVN_ERR(output_hunk(btn, 1/*modified*/, modified_start, modified_length));
1558 SVN_ERR(svn_stream_printf(btn->output_stream, btn->pool,
1559 (original_length == 1
1560 ? "%s (%" APR_OFF_T_FMT ")"
1561 : "%s (%" APR_OFF_T_FMT ",%" APR_OFF_T_FMT ")"),
1562 btn->conflict_original,
1563 original_start + 1, original_length));
1564 SVN_ERR(output_marker_eol(btn));
1565 SVN_ERR(output_hunk(btn, 0/*original*/, original_start, original_length));
1567 SVN_ERR(svn_stream_printf(btn->output_stream, btn->pool,
1568 "%s%s", btn->conflict_separator, btn->marker_eol));
1569 SVN_ERR(output_hunk(btn, 2/*latest*/, latest_start, latest_length));
1570 SVN_ERR(svn_stream_printf(btn->output_stream, btn->pool,
1571 (latest_length == 1
1572 ? "%s (%" APR_OFF_T_FMT ")"
1573 : "%s (%" APR_OFF_T_FMT ",%" APR_OFF_T_FMT ")"),
1574 btn->conflict_latest,
1575 latest_start + 1, latest_length));
1576 SVN_ERR(output_marker_eol(btn));
1578 /* Go into print-trailing-context mode instead. */
1579 make_trailing_context_printer(btn);
1581 return SVN_NO_ERROR;
1585 static svn_error_t *
1586 output_conflict(void *baton,
1587 apr_off_t original_start, apr_off_t original_length,
1588 apr_off_t modified_start, apr_off_t modified_length,
1589 apr_off_t latest_start, apr_off_t latest_length,
1590 svn_diff_t *diff)
1592 svn_diff3__file_output_baton_t *file_baton = baton;
1593 apr_size_t len;
1595 svn_diff_conflict_display_style_t style = file_baton->conflict_style;
1597 if (style == svn_diff_conflict_display_only_conflicts)
1598 return output_conflict_with_context(file_baton,
1599 original_start, original_length,
1600 modified_start, modified_length,
1601 latest_start, latest_length);
1603 if (style == svn_diff_conflict_display_resolved_modified_latest)
1605 if (diff)
1606 return svn_diff_output(diff, baton,
1607 &svn_diff3__file_output_vtable);
1608 else
1609 style = svn_diff_conflict_display_modified_latest;
1612 if (style == svn_diff_conflict_display_modified_latest ||
1613 style == svn_diff_conflict_display_modified_original_latest)
1615 len = strlen(file_baton->conflict_modified);
1616 SVN_ERR(svn_stream_write(file_baton->output_stream,
1617 file_baton->conflict_modified,
1618 &len));
1619 SVN_ERR(output_marker_eol(file_baton));
1621 SVN_ERR(output_hunk(baton, 1, modified_start, modified_length));
1623 if (style == svn_diff_conflict_display_modified_original_latest)
1625 len = strlen(file_baton->conflict_original);
1626 SVN_ERR(svn_stream_write(file_baton->output_stream,
1627 file_baton->conflict_original, &len));
1628 SVN_ERR(output_marker_eol(file_baton));
1629 SVN_ERR(output_hunk(baton, 0, original_start, original_length));
1632 len = strlen(file_baton->conflict_separator);
1633 SVN_ERR(svn_stream_write(file_baton->output_stream,
1634 file_baton->conflict_separator, &len));
1635 SVN_ERR(output_marker_eol(file_baton));
1637 SVN_ERR(output_hunk(baton, 2, latest_start, latest_length));
1639 len = strlen(file_baton->conflict_latest);
1640 SVN_ERR(svn_stream_write(file_baton->output_stream,
1641 file_baton->conflict_latest, &len));
1642 SVN_ERR(output_marker_eol(file_baton));
1644 else if (style == svn_diff_conflict_display_modified)
1645 SVN_ERR(output_hunk(baton, 1, modified_start, modified_length));
1646 else if (style == svn_diff_conflict_display_latest)
1647 SVN_ERR(output_hunk(baton, 2, latest_start, latest_length));
1648 else /* unknown style */
1649 SVN_ERR_MALFUNCTION();
1651 return SVN_NO_ERROR;
1655 /* Return the first eol marker found in [BUF, ENDP) as a
1656 * NUL-terminated string, or NULL if no eol marker is found.
1658 * If the last valid character of BUF is the first byte of a
1659 * potentially two-byte eol sequence, just return "\r", that is,
1660 * assume BUF represents a CR-only file. This is correct for callers
1661 * that pass an entire file at once, and is no more likely to be
1662 * incorrect than correct for any caller that doesn't.
1664 static const char *
1665 detect_eol(char *buf, char *endp)
1667 const char *eol = find_eol_start(buf, endp - buf);
1668 if (eol)
1670 if (*eol == '\n')
1671 return "\n";
1673 /* We found a CR. */
1674 ++eol;
1675 if (eol == endp || *eol != '\n')
1676 return "\r";
1677 return "\r\n";
1680 return NULL;
1683 svn_error_t *
1684 svn_diff_file_output_merge2(svn_stream_t *output_stream,
1685 svn_diff_t *diff,
1686 const char *original_path,
1687 const char *modified_path,
1688 const char *latest_path,
1689 const char *conflict_original,
1690 const char *conflict_modified,
1691 const char *conflict_latest,
1692 const char *conflict_separator,
1693 svn_diff_conflict_display_style_t style,
1694 apr_pool_t *pool)
1696 svn_diff3__file_output_baton_t baton;
1697 apr_file_t *file[3];
1698 apr_off_t size;
1699 int idx;
1700 #if APR_HAS_MMAP
1701 apr_mmap_t *mm[3] = { 0 };
1702 #endif /* APR_HAS_MMAP */
1703 const char *eol;
1704 svn_boolean_t conflicts_only =
1705 (style == svn_diff_conflict_display_only_conflicts);
1707 memset(&baton, 0, sizeof(baton));
1708 if (conflicts_only)
1710 baton.pool = svn_pool_create(pool);
1711 make_context_saver(&baton);
1712 baton.real_output_stream = output_stream;
1714 else
1715 baton.output_stream = output_stream;
1716 baton.path[0] = original_path;
1717 baton.path[1] = modified_path;
1718 baton.path[2] = latest_path;
1719 SVN_ERR(svn_utf_cstring_from_utf8(&baton.conflict_modified,
1720 conflict_modified ? conflict_modified
1721 : apr_psprintf(pool, "<<<<<<< %s",
1722 modified_path),
1723 pool));
1724 SVN_ERR(svn_utf_cstring_from_utf8(&baton.conflict_original,
1725 conflict_original ? conflict_original
1726 : apr_psprintf(pool, "||||||| %s",
1727 original_path),
1728 pool));
1729 SVN_ERR(svn_utf_cstring_from_utf8(&baton.conflict_separator,
1730 conflict_separator ? conflict_separator
1731 : "=======", pool));
1732 SVN_ERR(svn_utf_cstring_from_utf8(&baton.conflict_latest,
1733 conflict_latest ? conflict_latest
1734 : apr_psprintf(pool, ">>>>>>> %s",
1735 latest_path),
1736 pool));
1738 baton.conflict_style = style;
1740 for (idx = 0; idx < 3; idx++)
1742 SVN_ERR(map_or_read_file(&file[idx],
1743 MMAP_T_ARG(mm[idx])
1744 &baton.buffer[idx], &size,
1745 baton.path[idx], pool));
1747 baton.curp[idx] = baton.buffer[idx];
1748 baton.endp[idx] = baton.buffer[idx];
1750 if (baton.endp[idx])
1751 baton.endp[idx] += size;
1754 /* Check what eol marker we should use for conflict markers.
1755 We use the eol marker of the modified file and fall back on the
1756 platform's eol marker if that file doesn't contain any newlines. */
1757 eol = detect_eol(baton.buffer[1], baton.endp[1]);
1758 if (! eol)
1759 eol = APR_EOL_STR;
1760 baton.marker_eol = eol;
1762 SVN_ERR(svn_diff_output(diff, &baton,
1763 &svn_diff3__file_output_vtable));
1765 for (idx = 0; idx < 3; idx++)
1767 #if APR_HAS_MMAP
1768 if (mm[idx])
1770 apr_status_t rv = apr_mmap_delete(mm[idx]);
1771 if (rv != APR_SUCCESS)
1773 return svn_error_wrap_apr(rv, _("Failed to delete mmap '%s'"),
1774 baton.path[idx]);
1777 #endif /* APR_HAS_MMAP */
1779 if (file[idx])
1781 SVN_ERR(svn_io_file_close(file[idx], pool));
1785 if (conflicts_only)
1786 svn_pool_destroy(baton.pool);
1788 return SVN_NO_ERROR;
1792 svn_error_t *
1793 svn_diff_file_output_merge(svn_stream_t *output_stream,
1794 svn_diff_t *diff,
1795 const char *original_path,
1796 const char *modified_path,
1797 const char *latest_path,
1798 const char *conflict_original,
1799 const char *conflict_modified,
1800 const char *conflict_latest,
1801 const char *conflict_separator,
1802 svn_boolean_t display_original_in_conflict,
1803 svn_boolean_t display_resolved_conflicts,
1804 apr_pool_t *pool)
1806 svn_diff_conflict_display_style_t style =
1807 svn_diff_conflict_display_modified_latest;
1809 if (display_resolved_conflicts)
1810 style = svn_diff_conflict_display_resolved_modified_latest;
1812 if (display_original_in_conflict)
1813 style = svn_diff_conflict_display_modified_original_latest;
1815 return svn_diff_file_output_merge2(output_stream,
1816 diff,
1817 original_path,
1818 modified_path,
1819 latest_path,
1820 conflict_original,
1821 conflict_modified,
1822 conflict_latest,
1823 conflict_separator,
1824 style,
1825 pool);