source: valtobtest/subversion-1.6.2/subversion/libsvn_diff/diff_file.c @ 3

Last change on this file since 3 was 3, checked in by valtob, 15 years ago

subversion source 1.6.2 as test

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