2 * diff_memory.c : routines for doing diffs on in-memory data
\r
4 * ====================================================================
\r
5 * Copyright (c) 2007 CollabNet. All rights reserved.
\r
7 * This software is licensed as described in the file COPYING, which
\r
8 * you should have received as part of this distribution. The terms
\r
9 * are also available at http://subversion.tigris.org/license-1.html.
\r
10 * If newer versions of this license are posted there, you may use a
\r
11 * newer version instead, at your option.
\r
13 * This software consists of voluntary contributions made by many
\r
14 * individuals. For exact contribution history, see the revision
\r
15 * history and logs, available at http://subversion.tigris.org/.
\r
16 * ====================================================================
\r
19 #define WANT_MEMFUNC
\r
20 #define WANT_STRFUNC
\r
22 #include <apr_want.h>
\r
23 #include <apr_tables.h>
\r
25 #include <apr_general.h>
\r
26 #include "svn_error.h"
\r
27 #include "svn_version.h"
\r
29 #include "svn_ctype.h"
\r
31 #include "svn_diff.h"
\r
32 #include "svn_pools.h"
\r
33 #include "svn_types.h"
\r
34 #include "svn_string.h"
\r
35 #include "svn_utf.h"
\r
37 //#include "svn_private_config.h"
\r
39 typedef struct source_tokens_t
\r
41 /* A token simply is an svn_string_t pointing to
\r
42 the data of the in-memory data source, containing
\r
43 the raw token text, with length stored in the string */
\r
44 apr_array_header_t *tokens;
\r
46 /* Next token to be consumed */
\r
47 apr_size_t next_token;
\r
49 /* The source, containing the in-memory data to be diffed */
\r
50 svn_string_t *source;
\r
52 /* The last token ends with a newline character (sequence) */
\r
53 svn_boolean_t ends_without_eol;
\r
56 typedef struct diff_mem_baton_t
\r
58 /* The tokens for each of the sources */
\r
59 source_tokens_t sources[4];
\r
61 /* Normalization buffer; we only ever compare 2 tokens at the same time */
\r
62 char *normalization_buf[2];
\r
64 /* Options for normalized comparison of the data sources */
\r
65 const svn_diff_file_options_t *normalization_options;
\r
70 datasource_to_index(svn_diff_datasource_e datasource)
\r
74 case svn_diff_datasource_original:
\r
77 case svn_diff_datasource_modified:
\r
80 case svn_diff_datasource_latest:
\r
83 case svn_diff_datasource_ancestor:
\r
91 /* Implements svn_diff_fns_t::datasource_open */
\r
92 static svn_error_t *
\r
93 datasource_open(void *baton, svn_diff_datasource_e datasource)
\r
95 /* Do nothing: everything is already there and initialized to 0 */
\r
96 return SVN_NO_ERROR;
\r
100 /* Implements svn_diff_fns_t::datasource_close */
\r
101 static svn_error_t *
\r
102 datasource_close(void *baton, svn_diff_datasource_e datasource)
\r
104 /* Do nothing. The compare_token function needs previous datasources
\r
105 * to stay available until all datasources are processed.
\r
108 return SVN_NO_ERROR;
\r
112 /* Implements svn_diff_fns_t::datasource_get_next_token */
\r
113 static svn_error_t *
\r
114 datasource_get_next_token(apr_uint32_t *hash, void **token, void *baton,
\r
115 svn_diff_datasource_e datasource)
\r
117 diff_mem_baton_t *mem_baton = baton;
\r
118 source_tokens_t *src = &(mem_baton->sources[datasource_to_index(datasource)]);
\r
120 if (src->tokens->nelts > src->next_token)
\r
122 /* There are actually tokens to be returned */
\r
123 char *buf = mem_baton->normalization_buf[0];
\r
124 svn_string_t *tok = (*token)
\r
125 = APR_ARRAY_IDX(src->tokens, src->next_token, svn_string_t *);
\r
126 apr_off_t len = tok->len;
\r
127 svn_diff__normalize_state_t state
\r
128 = svn_diff__normalize_state_normal;
\r
130 svn_diff__normalize_buffer(&buf, &len, &state, tok->data,
\r
131 mem_baton->normalization_options);
\r
132 *hash = svn_diff__adler32(0, buf, len);
\r
138 return SVN_NO_ERROR;
\r
141 /* Implements svn_diff_fns_t::token_compare */
\r
142 static svn_error_t *
\r
143 token_compare(void *baton, void *token1, void *token2, int *result)
\r
145 /* Implement the same behaviour as diff_file.c:token_compare(),
\r
146 but be simpler, because we know we'll have all data in memory */
\r
147 diff_mem_baton_t *btn = baton;
\r
148 svn_string_t *t1 = token1;
\r
149 svn_string_t *t2 = token2;
\r
150 char *buf1 = btn->normalization_buf[0];
\r
151 char *buf2 = btn->normalization_buf[1];
\r
152 apr_off_t len1 = t1->len;
\r
153 apr_off_t len2 = t2->len;
\r
154 svn_diff__normalize_state_t state = svn_diff__normalize_state_normal;
\r
156 svn_diff__normalize_buffer(&buf1, &len1, &state, t1->data,
\r
157 btn->normalization_options);
\r
158 state = svn_diff__normalize_state_normal;
\r
159 svn_diff__normalize_buffer(&buf2, &len2, &state, t2->data,
\r
160 btn->normalization_options);
\r
163 *result = (len1 < len2) ? -1 : 1;
\r
165 *result = (len1 == 0) ? 0 : memcmp(buf1, buf2, len1);
\r
167 return SVN_NO_ERROR;
\r
170 /* Implements svn_diff_fns_t::token_discard */
\r
172 token_discard(void *baton, void *token)
\r
174 /* No-op, we have no use for discarded tokens... */
\r
178 /* Implements svn_diff_fns_t::token_discard_all */
\r
180 token_discard_all(void *baton)
\r
183 Note that in the file case, this function discards all
\r
184 tokens allocated, but we're geared toward small in-memory diffs.
\r
185 Meaning that there's no special pool to clear.
\r
190 static const svn_diff_fns_t svn_diff__mem_vtable =
\r
194 datasource_get_next_token,
\r
200 /* Fill SRC with the diff tokens (e.g. lines).
\r
202 TEXT is assumed to live long enough for the tokens to
\r
203 stay valid during their lifetime: no data is copied,
\r
204 instead, svn_string_t's are allocated pointing straight
\r
208 fill_source_tokens(source_tokens_t *src,
\r
209 const svn_string_t *text,
\r
214 const char *startp;
\r
216 src->tokens = apr_array_make(pool, 0, sizeof(svn_string_t *));
\r
217 src->next_token = 0;
\r
218 src->source = (svn_string_t *)text;
\r
220 for (startp = curp = text->data, endp = curp + text->len;
\r
221 curp != endp; curp++)
\r
223 if (curp != endp && *curp == '\r' && *(curp + 1) == '\n')
\r
226 if (*curp == '\r' || *curp == '\n')
\r
228 APR_ARRAY_PUSH(src->tokens, svn_string_t *) =
\r
229 svn_string_ncreate(startp, curp - startp + 1, pool);
\r
235 /* If there's anything remaining (ie last line doesn't have a newline) */
\r
236 if (startp != endp)
\r
238 APR_ARRAY_PUSH(src->tokens, svn_string_t *) =
\r
239 svn_string_ncreate(startp, endp - startp, pool);
\r
240 src->ends_without_eol = TRUE;
\r
243 src->ends_without_eol = FALSE;
\r
248 alloc_normalization_bufs(diff_mem_baton_t *btn,
\r
252 apr_size_t max_len = 0;
\r
256 for (i = 0; i < sources; i++)
\r
258 apr_array_header_t *tokens = btn->sources[i].tokens;
\r
259 if (tokens->nelts > 0)
\r
260 for (idx = 0; idx < tokens->nelts; idx++)
\r
262 apr_size_t token_len
\r
263 = APR_ARRAY_IDX(tokens, idx, svn_string_t *)->len;
\r
264 max_len = (max_len < token_len) ? token_len : max_len;
\r
268 btn->normalization_buf[0] = apr_palloc(pool, max_len);
\r
269 btn->normalization_buf[1] = apr_palloc(pool, max_len);
\r
273 svn_diff_mem_string_diff(svn_diff_t **diff,
\r
274 const svn_string_t *original,
\r
275 const svn_string_t *modified,
\r
276 const svn_diff_file_options_t *options,
\r
279 diff_mem_baton_t baton;
\r
281 fill_source_tokens(&(baton.sources[0]), original, pool);
\r
282 fill_source_tokens(&(baton.sources[1]), modified, pool);
\r
283 alloc_normalization_bufs(&baton, 2, pool);
\r
285 baton.normalization_options = options;
\r
287 return svn_diff_diff(diff, &baton, &svn_diff__mem_vtable, pool);
\r
291 svn_diff_mem_string_diff3(svn_diff_t **diff,
\r
292 const svn_string_t *original,
\r
293 const svn_string_t *modified,
\r
294 const svn_string_t *latest,
\r
295 const svn_diff_file_options_t *options,
\r
298 diff_mem_baton_t baton;
\r
300 fill_source_tokens(&(baton.sources[0]), original, pool);
\r
301 fill_source_tokens(&(baton.sources[1]), modified, pool);
\r
302 fill_source_tokens(&(baton.sources[2]), latest, pool);
\r
303 alloc_normalization_bufs(&baton, 3, pool);
\r
305 baton.normalization_options = options;
\r
307 return svn_diff_diff3(diff, &baton, &svn_diff__mem_vtable, pool);
\r
312 svn_diff_mem_string_diff4(svn_diff_t **diff,
\r
313 const svn_string_t *original,
\r
314 const svn_string_t *modified,
\r
315 const svn_string_t *latest,
\r
316 const svn_string_t *ancestor,
\r
317 const svn_diff_file_options_t *options,
\r
320 diff_mem_baton_t baton;
\r
322 fill_source_tokens(&(baton.sources[0]), original, pool);
\r
323 fill_source_tokens(&(baton.sources[1]), modified, pool);
\r
324 fill_source_tokens(&(baton.sources[2]), latest, pool);
\r
325 fill_source_tokens(&(baton.sources[3]), ancestor, pool);
\r
326 alloc_normalization_bufs(&baton, 4, pool);
\r
328 baton.normalization_options = options;
\r
330 return svn_diff_diff4(diff, &baton, &svn_diff__mem_vtable, pool);
\r
334 typedef enum unified_output_e
\r
336 unified_output_context = 0,
\r
337 unified_output_delete,
\r
338 unified_output_insert
\r
339 } unified_output_e;
\r
341 /* Baton for generating unified diffs */
\r
342 typedef struct unified_output_baton_t
\r
344 svn_stream_t *output_stream;
\r
345 const char *header_encoding;
\r
346 source_tokens_t sources[2]; /* 0 == original; 1 == modified */
\r
347 apr_size_t next_token; /* next token in original source */
\r
349 /* Cached markers, in header_encoding,
\r
350 indexed using unified_output_e */
\r
351 const char *prefix_str[3];
\r
353 svn_stringbuf_t *hunk; /* in-progress hunk data */
\r
354 apr_off_t hunk_length[2]; /* 0 == original; 1 == modified */
\r
355 apr_off_t hunk_start[2]; /* 0 == original; 1 == modified */
\r
357 /* Pool for allocation of temporary memory in the callbacks
\r
358 Should be cleared on entry of each iteration of a callback */
\r
363 /* Append tokens (lines) FIRST up to PAST_LAST
\r
364 from token-source index TOKENS with change-type TYPE
\r
365 to the current hunk.
\r
367 static svn_error_t *
\r
368 output_unified_token_range(output_baton_t *btn,
\r
370 unified_output_e type,
\r
372 apr_off_t past_last)
\r
374 source_tokens_t *source = &btn->sources[tokens];
\r
377 past_last = (past_last > source->tokens->nelts)
\r
378 ? source->tokens->nelts : past_last;
\r
381 /* We get context from the original source, don't expect
\r
382 to be asked to output a block which starts before
\r
383 what we already have written. */
\r
384 first = (first < btn->next_token) ? btn->next_token : first;
\r
386 if (first >= past_last)
\r
387 return SVN_NO_ERROR;
\r
389 /* Do the loop with prefix and token */
\r
390 for (idx = first; idx < past_last; idx++)
\r
392 svn_string_t *token
\r
393 = APR_ARRAY_IDX(source->tokens, idx, svn_string_t *);
\r
394 svn_stringbuf_appendcstr(btn->hunk, btn->prefix_str[type]);
\r
395 svn_stringbuf_appendbytes(btn->hunk, token->data, token->len);
\r
397 if (type == unified_output_context)
\r
399 btn->hunk_length[0]++;
\r
400 btn->hunk_length[1]++;
\r
402 else if (type == unified_output_delete)
\r
403 btn->hunk_length[0]++;
\r
405 btn->hunk_length[1]++;
\r
408 if (past_last == source->tokens->nelts && source->ends_without_eol)
\r
410 const char *out_str;
\r
411 SVN_ERR(svn_utf_cstring_from_utf8_ex2
\r
413 /* The string below is intentionally not marked for translation:
\r
414 it's vital to correct operation of the diff(1)/patch(1)
\r
416 APR_EOL_STR "\\ No newline at end of file" APR_EOL_STR,
\r
417 btn->header_encoding, btn->pool));
\r
418 svn_stringbuf_appendcstr(btn->hunk, out_str);
\r
422 btn->next_token = past_last;
\r
424 return SVN_NO_ERROR;
\r
427 /* Flush the hunk currently built up in BATON
\r
428 into the baton's output_stream */
\r
429 static svn_error_t *
\r
430 output_unified_flush_hunk(output_baton_t *baton)
\r
432 apr_off_t target_token;
\r
433 apr_size_t hunk_len;
\r
435 if (svn_stringbuf_isempty(baton->hunk))
\r
436 return SVN_NO_ERROR;
\r
438 svn_pool_clear(baton->pool);
\r
440 /* Write the trailing context */
\r
441 target_token = baton->hunk_start[0] + baton->hunk_length[0]
\r
442 + SVN_DIFF__UNIFIED_CONTEXT_SIZE;
\r
443 SVN_ERR(output_unified_token_range(baton, 0 /*original*/,
\r
444 unified_output_context,
\r
445 baton->next_token, target_token));
\r
447 /* Write the hunk header */
\r
448 if (baton->hunk_length[0] > 0)
\r
449 /* Convert our 0-based line numbers into unidiff 1-based numbers */
\r
450 baton->hunk_start[0]++;
\r
451 SVN_ERR(svn_stream_printf_from_utf8
\r
452 (baton->output_stream, baton->header_encoding,
\r
454 /* Hunk length 1 is implied, don't show the
\r
455 length field if we have a hunk that long */
\r
456 (baton->hunk_length[0] == 1)
\r
457 ? ("@@ -%" APR_OFF_T_FMT)
\r
458 : ("@@ -%" APR_OFF_T_FMT ",%" APR_OFF_T_FMT),
\r
459 baton->hunk_start[0], baton->hunk_length[0]));
\r
461 if (baton->hunk_length[1] > 0)
\r
462 /* Convert our 0-based line numbers into unidiff 1-based numbers */
\r
463 baton->hunk_start[1]++;
\r
464 SVN_ERR(svn_stream_printf_from_utf8
\r
465 (baton->output_stream, baton->header_encoding,
\r
467 /* Hunk length 1 is implied, don't show the
\r
468 length field if we have a hunk that long */
\r
469 (baton->hunk_length[1] == 1)
\r
470 ? (" +%" APR_OFF_T_FMT " @@" APR_EOL_STR)
\r
471 : (" +%" APR_OFF_T_FMT ",%" APR_OFF_T_FMT " @@" APR_EOL_STR),
\r
472 baton->hunk_start[1], baton->hunk_length[1]));
\r
474 hunk_len = baton->hunk->len;
\r
475 SVN_ERR(svn_stream_write(baton->output_stream,
\r
476 baton->hunk->data, &hunk_len));
\r
478 baton->hunk_length[0] = baton->hunk_length[1] = 0;
\r
479 svn_stringbuf_setempty(baton->hunk);
\r
481 return SVN_NO_ERROR;
\r
484 /* Implements svn_diff_output_fns_t::output_diff_modified */
\r
485 static svn_error_t *
\r
486 output_unified_diff_modified(void *baton,
\r
487 apr_off_t original_start,
\r
488 apr_off_t original_length,
\r
489 apr_off_t modified_start,
\r
490 apr_off_t modified_length,
\r
491 apr_off_t latest_start,
\r
492 apr_off_t latest_length)
\r
494 output_baton_t *btn = baton;
\r
495 apr_off_t targ_orig, targ_mod;
\r
497 targ_orig = original_start - SVN_DIFF__UNIFIED_CONTEXT_SIZE;
\r
498 targ_orig = (targ_orig < 0) ? 0 : targ_orig;
\r
499 targ_mod = modified_start;
\r
501 if (btn->next_token + SVN_DIFF__UNIFIED_CONTEXT_SIZE < targ_orig)
\r
502 SVN_ERR(output_unified_flush_hunk(btn));
\r
504 if (btn->hunk_length[0] == 0
\r
505 && btn->hunk_length[1] == 0)
\r
507 btn->hunk_start[0] = targ_orig;
\r
508 btn->hunk_start[1] = targ_mod + targ_orig - original_start;
\r
511 SVN_ERR(output_unified_token_range(btn, 0/*original*/,
\r
512 unified_output_context,
\r
513 targ_orig, original_start));
\r
514 SVN_ERR(output_unified_token_range(btn, 0/*original*/,
\r
515 unified_output_delete,
\r
517 original_start + original_length));
\r
518 return output_unified_token_range(btn, 1/*modified*/, unified_output_insert,
\r
520 modified_start + modified_length);
\r
523 static const svn_diff_output_fns_t mem_output_unified_vtable =
\r
525 NULL, /* output_common */
\r
526 output_unified_diff_modified,
\r
527 NULL, /* output_diff_latest */
\r
528 NULL, /* output_diff_common */
\r
529 NULL /* output_conflict */
\r
534 svn_diff_mem_string_output_unified(svn_stream_t *output_stream,
\r
536 const char *original_header,
\r
537 const char *modified_header,
\r
538 const char *header_encoding,
\r
539 const svn_string_t *original,
\r
540 const svn_string_t *modified,
\r
544 if (svn_diff_contains_diffs(diff))
\r
546 output_baton_t baton;
\r
548 memset(&baton, 0, sizeof(baton));
\r
549 baton.output_stream = output_stream;
\r
550 baton.pool = svn_pool_create(pool);
\r
551 baton.header_encoding = header_encoding;
\r
552 baton.hunk = svn_stringbuf_create("", pool);
\r
554 SVN_ERR(svn_utf_cstring_from_utf8_ex2
\r
555 (&(baton.prefix_str[unified_output_context]), " ",
\r
556 header_encoding, pool));
\r
557 SVN_ERR(svn_utf_cstring_from_utf8_ex2
\r
558 (&(baton.prefix_str[unified_output_delete]), "-",
\r
559 header_encoding, pool));
\r
560 SVN_ERR(svn_utf_cstring_from_utf8_ex2
\r
561 (&(baton.prefix_str[unified_output_insert]), "+",
\r
562 header_encoding, pool));
\r
564 fill_source_tokens(&baton.sources[0], original, pool);
\r
565 fill_source_tokens(&baton.sources[1], modified, pool);
\r
567 SVN_ERR(svn_stream_printf_from_utf8
\r
568 (output_stream, header_encoding, pool,
\r
569 "--- %s" APR_EOL_STR
\r
570 "+++ %s" APR_EOL_STR,
\r
571 original_header, modified_header));
\r
573 SVN_ERR(svn_diff_output(diff, &baton,
\r
574 &mem_output_unified_vtable));
\r
575 SVN_ERR(output_unified_flush_hunk(&baton));
\r
577 svn_pool_destroy(baton.pool);
\r
580 return SVN_NO_ERROR;
\r
585 /* diff3 merge output */
\r
587 /* A stream to remember *leading* context. Note that this stream does
\r
588 *not* copy the data that it is remembering; it just saves
\r
591 svn_stream_t *stream;
\r
592 const char *data[SVN_DIFF__UNIFIED_CONTEXT_SIZE];
\r
593 apr_size_t len[SVN_DIFF__UNIFIED_CONTEXT_SIZE];
\r
594 apr_size_t next_slot;
\r
595 apr_size_t total_written;
\r
599 static svn_error_t *
\r
600 context_saver_stream_write(void *baton,
\r
604 context_saver_t *cs = baton;
\r
605 cs->data[cs->next_slot] = data;
\r
606 cs->len[cs->next_slot] = *len;
\r
607 cs->next_slot = (cs->next_slot + 1) % SVN_DIFF__UNIFIED_CONTEXT_SIZE;
\r
608 cs->total_written++;
\r
609 return SVN_NO_ERROR;
\r
613 typedef struct merge_output_baton_t
\r
615 svn_stream_t *output_stream;
\r
617 /* Tokenized source text */
\r
618 source_tokens_t sources[3];
\r
619 apr_off_t next_token;
\r
621 /* Markers for marking conflicted sections */
\r
622 const char *markers[4]; /* 0 = original, 1 = modified,
\r
623 2 = separator, 3 = latest (end) */
\r
624 const char *marker_eol;
\r
626 svn_diff_conflict_display_style_t conflict_style;
\r
628 /* The rest of the fields are for
\r
629 svn_diff_conflict_display_only_conflicts only. Note that for
\r
630 these batons, OUTPUT_STREAM is either CONTEXT_SAVER->STREAM or
\r
631 (soon after a conflict) a "trailing context stream", never the
\r
632 actual output stream.*/
\r
633 /* The actual output stream. */
\r
634 svn_stream_t *real_output_stream;
\r
635 context_saver_t *context_saver;
\r
636 /* Used to allocate context_saver and trailing context streams, and
\r
637 for some printfs. */
\r
639 } merge_output_baton_t;
\r
642 static svn_error_t *
\r
643 flush_context_saver(context_saver_t *cs,
\r
644 svn_stream_t *output_stream)
\r
647 for (i = 0; i < SVN_DIFF__UNIFIED_CONTEXT_SIZE; i++)
\r
649 int slot = (i + cs->next_slot) % SVN_DIFF__UNIFIED_CONTEXT_SIZE;
\r
650 if (cs->data[slot])
\r
652 apr_size_t len = cs->len[slot];
\r
653 SVN_ERR(svn_stream_write(output_stream, cs->data[slot], &len));
\r
656 return SVN_NO_ERROR;
\r
661 make_context_saver(merge_output_baton_t *mob)
\r
663 context_saver_t *cs;
\r
665 svn_pool_clear(mob->pool);
\r
666 cs = apr_pcalloc(mob->pool, sizeof(*cs));
\r
667 cs->stream = svn_stream_empty(mob->pool);
\r
668 svn_stream_set_baton(cs->stream, cs);
\r
669 svn_stream_set_write(cs->stream, context_saver_stream_write);
\r
670 mob->context_saver = cs;
\r
671 mob->output_stream = cs->stream;
\r
675 /* A stream which prints SVN_DIFF__UNIFIED_CONTEXT_SIZE lines to
\r
676 BATON->REAL_OUTPUT_STREAM, and then changes BATON->OUTPUT_STREAM to
\r
677 a context_saver; used for *trailing* context. */
\r
679 struct trailing_context_printer {
\r
680 apr_size_t lines_to_print;
\r
681 merge_output_baton_t *mob;
\r
685 static svn_error_t *
\r
686 trailing_context_printer_write(void *baton,
\r
690 struct trailing_context_printer *tcp = baton;
\r
691 SVN_ERR_ASSERT(tcp->lines_to_print > 0);
\r
692 SVN_ERR(svn_stream_write(tcp->mob->real_output_stream, data, len));
\r
693 tcp->lines_to_print--;
\r
694 if (tcp->lines_to_print == 0)
\r
695 make_context_saver(tcp->mob);
\r
696 return SVN_NO_ERROR;
\r
701 make_trailing_context_printer(merge_output_baton_t *btn)
\r
703 struct trailing_context_printer *tcp;
\r
706 svn_pool_clear(btn->pool);
\r
708 tcp = apr_pcalloc(btn->pool, sizeof(*tcp));
\r
709 tcp->lines_to_print = SVN_DIFF__UNIFIED_CONTEXT_SIZE;
\r
711 s = svn_stream_empty(btn->pool);
\r
712 svn_stream_set_baton(s, tcp);
\r
713 svn_stream_set_write(s, trailing_context_printer_write);
\r
714 btn->output_stream = s;
\r
718 static svn_error_t *
\r
719 output_merge_token_range(apr_size_t *lines_printed_p,
\r
720 merge_output_baton_t *btn,
\r
721 int idx, apr_off_t first,
\r
724 apr_array_header_t *tokens = btn->sources[idx].tokens;
\r
725 apr_size_t lines_printed = 0;
\r
727 for (; length > 0 && first < tokens->nelts; length--, first++)
\r
729 svn_string_t *token = APR_ARRAY_IDX(tokens, first, svn_string_t *);
\r
730 apr_size_t len = token->len;
\r
732 /* Note that the trailing context printer assumes that
\r
733 svn_stream_write is called exactly once per line. */
\r
734 SVN_ERR(svn_stream_write(btn->output_stream, token->data, &len));
\r
738 if (lines_printed_p)
\r
739 *lines_printed_p = lines_printed;
\r
741 return SVN_NO_ERROR;
\r
744 static svn_error_t *
\r
745 output_marker_eol(merge_output_baton_t *btn)
\r
747 apr_size_t len = strlen(btn->marker_eol);
\r
748 return svn_stream_write(btn->output_stream, btn->marker_eol, &len);
\r
751 static svn_error_t *
\r
752 output_merge_marker(merge_output_baton_t *btn, int idx)
\r
754 apr_size_t len = strlen(btn->markers[idx]);
\r
755 SVN_ERR(svn_stream_write(btn->output_stream, btn->markers[idx], &len));
\r
756 return output_marker_eol(btn);
\r
759 static svn_error_t *
\r
760 output_common_modified(void *baton,
\r
761 apr_off_t original_start, apr_off_t original_length,
\r
762 apr_off_t modified_start, apr_off_t modified_length,
\r
763 apr_off_t latest_start, apr_off_t latest_length)
\r
765 return output_merge_token_range(NULL, baton, 1/*modified*/,
\r
766 modified_start, modified_length);
\r
769 static svn_error_t *
\r
770 output_latest(void *baton,
\r
771 apr_off_t original_start, apr_off_t original_length,
\r
772 apr_off_t modified_start, apr_off_t modified_length,
\r
773 apr_off_t latest_start, apr_off_t latest_length)
\r
775 return output_merge_token_range(NULL, baton, 2/*latest*/,
\r
776 latest_start, latest_length);
\r
779 static svn_error_t *
\r
780 output_conflict(void *baton,
\r
781 apr_off_t original_start, apr_off_t original_length,
\r
782 apr_off_t modified_start, apr_off_t modified_length,
\r
783 apr_off_t latest_start, apr_off_t latest_length,
\r
786 static const svn_diff_output_fns_t merge_output_vtable =
\r
788 output_common_modified, /* common */
\r
789 output_common_modified, /* modified */
\r
791 output_common_modified, /* output_diff_common */
\r
795 static svn_error_t *
\r
796 output_conflict(void *baton,
\r
797 apr_off_t original_start, apr_off_t original_length,
\r
798 apr_off_t modified_start, apr_off_t modified_length,
\r
799 apr_off_t latest_start, apr_off_t latest_length,
\r
802 merge_output_baton_t *btn = baton;
\r
804 svn_diff_conflict_display_style_t style = btn->conflict_style;
\r
806 if (style == svn_diff_conflict_display_resolved_modified_latest)
\r
809 return svn_diff_output(diff, baton, &merge_output_vtable);
\r
811 style = svn_diff_conflict_display_modified_latest;
\r
814 if (style == svn_diff_conflict_display_modified_latest ||
\r
815 style == svn_diff_conflict_display_modified_original_latest)
\r
817 SVN_ERR(output_merge_marker(btn, 1/*modified*/));
\r
818 SVN_ERR(output_merge_token_range(NULL, btn, 1/*modified*/,
\r
819 modified_start, modified_length));
\r
821 if (style == svn_diff_conflict_display_modified_original_latest)
\r
823 SVN_ERR(output_merge_marker(btn, 0/*original*/));
\r
824 SVN_ERR(output_merge_token_range(NULL, btn, 0/*original*/,
\r
825 original_start, original_length));
\r
828 SVN_ERR(output_merge_marker(btn, 2/*separator*/));
\r
829 SVN_ERR(output_merge_token_range(NULL, btn, 2/*latest*/,
\r
830 latest_start, latest_length));
\r
831 SVN_ERR(output_merge_marker(btn, 3/*latest (end)*/));
\r
833 else if (style == svn_diff_conflict_display_modified)
\r
834 SVN_ERR(output_merge_token_range(NULL, btn, 1/*modified*/,
\r
835 modified_start, modified_length));
\r
836 else if (style == svn_diff_conflict_display_latest)
\r
837 SVN_ERR(output_merge_token_range(NULL, btn, 2/*latest*/,
\r
838 latest_start, latest_length));
\r
839 else /* unknown style */
\r
840 SVN_ERR_MALFUNCTION();
\r
842 return SVN_NO_ERROR;
\r
846 static svn_error_t *
\r
847 output_conflict_with_context(void *baton,
\r
848 apr_off_t original_start,
\r
849 apr_off_t original_length,
\r
850 apr_off_t modified_start,
\r
851 apr_off_t modified_length,
\r
852 apr_off_t latest_start,
\r
853 apr_off_t latest_length,
\r
856 merge_output_baton_t *btn = baton;
\r
858 /* Are we currently saving starting context (as opposed to printing
\r
859 trailing context)? If so, flush it. */
\r
860 if (btn->output_stream == btn->context_saver->stream)
\r
862 if (btn->context_saver->total_written > SVN_DIFF__UNIFIED_CONTEXT_SIZE)
\r
863 SVN_ERR(svn_stream_printf(btn->real_output_stream, btn->pool, "@@\n"));
\r
864 SVN_ERR(flush_context_saver(btn->context_saver, btn->real_output_stream));
\r
867 /* Print to the real output stream. */
\r
868 btn->output_stream = btn->real_output_stream;
\r
870 /* Output the conflict itself. */
\r
871 SVN_ERR(svn_stream_printf(btn->output_stream, btn->pool,
\r
872 (modified_length == 1
\r
873 ? "%s (%" APR_OFF_T_FMT ")"
\r
874 : "%s (%" APR_OFF_T_FMT ",%" APR_OFF_T_FMT ")"),
\r
876 modified_start + 1, modified_length));
\r
877 SVN_ERR(output_marker_eol(btn));
\r
878 SVN_ERR(output_merge_token_range(NULL, btn, 1/*modified*/,
\r
879 modified_start, modified_length));
\r
881 SVN_ERR(svn_stream_printf(btn->output_stream, btn->pool,
\r
882 (original_length == 1
\r
883 ? "%s (%" APR_OFF_T_FMT ")"
\r
884 : "%s (%" APR_OFF_T_FMT ",%" APR_OFF_T_FMT ")"),
\r
886 original_start + 1, original_length));
\r
887 SVN_ERR(output_marker_eol(btn));
\r
888 SVN_ERR(output_merge_token_range(NULL, btn, 0/*original*/,
\r
889 original_start, original_length));
\r
891 SVN_ERR(output_merge_marker(btn, 2/*separator*/));
\r
892 SVN_ERR(output_merge_token_range(NULL, btn, 2/*latest*/,
\r
893 latest_start, latest_length));
\r
894 SVN_ERR(svn_stream_printf(btn->output_stream, btn->pool,
\r
895 (latest_length == 1
\r
896 ? "%s (%" APR_OFF_T_FMT ")"
\r
897 : "%s (%" APR_OFF_T_FMT ",%" APR_OFF_T_FMT ")"),
\r
899 latest_start + 1, latest_length));
\r
900 SVN_ERR(output_marker_eol(btn));
\r
902 /* Go into print-trailing-context mode instead. */
\r
903 make_trailing_context_printer(btn);
\r
905 return SVN_NO_ERROR;
\r
909 static const svn_diff_output_fns_t merge_only_conflicts_output_vtable =
\r
911 output_common_modified,
\r
912 output_common_modified,
\r
914 output_common_modified,
\r
915 output_conflict_with_context
\r
919 /* TOKEN is the first token in the modified file.
\r
920 Return its line-ending, if any. */
\r
921 static const char *
\r
922 detect_eol(svn_string_t *token)
\r
926 if (token->len == 0)
\r
929 curp = token->data + token->len - 1;
\r
932 else if (*curp != '\n')
\r
936 if (token->len == 1
\r
937 || *(--curp) != '\r')
\r
945 svn_diff_mem_string_output_merge2(svn_stream_t *output_stream,
\r
947 const svn_string_t *original,
\r
948 const svn_string_t *modified,
\r
949 const svn_string_t *latest,
\r
950 const char *conflict_original,
\r
951 const char *conflict_modified,
\r
952 const char *conflict_latest,
\r
953 const char *conflict_separator,
\r
954 svn_diff_conflict_display_style_t style,
\r
957 merge_output_baton_t btn;
\r
959 svn_boolean_t conflicts_only =
\r
960 (style == svn_diff_conflict_display_only_conflicts);
\r
961 const svn_diff_output_fns_t *vtable = conflicts_only
\r
962 ? &merge_only_conflicts_output_vtable : &merge_output_vtable;
\r
964 memset(&btn, 0, sizeof(btn));
\r
966 if (conflicts_only)
\r
968 btn.pool = svn_pool_create(pool);
\r
969 make_context_saver(&btn);
\r
970 btn.real_output_stream = output_stream;
\r
973 btn.output_stream = output_stream;
\r
975 fill_source_tokens(&(btn.sources[0]), original, pool);
\r
976 fill_source_tokens(&(btn.sources[1]), modified, pool);
\r
977 fill_source_tokens(&(btn.sources[2]), latest, pool);
\r
979 btn.conflict_style = style;
\r
981 if (btn.sources[1].tokens->nelts > 0)
\r
983 eol = detect_eol(APR_ARRAY_IDX(btn.sources[1].tokens, 0, svn_string_t *));
\r
985 eol = APR_EOL_STR; /* use the platform default */
\r
988 eol = APR_EOL_STR; /* use the platform default */
\r
990 btn.marker_eol = eol;
\r
992 SVN_ERR(svn_utf_cstring_from_utf8(&btn.markers[1],
\r
994 ? conflict_modified
\r
995 : "<<<<<<< (modified)",
\r
997 SVN_ERR(svn_utf_cstring_from_utf8(&btn.markers[0],
\r
999 ? conflict_original
\r
1000 : "||||||| (original)",
\r
1002 SVN_ERR(svn_utf_cstring_from_utf8(&btn.markers[2],
\r
1003 conflict_separator
\r
1004 ? conflict_separator
\r
1007 SVN_ERR(svn_utf_cstring_from_utf8(&btn.markers[3],
\r
1010 : ">>>>>>> (latest)",
\r
1013 SVN_ERR(svn_diff_output(diff, &btn, vtable));
\r
1014 if (conflicts_only)
\r
1015 svn_pool_destroy(btn.pool);
\r
1017 return SVN_NO_ERROR;
\r
1021 svn_diff_mem_string_output_merge(svn_stream_t *output_stream,
\r
1023 const svn_string_t *original,
\r
1024 const svn_string_t *modified,
\r
1025 const svn_string_t *latest,
\r
1026 const char *conflict_original,
\r
1027 const char *conflict_modified,
\r
1028 const char *conflict_latest,
\r
1029 const char *conflict_separator,
\r
1030 svn_boolean_t display_original_in_conflict,
\r
1031 svn_boolean_t display_resolved_conflicts,
\r
1034 svn_diff_conflict_display_style_t style =
\r
1035 svn_diff_conflict_display_modified_latest;
\r
1037 if (display_resolved_conflicts)
\r
1038 style = svn_diff_conflict_display_resolved_modified_latest;
\r
1040 if (display_original_in_conflict)
\r
1041 style = svn_diff_conflict_display_modified_original_latest;
\r
1043 return svn_diff_mem_string_output_merge2(output_stream,
\r
1048 conflict_original,
\r
1049 conflict_modified,
\r
1051 conflict_separator,
\r