]> CyberLeo.Net >> Repos - FreeBSD/releng/10.0.git/blob - contrib/subversion/subversion/libsvn_diff/diff_memory.c
- Copy stable/10 (r259064) to releng/10.0 as part of the
[FreeBSD/releng/10.0.git] / contrib / subversion / subversion / libsvn_diff / diff_memory.c
1 /*
2  * diff_memory.c :  routines for doing diffs on in-memory data
3  *
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
12  *
13  *      http://www.apache.org/licenses/LICENSE-2.0
14  *
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
20  *    under the License.
21  * ====================================================================
22  */
23
24 #define WANT_MEMFUNC
25 #define WANT_STRFUNC
26 #include <apr.h>
27 #include <apr_want.h>
28 #include <apr_tables.h>
29
30 #include <assert.h>
31
32 #include "svn_diff.h"
33 #include "svn_pools.h"
34 #include "svn_types.h"
35 #include "svn_string.h"
36 #include "svn_utf.h"
37 #include "diff.h"
38 #include "svn_private_config.h"
39 #include "private/svn_adler32.h"
40 #include "private/svn_diff_private.h"
41
42 typedef struct source_tokens_t
43 {
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;
48
49   /* Next token to be consumed */
50   apr_size_t next_token;
51
52   /* The source, containing the in-memory data to be diffed */
53   const svn_string_t *source;
54
55   /* The last token ends with a newline character (sequence) */
56   svn_boolean_t ends_without_eol;
57 } source_tokens_t;
58
59 typedef struct diff_mem_baton_t
60 {
61   /* The tokens for each of the sources */
62   source_tokens_t sources[4];
63
64   /* Normalization buffer; we only ever compare 2 tokens at the same time */
65   char *normalization_buf[2];
66
67   /* Options for normalized comparison of the data sources */
68   const svn_diff_file_options_t *normalization_options;
69 } diff_mem_baton_t;
70
71
72 static int
73 datasource_to_index(svn_diff_datasource_e datasource)
74 {
75   switch (datasource)
76     {
77     case svn_diff_datasource_original:
78       return 0;
79
80     case svn_diff_datasource_modified:
81       return 1;
82
83     case svn_diff_datasource_latest:
84       return 2;
85
86     case svn_diff_datasource_ancestor:
87       return 3;
88     }
89
90   return -1;
91 }
92
93
94 /* Implements svn_diff_fns2_t::datasources_open */
95 static svn_error_t *
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)
101 {
102   /* Do nothing: everything is already there and initialized to 0 */
103   *prefix_lines = 0;
104   *suffix_lines = 0;
105   return SVN_NO_ERROR;
106 }
107
108
109 /* Implements svn_diff_fns2_t::datasource_close */
110 static svn_error_t *
111 datasource_close(void *baton, svn_diff_datasource_e datasource)
112 {
113   /* Do nothing.  The compare_token function needs previous datasources
114    * to stay available until all datasources are processed.
115    */
116
117   return SVN_NO_ERROR;
118 }
119
120
121 /* Implements svn_diff_fns2_t::datasource_get_next_token */
122 static svn_error_t *
123 datasource_get_next_token(apr_uint32_t *hash, void **token, void *baton,
124                           svn_diff_datasource_e datasource)
125 {
126   diff_mem_baton_t *mem_baton = baton;
127   source_tokens_t *src = &(mem_baton->sources[datasource_to_index(datasource)]);
128
129   if ((apr_size_t)src->tokens->nelts > src->next_token)
130     {
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;
138
139       svn_diff__normalize_buffer(&buf, &len, &state, tok->data,
140                                  mem_baton->normalization_options);
141       *hash = svn__adler32(0, buf, len);
142       src->next_token++;
143     }
144   else
145     *token = NULL;
146
147   return SVN_NO_ERROR;
148 }
149
150 /* Implements svn_diff_fns2_t::token_compare */
151 static svn_error_t *
152 token_compare(void *baton, void *token1, void *token2, int *result)
153 {
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;
164
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);
170
171   if (len1 != len2)
172     *result = (len1 < len2) ? -1 : 1;
173   else
174     *result = (len1 == 0) ? 0 : memcmp(buf1, buf2, (size_t) len1);
175
176   return SVN_NO_ERROR;
177 }
178
179 /* Implements svn_diff_fns2_t::token_discard */
180 static void
181 token_discard(void *baton, void *token)
182 {
183   /* No-op, we have no use for discarded tokens... */
184 }
185
186
187 /* Implements svn_diff_fns2_t::token_discard_all */
188 static void
189 token_discard_all(void *baton)
190 {
191   /* Do nothing.
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.
195   */
196 }
197
198
199 static const svn_diff_fns2_t svn_diff__mem_vtable =
200 {
201   datasources_open,
202   datasource_close,
203   datasource_get_next_token,
204   token_compare,
205   token_discard,
206   token_discard_all
207 };
208
209 /* Fill SRC with the diff tokens (e.g. lines).
210
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
214    into TEXT.
215 */
216 static void
217 fill_source_tokens(source_tokens_t *src,
218                    const svn_string_t *text,
219                    apr_pool_t *pool)
220 {
221   const char *curp;
222   const char *endp;
223   const char *startp;
224
225   src->tokens = apr_array_make(pool, 0, sizeof(svn_string_t *));
226   src->next_token = 0;
227   src->source = text;
228
229   for (startp = curp = text->data, endp = curp + text->len;
230        curp != endp; curp++)
231     {
232       if (curp != endp && *curp == '\r' && *(curp + 1) == '\n')
233         curp++;
234
235       if (*curp == '\r' || *curp == '\n')
236         {
237           APR_ARRAY_PUSH(src->tokens, svn_string_t *) =
238             svn_string_ncreate(startp, curp - startp + 1, pool);
239
240           startp = curp + 1;
241         }
242     }
243
244   /* If there's anything remaining (ie last line doesn't have a newline) */
245   if (startp != endp)
246     {
247       APR_ARRAY_PUSH(src->tokens, svn_string_t *) =
248         svn_string_ncreate(startp, endp - startp, pool);
249       src->ends_without_eol = TRUE;
250     }
251   else
252     src->ends_without_eol = FALSE;
253 }
254
255
256 static void
257 alloc_normalization_bufs(diff_mem_baton_t *btn,
258                          int sources,
259                          apr_pool_t *pool)
260 {
261   apr_size_t max_len = 0;
262   apr_off_t idx;
263   int i;
264
265   for (i = 0; i < sources; i++)
266     {
267       apr_array_header_t *tokens = btn->sources[i].tokens;
268       if (tokens->nelts > 0)
269         for (idx = 0; idx < tokens->nelts; idx++)
270           {
271             apr_size_t token_len
272               = APR_ARRAY_IDX(tokens, idx, svn_string_t *)->len;
273             max_len = (max_len < token_len) ? token_len : max_len;
274           }
275     }
276
277   btn->normalization_buf[0] = apr_palloc(pool, max_len);
278   btn->normalization_buf[1] = apr_palloc(pool, max_len);
279 }
280
281 svn_error_t *
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,
286                          apr_pool_t *pool)
287 {
288   diff_mem_baton_t baton;
289
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);
293
294   baton.normalization_options = options;
295
296   return svn_diff_diff_2(diff, &baton, &svn_diff__mem_vtable, pool);
297 }
298
299 svn_error_t *
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,
305                           apr_pool_t *pool)
306 {
307   diff_mem_baton_t baton;
308
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);
313
314   baton.normalization_options = options;
315
316   return svn_diff_diff3_2(diff, &baton, &svn_diff__mem_vtable, pool);
317 }
318
319
320 svn_error_t *
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,
327                           apr_pool_t *pool)
328 {
329   diff_mem_baton_t baton;
330
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);
336
337   baton.normalization_options = options;
338
339   return svn_diff_diff4_2(diff, &baton, &svn_diff__mem_vtable, pool);
340 }
341
342
343 typedef enum unified_output_e
344 {
345   unified_output_context = 0,
346   unified_output_delete,
347   unified_output_insert,
348   unified_output_skip
349 } unified_output_e;
350
351 /* Baton for generating unified diffs */
352 typedef struct unified_output_baton_t
353 {
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 */
358
359   /* Cached markers, in header_encoding,
360      indexed using unified_output_e */
361   const char *prefix_str[3];
362
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 */
366
367   /* The delimiters of the hunk header, '@@' for text hunks and '##' for
368    * property hunks. */
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;
373
374   /* Pool for allocation of temporary memory in the callbacks
375      Should be cleared on entry of each iteration of a callback */
376   apr_pool_t *pool;
377 } output_baton_t;
378
379
380 /* Append tokens (lines) FIRST up to PAST_LAST
381    from token-source index TOKENS with change-type TYPE
382    to the current hunk.
383 */
384 static svn_error_t *
385 output_unified_token_range(output_baton_t *btn,
386                            int tokens,
387                            unified_output_e type,
388                            apr_off_t until)
389 {
390   source_tokens_t *source = &btn->sources[tokens];
391
392   if (until > source->tokens->nelts)
393     until = source->tokens->nelts;
394
395   if (until <= btn->current_token[tokens])
396     return SVN_NO_ERROR;
397
398   /* Do the loop with prefix and token */
399   while (TRUE)
400     {
401       svn_string_t *token =
402         APR_ARRAY_IDX(source->tokens, btn->current_token[tokens],
403                       svn_string_t *);
404
405       if (type != unified_output_skip)
406         {
407           svn_stringbuf_appendcstr(btn->hunk, btn->prefix_str[type]);
408           svn_stringbuf_appendbytes(btn->hunk, token->data, token->len);
409         }
410
411       if (type == unified_output_context)
412         {
413           btn->hunk_length[0]++;
414           btn->hunk_length[1]++;
415         }
416       else if (type == unified_output_delete)
417         btn->hunk_length[0]++;
418       else if (type == unified_output_insert)
419         btn->hunk_length[1]++;
420
421       /* ### TODO: Add skip processing for -p handling? */
422
423       btn->current_token[tokens]++;
424       if (btn->current_token[tokens] == until)
425         break;
426     }
427
428   if (btn->current_token[tokens] == source->tokens->nelts
429       && source->ends_without_eol)
430     {
431       const char *out_str;
432
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);
437     }
438
439
440
441   return SVN_NO_ERROR;
442 }
443
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. */
448 static svn_error_t *
449 output_unified_flush_hunk(output_baton_t *baton,
450                           const char *hunk_delimiter)
451 {
452   apr_off_t target_token;
453   apr_size_t hunk_len;
454   apr_off_t old_start;
455   apr_off_t new_start;
456
457   if (svn_stringbuf_isempty(baton->hunk))
458     return SVN_NO_ERROR;
459
460   svn_pool_clear(baton->pool);
461
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,
467                                      target_token));
468   if (hunk_delimiter == NULL)
469     hunk_delimiter = "@@";
470
471   old_start = baton->hunk_start[0];
472   new_start = baton->hunk_start[1];
473
474   /* If the file is non-empty, convert the line indexes from
475      zero based to one based */
476   if (baton->hunk_length[0])
477     old_start++;
478   if (baton->hunk_length[1])
479     new_start++;
480
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 */,
487             baton->pool));
488
489   hunk_len = baton->hunk->len;
490   SVN_ERR(svn_stream_write(baton->output_stream,
491                            baton->hunk->data, &hunk_len));
492
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);
499
500   return SVN_NO_ERROR;
501 }
502
503 /* Implements svn_diff_output_fns_t::output_diff_modified */
504 static svn_error_t *
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)
512 {
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;
517
518   if (original_start > SVN_DIFF__UNIFIED_CONTEXT_SIZE)
519     context_prefix_length = SVN_DIFF__UNIFIED_CONTEXT_SIZE;
520   else
521     context_prefix_length = original_start;
522
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)
526     {
527       prev_context_end = output_baton->hunk_start[0]
528                          + output_baton->hunk_length[0]
529                          + SVN_DIFF__UNIFIED_CONTEXT_SIZE;
530     }
531   else
532     {
533       prev_context_end = -1;
534
535       if (output_baton->hunk_start[0] == 0
536           && (original_length > 0 || modified_length > 0))
537         init_hunk = TRUE;
538     }
539
540   /* If the changed range is far enough from the previous range, flush the current
541      hunk. */
542   {
543     apr_off_t new_hunk_start = (original_start - context_prefix_length);
544
545     if (output_baton->current_token[0] < new_hunk_start
546           && prev_context_end <= new_hunk_start)
547       {
548         SVN_ERR(output_unified_flush_hunk(output_baton,
549                                           output_baton->hunk_delimiter));
550         init_hunk = TRUE;
551       }
552     else if (output_baton->hunk_length[0] > 0
553              || output_baton->hunk_length[1] > 0)
554       {
555         /* We extend the current hunk */
556
557         /* Original: Output the context preceding the changed range */
558         SVN_ERR(output_unified_token_range(output_baton, 0 /* original */,
559                                            unified_output_context,
560                                            original_start));
561       }
562   }
563
564   /* Original: Skip lines until we are at the beginning of the context we want
565      to display */
566   SVN_ERR(output_unified_token_range(output_baton, 0 /* original */,
567                                      unified_output_skip,
568                                      original_start - context_prefix_length));
569
570   if (init_hunk)
571     {
572       SVN_ERR_ASSERT(output_baton->hunk_length[0] == 0
573                      && output_baton->hunk_length[1] == 0);
574
575       output_baton->hunk_start[0] = original_start - context_prefix_length;
576       output_baton->hunk_start[1] = modified_start - context_prefix_length;
577     }
578
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 */,
581                                      unified_output_skip,
582                                      modified_start));
583
584   /* Original: Output the context preceding the changed range */
585   SVN_ERR(output_unified_token_range(output_baton, 0 /* original */,
586                                     unified_output_context,
587                                     original_start));
588
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));
596
597   return SVN_NO_ERROR;
598 }
599
600 static const svn_diff_output_fns_t mem_output_unified_vtable =
601 {
602   NULL, /* output_common */
603   output_unified_diff_modified,
604   NULL, /* output_diff_latest */
605   NULL, /* output_diff_common */
606   NULL  /* output_conflict */
607 };
608
609
610 svn_error_t *
611 svn_diff_mem_string_output_unified2(svn_stream_t *output_stream,
612                                     svn_diff_t *diff,
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,
620                                     apr_pool_t *pool)
621 {
622
623   if (svn_diff_contains_diffs(diff))
624     {
625       output_baton_t baton;
626
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;
637
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));
647
648       fill_source_tokens(&baton.sources[0], original, pool);
649       fill_source_tokens(&baton.sources[1], modified, pool);
650
651       if (with_diff_header)
652         {
653           SVN_ERR(svn_diff__unidiff_write_header(
654                     output_stream, header_encoding,
655                     original_header, modified_header, pool));
656         }
657
658       SVN_ERR(svn_diff_output(diff, &baton,
659                               &mem_output_unified_vtable));
660
661       SVN_ERR(output_unified_flush_hunk(&baton, hunk_delimiter));
662
663       svn_pool_destroy(baton.pool);
664     }
665
666   return SVN_NO_ERROR;
667 }
668
669 svn_error_t *
670 svn_diff_mem_string_output_unified(svn_stream_t *output_stream,
671                                    svn_diff_t *diff,
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,
677                                    apr_pool_t *pool)
678 {
679   SVN_ERR(svn_diff_mem_string_output_unified2(output_stream,
680                                               diff,
681                                               TRUE,
682                                               NULL,
683                                               original_header,
684                                               modified_header,
685                                               header_encoding,
686                                               original,
687                                               modified,
688                                               pool));
689   return SVN_NO_ERROR;
690 }
691
692
693 \f
694 /* diff3 merge output */
695
696 /* A stream to remember *leading* context.  Note that this stream does
697    *not* copy the data that it is remembering; it just saves
698    *pointers! */
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;
705 } context_saver_t;
706
707
708 static svn_error_t *
709 context_saver_stream_write(void *baton,
710                            const char *data,
711                            apr_size_t *len)
712 {
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;
717   cs->total_written++;
718   return SVN_NO_ERROR;
719 }
720
721
722 typedef struct merge_output_baton_t
723 {
724   svn_stream_t *output_stream;
725
726   /* Tokenized source text */
727   source_tokens_t sources[3];
728   apr_off_t next_token[3];
729
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;
734
735   svn_diff_conflict_display_style_t conflict_style;
736
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
746      for some printfs. */
747   apr_pool_t *pool;
748 } merge_output_baton_t;
749
750
751 static svn_error_t *
752 flush_context_saver(context_saver_t *cs,
753                     svn_stream_t *output_stream)
754 {
755   int i;
756   for (i = 0; i < SVN_DIFF__UNIFIED_CONTEXT_SIZE; i++)
757     {
758       apr_size_t slot = (i + cs->next_slot) % SVN_DIFF__UNIFIED_CONTEXT_SIZE;
759       if (cs->data[slot])
760         {
761           apr_size_t len = cs->len[slot];
762           SVN_ERR(svn_stream_write(output_stream, cs->data[slot], &len));
763         }
764     }
765   return SVN_NO_ERROR;
766 }
767
768
769 static void
770 make_context_saver(merge_output_baton_t *mob)
771 {
772   context_saver_t *cs;
773
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;
781 }
782
783
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. */
787
788 struct trailing_context_printer {
789   apr_size_t lines_to_print;
790   merge_output_baton_t *mob;
791 };
792
793
794 static svn_error_t *
795 trailing_context_printer_write(void *baton,
796                                const char *data,
797                                apr_size_t *len)
798 {
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);
805   return SVN_NO_ERROR;
806 }
807
808
809 static void
810 make_trailing_context_printer(merge_output_baton_t *btn)
811 {
812   struct trailing_context_printer *tcp;
813   svn_stream_t *s;
814
815   svn_pool_clear(btn->pool);
816
817   tcp = apr_pcalloc(btn->pool, sizeof(*tcp));
818   tcp->lines_to_print = SVN_DIFF__UNIFIED_CONTEXT_SIZE;
819   tcp->mob = btn;
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;
824 }
825
826
827 static svn_error_t *
828 output_merge_token_range(apr_size_t *lines_printed_p,
829                          merge_output_baton_t *btn,
830                          int idx, apr_off_t first,
831                          apr_off_t length)
832 {
833   apr_array_header_t *tokens = btn->sources[idx].tokens;
834   apr_size_t lines_printed = 0;
835
836   for (; length > 0 && first < tokens->nelts; length--, first++)
837     {
838       svn_string_t *token = APR_ARRAY_IDX(tokens, first, svn_string_t *);
839       apr_size_t len = token->len;
840
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));
844       lines_printed++;
845     }
846
847   if (lines_printed_p)
848     *lines_printed_p = lines_printed;
849
850   return SVN_NO_ERROR;
851 }
852
853 static svn_error_t *
854 output_marker_eol(merge_output_baton_t *btn)
855 {
856   return svn_stream_puts(btn->output_stream, btn->marker_eol);
857 }
858
859 static svn_error_t *
860 output_merge_marker(merge_output_baton_t *btn, int idx)
861 {
862   SVN_ERR(svn_stream_puts(btn->output_stream, btn->markers[idx]));
863   return output_marker_eol(btn);
864 }
865
866 static svn_error_t *
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)
871 {
872   return output_merge_token_range(NULL, baton, 1/*modified*/,
873                                   modified_start, modified_length);
874 }
875
876 static svn_error_t *
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)
881 {
882   return output_merge_token_range(NULL, baton, 2/*latest*/,
883                                   latest_start, latest_length);
884 }
885
886 static svn_error_t *
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,
891                 svn_diff_t *diff);
892
893 static const svn_diff_output_fns_t merge_output_vtable =
894 {
895   output_common_modified, /* common */
896   output_common_modified, /* modified */
897   output_latest,
898   output_common_modified, /* output_diff_common */
899   output_conflict
900 };
901
902 static svn_error_t *
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,
907                 svn_diff_t *diff)
908 {
909   merge_output_baton_t *btn = baton;
910
911   svn_diff_conflict_display_style_t style = btn->conflict_style;
912
913   if (style == svn_diff_conflict_display_resolved_modified_latest)
914     {
915       if (diff)
916         return svn_diff_output(diff, baton, &merge_output_vtable);
917       else
918         style = svn_diff_conflict_display_modified_latest;
919     }
920
921   if (style == svn_diff_conflict_display_modified_latest ||
922       style == svn_diff_conflict_display_modified_original_latest)
923     {
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));
927
928       if (style == svn_diff_conflict_display_modified_original_latest)
929         {
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));
933         }
934
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)*/));
939     }
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();
948
949   return SVN_NO_ERROR;
950 }
951
952
953 static svn_error_t *
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,
961                              svn_diff_t *diff)
962 {
963   merge_output_baton_t *btn = baton;
964
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)
968     {
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));
972     }
973
974   /* Print to the real output stream. */
975   btn->output_stream = btn->real_output_stream;
976
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 ")"),
982                             btn->markers[1],
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));
987
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 ")"),
992                             btn->markers[0],
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));
997
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,
1002                             (latest_length == 1
1003                              ? "%s (%" APR_OFF_T_FMT ")"
1004                              : "%s (%" APR_OFF_T_FMT ",%" APR_OFF_T_FMT ")"),
1005                             btn->markers[3],
1006                             latest_start + 1, latest_length));
1007   SVN_ERR(output_marker_eol(btn));
1008
1009   /* Go into print-trailing-context mode instead. */
1010   make_trailing_context_printer(btn);
1011
1012   return SVN_NO_ERROR;
1013 }
1014
1015
1016 static const svn_diff_output_fns_t merge_only_conflicts_output_vtable =
1017 {
1018   output_common_modified,
1019   output_common_modified,
1020   output_latest,
1021   output_common_modified,
1022   output_conflict_with_context
1023 };
1024
1025
1026 /* TOKEN is the first token in the modified file.
1027    Return its line-ending, if any. */
1028 static const char *
1029 detect_eol(svn_string_t *token)
1030 {
1031   const char *curp;
1032
1033   if (token->len == 0)
1034     return NULL;
1035
1036   curp = token->data + token->len - 1;
1037   if (*curp == '\r')
1038     return "\r";
1039   else if (*curp != '\n')
1040     return NULL;
1041   else
1042     {
1043       if (token->len == 1
1044           || *(--curp) != '\r')
1045         return "\n";
1046       else
1047         return "\r\n";
1048     }
1049 }
1050
1051 svn_error_t *
1052 svn_diff_mem_string_output_merge2(svn_stream_t *output_stream,
1053                                   svn_diff_t *diff,
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,
1062                                   apr_pool_t *pool)
1063 {
1064   merge_output_baton_t btn;
1065   const char *eol;
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;
1070
1071   memset(&btn, 0, sizeof(btn));
1072
1073   if (conflicts_only)
1074     {
1075       btn.pool = svn_pool_create(pool);
1076       make_context_saver(&btn);
1077       btn.real_output_stream = output_stream;
1078     }
1079   else
1080     btn.output_stream = output_stream;
1081
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);
1085
1086   btn.conflict_style = style;
1087
1088   if (btn.sources[1].tokens->nelts > 0)
1089     {
1090       eol = detect_eol(APR_ARRAY_IDX(btn.sources[1].tokens, 0, svn_string_t *));
1091       if (!eol)
1092         eol = APR_EOL_STR;  /* use the platform default */
1093     }
1094   else
1095     eol = APR_EOL_STR;  /* use the platform default */
1096
1097   btn.marker_eol = eol;
1098
1099   SVN_ERR(svn_utf_cstring_from_utf8(&btn.markers[1],
1100                                     conflict_modified
1101                                     ? conflict_modified
1102                                     : "<<<<<<< (modified)",
1103                                     pool));
1104   SVN_ERR(svn_utf_cstring_from_utf8(&btn.markers[0],
1105                                     conflict_original
1106                                     ? conflict_original
1107                                     : "||||||| (original)",
1108                                     pool));
1109   SVN_ERR(svn_utf_cstring_from_utf8(&btn.markers[2],
1110                                     conflict_separator
1111                                     ? conflict_separator
1112                                     : "=======",
1113                                     pool));
1114   SVN_ERR(svn_utf_cstring_from_utf8(&btn.markers[3],
1115                                     conflict_latest
1116                                     ? conflict_latest
1117                                     : ">>>>>>> (latest)",
1118                                     pool));
1119
1120   SVN_ERR(svn_diff_output(diff, &btn, vtable));
1121   if (conflicts_only)
1122     svn_pool_destroy(btn.pool);
1123
1124   return SVN_NO_ERROR;
1125 }
1126
1127 svn_error_t *
1128 svn_diff_mem_string_output_merge(svn_stream_t *output_stream,
1129                                  svn_diff_t *diff,
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,
1139                                  apr_pool_t *pool)
1140 {
1141   svn_diff_conflict_display_style_t style =
1142     svn_diff_conflict_display_modified_latest;
1143
1144   if (display_resolved_conflicts)
1145     style = svn_diff_conflict_display_resolved_modified_latest;
1146
1147   if (display_original_in_conflict)
1148     style = svn_diff_conflict_display_modified_original_latest;
1149
1150   return svn_diff_mem_string_output_merge2(output_stream,
1151                                            diff,
1152                                            original,
1153                                            modified,
1154                                            latest,
1155                                            conflict_original,
1156                                            conflict_modified,
1157                                            conflict_latest,
1158                                            conflict_separator,
1159                                            style,
1160                                            pool);
1161 }