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