1251881Speter/*
2251881Speter * diff_memory.c :  routines for doing diffs on in-memory data
3251881Speter *
4251881Speter * ====================================================================
5251881Speter *    Licensed to the Apache Software Foundation (ASF) under one
6251881Speter *    or more contributor license agreements.  See the NOTICE file
7251881Speter *    distributed with this work for additional information
8251881Speter *    regarding copyright ownership.  The ASF licenses this file
9251881Speter *    to you under the Apache License, Version 2.0 (the
10251881Speter *    "License"); you may not use this file except in compliance
11251881Speter *    with the License.  You may obtain a copy of the License at
12251881Speter *
13251881Speter *      http://www.apache.org/licenses/LICENSE-2.0
14251881Speter *
15251881Speter *    Unless required by applicable law or agreed to in writing,
16251881Speter *    software distributed under the License is distributed on an
17251881Speter *    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
18251881Speter *    KIND, either express or implied.  See the License for the
19251881Speter *    specific language governing permissions and limitations
20251881Speter *    under the License.
21251881Speter * ====================================================================
22251881Speter */
23251881Speter
24251881Speter#define WANT_MEMFUNC
25251881Speter#define WANT_STRFUNC
26251881Speter#include <apr.h>
27251881Speter#include <apr_want.h>
28251881Speter#include <apr_tables.h>
29251881Speter
30251881Speter#include <assert.h>
31251881Speter
32251881Speter#include "svn_diff.h"
33251881Speter#include "svn_pools.h"
34251881Speter#include "svn_types.h"
35251881Speter#include "svn_string.h"
36251881Speter#include "svn_utf.h"
37251881Speter#include "diff.h"
38251881Speter#include "svn_private_config.h"
39251881Speter#include "private/svn_adler32.h"
40251881Speter#include "private/svn_diff_private.h"
41251881Speter
42251881Spetertypedef struct source_tokens_t
43251881Speter{
44251881Speter  /* A token simply is an svn_string_t pointing to
45251881Speter     the data of the in-memory data source, containing
46251881Speter     the raw token text, with length stored in the string */
47251881Speter  apr_array_header_t *tokens;
48251881Speter
49251881Speter  /* Next token to be consumed */
50251881Speter  apr_size_t next_token;
51251881Speter
52251881Speter  /* The source, containing the in-memory data to be diffed */
53251881Speter  const svn_string_t *source;
54251881Speter
55251881Speter  /* The last token ends with a newline character (sequence) */
56251881Speter  svn_boolean_t ends_without_eol;
57251881Speter} source_tokens_t;
58251881Speter
59251881Spetertypedef struct diff_mem_baton_t
60251881Speter{
61251881Speter  /* The tokens for each of the sources */
62251881Speter  source_tokens_t sources[4];
63251881Speter
64251881Speter  /* Normalization buffer; we only ever compare 2 tokens at the same time */
65251881Speter  char *normalization_buf[2];
66251881Speter
67251881Speter  /* Options for normalized comparison of the data sources */
68251881Speter  const svn_diff_file_options_t *normalization_options;
69251881Speter} diff_mem_baton_t;
70251881Speter
71251881Speter
72251881Speterstatic int
73251881Speterdatasource_to_index(svn_diff_datasource_e datasource)
74251881Speter{
75251881Speter  switch (datasource)
76251881Speter    {
77251881Speter    case svn_diff_datasource_original:
78251881Speter      return 0;
79251881Speter
80251881Speter    case svn_diff_datasource_modified:
81251881Speter      return 1;
82251881Speter
83251881Speter    case svn_diff_datasource_latest:
84251881Speter      return 2;
85251881Speter
86251881Speter    case svn_diff_datasource_ancestor:
87251881Speter      return 3;
88251881Speter    }
89251881Speter
90251881Speter  return -1;
91251881Speter}
92251881Speter
93251881Speter
94251881Speter/* Implements svn_diff_fns2_t::datasources_open */
95251881Speterstatic svn_error_t *
96251881Speterdatasources_open(void *baton,
97251881Speter                 apr_off_t *prefix_lines,
98251881Speter                 apr_off_t *suffix_lines,
99251881Speter                 const svn_diff_datasource_e *datasources,
100251881Speter                 apr_size_t datasources_len)
101251881Speter{
102251881Speter  /* Do nothing: everything is already there and initialized to 0 */
103251881Speter  *prefix_lines = 0;
104251881Speter  *suffix_lines = 0;
105251881Speter  return SVN_NO_ERROR;
106251881Speter}
107251881Speter
108251881Speter
109251881Speter/* Implements svn_diff_fns2_t::datasource_close */
110251881Speterstatic svn_error_t *
111251881Speterdatasource_close(void *baton, svn_diff_datasource_e datasource)
112251881Speter{
113251881Speter  /* Do nothing.  The compare_token function needs previous datasources
114251881Speter   * to stay available until all datasources are processed.
115251881Speter   */
116251881Speter
117251881Speter  return SVN_NO_ERROR;
118251881Speter}
119251881Speter
120251881Speter
121251881Speter/* Implements svn_diff_fns2_t::datasource_get_next_token */
122251881Speterstatic svn_error_t *
123251881Speterdatasource_get_next_token(apr_uint32_t *hash, void **token, void *baton,
124251881Speter                          svn_diff_datasource_e datasource)
125251881Speter{
126251881Speter  diff_mem_baton_t *mem_baton = baton;
127251881Speter  source_tokens_t *src = &(mem_baton->sources[datasource_to_index(datasource)]);
128251881Speter
129251881Speter  if ((apr_size_t)src->tokens->nelts > src->next_token)
130251881Speter    {
131251881Speter      /* There are actually tokens to be returned */
132251881Speter      char *buf = mem_baton->normalization_buf[0];
133251881Speter      svn_string_t *tok = (*token)
134251881Speter        = APR_ARRAY_IDX(src->tokens, src->next_token, svn_string_t *);
135251881Speter      apr_off_t len = tok->len;
136251881Speter      svn_diff__normalize_state_t state
137251881Speter        = svn_diff__normalize_state_normal;
138251881Speter
139251881Speter      svn_diff__normalize_buffer(&buf, &len, &state, tok->data,
140251881Speter                                 mem_baton->normalization_options);
141251881Speter      *hash = svn__adler32(0, buf, len);
142251881Speter      src->next_token++;
143251881Speter    }
144251881Speter  else
145251881Speter    *token = NULL;
146251881Speter
147251881Speter  return SVN_NO_ERROR;
148251881Speter}
149251881Speter
150251881Speter/* Implements svn_diff_fns2_t::token_compare */
151251881Speterstatic svn_error_t *
152251881Spetertoken_compare(void *baton, void *token1, void *token2, int *result)
153251881Speter{
154251881Speter  /* Implement the same behaviour as diff_file.c:token_compare(),
155251881Speter     but be simpler, because we know we'll have all data in memory */
156251881Speter  diff_mem_baton_t *btn = baton;
157251881Speter  svn_string_t *t1 = token1;
158251881Speter  svn_string_t *t2 = token2;
159251881Speter  char *buf1 = btn->normalization_buf[0];
160251881Speter  char *buf2 = btn->normalization_buf[1];
161251881Speter  apr_off_t len1 = t1->len;
162251881Speter  apr_off_t len2 = t2->len;
163251881Speter  svn_diff__normalize_state_t state = svn_diff__normalize_state_normal;
164251881Speter
165251881Speter  svn_diff__normalize_buffer(&buf1, &len1, &state, t1->data,
166251881Speter                             btn->normalization_options);
167251881Speter  state = svn_diff__normalize_state_normal;
168251881Speter  svn_diff__normalize_buffer(&buf2, &len2, &state, t2->data,
169251881Speter                             btn->normalization_options);
170251881Speter
171251881Speter  if (len1 != len2)
172251881Speter    *result = (len1 < len2) ? -1 : 1;
173251881Speter  else
174251881Speter    *result = (len1 == 0) ? 0 : memcmp(buf1, buf2, (size_t) len1);
175251881Speter
176251881Speter  return SVN_NO_ERROR;
177251881Speter}
178251881Speter
179251881Speter/* Implements svn_diff_fns2_t::token_discard */
180251881Speterstatic void
181251881Spetertoken_discard(void *baton, void *token)
182251881Speter{
183251881Speter  /* No-op, we have no use for discarded tokens... */
184251881Speter}
185251881Speter
186251881Speter
187251881Speter/* Implements svn_diff_fns2_t::token_discard_all */
188251881Speterstatic void
189251881Spetertoken_discard_all(void *baton)
190251881Speter{
191251881Speter  /* Do nothing.
192251881Speter     Note that in the file case, this function discards all
193251881Speter     tokens allocated, but we're geared toward small in-memory diffs.
194251881Speter     Meaning that there's no special pool to clear.
195251881Speter  */
196251881Speter}
197251881Speter
198251881Speter
199251881Speterstatic const svn_diff_fns2_t svn_diff__mem_vtable =
200251881Speter{
201251881Speter  datasources_open,
202251881Speter  datasource_close,
203251881Speter  datasource_get_next_token,
204251881Speter  token_compare,
205251881Speter  token_discard,
206251881Speter  token_discard_all
207251881Speter};
208251881Speter
209251881Speter/* Fill SRC with the diff tokens (e.g. lines).
210251881Speter
211251881Speter   TEXT is assumed to live long enough for the tokens to
212251881Speter   stay valid during their lifetime: no data is copied,
213251881Speter   instead, svn_string_t's are allocated pointing straight
214251881Speter   into TEXT.
215251881Speter*/
216251881Speterstatic void
217251881Speterfill_source_tokens(source_tokens_t *src,
218251881Speter                   const svn_string_t *text,
219251881Speter                   apr_pool_t *pool)
220251881Speter{
221251881Speter  const char *curp;
222251881Speter  const char *endp;
223251881Speter  const char *startp;
224251881Speter
225251881Speter  src->tokens = apr_array_make(pool, 0, sizeof(svn_string_t *));
226251881Speter  src->next_token = 0;
227251881Speter  src->source = text;
228251881Speter
229251881Speter  for (startp = curp = text->data, endp = curp + text->len;
230251881Speter       curp != endp; curp++)
231251881Speter    {
232251881Speter      if (curp != endp && *curp == '\r' && *(curp + 1) == '\n')
233251881Speter        curp++;
234251881Speter
235251881Speter      if (*curp == '\r' || *curp == '\n')
236251881Speter        {
237251881Speter          APR_ARRAY_PUSH(src->tokens, svn_string_t *) =
238251881Speter            svn_string_ncreate(startp, curp - startp + 1, pool);
239251881Speter
240251881Speter          startp = curp + 1;
241251881Speter        }
242251881Speter    }
243251881Speter
244251881Speter  /* If there's anything remaining (ie last line doesn't have a newline) */
245251881Speter  if (startp != endp)
246251881Speter    {
247251881Speter      APR_ARRAY_PUSH(src->tokens, svn_string_t *) =
248251881Speter        svn_string_ncreate(startp, endp - startp, pool);
249251881Speter      src->ends_without_eol = TRUE;
250251881Speter    }
251251881Speter  else
252251881Speter    src->ends_without_eol = FALSE;
253251881Speter}
254251881Speter
255251881Speter
256251881Speterstatic void
257251881Speteralloc_normalization_bufs(diff_mem_baton_t *btn,
258251881Speter                         int sources,
259251881Speter                         apr_pool_t *pool)
260251881Speter{
261251881Speter  apr_size_t max_len = 0;
262251881Speter  apr_off_t idx;
263251881Speter  int i;
264251881Speter
265251881Speter  for (i = 0; i < sources; i++)
266251881Speter    {
267251881Speter      apr_array_header_t *tokens = btn->sources[i].tokens;
268251881Speter      if (tokens->nelts > 0)
269251881Speter        for (idx = 0; idx < tokens->nelts; idx++)
270251881Speter          {
271251881Speter            apr_size_t token_len
272251881Speter              = APR_ARRAY_IDX(tokens, idx, svn_string_t *)->len;
273251881Speter            max_len = (max_len < token_len) ? token_len : max_len;
274251881Speter          }
275251881Speter    }
276251881Speter
277251881Speter  btn->normalization_buf[0] = apr_palloc(pool, max_len);
278251881Speter  btn->normalization_buf[1] = apr_palloc(pool, max_len);
279251881Speter}
280251881Speter
281251881Spetersvn_error_t *
282251881Spetersvn_diff_mem_string_diff(svn_diff_t **diff,
283251881Speter                         const svn_string_t *original,
284251881Speter                         const svn_string_t *modified,
285251881Speter                         const svn_diff_file_options_t *options,
286251881Speter                         apr_pool_t *pool)
287251881Speter{
288251881Speter  diff_mem_baton_t baton;
289251881Speter
290251881Speter  fill_source_tokens(&(baton.sources[0]), original, pool);
291251881Speter  fill_source_tokens(&(baton.sources[1]), modified, pool);
292251881Speter  alloc_normalization_bufs(&baton, 2, pool);
293251881Speter
294251881Speter  baton.normalization_options = options;
295251881Speter
296251881Speter  return svn_diff_diff_2(diff, &baton, &svn_diff__mem_vtable, pool);
297251881Speter}
298251881Speter
299251881Spetersvn_error_t *
300251881Spetersvn_diff_mem_string_diff3(svn_diff_t **diff,
301251881Speter                          const svn_string_t *original,
302251881Speter                          const svn_string_t *modified,
303251881Speter                          const svn_string_t *latest,
304251881Speter                          const svn_diff_file_options_t *options,
305251881Speter                          apr_pool_t *pool)
306251881Speter{
307251881Speter  diff_mem_baton_t baton;
308251881Speter
309251881Speter  fill_source_tokens(&(baton.sources[0]), original, pool);
310251881Speter  fill_source_tokens(&(baton.sources[1]), modified, pool);
311251881Speter  fill_source_tokens(&(baton.sources[2]), latest, pool);
312251881Speter  alloc_normalization_bufs(&baton, 3, pool);
313251881Speter
314251881Speter  baton.normalization_options = options;
315251881Speter
316251881Speter  return svn_diff_diff3_2(diff, &baton, &svn_diff__mem_vtable, pool);
317251881Speter}
318251881Speter
319251881Speter
320251881Spetersvn_error_t *
321251881Spetersvn_diff_mem_string_diff4(svn_diff_t **diff,
322251881Speter                          const svn_string_t *original,
323251881Speter                          const svn_string_t *modified,
324251881Speter                          const svn_string_t *latest,
325251881Speter                          const svn_string_t *ancestor,
326251881Speter                          const svn_diff_file_options_t *options,
327251881Speter                          apr_pool_t *pool)
328251881Speter{
329251881Speter  diff_mem_baton_t baton;
330251881Speter
331251881Speter  fill_source_tokens(&(baton.sources[0]), original, pool);
332251881Speter  fill_source_tokens(&(baton.sources[1]), modified, pool);
333251881Speter  fill_source_tokens(&(baton.sources[2]), latest, pool);
334251881Speter  fill_source_tokens(&(baton.sources[3]), ancestor, pool);
335251881Speter  alloc_normalization_bufs(&baton, 4, pool);
336251881Speter
337251881Speter  baton.normalization_options = options;
338251881Speter
339251881Speter  return svn_diff_diff4_2(diff, &baton, &svn_diff__mem_vtable, pool);
340251881Speter}
341251881Speter
342251881Speter
343251881Spetertypedef enum unified_output_e
344251881Speter{
345251881Speter  unified_output_context = 0,
346251881Speter  unified_output_delete,
347251881Speter  unified_output_insert,
348251881Speter  unified_output_skip
349251881Speter} unified_output_e;
350251881Speter
351251881Speter/* Baton for generating unified diffs */
352251881Spetertypedef struct unified_output_baton_t
353251881Speter{
354251881Speter  svn_stream_t *output_stream;
355251881Speter  const char *header_encoding;
356251881Speter  source_tokens_t sources[2]; /* 0 == original; 1 == modified */
357251881Speter  apr_off_t current_token[2]; /* current token per source */
358251881Speter
359251881Speter  /* Cached markers, in header_encoding,
360251881Speter     indexed using unified_output_e */
361251881Speter  const char *prefix_str[3];
362251881Speter
363251881Speter  svn_stringbuf_t *hunk;    /* in-progress hunk data */
364251881Speter  apr_off_t hunk_length[2]; /* 0 == original; 1 == modified */
365251881Speter  apr_off_t hunk_start[2];  /* 0 == original; 1 == modified */
366251881Speter
367251881Speter  /* The delimiters of the hunk header, '@@' for text hunks and '##' for
368251881Speter   * property hunks. */
369251881Speter  const char *hunk_delimiter;
370251881Speter  /* The string to print after a line that does not end with a newline.
371251881Speter   * It must start with a '\'.  Typically "\ No newline at end of file". */
372251881Speter  const char *no_newline_string;
373251881Speter
374251881Speter  /* Pool for allocation of temporary memory in the callbacks
375251881Speter     Should be cleared on entry of each iteration of a callback */
376251881Speter  apr_pool_t *pool;
377251881Speter} output_baton_t;
378251881Speter
379251881Speter
380251881Speter/* Append tokens (lines) FIRST up to PAST_LAST
381251881Speter   from token-source index TOKENS with change-type TYPE
382251881Speter   to the current hunk.
383251881Speter*/
384251881Speterstatic svn_error_t *
385251881Speteroutput_unified_token_range(output_baton_t *btn,
386251881Speter                           int tokens,
387251881Speter                           unified_output_e type,
388251881Speter                           apr_off_t until)
389251881Speter{
390251881Speter  source_tokens_t *source = &btn->sources[tokens];
391251881Speter
392251881Speter  if (until > source->tokens->nelts)
393251881Speter    until = source->tokens->nelts;
394251881Speter
395251881Speter  if (until <= btn->current_token[tokens])
396251881Speter    return SVN_NO_ERROR;
397251881Speter
398251881Speter  /* Do the loop with prefix and token */
399251881Speter  while (TRUE)
400251881Speter    {
401251881Speter      svn_string_t *token =
402251881Speter        APR_ARRAY_IDX(source->tokens, btn->current_token[tokens],
403251881Speter                      svn_string_t *);
404251881Speter
405251881Speter      if (type != unified_output_skip)
406251881Speter        {
407251881Speter          svn_stringbuf_appendcstr(btn->hunk, btn->prefix_str[type]);
408251881Speter          svn_stringbuf_appendbytes(btn->hunk, token->data, token->len);
409251881Speter        }
410251881Speter
411251881Speter      if (type == unified_output_context)
412251881Speter        {
413251881Speter          btn->hunk_length[0]++;
414251881Speter          btn->hunk_length[1]++;
415251881Speter        }
416251881Speter      else if (type == unified_output_delete)
417251881Speter        btn->hunk_length[0]++;
418251881Speter      else if (type == unified_output_insert)
419251881Speter        btn->hunk_length[1]++;
420251881Speter
421251881Speter      /* ### TODO: Add skip processing for -p handling? */
422251881Speter
423251881Speter      btn->current_token[tokens]++;
424251881Speter      if (btn->current_token[tokens] == until)
425251881Speter        break;
426251881Speter    }
427251881Speter
428251881Speter  if (btn->current_token[tokens] == source->tokens->nelts
429251881Speter      && source->ends_without_eol)
430251881Speter    {
431251881Speter      const char *out_str;
432251881Speter
433251881Speter      SVN_ERR(svn_utf_cstring_from_utf8_ex2(
434251881Speter                &out_str, btn->no_newline_string,
435251881Speter                btn->header_encoding, btn->pool));
436251881Speter      svn_stringbuf_appendcstr(btn->hunk, out_str);
437251881Speter    }
438251881Speter
439251881Speter
440251881Speter
441251881Speter  return SVN_NO_ERROR;
442251881Speter}
443251881Speter
444251881Speter/* Flush the hunk currently built up in BATON
445251881Speter   into the BATON's output_stream.
446251881Speter   Use the specified HUNK_DELIMITER.
447251881Speter   If HUNK_DELIMITER is NULL, fall back to the default delimiter. */
448251881Speterstatic svn_error_t *
449251881Speteroutput_unified_flush_hunk(output_baton_t *baton,
450251881Speter                          const char *hunk_delimiter)
451251881Speter{
452251881Speter  apr_off_t target_token;
453251881Speter  apr_size_t hunk_len;
454251881Speter  apr_off_t old_start;
455251881Speter  apr_off_t new_start;
456251881Speter
457251881Speter  if (svn_stringbuf_isempty(baton->hunk))
458251881Speter    return SVN_NO_ERROR;
459251881Speter
460251881Speter  svn_pool_clear(baton->pool);
461251881Speter
462251881Speter  /* Write the trailing context */
463251881Speter  target_token = baton->hunk_start[0] + baton->hunk_length[0]
464251881Speter                 + SVN_DIFF__UNIFIED_CONTEXT_SIZE;
465251881Speter  SVN_ERR(output_unified_token_range(baton, 0 /*original*/,
466251881Speter                                     unified_output_context,
467251881Speter                                     target_token));
468251881Speter  if (hunk_delimiter == NULL)
469251881Speter    hunk_delimiter = "@@";
470251881Speter
471251881Speter  old_start = baton->hunk_start[0];
472251881Speter  new_start = baton->hunk_start[1];
473251881Speter
474251881Speter  /* If the file is non-empty, convert the line indexes from
475251881Speter     zero based to one based */
476251881Speter  if (baton->hunk_length[0])
477251881Speter    old_start++;
478251881Speter  if (baton->hunk_length[1])
479251881Speter    new_start++;
480251881Speter
481251881Speter  /* Write the hunk header */
482251881Speter  SVN_ERR(svn_diff__unified_write_hunk_header(
483251881Speter            baton->output_stream, baton->header_encoding, hunk_delimiter,
484251881Speter            old_start, baton->hunk_length[0],
485251881Speter            new_start, baton->hunk_length[1],
486251881Speter            NULL /* hunk_extra_context */,
487251881Speter            baton->pool));
488251881Speter
489251881Speter  hunk_len = baton->hunk->len;
490251881Speter  SVN_ERR(svn_stream_write(baton->output_stream,
491251881Speter                           baton->hunk->data, &hunk_len));
492251881Speter
493251881Speter  /* Prepare for the next hunk */
494251881Speter  baton->hunk_length[0] = 0;
495251881Speter  baton->hunk_length[1] = 0;
496251881Speter  baton->hunk_start[0] = 0;
497251881Speter  baton->hunk_start[1] = 0;
498251881Speter  svn_stringbuf_setempty(baton->hunk);
499251881Speter
500251881Speter  return SVN_NO_ERROR;
501251881Speter}
502251881Speter
503251881Speter/* Implements svn_diff_output_fns_t::output_diff_modified */
504251881Speterstatic svn_error_t *
505251881Speteroutput_unified_diff_modified(void *baton,
506251881Speter                             apr_off_t original_start,
507251881Speter                             apr_off_t original_length,
508251881Speter                             apr_off_t modified_start,
509251881Speter                             apr_off_t modified_length,
510251881Speter                             apr_off_t latest_start,
511251881Speter                             apr_off_t latest_length)
512251881Speter{
513251881Speter  output_baton_t *output_baton = baton;
514251881Speter  apr_off_t context_prefix_length;
515251881Speter  apr_off_t prev_context_end;
516251881Speter  svn_boolean_t init_hunk = FALSE;
517251881Speter
518251881Speter  if (original_start > SVN_DIFF__UNIFIED_CONTEXT_SIZE)
519251881Speter    context_prefix_length = SVN_DIFF__UNIFIED_CONTEXT_SIZE;
520251881Speter  else
521251881Speter    context_prefix_length = original_start;
522251881Speter
523251881Speter  /* Calculate where the previous hunk will end if we would write it now
524251881Speter     (including the necessary context at the end) */
525251881Speter  if (output_baton->hunk_length[0] > 0 || output_baton->hunk_length[1] > 0)
526251881Speter    {
527251881Speter      prev_context_end = output_baton->hunk_start[0]
528251881Speter                         + output_baton->hunk_length[0]
529251881Speter                         + SVN_DIFF__UNIFIED_CONTEXT_SIZE;
530251881Speter    }
531251881Speter  else
532251881Speter    {
533251881Speter      prev_context_end = -1;
534251881Speter
535251881Speter      if (output_baton->hunk_start[0] == 0
536251881Speter          && (original_length > 0 || modified_length > 0))
537251881Speter        init_hunk = TRUE;
538251881Speter    }
539251881Speter
540251881Speter  /* If the changed range is far enough from the previous range, flush the current
541251881Speter     hunk. */
542251881Speter  {
543251881Speter    apr_off_t new_hunk_start = (original_start - context_prefix_length);
544251881Speter
545251881Speter    if (output_baton->current_token[0] < new_hunk_start
546251881Speter          && prev_context_end <= new_hunk_start)
547251881Speter      {
548251881Speter        SVN_ERR(output_unified_flush_hunk(output_baton,
549251881Speter                                          output_baton->hunk_delimiter));
550251881Speter        init_hunk = TRUE;
551251881Speter      }
552251881Speter    else if (output_baton->hunk_length[0] > 0
553251881Speter             || output_baton->hunk_length[1] > 0)
554251881Speter      {
555251881Speter        /* We extend the current hunk */
556251881Speter
557251881Speter        /* Original: Output the context preceding the changed range */
558251881Speter        SVN_ERR(output_unified_token_range(output_baton, 0 /* original */,
559251881Speter                                           unified_output_context,
560251881Speter                                           original_start));
561251881Speter      }
562251881Speter  }
563251881Speter
564251881Speter  /* Original: Skip lines until we are at the beginning of the context we want
565251881Speter     to display */
566251881Speter  SVN_ERR(output_unified_token_range(output_baton, 0 /* original */,
567251881Speter                                     unified_output_skip,
568251881Speter                                     original_start - context_prefix_length));
569251881Speter
570251881Speter  if (init_hunk)
571251881Speter    {
572251881Speter      SVN_ERR_ASSERT(output_baton->hunk_length[0] == 0
573251881Speter                     && output_baton->hunk_length[1] == 0);
574251881Speter
575251881Speter      output_baton->hunk_start[0] = original_start - context_prefix_length;
576251881Speter      output_baton->hunk_start[1] = modified_start - context_prefix_length;
577251881Speter    }
578251881Speter
579251881Speter  /* Modified: Skip lines until we are at the start of the changed range */
580251881Speter  SVN_ERR(output_unified_token_range(output_baton, 1 /* modified */,
581251881Speter                                     unified_output_skip,
582251881Speter                                     modified_start));
583251881Speter
584251881Speter  /* Original: Output the context preceding the changed range */
585251881Speter  SVN_ERR(output_unified_token_range(output_baton, 0 /* original */,
586251881Speter                                    unified_output_context,
587251881Speter                                    original_start));
588251881Speter
589251881Speter  /* Both: Output the changed range */
590251881Speter  SVN_ERR(output_unified_token_range(output_baton, 0 /* original */,
591251881Speter                                     unified_output_delete,
592251881Speter                                     original_start + original_length));
593251881Speter  SVN_ERR(output_unified_token_range(output_baton, 1 /* modified */,
594251881Speter                                     unified_output_insert,
595251881Speter                                     modified_start + modified_length));
596251881Speter
597251881Speter  return SVN_NO_ERROR;
598251881Speter}
599251881Speter
600251881Speterstatic const svn_diff_output_fns_t mem_output_unified_vtable =
601251881Speter{
602251881Speter  NULL, /* output_common */
603251881Speter  output_unified_diff_modified,
604251881Speter  NULL, /* output_diff_latest */
605251881Speter  NULL, /* output_diff_common */
606251881Speter  NULL  /* output_conflict */
607251881Speter};
608251881Speter
609251881Speter
610251881Spetersvn_error_t *
611251881Spetersvn_diff_mem_string_output_unified2(svn_stream_t *output_stream,
612251881Speter                                    svn_diff_t *diff,
613251881Speter                                    svn_boolean_t with_diff_header,
614251881Speter                                    const char *hunk_delimiter,
615251881Speter                                    const char *original_header,
616251881Speter                                    const char *modified_header,
617251881Speter                                    const char *header_encoding,
618251881Speter                                    const svn_string_t *original,
619251881Speter                                    const svn_string_t *modified,
620251881Speter                                    apr_pool_t *pool)
621251881Speter{
622251881Speter
623251881Speter  if (svn_diff_contains_diffs(diff))
624251881Speter    {
625251881Speter      output_baton_t baton;
626251881Speter
627251881Speter      memset(&baton, 0, sizeof(baton));
628251881Speter      baton.output_stream = output_stream;
629251881Speter      baton.pool = svn_pool_create(pool);
630251881Speter      baton.header_encoding = header_encoding;
631251881Speter      baton.hunk = svn_stringbuf_create_empty(pool);
632251881Speter      baton.hunk_delimiter = hunk_delimiter;
633251881Speter      baton.no_newline_string
634251881Speter        = (hunk_delimiter == NULL || strcmp(hunk_delimiter, "##") != 0)
635251881Speter          ? APR_EOL_STR SVN_DIFF__NO_NEWLINE_AT_END_OF_FILE APR_EOL_STR
636251881Speter          : APR_EOL_STR SVN_DIFF__NO_NEWLINE_AT_END_OF_PROPERTY APR_EOL_STR;
637251881Speter
638251881Speter      SVN_ERR(svn_utf_cstring_from_utf8_ex2
639251881Speter              (&(baton.prefix_str[unified_output_context]), " ",
640251881Speter               header_encoding, pool));
641251881Speter      SVN_ERR(svn_utf_cstring_from_utf8_ex2
642251881Speter              (&(baton.prefix_str[unified_output_delete]), "-",
643251881Speter               header_encoding, pool));
644251881Speter      SVN_ERR(svn_utf_cstring_from_utf8_ex2
645251881Speter              (&(baton.prefix_str[unified_output_insert]), "+",
646251881Speter               header_encoding, pool));
647251881Speter
648251881Speter      fill_source_tokens(&baton.sources[0], original, pool);
649251881Speter      fill_source_tokens(&baton.sources[1], modified, pool);
650251881Speter
651251881Speter      if (with_diff_header)
652251881Speter        {
653251881Speter          SVN_ERR(svn_diff__unidiff_write_header(
654251881Speter                    output_stream, header_encoding,
655251881Speter                    original_header, modified_header, pool));
656251881Speter        }
657251881Speter
658251881Speter      SVN_ERR(svn_diff_output(diff, &baton,
659251881Speter                              &mem_output_unified_vtable));
660251881Speter
661251881Speter      SVN_ERR(output_unified_flush_hunk(&baton, hunk_delimiter));
662251881Speter
663251881Speter      svn_pool_destroy(baton.pool);
664251881Speter    }
665251881Speter
666251881Speter  return SVN_NO_ERROR;
667251881Speter}
668251881Speter
669251881Spetersvn_error_t *
670251881Spetersvn_diff_mem_string_output_unified(svn_stream_t *output_stream,
671251881Speter                                   svn_diff_t *diff,
672251881Speter                                   const char *original_header,
673251881Speter                                   const char *modified_header,
674251881Speter                                   const char *header_encoding,
675251881Speter                                   const svn_string_t *original,
676251881Speter                                   const svn_string_t *modified,
677251881Speter                                   apr_pool_t *pool)
678251881Speter{
679251881Speter  SVN_ERR(svn_diff_mem_string_output_unified2(output_stream,
680251881Speter                                              diff,
681251881Speter                                              TRUE,
682251881Speter                                              NULL,
683251881Speter                                              original_header,
684251881Speter                                              modified_header,
685251881Speter                                              header_encoding,
686251881Speter                                              original,
687251881Speter                                              modified,
688251881Speter                                              pool));
689251881Speter  return SVN_NO_ERROR;
690251881Speter}
691251881Speter
692251881Speter
693251881Speter
694251881Speter/* diff3 merge output */
695251881Speter
696251881Speter/* A stream to remember *leading* context.  Note that this stream does
697251881Speter   *not* copy the data that it is remembering; it just saves
698251881Speter   *pointers! */
699251881Spetertypedef struct context_saver_t {
700251881Speter  svn_stream_t *stream;
701251881Speter  const char *data[SVN_DIFF__UNIFIED_CONTEXT_SIZE];
702251881Speter  apr_size_t len[SVN_DIFF__UNIFIED_CONTEXT_SIZE];
703251881Speter  apr_size_t next_slot;
704251881Speter  apr_size_t total_written;
705251881Speter} context_saver_t;
706251881Speter
707251881Speter
708251881Speterstatic svn_error_t *
709251881Spetercontext_saver_stream_write(void *baton,
710251881Speter                           const char *data,
711251881Speter                           apr_size_t *len)
712251881Speter{
713251881Speter  context_saver_t *cs = baton;
714251881Speter  cs->data[cs->next_slot] = data;
715251881Speter  cs->len[cs->next_slot] = *len;
716251881Speter  cs->next_slot = (cs->next_slot + 1) % SVN_DIFF__UNIFIED_CONTEXT_SIZE;
717251881Speter  cs->total_written++;
718251881Speter  return SVN_NO_ERROR;
719251881Speter}
720251881Speter
721251881Speter
722251881Spetertypedef struct merge_output_baton_t
723251881Speter{
724251881Speter  svn_stream_t *output_stream;
725251881Speter
726251881Speter  /* Tokenized source text */
727251881Speter  source_tokens_t sources[3];
728251881Speter  apr_off_t next_token[3];
729251881Speter
730251881Speter  /* Markers for marking conflicted sections */
731251881Speter  const char *markers[4]; /* 0 = original, 1 = modified,
732251881Speter                             2 = separator, 3 = latest (end) */
733251881Speter  const char *marker_eol;
734251881Speter
735251881Speter  svn_diff_conflict_display_style_t conflict_style;
736251881Speter
737251881Speter  /* The rest of the fields are for
738251881Speter     svn_diff_conflict_display_only_conflicts only.  Note that for
739251881Speter     these batons, OUTPUT_STREAM is either CONTEXT_SAVER->STREAM or
740251881Speter     (soon after a conflict) a "trailing context stream", never the
741251881Speter     actual output stream.*/
742251881Speter  /* The actual output stream. */
743251881Speter  svn_stream_t *real_output_stream;
744251881Speter  context_saver_t *context_saver;
745251881Speter  /* Used to allocate context_saver and trailing context streams, and
746251881Speter     for some printfs. */
747251881Speter  apr_pool_t *pool;
748251881Speter} merge_output_baton_t;
749251881Speter
750251881Speter
751251881Speterstatic svn_error_t *
752251881Speterflush_context_saver(context_saver_t *cs,
753251881Speter                    svn_stream_t *output_stream)
754251881Speter{
755251881Speter  int i;
756251881Speter  for (i = 0; i < SVN_DIFF__UNIFIED_CONTEXT_SIZE; i++)
757251881Speter    {
758251881Speter      apr_size_t slot = (i + cs->next_slot) % SVN_DIFF__UNIFIED_CONTEXT_SIZE;
759251881Speter      if (cs->data[slot])
760251881Speter        {
761251881Speter          apr_size_t len = cs->len[slot];
762251881Speter          SVN_ERR(svn_stream_write(output_stream, cs->data[slot], &len));
763251881Speter        }
764251881Speter    }
765251881Speter  return SVN_NO_ERROR;
766251881Speter}
767251881Speter
768251881Speter
769251881Speterstatic void
770251881Spetermake_context_saver(merge_output_baton_t *mob)
771251881Speter{
772251881Speter  context_saver_t *cs;
773251881Speter
774251881Speter  svn_pool_clear(mob->pool);
775251881Speter  cs = apr_pcalloc(mob->pool, sizeof(*cs));
776251881Speter  cs->stream = svn_stream_empty(mob->pool);
777251881Speter  svn_stream_set_baton(cs->stream, cs);
778251881Speter  svn_stream_set_write(cs->stream, context_saver_stream_write);
779251881Speter  mob->context_saver = cs;
780251881Speter  mob->output_stream = cs->stream;
781251881Speter}
782251881Speter
783251881Speter
784251881Speter/* A stream which prints SVN_DIFF__UNIFIED_CONTEXT_SIZE lines to
785251881Speter   BATON->REAL_OUTPUT_STREAM, and then changes BATON->OUTPUT_STREAM to
786251881Speter   a context_saver; used for *trailing* context. */
787251881Speter
788251881Speterstruct trailing_context_printer {
789251881Speter  apr_size_t lines_to_print;
790251881Speter  merge_output_baton_t *mob;
791251881Speter};
792251881Speter
793251881Speter
794251881Speterstatic svn_error_t *
795251881Spetertrailing_context_printer_write(void *baton,
796251881Speter                               const char *data,
797251881Speter                               apr_size_t *len)
798251881Speter{
799251881Speter  struct trailing_context_printer *tcp = baton;
800251881Speter  SVN_ERR_ASSERT(tcp->lines_to_print > 0);
801251881Speter  SVN_ERR(svn_stream_write(tcp->mob->real_output_stream, data, len));
802251881Speter  tcp->lines_to_print--;
803251881Speter  if (tcp->lines_to_print == 0)
804251881Speter    make_context_saver(tcp->mob);
805251881Speter  return SVN_NO_ERROR;
806251881Speter}
807251881Speter
808251881Speter
809251881Speterstatic void
810251881Spetermake_trailing_context_printer(merge_output_baton_t *btn)
811251881Speter{
812251881Speter  struct trailing_context_printer *tcp;
813251881Speter  svn_stream_t *s;
814251881Speter
815251881Speter  svn_pool_clear(btn->pool);
816251881Speter
817251881Speter  tcp = apr_pcalloc(btn->pool, sizeof(*tcp));
818251881Speter  tcp->lines_to_print = SVN_DIFF__UNIFIED_CONTEXT_SIZE;
819251881Speter  tcp->mob = btn;
820251881Speter  s = svn_stream_empty(btn->pool);
821251881Speter  svn_stream_set_baton(s, tcp);
822251881Speter  svn_stream_set_write(s, trailing_context_printer_write);
823251881Speter  btn->output_stream = s;
824251881Speter}
825251881Speter
826251881Speter
827251881Speterstatic svn_error_t *
828251881Speteroutput_merge_token_range(apr_size_t *lines_printed_p,
829251881Speter                         merge_output_baton_t *btn,
830251881Speter                         int idx, apr_off_t first,
831251881Speter                         apr_off_t length)
832251881Speter{
833251881Speter  apr_array_header_t *tokens = btn->sources[idx].tokens;
834251881Speter  apr_size_t lines_printed = 0;
835251881Speter
836251881Speter  for (; length > 0 && first < tokens->nelts; length--, first++)
837251881Speter    {
838251881Speter      svn_string_t *token = APR_ARRAY_IDX(tokens, first, svn_string_t *);
839251881Speter      apr_size_t len = token->len;
840251881Speter
841251881Speter      /* Note that the trailing context printer assumes that
842251881Speter         svn_stream_write is called exactly once per line. */
843251881Speter      SVN_ERR(svn_stream_write(btn->output_stream, token->data, &len));
844251881Speter      lines_printed++;
845251881Speter    }
846251881Speter
847251881Speter  if (lines_printed_p)
848251881Speter    *lines_printed_p = lines_printed;
849251881Speter
850251881Speter  return SVN_NO_ERROR;
851251881Speter}
852251881Speter
853251881Speterstatic svn_error_t *
854251881Speteroutput_marker_eol(merge_output_baton_t *btn)
855251881Speter{
856251881Speter  return svn_stream_puts(btn->output_stream, btn->marker_eol);
857251881Speter}
858251881Speter
859251881Speterstatic svn_error_t *
860251881Speteroutput_merge_marker(merge_output_baton_t *btn, int idx)
861251881Speter{
862251881Speter  SVN_ERR(svn_stream_puts(btn->output_stream, btn->markers[idx]));
863251881Speter  return output_marker_eol(btn);
864251881Speter}
865251881Speter
866251881Speterstatic svn_error_t *
867251881Speteroutput_common_modified(void *baton,
868251881Speter                       apr_off_t original_start, apr_off_t original_length,
869251881Speter                       apr_off_t modified_start, apr_off_t modified_length,
870251881Speter                       apr_off_t latest_start, apr_off_t latest_length)
871251881Speter{
872251881Speter  return output_merge_token_range(NULL, baton, 1/*modified*/,
873251881Speter                                  modified_start, modified_length);
874251881Speter}
875251881Speter
876251881Speterstatic svn_error_t *
877251881Speteroutput_latest(void *baton,
878251881Speter              apr_off_t original_start, apr_off_t original_length,
879251881Speter              apr_off_t modified_start, apr_off_t modified_length,
880251881Speter              apr_off_t latest_start, apr_off_t latest_length)
881251881Speter{
882251881Speter  return output_merge_token_range(NULL, baton, 2/*latest*/,
883251881Speter                                  latest_start, latest_length);
884251881Speter}
885251881Speter
886251881Speterstatic svn_error_t *
887251881Speteroutput_conflict(void *baton,
888251881Speter                apr_off_t original_start, apr_off_t original_length,
889251881Speter                apr_off_t modified_start, apr_off_t modified_length,
890251881Speter                apr_off_t latest_start, apr_off_t latest_length,
891251881Speter                svn_diff_t *diff);
892251881Speter
893251881Speterstatic const svn_diff_output_fns_t merge_output_vtable =
894251881Speter{
895251881Speter  output_common_modified, /* common */
896251881Speter  output_common_modified, /* modified */
897251881Speter  output_latest,
898251881Speter  output_common_modified, /* output_diff_common */
899251881Speter  output_conflict
900251881Speter};
901251881Speter
902251881Speterstatic svn_error_t *
903251881Speteroutput_conflict(void *baton,
904251881Speter                apr_off_t original_start, apr_off_t original_length,
905251881Speter                apr_off_t modified_start, apr_off_t modified_length,
906251881Speter                apr_off_t latest_start, apr_off_t latest_length,
907251881Speter                svn_diff_t *diff)
908251881Speter{
909251881Speter  merge_output_baton_t *btn = baton;
910251881Speter
911251881Speter  svn_diff_conflict_display_style_t style = btn->conflict_style;
912251881Speter
913251881Speter  if (style == svn_diff_conflict_display_resolved_modified_latest)
914251881Speter    {
915251881Speter      if (diff)
916251881Speter        return svn_diff_output(diff, baton, &merge_output_vtable);
917251881Speter      else
918251881Speter        style = svn_diff_conflict_display_modified_latest;
919251881Speter    }
920251881Speter
921251881Speter  if (style == svn_diff_conflict_display_modified_latest ||
922251881Speter      style == svn_diff_conflict_display_modified_original_latest)
923251881Speter    {
924251881Speter      SVN_ERR(output_merge_marker(btn, 1/*modified*/));
925251881Speter      SVN_ERR(output_merge_token_range(NULL, btn, 1/*modified*/,
926251881Speter                                       modified_start, modified_length));
927251881Speter
928251881Speter      if (style == svn_diff_conflict_display_modified_original_latest)
929251881Speter        {
930251881Speter          SVN_ERR(output_merge_marker(btn, 0/*original*/));
931251881Speter          SVN_ERR(output_merge_token_range(NULL, btn, 0/*original*/,
932251881Speter                                           original_start, original_length));
933251881Speter        }
934251881Speter
935251881Speter      SVN_ERR(output_merge_marker(btn, 2/*separator*/));
936251881Speter      SVN_ERR(output_merge_token_range(NULL, btn, 2/*latest*/,
937251881Speter                                       latest_start, latest_length));
938251881Speter      SVN_ERR(output_merge_marker(btn, 3/*latest (end)*/));
939251881Speter    }
940251881Speter  else if (style == svn_diff_conflict_display_modified)
941251881Speter      SVN_ERR(output_merge_token_range(NULL, btn, 1/*modified*/,
942251881Speter                                       modified_start, modified_length));
943251881Speter  else if (style == svn_diff_conflict_display_latest)
944251881Speter      SVN_ERR(output_merge_token_range(NULL, btn, 2/*latest*/,
945251881Speter                                       latest_start, latest_length));
946251881Speter  else /* unknown style */
947251881Speter    SVN_ERR_MALFUNCTION();
948251881Speter
949251881Speter  return SVN_NO_ERROR;
950251881Speter}
951251881Speter
952251881Speter
953251881Speterstatic svn_error_t *
954251881Speteroutput_conflict_with_context(void *baton,
955251881Speter                             apr_off_t original_start,
956251881Speter                             apr_off_t original_length,
957251881Speter                             apr_off_t modified_start,
958251881Speter                             apr_off_t modified_length,
959251881Speter                             apr_off_t latest_start,
960251881Speter                             apr_off_t latest_length,
961251881Speter                             svn_diff_t *diff)
962251881Speter{
963251881Speter  merge_output_baton_t *btn = baton;
964251881Speter
965251881Speter  /* Are we currently saving starting context (as opposed to printing
966251881Speter     trailing context)?  If so, flush it. */
967251881Speter  if (btn->output_stream == btn->context_saver->stream)
968251881Speter    {
969251881Speter      if (btn->context_saver->total_written > SVN_DIFF__UNIFIED_CONTEXT_SIZE)
970251881Speter        SVN_ERR(svn_stream_puts(btn->real_output_stream, "@@\n"));
971251881Speter      SVN_ERR(flush_context_saver(btn->context_saver, btn->real_output_stream));
972251881Speter    }
973251881Speter
974251881Speter  /* Print to the real output stream. */
975251881Speter  btn->output_stream = btn->real_output_stream;
976251881Speter
977251881Speter  /* Output the conflict itself. */
978251881Speter  SVN_ERR(svn_stream_printf(btn->output_stream, btn->pool,
979251881Speter                            (modified_length == 1
980251881Speter                             ? "%s (%" APR_OFF_T_FMT ")"
981251881Speter                             : "%s (%" APR_OFF_T_FMT ",%" APR_OFF_T_FMT ")"),
982251881Speter                            btn->markers[1],
983251881Speter                            modified_start + 1, modified_length));
984251881Speter  SVN_ERR(output_marker_eol(btn));
985251881Speter  SVN_ERR(output_merge_token_range(NULL, btn, 1/*modified*/,
986251881Speter                                   modified_start, modified_length));
987251881Speter
988251881Speter  SVN_ERR(svn_stream_printf(btn->output_stream, btn->pool,
989251881Speter                            (original_length == 1
990251881Speter                             ? "%s (%" APR_OFF_T_FMT ")"
991251881Speter                             : "%s (%" APR_OFF_T_FMT ",%" APR_OFF_T_FMT ")"),
992251881Speter                            btn->markers[0],
993251881Speter                            original_start + 1, original_length));
994251881Speter  SVN_ERR(output_marker_eol(btn));
995251881Speter  SVN_ERR(output_merge_token_range(NULL, btn, 0/*original*/,
996251881Speter                                   original_start, original_length));
997251881Speter
998251881Speter  SVN_ERR(output_merge_marker(btn, 2/*separator*/));
999251881Speter  SVN_ERR(output_merge_token_range(NULL, btn, 2/*latest*/,
1000251881Speter                                   latest_start, latest_length));
1001251881Speter  SVN_ERR(svn_stream_printf(btn->output_stream, btn->pool,
1002251881Speter                            (latest_length == 1
1003251881Speter                             ? "%s (%" APR_OFF_T_FMT ")"
1004251881Speter                             : "%s (%" APR_OFF_T_FMT ",%" APR_OFF_T_FMT ")"),
1005251881Speter                            btn->markers[3],
1006251881Speter                            latest_start + 1, latest_length));
1007251881Speter  SVN_ERR(output_marker_eol(btn));
1008251881Speter
1009251881Speter  /* Go into print-trailing-context mode instead. */
1010251881Speter  make_trailing_context_printer(btn);
1011251881Speter
1012251881Speter  return SVN_NO_ERROR;
1013251881Speter}
1014251881Speter
1015251881Speter
1016251881Speterstatic const svn_diff_output_fns_t merge_only_conflicts_output_vtable =
1017251881Speter{
1018251881Speter  output_common_modified,
1019251881Speter  output_common_modified,
1020251881Speter  output_latest,
1021251881Speter  output_common_modified,
1022251881Speter  output_conflict_with_context
1023251881Speter};
1024251881Speter
1025251881Speter
1026251881Speter/* TOKEN is the first token in the modified file.
1027251881Speter   Return its line-ending, if any. */
1028251881Speterstatic const char *
1029251881Speterdetect_eol(svn_string_t *token)
1030251881Speter{
1031251881Speter  const char *curp;
1032251881Speter
1033251881Speter  if (token->len == 0)
1034251881Speter    return NULL;
1035251881Speter
1036251881Speter  curp = token->data + token->len - 1;
1037251881Speter  if (*curp == '\r')
1038251881Speter    return "\r";
1039251881Speter  else if (*curp != '\n')
1040251881Speter    return NULL;
1041251881Speter  else
1042251881Speter    {
1043251881Speter      if (token->len == 1
1044251881Speter          || *(--curp) != '\r')
1045251881Speter        return "\n";
1046251881Speter      else
1047251881Speter        return "\r\n";
1048251881Speter    }
1049251881Speter}
1050251881Speter
1051251881Spetersvn_error_t *
1052251881Spetersvn_diff_mem_string_output_merge2(svn_stream_t *output_stream,
1053251881Speter                                  svn_diff_t *diff,
1054251881Speter                                  const svn_string_t *original,
1055251881Speter                                  const svn_string_t *modified,
1056251881Speter                                  const svn_string_t *latest,
1057251881Speter                                  const char *conflict_original,
1058251881Speter                                  const char *conflict_modified,
1059251881Speter                                  const char *conflict_latest,
1060251881Speter                                  const char *conflict_separator,
1061251881Speter                                  svn_diff_conflict_display_style_t style,
1062251881Speter                                  apr_pool_t *pool)
1063251881Speter{
1064251881Speter  merge_output_baton_t btn;
1065251881Speter  const char *eol;
1066251881Speter  svn_boolean_t conflicts_only =
1067251881Speter    (style == svn_diff_conflict_display_only_conflicts);
1068251881Speter  const svn_diff_output_fns_t *vtable = conflicts_only
1069251881Speter     ? &merge_only_conflicts_output_vtable : &merge_output_vtable;
1070251881Speter
1071251881Speter  memset(&btn, 0, sizeof(btn));
1072251881Speter
1073251881Speter  if (conflicts_only)
1074251881Speter    {
1075251881Speter      btn.pool = svn_pool_create(pool);
1076251881Speter      make_context_saver(&btn);
1077251881Speter      btn.real_output_stream = output_stream;
1078251881Speter    }
1079251881Speter  else
1080251881Speter    btn.output_stream = output_stream;
1081251881Speter
1082251881Speter  fill_source_tokens(&(btn.sources[0]), original, pool);
1083251881Speter  fill_source_tokens(&(btn.sources[1]), modified, pool);
1084251881Speter  fill_source_tokens(&(btn.sources[2]), latest, pool);
1085251881Speter
1086251881Speter  btn.conflict_style = style;
1087251881Speter
1088251881Speter  if (btn.sources[1].tokens->nelts > 0)
1089251881Speter    {
1090251881Speter      eol = detect_eol(APR_ARRAY_IDX(btn.sources[1].tokens, 0, svn_string_t *));
1091251881Speter      if (!eol)
1092251881Speter        eol = APR_EOL_STR;  /* use the platform default */
1093251881Speter    }
1094251881Speter  else
1095251881Speter    eol = APR_EOL_STR;  /* use the platform default */
1096251881Speter
1097251881Speter  btn.marker_eol = eol;
1098251881Speter
1099251881Speter  SVN_ERR(svn_utf_cstring_from_utf8(&btn.markers[1],
1100251881Speter                                    conflict_modified
1101251881Speter                                    ? conflict_modified
1102251881Speter                                    : "<<<<<<< (modified)",
1103251881Speter                                    pool));
1104251881Speter  SVN_ERR(svn_utf_cstring_from_utf8(&btn.markers[0],
1105251881Speter                                    conflict_original
1106251881Speter                                    ? conflict_original
1107251881Speter                                    : "||||||| (original)",
1108251881Speter                                    pool));
1109251881Speter  SVN_ERR(svn_utf_cstring_from_utf8(&btn.markers[2],
1110251881Speter                                    conflict_separator
1111251881Speter                                    ? conflict_separator
1112251881Speter                                    : "=======",
1113251881Speter                                    pool));
1114251881Speter  SVN_ERR(svn_utf_cstring_from_utf8(&btn.markers[3],
1115251881Speter                                    conflict_latest
1116251881Speter                                    ? conflict_latest
1117251881Speter                                    : ">>>>>>> (latest)",
1118251881Speter                                    pool));
1119251881Speter
1120251881Speter  SVN_ERR(svn_diff_output(diff, &btn, vtable));
1121251881Speter  if (conflicts_only)
1122251881Speter    svn_pool_destroy(btn.pool);
1123251881Speter
1124251881Speter  return SVN_NO_ERROR;
1125251881Speter}
1126251881Speter
1127251881Spetersvn_error_t *
1128251881Spetersvn_diff_mem_string_output_merge(svn_stream_t *output_stream,
1129251881Speter                                 svn_diff_t *diff,
1130251881Speter                                 const svn_string_t *original,
1131251881Speter                                 const svn_string_t *modified,
1132251881Speter                                 const svn_string_t *latest,
1133251881Speter                                 const char *conflict_original,
1134251881Speter                                 const char *conflict_modified,
1135251881Speter                                 const char *conflict_latest,
1136251881Speter                                 const char *conflict_separator,
1137251881Speter                                 svn_boolean_t display_original_in_conflict,
1138251881Speter                                 svn_boolean_t display_resolved_conflicts,
1139251881Speter                                 apr_pool_t *pool)
1140251881Speter{
1141251881Speter  svn_diff_conflict_display_style_t style =
1142251881Speter    svn_diff_conflict_display_modified_latest;
1143251881Speter
1144251881Speter  if (display_resolved_conflicts)
1145251881Speter    style = svn_diff_conflict_display_resolved_modified_latest;
1146251881Speter
1147251881Speter  if (display_original_in_conflict)
1148251881Speter    style = svn_diff_conflict_display_modified_original_latest;
1149251881Speter
1150251881Speter  return svn_diff_mem_string_output_merge2(output_stream,
1151251881Speter                                           diff,
1152251881Speter                                           original,
1153251881Speter                                           modified,
1154251881Speter                                           latest,
1155251881Speter                                           conflict_original,
1156251881Speter                                           conflict_modified,
1157251881Speter                                           conflict_latest,
1158251881Speter                                           conflict_separator,
1159251881Speter                                           style,
1160251881Speter                                           pool);
1161251881Speter}
1162