2
* diff_file.c : routines for doing diffs on files
4
* ====================================================================
5
* Copyright (c) 2000-2004 CollabNet. All rights reserved.
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.
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
* ====================================================================
21
#include <apr_pools.h>
22
#include <apr_general.h>
24
#include <apr_file_io.h>
25
#include <apr_file_info.h>
29
#include "svn_error.h"
31
#include "svn_types.h"
32
#include "svn_string.h"
34
#include "svn_pools.h"
36
#include "svn_private_config.h"
39
typedef struct svn_diff__file_token_t
41
struct svn_diff__file_token_t *next;
42
svn_diff_datasource_e datasource;
45
} svn_diff__file_token_t;
48
typedef struct svn_diff__file_baton_t
60
svn_diff__file_token_t *tokens;
63
} svn_diff__file_baton_t;
68
svn_diff__file_datasource_to_index(svn_diff_datasource_e datasource)
72
case svn_diff_datasource_original:
75
case svn_diff_datasource_modified:
78
case svn_diff_datasource_latest:
81
case svn_diff_datasource_ancestor:
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.
92
#define CHUNK_SHIFT 17
93
#define CHUNK_SIZE (1 << CHUNK_SHIFT)
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))
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.
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)
109
/* XXX: The final offset may not be the one we asked for.
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));
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.
125
#define MMAP_T_PARAM(NAME) apr_mmap_t **NAME,
126
#define MMAP_T_ARG(NAME) &(NAME),
128
#define MMAP_T_PARAM(NAME)
129
#define MMAP_T_ARG(NAME)
134
map_or_read_file(apr_file_t **file,
136
char **buffer, apr_off_t *size,
137
const char *path, apr_pool_t *pool)
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));
148
if (finfo.size > APR_MMAP_THRESHOLD)
150
rv = apr_mmap_create(mm, *file, 0, finfo.size, APR_MMAP_READ, pool);
151
if (rv == APR_SUCCESS)
156
/* On failure we just fall through and try reading the file into
160
#endif /* APR_HAS_MMAP */
162
if (*buffer == NULL && finfo.size > 0)
164
*buffer = apr_palloc(pool, finfo.size);
166
SVN_ERR(svn_io_file_read_full(*file, *buffer, finfo.size, NULL, pool));
168
/* Since we have the entire contents of the file we can
171
SVN_ERR(svn_io_file_close(*file, pool));
184
svn_diff__file_datasource_open(void *baton,
185
svn_diff_datasource_e datasource)
187
svn_diff__file_baton_t *file_baton = baton;
194
idx = svn_diff__file_datasource_to_index(datasource);
196
SVN_ERR(svn_io_file_open(&file_baton->file[idx], file_baton->path[idx],
197
APR_READ, APR_OS_DEFAULT, file_baton->pool));
199
SVN_ERR(svn_io_file_info_get(&finfo, APR_FINFO_SIZE,
200
file_baton->file[idx], file_baton->pool));
202
file_baton->size[idx] = finfo.size;
203
length = finfo.size > CHUNK_SIZE ? CHUNK_SIZE : finfo.size;
208
endp = curp = apr_palloc(file_baton->pool, length);
211
file_baton->buffer[idx] = file_baton->curp[idx] = curp;
212
file_baton->endp[idx] = endp;
214
SVN_ERR(read_chunk(file_baton->file[idx], file_baton->path[idx],
215
curp, length, 0, file_baton->pool));
223
svn_diff__file_datasource_close(void *baton,
224
svn_diff_datasource_e datasource)
226
/* Do nothing. The compare_token function needs previous datasources
227
* to stay available until all datasources are processed.
236
svn_diff__file_datasource_get_next_token(apr_uint32_t *hash, void **token,
238
svn_diff_datasource_e datasource)
240
svn_diff__file_baton_t *file_baton = baton;
241
svn_diff__file_token_t *file_token;
252
idx = svn_diff__file_datasource_to_index(datasource);
254
curp = file_baton->curp[idx];
255
endp = file_baton->endp[idx];
257
last_chunk = offset_to_chunk(file_baton->size[idx]);
260
&& last_chunk == file_baton->chunk[idx])
265
/* Get a new token */
266
file_token = file_baton->tokens;
269
file_baton->tokens = file_token->next;
273
file_token = apr_palloc(file_baton->pool, sizeof(*file_token));
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;
283
/* XXX: '\n' doesn't really cut it. We need to be able to detect
284
* XXX: '\n', '\r' and '\r\n'.
286
eol = memchr(curp, '\n', endp - curp);
293
if (file_baton->chunk[idx] == last_chunk)
299
length = endp - curp;
300
file_token->length += length;
301
h = svn_diff__adler32(h, curp, length);
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;
308
file_baton->endp[idx] = endp;
310
SVN_ERR(read_chunk(file_baton->file[idx], file_baton->path[idx],
312
chunk_to_offset(file_baton->chunk[idx]),
317
file_token->length += length;
318
*hash = svn_diff__adler32(h, curp, length);
320
file_baton->curp[idx] = eol;
326
#define COMPARE_CHUNK_SIZE 4096
330
svn_diff__file_token_compare(void *baton,
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];
343
apr_off_t total_length;
348
if (file_token1->length < file_token2->length)
354
if (file_token1->length > file_token2->length)
360
total_length = file_token1->length;
361
if (total_length == 0)
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]];
376
for (i = 0; i < 2; i++)
378
if (offset_to_chunk(offset[i]) == chunk[i])
380
/* If the start of the token is in memory, the entire token is
383
bufp[i] = file_baton->buffer[idx[i]];
384
bufp[i] += offset_in_chunk(offset[i]);
386
length[i] = total_length;
390
/* Read a chunk from disk into a buffer */
392
length[i] = total_length > COMPARE_CHUNK_SIZE ?
393
COMPARE_CHUNK_SIZE : total_length;
395
SVN_ERR(read_chunk(file_baton->file[idx[i]],
396
file_baton->path[idx[i]],
397
bufp[i], length[i], offset[i],
402
len = length[0] > length[1] ? length[1] : length[0];
406
/* Compare two chunks (that could be entire tokens if they both reside
409
*compare = memcmp(bufp[0], bufp[1], len);
415
while(total_length > 0);
424
svn_diff__file_token_discard(void *baton,
427
svn_diff__file_baton_t *file_baton = baton;
428
svn_diff__file_token_t *file_token = token;
430
file_token->next = file_baton->tokens;
431
file_baton->tokens = file_token;
437
svn_diff__file_token_discard_all(void *baton)
439
svn_diff__file_baton_t *file_baton = baton;
441
/* Discard all memory in use by the tokens, and close all open files. */
442
svn_pool_clear(file_baton->pool);
446
static const svn_diff_fns_t svn_diff__file_vtable =
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
457
svn_diff_file_diff(svn_diff_t **diff,
458
const char *original,
459
const char *modified,
462
svn_diff__file_baton_t baton;
464
memset(&baton, 0, sizeof(baton));
465
baton.path[0] = original;
466
baton.path[1] = modified;
467
baton.pool = svn_pool_create(pool);
469
SVN_ERR(svn_diff_diff(diff, &baton, &svn_diff__file_vtable, pool));
471
svn_pool_destroy(baton.pool);
476
svn_diff_file_diff3(svn_diff_t **diff,
477
const char *original,
478
const char *modified,
482
svn_diff__file_baton_t baton;
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);
490
SVN_ERR(svn_diff_diff3(diff, &baton, &svn_diff__file_vtable, pool));
492
svn_pool_destroy(baton.pool);
497
svn_diff_file_diff4(svn_diff_t **diff,
498
const char *original,
499
const char *modified,
501
const char *ancestor,
504
svn_diff__file_baton_t baton;
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);
513
SVN_ERR(svn_diff_diff4(diff, &baton, &svn_diff__file_vtable, pool));
515
svn_pool_destroy(baton.pool);
520
/** Display unified context diffs **/
522
#define SVN_DIFF__UNIFIED_CONTEXT_SIZE 3
524
typedef struct svn_diff__file_output_baton_t
526
svn_stream_t *output_stream;
531
apr_off_t current_line[2];
533
char buffer[2][4096];
534
apr_size_t length[2];
537
apr_off_t hunk_start[2];
538
apr_off_t hunk_length[2];
539
svn_stringbuf_t *hunk;
542
} svn_diff__file_output_baton_t;
544
typedef enum svn_diff__file_output_unified_type_e
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;
555
svn_diff__file_output_unified_line(svn_diff__file_output_baton_t *baton,
556
svn_diff__file_output_unified_type_e type,
563
svn_boolean_t bytes_processed = FALSE;
565
length = baton->length[idx];
566
curp = baton->curp[idx];
568
/* Lazily update the current line even if we're at EOF.
569
* This way we fake output of context at EOF
571
baton->current_line[idx]++;
573
if (length == 0 && apr_file_eof(baton->file[idx]))
582
if (!bytes_processed)
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]++;
591
case svn_diff__file_output_unified_delete:
592
svn_stringbuf_appendbytes(baton->hunk, "-", 1);
593
baton->hunk_length[0]++;
595
case svn_diff__file_output_unified_insert:
596
svn_stringbuf_appendbytes(baton->hunk, "+", 1);
597
baton->hunk_length[1]++;
604
/* XXX: '\n' doesn't really cut it. We need to be able to detect
605
* XXX: '\n', '\r' and '\r\n'.
607
eol = memchr(curp, '\n', length);
614
len = (apr_size_t)(eol - curp);
617
if (type != svn_diff__file_output_unified_skip)
619
svn_stringbuf_appendbytes(baton->hunk, curp, len);
622
baton->curp[idx] = eol;
623
baton->length[idx] = length;
630
if (type != svn_diff__file_output_unified_skip)
632
svn_stringbuf_appendbytes(baton->hunk, curp, length);
635
bytes_processed = TRUE;
638
curp = baton->buffer[idx];
639
length = sizeof(baton->buffer[idx]);
641
err = svn_io_file_read(baton->file[idx], curp, &length, baton->pool);
645
if (err && ! APR_STATUS_IS_EOF(err->apr_err))
648
if (err && APR_STATUS_IS_EOF(err->apr_err))
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))
655
svn_stringbuf_appendcstr(baton->hunk,
656
APR_EOL_STR "\\ No newline at end of file" APR_EOL_STR);
659
baton->length[idx] = 0;
667
svn_diff__file_output_unified_flush_hunk(svn_diff__file_output_baton_t *baton)
669
apr_off_t target_line;
673
if (svn_stringbuf_isempty(baton->hunk))
675
/* Nothing to flush */
679
target_line = baton->hunk_start[0] + baton->hunk_length[0]
680
+ SVN_DIFF__UNIFIED_CONTEXT_SIZE;
682
/* Add trailing context to the hunk */
683
while (baton->current_line[0] < target_line)
685
SVN_ERR(svn_diff__file_output_unified_line(baton,
686
svn_diff__file_output_unified_context, 0));
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++)
693
if (baton->hunk_length[i] > 0)
694
baton->hunk_start[i]++;
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
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)
706
SVN_ERR(svn_stream_printf(baton->output_stream, baton->pool,
708
baton->hunk_length[0]));
711
SVN_ERR(svn_stream_printf(baton->output_stream, baton->pool,
713
baton->hunk_start[1]));
714
if (baton->hunk_length[1] != 1)
716
SVN_ERR(svn_stream_printf(baton->output_stream, baton->pool,
718
baton->hunk_length[1]));
721
SVN_ERR(svn_stream_printf(baton->output_stream, baton->pool,
724
/* Output the hunk content */
725
hunk_len = baton->hunk->len;
726
SVN_ERR(svn_stream_write(baton->output_stream, baton->hunk->data,
729
/* Prepare for the next hunk */
730
baton->hunk_length[0] = 0;
731
baton->hunk_length[1] = 0;
732
svn_stringbuf_setempty(baton->hunk);
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)
744
svn_diff__file_output_baton_t *output_baton = baton;
745
apr_off_t target_line[2];
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;
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.
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))
761
SVN_ERR(svn_diff__file_output_unified_flush_hunk(output_baton));
763
output_baton->hunk_start[0] = target_line[0];
764
output_baton->hunk_start[1] = target_line[1] + target_line[0]
767
/* Skip lines until we are at the beginning of the context we want to
769
while (output_baton->current_line[0] < target_line[0])
771
SVN_ERR(svn_diff__file_output_unified_line(output_baton,
772
svn_diff__file_output_unified_skip, 0));
776
/* Skip lines until we are at the start of the changed range */
777
while (output_baton->current_line[1] < target_line[1])
779
SVN_ERR(svn_diff__file_output_unified_line(output_baton,
780
svn_diff__file_output_unified_skip, 1));
783
/* Output the context preceding the changed range */
784
while (output_baton->current_line[0] < original_start)
786
SVN_ERR(svn_diff__file_output_unified_line(output_baton,
787
svn_diff__file_output_unified_context, 0));
790
target_line[0] = original_start + original_length;
791
target_line[1] = modified_start + modified_length;
793
/* Output the changed range */
794
for (i = 0; i < 2; i++)
796
while (output_baton->current_line[i] < target_line[i])
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));
809
svn_diff__file_output_unified_default_hdr(apr_pool_t *pool,
812
apr_finfo_t file_info;
813
apr_time_exp_t exploded_time;
814
char time_buffer[64];
817
svn_io_stat(&file_info, path, APR_FINFO_MTIME, pool);
818
apr_time_exp_lt(&exploded_time, file_info.mtime);
820
apr_strftime(time_buffer, &time_len, sizeof(time_buffer) - 1,
821
"%a %b %e %H:%M:%S %Y", &exploded_time);
823
return apr_psprintf(pool, "%s\t%s", path, time_buffer);
826
static const svn_diff_output_fns_t svn_diff__file_output_unified_vtable =
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 */
836
svn_diff_file_output_unified(svn_stream_t *output_stream,
838
const char *original_path,
839
const char *modified_path,
840
const char *original_header,
841
const char *modified_header,
844
svn_diff__file_output_baton_t baton;
847
if (svn_diff_contains_diffs(diff))
849
memset(&baton, 0, sizeof(baton));
850
baton.output_stream = output_stream;
852
baton.path[0] = original_path;
853
baton.path[1] = modified_path;
854
baton.hunk = svn_stringbuf_create("", pool);
856
for (i = 0; i < 2; i++)
858
SVN_ERR(svn_io_file_open(&baton.file[i], baton.path[i],
859
APR_READ, APR_OS_DEFAULT, pool) );
862
if (original_header == NULL)
865
svn_diff__file_output_unified_default_hdr(pool, original_path);
868
if (modified_header == NULL)
871
svn_diff__file_output_unified_default_hdr(pool, modified_path);
874
SVN_ERR(svn_stream_printf(output_stream, pool,
876
"+++ %s" APR_EOL_STR,
877
original_header, modified_header));
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));
883
for (i = 0; i < 2; i++)
885
SVN_ERR(svn_io_file_close(baton.file[i], pool));
893
/** Display diff3 **/
895
typedef struct svn_diff3__file_output_baton_t
897
svn_stream_t *output_stream;
901
apr_off_t current_line[3];
907
const char *conflict_modified;
908
const char *conflict_original;
909
const char *conflict_separator;
910
const char *conflict_latest;
912
svn_boolean_t display_original_in_conflict;
913
svn_boolean_t display_resolved_conflicts;
916
} svn_diff3__file_output_baton_t;
918
typedef enum svn_diff3__file_output_type_e
920
svn_diff3__file_output_skip,
921
svn_diff3__file_output_normal
922
} svn_diff3__file_output_type_e;
927
svn_diff3__file_output_line(svn_diff3__file_output_baton_t *baton,
928
svn_diff3__file_output_type_e type,
936
curp = baton->curp[idx];
937
endp = baton->endp[idx];
939
/* Lazily update the current line even if we're at EOF.
941
baton->current_line[idx]++;
946
/* XXX: '\n' doesn't really cut it. We need to be able to detect
947
* XXX: '\n', '\r' and '\r\n'.
949
eol = memchr(curp, '\n', endp - curp);
955
if (type != svn_diff3__file_output_skip)
958
SVN_ERR(svn_stream_write(baton->output_stream, curp, &len));
961
baton->curp[idx] = eol;
968
svn_diff3__file_output_hunk(void *baton,
970
apr_off_t target_line, apr_off_t target_length)
972
svn_diff3__file_output_baton_t *output_baton = baton;
974
/* Skip lines until we are at the start of the changed range */
975
while (output_baton->current_line[idx] < target_line)
977
SVN_ERR(svn_diff3__file_output_line(output_baton,
978
svn_diff3__file_output_skip, idx));
981
target_line += target_length;
983
while (output_baton->current_line[idx] < target_line)
985
SVN_ERR(svn_diff3__file_output_line(output_baton,
986
svn_diff3__file_output_normal, idx));
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)
999
return svn_diff3__file_output_hunk(baton, 0,
1000
original_start, original_length);
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)
1010
return svn_diff3__file_output_hunk(baton, 1,
1011
modified_start, modified_length);
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)
1021
return svn_diff3__file_output_hunk(baton, 2,
1022
latest_start, latest_length);
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,
1033
static const svn_diff_output_fns_t svn_diff3__file_output_vtable =
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
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,
1050
svn_diff3__file_output_baton_t *file_baton = baton;
1053
if (diff && file_baton->display_resolved_conflicts)
1055
return svn_diff_output(diff, baton,
1056
&svn_diff3__file_output_vtable);
1059
len = strlen(file_baton->conflict_modified);
1060
SVN_ERR(svn_stream_write(file_baton->output_stream,
1061
file_baton->conflict_modified,
1063
len = sizeof(APR_EOL_STR) - 1;
1064
SVN_ERR(svn_stream_write(file_baton->output_stream,
1065
APR_EOL_STR, &len));
1067
SVN_ERR(svn_diff3__file_output_hunk(baton, 1,
1068
modified_start, modified_length));
1070
if (file_baton->display_original_in_conflict)
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));
1079
SVN_ERR(svn_diff3__file_output_hunk(baton, 0,
1080
original_start, original_length));
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));
1090
SVN_ERR(svn_diff3__file_output_hunk(baton, 2,
1091
latest_start, latest_length));
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));
1100
return SVN_NO_ERROR;
1104
svn_diff_file_output_merge(svn_stream_t *output_stream,
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,
1117
svn_diff3__file_output_baton_t baton;
1118
apr_file_t *file[3];
1122
apr_mmap_t *mm[3] = { 0 };
1123
#endif /* APR_HAS_MMAP */
1125
memset(&baton, 0, sizeof(baton));
1126
baton.output_stream = output_stream;
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
1137
baton.conflict_latest = conflict_latest ? conflict_latest
1138
: apr_psprintf(pool, ">>>>>>> %s", latest_path);
1140
baton.display_original_in_conflict = display_original_in_conflict;
1141
baton.display_resolved_conflicts = display_resolved_conflicts &&
1142
!display_original_in_conflict;
1144
for (idx = 0; idx < 3; idx++)
1146
SVN_ERR(map_or_read_file(&file[idx],
1148
&baton.buffer[idx], &size,
1149
baton.path[idx], pool));
1151
baton.curp[idx] = baton.buffer[idx];
1152
baton.endp[idx] = baton.buffer[idx];
1154
if (baton.endp[idx])
1155
baton.endp[idx] += size;
1158
SVN_ERR(svn_diff_output(diff, &baton,
1159
&svn_diff3__file_output_vtable));
1161
for (idx = 0; idx < 3; idx++)
1166
apr_status_t rv = apr_mmap_delete(mm[idx]);
1167
if (rv != APR_SUCCESS)
1169
return svn_error_wrap_apr(rv, _("Failed to delete mmap '%s'"),
1173
#endif /* APR_HAS_MMAP */
1177
SVN_ERR(svn_io_file_close(file[idx], pool));
1181
return SVN_NO_ERROR;