TortoiseGitMerge: Use shipped zlib1.dll instead of own copy
[TortoiseGit.git] / src / TortoiseMerge / libsvn_diff / diff_memory.c
blobcb6d80d5514690fac5bae6497b16dcb7734c0d0a
1 /*
2 * diff_memory.c : routines for doing diffs on in-memory data
4 * ====================================================================
5 * Copyright (c) 2007 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 * ====================================================================
19 #define WANT_MEMFUNC
20 #define WANT_STRFUNC
21 #include <apr.h>
22 #include <apr_want.h>
23 #include <apr_tables.h>
25 #include <apr_general.h>
26 #include "svn_error.h"
27 #include "svn_version.h"
28 #include "svn_io.h"
29 #include "svn_ctype.h"
31 #include "svn_diff.h"
32 #include "svn_pools.h"
33 #include "svn_types.h"
34 #include "svn_string.h"
35 #include "svn_utf.h"
36 #include "diff.h"
38 typedef struct source_tokens_t
40 /* A token simply is an svn_string_t pointing to
41 the data of the in-memory data source, containing
42 the raw token text, with length stored in the string */
43 apr_array_header_t *tokens;
45 /* Next token to be consumed */
46 apr_size_t next_token;
48 /* The source, containing the in-memory data to be diffed */
49 svn_string_t *source;
51 /* The last token ends with a newline character (sequence) */
52 svn_boolean_t ends_without_eol;
53 } source_tokens_t;
55 typedef struct diff_mem_baton_t
57 /* The tokens for each of the sources */
58 source_tokens_t sources[4];
60 /* Normalization buffer; we only ever compare 2 tokens at the same time */
61 char *normalization_buf[2];
63 /* Options for normalized comparison of the data sources */
64 const svn_diff_file_options_t *normalization_options;
65 } diff_mem_baton_t;
68 static int
69 datasource_to_index(svn_diff_datasource_e datasource)
71 switch (datasource)
73 case svn_diff_datasource_original:
74 return 0;
76 case svn_diff_datasource_modified:
77 return 1;
79 case svn_diff_datasource_latest:
80 return 2;
82 case svn_diff_datasource_ancestor:
83 return 3;
86 return -1;
90 /* Implements svn_diff_fns_t::datasource_open */
91 static svn_error_t *
92 datasource_open(void *baton, svn_diff_datasource_e datasource)
94 /* Do nothing: everything is already there and initialized to 0 */
95 return SVN_NO_ERROR;
99 /* Implements svn_diff_fns_t::datasource_close */
100 static svn_error_t *
101 datasource_close(void *baton, svn_diff_datasource_e datasource)
103 /* Do nothing. The compare_token function needs previous datasources
104 * to stay available until all datasources are processed.
107 return SVN_NO_ERROR;
111 /* Implements svn_diff_fns_t::datasource_get_next_token */
112 static svn_error_t *
113 datasource_get_next_token(apr_uint32_t *hash, void **token, void *baton,
114 svn_diff_datasource_e datasource)
116 diff_mem_baton_t *mem_baton = baton;
117 source_tokens_t *src = &(mem_baton->sources[datasource_to_index(datasource)]);
119 if (src->tokens->nelts > src->next_token)
121 /* There are actually tokens to be returned */
122 char *buf = mem_baton->normalization_buf[0];
123 svn_string_t *tok = (*token)
124 = APR_ARRAY_IDX(src->tokens, src->next_token, svn_string_t *);
125 apr_off_t len = tok->len;
126 svn_diff__normalize_state_t state
127 = svn_diff__normalize_state_normal;
129 svn_diff__normalize_buffer(&buf, &len, &state, tok->data,
130 mem_baton->normalization_options);
131 *hash = svn_diff__adler32(0, buf, len);
132 src->next_token++;
134 else
135 *token = NULL;
137 return SVN_NO_ERROR;
140 /* Implements svn_diff_fns_t::token_compare */
141 static svn_error_t *
142 token_compare(void *baton, void *token1, void *token2, int *result)
144 /* Implement the same behaviour as diff_file.c:token_compare(),
145 but be simpler, because we know we'll have all data in memory */
146 diff_mem_baton_t *btn = baton;
147 svn_string_t *t1 = token1;
148 svn_string_t *t2 = token2;
149 char *buf1 = btn->normalization_buf[0];
150 char *buf2 = btn->normalization_buf[1];
151 apr_off_t len1 = t1->len;
152 apr_off_t len2 = t2->len;
153 svn_diff__normalize_state_t state = svn_diff__normalize_state_normal;
155 svn_diff__normalize_buffer(&buf1, &len1, &state, t1->data,
156 btn->normalization_options);
157 state = svn_diff__normalize_state_normal;
158 svn_diff__normalize_buffer(&buf2, &len2, &state, t2->data,
159 btn->normalization_options);
161 if (len1 != len2)
162 *result = (len1 < len2) ? -1 : 1;
163 else
164 *result = (len1 == 0) ? 0 : memcmp(buf1, buf2, (size_t) len1);
166 return SVN_NO_ERROR;
169 /* Implements svn_diff_fns_t::token_discard */
170 static void
171 token_discard(void *baton, void *token)
173 /* No-op, we have no use for discarded tokens... */
177 /* Implements svn_diff_fns_t::token_discard_all */
178 static void
179 token_discard_all(void *baton)
181 /* Do nothing.
182 Note that in the file case, this function discards all
183 tokens allocated, but we're geared toward small in-memory diffs.
184 Meaning that there's no special pool to clear.
189 static const svn_diff_fns_t svn_diff__mem_vtable =
191 datasource_open,
192 datasource_close,
193 datasource_get_next_token,
194 token_compare,
195 token_discard,
196 token_discard_all
199 /* Fill SRC with the diff tokens (e.g. lines).
201 TEXT is assumed to live long enough for the tokens to
202 stay valid during their lifetime: no data is copied,
203 instead, svn_string_t's are allocated pointing straight
204 into TEXT.
206 static void
207 fill_source_tokens(source_tokens_t *src,
208 const svn_string_t *text,
209 apr_pool_t *pool)
211 const char *curp;
212 const char *endp;
213 const char *startp;
215 src->tokens = apr_array_make(pool, 0, sizeof(svn_string_t *));
216 src->next_token = 0;
217 src->source = (svn_string_t *)text;
219 for (startp = curp = text->data, endp = curp + text->len;
220 curp != endp; curp++)
222 if (curp != endp && *curp == '\r' && *(curp + 1) == '\n')
223 curp++;
225 if (*curp == '\r' || *curp == '\n')
227 APR_ARRAY_PUSH(src->tokens, svn_string_t *) =
228 svn_string_ncreate(startp, curp - startp + 1, pool);
230 startp = curp + 1;
234 /* If there's anything remaining (ie last line doesn't have a newline) */
235 if (startp != endp)
237 APR_ARRAY_PUSH(src->tokens, svn_string_t *) =
238 svn_string_ncreate(startp, endp - startp, pool);
239 src->ends_without_eol = TRUE;
241 else
242 src->ends_without_eol = FALSE;
246 static void
247 alloc_normalization_bufs(diff_mem_baton_t *btn,
248 int sources,
249 apr_pool_t *pool)
251 apr_size_t max_len = 0;
252 apr_off_t idx;
253 int i;
255 for (i = 0; i < sources; i++)
257 apr_array_header_t *tokens = btn->sources[i].tokens;
258 if (tokens->nelts > 0)
259 for (idx = 0; idx < tokens->nelts; idx++)
261 apr_size_t token_len
262 = APR_ARRAY_IDX(tokens, idx, svn_string_t *)->len;
263 max_len = (max_len < token_len) ? token_len : max_len;
267 btn->normalization_buf[0] = apr_palloc(pool, max_len);
268 btn->normalization_buf[1] = apr_palloc(pool, max_len);
271 svn_error_t *
272 svn_diff_mem_string_diff(svn_diff_t **diff,
273 const svn_string_t *original,
274 const svn_string_t *modified,
275 const svn_diff_file_options_t *options,
276 apr_pool_t *pool)
278 diff_mem_baton_t baton;
280 fill_source_tokens(&(baton.sources[0]), original, pool);
281 fill_source_tokens(&(baton.sources[1]), modified, pool);
282 alloc_normalization_bufs(&baton, 2, pool);
284 baton.normalization_options = options;
286 return svn_diff_diff(diff, &baton, &svn_diff__mem_vtable, pool);
289 svn_error_t *
290 svn_diff_mem_string_diff3(svn_diff_t **diff,
291 const svn_string_t *original,
292 const svn_string_t *modified,
293 const svn_string_t *latest,
294 const svn_diff_file_options_t *options,
295 apr_pool_t *pool)
297 diff_mem_baton_t baton;
299 fill_source_tokens(&(baton.sources[0]), original, pool);
300 fill_source_tokens(&(baton.sources[1]), modified, pool);
301 fill_source_tokens(&(baton.sources[2]), latest, pool);
302 alloc_normalization_bufs(&baton, 3, pool);
304 baton.normalization_options = options;
306 return svn_diff_diff3(diff, &baton, &svn_diff__mem_vtable, pool);
310 svn_error_t *
311 svn_diff_mem_string_diff4(svn_diff_t **diff,
312 const svn_string_t *original,
313 const svn_string_t *modified,
314 const svn_string_t *latest,
315 const svn_string_t *ancestor,
316 const svn_diff_file_options_t *options,
317 apr_pool_t *pool)
319 diff_mem_baton_t baton;
321 fill_source_tokens(&(baton.sources[0]), original, pool);
322 fill_source_tokens(&(baton.sources[1]), modified, pool);
323 fill_source_tokens(&(baton.sources[2]), latest, pool);
324 fill_source_tokens(&(baton.sources[3]), ancestor, pool);
325 alloc_normalization_bufs(&baton, 4, pool);
327 baton.normalization_options = options;
329 return svn_diff_diff4(diff, &baton, &svn_diff__mem_vtable, pool);
333 typedef enum unified_output_e
335 unified_output_context = 0,
336 unified_output_delete,
337 unified_output_insert
338 } unified_output_e;
340 /* Baton for generating unified diffs */
341 typedef struct unified_output_baton_t
343 svn_stream_t *output_stream;
344 const char *header_encoding;
345 source_tokens_t sources[2]; /* 0 == original; 1 == modified */
346 apr_off_t next_token; /* next token in original source */
348 /* Cached markers, in header_encoding,
349 indexed using unified_output_e */
350 const char *prefix_str[3];
352 svn_stringbuf_t *hunk; /* in-progress hunk data */
353 apr_off_t hunk_length[2]; /* 0 == original; 1 == modified */
354 apr_off_t hunk_start[2]; /* 0 == original; 1 == modified */
356 /* Pool for allocation of temporary memory in the callbacks
357 Should be cleared on entry of each iteration of a callback */
358 apr_pool_t *pool;
359 } output_baton_t;
362 /* Append tokens (lines) FIRST up to PAST_LAST
363 from token-source index TOKENS with change-type TYPE
364 to the current hunk.
366 static svn_error_t *
367 output_unified_token_range(output_baton_t *btn,
368 int tokens,
369 unified_output_e type,
370 apr_off_t first,
371 apr_off_t past_last)
373 source_tokens_t *source = &btn->sources[tokens];
374 apr_off_t idx;
376 past_last = (past_last > source->tokens->nelts)
377 ? source->tokens->nelts : past_last;
379 if (tokens == 0)
380 /* We get context from the original source, don't expect
381 to be asked to output a block which starts before
382 what we already have written. */
383 first = (first < btn->next_token) ? btn->next_token : first;
385 if (first >= past_last)
386 return SVN_NO_ERROR;
388 /* Do the loop with prefix and token */
389 for (idx = first; idx < past_last; idx++)
391 svn_string_t *token
392 = APR_ARRAY_IDX(source->tokens, idx, svn_string_t *);
393 svn_stringbuf_appendcstr(btn->hunk, btn->prefix_str[type]);
394 svn_stringbuf_appendbytes(btn->hunk, token->data, token->len);
396 if (type == unified_output_context)
398 btn->hunk_length[0]++;
399 btn->hunk_length[1]++;
401 else if (type == unified_output_delete)
402 btn->hunk_length[0]++;
403 else
404 btn->hunk_length[1]++;
407 if (past_last == source->tokens->nelts && source->ends_without_eol)
409 const char *out_str;
410 SVN_ERR(svn_utf_cstring_from_utf8_ex2
411 (&out_str,
412 /* The string below is intentionally not marked for translation:
413 it's vital to correct operation of the diff(1)/patch(1)
414 program pair. */
415 APR_EOL_STR "\\ No newline at end of file" APR_EOL_STR,
416 btn->header_encoding, btn->pool));
417 svn_stringbuf_appendcstr(btn->hunk, out_str);
420 if (tokens == 0)
421 btn->next_token = past_last;
423 return SVN_NO_ERROR;
426 /* Flush the hunk currently built up in BATON
427 into the baton's output_stream */
428 static svn_error_t *
429 output_unified_flush_hunk(output_baton_t *baton)
431 apr_off_t target_token;
432 apr_size_t hunk_len;
434 if (svn_stringbuf_isempty(baton->hunk))
435 return SVN_NO_ERROR;
437 svn_pool_clear(baton->pool);
439 /* Write the trailing context */
440 target_token = baton->hunk_start[0] + baton->hunk_length[0]
441 + SVN_DIFF__UNIFIED_CONTEXT_SIZE;
442 SVN_ERR(output_unified_token_range(baton, 0 /*original*/,
443 unified_output_context,
444 baton->next_token, target_token));
446 /* Write the hunk header */
447 if (baton->hunk_length[0] > 0)
448 /* Convert our 0-based line numbers into unidiff 1-based numbers */
449 baton->hunk_start[0]++;
450 SVN_ERR(svn_stream_printf_from_utf8
451 (baton->output_stream, baton->header_encoding,
452 baton->pool,
453 /* Hunk length 1 is implied, don't show the
454 length field if we have a hunk that long */
455 (baton->hunk_length[0] == 1)
456 ? ("@@ -%" APR_OFF_T_FMT)
457 : ("@@ -%" APR_OFF_T_FMT ",%" APR_OFF_T_FMT),
458 baton->hunk_start[0], baton->hunk_length[0]));
460 if (baton->hunk_length[1] > 0)
461 /* Convert our 0-based line numbers into unidiff 1-based numbers */
462 baton->hunk_start[1]++;
463 SVN_ERR(svn_stream_printf_from_utf8
464 (baton->output_stream, baton->header_encoding,
465 baton->pool,
466 /* Hunk length 1 is implied, don't show the
467 length field if we have a hunk that long */
468 (baton->hunk_length[1] == 1)
469 ? (" +%" APR_OFF_T_FMT " @@" APR_EOL_STR)
470 : (" +%" APR_OFF_T_FMT ",%" APR_OFF_T_FMT " @@" APR_EOL_STR),
471 baton->hunk_start[1], baton->hunk_length[1]));
473 hunk_len = baton->hunk->len;
474 SVN_ERR(svn_stream_write(baton->output_stream,
475 baton->hunk->data, &hunk_len));
477 baton->hunk_length[0] = baton->hunk_length[1] = 0;
478 svn_stringbuf_setempty(baton->hunk);
480 return SVN_NO_ERROR;
483 /* Implements svn_diff_output_fns_t::output_diff_modified */
484 static svn_error_t *
485 output_unified_diff_modified(void *baton,
486 apr_off_t original_start,
487 apr_off_t original_length,
488 apr_off_t modified_start,
489 apr_off_t modified_length,
490 apr_off_t latest_start,
491 apr_off_t latest_length)
493 output_baton_t *btn = baton;
494 apr_off_t targ_orig, targ_mod;
496 targ_orig = original_start - SVN_DIFF__UNIFIED_CONTEXT_SIZE;
497 targ_orig = (targ_orig < 0) ? 0 : targ_orig;
498 targ_mod = modified_start;
500 if (btn->next_token + SVN_DIFF__UNIFIED_CONTEXT_SIZE < targ_orig)
501 SVN_ERR(output_unified_flush_hunk(btn));
503 if (btn->hunk_length[0] == 0
504 && btn->hunk_length[1] == 0)
506 btn->hunk_start[0] = targ_orig;
507 btn->hunk_start[1] = targ_mod + targ_orig - original_start;
510 SVN_ERR(output_unified_token_range(btn, 0/*original*/,
511 unified_output_context,
512 targ_orig, original_start));
513 SVN_ERR(output_unified_token_range(btn, 0/*original*/,
514 unified_output_delete,
515 original_start,
516 original_start + original_length));
517 return output_unified_token_range(btn, 1/*modified*/, unified_output_insert,
518 modified_start,
519 modified_start + modified_length);
522 static const svn_diff_output_fns_t mem_output_unified_vtable =
524 NULL, /* output_common */
525 output_unified_diff_modified,
526 NULL, /* output_diff_latest */
527 NULL, /* output_diff_common */
528 NULL /* output_conflict */
532 svn_error_t *
533 svn_diff_mem_string_output_unified(svn_stream_t *output_stream,
534 svn_diff_t *diff,
535 const char *original_header,
536 const char *modified_header,
537 const char *header_encoding,
538 const svn_string_t *original,
539 const svn_string_t *modified,
540 apr_pool_t *pool)
543 if (svn_diff_contains_diffs(diff))
545 output_baton_t baton;
547 memset(&baton, 0, sizeof(baton));
548 baton.output_stream = output_stream;
549 baton.pool = svn_pool_create(pool);
550 baton.header_encoding = header_encoding;
551 baton.hunk = svn_stringbuf_create("", pool);
553 SVN_ERR(svn_utf_cstring_from_utf8_ex2
554 (&(baton.prefix_str[unified_output_context]), " ",
555 header_encoding, pool));
556 SVN_ERR(svn_utf_cstring_from_utf8_ex2
557 (&(baton.prefix_str[unified_output_delete]), "-",
558 header_encoding, pool));
559 SVN_ERR(svn_utf_cstring_from_utf8_ex2
560 (&(baton.prefix_str[unified_output_insert]), "+",
561 header_encoding, pool));
563 fill_source_tokens(&baton.sources[0], original, pool);
564 fill_source_tokens(&baton.sources[1], modified, pool);
566 SVN_ERR(svn_stream_printf_from_utf8
567 (output_stream, header_encoding, pool,
568 "--- %s" APR_EOL_STR
569 "+++ %s" APR_EOL_STR,
570 original_header, modified_header));
572 SVN_ERR(svn_diff_output(diff, &baton,
573 &mem_output_unified_vtable));
574 SVN_ERR(output_unified_flush_hunk(&baton));
576 svn_pool_destroy(baton.pool);
579 return SVN_NO_ERROR;
584 /* diff3 merge output */
586 /* A stream to remember *leading* context. Note that this stream does
587 *not* copy the data that it is remembering; it just saves
588 *pointers! */
589 typedef struct {
590 svn_stream_t *stream;
591 const char *data[SVN_DIFF__UNIFIED_CONTEXT_SIZE];
592 apr_size_t len[SVN_DIFF__UNIFIED_CONTEXT_SIZE];
593 apr_size_t next_slot;
594 apr_size_t total_written;
595 } context_saver_t;
598 static svn_error_t *
599 context_saver_stream_write(void *baton,
600 const char *data,
601 apr_size_t *len)
603 context_saver_t *cs = baton;
604 cs->data[cs->next_slot] = data;
605 cs->len[cs->next_slot] = *len;
606 cs->next_slot = (cs->next_slot + 1) % SVN_DIFF__UNIFIED_CONTEXT_SIZE;
607 cs->total_written++;
608 return SVN_NO_ERROR;
612 typedef struct merge_output_baton_t
614 svn_stream_t *output_stream;
616 /* Tokenized source text */
617 source_tokens_t sources[3];
618 apr_off_t next_token;
620 /* Markers for marking conflicted sections */
621 const char *markers[4]; /* 0 = original, 1 = modified,
622 2 = separator, 3 = latest (end) */
623 const char *marker_eol;
625 svn_diff_conflict_display_style_t conflict_style;
627 /* The rest of the fields are for
628 svn_diff_conflict_display_only_conflicts only. Note that for
629 these batons, OUTPUT_STREAM is either CONTEXT_SAVER->STREAM or
630 (soon after a conflict) a "trailing context stream", never the
631 actual output stream.*/
632 /* The actual output stream. */
633 svn_stream_t *real_output_stream;
634 context_saver_t *context_saver;
635 /* Used to allocate context_saver and trailing context streams, and
636 for some printfs. */
637 apr_pool_t *pool;
638 } merge_output_baton_t;
641 static svn_error_t *
642 flush_context_saver(context_saver_t *cs,
643 svn_stream_t *output_stream)
645 int i;
646 for (i = 0; i < SVN_DIFF__UNIFIED_CONTEXT_SIZE; i++)
648 int slot = (i + cs->next_slot) % SVN_DIFF__UNIFIED_CONTEXT_SIZE;
649 if (cs->data[slot])
651 apr_size_t len = cs->len[slot];
652 SVN_ERR(svn_stream_write(output_stream, cs->data[slot], &len));
655 return SVN_NO_ERROR;
659 static void
660 make_context_saver(merge_output_baton_t *mob)
662 context_saver_t *cs;
664 svn_pool_clear(mob->pool);
665 cs = apr_pcalloc(mob->pool, sizeof(*cs));
666 cs->stream = svn_stream_empty(mob->pool);
667 svn_stream_set_baton(cs->stream, cs);
668 svn_stream_set_write(cs->stream, context_saver_stream_write);
669 mob->context_saver = cs;
670 mob->output_stream = cs->stream;
674 /* A stream which prints SVN_DIFF__UNIFIED_CONTEXT_SIZE lines to
675 BATON->REAL_OUTPUT_STREAM, and then changes BATON->OUTPUT_STREAM to
676 a context_saver; used for *trailing* context. */
678 struct trailing_context_printer {
679 apr_size_t lines_to_print;
680 merge_output_baton_t *mob;
684 static svn_error_t *
685 trailing_context_printer_write(void *baton,
686 const char *data,
687 apr_size_t *len)
689 struct trailing_context_printer *tcp = baton;
690 SVN_ERR_ASSERT(tcp->lines_to_print > 0);
691 SVN_ERR(svn_stream_write(tcp->mob->real_output_stream, data, len));
692 tcp->lines_to_print--;
693 if (tcp->lines_to_print == 0)
694 make_context_saver(tcp->mob);
695 return SVN_NO_ERROR;
699 static void
700 make_trailing_context_printer(merge_output_baton_t *btn)
702 struct trailing_context_printer *tcp;
703 svn_stream_t *s;
705 svn_pool_clear(btn->pool);
707 tcp = apr_pcalloc(btn->pool, sizeof(*tcp));
708 tcp->lines_to_print = SVN_DIFF__UNIFIED_CONTEXT_SIZE;
709 tcp->mob = btn;
710 s = svn_stream_empty(btn->pool);
711 svn_stream_set_baton(s, tcp);
712 svn_stream_set_write(s, trailing_context_printer_write);
713 btn->output_stream = s;
717 static svn_error_t *
718 output_merge_token_range(apr_size_t *lines_printed_p,
719 merge_output_baton_t *btn,
720 int idx, apr_off_t first,
721 apr_off_t length)
723 apr_array_header_t *tokens = btn->sources[idx].tokens;
724 apr_size_t lines_printed = 0;
726 for (; length > 0 && first < tokens->nelts; length--, first++)
728 svn_string_t *token = APR_ARRAY_IDX(tokens, first, svn_string_t *);
729 apr_size_t len = token->len;
731 /* Note that the trailing context printer assumes that
732 svn_stream_write is called exactly once per line. */
733 SVN_ERR(svn_stream_write(btn->output_stream, token->data, &len));
734 lines_printed++;
737 if (lines_printed_p)
738 *lines_printed_p = lines_printed;
740 return SVN_NO_ERROR;
743 static svn_error_t *
744 output_marker_eol(merge_output_baton_t *btn)
746 apr_size_t len = strlen(btn->marker_eol);
747 return svn_stream_write(btn->output_stream, btn->marker_eol, &len);
750 static svn_error_t *
751 output_merge_marker(merge_output_baton_t *btn, int idx)
753 apr_size_t len = strlen(btn->markers[idx]);
754 SVN_ERR(svn_stream_write(btn->output_stream, btn->markers[idx], &len));
755 return output_marker_eol(btn);
758 static svn_error_t *
759 output_common_modified(void *baton,
760 apr_off_t original_start, apr_off_t original_length,
761 apr_off_t modified_start, apr_off_t modified_length,
762 apr_off_t latest_start, apr_off_t latest_length)
764 return output_merge_token_range(NULL, baton, 1/*modified*/,
765 modified_start, modified_length);
768 static svn_error_t *
769 output_latest(void *baton,
770 apr_off_t original_start, apr_off_t original_length,
771 apr_off_t modified_start, apr_off_t modified_length,
772 apr_off_t latest_start, apr_off_t latest_length)
774 return output_merge_token_range(NULL, baton, 2/*latest*/,
775 latest_start, latest_length);
778 static svn_error_t *
779 output_conflict(void *baton,
780 apr_off_t original_start, apr_off_t original_length,
781 apr_off_t modified_start, apr_off_t modified_length,
782 apr_off_t latest_start, apr_off_t latest_length,
783 svn_diff_t *diff);
785 static const svn_diff_output_fns_t merge_output_vtable =
787 output_common_modified, /* common */
788 output_common_modified, /* modified */
789 output_latest,
790 output_common_modified, /* output_diff_common */
791 output_conflict
794 static svn_error_t *
795 output_conflict(void *baton,
796 apr_off_t original_start, apr_off_t original_length,
797 apr_off_t modified_start, apr_off_t modified_length,
798 apr_off_t latest_start, apr_off_t latest_length,
799 svn_diff_t *diff)
801 merge_output_baton_t *btn = baton;
803 svn_diff_conflict_display_style_t style = btn->conflict_style;
805 if (style == svn_diff_conflict_display_resolved_modified_latest)
807 if (diff)
808 return svn_diff_output(diff, baton, &merge_output_vtable);
809 else
810 style = svn_diff_conflict_display_modified_latest;
813 if (style == svn_diff_conflict_display_modified_latest ||
814 style == svn_diff_conflict_display_modified_original_latest)
816 SVN_ERR(output_merge_marker(btn, 1/*modified*/));
817 SVN_ERR(output_merge_token_range(NULL, btn, 1/*modified*/,
818 modified_start, modified_length));
820 if (style == svn_diff_conflict_display_modified_original_latest)
822 SVN_ERR(output_merge_marker(btn, 0/*original*/));
823 SVN_ERR(output_merge_token_range(NULL, btn, 0/*original*/,
824 original_start, original_length));
827 SVN_ERR(output_merge_marker(btn, 2/*separator*/));
828 SVN_ERR(output_merge_token_range(NULL, btn, 2/*latest*/,
829 latest_start, latest_length));
830 SVN_ERR(output_merge_marker(btn, 3/*latest (end)*/));
832 else if (style == svn_diff_conflict_display_modified)
833 SVN_ERR(output_merge_token_range(NULL, btn, 1/*modified*/,
834 modified_start, modified_length));
835 else if (style == svn_diff_conflict_display_latest)
836 SVN_ERR(output_merge_token_range(NULL, btn, 2/*latest*/,
837 latest_start, latest_length));
838 else /* unknown style */
839 SVN_ERR_MALFUNCTION();
841 return SVN_NO_ERROR;
845 static svn_error_t *
846 output_conflict_with_context(void *baton,
847 apr_off_t original_start,
848 apr_off_t original_length,
849 apr_off_t modified_start,
850 apr_off_t modified_length,
851 apr_off_t latest_start,
852 apr_off_t latest_length,
853 svn_diff_t *diff)
855 merge_output_baton_t *btn = baton;
857 /* Are we currently saving starting context (as opposed to printing
858 trailing context)? If so, flush it. */
859 if (btn->output_stream == btn->context_saver->stream)
861 if (btn->context_saver->total_written > SVN_DIFF__UNIFIED_CONTEXT_SIZE)
862 SVN_ERR(svn_stream_printf(btn->real_output_stream, btn->pool, "@@\n"));
863 SVN_ERR(flush_context_saver(btn->context_saver, btn->real_output_stream));
866 /* Print to the real output stream. */
867 btn->output_stream = btn->real_output_stream;
869 /* Output the conflict itself. */
870 SVN_ERR(svn_stream_printf(btn->output_stream, btn->pool,
871 (modified_length == 1
872 ? "%s (%" APR_OFF_T_FMT ")"
873 : "%s (%" APR_OFF_T_FMT ",%" APR_OFF_T_FMT ")"),
874 btn->markers[1],
875 modified_start + 1, modified_length));
876 SVN_ERR(output_marker_eol(btn));
877 SVN_ERR(output_merge_token_range(NULL, btn, 1/*modified*/,
878 modified_start, modified_length));
880 SVN_ERR(svn_stream_printf(btn->output_stream, btn->pool,
881 (original_length == 1
882 ? "%s (%" APR_OFF_T_FMT ")"
883 : "%s (%" APR_OFF_T_FMT ",%" APR_OFF_T_FMT ")"),
884 btn->markers[0],
885 original_start + 1, original_length));
886 SVN_ERR(output_marker_eol(btn));
887 SVN_ERR(output_merge_token_range(NULL, btn, 0/*original*/,
888 original_start, original_length));
890 SVN_ERR(output_merge_marker(btn, 2/*separator*/));
891 SVN_ERR(output_merge_token_range(NULL, btn, 2/*latest*/,
892 latest_start, latest_length));
893 SVN_ERR(svn_stream_printf(btn->output_stream, btn->pool,
894 (latest_length == 1
895 ? "%s (%" APR_OFF_T_FMT ")"
896 : "%s (%" APR_OFF_T_FMT ",%" APR_OFF_T_FMT ")"),
897 btn->markers[3],
898 latest_start + 1, latest_length));
899 SVN_ERR(output_marker_eol(btn));
901 /* Go into print-trailing-context mode instead. */
902 make_trailing_context_printer(btn);
904 return SVN_NO_ERROR;
908 static const svn_diff_output_fns_t merge_only_conflicts_output_vtable =
910 output_common_modified,
911 output_common_modified,
912 output_latest,
913 output_common_modified,
914 output_conflict_with_context
918 /* TOKEN is the first token in the modified file.
919 Return its line-ending, if any. */
920 static const char *
921 detect_eol(svn_string_t *token)
923 const char *curp;
925 if (token->len == 0)
926 return NULL;
928 curp = token->data + token->len - 1;
929 if (*curp == '\r')
930 return "\r";
931 else if (*curp != '\n')
932 return NULL;
933 else
935 if (token->len == 1
936 || *(--curp) != '\r')
937 return "\n";
938 else
939 return "\r\n";
943 svn_error_t *
944 svn_diff_mem_string_output_merge2(svn_stream_t *output_stream,
945 svn_diff_t *diff,
946 const svn_string_t *original,
947 const svn_string_t *modified,
948 const svn_string_t *latest,
949 const char *conflict_original,
950 const char *conflict_modified,
951 const char *conflict_latest,
952 const char *conflict_separator,
953 svn_diff_conflict_display_style_t style,
954 apr_pool_t *pool)
956 merge_output_baton_t btn;
957 const char *eol;
958 svn_boolean_t conflicts_only =
959 (style == svn_diff_conflict_display_only_conflicts);
960 const svn_diff_output_fns_t *vtable = conflicts_only
961 ? &merge_only_conflicts_output_vtable : &merge_output_vtable;
963 memset(&btn, 0, sizeof(btn));
965 if (conflicts_only)
967 btn.pool = svn_pool_create(pool);
968 make_context_saver(&btn);
969 btn.real_output_stream = output_stream;
971 else
972 btn.output_stream = output_stream;
974 fill_source_tokens(&(btn.sources[0]), original, pool);
975 fill_source_tokens(&(btn.sources[1]), modified, pool);
976 fill_source_tokens(&(btn.sources[2]), latest, pool);
978 btn.conflict_style = style;
980 if (btn.sources[1].tokens->nelts > 0)
982 eol = detect_eol(APR_ARRAY_IDX(btn.sources[1].tokens, 0, svn_string_t *));
983 if (!eol)
984 eol = APR_EOL_STR; /* use the platform default */
986 else
987 eol = APR_EOL_STR; /* use the platform default */
989 btn.marker_eol = eol;
991 SVN_ERR(svn_utf_cstring_from_utf8(&btn.markers[1],
992 conflict_modified
993 ? conflict_modified
994 : "<<<<<<< (modified)",
995 pool));
996 SVN_ERR(svn_utf_cstring_from_utf8(&btn.markers[0],
997 conflict_original
998 ? conflict_original
999 : "||||||| (original)",
1000 pool));
1001 SVN_ERR(svn_utf_cstring_from_utf8(&btn.markers[2],
1002 conflict_separator
1003 ? conflict_separator
1004 : "=======",
1005 pool));
1006 SVN_ERR(svn_utf_cstring_from_utf8(&btn.markers[3],
1007 conflict_latest
1008 ? conflict_latest
1009 : ">>>>>>> (latest)",
1010 pool));
1012 SVN_ERR(svn_diff_output(diff, &btn, vtable));
1013 if (conflicts_only)
1014 svn_pool_destroy(btn.pool);
1016 return SVN_NO_ERROR;
1019 svn_error_t *
1020 svn_diff_mem_string_output_merge(svn_stream_t *output_stream,
1021 svn_diff_t *diff,
1022 const svn_string_t *original,
1023 const svn_string_t *modified,
1024 const svn_string_t *latest,
1025 const char *conflict_original,
1026 const char *conflict_modified,
1027 const char *conflict_latest,
1028 const char *conflict_separator,
1029 svn_boolean_t display_original_in_conflict,
1030 svn_boolean_t display_resolved_conflicts,
1031 apr_pool_t *pool)
1033 svn_diff_conflict_display_style_t style =
1034 svn_diff_conflict_display_modified_latest;
1036 if (display_resolved_conflicts)
1037 style = svn_diff_conflict_display_resolved_modified_latest;
1039 if (display_original_in_conflict)
1040 style = svn_diff_conflict_display_modified_original_latest;
1042 return svn_diff_mem_string_output_merge2(output_stream,
1043 diff,
1044 original,
1045 modified,
1046 latest,
1047 conflict_original,
1048 conflict_modified,
1049 conflict_latest,
1050 conflict_separator,
1051 style,
1052 pool);