2 * diff_memory.c : routines for doing diffs on in-memory data
4 * ====================================================================
5 * Licensed to the Apache Software Foundation (ASF) under one
6 * or more contributor license agreements. See the NOTICE file
7 * distributed with this work for additional information
8 * regarding copyright ownership. The ASF licenses this file
9 * to you under the Apache License, Version 2.0 (the
10 * "License"); you may not use this file except in compliance
11 * with the License. You may obtain a copy of the License at
13 * http://www.apache.org/licenses/LICENSE-2.0
15 * Unless required by applicable law or agreed to in writing,
16 * software distributed under the License is distributed on an
17 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
18 * KIND, either express or implied. See the License for the
19 * specific language governing permissions and limitations
21 * ====================================================================
28 #include <apr_tables.h>
33 #include "svn_pools.h"
34 #include "svn_types.h"
35 #include "svn_string.h"
38 #include "svn_private_config.h"
39 #include "private/svn_adler32.h"
40 #include "private/svn_diff_private.h"
42 typedef struct source_tokens_t
44 /* A token simply is an svn_string_t pointing to
45 the data of the in-memory data source, containing
46 the raw token text, with length stored in the string */
47 apr_array_header_t *tokens;
49 /* Next token to be consumed */
50 apr_size_t next_token;
52 /* The source, containing the in-memory data to be diffed */
53 const svn_string_t *source;
55 /* The last token ends with a newline character (sequence) */
56 svn_boolean_t ends_without_eol;
59 typedef struct diff_mem_baton_t
61 /* The tokens for each of the sources */
62 source_tokens_t sources[4];
64 /* Normalization buffer; we only ever compare 2 tokens at the same time */
65 char *normalization_buf[2];
67 /* Options for normalized comparison of the data sources */
68 const svn_diff_file_options_t *normalization_options;
73 datasource_to_index(svn_diff_datasource_e datasource)
77 case svn_diff_datasource_original:
80 case svn_diff_datasource_modified:
83 case svn_diff_datasource_latest:
86 case svn_diff_datasource_ancestor:
94 /* Implements svn_diff_fns2_t::datasources_open */
96 datasources_open(void *baton,
97 apr_off_t *prefix_lines,
98 apr_off_t *suffix_lines,
99 const svn_diff_datasource_e *datasources,
100 apr_size_t datasources_len)
102 /* Do nothing: everything is already there and initialized to 0 */
109 /* Implements svn_diff_fns2_t::datasource_close */
111 datasource_close(void *baton, svn_diff_datasource_e datasource)
113 /* Do nothing. The compare_token function needs previous datasources
114 * to stay available until all datasources are processed.
121 /* Implements svn_diff_fns2_t::datasource_get_next_token */
123 datasource_get_next_token(apr_uint32_t *hash, void **token, void *baton,
124 svn_diff_datasource_e datasource)
126 diff_mem_baton_t *mem_baton = baton;
127 source_tokens_t *src = &(mem_baton->sources[datasource_to_index(datasource)]);
129 if ((apr_size_t)src->tokens->nelts > src->next_token)
131 /* There are actually tokens to be returned */
132 char *buf = mem_baton->normalization_buf[0];
133 svn_string_t *tok = (*token)
134 = APR_ARRAY_IDX(src->tokens, src->next_token, svn_string_t *);
135 apr_off_t len = tok->len;
136 svn_diff__normalize_state_t state
137 = svn_diff__normalize_state_normal;
139 svn_diff__normalize_buffer(&buf, &len, &state, tok->data,
140 mem_baton->normalization_options);
141 *hash = svn__adler32(0, buf, len);
150 /* Implements svn_diff_fns2_t::token_compare */
152 token_compare(void *baton, void *token1, void *token2, int *result)
154 /* Implement the same behaviour as diff_file.c:token_compare(),
155 but be simpler, because we know we'll have all data in memory */
156 diff_mem_baton_t *btn = baton;
157 svn_string_t *t1 = token1;
158 svn_string_t *t2 = token2;
159 char *buf1 = btn->normalization_buf[0];
160 char *buf2 = btn->normalization_buf[1];
161 apr_off_t len1 = t1->len;
162 apr_off_t len2 = t2->len;
163 svn_diff__normalize_state_t state = svn_diff__normalize_state_normal;
165 svn_diff__normalize_buffer(&buf1, &len1, &state, t1->data,
166 btn->normalization_options);
167 state = svn_diff__normalize_state_normal;
168 svn_diff__normalize_buffer(&buf2, &len2, &state, t2->data,
169 btn->normalization_options);
172 *result = (len1 < len2) ? -1 : 1;
174 *result = (len1 == 0) ? 0 : memcmp(buf1, buf2, (size_t) len1);
179 /* Implements svn_diff_fns2_t::token_discard */
181 token_discard(void *baton, void *token)
183 /* No-op, we have no use for discarded tokens... */
187 /* Implements svn_diff_fns2_t::token_discard_all */
189 token_discard_all(void *baton)
192 Note that in the file case, this function discards all
193 tokens allocated, but we're geared toward small in-memory diffs.
194 Meaning that there's no special pool to clear.
199 static const svn_diff_fns2_t svn_diff__mem_vtable =
203 datasource_get_next_token,
209 /* Fill SRC with the diff tokens (e.g. lines).
211 TEXT is assumed to live long enough for the tokens to
212 stay valid during their lifetime: no data is copied,
213 instead, svn_string_t's are allocated pointing straight
217 fill_source_tokens(source_tokens_t *src,
218 const svn_string_t *text,
225 src->tokens = apr_array_make(pool, 0, sizeof(svn_string_t *));
229 for (startp = curp = text->data, endp = curp + text->len;
230 curp != endp; curp++)
232 if (curp != endp && *curp == '\r' && *(curp + 1) == '\n')
235 if (*curp == '\r' || *curp == '\n')
237 APR_ARRAY_PUSH(src->tokens, svn_string_t *) =
238 svn_string_ncreate(startp, curp - startp + 1, pool);
244 /* If there's anything remaining (ie last line doesn't have a newline) */
247 APR_ARRAY_PUSH(src->tokens, svn_string_t *) =
248 svn_string_ncreate(startp, endp - startp, pool);
249 src->ends_without_eol = TRUE;
252 src->ends_without_eol = FALSE;
257 alloc_normalization_bufs(diff_mem_baton_t *btn,
261 apr_size_t max_len = 0;
265 for (i = 0; i < sources; i++)
267 apr_array_header_t *tokens = btn->sources[i].tokens;
268 if (tokens->nelts > 0)
269 for (idx = 0; idx < tokens->nelts; idx++)
272 = APR_ARRAY_IDX(tokens, idx, svn_string_t *)->len;
273 max_len = (max_len < token_len) ? token_len : max_len;
277 btn->normalization_buf[0] = apr_palloc(pool, max_len);
278 btn->normalization_buf[1] = apr_palloc(pool, max_len);
282 svn_diff_mem_string_diff(svn_diff_t **diff,
283 const svn_string_t *original,
284 const svn_string_t *modified,
285 const svn_diff_file_options_t *options,
288 diff_mem_baton_t baton;
290 fill_source_tokens(&(baton.sources[0]), original, pool);
291 fill_source_tokens(&(baton.sources[1]), modified, pool);
292 alloc_normalization_bufs(&baton, 2, pool);
294 baton.normalization_options = options;
296 return svn_diff_diff_2(diff, &baton, &svn_diff__mem_vtable, pool);
300 svn_diff_mem_string_diff3(svn_diff_t **diff,
301 const svn_string_t *original,
302 const svn_string_t *modified,
303 const svn_string_t *latest,
304 const svn_diff_file_options_t *options,
307 diff_mem_baton_t baton;
309 fill_source_tokens(&(baton.sources[0]), original, pool);
310 fill_source_tokens(&(baton.sources[1]), modified, pool);
311 fill_source_tokens(&(baton.sources[2]), latest, pool);
312 alloc_normalization_bufs(&baton, 3, pool);
314 baton.normalization_options = options;
316 return svn_diff_diff3_2(diff, &baton, &svn_diff__mem_vtable, pool);
321 svn_diff_mem_string_diff4(svn_diff_t **diff,
322 const svn_string_t *original,
323 const svn_string_t *modified,
324 const svn_string_t *latest,
325 const svn_string_t *ancestor,
326 const svn_diff_file_options_t *options,
329 diff_mem_baton_t baton;
331 fill_source_tokens(&(baton.sources[0]), original, pool);
332 fill_source_tokens(&(baton.sources[1]), modified, pool);
333 fill_source_tokens(&(baton.sources[2]), latest, pool);
334 fill_source_tokens(&(baton.sources[3]), ancestor, pool);
335 alloc_normalization_bufs(&baton, 4, pool);
337 baton.normalization_options = options;
339 return svn_diff_diff4_2(diff, &baton, &svn_diff__mem_vtable, pool);
343 typedef enum unified_output_e
345 unified_output_context = 0,
346 unified_output_delete,
347 unified_output_insert,
351 /* Baton for generating unified diffs */
352 typedef struct unified_output_baton_t
354 svn_stream_t *output_stream;
355 const char *header_encoding;
356 source_tokens_t sources[2]; /* 0 == original; 1 == modified */
357 apr_off_t current_token[2]; /* current token per source */
359 /* Cached markers, in header_encoding,
360 indexed using unified_output_e */
361 const char *prefix_str[3];
363 svn_stringbuf_t *hunk; /* in-progress hunk data */
364 apr_off_t hunk_length[2]; /* 0 == original; 1 == modified */
365 apr_off_t hunk_start[2]; /* 0 == original; 1 == modified */
367 /* The delimiters of the hunk header, '@@' for text hunks and '##' for
369 const char *hunk_delimiter;
370 /* The string to print after a line that does not end with a newline.
371 * It must start with a '\'. Typically "\ No newline at end of file". */
372 const char *no_newline_string;
374 /* Pool for allocation of temporary memory in the callbacks
375 Should be cleared on entry of each iteration of a callback */
380 /* Append tokens (lines) FIRST up to PAST_LAST
381 from token-source index TOKENS with change-type TYPE
385 output_unified_token_range(output_baton_t *btn,
387 unified_output_e type,
390 source_tokens_t *source = &btn->sources[tokens];
392 if (until > source->tokens->nelts)
393 until = source->tokens->nelts;
395 if (until <= btn->current_token[tokens])
398 /* Do the loop with prefix and token */
401 svn_string_t *token =
402 APR_ARRAY_IDX(source->tokens, btn->current_token[tokens],
405 if (type != unified_output_skip)
407 svn_stringbuf_appendcstr(btn->hunk, btn->prefix_str[type]);
408 svn_stringbuf_appendbytes(btn->hunk, token->data, token->len);
411 if (type == unified_output_context)
413 btn->hunk_length[0]++;
414 btn->hunk_length[1]++;
416 else if (type == unified_output_delete)
417 btn->hunk_length[0]++;
418 else if (type == unified_output_insert)
419 btn->hunk_length[1]++;
421 /* ### TODO: Add skip processing for -p handling? */
423 btn->current_token[tokens]++;
424 if (btn->current_token[tokens] == until)
428 if (btn->current_token[tokens] == source->tokens->nelts
429 && source->ends_without_eol)
433 SVN_ERR(svn_utf_cstring_from_utf8_ex2(
434 &out_str, btn->no_newline_string,
435 btn->header_encoding, btn->pool));
436 svn_stringbuf_appendcstr(btn->hunk, out_str);
444 /* Flush the hunk currently built up in BATON
445 into the BATON's output_stream.
446 Use the specified HUNK_DELIMITER.
447 If HUNK_DELIMITER is NULL, fall back to the default delimiter. */
449 output_unified_flush_hunk(output_baton_t *baton,
450 const char *hunk_delimiter)
452 apr_off_t target_token;
457 if (svn_stringbuf_isempty(baton->hunk))
460 svn_pool_clear(baton->pool);
462 /* Write the trailing context */
463 target_token = baton->hunk_start[0] + baton->hunk_length[0]
464 + SVN_DIFF__UNIFIED_CONTEXT_SIZE;
465 SVN_ERR(output_unified_token_range(baton, 0 /*original*/,
466 unified_output_context,
468 if (hunk_delimiter == NULL)
469 hunk_delimiter = "@@";
471 old_start = baton->hunk_start[0];
472 new_start = baton->hunk_start[1];
474 /* If the file is non-empty, convert the line indexes from
475 zero based to one based */
476 if (baton->hunk_length[0])
478 if (baton->hunk_length[1])
481 /* Write the hunk header */
482 SVN_ERR(svn_diff__unified_write_hunk_header(
483 baton->output_stream, baton->header_encoding, hunk_delimiter,
484 old_start, baton->hunk_length[0],
485 new_start, baton->hunk_length[1],
486 NULL /* hunk_extra_context */,
489 hunk_len = baton->hunk->len;
490 SVN_ERR(svn_stream_write(baton->output_stream,
491 baton->hunk->data, &hunk_len));
493 /* Prepare for the next hunk */
494 baton->hunk_length[0] = 0;
495 baton->hunk_length[1] = 0;
496 baton->hunk_start[0] = 0;
497 baton->hunk_start[1] = 0;
498 svn_stringbuf_setempty(baton->hunk);
503 /* Implements svn_diff_output_fns_t::output_diff_modified */
505 output_unified_diff_modified(void *baton,
506 apr_off_t original_start,
507 apr_off_t original_length,
508 apr_off_t modified_start,
509 apr_off_t modified_length,
510 apr_off_t latest_start,
511 apr_off_t latest_length)
513 output_baton_t *output_baton = baton;
514 apr_off_t context_prefix_length;
515 apr_off_t prev_context_end;
516 svn_boolean_t init_hunk = FALSE;
518 if (original_start > SVN_DIFF__UNIFIED_CONTEXT_SIZE)
519 context_prefix_length = SVN_DIFF__UNIFIED_CONTEXT_SIZE;
521 context_prefix_length = original_start;
523 /* Calculate where the previous hunk will end if we would write it now
524 (including the necessary context at the end) */
525 if (output_baton->hunk_length[0] > 0 || output_baton->hunk_length[1] > 0)
527 prev_context_end = output_baton->hunk_start[0]
528 + output_baton->hunk_length[0]
529 + SVN_DIFF__UNIFIED_CONTEXT_SIZE;
533 prev_context_end = -1;
535 if (output_baton->hunk_start[0] == 0
536 && (original_length > 0 || modified_length > 0))
540 /* If the changed range is far enough from the previous range, flush the current
543 apr_off_t new_hunk_start = (original_start - context_prefix_length);
545 if (output_baton->current_token[0] < new_hunk_start
546 && prev_context_end <= new_hunk_start)
548 SVN_ERR(output_unified_flush_hunk(output_baton,
549 output_baton->hunk_delimiter));
552 else if (output_baton->hunk_length[0] > 0
553 || output_baton->hunk_length[1] > 0)
555 /* We extend the current hunk */
557 /* Original: Output the context preceding the changed range */
558 SVN_ERR(output_unified_token_range(output_baton, 0 /* original */,
559 unified_output_context,
564 /* Original: Skip lines until we are at the beginning of the context we want
566 SVN_ERR(output_unified_token_range(output_baton, 0 /* original */,
568 original_start - context_prefix_length));
572 SVN_ERR_ASSERT(output_baton->hunk_length[0] == 0
573 && output_baton->hunk_length[1] == 0);
575 output_baton->hunk_start[0] = original_start - context_prefix_length;
576 output_baton->hunk_start[1] = modified_start - context_prefix_length;
579 /* Modified: Skip lines until we are at the start of the changed range */
580 SVN_ERR(output_unified_token_range(output_baton, 1 /* modified */,
584 /* Original: Output the context preceding the changed range */
585 SVN_ERR(output_unified_token_range(output_baton, 0 /* original */,
586 unified_output_context,
589 /* Both: Output the changed range */
590 SVN_ERR(output_unified_token_range(output_baton, 0 /* original */,
591 unified_output_delete,
592 original_start + original_length));
593 SVN_ERR(output_unified_token_range(output_baton, 1 /* modified */,
594 unified_output_insert,
595 modified_start + modified_length));
600 static const svn_diff_output_fns_t mem_output_unified_vtable =
602 NULL, /* output_common */
603 output_unified_diff_modified,
604 NULL, /* output_diff_latest */
605 NULL, /* output_diff_common */
606 NULL /* output_conflict */
611 svn_diff_mem_string_output_unified2(svn_stream_t *output_stream,
613 svn_boolean_t with_diff_header,
614 const char *hunk_delimiter,
615 const char *original_header,
616 const char *modified_header,
617 const char *header_encoding,
618 const svn_string_t *original,
619 const svn_string_t *modified,
623 if (svn_diff_contains_diffs(diff))
625 output_baton_t baton;
627 memset(&baton, 0, sizeof(baton));
628 baton.output_stream = output_stream;
629 baton.pool = svn_pool_create(pool);
630 baton.header_encoding = header_encoding;
631 baton.hunk = svn_stringbuf_create_empty(pool);
632 baton.hunk_delimiter = hunk_delimiter;
633 baton.no_newline_string
634 = (hunk_delimiter == NULL || strcmp(hunk_delimiter, "##") != 0)
635 ? APR_EOL_STR SVN_DIFF__NO_NEWLINE_AT_END_OF_FILE APR_EOL_STR
636 : APR_EOL_STR SVN_DIFF__NO_NEWLINE_AT_END_OF_PROPERTY APR_EOL_STR;
638 SVN_ERR(svn_utf_cstring_from_utf8_ex2
639 (&(baton.prefix_str[unified_output_context]), " ",
640 header_encoding, pool));
641 SVN_ERR(svn_utf_cstring_from_utf8_ex2
642 (&(baton.prefix_str[unified_output_delete]), "-",
643 header_encoding, pool));
644 SVN_ERR(svn_utf_cstring_from_utf8_ex2
645 (&(baton.prefix_str[unified_output_insert]), "+",
646 header_encoding, pool));
648 fill_source_tokens(&baton.sources[0], original, pool);
649 fill_source_tokens(&baton.sources[1], modified, pool);
651 if (with_diff_header)
653 SVN_ERR(svn_diff__unidiff_write_header(
654 output_stream, header_encoding,
655 original_header, modified_header, pool));
658 SVN_ERR(svn_diff_output(diff, &baton,
659 &mem_output_unified_vtable));
661 SVN_ERR(output_unified_flush_hunk(&baton, hunk_delimiter));
663 svn_pool_destroy(baton.pool);
670 svn_diff_mem_string_output_unified(svn_stream_t *output_stream,
672 const char *original_header,
673 const char *modified_header,
674 const char *header_encoding,
675 const svn_string_t *original,
676 const svn_string_t *modified,
679 SVN_ERR(svn_diff_mem_string_output_unified2(output_stream,
694 /* diff3 merge output */
696 /* A stream to remember *leading* context. Note that this stream does
697 *not* copy the data that it is remembering; it just saves
699 typedef struct context_saver_t {
700 svn_stream_t *stream;
701 const char *data[SVN_DIFF__UNIFIED_CONTEXT_SIZE];
702 apr_size_t len[SVN_DIFF__UNIFIED_CONTEXT_SIZE];
703 apr_size_t next_slot;
704 apr_size_t total_written;
709 context_saver_stream_write(void *baton,
713 context_saver_t *cs = baton;
714 cs->data[cs->next_slot] = data;
715 cs->len[cs->next_slot] = *len;
716 cs->next_slot = (cs->next_slot + 1) % SVN_DIFF__UNIFIED_CONTEXT_SIZE;
722 typedef struct merge_output_baton_t
724 svn_stream_t *output_stream;
726 /* Tokenized source text */
727 source_tokens_t sources[3];
728 apr_off_t next_token[3];
730 /* Markers for marking conflicted sections */
731 const char *markers[4]; /* 0 = original, 1 = modified,
732 2 = separator, 3 = latest (end) */
733 const char *marker_eol;
735 svn_diff_conflict_display_style_t conflict_style;
737 /* The rest of the fields are for
738 svn_diff_conflict_display_only_conflicts only. Note that for
739 these batons, OUTPUT_STREAM is either CONTEXT_SAVER->STREAM or
740 (soon after a conflict) a "trailing context stream", never the
741 actual output stream.*/
742 /* The actual output stream. */
743 svn_stream_t *real_output_stream;
744 context_saver_t *context_saver;
745 /* Used to allocate context_saver and trailing context streams, and
748 } merge_output_baton_t;
752 flush_context_saver(context_saver_t *cs,
753 svn_stream_t *output_stream)
756 for (i = 0; i < SVN_DIFF__UNIFIED_CONTEXT_SIZE; i++)
758 apr_size_t slot = (i + cs->next_slot) % SVN_DIFF__UNIFIED_CONTEXT_SIZE;
761 apr_size_t len = cs->len[slot];
762 SVN_ERR(svn_stream_write(output_stream, cs->data[slot], &len));
770 make_context_saver(merge_output_baton_t *mob)
774 svn_pool_clear(mob->pool);
775 cs = apr_pcalloc(mob->pool, sizeof(*cs));
776 cs->stream = svn_stream_empty(mob->pool);
777 svn_stream_set_baton(cs->stream, cs);
778 svn_stream_set_write(cs->stream, context_saver_stream_write);
779 mob->context_saver = cs;
780 mob->output_stream = cs->stream;
784 /* A stream which prints SVN_DIFF__UNIFIED_CONTEXT_SIZE lines to
785 BATON->REAL_OUTPUT_STREAM, and then changes BATON->OUTPUT_STREAM to
786 a context_saver; used for *trailing* context. */
788 struct trailing_context_printer {
789 apr_size_t lines_to_print;
790 merge_output_baton_t *mob;
795 trailing_context_printer_write(void *baton,
799 struct trailing_context_printer *tcp = baton;
800 SVN_ERR_ASSERT(tcp->lines_to_print > 0);
801 SVN_ERR(svn_stream_write(tcp->mob->real_output_stream, data, len));
802 tcp->lines_to_print--;
803 if (tcp->lines_to_print == 0)
804 make_context_saver(tcp->mob);
810 make_trailing_context_printer(merge_output_baton_t *btn)
812 struct trailing_context_printer *tcp;
815 svn_pool_clear(btn->pool);
817 tcp = apr_pcalloc(btn->pool, sizeof(*tcp));
818 tcp->lines_to_print = SVN_DIFF__UNIFIED_CONTEXT_SIZE;
820 s = svn_stream_empty(btn->pool);
821 svn_stream_set_baton(s, tcp);
822 svn_stream_set_write(s, trailing_context_printer_write);
823 btn->output_stream = s;
828 output_merge_token_range(apr_size_t *lines_printed_p,
829 merge_output_baton_t *btn,
830 int idx, apr_off_t first,
833 apr_array_header_t *tokens = btn->sources[idx].tokens;
834 apr_size_t lines_printed = 0;
836 for (; length > 0 && first < tokens->nelts; length--, first++)
838 svn_string_t *token = APR_ARRAY_IDX(tokens, first, svn_string_t *);
839 apr_size_t len = token->len;
841 /* Note that the trailing context printer assumes that
842 svn_stream_write is called exactly once per line. */
843 SVN_ERR(svn_stream_write(btn->output_stream, token->data, &len));
848 *lines_printed_p = lines_printed;
854 output_marker_eol(merge_output_baton_t *btn)
856 return svn_stream_puts(btn->output_stream, btn->marker_eol);
860 output_merge_marker(merge_output_baton_t *btn, int idx)
862 SVN_ERR(svn_stream_puts(btn->output_stream, btn->markers[idx]));
863 return output_marker_eol(btn);
867 output_common_modified(void *baton,
868 apr_off_t original_start, apr_off_t original_length,
869 apr_off_t modified_start, apr_off_t modified_length,
870 apr_off_t latest_start, apr_off_t latest_length)
872 return output_merge_token_range(NULL, baton, 1/*modified*/,
873 modified_start, modified_length);
877 output_latest(void *baton,
878 apr_off_t original_start, apr_off_t original_length,
879 apr_off_t modified_start, apr_off_t modified_length,
880 apr_off_t latest_start, apr_off_t latest_length)
882 return output_merge_token_range(NULL, baton, 2/*latest*/,
883 latest_start, latest_length);
887 output_conflict(void *baton,
888 apr_off_t original_start, apr_off_t original_length,
889 apr_off_t modified_start, apr_off_t modified_length,
890 apr_off_t latest_start, apr_off_t latest_length,
893 static const svn_diff_output_fns_t merge_output_vtable =
895 output_common_modified, /* common */
896 output_common_modified, /* modified */
898 output_common_modified, /* output_diff_common */
903 output_conflict(void *baton,
904 apr_off_t original_start, apr_off_t original_length,
905 apr_off_t modified_start, apr_off_t modified_length,
906 apr_off_t latest_start, apr_off_t latest_length,
909 merge_output_baton_t *btn = baton;
911 svn_diff_conflict_display_style_t style = btn->conflict_style;
913 if (style == svn_diff_conflict_display_resolved_modified_latest)
916 return svn_diff_output(diff, baton, &merge_output_vtable);
918 style = svn_diff_conflict_display_modified_latest;
921 if (style == svn_diff_conflict_display_modified_latest ||
922 style == svn_diff_conflict_display_modified_original_latest)
924 SVN_ERR(output_merge_marker(btn, 1/*modified*/));
925 SVN_ERR(output_merge_token_range(NULL, btn, 1/*modified*/,
926 modified_start, modified_length));
928 if (style == svn_diff_conflict_display_modified_original_latest)
930 SVN_ERR(output_merge_marker(btn, 0/*original*/));
931 SVN_ERR(output_merge_token_range(NULL, btn, 0/*original*/,
932 original_start, original_length));
935 SVN_ERR(output_merge_marker(btn, 2/*separator*/));
936 SVN_ERR(output_merge_token_range(NULL, btn, 2/*latest*/,
937 latest_start, latest_length));
938 SVN_ERR(output_merge_marker(btn, 3/*latest (end)*/));
940 else if (style == svn_diff_conflict_display_modified)
941 SVN_ERR(output_merge_token_range(NULL, btn, 1/*modified*/,
942 modified_start, modified_length));
943 else if (style == svn_diff_conflict_display_latest)
944 SVN_ERR(output_merge_token_range(NULL, btn, 2/*latest*/,
945 latest_start, latest_length));
946 else /* unknown style */
947 SVN_ERR_MALFUNCTION();
954 output_conflict_with_context(void *baton,
955 apr_off_t original_start,
956 apr_off_t original_length,
957 apr_off_t modified_start,
958 apr_off_t modified_length,
959 apr_off_t latest_start,
960 apr_off_t latest_length,
963 merge_output_baton_t *btn = baton;
965 /* Are we currently saving starting context (as opposed to printing
966 trailing context)? If so, flush it. */
967 if (btn->output_stream == btn->context_saver->stream)
969 if (btn->context_saver->total_written > SVN_DIFF__UNIFIED_CONTEXT_SIZE)
970 SVN_ERR(svn_stream_puts(btn->real_output_stream, "@@\n"));
971 SVN_ERR(flush_context_saver(btn->context_saver, btn->real_output_stream));
974 /* Print to the real output stream. */
975 btn->output_stream = btn->real_output_stream;
977 /* Output the conflict itself. */
978 SVN_ERR(svn_stream_printf(btn->output_stream, btn->pool,
979 (modified_length == 1
980 ? "%s (%" APR_OFF_T_FMT ")"
981 : "%s (%" APR_OFF_T_FMT ",%" APR_OFF_T_FMT ")"),
983 modified_start + 1, modified_length));
984 SVN_ERR(output_marker_eol(btn));
985 SVN_ERR(output_merge_token_range(NULL, btn, 1/*modified*/,
986 modified_start, modified_length));
988 SVN_ERR(svn_stream_printf(btn->output_stream, btn->pool,
989 (original_length == 1
990 ? "%s (%" APR_OFF_T_FMT ")"
991 : "%s (%" APR_OFF_T_FMT ",%" APR_OFF_T_FMT ")"),
993 original_start + 1, original_length));
994 SVN_ERR(output_marker_eol(btn));
995 SVN_ERR(output_merge_token_range(NULL, btn, 0/*original*/,
996 original_start, original_length));
998 SVN_ERR(output_merge_marker(btn, 2/*separator*/));
999 SVN_ERR(output_merge_token_range(NULL, btn, 2/*latest*/,
1000 latest_start, latest_length));
1001 SVN_ERR(svn_stream_printf(btn->output_stream, btn->pool,
1003 ? "%s (%" APR_OFF_T_FMT ")"
1004 : "%s (%" APR_OFF_T_FMT ",%" APR_OFF_T_FMT ")"),
1006 latest_start + 1, latest_length));
1007 SVN_ERR(output_marker_eol(btn));
1009 /* Go into print-trailing-context mode instead. */
1010 make_trailing_context_printer(btn);
1012 return SVN_NO_ERROR;
1016 static const svn_diff_output_fns_t merge_only_conflicts_output_vtable =
1018 output_common_modified,
1019 output_common_modified,
1021 output_common_modified,
1022 output_conflict_with_context
1026 /* TOKEN is the first token in the modified file.
1027 Return its line-ending, if any. */
1029 detect_eol(svn_string_t *token)
1033 if (token->len == 0)
1036 curp = token->data + token->len - 1;
1039 else if (*curp != '\n')
1044 || *(--curp) != '\r')
1052 svn_diff_mem_string_output_merge2(svn_stream_t *output_stream,
1054 const svn_string_t *original,
1055 const svn_string_t *modified,
1056 const svn_string_t *latest,
1057 const char *conflict_original,
1058 const char *conflict_modified,
1059 const char *conflict_latest,
1060 const char *conflict_separator,
1061 svn_diff_conflict_display_style_t style,
1064 merge_output_baton_t btn;
1066 svn_boolean_t conflicts_only =
1067 (style == svn_diff_conflict_display_only_conflicts);
1068 const svn_diff_output_fns_t *vtable = conflicts_only
1069 ? &merge_only_conflicts_output_vtable : &merge_output_vtable;
1071 memset(&btn, 0, sizeof(btn));
1075 btn.pool = svn_pool_create(pool);
1076 make_context_saver(&btn);
1077 btn.real_output_stream = output_stream;
1080 btn.output_stream = output_stream;
1082 fill_source_tokens(&(btn.sources[0]), original, pool);
1083 fill_source_tokens(&(btn.sources[1]), modified, pool);
1084 fill_source_tokens(&(btn.sources[2]), latest, pool);
1086 btn.conflict_style = style;
1088 if (btn.sources[1].tokens->nelts > 0)
1090 eol = detect_eol(APR_ARRAY_IDX(btn.sources[1].tokens, 0, svn_string_t *));
1092 eol = APR_EOL_STR; /* use the platform default */
1095 eol = APR_EOL_STR; /* use the platform default */
1097 btn.marker_eol = eol;
1099 SVN_ERR(svn_utf_cstring_from_utf8(&btn.markers[1],
1102 : "<<<<<<< (modified)",
1104 SVN_ERR(svn_utf_cstring_from_utf8(&btn.markers[0],
1107 : "||||||| (original)",
1109 SVN_ERR(svn_utf_cstring_from_utf8(&btn.markers[2],
1111 ? conflict_separator
1114 SVN_ERR(svn_utf_cstring_from_utf8(&btn.markers[3],
1117 : ">>>>>>> (latest)",
1120 SVN_ERR(svn_diff_output(diff, &btn, vtable));
1122 svn_pool_destroy(btn.pool);
1124 return SVN_NO_ERROR;
1128 svn_diff_mem_string_output_merge(svn_stream_t *output_stream,
1130 const svn_string_t *original,
1131 const svn_string_t *modified,
1132 const svn_string_t *latest,
1133 const char *conflict_original,
1134 const char *conflict_modified,
1135 const char *conflict_latest,
1136 const char *conflict_separator,
1137 svn_boolean_t display_original_in_conflict,
1138 svn_boolean_t display_resolved_conflicts,
1141 svn_diff_conflict_display_style_t style =
1142 svn_diff_conflict_display_modified_latest;
1144 if (display_resolved_conflicts)
1145 style = svn_diff_conflict_display_resolved_modified_latest;
1147 if (display_original_in_conflict)
1148 style = svn_diff_conflict_display_modified_original_latest;
1150 return svn_diff_mem_string_output_merge2(output_stream,