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
42typedef 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
59typedef 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
72static int
73datasource_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 */
95static svn_error_t *
96datasources_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 */
110static svn_error_t *
111datasource_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 */
122static svn_error_t *
123datasource_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 */
151static svn_error_t *
152token_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 */
180static void
181token_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 */
188static void
189token_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
199static 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*/
216static void
217fill_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
256static void
257alloc_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
281svn_error_t *
282svn_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
299svn_error_t *
300svn_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
320svn_error_t *
321svn_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
343typedef 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 */
352typedef 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*/
384static svn_error_t *
385output_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. */
448static svn_error_t *
449output_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 */
504static svn_error_t *
505output_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
600static 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
610svn_error_t *
611svn_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
669svn_error_t *
670svn_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
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! */
699typedef 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
708static svn_error_t *
709context_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
722typedef 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
751static svn_error_t *
752flush_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
769static void
770make_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
788struct trailing_context_printer {
789  apr_size_t lines_to_print;
790  merge_output_baton_t *mob;
791};
792
793
794static svn_error_t *
795trailing_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
809static void
810make_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
827static svn_error_t *
828output_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
853static svn_error_t *
854output_marker_eol(merge_output_baton_t *btn)
855{
856  return svn_stream_puts(btn->output_stream, btn->marker_eol);
857}
858
859static svn_error_t *
860output_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
866static svn_error_t *
867output_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
876static svn_error_t *
877output_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
886static svn_error_t *
887output_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
893static 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
902static svn_error_t *
903output_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
953static svn_error_t *
954output_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
1016static 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. */
1028static const char *
1029detect_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
1051svn_error_t *
1052svn_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
1127svn_error_t *
1128svn_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}
1162