OSDN Git Service

Fail try to porting
[tortoisegit/TortoiseGitJp.git] / src / TortoiseMerge / libsvn_diff / diff_file.c
1 /*\r
2  * diff_file.c :  routines for doing diffs on files\r
3  *\r
4  * ====================================================================\r
5  * Copyright (c) 2000-2006 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 \r
20 #include <apr.h>\r
21 #include <apr_pools.h>\r
22 #include <apr_general.h>\r
23 #include <apr_file_io.h>\r
24 #include <apr_file_info.h>\r
25 #include <apr_time.h>\r
26 #include <apr_mmap.h>\r
27 #include <apr_getopt.h>\r
28 \r
29 #include "svn_error.h"\r
30 #include "svn_types.h"\r
31 \r
32 #include "svn_string.h"\r
33 //#include "svn_io.h"\r
34 //#include "svn_utf.h"\r
35 #include "svn_pools.h"\r
36 #include "diff.h"\r
37 //#include "svn_private_config.h"\r
38 //#include "svn_path.h"\r
39 //#include "svn_ctype.h"\r
40 #include "svn_diff.h"\r
41 \r
42 \r
43 /* A token, i.e. a line read from a\r
44 file. */\r
45 typedef struct svn_diff__file_token_t\r
46 {\r
47   /* Next token in free list. */\r
48   struct svn_diff__file_token_t *next;\r
49   svn_diff_datasource_e datasource;\r
50   /* Offset in the datasource. */\r
51   apr_off_t offset;\r
52   /* Offset of the normalized token (may skip leading whitespace) */\r
53   apr_off_t norm_offset;\r
54   /* Total length - before normalization. */\r
55   apr_off_t raw_length;\r
56   /* Total length - after normalization. */\r
57   apr_off_t length;\r
58 } svn_diff__file_token_t;\r
59 \r
60 \r
61 typedef struct svn_diff__file_baton_t\r
62 {\r
63   const svn_diff_file_options_t *options;\r
64   const char *path[4];\r
65 \r
66   apr_file_t *file[4];\r
67   apr_off_t size[4];\r
68 \r
69   int chunk[4];\r
70   char *buffer[4];\r
71   char *curp[4];\r
72   char *endp[4];\r
73 \r
74   /* List of free tokens that may be reused. */\r
75   svn_diff__file_token_t *tokens;\r
76 \r
77   svn_diff__normalize_state_t normalize_state[4];\r
78 \r
79   apr_pool_t *pool;\r
80 } svn_diff__file_baton_t;\r
81 \r
82 \r
83 /* Look for the start of an end-of-line sequence (i.e. CR or LF)\r
84  * in the array pointed to by BUF, of length LEN.\r
85  * If such a byte is found, return the pointer to it, else return NULL.\r
86  */\r
87 static char *\r
88 find_eol_start(char *buf, apr_size_t len)\r
89 {\r
90   for (; len > 0; ++buf, --len)\r
91     {\r
92       if (*buf == '\n' || *buf == '\r')\r
93         return buf;\r
94     }\r
95   return NULL;\r
96 }\r
97 \r
98 static int\r
99 datasource_to_index(svn_diff_datasource_e datasource)\r
100 {\r
101   switch (datasource)\r
102     {\r
103     case svn_diff_datasource_original:\r
104       return 0;\r
105 \r
106     case svn_diff_datasource_modified:\r
107       return 1;\r
108 \r
109     case svn_diff_datasource_latest:\r
110       return 2;\r
111 \r
112     case svn_diff_datasource_ancestor:\r
113       return 3;\r
114     }\r
115 \r
116   return -1;\r
117 }\r
118 \r
119 /* Files are read in chunks of 128k.  There is no support for this number\r
120  * whatsoever.  If there is a number someone comes up with that has some\r
121  * argumentation, let's use that.\r
122  */\r
123 #define CHUNK_SHIFT 17\r
124 #define CHUNK_SIZE (1 << CHUNK_SHIFT)\r
125 \r
126 #define chunk_to_offset(chunk) ((chunk) << CHUNK_SHIFT)\r
127 #define offset_to_chunk(offset) ((offset) >> CHUNK_SHIFT)\r
128 #define offset_in_chunk(offset) ((offset) & (CHUNK_SIZE - 1))\r
129 \r
130 \r
131 /* Read a chunk from a FILE into BUFFER, starting from OFFSET, going for\r
132  * *LENGTH.  The actual bytes read are stored in *LENGTH on return.\r
133  */\r
134 static APR_INLINE svn_error_t *\r
135 read_chunk(apr_file_t *file, const char *path,\r
136            char *buffer, apr_size_t length,\r
137            apr_off_t offset, apr_pool_t *pool)\r
138 {\r
139   /* XXX: The final offset may not be the one we asked for.\r
140    * XXX: Check.\r
141    */\r
142   SVN_ERR(svn_io_file_seek(file, APR_SET, &offset, pool));\r
143   return svn_io_file_read_full(file, buffer, length, NULL, pool);\r
144 }\r
145 \r
146 \r
147 /* Map or read a file at PATH. *BUFFER will point to the file\r
148  * contents; if the file was mapped, *FILE and *MM will contain the\r
149  * mmap context; otherwise they will be NULL.  SIZE will contain the\r
150  * file size.  Allocate from POOL.\r
151  */\r
152 #if APR_HAS_MMAP\r
153 #define MMAP_T_PARAM(NAME) apr_mmap_t **NAME,\r
154 #define MMAP_T_ARG(NAME)   &(NAME),\r
155 #else\r
156 #define MMAP_T_PARAM(NAME)\r
157 #define MMAP_T_ARG(NAME)\r
158 #endif\r
159 \r
160 static svn_error_t *\r
161 map_or_read_file(apr_file_t **file,\r
162                  MMAP_T_PARAM(mm)\r
163                  char **buffer, apr_off_t *size,\r
164                  const char *path, apr_pool_t *pool)\r
165 {\r
166   apr_finfo_t finfo;\r
167   apr_status_t rv;\r
168 \r
169   *buffer = NULL;\r
170 \r
171   SVN_ERR(svn_io_file_open(file, path, APR_READ, APR_OS_DEFAULT, pool));\r
172   SVN_ERR(svn_io_file_info_get(&finfo, APR_FINFO_SIZE, *file, pool));\r
173 \r
174 #if APR_HAS_MMAP\r
175   if (finfo.size > APR_MMAP_THRESHOLD)\r
176     {\r
177       rv = apr_mmap_create(mm, *file, 0, finfo.size, APR_MMAP_READ, pool);\r
178       if (rv == APR_SUCCESS)\r
179         {\r
180           *buffer = (*mm)->mm;\r
181         }\r
182 \r
183       /* On failure we just fall through and try reading the file into\r
184        * memory instead.\r
185        */\r
186     }\r
187 #endif /* APR_HAS_MMAP */\r
188 \r
189    if (*buffer == NULL && finfo.size > 0)\r
190     {\r
191       *buffer = apr_palloc(pool, finfo.size);\r
192 \r
193       SVN_ERR(svn_io_file_read_full(*file, *buffer, finfo.size, NULL, pool));\r
194 \r
195       /* Since we have the entire contents of the file we can\r
196        * close it now.\r
197        */\r
198       SVN_ERR(svn_io_file_close(*file, pool));\r
199 \r
200       *file = NULL;\r
201     }\r
202 \r
203   *size = finfo.size;\r
204 \r
205   return SVN_NO_ERROR;\r
206 }\r
207 \r
208 \r
209 /* Implements svn_diff_fns_t::datasource_open */\r
210 static svn_error_t *\r
211 datasource_open(void *baton, svn_diff_datasource_e datasource)\r
212 {\r
213   svn_diff__file_baton_t *file_baton = baton;\r
214   int idx;\r
215   apr_finfo_t finfo;\r
216   apr_size_t length;\r
217   char *curp;\r
218   char *endp;\r
219 \r
220   idx = datasource_to_index(datasource);\r
221 \r
222   SVN_ERR(svn_io_file_open(&file_baton->file[idx], file_baton->path[idx],\r
223                            APR_READ, APR_OS_DEFAULT, file_baton->pool));\r
224 \r
225   SVN_ERR(svn_io_file_info_get(&finfo, APR_FINFO_SIZE,\r
226                                file_baton->file[idx], file_baton->pool));\r
227 \r
228   file_baton->size[idx] = finfo.size;\r
229   length = finfo.size > CHUNK_SIZE ? CHUNK_SIZE : finfo.size;\r
230 \r
231   if (length == 0)\r
232     return SVN_NO_ERROR;\r
233 \r
234   endp = curp = apr_palloc(file_baton->pool, length);\r
235   endp += length;\r
236 \r
237   file_baton->buffer[idx] = file_baton->curp[idx] = curp;\r
238   file_baton->endp[idx] = endp;\r
239 \r
240   return read_chunk(file_baton->file[idx], file_baton->path[idx],\r
241                     curp, length, 0, file_baton->pool);\r
242 }\r
243 \r
244 \r
245 /* Implements svn_diff_fns_t::datasource_close */\r
246 static svn_error_t *\r
247 datasource_close(void *baton, svn_diff_datasource_e datasource)\r
248 {\r
249   /* Do nothing.  The compare_token function needs previous datasources\r
250    * to stay available until all datasources are processed.\r
251    */\r
252 \r
253   return SVN_NO_ERROR;\r
254 }\r
255 \r
256 /* Implements svn_diff_fns_t::datasource_get_next_token */\r
257 static svn_error_t *\r
258 datasource_get_next_token(apr_uint32_t *hash, void **token, void *baton,\r
259                           svn_diff_datasource_e datasource)\r
260 {\r
261   svn_diff__file_baton_t *file_baton = baton;\r
262   svn_diff__file_token_t *file_token;\r
263   int idx;\r
264   char *endp;\r
265   char *curp;\r
266   char *eol;\r
267   int last_chunk;\r
268   apr_off_t length;\r
269   apr_uint32_t h = 0;\r
270   /* Did the last chunk end in a CR character? */\r
271   svn_boolean_t had_cr = FALSE;\r
272 \r
273   *token = NULL;\r
274 \r
275   idx = datasource_to_index(datasource);\r
276 \r
277   curp = file_baton->curp[idx];\r
278   endp = file_baton->endp[idx];\r
279 \r
280   last_chunk = offset_to_chunk(file_baton->size[idx]);\r
281 \r
282   if (curp == endp\r
283       && last_chunk == file_baton->chunk[idx])\r
284     {\r
285       return SVN_NO_ERROR;\r
286     }\r
287 \r
288   /* Get a new token */\r
289   file_token = file_baton->tokens;\r
290   if (file_token)\r
291     {\r
292       file_baton->tokens = file_token->next;\r
293     }\r
294   else\r
295     {\r
296       file_token = apr_palloc(file_baton->pool, sizeof(*file_token));\r
297     }\r
298 \r
299   file_token->datasource = datasource;\r
300   file_token->offset = chunk_to_offset(file_baton->chunk[idx])\r
301                        + (curp - file_baton->buffer[idx]);\r
302   file_token->raw_length = 0;\r
303   file_token->length = 0;\r
304 \r
305   while (1)\r
306     {\r
307       eol = find_eol_start(curp, endp - curp);\r
308       if (eol)\r
309         {\r
310           had_cr = (*eol == '\r');\r
311           eol++;\r
312           /* If we have the whole eol sequence in the chunk... */\r
313           if (!had_cr || eol != endp)\r
314             {\r
315               if (had_cr && *eol == '\n')\r
316                 ++eol;\r
317               break;\r
318             }\r
319         }\r
320 \r
321       if (file_baton->chunk[idx] == last_chunk)\r
322         {\r
323           eol = endp;\r
324           break;\r
325         }\r
326 \r
327       length = endp - curp;\r
328       file_token->raw_length += length;\r
329       svn_diff__normalize_buffer(&curp, &length,\r
330                                  &file_baton->normalize_state[idx],\r
331                                  curp, file_baton->options);\r
332       file_token->length += length;\r
333       h = svn_diff__adler32(h, curp, length);\r
334 \r
335       curp = endp = file_baton->buffer[idx];\r
336       file_baton->chunk[idx]++;\r
337       length = file_baton->chunk[idx] == last_chunk ?\r
338         offset_in_chunk(file_baton->size[idx]) : CHUNK_SIZE;\r
339       endp += length;\r
340       file_baton->endp[idx] = endp;\r
341 \r
342       SVN_ERR(read_chunk(file_baton->file[idx], file_baton->path[idx],\r
343                          curp, length,\r
344                          chunk_to_offset(file_baton->chunk[idx]),\r
345                          file_baton->pool));\r
346 \r
347       /* If the last chunk ended in a CR, we're done. */\r
348       if (had_cr)\r
349         {\r
350           eol = curp;\r
351           if (*curp == '\n')\r
352             ++eol;\r
353           break;\r
354         }\r
355     }\r
356 \r
357   length = eol - curp;\r
358   file_token->raw_length += length;\r
359   file_baton->curp[idx] = eol;\r
360 \r
361   /* If the file length is exactly a multiple of CHUNK_SIZE, we will end up\r
362    * with a spurious empty token.  Avoid returning it.\r
363    * Note that we use the unnormalized length; we don't want a line containing\r
364    * only spaces (and no trailing newline) to appear like a non-existent\r
365    * line. */\r
366   if (file_token->raw_length > 0)\r
367     {\r
368       char *c = curp;\r
369       svn_diff__normalize_buffer(&c, &length,\r
370                                  &file_baton->normalize_state[idx],\r
371                                  curp, file_baton->options);\r
372 \r
373       file_token->norm_offset = file_token->offset + (c - curp);\r
374       file_token->length += length;\r
375 \r
376       *hash = svn_diff__adler32(h, c, length);\r
377       *token = file_token;\r
378     }\r
379 \r
380   return SVN_NO_ERROR;\r
381 }\r
382 \r
383 #define COMPARE_CHUNK_SIZE 4096\r
384 \r
385 /* Implements svn_diff_fns_t::token_compare */\r
386 static svn_error_t *\r
387 token_compare(void *baton, void *token1, void *token2, int *compare)\r
388 {\r
389   svn_diff__file_baton_t *file_baton = baton;\r
390   svn_diff__file_token_t *file_token[2];\r
391   char buffer[2][COMPARE_CHUNK_SIZE];\r
392   char *bufp[2];\r
393   apr_off_t offset[2];\r
394   int idx[2];\r
395   apr_off_t length[2];\r
396   apr_off_t total_length;\r
397   /* How much is left to read of each token from the file. */\r
398   apr_off_t raw_length[2];\r
399   int i;\r
400   int chunk[2];\r
401   svn_diff__normalize_state_t state[2];\r
402 \r
403   file_token[0] = token1;\r
404   file_token[1] = token2;\r
405   if (file_token[0]->length < file_token[1]->length)\r
406     {\r
407       *compare = -1;\r
408       return SVN_NO_ERROR;\r
409     }\r
410 \r
411   if (file_token[0]->length > file_token[1]->length)\r
412     {\r
413       *compare = 1;\r
414       return SVN_NO_ERROR;\r
415     }\r
416 \r
417   total_length = file_token[0]->length;\r
418   if (total_length == 0)\r
419     {\r
420       *compare = 0;\r
421       return SVN_NO_ERROR;\r
422     }\r
423 \r
424   for (i = 0; i < 2; ++i)\r
425     {\r
426       idx[i] = datasource_to_index(file_token[i]->datasource);\r
427       offset[i] = file_token[i]->norm_offset;\r
428       chunk[i] = file_baton->chunk[idx[i]];\r
429       state[i] = svn_diff__normalize_state_normal;\r
430 \r
431       if (offset_to_chunk(offset[i]) == chunk[i])\r
432         {\r
433           /* If the start of the token is in memory, the entire token is\r
434            * in memory.\r
435            */\r
436           bufp[i] = file_baton->buffer[idx[i]];\r
437           bufp[i] += offset_in_chunk(offset[i]);\r
438 \r
439           length[i] = total_length;\r
440           raw_length[i] = 0;\r
441         }\r
442       else\r
443         {\r
444           length[i] = 0;\r
445           raw_length[i] = file_token[i]->raw_length;\r
446         }\r
447     }\r
448 \r
449   do\r
450     {\r
451       apr_off_t len;\r
452       for (i = 0; i < 2; i++)\r
453         {\r
454           if (length[i] == 0)\r
455             {\r
456               /* Error if raw_length is 0, that's an unexpected change\r
457                * of the file that can happen when ingoring whitespace\r
458                * and that can lead to an infinite loop. */\r
459               if (raw_length[i] == 0)\r
460                 return svn_error_createf(SVN_ERR_DIFF_DATASOURCE_MODIFIED,\r
461                                          NULL,\r
462                                          _("The file '%s' changed unexpectedly"\r
463                                            " during diff"),\r
464                                          file_baton->path[idx[i]]);\r
465 \r
466               /* Read a chunk from disk into a buffer */\r
467               bufp[i] = buffer[i];\r
468               length[i] = raw_length[i] > COMPARE_CHUNK_SIZE ?\r
469                 COMPARE_CHUNK_SIZE : raw_length[i];\r
470 \r
471               SVN_ERR(read_chunk(file_baton->file[idx[i]],\r
472                                  file_baton->path[idx[i]],\r
473                                  bufp[i], length[i], offset[i],\r
474                                  file_baton->pool));\r
475               offset[i] += length[i];\r
476               raw_length[i] -= length[i];\r
477               /* bufp[i] gets reset to buffer[i] before reading each chunk,\r
478                  so, overwriting it isn't a problem */\r
479               svn_diff__normalize_buffer(&bufp[i], &length[i], &state[i],\r
480                                          bufp[i], file_baton->options);\r
481             }\r
482         }\r
483 \r
484       len = length[0] > length[1] ? length[1] : length[0];\r
485 \r
486       /* Compare two chunks (that could be entire tokens if they both reside\r
487        * in memory).\r
488        */\r
489       *compare = memcmp(bufp[0], bufp[1], len);\r
490       if (*compare != 0)\r
491         return SVN_NO_ERROR;\r
492 \r
493       total_length -= len;\r
494       length[0] -= len;\r
495       length[1] -= len;\r
496       bufp[0] += len;\r
497       bufp[1] += len;\r
498     }\r
499   while(total_length > 0);\r
500 \r
501   *compare = 0;\r
502   return SVN_NO_ERROR;\r
503 }\r
504 \r
505 \r
506 /* Implements svn_diff_fns_t::token_discard */\r
507 static void\r
508 token_discard(void *baton, void *token)\r
509 {\r
510   svn_diff__file_baton_t *file_baton = baton;\r
511   svn_diff__file_token_t *file_token = token;\r
512 \r
513   file_token->next = file_baton->tokens;\r
514   file_baton->tokens = file_token;\r
515 }\r
516 \r
517 \r
518 /* Implements svn_diff_fns_t::token_discard_all */\r
519 static void\r
520 token_discard_all(void *baton)\r
521 {\r
522   svn_diff__file_baton_t *file_baton = baton;\r
523 \r
524   /* Discard all memory in use by the tokens, and close all open files. */\r
525   svn_pool_clear(file_baton->pool);\r
526 }\r
527 \r
528 \r
529 static const svn_diff_fns_t svn_diff__file_vtable =\r
530 {\r
531   datasource_open,\r
532   datasource_close,\r
533   datasource_get_next_token,\r
534   token_compare,\r
535   token_discard,\r
536   token_discard_all\r
537 };\r
538 \r
539 /* Id for the --ignore-eol-style option, which doesn't have a short name. */\r
540 #define SVN_DIFF__OPT_IGNORE_EOL_STYLE 256\r
541 \r
542 /* Options supported by svn_diff_file_options_parse(). */\r
543 static const apr_getopt_option_t diff_options[] =\r
544 {\r
545   { "ignore-space-change", 'b', 0, NULL },\r
546   { "ignore-all-space", 'w', 0, NULL },\r
547   { "ignore-eol-style", SVN_DIFF__OPT_IGNORE_EOL_STYLE, 0, NULL },\r
548   { "show-c-function", 'p', 0, NULL },\r
549   /* ### For compatibility; we don't support the argument to -u, because\r
550    * ### we don't have optional argument support. */\r
551   { "unified", 'u', 0, NULL },\r
552   { NULL, 0, 0, NULL }\r
553 };\r
554 \r
555 svn_diff_file_options_t *\r
556 svn_diff_file_options_create(apr_pool_t *pool)\r
557 {\r
558   return apr_pcalloc(pool, sizeof(svn_diff_file_options_t));\r
559 }\r
560 \r
561 #if 0\r
562 svn_error_t *\r
563 svn_diff_file_options_parse(svn_diff_file_options_t *options,\r
564                             const apr_array_header_t *args,\r
565                             apr_pool_t *pool)\r
566 {\r
567   apr_getopt_t *os;\r
568   /* Make room for each option (starting at index 1) plus trailing NULL. */\r
569   const char **argv = apr_palloc(pool, sizeof(char*) * (args->nelts + 2));\r
570 \r
571   argv[0] = "";\r
572   memcpy((void *) (argv + 1), args->elts, sizeof(char*) * args->nelts);\r
573   argv[args->nelts + 1] = NULL;\r
574 \r
575   apr_getopt_init(&os, pool, args->nelts + 1, argv);\r
576   /* No printing of error messages, please! */\r
577   os->errfn = NULL;\r
578   while (1)\r
579     {\r
580       const char *opt_arg;\r
581       int opt_id;\r
582       apr_status_t err = apr_getopt_long(os, diff_options, &opt_id, &opt_arg);\r
583 \r
584       if (APR_STATUS_IS_EOF(err))\r
585         break;\r
586       if (err)\r
587         return svn_error_wrap_apr(err, _("Error parsing diff options"));\r
588 \r
589       switch (opt_id)\r
590         {\r
591         case 'b':\r
592           /* -w takes precedence over -b. */\r
593           if (! options->ignore_space)\r
594             options->ignore_space = svn_diff_file_ignore_space_change;\r
595           break;\r
596         case 'w':\r
597           options->ignore_space = svn_diff_file_ignore_space_all;\r
598           break;\r
599         case SVN_DIFF__OPT_IGNORE_EOL_STYLE:\r
600           options->ignore_eol_style = TRUE;\r
601           break;\r
602         case 'p':\r
603           options->show_c_function = TRUE;\r
604           break;\r
605         default:\r
606           break;\r
607         }\r
608     }\r
609 \r
610   /* Check for spurious arguments. */\r
611   if (os->ind < os->argc)\r
612     return svn_error_createf(SVN_ERR_INVALID_DIFF_OPTION, NULL,\r
613                              _("Invalid argument '%s' in diff options"),\r
614                              os->argv[os->ind]);\r
615 \r
616   return SVN_NO_ERROR;\r
617 }\r
618 #endif\r
619 svn_error_t *\r
620 svn_diff_file_diff_2(svn_diff_t **diff,\r
621                      const char *original,\r
622                      const char *modified,\r
623                      const svn_diff_file_options_t *options,\r
624                      apr_pool_t *pool)\r
625 {\r
626   svn_diff__file_baton_t baton;\r
627 \r
628   memset(&baton, 0, sizeof(baton));\r
629   baton.options = options;\r
630   baton.path[0] = original;\r
631   baton.path[1] = modified;\r
632   baton.pool = svn_pool_create(pool);\r
633 \r
634   SVN_ERR(svn_diff_diff(diff, &baton, &svn_diff__file_vtable, pool));\r
635 \r
636   svn_pool_destroy(baton.pool);\r
637   return SVN_NO_ERROR;\r
638 }\r
639 \r
640 svn_error_t *\r
641 svn_diff_file_diff(svn_diff_t **diff,\r
642                    const char *original,\r
643                    const char *modified,\r
644                    apr_pool_t *pool)\r
645 {\r
646   return svn_diff_file_diff_2(diff, original, modified,\r
647                               svn_diff_file_options_create(pool), pool);\r
648 }\r
649 \r
650 svn_error_t *\r
651 svn_diff_file_diff3_2(svn_diff_t **diff,\r
652                       const char *original,\r
653                       const char *modified,\r
654                       const char *latest,\r
655                       const svn_diff_file_options_t *options,\r
656                       apr_pool_t *pool)\r
657 {\r
658   svn_diff__file_baton_t baton;\r
659 \r
660   memset(&baton, 0, sizeof(baton));\r
661   baton.options = options;\r
662   baton.path[0] = original;\r
663   baton.path[1] = modified;\r
664   baton.path[2] = latest;\r
665   baton.pool = svn_pool_create(pool);\r
666 \r
667   SVN_ERR(svn_diff_diff3(diff, &baton, &svn_diff__file_vtable, pool));\r
668 \r
669   svn_pool_destroy(baton.pool);\r
670   return SVN_NO_ERROR;\r
671 }\r
672 \r
673 svn_error_t *\r
674 svn_diff_file_diff3(svn_diff_t **diff,\r
675                     const char *original,\r
676                     const char *modified,\r
677                     const char *latest,\r
678                     apr_pool_t *pool)\r
679 {\r
680   return svn_diff_file_diff3_2(diff, original, modified, latest,\r
681                                svn_diff_file_options_create(pool), pool);\r
682 }\r
683 \r
684 svn_error_t *\r
685 svn_diff_file_diff4_2(svn_diff_t **diff,\r
686                       const char *original,\r
687                       const char *modified,\r
688                       const char *latest,\r
689                       const char *ancestor,\r
690                       const svn_diff_file_options_t *options,\r
691                       apr_pool_t *pool)\r
692 {\r
693   svn_diff__file_baton_t baton;\r
694 \r
695   memset(&baton, 0, sizeof(baton));\r
696   baton.options = options;\r
697   baton.path[0] = original;\r
698   baton.path[1] = modified;\r
699   baton.path[2] = latest;\r
700   baton.path[3] = ancestor;\r
701   baton.pool = svn_pool_create(pool);\r
702 \r
703   SVN_ERR(svn_diff_diff4(diff, &baton, &svn_diff__file_vtable, pool));\r
704 \r
705   svn_pool_destroy(baton.pool);\r
706   return SVN_NO_ERROR;\r
707 }\r
708 \r
709 svn_error_t *\r
710 svn_diff_file_diff4(svn_diff_t **diff,\r
711                     const char *original,\r
712                     const char *modified,\r
713                     const char *latest,\r
714                     const char *ancestor,\r
715                     apr_pool_t *pool)\r
716 {\r
717   return svn_diff_file_diff4_2(diff, original, modified, latest, ancestor,\r
718                                svn_diff_file_options_create(pool), pool);\r
719 }\r
720 \f\r
721 /** Display unified context diffs **/\r
722 \r
723 /* Maximum length of the extra context to show when show_c_function is set.\r
724  * GNU diff uses 40, let's be brave and use 50 instead. */\r
725 #define SVN_DIFF__EXTRA_CONTEXT_LENGTH 50\r
726 typedef struct svn_diff__file_output_baton_t\r
727 {\r
728   svn_stream_t *output_stream;\r
729   const char *header_encoding;\r
730 \r
731   /* Cached markers, in header_encoding. */\r
732   const char *context_str;\r
733   const char *delete_str;\r
734   const char *insert_str;\r
735 \r
736   const char *path[2];\r
737   apr_file_t *file[2];\r
738 \r
739   apr_off_t   current_line[2];\r
740 \r
741   char        buffer[2][4096];\r
742   apr_size_t  length[2];\r
743   char       *curp[2];\r
744 \r
745   apr_off_t   hunk_start[2];\r
746   apr_off_t   hunk_length[2];\r
747   svn_stringbuf_t *hunk;\r
748 \r
749   /* Should we emit C functions in the unified diff header */\r
750   svn_boolean_t show_c_function;\r
751   /* Extra strings to skip over if we match. */\r
752   apr_array_header_t *extra_skip_match;\r
753   /* "Context" to append to the @@ line when the show_c_function option\r
754    * is set. */\r
755   svn_stringbuf_t *extra_context;\r
756   /* Extra context for the current hunk. */\r
757   char hunk_extra_context[SVN_DIFF__EXTRA_CONTEXT_LENGTH + 1];\r
758 \r
759   apr_pool_t *pool;\r
760 } svn_diff__file_output_baton_t;\r
761 \r
762 typedef enum svn_diff__file_output_unified_type_e\r
763 {\r
764   svn_diff__file_output_unified_skip,\r
765   svn_diff__file_output_unified_context,\r
766   svn_diff__file_output_unified_delete,\r
767   svn_diff__file_output_unified_insert\r
768 } svn_diff__file_output_unified_type_e;\r
769 \r
770 \r
771 static svn_error_t *\r
772 output_unified_line(svn_diff__file_output_baton_t *baton,\r
773                     svn_diff__file_output_unified_type_e type, int idx)\r
774 {\r
775   char *curp;\r
776   char *eol;\r
777   apr_size_t length;\r
778   svn_error_t *err;\r
779   svn_boolean_t bytes_processed = FALSE;\r
780   svn_boolean_t had_cr = FALSE;\r
781   /* Are we collecting extra context? */\r
782   svn_boolean_t collect_extra = FALSE;\r
783 \r
784   length = baton->length[idx];\r
785   curp = baton->curp[idx];\r
786 \r
787   /* Lazily update the current line even if we're at EOF.\r
788    * This way we fake output of context at EOF\r
789    */\r
790   baton->current_line[idx]++;\r
791 \r
792   if (length == 0 && apr_file_eof(baton->file[idx]))\r
793     {\r
794       return SVN_NO_ERROR;\r
795     }\r
796 \r
797   do\r
798     {\r
799       if (length > 0)\r
800         {\r
801           if (!bytes_processed)\r
802             {\r
803               switch (type)\r
804                 {\r
805                 case svn_diff__file_output_unified_context:\r
806                   svn_stringbuf_appendcstr(baton->hunk, baton->context_str);\r
807                   baton->hunk_length[0]++;\r
808                   baton->hunk_length[1]++;\r
809                   break;\r
810                 case svn_diff__file_output_unified_delete:\r
811                   svn_stringbuf_appendcstr(baton->hunk, baton->delete_str);\r
812                   baton->hunk_length[0]++;\r
813                   break;\r
814                 case svn_diff__file_output_unified_insert:\r
815                   svn_stringbuf_appendcstr(baton->hunk, baton->insert_str);\r
816                   baton->hunk_length[1]++;\r
817                   break;\r
818                 default:\r
819                   break;\r
820                 }\r
821 \r
822               if (baton->show_c_function\r
823                   && (type == svn_diff__file_output_unified_skip\r
824                       || type == svn_diff__file_output_unified_context)\r
825                   && (svn_ctype_isalpha(*curp) || *curp == '$' || *curp == '_')\r
826                   && !svn_cstring_match_glob_list(curp,\r
827                                                   baton->extra_skip_match))\r
828                 {\r
829                   svn_stringbuf_setempty(baton->extra_context);\r
830                   collect_extra = TRUE;\r
831                 }\r
832             }\r
833 \r
834           eol = find_eol_start(curp, length);\r
835 \r
836           if (eol != NULL)\r
837             {\r
838               apr_size_t len;\r
839 \r
840               had_cr = (*eol == '\r');\r
841               eol++;\r
842               len = (apr_size_t)(eol - curp);\r
843 \r
844               if (! had_cr || len < length)\r
845                 {\r
846                   if (had_cr && *eol == '\n')\r
847                     {\r
848                       ++eol;\r
849                       ++len;\r
850                     }\r
851 \r
852                   length -= len;\r
853 \r
854                   if (type != svn_diff__file_output_unified_skip)\r
855                     {\r
856                       svn_stringbuf_appendbytes(baton->hunk, curp, len);\r
857                     }\r
858                   if (collect_extra)\r
859                     {\r
860                       svn_stringbuf_appendbytes(baton->extra_context,\r
861                                                 curp, len);\r
862                     }\r
863 \r
864                   baton->curp[idx] = eol;\r
865                   baton->length[idx] = length;\r
866 \r
867                   err = SVN_NO_ERROR;\r
868 \r
869                   break;\r
870                 }\r
871             }\r
872 \r
873           if (type != svn_diff__file_output_unified_skip)\r
874             {\r
875               svn_stringbuf_appendbytes(baton->hunk, curp, length);\r
876             }\r
877 \r
878           if (collect_extra)\r
879             {\r
880               svn_stringbuf_appendbytes(baton->extra_context, curp, length);\r
881             }\r
882 \r
883           bytes_processed = TRUE;\r
884         }\r
885 \r
886       curp = baton->buffer[idx];\r
887       length = sizeof(baton->buffer[idx]);\r
888 \r
889       err = svn_io_file_read(baton->file[idx], curp, &length, baton->pool);\r
890 \r
891       /* If the last chunk ended with a CR, we look for an LF at the start\r
892          of this chunk. */\r
893       if (had_cr)\r
894         {\r
895           if (! err && length > 0 && *curp == '\n')\r
896             {\r
897               if (type != svn_diff__file_output_unified_skip)\r
898                 {\r
899                   svn_stringbuf_appendbytes(baton->hunk, curp, 1);\r
900                 }\r
901               /* We don't append the LF to extra_context, since it would\r
902                * just be stripped anyway. */\r
903               ++curp;\r
904               --length;\r
905             }\r
906 \r
907           baton->curp[idx] = curp;\r
908           baton->length[idx] = length;\r
909 \r
910           break;\r
911         }\r
912     }\r
913   while (! err);\r
914 \r
915   if (err && ! APR_STATUS_IS_EOF(err->apr_err))\r
916     return err;\r
917 \r
918   if (err && APR_STATUS_IS_EOF(err->apr_err))\r
919     {\r
920       svn_error_clear(err);\r
921       /* Special case if we reach the end of file AND the last line is in the\r
922          changed range AND the file doesn't end with a newline */\r
923       if (bytes_processed && (type != svn_diff__file_output_unified_skip)\r
924           && ! had_cr)\r
925         {\r
926           const char *out_str;\r
927           SVN_ERR(svn_utf_cstring_from_utf8_ex2\r
928                   (&out_str,\r
929                    /* The string below is intentionally not marked for\r
930                       translation: it's vital to correct operation of\r
931                       the diff(1)/patch(1) program pair. */\r
932                    APR_EOL_STR "\\ No newline at end of file" APR_EOL_STR,\r
933                    baton->header_encoding, baton->pool));\r
934           svn_stringbuf_appendcstr(baton->hunk, out_str);\r
935         }\r
936 \r
937       baton->length[idx] = 0;\r
938     }\r
939 \r
940   return SVN_NO_ERROR;\r
941 }\r
942 \r
943 static svn_error_t *\r
944 output_unified_flush_hunk(svn_diff__file_output_baton_t *baton)\r
945 {\r
946   apr_off_t target_line;\r
947   apr_size_t hunk_len;\r
948   int i;\r
949 \r
950   if (svn_stringbuf_isempty(baton->hunk))\r
951     {\r
952       /* Nothing to flush */\r
953       return SVN_NO_ERROR;\r
954     }\r
955 \r
956   target_line = baton->hunk_start[0] + baton->hunk_length[0]\r
957                 + SVN_DIFF__UNIFIED_CONTEXT_SIZE;\r
958 \r
959   /* Add trailing context to the hunk */\r
960   while (baton->current_line[0] < target_line)\r
961     {\r
962       SVN_ERR(output_unified_line\r
963               (baton, svn_diff__file_output_unified_context, 0));\r
964     }\r
965 \r
966   /* If the file is non-empty, convert the line indexes from\r
967      zero based to one based */\r
968   for (i = 0; i < 2; i++)\r
969     {\r
970       if (baton->hunk_length[i] > 0)\r
971         baton->hunk_start[i]++;\r
972     }\r
973 \r
974   /* Output the hunk header.  If the hunk length is 1, the file is a one line\r
975      file.  In this case, surpress the number of lines in the hunk (it is\r
976      1 implicitly)\r
977    */\r
978   SVN_ERR(svn_stream_printf_from_utf8(baton->output_stream,\r
979                                       baton->header_encoding,\r
980                                       baton->pool,\r
981                                       "@@ -%" APR_OFF_T_FMT,\r
982                                       baton->hunk_start[0]));\r
983   if (baton->hunk_length[0] != 1)\r
984     {\r
985       SVN_ERR(svn_stream_printf_from_utf8(baton->output_stream,\r
986                                           baton->header_encoding,\r
987                                           baton->pool, ",%" APR_OFF_T_FMT,\r
988                                           baton->hunk_length[0]));\r
989     }\r
990 \r
991   SVN_ERR(svn_stream_printf_from_utf8(baton->output_stream,\r
992                                       baton->header_encoding,\r
993                                       baton->pool, " +%" APR_OFF_T_FMT,\r
994                                       baton->hunk_start[1]));\r
995   if (baton->hunk_length[1] != 1)\r
996     {\r
997       SVN_ERR(svn_stream_printf_from_utf8(baton->output_stream,\r
998                                           baton->header_encoding,\r
999                                           baton->pool, ",%" APR_OFF_T_FMT,\r
1000                                           baton->hunk_length[1]));\r
1001     }\r
1002 \r
1003   SVN_ERR(svn_stream_printf_from_utf8(baton->output_stream,\r
1004                                       baton->header_encoding,\r
1005                                       baton->pool, " @@%s%s" APR_EOL_STR,\r
1006                                       baton->hunk_extra_context[0]\r
1007                                       ? " " : "",\r
1008                                       baton->hunk_extra_context));\r
1009 \r
1010   /* Output the hunk content */\r
1011   hunk_len = baton->hunk->len;\r
1012   SVN_ERR(svn_stream_write(baton->output_stream, baton->hunk->data,\r
1013                            &hunk_len));\r
1014 \r
1015   /* Prepare for the next hunk */\r
1016   baton->hunk_length[0] = 0;\r
1017   baton->hunk_length[1] = 0;\r
1018   svn_stringbuf_setempty(baton->hunk);\r
1019 \r
1020   return SVN_NO_ERROR;\r
1021 }\r
1022 \r
1023 static svn_error_t *\r
1024 output_unified_diff_modified(void *baton,\r
1025   apr_off_t original_start, apr_off_t original_length,\r
1026   apr_off_t modified_start, apr_off_t modified_length,\r
1027   apr_off_t latest_start, apr_off_t latest_length)\r
1028 {\r
1029   svn_diff__file_output_baton_t *output_baton = baton;\r
1030   apr_off_t target_line[2];\r
1031   int i;\r
1032 \r
1033   target_line[0] = original_start >= SVN_DIFF__UNIFIED_CONTEXT_SIZE\r
1034                    ? original_start - SVN_DIFF__UNIFIED_CONTEXT_SIZE : 0;\r
1035   target_line[1] = modified_start;\r
1036 \r
1037   /* If the changed ranges are far enough apart (no overlapping or connecting\r
1038      context), flush the current hunk, initialize the next hunk and skip the\r
1039      lines not in context.  Also do this when this is the first hunk.\r
1040    */\r
1041   if (output_baton->current_line[0] < target_line[0]\r
1042       && (output_baton->hunk_start[0] + output_baton->hunk_length[0]\r
1043           + SVN_DIFF__UNIFIED_CONTEXT_SIZE < target_line[0]\r
1044           || output_baton->hunk_length[0] == 0))\r
1045     {\r
1046       SVN_ERR(output_unified_flush_hunk(output_baton));\r
1047 \r
1048       output_baton->hunk_start[0] = target_line[0];\r
1049       output_baton->hunk_start[1] = target_line[1] + target_line[0]\r
1050                                     - original_start;\r
1051 \r
1052       /* Skip lines until we are at the beginning of the context we want to\r
1053          display */\r
1054       while (output_baton->current_line[0] < target_line[0])\r
1055         {\r
1056           SVN_ERR(output_unified_line(output_baton,\r
1057                                       svn_diff__file_output_unified_skip, 0));\r
1058         }\r
1059 \r
1060       if (output_baton->show_c_function)\r
1061         {\r
1062           int p;\r
1063 \r
1064           /* Save the extra context for later use.\r
1065            * Note that the last byte of the hunk_extra_context array is never\r
1066            * touched after it is zero-initialized, so the array is always\r
1067            * 0-terminated. */\r
1068           strncpy(output_baton->hunk_extra_context,\r
1069                   output_baton->extra_context->data,\r
1070                   SVN_DIFF__EXTRA_CONTEXT_LENGTH);\r
1071           /* Trim whitespace at the end, most notably to get rid of any\r
1072            * newline characters. */\r
1073           p = strlen(output_baton->hunk_extra_context);\r
1074           while (p > 0\r
1075                  && svn_ctype_isspace(output_baton->hunk_extra_context[p - 1]))\r
1076             {\r
1077               output_baton->hunk_extra_context[--p] = '\0';\r
1078             }\r
1079         }\r
1080     }\r
1081 \r
1082   /* Skip lines until we are at the start of the changed range */\r
1083   while (output_baton->current_line[1] < target_line[1])\r
1084     {\r
1085       SVN_ERR(output_unified_line(output_baton,\r
1086                                   svn_diff__file_output_unified_skip, 1));\r
1087     }\r
1088 \r
1089   /* Output the context preceding the changed range */\r
1090   while (output_baton->current_line[0] < original_start)\r
1091     {\r
1092       SVN_ERR(output_unified_line(output_baton,\r
1093                                   svn_diff__file_output_unified_context, 0));\r
1094     }\r
1095 \r
1096   target_line[0] = original_start + original_length;\r
1097   target_line[1] = modified_start + modified_length;\r
1098 \r
1099   /* Output the changed range */\r
1100   for (i = 0; i < 2; i++)\r
1101     {\r
1102       while (output_baton->current_line[i] < target_line[i])\r
1103         {\r
1104           SVN_ERR(output_unified_line\r
1105                           (output_baton,\r
1106                            i == 0 ? svn_diff__file_output_unified_delete\r
1107                                   : svn_diff__file_output_unified_insert, i));\r
1108         }\r
1109     }\r
1110 \r
1111   return SVN_NO_ERROR;\r
1112 }\r
1113 \r
1114 #if 0\r
1115 /* Set *HEADER to a new string consisting of PATH, a tab, and PATH's mtime. */\r
1116 static svn_error_t *\r
1117 output_unified_default_hdr(const char **header, const char *path,\r
1118                            apr_pool_t *pool)\r
1119 {\r
1120   apr_finfo_t file_info;\r
1121   apr_time_exp_t exploded_time;\r
1122   char time_buffer[64];\r
1123   apr_size_t time_len;\r
1124 \r
1125   SVN_ERR(svn_io_stat(&file_info, path, APR_FINFO_MTIME, pool));\r
1126   apr_time_exp_lt(&exploded_time, file_info.mtime);\r
1127 \r
1128   apr_strftime(time_buffer, &time_len, sizeof(time_buffer) - 1,\r
1129                "%a %b %e %H:%M:%S %Y", &exploded_time);\r
1130 \r
1131   *header = apr_psprintf(pool, "%s\t%s", path, time_buffer);\r
1132 \r
1133   return SVN_NO_ERROR;\r
1134 }\r
1135 #endif\r
1136 \r
1137 static const svn_diff_output_fns_t svn_diff__file_output_unified_vtable =\r
1138 {\r
1139   NULL, /* output_common */\r
1140   output_unified_diff_modified,\r
1141   NULL, /* output_diff_latest */\r
1142   NULL, /* output_diff_common */\r
1143   NULL  /* output_conflict */\r
1144 };\r
1145 \r
1146 svn_error_t *\r
1147 svn_diff_file_output_unified3(svn_stream_t *output_stream,\r
1148                               svn_diff_t *diff,\r
1149                               const char *original_path,\r
1150                               const char *modified_path,\r
1151                               const char *original_header,\r
1152                               const char *modified_header,\r
1153                               const char *header_encoding,\r
1154                               const char *relative_to_dir,\r
1155                               svn_boolean_t show_c_function,\r
1156                               apr_pool_t *pool)\r
1157 {\r
1158   svn_diff__file_output_baton_t baton;\r
1159   int i;\r
1160 \r
1161   if (svn_diff_contains_diffs(diff))\r
1162     {\r
1163       const char **c;\r
1164 \r
1165       memset(&baton, 0, sizeof(baton));\r
1166       baton.output_stream = output_stream;\r
1167       baton.pool = pool;\r
1168       baton.header_encoding = header_encoding;\r
1169       baton.path[0] = original_path;\r
1170       baton.path[1] = modified_path;\r
1171       baton.hunk = svn_stringbuf_create("", pool);\r
1172       baton.show_c_function = show_c_function;\r
1173       baton.extra_context = svn_stringbuf_create("", pool);\r
1174       baton.extra_skip_match = apr_array_make(pool, 3, sizeof(char **));\r
1175 \r
1176       c = apr_array_push(baton.extra_skip_match);\r
1177       *c = "public:*";\r
1178       c = apr_array_push(baton.extra_skip_match);\r
1179       *c = "private:*";\r
1180       c = apr_array_push(baton.extra_skip_match);\r
1181       *c = "protected:*";\r
1182 \r
1183       SVN_ERR(svn_utf_cstring_from_utf8_ex2(&baton.context_str, " ",\r
1184                                             header_encoding, pool));\r
1185       SVN_ERR(svn_utf_cstring_from_utf8_ex2(&baton.delete_str, "-",\r
1186                                             header_encoding, pool));\r
1187       SVN_ERR(svn_utf_cstring_from_utf8_ex2(&baton.insert_str, "+",\r
1188                                             header_encoding, pool));\r
1189 \r
1190   if (relative_to_dir)\r
1191     {\r
1192       /* Possibly adjust the "original" and "modified" paths shown in\r
1193          the output (see issue #2723). */\r
1194       const char *child_path;\r
1195 \r
1196       if (! original_header)\r
1197         {\r
1198           child_path = svn_path_is_child(relative_to_dir,\r
1199                                          original_path, pool);\r
1200           if (child_path)\r
1201             original_path = child_path;\r
1202           else\r
1203             return svn_error_createf(SVN_ERR_BAD_RELATIVE_PATH, NULL,\r
1204                                      _("Path '%s' must be an immediate child of "\r
1205                                        "the directory '%s'"),\r
1206                                      original_path, relative_to_dir);\r
1207         }\r
1208 \r
1209       if (! modified_header)\r
1210         {\r
1211           child_path = svn_path_is_child(relative_to_dir, modified_path, pool);\r
1212           if (child_path)\r
1213             modified_path = child_path;\r
1214           else\r
1215             return svn_error_createf(SVN_ERR_BAD_RELATIVE_PATH, NULL,\r
1216                                      _("Path '%s' must be an immediate child of "\r
1217                                        "the directory '%s'"),\r
1218                                      modified_path, relative_to_dir);\r
1219         }\r
1220     }\r
1221 \r
1222       for (i = 0; i < 2; i++)\r
1223         {\r
1224           SVN_ERR(svn_io_file_open(&baton.file[i], baton.path[i],\r
1225                                    APR_READ, APR_OS_DEFAULT, pool));\r
1226         }\r
1227 \r
1228       if (original_header == NULL)\r
1229         {\r
1230           SVN_ERR(output_unified_default_hdr\r
1231                   (&original_header, original_path, pool));\r
1232         }\r
1233 \r
1234       if (modified_header == NULL)\r
1235         {\r
1236           SVN_ERR(output_unified_default_hdr\r
1237                   (&modified_header, modified_path, pool));\r
1238         }\r
1239 \r
1240       SVN_ERR(svn_stream_printf_from_utf8(output_stream, header_encoding, pool,\r
1241                                           "--- %s" APR_EOL_STR\r
1242                                           "+++ %s" APR_EOL_STR,\r
1243                                           original_header, modified_header));\r
1244 \r
1245       SVN_ERR(svn_diff_output(diff, &baton,\r
1246                               &svn_diff__file_output_unified_vtable));\r
1247       SVN_ERR(output_unified_flush_hunk(&baton));\r
1248 \r
1249       for (i = 0; i < 2; i++)\r
1250         {\r
1251           SVN_ERR(svn_io_file_close(baton.file[i], pool));\r
1252         }\r
1253     }\r
1254 \r
1255   return SVN_NO_ERROR;\r
1256 }\r
1257 \r
1258 \f\r
1259 /** Display diff3 **/\r
1260 \r
1261 /* A stream to remember *leading* context.  Note that this stream does\r
1262    *not* copy the data that it is remembering; it just saves\r
1263    *pointers! */\r
1264 typedef struct {\r
1265   svn_stream_t *stream;\r
1266   const char *data[SVN_DIFF__UNIFIED_CONTEXT_SIZE];\r
1267   apr_size_t len[SVN_DIFF__UNIFIED_CONTEXT_SIZE];\r
1268   apr_size_t next_slot;\r
1269   apr_size_t total_written;\r
1270 } context_saver_t;\r
1271 \r
1272 \r
1273 static svn_error_t *\r
1274 context_saver_stream_write(void *baton,\r
1275                            const char *data,\r
1276                            apr_size_t *len)\r
1277 {\r
1278   context_saver_t *cs = baton;\r
1279   cs->data[cs->next_slot] = data;\r
1280   cs->len[cs->next_slot] = *len;\r
1281   cs->next_slot = (cs->next_slot + 1) % SVN_DIFF__UNIFIED_CONTEXT_SIZE;\r
1282   cs->total_written++;\r
1283   return SVN_NO_ERROR;\r
1284 }\r
1285 \r
1286 typedef struct svn_diff3__file_output_baton_t\r
1287 {\r
1288   svn_stream_t *output_stream;\r
1289 \r
1290   const char *path[3];\r
1291 \r
1292   apr_off_t   current_line[3];\r
1293 \r
1294   char       *buffer[3];\r
1295   char       *endp[3];\r
1296   char       *curp[3];\r
1297 \r
1298   /* The following four members are in the encoding used for the output. */\r
1299   const char *conflict_modified;\r
1300   const char *conflict_original;\r
1301   const char *conflict_separator;\r
1302   const char *conflict_latest;\r
1303 \r
1304   const char *marker_eol;\r
1305 \r
1306   svn_diff_conflict_display_style_t conflict_style;\r
1307 \r
1308   /* The rest of the fields are for\r
1309      svn_diff_conflict_display_only_conflicts only.  Note that for\r
1310      these batons, OUTPUT_STREAM is either CONTEXT_SAVER->STREAM or\r
1311      (soon after a conflict) a "trailing context stream", never the\r
1312      actual output stream.*/\r
1313   /* The actual output stream. */\r
1314   svn_stream_t *real_output_stream;\r
1315   context_saver_t *context_saver;\r
1316   /* Used to allocate context_saver and trailing context streams, and\r
1317      for some printfs. */\r
1318   apr_pool_t *pool;\r
1319 } svn_diff3__file_output_baton_t;\r
1320 \r
1321 static svn_error_t *\r
1322 flush_context_saver(context_saver_t *cs,\r
1323                     svn_stream_t *output_stream)\r
1324 {\r
1325   int i;\r
1326   for (i = 0; i < SVN_DIFF__UNIFIED_CONTEXT_SIZE; i++)\r
1327     {\r
1328       int slot = (i + cs->next_slot) % SVN_DIFF__UNIFIED_CONTEXT_SIZE;\r
1329       if (cs->data[slot])\r
1330         {\r
1331           apr_size_t len = cs->len[slot];\r
1332           SVN_ERR(svn_stream_write(output_stream, cs->data[slot], &len));\r
1333         }\r
1334     }\r
1335   return SVN_NO_ERROR;\r
1336 }\r
1337 \r
1338 static void\r
1339 make_context_saver(svn_diff3__file_output_baton_t *fob)\r
1340 {\r
1341   context_saver_t *cs;\r
1342 \r
1343   svn_pool_clear(fob->pool);\r
1344   cs = apr_pcalloc(fob->pool, sizeof(*cs));\r
1345   cs->stream = svn_stream_empty(fob->pool);\r
1346   svn_stream_set_baton(cs->stream, cs);\r
1347   svn_stream_set_write(cs->stream, context_saver_stream_write);\r
1348   fob->context_saver = cs;\r
1349   fob->output_stream = cs->stream;\r
1350 }\r
1351 \r
1352 \r
1353 /* A stream which prints SVN_DIFF__UNIFIED_CONTEXT_SIZE lines to\r
1354    BATON->REAL_OUTPUT_STREAM, and then changes BATON->OUTPUT_STREAM to\r
1355    a context_saver; used for *trailing* context. */\r
1356 \r
1357 struct trailing_context_printer {\r
1358   apr_size_t lines_to_print;\r
1359   svn_diff3__file_output_baton_t *fob;\r
1360 };\r
1361 \r
1362 \r
1363 \r
1364 static svn_error_t *\r
1365 trailing_context_printer_write(void *baton,\r
1366                                const char *data,\r
1367                                apr_size_t *len)\r
1368 {\r
1369   struct trailing_context_printer *tcp = baton;\r
1370   SVN_ERR_ASSERT(tcp->lines_to_print > 0);\r
1371   SVN_ERR(svn_stream_write(tcp->fob->real_output_stream, data, len));\r
1372   tcp->lines_to_print--;\r
1373   if (tcp->lines_to_print == 0)\r
1374     make_context_saver(tcp->fob);\r
1375   return SVN_NO_ERROR;\r
1376 }\r
1377 \r
1378 \r
1379 static void\r
1380 make_trailing_context_printer(svn_diff3__file_output_baton_t *btn)\r
1381 {\r
1382   struct trailing_context_printer *tcp;\r
1383   svn_stream_t *s;\r
1384 \r
1385   svn_pool_clear(btn->pool);\r
1386 \r
1387   tcp = apr_pcalloc(btn->pool, sizeof(*tcp));\r
1388   tcp->lines_to_print = SVN_DIFF__UNIFIED_CONTEXT_SIZE;\r
1389   tcp->fob = btn;\r
1390   s = svn_stream_empty(btn->pool);\r
1391   svn_stream_set_baton(s, tcp);\r
1392   svn_stream_set_write(s, trailing_context_printer_write);\r
1393   btn->output_stream = s;\r
1394 }\r
1395 \r
1396 \r
1397 \r
1398 typedef enum svn_diff3__file_output_type_e\r
1399 {\r
1400   svn_diff3__file_output_skip,\r
1401   svn_diff3__file_output_normal\r
1402 } svn_diff3__file_output_type_e;\r
1403 \r
1404 \r
1405 static svn_error_t *\r
1406 output_line(svn_diff3__file_output_baton_t *baton,\r
1407             svn_diff3__file_output_type_e type, int idx)\r
1408 {\r
1409   char *curp;\r
1410   char *endp;\r
1411   char *eol;\r
1412   apr_size_t len;\r
1413 \r
1414   curp = baton->curp[idx];\r
1415   endp = baton->endp[idx];\r
1416 \r
1417   /* Lazily update the current line even if we're at EOF.\r
1418    */\r
1419   baton->current_line[idx]++;\r
1420 \r
1421   if (curp == endp)\r
1422     return SVN_NO_ERROR;\r
1423 \r
1424   eol = find_eol_start(curp, endp - curp);\r
1425   if (!eol)\r
1426     eol = endp;\r
1427   else\r
1428     {\r
1429       svn_boolean_t had_cr = (*eol == '\r');\r
1430       eol++;\r
1431       if (had_cr && eol != endp && *eol == '\n')\r
1432         eol++;\r
1433     }\r
1434 \r
1435   if (type != svn_diff3__file_output_skip)\r
1436     {\r
1437       len = eol - curp;\r
1438       /* Note that the trailing context printer assumes that\r
1439          svn_stream_write is called exactly once per line. */\r
1440       SVN_ERR(svn_stream_write(baton->output_stream, curp, &len));\r
1441     }\r
1442 \r
1443   baton->curp[idx] = eol;\r
1444 \r
1445   return SVN_NO_ERROR;\r
1446 }\r
1447 \r
1448 static svn_error_t *\r
1449 output_marker_eol(svn_diff3__file_output_baton_t *btn)\r
1450 {\r
1451   apr_size_t len = strlen(btn->marker_eol);\r
1452   return svn_stream_write(btn->output_stream, btn->marker_eol, &len);\r
1453 }\r
1454 \r
1455 static svn_error_t *\r
1456 output_hunk(void *baton, int idx, apr_off_t target_line,\r
1457             apr_off_t target_length)\r
1458 {\r
1459   svn_diff3__file_output_baton_t *output_baton = baton;\r
1460 \r
1461   /* Skip lines until we are at the start of the changed range */\r
1462   while (output_baton->current_line[idx] < target_line)\r
1463     {\r
1464       SVN_ERR(output_line(output_baton, svn_diff3__file_output_skip, idx));\r
1465     }\r
1466 \r
1467   target_line += target_length;\r
1468 \r
1469   while (output_baton->current_line[idx] < target_line)\r
1470     {\r
1471       SVN_ERR(output_line(output_baton, svn_diff3__file_output_normal, idx));\r
1472     }\r
1473 \r
1474   return SVN_NO_ERROR;\r
1475 }\r
1476 \r
1477 static svn_error_t *\r
1478 output_common(void *baton, apr_off_t original_start, apr_off_t original_length,\r
1479               apr_off_t modified_start, apr_off_t modified_length,\r
1480               apr_off_t latest_start, apr_off_t latest_length)\r
1481 {\r
1482   return output_hunk(baton, 1, modified_start, modified_length);\r
1483 }\r
1484 \r
1485 static svn_error_t *\r
1486 output_diff_modified(void *baton,\r
1487                      apr_off_t original_start, apr_off_t original_length,\r
1488                      apr_off_t modified_start, apr_off_t modified_length,\r
1489                      apr_off_t latest_start, apr_off_t latest_length)\r
1490 {\r
1491   return output_hunk(baton, 1, modified_start, modified_length);\r
1492 }\r
1493 \r
1494 static svn_error_t *\r
1495 output_diff_latest(void *baton,\r
1496                    apr_off_t original_start, apr_off_t original_length,\r
1497                    apr_off_t modified_start, apr_off_t modified_length,\r
1498                    apr_off_t latest_start, apr_off_t latest_length)\r
1499 {\r
1500   return output_hunk(baton, 2, latest_start, latest_length);\r
1501 }\r
1502 \r
1503 static svn_error_t *\r
1504 output_conflict(void *baton,\r
1505                 apr_off_t original_start, apr_off_t original_length,\r
1506                 apr_off_t modified_start, apr_off_t modified_length,\r
1507                 apr_off_t latest_start, apr_off_t latest_length,\r
1508                 svn_diff_t *diff);\r
1509 \r
1510 static const svn_diff_output_fns_t svn_diff3__file_output_vtable =\r
1511 {\r
1512   output_common,\r
1513   output_diff_modified,\r
1514   output_diff_latest,\r
1515   output_diff_modified, /* output_diff_common */\r
1516   output_conflict\r
1517 };\r
1518 \r
1519 \r
1520 \r
1521 static svn_error_t *\r
1522 output_conflict_with_context(svn_diff3__file_output_baton_t *btn,\r
1523                              apr_off_t original_start,\r
1524                              apr_off_t original_length,\r
1525                              apr_off_t modified_start,\r
1526                              apr_off_t modified_length,\r
1527                              apr_off_t latest_start,\r
1528                              apr_off_t latest_length)\r
1529 {\r
1530   /* Are we currently saving starting context (as opposed to printing\r
1531      trailing context)?  If so, flush it. */\r
1532   if (btn->output_stream == btn->context_saver->stream)\r
1533     {\r
1534       if (btn->context_saver->total_written > SVN_DIFF__UNIFIED_CONTEXT_SIZE)\r
1535         SVN_ERR(svn_stream_printf(btn->real_output_stream, btn->pool, "@@\n"));\r
1536       SVN_ERR(flush_context_saver(btn->context_saver, btn->real_output_stream));\r
1537     }\r
1538 \r
1539   /* Print to the real output stream. */\r
1540   btn->output_stream = btn->real_output_stream;\r
1541 \r
1542   /* Output the conflict itself. */\r
1543   SVN_ERR(svn_stream_printf(btn->output_stream, btn->pool,\r
1544                             (modified_length == 1\r
1545                              ? "%s (%" APR_OFF_T_FMT ")"\r
1546                              : "%s (%" APR_OFF_T_FMT ",%" APR_OFF_T_FMT ")"),\r
1547                             btn->conflict_modified,\r
1548                             modified_start + 1, modified_length));\r
1549   SVN_ERR(output_marker_eol(btn));\r
1550   SVN_ERR(output_hunk(btn, 1/*modified*/, modified_start, modified_length));\r
1551 \r
1552   SVN_ERR(svn_stream_printf(btn->output_stream, btn->pool,\r
1553                             (original_length == 1\r
1554                              ? "%s (%" APR_OFF_T_FMT ")"\r
1555                              : "%s (%" APR_OFF_T_FMT ",%" APR_OFF_T_FMT ")"),\r
1556                             btn->conflict_original,\r
1557                             original_start + 1, original_length));\r
1558   SVN_ERR(output_marker_eol(btn));\r
1559   SVN_ERR(output_hunk(btn, 0/*original*/, original_start, original_length));\r
1560 \r
1561   SVN_ERR(svn_stream_printf(btn->output_stream, btn->pool,\r
1562                             "%s%s", btn->conflict_separator, btn->marker_eol));\r
1563   SVN_ERR(output_hunk(btn, 2/*latest*/, latest_start, latest_length));\r
1564   SVN_ERR(svn_stream_printf(btn->output_stream, btn->pool,\r
1565                             (latest_length == 1\r
1566                              ? "%s (%" APR_OFF_T_FMT ")"\r
1567                              : "%s (%" APR_OFF_T_FMT ",%" APR_OFF_T_FMT ")"),\r
1568                             btn->conflict_latest,\r
1569                             latest_start + 1, latest_length));\r
1570   SVN_ERR(output_marker_eol(btn));\r
1571 \r
1572   /* Go into print-trailing-context mode instead. */\r
1573   make_trailing_context_printer(btn);\r
1574 \r
1575   return SVN_NO_ERROR;\r
1576 }\r
1577 \r
1578 \r
1579 static svn_error_t *\r
1580 output_conflict(void *baton,\r
1581                 apr_off_t original_start, apr_off_t original_length,\r
1582                 apr_off_t modified_start, apr_off_t modified_length,\r
1583                 apr_off_t latest_start, apr_off_t latest_length,\r
1584                 svn_diff_t *diff)\r
1585 {\r
1586   svn_diff3__file_output_baton_t *file_baton = baton;\r
1587   apr_size_t len;\r
1588 \r
1589   svn_diff_conflict_display_style_t style = file_baton->conflict_style;\r
1590 \r
1591   if (style == svn_diff_conflict_display_only_conflicts)\r
1592     return output_conflict_with_context(file_baton,\r
1593                                         original_start, original_length,\r
1594                                         modified_start, modified_length,\r
1595                                         latest_start, latest_length);\r
1596 \r
1597   if (style == svn_diff_conflict_display_resolved_modified_latest)\r
1598     {\r
1599       if (diff)\r
1600         return svn_diff_output(diff, baton,\r
1601                                &svn_diff3__file_output_vtable);\r
1602       else\r
1603         style = svn_diff_conflict_display_modified_latest;\r
1604     }\r
1605 \r
1606   if (style == svn_diff_conflict_display_modified_latest ||\r
1607       style == svn_diff_conflict_display_modified_original_latest)\r
1608     {\r
1609       len = strlen(file_baton->conflict_modified);\r
1610       SVN_ERR(svn_stream_write(file_baton->output_stream,\r
1611                                file_baton->conflict_modified,\r
1612                                &len));\r
1613       SVN_ERR(output_marker_eol(file_baton));\r
1614 \r
1615       SVN_ERR(output_hunk(baton, 1, modified_start, modified_length));\r
1616 \r
1617       if (style == svn_diff_conflict_display_modified_original_latest)\r
1618         {\r
1619           len = strlen(file_baton->conflict_original);\r
1620           SVN_ERR(svn_stream_write(file_baton->output_stream,\r
1621                                    file_baton->conflict_original, &len));\r
1622           SVN_ERR(output_marker_eol(file_baton));\r
1623           SVN_ERR(output_hunk(baton, 0, original_start, original_length));\r
1624         }\r
1625 \r
1626       len = strlen(file_baton->conflict_separator);\r
1627       SVN_ERR(svn_stream_write(file_baton->output_stream,\r
1628                                file_baton->conflict_separator, &len));\r
1629       SVN_ERR(output_marker_eol(file_baton));\r
1630 \r
1631       SVN_ERR(output_hunk(baton, 2, latest_start, latest_length));\r
1632 \r
1633       len = strlen(file_baton->conflict_latest);\r
1634       SVN_ERR(svn_stream_write(file_baton->output_stream,\r
1635                                file_baton->conflict_latest, &len));\r
1636       SVN_ERR(output_marker_eol(file_baton));\r
1637     }\r
1638   else if (style == svn_diff_conflict_display_modified)\r
1639     SVN_ERR(output_hunk(baton, 1, modified_start, modified_length));\r
1640   else if (style == svn_diff_conflict_display_latest)\r
1641     SVN_ERR(output_hunk(baton, 2, latest_start, latest_length));\r
1642   else /* unknown style */\r
1643     SVN_ERR_MALFUNCTION();\r
1644 \r
1645   return SVN_NO_ERROR;\r
1646 }\r
1647 \r
1648 \r
1649 /* Return the first eol marker found in [BUF, ENDP) as a\r
1650  * NUL-terminated string, or NULL if no eol marker is found.\r
1651  *\r
1652  * If the last valid character of BUF is the first byte of a\r
1653  * potentially two-byte eol sequence, just return "\r", that is,\r
1654  * assume BUF represents a CR-only file.  This is correct for callers\r
1655  * that pass an entire file at once, and is no more likely to be\r
1656  * incorrect than correct for any caller that doesn't.\r
1657  */\r
1658 static const char *\r
1659 detect_eol(char *buf, char *endp)\r
1660 {\r
1661   const char *eol = find_eol_start(buf, endp - buf);\r
1662   if (eol)\r
1663     {\r
1664       if (*eol == '\n')\r
1665         return "\n";\r
1666 \r
1667       /* We found a CR. */\r
1668       ++eol;\r
1669       if (eol == endp || *eol != '\n')\r
1670         return "\r";\r
1671       return "\r\n";\r
1672     }\r
1673 \r
1674   return NULL;\r
1675 }\r
1676 \r
1677 svn_error_t *\r
1678 svn_diff_file_output_merge2(svn_stream_t *output_stream,\r
1679                             svn_diff_t *diff,\r
1680                             const char *original_path,\r
1681                             const char *modified_path,\r
1682                             const char *latest_path,\r
1683                             const char *conflict_original,\r
1684                             const char *conflict_modified,\r
1685                             const char *conflict_latest,\r
1686                             const char *conflict_separator,\r
1687                             svn_diff_conflict_display_style_t style,\r
1688                             apr_pool_t *pool)\r
1689 {\r
1690   svn_diff3__file_output_baton_t baton;\r
1691   apr_file_t *file[3];\r
1692   apr_off_t size;\r
1693   int idx;\r
1694 #if APR_HAS_MMAP\r
1695   apr_mmap_t *mm[3] = { 0 };\r
1696 #endif /* APR_HAS_MMAP */\r
1697   const char *eol;\r
1698   svn_boolean_t conflicts_only =\r
1699     (style == svn_diff_conflict_display_only_conflicts);\r
1700 \r
1701   memset(&baton, 0, sizeof(baton));\r
1702   if (conflicts_only)\r
1703     {\r
1704       baton.pool = svn_pool_create(pool);\r
1705       make_context_saver(&baton);\r
1706       baton.real_output_stream = output_stream;\r
1707     }\r
1708   else\r
1709     baton.output_stream = output_stream;\r
1710   baton.path[0] = original_path;\r
1711   baton.path[1] = modified_path;\r
1712   baton.path[2] = latest_path;\r
1713   SVN_ERR(svn_utf_cstring_from_utf8(&baton.conflict_modified,\r
1714                                     conflict_modified ? conflict_modified\r
1715                                     : apr_psprintf(pool, "<<<<<<< %s",\r
1716                                                    modified_path),\r
1717                                     pool));\r
1718   SVN_ERR(svn_utf_cstring_from_utf8(&baton.conflict_original,\r
1719                                     conflict_original ? conflict_original\r
1720                                     : apr_psprintf(pool, "||||||| %s",\r
1721                                                    original_path),\r
1722                                     pool));\r
1723   SVN_ERR(svn_utf_cstring_from_utf8(&baton.conflict_separator,\r
1724                                     conflict_separator ? conflict_separator\r
1725                                     : "=======", pool));\r
1726   SVN_ERR(svn_utf_cstring_from_utf8(&baton.conflict_latest,\r
1727                                     conflict_latest ? conflict_latest\r
1728                                     : apr_psprintf(pool, ">>>>>>> %s",\r
1729                                                    latest_path),\r
1730                                     pool));\r
1731 \r
1732   baton.conflict_style = style;\r
1733 \r
1734   for (idx = 0; idx < 3; idx++)\r
1735     {\r
1736       SVN_ERR(map_or_read_file(&file[idx],\r
1737                                MMAP_T_ARG(mm[idx])\r
1738                                &baton.buffer[idx], &size,\r
1739                                baton.path[idx], pool));\r
1740 \r
1741       baton.curp[idx] = baton.buffer[idx];\r
1742       baton.endp[idx] = baton.buffer[idx];\r
1743 \r
1744       if (baton.endp[idx])\r
1745         baton.endp[idx] += size;\r
1746     }\r
1747 \r
1748   /* Check what eol marker we should use for conflict markers.\r
1749      We use the eol marker of the modified file and fall back on the\r
1750      platform's eol marker if that file doesn't contain any newlines. */\r
1751   eol = detect_eol(baton.buffer[1], baton.endp[1]);\r
1752   if (! eol)\r
1753     eol = APR_EOL_STR;\r
1754   baton.marker_eol = eol;\r
1755 \r
1756   SVN_ERR(svn_diff_output(diff, &baton,\r
1757                           &svn_diff3__file_output_vtable));\r
1758 \r
1759   for (idx = 0; idx < 3; idx++)\r
1760     {\r
1761 #if APR_HAS_MMAP\r
1762       if (mm[idx])\r
1763         {\r
1764           apr_status_t rv = apr_mmap_delete(mm[idx]);\r
1765           if (rv != APR_SUCCESS)\r
1766             {\r
1767               return svn_error_wrap_apr(rv, _("Failed to delete mmap '%s'"),\r
1768                                         baton.path[idx]);\r
1769             }\r
1770         }\r
1771 #endif /* APR_HAS_MMAP */\r
1772 \r
1773       if (file[idx])\r
1774         {\r
1775           SVN_ERR(svn_io_file_close(file[idx], pool));\r
1776         }\r
1777     }\r
1778 \r
1779   if (conflicts_only)\r
1780     svn_pool_destroy(baton.pool);\r
1781 \r
1782   return SVN_NO_ERROR;\r
1783 }\r
1784 \r
1785 \r
1786 svn_error_t *\r
1787 svn_diff_file_output_merge(svn_stream_t *output_stream,\r
1788                            svn_diff_t *diff,\r
1789                            const char *original_path,\r
1790                            const char *modified_path,\r
1791                            const char *latest_path,\r
1792                            const char *conflict_original,\r
1793                            const char *conflict_modified,\r
1794                            const char *conflict_latest,\r
1795                            const char *conflict_separator,\r
1796                            svn_boolean_t display_original_in_conflict,\r
1797                            svn_boolean_t display_resolved_conflicts,\r
1798                            apr_pool_t *pool)\r
1799 {\r
1800   svn_diff_conflict_display_style_t style =\r
1801     svn_diff_conflict_display_modified_latest;\r
1802 \r
1803   if (display_resolved_conflicts)\r
1804     style = svn_diff_conflict_display_resolved_modified_latest;\r
1805 \r
1806   if (display_original_in_conflict)\r
1807     style = svn_diff_conflict_display_modified_original_latest;\r
1808 \r
1809   return svn_diff_file_output_merge2(output_stream,\r
1810                                      diff,\r
1811                                      original_path,\r
1812                                      modified_path,\r
1813                                      latest_path,\r
1814                                      conflict_original,\r
1815                                      conflict_modified,\r
1816                                      conflict_latest,\r
1817                                      conflict_separator,\r
1818                                      style,\r
1819                                      pool);\r
1820 }\r