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 * ====================================================================
23 #include <apr_tables.h>
25 #include <apr_general.h>
26 #include "svn_error.h"
27 #include "svn_version.h"
29 #include "svn_ctype.h"
32 #include "svn_pools.h"
33 #include "svn_types.h"
34 #include "svn_string.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 */
51 /* The last token ends with a newline character (sequence) */
52 svn_boolean_t ends_without_eol
;
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
;
69 datasource_to_index(svn_diff_datasource_e datasource
)
73 case svn_diff_datasource_original
:
76 case svn_diff_datasource_modified
:
79 case svn_diff_datasource_latest
:
82 case svn_diff_datasource_ancestor
:
90 /* Implements svn_diff_fns_t::datasource_open */
92 datasource_open(void *baton
, svn_diff_datasource_e datasource
)
94 /* Do nothing: everything is already there and initialized to 0 */
99 /* Implements svn_diff_fns_t::datasource_close */
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.
111 /* Implements svn_diff_fns_t::datasource_get_next_token */
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
);
140 /* Implements svn_diff_fns_t::token_compare */
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
);
162 *result
= (len1
< len2
) ? -1 : 1;
164 *result
= (len1
== 0) ? 0 : memcmp(buf1
, buf2
, (size_t) len1
);
169 /* Implements svn_diff_fns_t::token_discard */
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 */
179 token_discard_all(void *baton
)
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
=
193 datasource_get_next_token
,
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
207 fill_source_tokens(source_tokens_t
*src
,
208 const svn_string_t
*text
,
215 src
->tokens
= apr_array_make(pool
, 0, sizeof(svn_string_t
*));
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')
225 if (*curp
== '\r' || *curp
== '\n')
227 APR_ARRAY_PUSH(src
->tokens
, svn_string_t
*) =
228 svn_string_ncreate(startp
, curp
- startp
+ 1, pool
);
234 /* If there's anything remaining (ie last line doesn't have a newline) */
237 APR_ARRAY_PUSH(src
->tokens
, svn_string_t
*) =
238 svn_string_ncreate(startp
, endp
- startp
, pool
);
239 src
->ends_without_eol
= TRUE
;
242 src
->ends_without_eol
= FALSE
;
247 alloc_normalization_bufs(diff_mem_baton_t
*btn
,
251 apr_size_t max_len
= 0;
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
++)
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
);
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
,
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
);
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
,
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
);
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
,
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
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 */
362 /* Append tokens (lines) FIRST up to PAST_LAST
363 from token-source index TOKENS with change-type TYPE
367 output_unified_token_range(output_baton_t
*btn
,
369 unified_output_e type
,
373 source_tokens_t
*source
= &btn
->sources
[tokens
];
376 past_last
= (past_last
> source
->tokens
->nelts
)
377 ? source
->tokens
->nelts
: past_last
;
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
)
388 /* Do the loop with prefix and token */
389 for (idx
= first
; idx
< past_last
; idx
++)
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]++;
404 btn
->hunk_length
[1]++;
407 if (past_last
== source
->tokens
->nelts
&& source
->ends_without_eol
)
410 SVN_ERR(svn_utf_cstring_from_utf8_ex2
412 /* The string below is intentionally not marked for translation:
413 it's vital to correct operation of the diff(1)/patch(1)
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
);
421 btn
->next_token
= past_last
;
426 /* Flush the hunk currently built up in BATON
427 into the baton's output_stream */
429 output_unified_flush_hunk(output_baton_t
*baton
)
431 apr_off_t target_token
;
434 if (svn_stringbuf_isempty(baton
->hunk
))
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
,
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
,
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
);
483 /* Implements svn_diff_output_fns_t::output_diff_modified */
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
,
516 original_start
+ original_length
));
517 return output_unified_token_range(btn
, 1/*modified*/, unified_output_insert
,
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 */
533 svn_diff_mem_string_output_unified(svn_stream_t
*output_stream
,
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
,
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
,
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
);
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
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
;
599 context_saver_stream_write(void *baton
,
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
;
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
638 } merge_output_baton_t
;
642 flush_context_saver(context_saver_t
*cs
,
643 svn_stream_t
*output_stream
)
646 for (i
= 0; i
< SVN_DIFF__UNIFIED_CONTEXT_SIZE
; i
++)
648 int slot
= (i
+ cs
->next_slot
) % SVN_DIFF__UNIFIED_CONTEXT_SIZE
;
651 apr_size_t len
= cs
->len
[slot
];
652 SVN_ERR(svn_stream_write(output_stream
, cs
->data
[slot
], &len
));
660 make_context_saver(merge_output_baton_t
*mob
)
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
;
685 trailing_context_printer_write(void *baton
,
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
);
700 make_trailing_context_printer(merge_output_baton_t
*btn
)
702 struct trailing_context_printer
*tcp
;
705 svn_pool_clear(btn
->pool
);
707 tcp
= apr_pcalloc(btn
->pool
, sizeof(*tcp
));
708 tcp
->lines_to_print
= SVN_DIFF__UNIFIED_CONTEXT_SIZE
;
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
;
718 output_merge_token_range(apr_size_t
*lines_printed_p
,
719 merge_output_baton_t
*btn
,
720 int idx
, apr_off_t first
,
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
));
738 *lines_printed_p
= lines_printed
;
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
);
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
);
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
);
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
);
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
,
785 static const svn_diff_output_fns_t merge_output_vtable
=
787 output_common_modified
, /* common */
788 output_common_modified
, /* modified */
790 output_common_modified
, /* output_diff_common */
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
,
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
)
808 return svn_diff_output(diff
, baton
, &merge_output_vtable
);
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();
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
,
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
")"),
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
")"),
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
,
895 ? "%s (%" APR_OFF_T_FMT
")"
896 : "%s (%" APR_OFF_T_FMT
",%" APR_OFF_T_FMT
")"),
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
);
908 static const svn_diff_output_fns_t merge_only_conflicts_output_vtable
=
910 output_common_modified
,
911 output_common_modified
,
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. */
921 detect_eol(svn_string_t
*token
)
928 curp
= token
->data
+ token
->len
- 1;
931 else if (*curp
!= '\n')
936 || *(--curp
) != '\r')
944 svn_diff_mem_string_output_merge2(svn_stream_t
*output_stream
,
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
,
956 merge_output_baton_t btn
;
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
));
967 btn
.pool
= svn_pool_create(pool
);
968 make_context_saver(&btn
);
969 btn
.real_output_stream
= output_stream
;
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
*));
984 eol
= APR_EOL_STR
; /* use the platform default */
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],
994 : "<<<<<<< (modified)",
996 SVN_ERR(svn_utf_cstring_from_utf8(&btn
.markers
[0],
999 : "||||||| (original)",
1001 SVN_ERR(svn_utf_cstring_from_utf8(&btn
.markers
[2],
1003 ? conflict_separator
1006 SVN_ERR(svn_utf_cstring_from_utf8(&btn
.markers
[3],
1009 : ">>>>>>> (latest)",
1012 SVN_ERR(svn_diff_output(diff
, &btn
, vtable
));
1014 svn_pool_destroy(btn
.pool
);
1016 return SVN_NO_ERROR
;
1020 svn_diff_mem_string_output_merge(svn_stream_t
*output_stream
,
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
,
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
,