~svn/ubuntu/raring/subversion/ppa

« back to all changes in this revision

Viewing changes to subversion/libsvn_diff/diff_file.c

  • Committer: Bazaar Package Importer
  • Author(s): Adam Conrad
  • Date: 2005-12-05 01:26:14 UTC
  • mfrom: (1.1.2 upstream)
  • Revision ID: james.westby@ubuntu.com-20051205012614-qom4xfypgtsqc2xq
Tags: 1.2.3dfsg1-3ubuntu1
Merge with the final Debian release of 1.2.3dfsg1-3, bringing in
fixes to the clean target, better documentation of the libdb4.3
upgrade and build fixes to work with swig1.3_1.3.27.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/*
 
2
 * diff_file.c :  routines for doing diffs on files
 
3
 *
 
4
 * ====================================================================
 
5
 * Copyright (c) 2000-2004 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_md5.h>
 
24
#include <apr_file_io.h>
 
25
#include <apr_file_info.h>
 
26
#include <apr_time.h>
 
27
#include <apr_mmap.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_pools.h"
 
35
#include "diff.h"
 
36
#include "svn_private_config.h"
 
37
 
 
38
 
 
39
typedef struct svn_diff__file_token_t
 
40
{
 
41
  struct svn_diff__file_token_t *next;
 
42
  svn_diff_datasource_e datasource;
 
43
  apr_off_t offset;
 
44
  apr_off_t length;
 
45
} svn_diff__file_token_t;
 
46
 
 
47
 
 
48
typedef struct svn_diff__file_baton_t
 
49
{
 
50
  const char *path[4];
 
51
 
 
52
  apr_file_t *file[4];
 
53
  apr_off_t size[4];
 
54
 
 
55
  int chunk[4];
 
56
  char *buffer[4];
 
57
  char *curp[4];
 
58
  char *endp[4];
 
59
 
 
60
  svn_diff__file_token_t *tokens;
 
61
 
 
62
  apr_pool_t *pool;
 
63
} svn_diff__file_baton_t;
 
64
 
 
65
 
 
66
static
 
67
int
 
68
svn_diff__file_datasource_to_index(svn_diff_datasource_e datasource)
 
69
{
 
70
  switch (datasource)
 
71
    {
 
72
    case svn_diff_datasource_original:
 
73
      return 0;
 
74
 
 
75
    case svn_diff_datasource_modified:
 
76
      return 1;
 
77
 
 
78
    case svn_diff_datasource_latest:
 
79
      return 2;
 
80
 
 
81
    case svn_diff_datasource_ancestor:
 
82
      return 3;
 
83
    }
 
84
 
 
85
  return -1;
 
86
}
 
87
 
 
88
/* Files are read in chunks of 128k.  There is no support for this number
 
89
 * whatsoever.  If there is a number someone comes up with that has some
 
90
 * argumentation, let's use that.
 
91
 */
 
92
#define CHUNK_SHIFT 17
 
93
#define CHUNK_SIZE (1 << CHUNK_SHIFT)
 
94
 
 
95
#define chunk_to_offset(chunk) ((chunk) << CHUNK_SHIFT)
 
96
#define offset_to_chunk(offset) ((offset) >> CHUNK_SHIFT)
 
97
#define offset_in_chunk(offset) ((offset) & (CHUNK_SIZE - 1))
 
98
 
 
99
 
 
100
/* Read a chunk from a FILE into BUFFER, starting from OFFSET, going for
 
101
 * *LENGTH.  The actual bytes read are stored in *LENGTH on return.
 
102
 */
 
103
static APR_INLINE
 
104
svn_error_t *
 
105
read_chunk(apr_file_t *file, const char *path,
 
106
           char *buffer, apr_size_t length,
 
107
           apr_off_t offset, apr_pool_t *pool)
 
108
{
 
109
  /* XXX: The final offset may not be the one we asked for.
 
110
   * XXX: Check.
 
111
   */
 
112
  SVN_ERR(svn_io_file_seek(file, APR_SET, &offset, pool));
 
113
  SVN_ERR(svn_io_file_read_full(file, buffer, length, NULL, pool));
 
114
 
 
115
  return SVN_NO_ERROR;
 
116
}
 
117
 
 
118
 
 
119
/* Map or read a file at PATH. *BUFFER will point to the file
 
120
 * contents; if the file was mapped, *FILE and *MM will contain the
 
121
 * mmap context; otherwise they will be NULL.  SIZE will contain the
 
122
 * file size.  Allocate from POOL.
 
123
 */
 
124
#if APR_HAS_MMAP
 
125
#define MMAP_T_PARAM(NAME) apr_mmap_t **NAME,
 
126
#define MMAP_T_ARG(NAME)   &(NAME),
 
127
#else
 
128
#define MMAP_T_PARAM(NAME)
 
129
#define MMAP_T_ARG(NAME)
 
130
#endif
 
131
 
 
132
static
 
133
svn_error_t *
 
134
map_or_read_file(apr_file_t **file,
 
135
                 MMAP_T_PARAM(mm)
 
136
                 char **buffer, apr_off_t *size,
 
137
                 const char *path, apr_pool_t *pool)
 
138
{
 
139
  apr_finfo_t finfo;
 
140
  apr_status_t rv;
 
141
 
 
142
  *buffer = NULL;
 
143
 
 
144
  SVN_ERR(svn_io_file_open(file, path, APR_READ, APR_OS_DEFAULT, pool));
 
145
  SVN_ERR(svn_io_file_info_get(&finfo, APR_FINFO_SIZE, *file, pool));
 
146
 
 
147
#if APR_HAS_MMAP
 
148
  if (finfo.size > APR_MMAP_THRESHOLD)
 
149
    {
 
150
      rv = apr_mmap_create(mm, *file, 0, finfo.size, APR_MMAP_READ, pool);
 
151
      if (rv == APR_SUCCESS)
 
152
        {
 
153
          *buffer = (*mm)->mm;
 
154
        }
 
155
 
 
156
      /* On failure we just fall through and try reading the file into
 
157
       * memory instead.
 
158
       */
 
159
    }
 
160
#endif /* APR_HAS_MMAP */
 
161
 
 
162
   if (*buffer == NULL && finfo.size > 0)
 
163
    {
 
164
      *buffer = apr_palloc(pool, finfo.size);
 
165
 
 
166
      SVN_ERR(svn_io_file_read_full(*file, *buffer, finfo.size, NULL, pool));
 
167
 
 
168
      /* Since we have the entire contents of the file we can
 
169
       * close it now.
 
170
       */
 
171
      SVN_ERR(svn_io_file_close(*file, pool));
 
172
 
 
173
      *file = NULL;
 
174
    }
 
175
 
 
176
  *size = finfo.size;
 
177
 
 
178
  return SVN_NO_ERROR;
 
179
}
 
180
 
 
181
 
 
182
static
 
183
svn_error_t *
 
184
svn_diff__file_datasource_open(void *baton,
 
185
                               svn_diff_datasource_e datasource)
 
186
{
 
187
  svn_diff__file_baton_t *file_baton = baton;
 
188
  int idx;
 
189
  apr_finfo_t finfo;
 
190
  apr_size_t length;
 
191
  char *curp;
 
192
  char *endp;
 
193
 
 
194
  idx = svn_diff__file_datasource_to_index(datasource);
 
195
 
 
196
  SVN_ERR(svn_io_file_open(&file_baton->file[idx], file_baton->path[idx],
 
197
                           APR_READ, APR_OS_DEFAULT, file_baton->pool));
 
198
 
 
199
  SVN_ERR(svn_io_file_info_get(&finfo, APR_FINFO_SIZE, 
 
200
                               file_baton->file[idx], file_baton->pool));
 
201
 
 
202
  file_baton->size[idx] = finfo.size;
 
203
  length = finfo.size > CHUNK_SIZE ? CHUNK_SIZE : finfo.size;
 
204
 
 
205
  if (length == 0)
 
206
    return SVN_NO_ERROR;
 
207
 
 
208
  endp = curp = apr_palloc(file_baton->pool, length);
 
209
  endp += length;
 
210
 
 
211
  file_baton->buffer[idx] = file_baton->curp[idx] = curp;
 
212
  file_baton->endp[idx] = endp;
 
213
 
 
214
  SVN_ERR(read_chunk(file_baton->file[idx], file_baton->path[idx],
 
215
                     curp, length, 0, file_baton->pool));
 
216
 
 
217
  return SVN_NO_ERROR;
 
218
}
 
219
 
 
220
 
 
221
static
 
222
svn_error_t *
 
223
svn_diff__file_datasource_close(void *baton,
 
224
                                svn_diff_datasource_e datasource)
 
225
{
 
226
  /* Do nothing.  The compare_token function needs previous datasources
 
227
   * to stay available until all datasources are processed.
 
228
   */
 
229
 
 
230
  return SVN_NO_ERROR;
 
231
}
 
232
 
 
233
 
 
234
static
 
235
svn_error_t *
 
236
svn_diff__file_datasource_get_next_token(apr_uint32_t *hash, void **token,
 
237
                                         void *baton,
 
238
                                         svn_diff_datasource_e datasource)
 
239
{
 
240
  svn_diff__file_baton_t *file_baton = baton;
 
241
  svn_diff__file_token_t *file_token;
 
242
  int idx;
 
243
  char *endp;
 
244
  char *curp;
 
245
  char *eol;
 
246
  int last_chunk;
 
247
  apr_size_t length;
 
248
  apr_uint32_t h = 0;
 
249
 
 
250
  *token = NULL;
 
251
 
 
252
  idx = svn_diff__file_datasource_to_index(datasource);
 
253
 
 
254
  curp = file_baton->curp[idx];
 
255
  endp = file_baton->endp[idx];
 
256
 
 
257
  last_chunk = offset_to_chunk(file_baton->size[idx]);
 
258
 
 
259
  if (curp == endp
 
260
      && last_chunk == file_baton->chunk[idx])
 
261
    {
 
262
      return SVN_NO_ERROR;
 
263
    }
 
264
 
 
265
  /* Get a new token */
 
266
  file_token = file_baton->tokens;
 
267
  if (file_token)
 
268
    {
 
269
      file_baton->tokens = file_token->next;
 
270
    }
 
271
  else
 
272
    {
 
273
      file_token = apr_palloc(file_baton->pool, sizeof(*file_token));
 
274
    }
 
275
 
 
276
  file_token->datasource = datasource;
 
277
  file_token->offset = chunk_to_offset(file_baton->chunk[idx])
 
278
                       + (curp - file_baton->buffer[idx]);
 
279
  file_token->length = 0;
 
280
 
 
281
  while (1)
 
282
    {
 
283
      /* XXX: '\n' doesn't really cut it.  We need to be able to detect
 
284
       * XXX: '\n', '\r' and '\r\n'.
 
285
       */
 
286
      eol = memchr(curp, '\n', endp - curp);
 
287
      if (eol)
 
288
        {
 
289
          eol++;
 
290
          break;
 
291
        }
 
292
 
 
293
      if (file_baton->chunk[idx] == last_chunk)
 
294
        {
 
295
          eol = endp;
 
296
          break;
 
297
        }
 
298
 
 
299
      length = endp - curp;
 
300
      file_token->length += length;
 
301
      h = svn_diff__adler32(h, curp, length);
 
302
 
 
303
      curp = endp = file_baton->buffer[idx];
 
304
      file_baton->chunk[idx]++;
 
305
      length = file_baton->chunk[idx] == last_chunk ? 
 
306
        offset_in_chunk(file_baton->size[idx]) : CHUNK_SIZE;
 
307
      endp += length;
 
308
      file_baton->endp[idx] = endp;
 
309
 
 
310
      SVN_ERR(read_chunk(file_baton->file[idx], file_baton->path[idx],
 
311
                         curp, length, 
 
312
                         chunk_to_offset(file_baton->chunk[idx]),
 
313
                         file_baton->pool));
 
314
    }
 
315
 
 
316
  length = eol - curp;
 
317
  file_token->length += length;
 
318
  *hash = svn_diff__adler32(h, curp, length);
 
319
 
 
320
  file_baton->curp[idx] = eol;
 
321
  *token = file_token;
 
322
 
 
323
  return SVN_NO_ERROR;
 
324
}
 
325
 
 
326
#define COMPARE_CHUNK_SIZE 4096
 
327
 
 
328
static
 
329
svn_error_t *
 
330
svn_diff__file_token_compare(void *baton,
 
331
                             void *token1,
 
332
                             void *token2,
 
333
                             int *compare)
 
334
{
 
335
  svn_diff__file_baton_t *file_baton = baton;
 
336
  svn_diff__file_token_t *file_token1 = token1;
 
337
  svn_diff__file_token_t *file_token2 = token2;
 
338
  char buffer[2][COMPARE_CHUNK_SIZE];
 
339
  char *bufp[2];
 
340
  apr_off_t offset[2];
 
341
  int idx[2];
 
342
  apr_off_t length[2];
 
343
  apr_off_t total_length;
 
344
  apr_off_t len;
 
345
  int i;
 
346
  int chunk[2];
 
347
 
 
348
  if (file_token1->length < file_token2->length)
 
349
    {
 
350
      *compare = -1;
 
351
      return SVN_NO_ERROR;
 
352
    }
 
353
 
 
354
  if (file_token1->length > file_token2->length)
 
355
    {
 
356
      *compare = 1;
 
357
      return SVN_NO_ERROR;
 
358
    }
 
359
 
 
360
  total_length = file_token1->length;
 
361
  if (total_length == 0)
 
362
    {
 
363
      *compare = 0;
 
364
      return SVN_NO_ERROR;
 
365
    }
 
366
 
 
367
  idx[0] = svn_diff__file_datasource_to_index(file_token1->datasource);
 
368
  idx[1] = svn_diff__file_datasource_to_index(file_token2->datasource);
 
369
  offset[0] = file_token1->offset;
 
370
  offset[1] = file_token2->offset;
 
371
  chunk[0] = file_baton->chunk[idx[0]];
 
372
  chunk[1] = file_baton->chunk[idx[1]];
 
373
 
 
374
  do
 
375
    {
 
376
      for (i = 0; i < 2; i++)
 
377
        {
 
378
          if (offset_to_chunk(offset[i]) == chunk[i])
 
379
            {
 
380
              /* If the start of the token is in memory, the entire token is
 
381
               * in memory.
 
382
               */
 
383
              bufp[i] = file_baton->buffer[idx[i]];
 
384
              bufp[i] += offset_in_chunk(offset[i]);
 
385
 
 
386
              length[i] = total_length;
 
387
            }
 
388
          else
 
389
            {
 
390
              /* Read a chunk from disk into a buffer */
 
391
              bufp[i] = buffer[i];
 
392
              length[i] = total_length > COMPARE_CHUNK_SIZE ? 
 
393
                COMPARE_CHUNK_SIZE : total_length;
 
394
 
 
395
              SVN_ERR(read_chunk(file_baton->file[idx[i]],
 
396
                                 file_baton->path[idx[i]],
 
397
                                 bufp[i], length[i], offset[i],
 
398
                                 file_baton->pool));
 
399
            }
 
400
        }
 
401
 
 
402
      len = length[0] > length[1] ? length[1] : length[0];
 
403
      offset[0] += len;
 
404
      offset[1] += len;
 
405
 
 
406
      /* Compare two chunks (that could be entire tokens if they both reside
 
407
       * in memory).
 
408
       */
 
409
      *compare = memcmp(bufp[0], bufp[1], len);
 
410
      if (*compare != 0)
 
411
        return SVN_NO_ERROR;
 
412
 
 
413
      total_length -= len;
 
414
    }
 
415
  while(total_length > 0);
 
416
 
 
417
  *compare = 0;
 
418
  return SVN_NO_ERROR;
 
419
}
 
420
 
 
421
 
 
422
static
 
423
void
 
424
svn_diff__file_token_discard(void *baton,
 
425
                             void *token)
 
426
{
 
427
  svn_diff__file_baton_t *file_baton = baton;
 
428
  svn_diff__file_token_t *file_token = token;
 
429
 
 
430
  file_token->next = file_baton->tokens;
 
431
  file_baton->tokens = file_token;
 
432
}
 
433
 
 
434
 
 
435
static
 
436
void
 
437
svn_diff__file_token_discard_all(void *baton)
 
438
{
 
439
  svn_diff__file_baton_t *file_baton = baton;
 
440
 
 
441
  /* Discard all memory in use by the tokens, and close all open files. */
 
442
  svn_pool_clear(file_baton->pool);
 
443
}
 
444
 
 
445
 
 
446
static const svn_diff_fns_t svn_diff__file_vtable =
 
447
{
 
448
  svn_diff__file_datasource_open,
 
449
  svn_diff__file_datasource_close,
 
450
  svn_diff__file_datasource_get_next_token,
 
451
  svn_diff__file_token_compare,
 
452
  svn_diff__file_token_discard,
 
453
  svn_diff__file_token_discard_all
 
454
};
 
455
 
 
456
svn_error_t *
 
457
svn_diff_file_diff(svn_diff_t **diff,
 
458
                   const char *original,
 
459
                   const char *modified,
 
460
                   apr_pool_t *pool)
 
461
{
 
462
  svn_diff__file_baton_t baton;
 
463
 
 
464
  memset(&baton, 0, sizeof(baton));
 
465
  baton.path[0] = original;
 
466
  baton.path[1] = modified;
 
467
  baton.pool = svn_pool_create(pool);
 
468
 
 
469
  SVN_ERR(svn_diff_diff(diff, &baton, &svn_diff__file_vtable, pool));
 
470
 
 
471
  svn_pool_destroy(baton.pool);
 
472
  return SVN_NO_ERROR;
 
473
}
 
474
 
 
475
svn_error_t *
 
476
svn_diff_file_diff3(svn_diff_t **diff,
 
477
                    const char *original,
 
478
                    const char *modified,
 
479
                    const char *latest,
 
480
                    apr_pool_t *pool)
 
481
{
 
482
  svn_diff__file_baton_t baton;
 
483
 
 
484
  memset(&baton, 0, sizeof(baton));
 
485
  baton.path[0] = original;
 
486
  baton.path[1] = modified;
 
487
  baton.path[2] = latest;
 
488
  baton.pool = svn_pool_create(pool);
 
489
 
 
490
  SVN_ERR(svn_diff_diff3(diff, &baton, &svn_diff__file_vtable, pool));
 
491
 
 
492
  svn_pool_destroy(baton.pool);
 
493
  return SVN_NO_ERROR;
 
494
}
 
495
 
 
496
svn_error_t *
 
497
svn_diff_file_diff4(svn_diff_t **diff,
 
498
                    const char *original,
 
499
                    const char *modified,
 
500
                    const char *latest,
 
501
                    const char *ancestor,
 
502
                    apr_pool_t *pool)
 
503
{
 
504
  svn_diff__file_baton_t baton;
 
505
 
 
506
  memset(&baton, 0, sizeof(baton));
 
507
  baton.path[0] = original;
 
508
  baton.path[1] = modified;
 
509
  baton.path[2] = latest;
 
510
  baton.path[3] = ancestor;
 
511
  baton.pool = svn_pool_create(pool);
 
512
 
 
513
  SVN_ERR(svn_diff_diff4(diff, &baton, &svn_diff__file_vtable, pool));
 
514
 
 
515
  svn_pool_destroy(baton.pool);
 
516
  return SVN_NO_ERROR;
 
517
}
 
518
 
 
519
 
 
520
/** Display unified context diffs **/
 
521
 
 
522
#define SVN_DIFF__UNIFIED_CONTEXT_SIZE 3
 
523
 
 
524
typedef struct svn_diff__file_output_baton_t
 
525
{
 
526
  svn_stream_t *output_stream;
 
527
 
 
528
  const char *path[2];
 
529
  apr_file_t *file[2];
 
530
 
 
531
  apr_off_t   current_line[2];
 
532
 
 
533
  char        buffer[2][4096];
 
534
  apr_size_t  length[2];
 
535
  char       *curp[2];
 
536
 
 
537
  apr_off_t   hunk_start[2];
 
538
  apr_off_t   hunk_length[2];
 
539
  svn_stringbuf_t *hunk;
 
540
 
 
541
  apr_pool_t *pool;
 
542
} svn_diff__file_output_baton_t;
 
543
 
 
544
typedef enum svn_diff__file_output_unified_type_e
 
545
{
 
546
  svn_diff__file_output_unified_skip,
 
547
  svn_diff__file_output_unified_context,
 
548
  svn_diff__file_output_unified_delete,
 
549
  svn_diff__file_output_unified_insert
 
550
} svn_diff__file_output_unified_type_e;
 
551
 
 
552
 
 
553
static
 
554
svn_error_t *
 
555
svn_diff__file_output_unified_line(svn_diff__file_output_baton_t *baton,
 
556
                                   svn_diff__file_output_unified_type_e type,
 
557
                                   int idx)
 
558
{
 
559
  char *curp;
 
560
  char *eol;
 
561
  apr_size_t length;
 
562
  svn_error_t *err;
 
563
  svn_boolean_t bytes_processed = FALSE;
 
564
 
 
565
  length = baton->length[idx];
 
566
  curp = baton->curp[idx];
 
567
 
 
568
  /* Lazily update the current line even if we're at EOF.
 
569
   * This way we fake output of context at EOF
 
570
   */
 
571
  baton->current_line[idx]++;
 
572
 
 
573
  if (length == 0 && apr_file_eof(baton->file[idx]))
 
574
    {
 
575
      return SVN_NO_ERROR;
 
576
    }
 
577
 
 
578
  do
 
579
    {
 
580
      if (length > 0)
 
581
        {
 
582
          if (!bytes_processed)
 
583
            {
 
584
              switch (type)
 
585
                {
 
586
                case svn_diff__file_output_unified_context:
 
587
                  svn_stringbuf_appendbytes(baton->hunk, " ", 1);
 
588
                  baton->hunk_length[0]++;
 
589
                  baton->hunk_length[1]++;
 
590
                  break;
 
591
                case svn_diff__file_output_unified_delete:
 
592
                  svn_stringbuf_appendbytes(baton->hunk, "-", 1);
 
593
                  baton->hunk_length[0]++;
 
594
                  break;
 
595
                case svn_diff__file_output_unified_insert:
 
596
                  svn_stringbuf_appendbytes(baton->hunk, "+", 1);
 
597
                  baton->hunk_length[1]++;
 
598
                  break;
 
599
                default:
 
600
                  break;
 
601
                }
 
602
            }
 
603
 
 
604
          /* XXX: '\n' doesn't really cut it.  We need to be able to detect
 
605
           * XXX: '\n', '\r' and '\r\n'.
 
606
           */
 
607
          eol = memchr(curp, '\n', length);
 
608
 
 
609
          if (eol != NULL)
 
610
            {
 
611
              apr_size_t len;
 
612
 
 
613
              eol++;
 
614
              len = (apr_size_t)(eol - curp);
 
615
              length -= len;
 
616
 
 
617
              if (type != svn_diff__file_output_unified_skip)
 
618
                {
 
619
                  svn_stringbuf_appendbytes(baton->hunk, curp, len);
 
620
                }
 
621
 
 
622
              baton->curp[idx] = eol;
 
623
              baton->length[idx] = length;
 
624
 
 
625
              err = SVN_NO_ERROR;
 
626
 
 
627
              break;
 
628
            }
 
629
 
 
630
          if (type != svn_diff__file_output_unified_skip)
 
631
            {
 
632
              svn_stringbuf_appendbytes(baton->hunk, curp, length);
 
633
            }
 
634
 
 
635
          bytes_processed = TRUE;
 
636
        }
 
637
 
 
638
      curp = baton->buffer[idx];
 
639
      length = sizeof(baton->buffer[idx]);
 
640
 
 
641
      err = svn_io_file_read(baton->file[idx], curp, &length, baton->pool);
 
642
    }
 
643
  while (! err);
 
644
 
 
645
  if (err && ! APR_STATUS_IS_EOF(err->apr_err))
 
646
    return err;
 
647
 
 
648
  if (err && APR_STATUS_IS_EOF(err->apr_err))
 
649
    {
 
650
      svn_error_clear (err);
 
651
      /* Special case if we reach the end of file AND the last line is in the
 
652
         changed range AND the file doesn't end with a newline */
 
653
      if (bytes_processed && (type != svn_diff__file_output_unified_skip))
 
654
        {
 
655
          svn_stringbuf_appendcstr(baton->hunk,
 
656
            APR_EOL_STR "\\ No newline at end of file" APR_EOL_STR);
 
657
        }
 
658
 
 
659
      baton->length[idx] = 0;
 
660
    }
 
661
 
 
662
  return SVN_NO_ERROR;
 
663
}
 
664
 
 
665
static
 
666
svn_error_t *
 
667
svn_diff__file_output_unified_flush_hunk(svn_diff__file_output_baton_t *baton)
 
668
{
 
669
  apr_off_t target_line;
 
670
  apr_size_t hunk_len;
 
671
  int i;
 
672
 
 
673
  if (svn_stringbuf_isempty(baton->hunk))
 
674
    {
 
675
      /* Nothing to flush */
 
676
      return SVN_NO_ERROR;
 
677
    }
 
678
 
 
679
  target_line = baton->hunk_start[0] + baton->hunk_length[0]
 
680
                + SVN_DIFF__UNIFIED_CONTEXT_SIZE;
 
681
 
 
682
  /* Add trailing context to the hunk */
 
683
  while (baton->current_line[0] < target_line)
 
684
    {
 
685
      SVN_ERR(svn_diff__file_output_unified_line(baton,
 
686
                svn_diff__file_output_unified_context, 0));
 
687
    }
 
688
 
 
689
  /* If the file is non-empty, convert the line indexes from
 
690
     zero based to one based */
 
691
  for (i = 0; i < 2; i++)
 
692
    {
 
693
      if (baton->hunk_length[i] > 0)
 
694
        baton->hunk_start[i]++;
 
695
    }
 
696
 
 
697
  /* Output the hunk header.  If the hunk length is 1, the file is a one line
 
698
     file.  In this case, surpress the number of lines in the hunk (it is
 
699
     1 implicitly) 
 
700
   */
 
701
  SVN_ERR(svn_stream_printf(baton->output_stream, baton->pool,
 
702
                            "@@ -%" APR_OFF_T_FMT,
 
703
                            baton->hunk_start[0]));
 
704
  if (baton->hunk_length[0] != 1)
 
705
    {
 
706
      SVN_ERR(svn_stream_printf(baton->output_stream, baton->pool,
 
707
                                ",%" APR_OFF_T_FMT,
 
708
                                baton->hunk_length[0]));
 
709
    }
 
710
 
 
711
  SVN_ERR(svn_stream_printf(baton->output_stream, baton->pool,
 
712
                            " +%" APR_OFF_T_FMT,
 
713
                            baton->hunk_start[1]));
 
714
  if (baton->hunk_length[1] != 1)
 
715
    {
 
716
      SVN_ERR(svn_stream_printf(baton->output_stream, baton->pool,
 
717
                                ",%" APR_OFF_T_FMT,
 
718
                                baton->hunk_length[1]));
 
719
    }
 
720
 
 
721
  SVN_ERR(svn_stream_printf(baton->output_stream, baton->pool,
 
722
                            " @@" APR_EOL_STR));
 
723
 
 
724
  /* Output the hunk content */
 
725
  hunk_len = baton->hunk->len;
 
726
  SVN_ERR(svn_stream_write(baton->output_stream, baton->hunk->data,
 
727
                           &hunk_len));
 
728
 
 
729
  /* Prepare for the next hunk */
 
730
  baton->hunk_length[0] = 0;
 
731
  baton->hunk_length[1] = 0;
 
732
  svn_stringbuf_setempty(baton->hunk);
 
733
 
 
734
  return SVN_NO_ERROR;
 
735
}
 
736
 
 
737
static
 
738
svn_error_t *
 
739
svn_diff__file_output_unified_diff_modified(void *baton,
 
740
  apr_off_t original_start, apr_off_t original_length,
 
741
  apr_off_t modified_start, apr_off_t modified_length,
 
742
  apr_off_t latest_start, apr_off_t latest_length)
 
743
{
 
744
  svn_diff__file_output_baton_t *output_baton = baton;
 
745
  apr_off_t target_line[2];
 
746
  int i;
 
747
 
 
748
  target_line[0] = original_start >= SVN_DIFF__UNIFIED_CONTEXT_SIZE
 
749
                   ? original_start - SVN_DIFF__UNIFIED_CONTEXT_SIZE : 0;
 
750
  target_line[1] = modified_start;
 
751
 
 
752
  /* If the changed ranges are far enough apart (no overlapping or connecting
 
753
     context), flush the current hunk, initialize the next hunk and skip the
 
754
     lines not in context.  Also do this when this is the first hunk.
 
755
   */
 
756
  if (output_baton->current_line[0] < target_line[0]
 
757
      && (output_baton->hunk_start[0] + output_baton->hunk_length[0]
 
758
          + SVN_DIFF__UNIFIED_CONTEXT_SIZE < target_line[0]
 
759
          || output_baton->hunk_length[0] == 0))
 
760
    {
 
761
      SVN_ERR(svn_diff__file_output_unified_flush_hunk(output_baton));
 
762
 
 
763
      output_baton->hunk_start[0] = target_line[0];
 
764
      output_baton->hunk_start[1] = target_line[1] + target_line[0]
 
765
                                    - original_start;
 
766
 
 
767
      /* Skip lines until we are at the beginning of the context we want to
 
768
         display */
 
769
      while (output_baton->current_line[0] < target_line[0])
 
770
        {
 
771
          SVN_ERR(svn_diff__file_output_unified_line(output_baton,
 
772
                    svn_diff__file_output_unified_skip, 0));
 
773
        }
 
774
    }
 
775
 
 
776
  /* Skip lines until we are at the start of the changed range */
 
777
  while (output_baton->current_line[1] < target_line[1])
 
778
    {
 
779
      SVN_ERR(svn_diff__file_output_unified_line(output_baton,
 
780
                svn_diff__file_output_unified_skip, 1));
 
781
    }
 
782
 
 
783
  /* Output the context preceding the changed range */
 
784
  while (output_baton->current_line[0] < original_start)
 
785
    {
 
786
      SVN_ERR(svn_diff__file_output_unified_line(output_baton,
 
787
                svn_diff__file_output_unified_context, 0));
 
788
    }
 
789
 
 
790
  target_line[0] = original_start + original_length;
 
791
  target_line[1] = modified_start + modified_length;
 
792
 
 
793
  /* Output the changed range */
 
794
  for (i = 0; i < 2; i++)
 
795
    {
 
796
      while (output_baton->current_line[i] < target_line[i])
 
797
        {
 
798
          SVN_ERR(svn_diff__file_output_unified_line(output_baton, i == 0
 
799
                    ? svn_diff__file_output_unified_delete
 
800
                    : svn_diff__file_output_unified_insert, i));
 
801
        }
 
802
    }
 
803
 
 
804
  return SVN_NO_ERROR;
 
805
}
 
806
 
 
807
static
 
808
const char *
 
809
svn_diff__file_output_unified_default_hdr(apr_pool_t *pool,
 
810
                                          const char *path)
 
811
{
 
812
  apr_finfo_t file_info;
 
813
  apr_time_exp_t exploded_time;
 
814
  char time_buffer[64];
 
815
  apr_size_t time_len;
 
816
 
 
817
  svn_io_stat(&file_info, path, APR_FINFO_MTIME, pool);
 
818
  apr_time_exp_lt(&exploded_time, file_info.mtime);
 
819
 
 
820
  apr_strftime(time_buffer, &time_len, sizeof(time_buffer) - 1,
 
821
               "%a %b %e %H:%M:%S %Y", &exploded_time);
 
822
 
 
823
  return apr_psprintf(pool, "%s\t%s", path, time_buffer);
 
824
}
 
825
 
 
826
static const svn_diff_output_fns_t svn_diff__file_output_unified_vtable =
 
827
{
 
828
  NULL, /* output_common */
 
829
  svn_diff__file_output_unified_diff_modified,
 
830
  NULL, /* output_diff_latest */
 
831
  NULL, /* output_diff_common */
 
832
  NULL  /* output_conflict */
 
833
};
 
834
 
 
835
svn_error_t *
 
836
svn_diff_file_output_unified(svn_stream_t *output_stream,
 
837
                             svn_diff_t *diff,
 
838
                             const char *original_path,
 
839
                             const char *modified_path,
 
840
                             const char *original_header,
 
841
                             const char *modified_header,
 
842
                             apr_pool_t *pool)
 
843
{
 
844
  svn_diff__file_output_baton_t baton;
 
845
  int i;
 
846
 
 
847
  if (svn_diff_contains_diffs(diff))
 
848
    {
 
849
      memset(&baton, 0, sizeof(baton));
 
850
      baton.output_stream = output_stream;
 
851
      baton.pool = pool;
 
852
      baton.path[0] = original_path;
 
853
      baton.path[1] = modified_path;
 
854
      baton.hunk = svn_stringbuf_create("", pool);
 
855
 
 
856
      for (i = 0; i < 2; i++)
 
857
        {
 
858
          SVN_ERR(svn_io_file_open(&baton.file[i], baton.path[i],
 
859
                                   APR_READ, APR_OS_DEFAULT, pool) );
 
860
        }
 
861
 
 
862
      if (original_header == NULL)
 
863
        {
 
864
          original_header =
 
865
            svn_diff__file_output_unified_default_hdr(pool, original_path);
 
866
        }
 
867
 
 
868
      if (modified_header == NULL)
 
869
        {
 
870
          modified_header =
 
871
            svn_diff__file_output_unified_default_hdr(pool, modified_path);
 
872
        }
 
873
 
 
874
      SVN_ERR(svn_stream_printf(output_stream, pool,
 
875
                                "--- %s" APR_EOL_STR
 
876
                                "+++ %s" APR_EOL_STR,
 
877
                                original_header, modified_header));
 
878
 
 
879
      SVN_ERR(svn_diff_output(diff, &baton,
 
880
                              &svn_diff__file_output_unified_vtable));
 
881
      SVN_ERR(svn_diff__file_output_unified_flush_hunk(&baton));
 
882
 
 
883
      for (i = 0; i < 2; i++)
 
884
        {
 
885
          SVN_ERR(svn_io_file_close(baton.file[i], pool));
 
886
        }
 
887
    }
 
888
 
 
889
  return SVN_NO_ERROR;
 
890
}
 
891
 
 
892
 
 
893
/** Display diff3 **/
 
894
 
 
895
typedef struct svn_diff3__file_output_baton_t
 
896
{
 
897
  svn_stream_t *output_stream;
 
898
 
 
899
  const char *path[3];
 
900
 
 
901
  apr_off_t   current_line[3];
 
902
 
 
903
  char       *buffer[3];
 
904
  char       *endp[3];
 
905
  char       *curp[3];
 
906
 
 
907
  const char *conflict_modified;
 
908
  const char *conflict_original;
 
909
  const char *conflict_separator;
 
910
  const char *conflict_latest;
 
911
 
 
912
  svn_boolean_t display_original_in_conflict;
 
913
  svn_boolean_t display_resolved_conflicts;
 
914
 
 
915
  apr_pool_t *pool;
 
916
} svn_diff3__file_output_baton_t;
 
917
 
 
918
typedef enum svn_diff3__file_output_type_e
 
919
{
 
920
  svn_diff3__file_output_skip,
 
921
  svn_diff3__file_output_normal
 
922
} svn_diff3__file_output_type_e;
 
923
 
 
924
 
 
925
static
 
926
svn_error_t *
 
927
svn_diff3__file_output_line(svn_diff3__file_output_baton_t *baton,
 
928
                            svn_diff3__file_output_type_e type,
 
929
                            int idx)
 
930
{
 
931
  char *curp;
 
932
  char *endp;
 
933
  char *eol;
 
934
  apr_size_t len;
 
935
 
 
936
  curp = baton->curp[idx];
 
937
  endp = baton->endp[idx];
 
938
 
 
939
  /* Lazily update the current line even if we're at EOF.
 
940
   */
 
941
  baton->current_line[idx]++;
 
942
 
 
943
  if (curp == endp)
 
944
    return SVN_NO_ERROR;
 
945
 
 
946
  /* XXX: '\n' doesn't really cut it.  We need to be able to detect
 
947
   * XXX: '\n', '\r' and '\r\n'.
 
948
   */
 
949
  eol = memchr(curp, '\n', endp - curp);
 
950
  if (!eol)
 
951
    eol = endp;
 
952
  else
 
953
    eol++;
 
954
 
 
955
  if (type != svn_diff3__file_output_skip)
 
956
    {
 
957
      len = eol - curp;
 
958
      SVN_ERR(svn_stream_write(baton->output_stream, curp, &len));
 
959
    }
 
960
 
 
961
  baton->curp[idx] = eol;
 
962
 
 
963
  return SVN_NO_ERROR;
 
964
}
 
965
 
 
966
static
 
967
svn_error_t *
 
968
svn_diff3__file_output_hunk(void *baton,
 
969
  int idx,
 
970
  apr_off_t target_line, apr_off_t target_length)
 
971
{
 
972
  svn_diff3__file_output_baton_t *output_baton = baton;
 
973
 
 
974
  /* Skip lines until we are at the start of the changed range */
 
975
  while (output_baton->current_line[idx] < target_line)
 
976
    {
 
977
      SVN_ERR(svn_diff3__file_output_line(output_baton,
 
978
                svn_diff3__file_output_skip, idx));
 
979
    }
 
980
 
 
981
  target_line += target_length;
 
982
 
 
983
  while (output_baton->current_line[idx] < target_line)
 
984
    {
 
985
      SVN_ERR(svn_diff3__file_output_line(output_baton,
 
986
                svn_diff3__file_output_normal, idx));
 
987
    }
 
988
 
 
989
  return SVN_NO_ERROR;
 
990
}
 
991
 
 
992
static
 
993
svn_error_t *
 
994
svn_diff3__file_output_common(void *baton,
 
995
  apr_off_t original_start, apr_off_t original_length,
 
996
  apr_off_t modified_start, apr_off_t modified_length,
 
997
  apr_off_t latest_start, apr_off_t latest_length)
 
998
{
 
999
  return svn_diff3__file_output_hunk(baton, 0,
 
1000
           original_start, original_length);
 
1001
}
 
1002
 
 
1003
static
 
1004
svn_error_t *
 
1005
svn_diff3__file_output_diff_modified(void *baton,
 
1006
  apr_off_t original_start, apr_off_t original_length,
 
1007
  apr_off_t modified_start, apr_off_t modified_length,
 
1008
  apr_off_t latest_start, apr_off_t latest_length)
 
1009
{
 
1010
  return svn_diff3__file_output_hunk(baton, 1,
 
1011
           modified_start, modified_length);
 
1012
}
 
1013
 
 
1014
static
 
1015
svn_error_t *
 
1016
svn_diff3__file_output_diff_latest(void *baton,
 
1017
  apr_off_t original_start, apr_off_t original_length,
 
1018
  apr_off_t modified_start, apr_off_t modified_length,
 
1019
  apr_off_t latest_start, apr_off_t latest_length)
 
1020
{
 
1021
  return svn_diff3__file_output_hunk(baton, 2,
 
1022
           latest_start, latest_length);
 
1023
}
 
1024
 
 
1025
static
 
1026
svn_error_t *
 
1027
svn_diff3__file_output_conflict(void *baton,
 
1028
  apr_off_t original_start, apr_off_t original_length,
 
1029
  apr_off_t modified_start, apr_off_t modified_length,
 
1030
  apr_off_t latest_start, apr_off_t latest_length,
 
1031
  svn_diff_t *diff);
 
1032
 
 
1033
static const svn_diff_output_fns_t svn_diff3__file_output_vtable =
 
1034
{
 
1035
  svn_diff3__file_output_common,
 
1036
  svn_diff3__file_output_diff_modified,
 
1037
  svn_diff3__file_output_diff_latest,
 
1038
  svn_diff3__file_output_diff_modified, /* output_diff_common */
 
1039
  svn_diff3__file_output_conflict
 
1040
};
 
1041
 
 
1042
static
 
1043
svn_error_t *
 
1044
svn_diff3__file_output_conflict(void *baton,
 
1045
  apr_off_t original_start, apr_off_t original_length,
 
1046
  apr_off_t modified_start, apr_off_t modified_length,
 
1047
  apr_off_t latest_start, apr_off_t latest_length,
 
1048
  svn_diff_t *diff)
 
1049
{
 
1050
  svn_diff3__file_output_baton_t *file_baton = baton;
 
1051
  apr_size_t len;
 
1052
 
 
1053
  if (diff && file_baton->display_resolved_conflicts)
 
1054
    {
 
1055
      return svn_diff_output(diff, baton,
 
1056
                             &svn_diff3__file_output_vtable);
 
1057
    }
 
1058
 
 
1059
  len = strlen(file_baton->conflict_modified);
 
1060
  SVN_ERR(svn_stream_write(file_baton->output_stream,
 
1061
                           file_baton->conflict_modified,
 
1062
                           &len));
 
1063
  len = sizeof(APR_EOL_STR) - 1;
 
1064
  SVN_ERR(svn_stream_write(file_baton->output_stream,
 
1065
                           APR_EOL_STR, &len));
 
1066
 
 
1067
  SVN_ERR(svn_diff3__file_output_hunk(baton, 1,
 
1068
            modified_start, modified_length));
 
1069
 
 
1070
  if (file_baton->display_original_in_conflict)
 
1071
    {
 
1072
      len = strlen(file_baton->conflict_original);
 
1073
      SVN_ERR(svn_stream_write(file_baton->output_stream,
 
1074
                               file_baton->conflict_original, &len));
 
1075
      len = sizeof(APR_EOL_STR) - 1;
 
1076
      SVN_ERR(svn_stream_write(file_baton->output_stream,
 
1077
                               APR_EOL_STR, &len));
 
1078
 
 
1079
      SVN_ERR(svn_diff3__file_output_hunk(baton, 0,
 
1080
              original_start, original_length));
 
1081
    }
 
1082
 
 
1083
  len = strlen(file_baton->conflict_separator);
 
1084
  SVN_ERR(svn_stream_write(file_baton->output_stream,
 
1085
                           file_baton->conflict_separator, &len));
 
1086
  len = sizeof(APR_EOL_STR) - 1;
 
1087
  SVN_ERR(svn_stream_write(file_baton->output_stream,
 
1088
                           APR_EOL_STR, &len));
 
1089
 
 
1090
  SVN_ERR(svn_diff3__file_output_hunk(baton, 2,
 
1091
            latest_start, latest_length));
 
1092
 
 
1093
  len = strlen(file_baton->conflict_latest);
 
1094
  SVN_ERR(svn_stream_write(file_baton->output_stream,
 
1095
                           file_baton->conflict_latest, &len));
 
1096
  len = sizeof(APR_EOL_STR) - 1;
 
1097
  SVN_ERR(svn_stream_write(file_baton->output_stream,
 
1098
                           APR_EOL_STR, &len));
 
1099
 
 
1100
  return SVN_NO_ERROR;
 
1101
}
 
1102
 
 
1103
svn_error_t *
 
1104
svn_diff_file_output_merge(svn_stream_t *output_stream,
 
1105
                           svn_diff_t *diff,
 
1106
                           const char *original_path,
 
1107
                           const char *modified_path,
 
1108
                           const char *latest_path,
 
1109
                           const char *conflict_original,
 
1110
                           const char *conflict_modified,
 
1111
                           const char *conflict_latest,
 
1112
                           const char *conflict_separator,
 
1113
                           svn_boolean_t display_original_in_conflict,
 
1114
                           svn_boolean_t display_resolved_conflicts,
 
1115
                           apr_pool_t *pool)
 
1116
{
 
1117
  svn_diff3__file_output_baton_t baton;
 
1118
  apr_file_t *file[3];
 
1119
  apr_off_t size;
 
1120
  int idx;
 
1121
#if APR_HAS_MMAP
 
1122
  apr_mmap_t *mm[3] = { 0 };
 
1123
#endif /* APR_HAS_MMAP */
 
1124
 
 
1125
  memset(&baton, 0, sizeof(baton));
 
1126
  baton.output_stream = output_stream;
 
1127
  baton.pool = pool;
 
1128
  baton.path[0] = original_path;
 
1129
  baton.path[1] = modified_path;
 
1130
  baton.path[2] = latest_path;
 
1131
  baton.conflict_modified = conflict_modified ? conflict_modified
 
1132
                            : apr_psprintf(pool, "<<<<<<< %s", modified_path);
 
1133
  baton.conflict_original = conflict_original ? conflict_original
 
1134
                            : apr_psprintf(pool, "||||||| %s", original_path);
 
1135
  baton.conflict_separator = conflict_separator ? conflict_separator
 
1136
                             : "=======";
 
1137
  baton.conflict_latest = conflict_latest ? conflict_latest
 
1138
                          : apr_psprintf(pool, ">>>>>>> %s", latest_path);
 
1139
 
 
1140
  baton.display_original_in_conflict = display_original_in_conflict;
 
1141
  baton.display_resolved_conflicts = display_resolved_conflicts &&
 
1142
                                     !display_original_in_conflict;
 
1143
 
 
1144
  for (idx = 0; idx < 3; idx++)
 
1145
    {
 
1146
      SVN_ERR(map_or_read_file(&file[idx],
 
1147
                               MMAP_T_ARG(mm[idx])
 
1148
                               &baton.buffer[idx], &size,
 
1149
                               baton.path[idx], pool));
 
1150
 
 
1151
      baton.curp[idx] = baton.buffer[idx];
 
1152
      baton.endp[idx] = baton.buffer[idx];
 
1153
 
 
1154
      if (baton.endp[idx])
 
1155
        baton.endp[idx] += size;
 
1156
    }
 
1157
 
 
1158
  SVN_ERR(svn_diff_output(diff, &baton,
 
1159
                          &svn_diff3__file_output_vtable));
 
1160
 
 
1161
  for (idx = 0; idx < 3; idx++)
 
1162
    {
 
1163
#if APR_HAS_MMAP
 
1164
      if (mm[idx])
 
1165
        {
 
1166
          apr_status_t rv = apr_mmap_delete(mm[idx]);
 
1167
          if (rv != APR_SUCCESS)
 
1168
            {
 
1169
              return svn_error_wrap_apr(rv, _("Failed to delete mmap '%s'"),
 
1170
                                        baton.path[idx]);
 
1171
            }
 
1172
        }
 
1173
#endif /* APR_HAS_MMAP */
 
1174
 
 
1175
      if (file[idx])
 
1176
        {
 
1177
          SVN_ERR(svn_io_file_close(file[idx], pool));
 
1178
        }
 
1179
    }
 
1180
 
 
1181
  return SVN_NO_ERROR;
 
1182
}