OSDN Git Service

Merge from feature_merge branch. Build TortoiseMerge successfully.
[tortoisegit/TortoiseGitJp.git] / src / TortoiseMerge / libsvn_diff / diff_memory.c
1 /*\r
2  * diff_memory.c :  routines for doing diffs on in-memory data\r
3  *\r
4  * ====================================================================\r
5  * Copyright (c) 2007 CollabNet.  All rights reserved.\r
6  *\r
7  * This software is licensed as described in the file COPYING, which\r
8  * you should have received as part of this distribution.  The terms\r
9  * are also available at http://subversion.tigris.org/license-1.html.\r
10  * If newer versions of this license are posted there, you may use a\r
11  * newer version instead, at your option.\r
12  *\r
13  * This software consists of voluntary contributions made by many\r
14  * individuals.  For exact contribution history, see the revision\r
15  * history and logs, available at http://subversion.tigris.org/.\r
16  * ====================================================================\r
17  */\r
18 \r
19 #define WANT_MEMFUNC\r
20 #define WANT_STRFUNC\r
21 #include <apr.h>\r
22 #include <apr_want.h>\r
23 #include <apr_tables.h>\r
24 \r
25 #include <apr_general.h>\r
26 #include "svn_error.h"\r
27 #include "svn_version.h"\r
28 #include "svn_io.h"\r
29 #include "svn_ctype.h"\r
30 \r
31 #include "svn_diff.h"\r
32 #include "svn_pools.h"\r
33 #include "svn_types.h"\r
34 #include "svn_string.h"\r
35 #include "svn_utf.h"\r
36 #include "diff.h"\r
37 //#include "svn_private_config.h"\r
38 \r
39 typedef struct source_tokens_t\r
40 {\r
41   /* A token simply is an svn_string_t pointing to\r
42      the data of the in-memory data source, containing\r
43      the raw token text, with length stored in the string */\r
44   apr_array_header_t *tokens;\r
45 \r
46   /* Next token to be consumed */\r
47   apr_size_t next_token;\r
48 \r
49   /* The source, containing the in-memory data to be diffed */\r
50   svn_string_t *source;\r
51 \r
52   /* The last token ends with a newline character (sequence) */\r
53   svn_boolean_t ends_without_eol;\r
54 } source_tokens_t;\r
55 \r
56 typedef struct diff_mem_baton_t\r
57 {\r
58   /* The tokens for each of the sources */\r
59   source_tokens_t sources[4];\r
60 \r
61   /* Normalization buffer; we only ever compare 2 tokens at the same time */\r
62   char *normalization_buf[2];\r
63 \r
64   /* Options for normalized comparison of the data sources */\r
65   const svn_diff_file_options_t *normalization_options;\r
66 } diff_mem_baton_t;\r
67 \r
68 \r
69 static int\r
70 datasource_to_index(svn_diff_datasource_e datasource)\r
71 {\r
72   switch (datasource)\r
73     {\r
74     case svn_diff_datasource_original:\r
75       return 0;\r
76 \r
77     case svn_diff_datasource_modified:\r
78       return 1;\r
79 \r
80     case svn_diff_datasource_latest:\r
81       return 2;\r
82 \r
83     case svn_diff_datasource_ancestor:\r
84       return 3;\r
85     }\r
86 \r
87   return -1;\r
88 }\r
89 \r
90 \r
91 /* Implements svn_diff_fns_t::datasource_open */\r
92 static svn_error_t *\r
93 datasource_open(void *baton, svn_diff_datasource_e datasource)\r
94 {\r
95   /* Do nothing: everything is already there and initialized to 0 */\r
96   return SVN_NO_ERROR;\r
97 }\r
98 \r
99 \r
100 /* Implements svn_diff_fns_t::datasource_close */\r
101 static svn_error_t *\r
102 datasource_close(void *baton, svn_diff_datasource_e datasource)\r
103 {\r
104   /* Do nothing.  The compare_token function needs previous datasources\r
105    * to stay available until all datasources are processed.\r
106    */\r
107 \r
108   return SVN_NO_ERROR;\r
109 }\r
110 \r
111 \r
112 /* Implements svn_diff_fns_t::datasource_get_next_token */\r
113 static svn_error_t *\r
114 datasource_get_next_token(apr_uint32_t *hash, void **token, void *baton,\r
115                           svn_diff_datasource_e datasource)\r
116 {\r
117   diff_mem_baton_t *mem_baton = baton;\r
118   source_tokens_t *src = &(mem_baton->sources[datasource_to_index(datasource)]);\r
119 \r
120   if (src->tokens->nelts > src->next_token)\r
121     {\r
122       /* There are actually tokens to be returned */\r
123       char *buf = mem_baton->normalization_buf[0];\r
124       svn_string_t *tok = (*token)\r
125         = APR_ARRAY_IDX(src->tokens, src->next_token, svn_string_t *);\r
126       apr_off_t len = tok->len;\r
127       svn_diff__normalize_state_t state\r
128         = svn_diff__normalize_state_normal;\r
129 \r
130       svn_diff__normalize_buffer(&buf, &len, &state, tok->data,\r
131                                  mem_baton->normalization_options);\r
132       *hash = svn_diff__adler32(0, buf, len);\r
133       src->next_token++;\r
134     }\r
135   else\r
136     *token = NULL;\r
137 \r
138   return SVN_NO_ERROR;\r
139 }\r
140 \r
141 /* Implements svn_diff_fns_t::token_compare */\r
142 static svn_error_t *\r
143 token_compare(void *baton, void *token1, void *token2, int *result)\r
144 {\r
145   /* Implement the same behaviour as diff_file.c:token_compare(),\r
146      but be simpler, because we know we'll have all data in memory */\r
147   diff_mem_baton_t *btn = baton;\r
148   svn_string_t *t1 = token1;\r
149   svn_string_t *t2 = token2;\r
150   char *buf1 = btn->normalization_buf[0];\r
151   char *buf2 = btn->normalization_buf[1];\r
152   apr_off_t len1 = t1->len;\r
153   apr_off_t len2 = t2->len;\r
154   svn_diff__normalize_state_t state = svn_diff__normalize_state_normal;\r
155 \r
156   svn_diff__normalize_buffer(&buf1, &len1, &state, t1->data,\r
157                              btn->normalization_options);\r
158   state = svn_diff__normalize_state_normal;\r
159   svn_diff__normalize_buffer(&buf2, &len2, &state, t2->data,\r
160                              btn->normalization_options);\r
161 \r
162   if (len1 != len2)\r
163     *result = (len1 < len2) ? -1 : 1;\r
164   else\r
165     *result = (len1 == 0) ? 0 : memcmp(buf1, buf2, len1);\r
166 \r
167   return SVN_NO_ERROR;\r
168 }\r
169 \r
170 /* Implements svn_diff_fns_t::token_discard */\r
171 static void\r
172 token_discard(void *baton, void *token)\r
173 {\r
174   /* No-op, we have no use for discarded tokens... */\r
175 }\r
176 \r
177 \r
178 /* Implements svn_diff_fns_t::token_discard_all */\r
179 static void\r
180 token_discard_all(void *baton)\r
181 {\r
182   /* Do nothing.\r
183      Note that in the file case, this function discards all\r
184      tokens allocated, but we're geared toward small in-memory diffs.\r
185      Meaning that there's no special pool to clear.\r
186   */\r
187 }\r
188 \r
189 \r
190 static const svn_diff_fns_t svn_diff__mem_vtable =\r
191 {\r
192   datasource_open,\r
193   datasource_close,\r
194   datasource_get_next_token,\r
195   token_compare,\r
196   token_discard,\r
197   token_discard_all\r
198 };\r
199 \r
200 /* Fill SRC with the diff tokens (e.g. lines).\r
201 \r
202    TEXT is assumed to live long enough for the tokens to\r
203    stay valid during their lifetime: no data is copied,\r
204    instead, svn_string_t's are allocated pointing straight\r
205    into TEXT.\r
206 */\r
207 static void\r
208 fill_source_tokens(source_tokens_t *src,\r
209                    const svn_string_t *text,\r
210                    apr_pool_t *pool)\r
211 {\r
212   const char *curp;\r
213   const char *endp;\r
214   const char *startp;\r
215 \r
216   src->tokens = apr_array_make(pool, 0, sizeof(svn_string_t *));\r
217   src->next_token = 0;\r
218   src->source = (svn_string_t *)text;\r
219 \r
220   for (startp = curp = text->data, endp = curp + text->len;\r
221        curp != endp; curp++)\r
222     {\r
223       if (curp != endp && *curp == '\r' && *(curp + 1) == '\n')\r
224         curp++;\r
225 \r
226       if (*curp == '\r' || *curp == '\n')\r
227         {\r
228           APR_ARRAY_PUSH(src->tokens, svn_string_t *) =\r
229             svn_string_ncreate(startp, curp - startp + 1, pool);\r
230 \r
231           startp = curp + 1;\r
232         }\r
233     }\r
234 \r
235   /* If there's anything remaining (ie last line doesn't have a newline) */\r
236   if (startp != endp)\r
237     {\r
238       APR_ARRAY_PUSH(src->tokens, svn_string_t *) =\r
239         svn_string_ncreate(startp, endp - startp, pool);\r
240       src->ends_without_eol = TRUE;\r
241     }\r
242   else\r
243     src->ends_without_eol = FALSE;\r
244 }\r
245 \r
246 \r
247 static void\r
248 alloc_normalization_bufs(diff_mem_baton_t *btn,\r
249                          int sources,\r
250                          apr_pool_t *pool)\r
251 {\r
252   apr_size_t max_len = 0;\r
253   apr_off_t idx;\r
254   int i;\r
255 \r
256   for (i = 0; i < sources; i++)\r
257     {\r
258       apr_array_header_t *tokens = btn->sources[i].tokens;\r
259       if (tokens->nelts > 0)\r
260         for (idx = 0; idx < tokens->nelts; idx++)\r
261           {\r
262             apr_size_t token_len\r
263               = APR_ARRAY_IDX(tokens, idx, svn_string_t *)->len;\r
264             max_len = (max_len < token_len) ? token_len : max_len;\r
265           }\r
266     }\r
267 \r
268   btn->normalization_buf[0] = apr_palloc(pool, max_len);\r
269   btn->normalization_buf[1] = apr_palloc(pool, max_len);\r
270 }\r
271 \r
272 svn_error_t *\r
273 svn_diff_mem_string_diff(svn_diff_t **diff,\r
274                          const svn_string_t *original,\r
275                          const svn_string_t *modified,\r
276                          const svn_diff_file_options_t *options,\r
277                          apr_pool_t *pool)\r
278 {\r
279   diff_mem_baton_t baton;\r
280 \r
281   fill_source_tokens(&(baton.sources[0]), original, pool);\r
282   fill_source_tokens(&(baton.sources[1]), modified, pool);\r
283   alloc_normalization_bufs(&baton, 2, pool);\r
284 \r
285   baton.normalization_options = options;\r
286 \r
287   return svn_diff_diff(diff, &baton, &svn_diff__mem_vtable, pool);\r
288 }\r
289 \r
290 svn_error_t *\r
291 svn_diff_mem_string_diff3(svn_diff_t **diff,\r
292                           const svn_string_t *original,\r
293                           const svn_string_t *modified,\r
294                           const svn_string_t *latest,\r
295                           const svn_diff_file_options_t *options,\r
296                           apr_pool_t *pool)\r
297 {\r
298   diff_mem_baton_t baton;\r
299 \r
300   fill_source_tokens(&(baton.sources[0]), original, pool);\r
301   fill_source_tokens(&(baton.sources[1]), modified, pool);\r
302   fill_source_tokens(&(baton.sources[2]), latest, pool);\r
303   alloc_normalization_bufs(&baton, 3, pool);\r
304 \r
305   baton.normalization_options = options;\r
306 \r
307   return svn_diff_diff3(diff, &baton, &svn_diff__mem_vtable, pool);\r
308 }\r
309 \r
310 \r
311 svn_error_t *\r
312 svn_diff_mem_string_diff4(svn_diff_t **diff,\r
313                           const svn_string_t *original,\r
314                           const svn_string_t *modified,\r
315                           const svn_string_t *latest,\r
316                           const svn_string_t *ancestor,\r
317                           const svn_diff_file_options_t *options,\r
318                           apr_pool_t *pool)\r
319 {\r
320   diff_mem_baton_t baton;\r
321 \r
322   fill_source_tokens(&(baton.sources[0]), original, pool);\r
323   fill_source_tokens(&(baton.sources[1]), modified, pool);\r
324   fill_source_tokens(&(baton.sources[2]), latest, pool);\r
325   fill_source_tokens(&(baton.sources[3]), ancestor, pool);\r
326   alloc_normalization_bufs(&baton, 4, pool);\r
327 \r
328   baton.normalization_options = options;\r
329 \r
330   return svn_diff_diff4(diff, &baton, &svn_diff__mem_vtable, pool);\r
331 }\r
332 \r
333 \r
334 typedef enum unified_output_e\r
335 {\r
336   unified_output_context = 0,\r
337   unified_output_delete,\r
338   unified_output_insert\r
339 } unified_output_e;\r
340 \r
341 /* Baton for generating unified diffs */\r
342 typedef struct unified_output_baton_t\r
343 {\r
344   svn_stream_t *output_stream;\r
345   const char *header_encoding;\r
346   source_tokens_t sources[2]; /* 0 == original; 1 == modified */\r
347   apr_size_t next_token; /* next token in original source */\r
348 \r
349   /* Cached markers, in header_encoding,\r
350      indexed using unified_output_e */\r
351   const char *prefix_str[3];\r
352 \r
353   svn_stringbuf_t *hunk;    /* in-progress hunk data */\r
354   apr_off_t hunk_length[2]; /* 0 == original; 1 == modified */\r
355   apr_off_t hunk_start[2];  /* 0 == original; 1 == modified */\r
356 \r
357   /* Pool for allocation of temporary memory in the callbacks\r
358      Should be cleared on entry of each iteration of a callback */\r
359   apr_pool_t *pool;\r
360 } output_baton_t;\r
361 \r
362 \r
363 /* Append tokens (lines) FIRST up to PAST_LAST\r
364    from token-source index TOKENS with change-type TYPE\r
365    to the current hunk.\r
366 */\r
367 static svn_error_t *\r
368 output_unified_token_range(output_baton_t *btn,\r
369                            int tokens,\r
370                            unified_output_e type,\r
371                            apr_off_t first,\r
372                            apr_off_t past_last)\r
373 {\r
374   source_tokens_t *source = &btn->sources[tokens];\r
375   apr_off_t idx;\r
376 \r
377   past_last = (past_last > source->tokens->nelts)\r
378     ? source->tokens->nelts : past_last;\r
379 \r
380   if (tokens == 0)\r
381     /* We get context from the original source, don't expect\r
382        to be asked to output a block which starts before\r
383        what we already have written. */\r
384     first = (first < btn->next_token) ? btn->next_token : first;\r
385 \r
386   if (first >= past_last)\r
387     return SVN_NO_ERROR;\r
388 \r
389   /* Do the loop with prefix and token */\r
390   for (idx = first; idx < past_last; idx++)\r
391     {\r
392       svn_string_t *token\r
393         = APR_ARRAY_IDX(source->tokens, idx, svn_string_t *);\r
394       svn_stringbuf_appendcstr(btn->hunk, btn->prefix_str[type]);\r
395       svn_stringbuf_appendbytes(btn->hunk, token->data, token->len);\r
396 \r
397       if (type == unified_output_context)\r
398         {\r
399           btn->hunk_length[0]++;\r
400           btn->hunk_length[1]++;\r
401         }\r
402       else if (type == unified_output_delete)\r
403         btn->hunk_length[0]++;\r
404       else\r
405         btn->hunk_length[1]++;\r
406 \r
407     }\r
408   if (past_last == source->tokens->nelts && source->ends_without_eol)\r
409     {\r
410       const char *out_str;\r
411       SVN_ERR(svn_utf_cstring_from_utf8_ex2\r
412               (&out_str,\r
413                /* The string below is intentionally not marked for translation:\r
414                   it's vital to correct operation of the diff(1)/patch(1)\r
415                   program pair. */\r
416                APR_EOL_STR "\\ No newline at end of file" APR_EOL_STR,\r
417                btn->header_encoding, btn->pool));\r
418       svn_stringbuf_appendcstr(btn->hunk, out_str);\r
419     }\r
420 \r
421   if (tokens == 0)\r
422     btn->next_token = past_last;\r
423 \r
424   return SVN_NO_ERROR;\r
425 }\r
426 \r
427 /* Flush the hunk currently built up in BATON\r
428    into the baton's output_stream */\r
429 static svn_error_t *\r
430 output_unified_flush_hunk(output_baton_t *baton)\r
431 {\r
432   apr_off_t target_token;\r
433   apr_size_t hunk_len;\r
434 \r
435   if (svn_stringbuf_isempty(baton->hunk))\r
436     return SVN_NO_ERROR;\r
437 \r
438   svn_pool_clear(baton->pool);\r
439 \r
440   /* Write the trailing context */\r
441   target_token = baton->hunk_start[0] + baton->hunk_length[0]\r
442     + SVN_DIFF__UNIFIED_CONTEXT_SIZE;\r
443   SVN_ERR(output_unified_token_range(baton, 0 /*original*/,\r
444                                      unified_output_context,\r
445                                      baton->next_token, target_token));\r
446 \r
447   /* Write the hunk header */\r
448   if (baton->hunk_length[0] > 0)\r
449     /* Convert our 0-based line numbers into unidiff 1-based numbers */\r
450     baton->hunk_start[0]++;\r
451   SVN_ERR(svn_stream_printf_from_utf8\r
452           (baton->output_stream, baton->header_encoding,\r
453            baton->pool,\r
454            /* Hunk length 1 is implied, don't show the\r
455               length field if we have a hunk that long */\r
456            (baton->hunk_length[0] == 1)\r
457            ? ("@@ -%" APR_OFF_T_FMT)\r
458            : ("@@ -%" APR_OFF_T_FMT ",%" APR_OFF_T_FMT),\r
459            baton->hunk_start[0], baton->hunk_length[0]));\r
460 \r
461   if (baton->hunk_length[1] > 0)\r
462     /* Convert our 0-based line numbers into unidiff 1-based numbers */\r
463     baton->hunk_start[1]++;\r
464   SVN_ERR(svn_stream_printf_from_utf8\r
465           (baton->output_stream, baton->header_encoding,\r
466            baton->pool,\r
467            /* Hunk length 1 is implied, don't show the\r
468               length field if we have a hunk that long */\r
469            (baton->hunk_length[1] == 1)\r
470            ? (" +%" APR_OFF_T_FMT " @@" APR_EOL_STR)\r
471            : (" +%" APR_OFF_T_FMT ",%" APR_OFF_T_FMT " @@" APR_EOL_STR),\r
472            baton->hunk_start[1], baton->hunk_length[1]));\r
473 \r
474   hunk_len = baton->hunk->len;\r
475   SVN_ERR(svn_stream_write(baton->output_stream,\r
476                            baton->hunk->data, &hunk_len));\r
477 \r
478   baton->hunk_length[0] = baton->hunk_length[1] = 0;\r
479   svn_stringbuf_setempty(baton->hunk);\r
480 \r
481   return SVN_NO_ERROR;\r
482 }\r
483 \r
484 /* Implements svn_diff_output_fns_t::output_diff_modified */\r
485 static svn_error_t *\r
486 output_unified_diff_modified(void *baton,\r
487                              apr_off_t original_start,\r
488                              apr_off_t original_length,\r
489                              apr_off_t modified_start,\r
490                              apr_off_t modified_length,\r
491                              apr_off_t latest_start,\r
492                              apr_off_t latest_length)\r
493 {\r
494   output_baton_t *btn = baton;\r
495   apr_off_t targ_orig, targ_mod;\r
496 \r
497   targ_orig = original_start - SVN_DIFF__UNIFIED_CONTEXT_SIZE;\r
498   targ_orig = (targ_orig < 0) ? 0 : targ_orig;\r
499   targ_mod = modified_start;\r
500 \r
501   if (btn->next_token + SVN_DIFF__UNIFIED_CONTEXT_SIZE < targ_orig)\r
502     SVN_ERR(output_unified_flush_hunk(btn));\r
503 \r
504   if (btn->hunk_length[0] == 0\r
505       && btn->hunk_length[1] == 0)\r
506     {\r
507       btn->hunk_start[0] = targ_orig;\r
508       btn->hunk_start[1] = targ_mod + targ_orig - original_start;\r
509     }\r
510 \r
511   SVN_ERR(output_unified_token_range(btn, 0/*original*/,\r
512                                      unified_output_context,\r
513                                      targ_orig, original_start));\r
514   SVN_ERR(output_unified_token_range(btn, 0/*original*/,\r
515                                      unified_output_delete,\r
516                                      original_start,\r
517                                      original_start + original_length));\r
518   return output_unified_token_range(btn, 1/*modified*/, unified_output_insert,\r
519                                     modified_start,\r
520                                     modified_start + modified_length);\r
521 }\r
522 \r
523 static const svn_diff_output_fns_t mem_output_unified_vtable =\r
524 {\r
525   NULL, /* output_common */\r
526   output_unified_diff_modified,\r
527   NULL, /* output_diff_latest */\r
528   NULL, /* output_diff_common */\r
529   NULL  /* output_conflict */\r
530 };\r
531 \r
532 \r
533 svn_error_t *\r
534 svn_diff_mem_string_output_unified(svn_stream_t *output_stream,\r
535                                    svn_diff_t *diff,\r
536                                    const char *original_header,\r
537                                    const char *modified_header,\r
538                                    const char *header_encoding,\r
539                                    const svn_string_t *original,\r
540                                    const svn_string_t *modified,\r
541                                    apr_pool_t *pool)\r
542 {\r
543 \r
544   if (svn_diff_contains_diffs(diff))\r
545     {\r
546       output_baton_t baton;\r
547 \r
548       memset(&baton, 0, sizeof(baton));\r
549       baton.output_stream = output_stream;\r
550       baton.pool = svn_pool_create(pool);\r
551       baton.header_encoding = header_encoding;\r
552       baton.hunk = svn_stringbuf_create("", pool);\r
553 \r
554       SVN_ERR(svn_utf_cstring_from_utf8_ex2\r
555               (&(baton.prefix_str[unified_output_context]), " ",\r
556                header_encoding, pool));\r
557       SVN_ERR(svn_utf_cstring_from_utf8_ex2\r
558               (&(baton.prefix_str[unified_output_delete]), "-",\r
559                header_encoding, pool));\r
560       SVN_ERR(svn_utf_cstring_from_utf8_ex2\r
561               (&(baton.prefix_str[unified_output_insert]), "+",\r
562                header_encoding, pool));\r
563 \r
564       fill_source_tokens(&baton.sources[0], original, pool);\r
565       fill_source_tokens(&baton.sources[1], modified, pool);\r
566 \r
567       SVN_ERR(svn_stream_printf_from_utf8\r
568               (output_stream, header_encoding, pool,\r
569                "--- %s" APR_EOL_STR\r
570                "+++ %s" APR_EOL_STR,\r
571                original_header, modified_header));\r
572 \r
573       SVN_ERR(svn_diff_output(diff, &baton,\r
574                               &mem_output_unified_vtable));\r
575       SVN_ERR(output_unified_flush_hunk(&baton));\r
576 \r
577       svn_pool_destroy(baton.pool);\r
578     }\r
579 \r
580   return SVN_NO_ERROR;\r
581 }\r
582 \r
583 \r
584 \f\r
585 /* diff3 merge output */\r
586 \r
587 /* A stream to remember *leading* context.  Note that this stream does\r
588    *not* copy the data that it is remembering; it just saves\r
589    *pointers! */\r
590 typedef struct {\r
591   svn_stream_t *stream;\r
592   const char *data[SVN_DIFF__UNIFIED_CONTEXT_SIZE];\r
593   apr_size_t len[SVN_DIFF__UNIFIED_CONTEXT_SIZE];\r
594   apr_size_t next_slot;\r
595   apr_size_t total_written;\r
596 } context_saver_t;\r
597 \r
598 \r
599 static svn_error_t *\r
600 context_saver_stream_write(void *baton,\r
601                            const char *data,\r
602                            apr_size_t *len)\r
603 {\r
604   context_saver_t *cs = baton;\r
605   cs->data[cs->next_slot] = data;\r
606   cs->len[cs->next_slot] = *len;\r
607   cs->next_slot = (cs->next_slot + 1) % SVN_DIFF__UNIFIED_CONTEXT_SIZE;\r
608   cs->total_written++;\r
609   return SVN_NO_ERROR;\r
610 }\r
611 \r
612 \r
613 typedef struct merge_output_baton_t\r
614 {\r
615   svn_stream_t *output_stream;\r
616 \r
617   /* Tokenized source text */\r
618   source_tokens_t sources[3];\r
619   apr_off_t next_token;\r
620 \r
621   /* Markers for marking conflicted sections */\r
622   const char *markers[4]; /* 0 = original, 1 = modified,\r
623                              2 = separator, 3 = latest (end) */\r
624   const char *marker_eol;\r
625 \r
626   svn_diff_conflict_display_style_t conflict_style;\r
627 \r
628   /* The rest of the fields are for\r
629      svn_diff_conflict_display_only_conflicts only.  Note that for\r
630      these batons, OUTPUT_STREAM is either CONTEXT_SAVER->STREAM or\r
631      (soon after a conflict) a "trailing context stream", never the\r
632      actual output stream.*/\r
633   /* The actual output stream. */\r
634   svn_stream_t *real_output_stream;\r
635   context_saver_t *context_saver;\r
636   /* Used to allocate context_saver and trailing context streams, and\r
637      for some printfs. */\r
638   apr_pool_t *pool;\r
639 } merge_output_baton_t;\r
640 \r
641 \r
642 static svn_error_t *\r
643 flush_context_saver(context_saver_t *cs,\r
644                     svn_stream_t *output_stream)\r
645 {\r
646   int i;\r
647   for (i = 0; i < SVN_DIFF__UNIFIED_CONTEXT_SIZE; i++)\r
648     {\r
649       int slot = (i + cs->next_slot) % SVN_DIFF__UNIFIED_CONTEXT_SIZE;\r
650       if (cs->data[slot])\r
651         {\r
652           apr_size_t len = cs->len[slot];\r
653           SVN_ERR(svn_stream_write(output_stream, cs->data[slot], &len));\r
654         }\r
655     }\r
656   return SVN_NO_ERROR;\r
657 }\r
658 \r
659 \r
660 static void\r
661 make_context_saver(merge_output_baton_t *mob)\r
662 {\r
663   context_saver_t *cs;\r
664 \r
665   svn_pool_clear(mob->pool);\r
666   cs = apr_pcalloc(mob->pool, sizeof(*cs));\r
667   cs->stream = svn_stream_empty(mob->pool);\r
668   svn_stream_set_baton(cs->stream, cs);\r
669   svn_stream_set_write(cs->stream, context_saver_stream_write);\r
670   mob->context_saver = cs;\r
671   mob->output_stream = cs->stream;\r
672 }\r
673 \r
674 \r
675 /* A stream which prints SVN_DIFF__UNIFIED_CONTEXT_SIZE lines to\r
676    BATON->REAL_OUTPUT_STREAM, and then changes BATON->OUTPUT_STREAM to\r
677    a context_saver; used for *trailing* context. */\r
678 \r
679 struct trailing_context_printer {\r
680   apr_size_t lines_to_print;\r
681   merge_output_baton_t *mob;\r
682 };\r
683 \r
684 \r
685 static svn_error_t *\r
686 trailing_context_printer_write(void *baton,\r
687                                const char *data,\r
688                                apr_size_t *len)\r
689 {\r
690   struct trailing_context_printer *tcp = baton;\r
691   SVN_ERR_ASSERT(tcp->lines_to_print > 0);\r
692   SVN_ERR(svn_stream_write(tcp->mob->real_output_stream, data, len));\r
693   tcp->lines_to_print--;\r
694   if (tcp->lines_to_print == 0)\r
695     make_context_saver(tcp->mob);\r
696   return SVN_NO_ERROR;\r
697 }\r
698 \r
699 \r
700 static void\r
701 make_trailing_context_printer(merge_output_baton_t *btn)\r
702 {\r
703   struct trailing_context_printer *tcp;\r
704   svn_stream_t *s;\r
705 \r
706   svn_pool_clear(btn->pool);\r
707 \r
708   tcp = apr_pcalloc(btn->pool, sizeof(*tcp));\r
709   tcp->lines_to_print = SVN_DIFF__UNIFIED_CONTEXT_SIZE;\r
710   tcp->mob = btn;\r
711   s = svn_stream_empty(btn->pool);\r
712   svn_stream_set_baton(s, tcp);\r
713   svn_stream_set_write(s, trailing_context_printer_write);\r
714   btn->output_stream = s;\r
715 }\r
716 \r
717 \r
718 static svn_error_t *\r
719 output_merge_token_range(apr_size_t *lines_printed_p,\r
720                          merge_output_baton_t *btn,\r
721                          int idx, apr_off_t first,\r
722                          apr_off_t length)\r
723 {\r
724   apr_array_header_t *tokens = btn->sources[idx].tokens;\r
725   apr_size_t lines_printed = 0;\r
726 \r
727   for (; length > 0 && first < tokens->nelts; length--, first++)\r
728     {\r
729       svn_string_t *token = APR_ARRAY_IDX(tokens, first, svn_string_t *);\r
730       apr_size_t len = token->len;\r
731 \r
732       /* Note that the trailing context printer assumes that\r
733          svn_stream_write is called exactly once per line. */\r
734       SVN_ERR(svn_stream_write(btn->output_stream, token->data, &len));\r
735       lines_printed++;\r
736     }\r
737 \r
738   if (lines_printed_p)\r
739     *lines_printed_p = lines_printed;\r
740 \r
741   return SVN_NO_ERROR;\r
742 }\r
743 \r
744 static svn_error_t *\r
745 output_marker_eol(merge_output_baton_t *btn)\r
746 {\r
747   apr_size_t len = strlen(btn->marker_eol);\r
748   return svn_stream_write(btn->output_stream, btn->marker_eol, &len);\r
749 }\r
750 \r
751 static svn_error_t *\r
752 output_merge_marker(merge_output_baton_t *btn, int idx)\r
753 {\r
754   apr_size_t len = strlen(btn->markers[idx]);\r
755   SVN_ERR(svn_stream_write(btn->output_stream, btn->markers[idx], &len));\r
756   return output_marker_eol(btn);\r
757 }\r
758 \r
759 static svn_error_t *\r
760 output_common_modified(void *baton,\r
761                        apr_off_t original_start, apr_off_t original_length,\r
762                        apr_off_t modified_start, apr_off_t modified_length,\r
763                        apr_off_t latest_start, apr_off_t latest_length)\r
764 {\r
765   return output_merge_token_range(NULL, baton, 1/*modified*/,\r
766                                   modified_start, modified_length);\r
767 }\r
768 \r
769 static svn_error_t *\r
770 output_latest(void *baton,\r
771               apr_off_t original_start, apr_off_t original_length,\r
772               apr_off_t modified_start, apr_off_t modified_length,\r
773               apr_off_t latest_start, apr_off_t latest_length)\r
774 {\r
775   return output_merge_token_range(NULL, baton, 2/*latest*/,\r
776                                   latest_start, latest_length);\r
777 }\r
778 \r
779 static svn_error_t *\r
780 output_conflict(void *baton,\r
781                 apr_off_t original_start, apr_off_t original_length,\r
782                 apr_off_t modified_start, apr_off_t modified_length,\r
783                 apr_off_t latest_start, apr_off_t latest_length,\r
784                 svn_diff_t *diff);\r
785 \r
786 static const svn_diff_output_fns_t merge_output_vtable =\r
787 {\r
788   output_common_modified, /* common */\r
789   output_common_modified, /* modified */\r
790   output_latest,\r
791   output_common_modified, /* output_diff_common */\r
792   output_conflict\r
793 };\r
794 \r
795 static svn_error_t *\r
796 output_conflict(void *baton,\r
797                 apr_off_t original_start, apr_off_t original_length,\r
798                 apr_off_t modified_start, apr_off_t modified_length,\r
799                 apr_off_t latest_start, apr_off_t latest_length,\r
800                 svn_diff_t *diff)\r
801 {\r
802   merge_output_baton_t *btn = baton;\r
803 \r
804   svn_diff_conflict_display_style_t style = btn->conflict_style;\r
805 \r
806   if (style == svn_diff_conflict_display_resolved_modified_latest)\r
807     {\r
808       if (diff)\r
809         return svn_diff_output(diff, baton, &merge_output_vtable);\r
810       else\r
811         style = svn_diff_conflict_display_modified_latest;\r
812     }\r
813 \r
814   if (style == svn_diff_conflict_display_modified_latest ||\r
815       style == svn_diff_conflict_display_modified_original_latest)\r
816     {\r
817       SVN_ERR(output_merge_marker(btn, 1/*modified*/));\r
818       SVN_ERR(output_merge_token_range(NULL, btn, 1/*modified*/,\r
819                                        modified_start, modified_length));\r
820 \r
821       if (style == svn_diff_conflict_display_modified_original_latest)\r
822         {\r
823           SVN_ERR(output_merge_marker(btn, 0/*original*/));\r
824           SVN_ERR(output_merge_token_range(NULL, btn, 0/*original*/,\r
825                                            original_start, original_length));\r
826         }\r
827 \r
828       SVN_ERR(output_merge_marker(btn, 2/*separator*/));\r
829       SVN_ERR(output_merge_token_range(NULL, btn, 2/*latest*/,\r
830                                        latest_start, latest_length));\r
831       SVN_ERR(output_merge_marker(btn, 3/*latest (end)*/));\r
832     }\r
833   else if (style == svn_diff_conflict_display_modified)\r
834       SVN_ERR(output_merge_token_range(NULL, btn, 1/*modified*/,\r
835                                        modified_start, modified_length));\r
836   else if (style == svn_diff_conflict_display_latest)\r
837       SVN_ERR(output_merge_token_range(NULL, btn, 2/*latest*/,\r
838                                        latest_start, latest_length));\r
839   else /* unknown style */\r
840     SVN_ERR_MALFUNCTION();\r
841 \r
842   return SVN_NO_ERROR;\r
843 }\r
844 \r
845 \r
846 static svn_error_t *\r
847 output_conflict_with_context(void *baton,\r
848                              apr_off_t original_start,\r
849                              apr_off_t original_length,\r
850                              apr_off_t modified_start,\r
851                              apr_off_t modified_length,\r
852                              apr_off_t latest_start,\r
853                              apr_off_t latest_length,\r
854                              svn_diff_t *diff)\r
855 {\r
856   merge_output_baton_t *btn = baton;\r
857 \r
858   /* Are we currently saving starting context (as opposed to printing\r
859      trailing context)?  If so, flush it. */\r
860   if (btn->output_stream == btn->context_saver->stream)\r
861     {\r
862       if (btn->context_saver->total_written > SVN_DIFF__UNIFIED_CONTEXT_SIZE)\r
863         SVN_ERR(svn_stream_printf(btn->real_output_stream, btn->pool, "@@\n"));\r
864       SVN_ERR(flush_context_saver(btn->context_saver, btn->real_output_stream));\r
865     }\r
866 \r
867   /* Print to the real output stream. */\r
868   btn->output_stream = btn->real_output_stream;\r
869 \r
870   /* Output the conflict itself. */\r
871   SVN_ERR(svn_stream_printf(btn->output_stream, btn->pool,\r
872                             (modified_length == 1\r
873                              ? "%s (%" APR_OFF_T_FMT ")"\r
874                              : "%s (%" APR_OFF_T_FMT ",%" APR_OFF_T_FMT ")"),\r
875                             btn->markers[1],\r
876                             modified_start + 1, modified_length));\r
877   SVN_ERR(output_marker_eol(btn));\r
878   SVN_ERR(output_merge_token_range(NULL, btn, 1/*modified*/,\r
879                                    modified_start, modified_length));\r
880 \r
881   SVN_ERR(svn_stream_printf(btn->output_stream, btn->pool,\r
882                             (original_length == 1\r
883                              ? "%s (%" APR_OFF_T_FMT ")"\r
884                              : "%s (%" APR_OFF_T_FMT ",%" APR_OFF_T_FMT ")"),\r
885                             btn->markers[0],\r
886                             original_start + 1, original_length));\r
887   SVN_ERR(output_marker_eol(btn));\r
888   SVN_ERR(output_merge_token_range(NULL, btn, 0/*original*/,\r
889                                    original_start, original_length));\r
890 \r
891   SVN_ERR(output_merge_marker(btn, 2/*separator*/));\r
892   SVN_ERR(output_merge_token_range(NULL, btn, 2/*latest*/,\r
893                                    latest_start, latest_length));\r
894   SVN_ERR(svn_stream_printf(btn->output_stream, btn->pool,\r
895                             (latest_length == 1\r
896                              ? "%s (%" APR_OFF_T_FMT ")"\r
897                              : "%s (%" APR_OFF_T_FMT ",%" APR_OFF_T_FMT ")"),\r
898                             btn->markers[3],\r
899                             latest_start + 1, latest_length));\r
900   SVN_ERR(output_marker_eol(btn));\r
901 \r
902   /* Go into print-trailing-context mode instead. */\r
903   make_trailing_context_printer(btn);\r
904 \r
905   return SVN_NO_ERROR;\r
906 }\r
907 \r
908 \r
909 static const svn_diff_output_fns_t merge_only_conflicts_output_vtable =\r
910 {\r
911   output_common_modified,\r
912   output_common_modified,\r
913   output_latest,\r
914   output_common_modified,\r
915   output_conflict_with_context\r
916 };\r
917 \r
918 \r
919 /* TOKEN is the first token in the modified file.\r
920    Return its line-ending, if any. */\r
921 static const char *\r
922 detect_eol(svn_string_t *token)\r
923 {\r
924   const char *curp;\r
925 \r
926   if (token->len == 0)\r
927     return NULL;\r
928 \r
929   curp = token->data + token->len - 1;\r
930   if (*curp == '\r')\r
931     return "\r";\r
932   else if (*curp != '\n')\r
933     return NULL;\r
934   else\r
935     {\r
936       if (token->len == 1\r
937           || *(--curp) != '\r')\r
938         return "\n";\r
939       else\r
940         return "\r\n";\r
941     }\r
942 }\r
943 \r
944 svn_error_t *\r
945 svn_diff_mem_string_output_merge2(svn_stream_t *output_stream,\r
946                                   svn_diff_t *diff,\r
947                                   const svn_string_t *original,\r
948                                   const svn_string_t *modified,\r
949                                   const svn_string_t *latest,\r
950                                   const char *conflict_original,\r
951                                   const char *conflict_modified,\r
952                                   const char *conflict_latest,\r
953                                   const char *conflict_separator,\r
954                                   svn_diff_conflict_display_style_t style,\r
955                                   apr_pool_t *pool)\r
956 {\r
957   merge_output_baton_t btn;\r
958   const char *eol;\r
959   svn_boolean_t conflicts_only =\r
960     (style == svn_diff_conflict_display_only_conflicts);\r
961   const svn_diff_output_fns_t *vtable = conflicts_only\r
962      ? &merge_only_conflicts_output_vtable : &merge_output_vtable;\r
963 \r
964   memset(&btn, 0, sizeof(btn));\r
965 \r
966   if (conflicts_only)\r
967     {\r
968       btn.pool = svn_pool_create(pool);\r
969       make_context_saver(&btn);\r
970       btn.real_output_stream = output_stream;\r
971     }\r
972   else\r
973     btn.output_stream = output_stream;\r
974 \r
975   fill_source_tokens(&(btn.sources[0]), original, pool);\r
976   fill_source_tokens(&(btn.sources[1]), modified, pool);\r
977   fill_source_tokens(&(btn.sources[2]), latest, pool);\r
978 \r
979   btn.conflict_style = style;\r
980 \r
981   if (btn.sources[1].tokens->nelts > 0)\r
982     {\r
983       eol = detect_eol(APR_ARRAY_IDX(btn.sources[1].tokens, 0, svn_string_t *));\r
984       if (!eol)\r
985         eol = APR_EOL_STR;  /* use the platform default */\r
986     }\r
987   else\r
988     eol = APR_EOL_STR;  /* use the platform default */\r
989 \r
990   btn.marker_eol = eol;\r
991 \r
992   SVN_ERR(svn_utf_cstring_from_utf8(&btn.markers[1],\r
993                                     conflict_modified\r
994                                     ? conflict_modified\r
995                                     : "<<<<<<< (modified)",\r
996                                     pool));\r
997   SVN_ERR(svn_utf_cstring_from_utf8(&btn.markers[0],\r
998                                     conflict_original\r
999                                     ? conflict_original\r
1000                                     : "||||||| (original)",\r
1001                                     pool));\r
1002   SVN_ERR(svn_utf_cstring_from_utf8(&btn.markers[2],\r
1003                                     conflict_separator\r
1004                                     ? conflict_separator\r
1005                                     : "=======",\r
1006                                     pool));\r
1007   SVN_ERR(svn_utf_cstring_from_utf8(&btn.markers[3],\r
1008                                     conflict_latest\r
1009                                     ? conflict_latest\r
1010                                     : ">>>>>>> (latest)",\r
1011                                     pool));\r
1012 \r
1013   SVN_ERR(svn_diff_output(diff, &btn, vtable));\r
1014   if (conflicts_only)\r
1015     svn_pool_destroy(btn.pool);\r
1016 \r
1017   return SVN_NO_ERROR;\r
1018 }\r
1019 \r
1020 svn_error_t *\r
1021 svn_diff_mem_string_output_merge(svn_stream_t *output_stream,\r
1022                                  svn_diff_t *diff,\r
1023                                  const svn_string_t *original,\r
1024                                  const svn_string_t *modified,\r
1025                                  const svn_string_t *latest,\r
1026                                  const char *conflict_original,\r
1027                                  const char *conflict_modified,\r
1028                                  const char *conflict_latest,\r
1029                                  const char *conflict_separator,\r
1030                                  svn_boolean_t display_original_in_conflict,\r
1031                                  svn_boolean_t display_resolved_conflicts,\r
1032                                  apr_pool_t *pool)\r
1033 {\r
1034   svn_diff_conflict_display_style_t style =\r
1035     svn_diff_conflict_display_modified_latest;\r
1036 \r
1037   if (display_resolved_conflicts)\r
1038     style = svn_diff_conflict_display_resolved_modified_latest;\r
1039 \r
1040   if (display_original_in_conflict)\r
1041     style = svn_diff_conflict_display_modified_original_latest;\r
1042 \r
1043   return svn_diff_mem_string_output_merge2(output_stream,\r
1044                                            diff,\r
1045                                            original,\r
1046                                            modified,\r
1047                                            latest,\r
1048                                            conflict_original,\r
1049                                            conflict_modified,\r
1050                                            conflict_latest,\r
1051                                            conflict_separator,\r
1052                                            style,\r
1053                                            pool);\r
1054 }\r