1 /*
2 * file-merge.c: internal file merge tool
3 *
4 * ====================================================================
5 * Licensed to the Apache Software Foundation (ASF) under one
6 * or more contributor license agreements. See the NOTICE file
7 * distributed with this work for additional information
8 * regarding copyright ownership. The ASF licenses this file
9 * to you under the Apache License, Version 2.0 (the
10 * "License"); you may not use this file except in compliance
11 * with the License. You may obtain a copy of the License at
12 *
13 * http://www.apache.org/licenses/LICENSE-2.0
14 *
15 * Unless required by applicable law or agreed to in writing,
16 * software distributed under the License is distributed on an
17 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
18 * KIND, either express or implied. See the License for the
19 * specific language governing permissions and limitations
20 * under the License.
21 * ====================================================================
22 */
23
24 /* This is an interactive file merge tool with an interface similar to
25 * the interactive mode of the UNIX sdiff ("side-by-side diff") utility.
26 * The merge tool is driven by Subversion's diff code and user input. */
27
28 #include "svn_cmdline.h"
29 #include "svn_dirent_uri.h"
30 #include "svn_error.h"
31 #include "svn_pools.h"
32 #include "svn_io.h"
33 #include "svn_utf.h"
34 #include "svn_xml.h"
35
36 #include "cl.h"
37
38 #include "svn_private_config.h"
39 #include "private/svn_utf_private.h"
40 #include "private/svn_cmdline_private.h"
41 #include "private/svn_dep_compat.h"
42
43 #if APR_HAVE_SYS_IOCTL_H
44 #include <sys/ioctl.h>
45 #endif
46
47 #if APR_HAVE_UNISTD_H
48 #include <unistd.h>
49 #endif
50
51 #include <fcntl.h>
52 #include <stdlib.h>
53
54 #if defined(HAVE_TERMIOS_H)
55 #include <termios.h>
56 #endif
57
58 /* Baton for functions in this file which implement svn_diff_output_fns_t. */
59 struct file_merge_baton {
60 /* The files being merged. */
61 apr_file_t *original_file;
62 apr_file_t *modified_file;
63 apr_file_t *latest_file;
64
65 /* Counters to keep track of the current line in each file. */
66 svn_linenum_t current_line_original;
67 svn_linenum_t current_line_modified;
68 svn_linenum_t current_line_latest;
69
70 /* The merge result is written to this file. */
71 apr_file_t *merged_file;
72
73 /* Whether the merged file remains in conflict after the merge. */
74 svn_boolean_t remains_in_conflict;
75
76 /* External editor command for editing chunks. */
77 const char *editor_cmd;
78
79 /* The client configuration hash. */
80 apr_hash_t *config;
81
82 /* Whether the merge should be aborted. */
83 svn_boolean_t abort_merge;
84
85 /* Pool for temporary allocations. */
86 apr_pool_t *scratch_pool;
87 };
88
89 /* Copy LEN lines from SOURCE_FILE to the MERGED_FILE, starting at
90 * line START. The CURRENT_LINE is the current line in the source file.
91 * The new current line is returned in *NEW_CURRENT_LINE. */
92 static svn_error_t *
copy_to_merged_file(svn_linenum_t * new_current_line,apr_file_t * merged_file,apr_file_t * source_file,apr_off_t start,apr_off_t len,svn_linenum_t current_line,apr_pool_t * scratch_pool)93 copy_to_merged_file(svn_linenum_t *new_current_line,
94 apr_file_t *merged_file,
95 apr_file_t *source_file,
96 apr_off_t start,
97 apr_off_t len,
98 svn_linenum_t current_line,
99 apr_pool_t *scratch_pool)
100 {
101 apr_pool_t *iterpool;
102 svn_stringbuf_t *line;
103 apr_size_t lines_read;
104 apr_size_t lines_copied;
105 svn_boolean_t eof;
106 svn_linenum_t orig_current_line = current_line;
107
108 lines_read = 0;
109 iterpool = svn_pool_create(scratch_pool);
110 while (current_line < start)
111 {
112 svn_pool_clear(iterpool);
113
114 SVN_ERR(svn_io_file_readline(source_file, &line, NULL, &eof,
115 APR_SIZE_MAX, iterpool, iterpool));
116 if (eof)
117 break;
118
119 current_line++;
120 lines_read++;
121 }
122
123 lines_copied = 0;
124 while (lines_copied < len)
125 {
126 apr_size_t bytes_written;
127 const char *eol_str;
128
129 svn_pool_clear(iterpool);
130
131 SVN_ERR(svn_io_file_readline(source_file, &line, &eol_str, &eof,
132 APR_SIZE_MAX, iterpool, iterpool));
133 if (eol_str)
134 svn_stringbuf_appendcstr(line, eol_str);
135 SVN_ERR(svn_io_file_write_full(merged_file, line->data, line->len,
136 &bytes_written, iterpool));
137 if (bytes_written != line->len)
138 return svn_error_create(SVN_ERR_IO_WRITE_ERROR, NULL,
139 _("Could not write data to merged file"));
140 if (eof)
141 break;
142 lines_copied++;
143 }
144 svn_pool_destroy(iterpool);
145
146 *new_current_line = orig_current_line + lines_read + lines_copied;
147
148 return SVN_NO_ERROR;
149 }
150
151 /* Copy common data to the merged file. */
152 static svn_error_t *
file_merge_output_common(void * output_baton,apr_off_t original_start,apr_off_t original_length,apr_off_t modified_start,apr_off_t modified_length,apr_off_t latest_start,apr_off_t latest_length)153 file_merge_output_common(void *output_baton,
154 apr_off_t original_start,
155 apr_off_t original_length,
156 apr_off_t modified_start,
157 apr_off_t modified_length,
158 apr_off_t latest_start,
159 apr_off_t latest_length)
160 {
161 struct file_merge_baton *b = output_baton;
162
163 if (b->abort_merge)
164 return SVN_NO_ERROR;
165
166 SVN_ERR(copy_to_merged_file(&b->current_line_original,
167 b->merged_file,
168 b->original_file,
169 original_start,
170 original_length,
171 b->current_line_original,
172 b->scratch_pool));
173 return SVN_NO_ERROR;
174 }
175
176 /* Original/latest match up, but modified differs.
177 * Copy modified data to the merged file. */
178 static svn_error_t *
file_merge_output_diff_modified(void * output_baton,apr_off_t original_start,apr_off_t original_length,apr_off_t modified_start,apr_off_t modified_length,apr_off_t latest_start,apr_off_t latest_length)179 file_merge_output_diff_modified(void *output_baton,
180 apr_off_t original_start,
181 apr_off_t original_length,
182 apr_off_t modified_start,
183 apr_off_t modified_length,
184 apr_off_t latest_start,
185 apr_off_t latest_length)
186 {
187 struct file_merge_baton *b = output_baton;
188
189 if (b->abort_merge)
190 return SVN_NO_ERROR;
191
192 SVN_ERR(copy_to_merged_file(&b->current_line_modified,
193 b->merged_file,
194 b->modified_file,
195 modified_start,
196 modified_length,
197 b->current_line_modified,
198 b->scratch_pool));
199
200 return SVN_NO_ERROR;
201 }
202
203 /* Original/modified match up, but latest differs.
204 * Copy latest data to the merged file. */
205 static svn_error_t *
file_merge_output_diff_latest(void * output_baton,apr_off_t original_start,apr_off_t original_length,apr_off_t modified_start,apr_off_t modified_length,apr_off_t latest_start,apr_off_t latest_length)206 file_merge_output_diff_latest(void *output_baton,
207 apr_off_t original_start,
208 apr_off_t original_length,
209 apr_off_t modified_start,
210 apr_off_t modified_length,
211 apr_off_t latest_start,
212 apr_off_t latest_length)
213 {
214 struct file_merge_baton *b = output_baton;
215
216 if (b->abort_merge)
217 return SVN_NO_ERROR;
218
219 SVN_ERR(copy_to_merged_file(&b->current_line_latest,
220 b->merged_file,
221 b->latest_file,
222 latest_start,
223 latest_length,
224 b->current_line_latest,
225 b->scratch_pool));
226
227 return SVN_NO_ERROR;
228 }
229
230 /* Modified/latest match up, but original differs.
231 * Copy latest data to the merged file. */
232 static svn_error_t *
file_merge_output_diff_common(void * output_baton,apr_off_t original_start,apr_off_t original_length,apr_off_t modified_start,apr_off_t modified_length,apr_off_t latest_start,apr_off_t latest_length)233 file_merge_output_diff_common(void *output_baton,
234 apr_off_t original_start,
235 apr_off_t original_length,
236 apr_off_t modified_start,
237 apr_off_t modified_length,
238 apr_off_t latest_start,
239 apr_off_t latest_length)
240 {
241 struct file_merge_baton *b = output_baton;
242
243 if (b->abort_merge)
244 return SVN_NO_ERROR;
245
246 SVN_ERR(copy_to_merged_file(&b->current_line_latest,
247 b->merged_file,
248 b->latest_file,
249 latest_start,
250 latest_length,
251 b->current_line_latest,
252 b->scratch_pool));
253 return SVN_NO_ERROR;
254 }
255
256
257 /* Return LEN lines within the diff chunk staring at line START
258 * in a *LINES array of svn_stringbuf_t* elements.
259 * Store the resulting current in in *NEW_CURRENT_LINE. */
260 static svn_error_t *
read_diff_chunk(apr_array_header_t ** lines,svn_linenum_t * new_current_line,apr_file_t * file,svn_linenum_t current_line,apr_off_t start,apr_off_t len,apr_pool_t * result_pool,apr_pool_t * scratch_pool)261 read_diff_chunk(apr_array_header_t **lines,
262 svn_linenum_t *new_current_line,
263 apr_file_t *file,
264 svn_linenum_t current_line,
265 apr_off_t start,
266 apr_off_t len,
267 apr_pool_t *result_pool,
268 apr_pool_t *scratch_pool)
269 {
270 svn_stringbuf_t *line;
271 const char *eol_str;
272 svn_boolean_t eof;
273 apr_pool_t *iterpool;
274
275 *lines = apr_array_make(result_pool, 0, sizeof(svn_stringbuf_t *));
276
277 /* Skip lines before start of range. */
278 iterpool = svn_pool_create(scratch_pool);
279 while (current_line < start)
280 {
281 svn_pool_clear(iterpool);
282 SVN_ERR(svn_io_file_readline(file, &line, NULL, &eof, APR_SIZE_MAX,
283 iterpool, iterpool));
284 if (eof)
285 return SVN_NO_ERROR;
286 current_line++;
287 }
288 svn_pool_destroy(iterpool);
289
290 /* Now read the lines. */
291 do
292 {
293 SVN_ERR(svn_io_file_readline(file, &line, &eol_str, &eof, APR_SIZE_MAX,
294 result_pool, scratch_pool));
295 if (eol_str)
296 svn_stringbuf_appendcstr(line, eol_str);
297 APR_ARRAY_PUSH(*lines, svn_stringbuf_t *) = line;
298 if (eof)
299 break;
300 current_line++;
301 }
302 while ((*lines)->nelts < len);
303
304 *new_current_line = current_line;
305
306 return SVN_NO_ERROR;
307 }
308
309 /* Return the terminal width in number of columns. */
310 static int
get_term_width(void)311 get_term_width(void)
312 {
313 char *columns_env;
314 #ifdef TIOCGWINSZ
315 int fd;
316
317 fd = open("/dev/tty", O_RDONLY, 0);
318 if (fd != -1)
319 {
320 struct winsize ws;
321 int error;
322
323 error = ioctl(fd, TIOCGWINSZ, &ws);
324 close(fd);
325 if (error != -1)
326 {
327 if (ws.ws_col < 80)
328 return 80;
329 return ws.ws_col;
330 }
331 }
332 #elif defined WIN32
333 CONSOLE_SCREEN_BUFFER_INFO csbi;
334
335 if (GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &csbi))
336 {
337 if (csbi.dwSize.X < 80)
338 return 80;
339 return csbi.dwSize.X;
340 }
341 #endif
342
343 columns_env = getenv("COLUMNS");
344 if (columns_env)
345 {
346 svn_error_t *err;
347 int cols;
348
349 err = svn_cstring_atoi(&cols, columns_env);
350 if (err)
351 {
352 svn_error_clear(err);
353 return 80;
354 }
355
356 if (cols < 80)
357 return 80;
358 return cols;
359 }
360 else
361 return 80;
362 }
363
364 #define LINE_DISPLAY_WIDTH ((get_term_width() / 2) - 2)
365
366 /* Prepare LINE for display, pruning or extending it to an appropriate
367 * display width, and stripping the EOL marker, if any.
368 * This function assumes that the data in LINE is encoded in UTF-8. */
369 static const char *
prepare_line_for_display(const char * line,apr_pool_t * pool)370 prepare_line_for_display(const char *line, apr_pool_t *pool)
371 {
372 svn_stringbuf_t *buf = svn_stringbuf_create(line, pool);
373 size_t width;
374 size_t line_width = LINE_DISPLAY_WIDTH;
375 apr_pool_t *iterpool;
376
377 /* Trim EOL. */
378 if (buf->len >= 2 &&
379 buf->data[buf->len - 2] == '\r' &&
380 buf->data[buf->len - 1] == '\n')
381 svn_stringbuf_chop(buf, 2);
382 else if (buf->len >= 1 &&
383 (buf->data[buf->len - 1] == '\n' ||
384 buf->data[buf->len - 1] == '\r'))
385 svn_stringbuf_chop(buf, 1);
386
387 /* Determine the on-screen width of the line. */
388 width = svn_utf_cstring_utf8_width(buf->data);
389 if (width == -1)
390 {
391 /* Determining the width failed. Try to get rid of unprintable
392 * characters in the line buffer. */
393 buf = svn_stringbuf_create(svn_xml_fuzzy_escape(buf->data, pool), pool);
394 width = svn_utf_cstring_utf8_width(buf->data);
395 if (width == -1)
396 width = buf->len; /* fallback: buffer length */
397 }
398
399 /* Trim further in case line is still too long, or add padding in case
400 * it is too short. */
401 iterpool = svn_pool_create(pool);
402 while (width > line_width)
403 {
404 const char *last_valid;
405
406 svn_pool_clear(iterpool);
407
408 svn_stringbuf_chop(buf, 1);
409
410 /* Be careful not to invalidate the UTF-8 string by trimming
411 * just part of a character. */
412 last_valid = svn_utf__last_valid(buf->data, buf->len);
413 if (last_valid < buf->data + buf->len)
414 svn_stringbuf_chop(buf, (buf->data + buf->len) - last_valid);
415
416 width = svn_utf_cstring_utf8_width(buf->data);
417 if (width == -1)
418 width = buf->len; /* fallback: buffer length */
419 }
420 svn_pool_destroy(iterpool);
421
422 while (width == 0 || width < line_width)
423 {
424 svn_stringbuf_appendbyte(buf, ' ');
425 width++;
426 }
427
428 SVN_ERR_ASSERT_NO_RETURN(width == line_width);
429 return buf->data;
430 }
431
432 /* Merge CHUNK1 and CHUNK2 into a new chunk with conflict markers. */
433 static apr_array_header_t *
merge_chunks_with_conflict_markers(apr_array_header_t * chunk1,apr_array_header_t * chunk2,apr_pool_t * result_pool)434 merge_chunks_with_conflict_markers(apr_array_header_t *chunk1,
435 apr_array_header_t *chunk2,
436 apr_pool_t *result_pool)
437 {
438 apr_array_header_t *merged_chunk;
439 int i;
440
441 merged_chunk = apr_array_make(result_pool, 0, sizeof(svn_stringbuf_t *));
442 /* ### would be nice to show filenames next to conflict markers */
443 APR_ARRAY_PUSH(merged_chunk, svn_stringbuf_t *) =
444 svn_stringbuf_create("<<<<<<<\n", result_pool);
445 for (i = 0; i < chunk1->nelts; i++)
446 {
447 APR_ARRAY_PUSH(merged_chunk, svn_stringbuf_t *) =
448 APR_ARRAY_IDX(chunk1, i, svn_stringbuf_t*);
449 }
450 APR_ARRAY_PUSH(merged_chunk, svn_stringbuf_t *) =
451 svn_stringbuf_create("=======\n", result_pool);
452 for (i = 0; i < chunk2->nelts; i++)
453 {
454 APR_ARRAY_PUSH(merged_chunk, svn_stringbuf_t *) =
455 APR_ARRAY_IDX(chunk2, i, svn_stringbuf_t*);
456 }
457 APR_ARRAY_PUSH(merged_chunk, svn_stringbuf_t *) =
458 svn_stringbuf_create(">>>>>>>\n", result_pool);
459
460 return merged_chunk;
461 }
462
463 /* Edit CHUNK and return the result in *MERGED_CHUNK allocated in POOL. */
464 static svn_error_t *
edit_chunk(apr_array_header_t ** merged_chunk,apr_array_header_t * chunk,const char * editor_cmd,apr_hash_t * config,apr_pool_t * result_pool,apr_pool_t * scratch_pool)465 edit_chunk(apr_array_header_t **merged_chunk,
466 apr_array_header_t *chunk,
467 const char *editor_cmd,
468 apr_hash_t *config,
469 apr_pool_t *result_pool,
470 apr_pool_t *scratch_pool)
471 {
472 apr_file_t *temp_file;
473 const char *temp_file_name;
474 int i;
475 apr_off_t pos;
476 svn_boolean_t eof;
477 svn_error_t *err;
478 apr_pool_t *iterpool;
479
480 SVN_ERR(svn_io_open_unique_file3(&temp_file, &temp_file_name, NULL,
481 svn_io_file_del_on_pool_cleanup,
482 scratch_pool, scratch_pool));
483 iterpool = svn_pool_create(scratch_pool);
484 for (i = 0; i < chunk->nelts; i++)
485 {
486 svn_stringbuf_t *line = APR_ARRAY_IDX(chunk, i, svn_stringbuf_t *);
487 apr_size_t bytes_written;
488
489 svn_pool_clear(iterpool);
490
491 SVN_ERR(svn_io_file_write_full(temp_file, line->data, line->len,
492 &bytes_written, iterpool));
493 if (line->len != bytes_written)
494 return svn_error_create(SVN_ERR_IO_WRITE_ERROR, NULL,
495 _("Could not write data to temporary file"));
496 }
497 SVN_ERR(svn_io_file_flush(temp_file, scratch_pool));
498
499 err = svn_cmdline__edit_file_externally(temp_file_name, editor_cmd,
500 config, scratch_pool);
501 if (err && (err->apr_err == SVN_ERR_CL_NO_EXTERNAL_EDITOR))
502 {
503 svn_error_t *root_err = svn_error_root_cause(err);
504
505 SVN_ERR(svn_cmdline_fprintf(stderr, scratch_pool, "%s\n",
506 root_err->message ? root_err->message :
507 _("No editor found.")));
508 svn_error_clear(err);
509 *merged_chunk = NULL;
510 svn_pool_destroy(iterpool);
511 return SVN_NO_ERROR;
512 }
513 else if (err && (err->apr_err == SVN_ERR_EXTERNAL_PROGRAM))
514 {
515 svn_error_t *root_err = svn_error_root_cause(err);
516
517 SVN_ERR(svn_cmdline_fprintf(stderr, scratch_pool, "%s\n",
518 root_err->message ? root_err->message :
519 _("Error running editor.")));
520 svn_error_clear(err);
521 *merged_chunk = NULL;
522 svn_pool_destroy(iterpool);
523 return SVN_NO_ERROR;
524 }
525 else if (err)
526 return svn_error_trace(err);
527
528 *merged_chunk = apr_array_make(result_pool, 1, sizeof(svn_stringbuf_t *));
529 pos = 0;
530 SVN_ERR(svn_io_file_seek(temp_file, APR_SET, &pos, scratch_pool));
531 do
532 {
533 svn_stringbuf_t *line;
534 const char *eol_str;
535
536 svn_pool_clear(iterpool);
537
538 SVN_ERR(svn_io_file_readline(temp_file, &line, &eol_str, &eof,
539 APR_SIZE_MAX, result_pool, iterpool));
540 if (eol_str)
541 svn_stringbuf_appendcstr(line, eol_str);
542
543 APR_ARRAY_PUSH(*merged_chunk, svn_stringbuf_t *) = line;
544 }
545 while (!eof);
546 svn_pool_destroy(iterpool);
547
548 SVN_ERR(svn_io_file_close(temp_file, scratch_pool));
549
550 return SVN_NO_ERROR;
551 }
552
553 /* Create a separator string of the appropriate length. */
554 static const char *
get_sep_string(apr_pool_t * result_pool)555 get_sep_string(apr_pool_t *result_pool)
556 {
557 int line_width = LINE_DISPLAY_WIDTH;
558 int i;
559 svn_stringbuf_t *buf;
560
561 buf = svn_stringbuf_create_empty(result_pool);
562 for (i = 0; i < line_width; i++)
563 svn_stringbuf_appendbyte(buf, '-');
564 svn_stringbuf_appendbyte(buf, '+');
565 for (i = 0; i < line_width; i++)
566 svn_stringbuf_appendbyte(buf, '-');
567 svn_stringbuf_appendbyte(buf, '\n');
568
569 return buf->data;
570 }
571
572 /* Merge chunks CHUNK1 and CHUNK2.
573 * Each lines array contains elements of type svn_stringbuf_t*.
574 * Return the result in *MERGED_CHUNK, or set *MERGED_CHUNK to NULL in
575 * case the user chooses to postpone resolution of this chunk.
576 * If the user wants to abort the merge, set *ABORT_MERGE to TRUE. */
577 static svn_error_t *
merge_chunks(apr_array_header_t ** merged_chunk,svn_boolean_t * abort_merge,apr_array_header_t * chunk1,apr_array_header_t * chunk2,svn_linenum_t current_line1,svn_linenum_t current_line2,const char * editor_cmd,apr_hash_t * config,apr_pool_t * result_pool,apr_pool_t * scratch_pool)578 merge_chunks(apr_array_header_t **merged_chunk,
579 svn_boolean_t *abort_merge,
580 apr_array_header_t *chunk1,
581 apr_array_header_t *chunk2,
582 svn_linenum_t current_line1,
583 svn_linenum_t current_line2,
584 const char *editor_cmd,
585 apr_hash_t *config,
586 apr_pool_t *result_pool,
587 apr_pool_t *scratch_pool)
588 {
589 svn_stringbuf_t *prompt;
590 int i;
591 int max_chunk_lines;
592 apr_pool_t *iterpool;
593
594 max_chunk_lines = chunk1->nelts > chunk2->nelts ? chunk1->nelts
595 : chunk2->nelts;
596 *abort_merge = FALSE;
597
598 /*
599 * Prepare the selection prompt.
600 */
601
602 prompt = svn_stringbuf_create(
603 apr_psprintf(scratch_pool, "%s\n%s|%s\n%s",
604 _("Conflicting section found during merge:"),
605 prepare_line_for_display(
606 apr_psprintf(scratch_pool,
607 _("(1) their version (at line %lu)"),
608 current_line1),
609 scratch_pool),
610 prepare_line_for_display(
611 apr_psprintf(scratch_pool,
612 _("(2) your version (at line %lu)"),
613 current_line2),
614 scratch_pool),
615 get_sep_string(scratch_pool)),
616 scratch_pool);
617
618 iterpool = svn_pool_create(scratch_pool);
619 for (i = 0; i < max_chunk_lines; i++)
620 {
621 const char *line1;
622 const char *line2;
623 const char *prompt_line;
624
625 svn_pool_clear(iterpool);
626
627 if (i < chunk1->nelts)
628 {
629 svn_stringbuf_t *line_utf8;
630
631 SVN_ERR(svn_utf_stringbuf_to_utf8(&line_utf8,
632 APR_ARRAY_IDX(chunk1, i,
633 svn_stringbuf_t*),
634 iterpool));
635 line1 = prepare_line_for_display(line_utf8->data, iterpool);
636 }
637 else
638 line1 = prepare_line_for_display("", iterpool);
639
640 if (i < chunk2->nelts)
641 {
642 svn_stringbuf_t *line_utf8;
643
644 SVN_ERR(svn_utf_stringbuf_to_utf8(&line_utf8,
645 APR_ARRAY_IDX(chunk2, i,
646 svn_stringbuf_t*),
647 iterpool));
648 line2 = prepare_line_for_display(line_utf8->data, iterpool);
649 }
650 else
651 line2 = prepare_line_for_display("", iterpool);
652
653 prompt_line = apr_psprintf(iterpool, "%s|%s\n", line1, line2);
654
655 svn_stringbuf_appendcstr(prompt, prompt_line);
656 }
657
658 svn_stringbuf_appendcstr(prompt, get_sep_string(scratch_pool));
659 svn_stringbuf_appendcstr(
660 prompt,
661 _("Select: (1) use their version, (2) use your version,\n"
662 " (12) their version first, then yours,\n"
663 " (21) your version first, then theirs,\n"
664 " (e1) edit their version and use the result,\n"
665 " (e2) edit your version and use the result,\n"
666 " (eb) edit both versions and use the result,\n"
667 " (p) postpone this conflicting section leaving conflict markers,\n"
668 " (a) abort file merge and return to main menu: "));
669
670 /* Now let's see what the user wants to do with this conflict. */
671 while (TRUE)
672 {
673 const char *answer;
674
675 svn_pool_clear(iterpool);
676
677 SVN_ERR(svn_cmdline_prompt_user2(&answer, prompt->data, NULL, iterpool));
678 if (strcmp(answer, "1") == 0)
679 {
680 *merged_chunk = chunk1;
681 break;
682 }
683 else if (strcmp(answer, "2") == 0)
684 {
685 *merged_chunk = chunk2;
686 break;
687 }
688 if (strcmp(answer, "12") == 0)
689 {
690 *merged_chunk = apr_array_make(result_pool,
691 chunk1->nelts + chunk2->nelts,
692 sizeof(svn_stringbuf_t *));
693 apr_array_cat(*merged_chunk, chunk1);
694 apr_array_cat(*merged_chunk, chunk2);
695 break;
696 }
697 if (strcmp(answer, "21") == 0)
698 {
699 *merged_chunk = apr_array_make(result_pool,
700 chunk1->nelts + chunk2->nelts,
701 sizeof(svn_stringbuf_t *));
702 apr_array_cat(*merged_chunk, chunk2);
703 apr_array_cat(*merged_chunk, chunk1);
704 break;
705 }
706 else if (strcmp(answer, "p") == 0)
707 {
708 *merged_chunk = NULL;
709 break;
710 }
711 else if (strcmp(answer, "e1") == 0)
712 {
713 SVN_ERR(edit_chunk(merged_chunk, chunk1, editor_cmd, config,
714 result_pool, iterpool));
715 if (*merged_chunk)
716 break;
717 }
718 else if (strcmp(answer, "e2") == 0)
719 {
720 SVN_ERR(edit_chunk(merged_chunk, chunk2, editor_cmd, config,
721 result_pool, iterpool));
722 if (*merged_chunk)
723 break;
724 }
725 else if (strcmp(answer, "eb") == 0)
726 {
727 apr_array_header_t *conflict_chunk;
728
729 conflict_chunk = merge_chunks_with_conflict_markers(chunk1, chunk2,
730 scratch_pool);
731 SVN_ERR(edit_chunk(merged_chunk, conflict_chunk, editor_cmd, config,
732 result_pool, iterpool));
733 if (*merged_chunk)
734 break;
735 }
736 else if (strcmp(answer, "a") == 0)
737 {
738 *abort_merge = TRUE;
739 break;
740 }
741 }
742 svn_pool_destroy(iterpool);
743
744 return SVN_NO_ERROR;
745 }
746
747 /* Perform a merge of chunks from FILE1 and FILE2, specified by START1/LEN1
748 * and START2/LEN2, respectively. Append the result to MERGED_FILE.
749 * The current line numbers for FILE1 and FILE2 are passed in *CURRENT_LINE1
750 * and *CURRENT_LINE2, and will be updated to new values upon return.
751 * If the user wants to abort the merge, set *ABORT_MERGE to TRUE. */
752 static svn_error_t *
merge_file_chunks(svn_boolean_t * remains_in_conflict,svn_boolean_t * abort_merge,apr_file_t * merged_file,apr_file_t * file1,apr_file_t * file2,apr_off_t start1,apr_off_t len1,apr_off_t start2,apr_off_t len2,svn_linenum_t * current_line1,svn_linenum_t * current_line2,const char * editor_cmd,apr_hash_t * config,apr_pool_t * scratch_pool)753 merge_file_chunks(svn_boolean_t *remains_in_conflict,
754 svn_boolean_t *abort_merge,
755 apr_file_t *merged_file,
756 apr_file_t *file1,
757 apr_file_t *file2,
758 apr_off_t start1,
759 apr_off_t len1,
760 apr_off_t start2,
761 apr_off_t len2,
762 svn_linenum_t *current_line1,
763 svn_linenum_t *current_line2,
764 const char *editor_cmd,
765 apr_hash_t *config,
766 apr_pool_t *scratch_pool)
767 {
768 apr_array_header_t *chunk1;
769 apr_array_header_t *chunk2;
770 apr_array_header_t *merged_chunk;
771 apr_pool_t *iterpool;
772 int i;
773
774 SVN_ERR(read_diff_chunk(&chunk1, current_line1, file1, *current_line1,
775 start1, len1, scratch_pool, scratch_pool));
776 SVN_ERR(read_diff_chunk(&chunk2, current_line2, file2, *current_line2,
777 start2, len2, scratch_pool, scratch_pool));
778
779 SVN_ERR(merge_chunks(&merged_chunk, abort_merge, chunk1, chunk2,
780 *current_line1, *current_line2,
781 editor_cmd, config,
782 scratch_pool, scratch_pool));
783
784 if (*abort_merge)
785 return SVN_NO_ERROR;
786
787 /* If the user chose 'postpone' put conflict markers and left/right
788 * versions into the merged file. */
789 if (merged_chunk == NULL)
790 {
791 *remains_in_conflict = TRUE;
792 merged_chunk = merge_chunks_with_conflict_markers(chunk1, chunk2,
793 scratch_pool);
794 }
795
796 iterpool = svn_pool_create(scratch_pool);
797 for (i = 0; i < merged_chunk->nelts; i++)
798 {
799 apr_size_t bytes_written;
800 svn_stringbuf_t *line = APR_ARRAY_IDX(merged_chunk, i,
801 svn_stringbuf_t *);
802
803 svn_pool_clear(iterpool);
804
805 SVN_ERR(svn_io_file_write_full(merged_file, line->data, line->len,
806 &bytes_written, iterpool));
807 if (line->len != bytes_written)
808 return svn_error_create(SVN_ERR_IO_WRITE_ERROR, NULL,
809 _("Could not write data to merged file"));
810 }
811 svn_pool_destroy(iterpool);
812
813 return SVN_NO_ERROR;
814 }
815
816 /* Original, modified, and latest all differ from one another.
817 * This is a conflict and we'll need to ask the user to merge it. */
818 static svn_error_t *
file_merge_output_conflict(void * output_baton,apr_off_t original_start,apr_off_t original_length,apr_off_t modified_start,apr_off_t modified_length,apr_off_t latest_start,apr_off_t latest_length,svn_diff_t * resolved_diff)819 file_merge_output_conflict(void *output_baton,
820 apr_off_t original_start,
821 apr_off_t original_length,
822 apr_off_t modified_start,
823 apr_off_t modified_length,
824 apr_off_t latest_start,
825 apr_off_t latest_length,
826 svn_diff_t *resolved_diff)
827 {
828 struct file_merge_baton *b = output_baton;
829
830 if (b->abort_merge)
831 return SVN_NO_ERROR;
832
833 SVN_ERR(merge_file_chunks(&b->remains_in_conflict,
834 &b->abort_merge,
835 b->merged_file,
836 b->modified_file,
837 b->latest_file,
838 modified_start,
839 modified_length,
840 latest_start,
841 latest_length,
842 &b->current_line_modified,
843 &b->current_line_latest,
844 b->editor_cmd,
845 b->config,
846 b->scratch_pool));
847 return SVN_NO_ERROR;
848 }
849
850 /* Our collection of diff output functions that get driven during the merge. */
851 static svn_diff_output_fns_t file_merge_diff_output_fns = {
852 file_merge_output_common,
853 file_merge_output_diff_modified,
854 file_merge_output_diff_latest,
855 file_merge_output_diff_common,
856 file_merge_output_conflict
857 };
858
859 svn_error_t *
svn_cl__merge_file(svn_boolean_t * remains_in_conflict,const char * base_path,const char * their_path,const char * my_path,const char * merged_path,const char * wc_path,const char * path_prefix,const char * editor_cmd,apr_hash_t * config,svn_cancel_func_t cancel_func,void * cancel_baton,apr_pool_t * scratch_pool)860 svn_cl__merge_file(svn_boolean_t *remains_in_conflict,
861 const char *base_path,
862 const char *their_path,
863 const char *my_path,
864 const char *merged_path,
865 const char *wc_path,
866 const char *path_prefix,
867 const char *editor_cmd,
868 apr_hash_t *config,
869 svn_cancel_func_t cancel_func,
870 void *cancel_baton,
871 apr_pool_t *scratch_pool)
872 {
873 svn_diff_t *diff;
874 svn_diff_file_options_t *diff_options;
875 apr_file_t *original_file;
876 apr_file_t *modified_file;
877 apr_file_t *latest_file;
878 apr_file_t *merged_file;
879 const char *merged_file_name;
880 struct file_merge_baton fmb;
881 svn_boolean_t executable;
882 const char *merged_path_local_style;
883 const char *merged_rel_path;
884 const char *wc_path_local_style;
885 const char *wc_rel_path = svn_dirent_skip_ancestor(path_prefix, wc_path);
886
887 /* PATH_PREFIX may not be an ancestor of WC_PATH, just use the
888 full WC_PATH in that case. */
889 if (wc_rel_path)
890 wc_path_local_style = svn_dirent_local_style(wc_rel_path, scratch_pool);
891 else
892 wc_path_local_style = svn_dirent_local_style(wc_path, scratch_pool);
893
894 SVN_ERR(svn_cmdline_printf(scratch_pool, _("Merging '%s'.\n"),
895 wc_path_local_style));
896
897 SVN_ERR(svn_io_file_open(&original_file, base_path,
898 APR_READ | APR_BUFFERED,
899 APR_OS_DEFAULT, scratch_pool));
900 SVN_ERR(svn_io_file_open(&modified_file, their_path,
901 APR_READ | APR_BUFFERED,
902 APR_OS_DEFAULT, scratch_pool));
903 SVN_ERR(svn_io_file_open(&latest_file, my_path,
904 APR_READ | APR_BUFFERED,
905 APR_OS_DEFAULT, scratch_pool));
906 SVN_ERR(svn_io_open_unique_file3(&merged_file, &merged_file_name,
907 NULL, svn_io_file_del_none,
908 scratch_pool, scratch_pool));
909
910 diff_options = svn_diff_file_options_create(scratch_pool);
911 SVN_ERR(svn_diff_file_diff3_2(&diff, base_path, their_path, my_path,
912 diff_options, scratch_pool));
913
914 fmb.original_file = original_file;
915 fmb.modified_file = modified_file;
916 fmb.latest_file = latest_file;
917 fmb.current_line_original = 0;
918 fmb.current_line_modified = 0;
919 fmb.current_line_latest = 0;
920 fmb.merged_file = merged_file;
921 fmb.remains_in_conflict = FALSE;
922 fmb.editor_cmd = editor_cmd;
923 fmb.config = config;
924 fmb.abort_merge = FALSE;
925 fmb.scratch_pool = scratch_pool;
926
927 SVN_ERR(svn_diff_output2(diff, &fmb, &file_merge_diff_output_fns,
928 cancel_func, cancel_baton));
929
930 SVN_ERR(svn_io_file_close(original_file, scratch_pool));
931 SVN_ERR(svn_io_file_close(modified_file, scratch_pool));
932 SVN_ERR(svn_io_file_close(latest_file, scratch_pool));
933 SVN_ERR(svn_io_file_close(merged_file, scratch_pool));
934
935 /* Start out assuming that conflicts remain. */
936 if (remains_in_conflict)
937 *remains_in_conflict = TRUE;
938
939 if (fmb.abort_merge)
940 {
941 SVN_ERR(svn_io_remove_file2(merged_file_name, TRUE, scratch_pool));
942 SVN_ERR(svn_cmdline_printf(scratch_pool, _("Merge of '%s' aborted.\n"),
943 wc_path_local_style));
944 return SVN_NO_ERROR;
945 }
946
947 SVN_ERR(svn_io_is_file_executable(&executable, merged_path, scratch_pool));
948
949 merged_rel_path = svn_dirent_skip_ancestor(path_prefix, merged_path);
950 if (merged_rel_path)
951 merged_path_local_style = svn_dirent_local_style(merged_rel_path,
952 scratch_pool);
953 else
954 merged_path_local_style = svn_dirent_local_style(merged_path,
955 scratch_pool);
956
957 SVN_ERR_W(svn_io_copy_file(merged_file_name, merged_path, FALSE,
958 scratch_pool),
959 apr_psprintf(scratch_pool,
960 _("Could not write merged result to '%s', saved "
961 "instead at '%s'.\n'%s' remains in conflict.\n"),
962 merged_path_local_style,
963 svn_dirent_local_style(merged_file_name,
964 scratch_pool),
965 wc_path_local_style));
966 SVN_ERR(svn_io_set_file_executable(merged_path, executable, FALSE,
967 scratch_pool));
968 SVN_ERR(svn_io_remove_file2(merged_file_name, TRUE, scratch_pool));
969
970 /* The merge was not aborted and we could install the merged result. The
971 * file remains in conflict unless all conflicting sections were resolved. */
972 if (remains_in_conflict)
973 *remains_in_conflict = fmb.remains_in_conflict;
974
975 if (fmb.remains_in_conflict)
976 SVN_ERR(svn_cmdline_printf(
977 scratch_pool,
978 _("Merge of '%s' completed (remains in conflict).\n"),
979 wc_path_local_style));
980 else
981 SVN_ERR(svn_cmdline_printf(
982 scratch_pool, _("Merge of '%s' completed.\n"),
983 wc_path_local_style));
984
985 return SVN_NO_ERROR;
986 }
987