1251881Speter/*
2251881Speter * file-merge.c: internal file merge tool
3251881Speter *
4251881Speter * ====================================================================
5251881Speter *    Licensed to the Apache Software Foundation (ASF) under one
6251881Speter *    or more contributor license agreements.  See the NOTICE file
7251881Speter *    distributed with this work for additional information
8251881Speter *    regarding copyright ownership.  The ASF licenses this file
9251881Speter *    to you under the Apache License, Version 2.0 (the
10251881Speter *    "License"); you may not use this file except in compliance
11251881Speter *    with the License.  You may obtain a copy of the License at
12251881Speter *
13251881Speter *      http://www.apache.org/licenses/LICENSE-2.0
14251881Speter *
15251881Speter *    Unless required by applicable law or agreed to in writing,
16251881Speter *    software distributed under the License is distributed on an
17251881Speter *    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
18251881Speter *    KIND, either express or implied.  See the License for the
19251881Speter *    specific language governing permissions and limitations
20251881Speter *    under the License.
21251881Speter * ====================================================================
22251881Speter */
23251881Speter
24251881Speter/* This is an interactive file merge tool with an interface similar to
25251881Speter * the interactive mode of the UNIX sdiff ("side-by-side diff") utility.
26251881Speter * The merge tool is driven by Subversion's diff code and user input. */
27251881Speter
28251881Speter#include "svn_cmdline.h"
29251881Speter#include "svn_dirent_uri.h"
30251881Speter#include "svn_error.h"
31251881Speter#include "svn_pools.h"
32251881Speter#include "svn_io.h"
33251881Speter#include "svn_utf.h"
34251881Speter#include "svn_xml.h"
35251881Speter
36251881Speter#include "cl.h"
37251881Speter
38251881Speter#include "svn_private_config.h"
39251881Speter#include "private/svn_utf_private.h"
40251881Speter#include "private/svn_cmdline_private.h"
41251881Speter#include "private/svn_dep_compat.h"
42251881Speter
43251881Speter#if APR_HAVE_SYS_IOCTL_H
44251881Speter#include <sys/ioctl.h>
45251881Speter#endif
46251881Speter
47251881Speter#if APR_HAVE_UNISTD_H
48251881Speter#include <unistd.h>
49251881Speter#endif
50251881Speter
51251881Speter#include <fcntl.h>
52251881Speter#include <stdlib.h>
53251881Speter
54251881Speter/* Baton for functions in this file which implement svn_diff_output_fns_t. */
55251881Speterstruct file_merge_baton {
56251881Speter  /* The files being merged. */
57251881Speter  apr_file_t *original_file;
58251881Speter  apr_file_t *modified_file;
59251881Speter  apr_file_t *latest_file;
60251881Speter
61251881Speter  /* Counters to keep track of the current line in each file. */
62251881Speter  svn_linenum_t current_line_original;
63251881Speter  svn_linenum_t current_line_modified;
64251881Speter  svn_linenum_t current_line_latest;
65251881Speter
66251881Speter  /* The merge result is written to this file. */
67251881Speter  apr_file_t *merged_file;
68251881Speter
69251881Speter  /* Whether the merged file remains in conflict after the merge. */
70251881Speter  svn_boolean_t remains_in_conflict;
71251881Speter
72251881Speter  /* External editor command for editing chunks. */
73251881Speter  const char *editor_cmd;
74251881Speter
75251881Speter  /* The client configuration hash. */
76251881Speter  apr_hash_t *config;
77251881Speter
78251881Speter  /* Wether the merge should be aborted. */
79251881Speter  svn_boolean_t abort_merge;
80251881Speter
81251881Speter  /* Pool for temporary allocations. */
82251881Speter  apr_pool_t *scratch_pool;
83251881Speter} file_merge_baton;
84251881Speter
85251881Speter/* Copy LEN lines from SOURCE_FILE to the MERGED_FILE, starting at
86251881Speter * line START. The CURRENT_LINE is the current line in the source file.
87251881Speter * The new current line is returned in *NEW_CURRENT_LINE. */
88251881Speterstatic svn_error_t *
89251881Spetercopy_to_merged_file(svn_linenum_t *new_current_line,
90251881Speter                    apr_file_t *merged_file,
91251881Speter                    apr_file_t *source_file,
92251881Speter                    apr_off_t start,
93251881Speter                    apr_off_t len,
94251881Speter                    svn_linenum_t current_line,
95251881Speter                    apr_pool_t *scratch_pool)
96251881Speter{
97251881Speter  apr_pool_t *iterpool;
98251881Speter  svn_stringbuf_t *line;
99251881Speter  apr_size_t lines_read;
100251881Speter  apr_size_t lines_copied;
101251881Speter  svn_boolean_t eof;
102251881Speter  svn_linenum_t orig_current_line = current_line;
103251881Speter
104251881Speter  lines_read = 0;
105251881Speter  iterpool = svn_pool_create(scratch_pool);
106251881Speter  while (current_line < start)
107251881Speter    {
108251881Speter      svn_pool_clear(iterpool);
109251881Speter
110251881Speter      SVN_ERR(svn_io_file_readline(source_file, &line, NULL, &eof,
111251881Speter                                   APR_SIZE_MAX, iterpool, iterpool));
112251881Speter      if (eof)
113251881Speter        break;
114251881Speter
115251881Speter      current_line++;
116251881Speter      lines_read++;
117251881Speter    }
118251881Speter
119251881Speter  lines_copied = 0;
120251881Speter  while (lines_copied < len)
121251881Speter    {
122251881Speter      apr_size_t bytes_written;
123251881Speter      const char *eol_str;
124251881Speter
125251881Speter      svn_pool_clear(iterpool);
126251881Speter
127251881Speter      SVN_ERR(svn_io_file_readline(source_file, &line, &eol_str, &eof,
128251881Speter                                   APR_SIZE_MAX, iterpool, iterpool));
129251881Speter      if (eol_str)
130251881Speter        svn_stringbuf_appendcstr(line, eol_str);
131251881Speter      SVN_ERR(svn_io_file_write_full(merged_file, line->data, line->len,
132251881Speter                                     &bytes_written, iterpool));
133251881Speter      if (bytes_written != line->len)
134251881Speter        return svn_error_create(SVN_ERR_IO_WRITE_ERROR, NULL,
135251881Speter                                _("Could not write data to merged file"));
136251881Speter      if (eof)
137251881Speter        break;
138251881Speter      lines_copied++;
139251881Speter    }
140251881Speter  svn_pool_destroy(iterpool);
141251881Speter
142251881Speter  *new_current_line = orig_current_line + lines_read + lines_copied;
143251881Speter
144251881Speter  return SVN_NO_ERROR;
145251881Speter}
146251881Speter
147251881Speter/* Copy common data to the merged file. */
148251881Speterstatic svn_error_t *
149251881Speterfile_merge_output_common(void *output_baton,
150251881Speter                         apr_off_t original_start,
151251881Speter                         apr_off_t original_length,
152251881Speter                         apr_off_t modified_start,
153251881Speter                         apr_off_t modified_length,
154251881Speter                         apr_off_t latest_start,
155251881Speter                         apr_off_t latest_length)
156251881Speter{
157251881Speter  struct file_merge_baton *b = output_baton;
158251881Speter
159251881Speter  if (b->abort_merge)
160251881Speter    return SVN_NO_ERROR;
161251881Speter
162251881Speter  SVN_ERR(copy_to_merged_file(&b->current_line_original,
163251881Speter                              b->merged_file,
164251881Speter                              b->original_file,
165251881Speter                              original_start,
166251881Speter                              original_length,
167251881Speter                              b->current_line_original,
168251881Speter                              b->scratch_pool));
169251881Speter  return SVN_NO_ERROR;
170251881Speter}
171251881Speter
172251881Speter/* Original/latest match up, but modified differs.
173251881Speter * Copy modified data to the merged file. */
174251881Speterstatic svn_error_t *
175251881Speterfile_merge_output_diff_modified(void *output_baton,
176251881Speter                                apr_off_t original_start,
177251881Speter                                apr_off_t original_length,
178251881Speter                                apr_off_t modified_start,
179251881Speter                                apr_off_t modified_length,
180251881Speter                                apr_off_t latest_start,
181251881Speter                                apr_off_t latest_length)
182251881Speter{
183251881Speter  struct file_merge_baton *b = output_baton;
184251881Speter
185251881Speter  if (b->abort_merge)
186251881Speter    return SVN_NO_ERROR;
187251881Speter
188251881Speter  SVN_ERR(copy_to_merged_file(&b->current_line_modified,
189251881Speter                              b->merged_file,
190251881Speter                              b->modified_file,
191251881Speter                              modified_start,
192251881Speter                              modified_length,
193251881Speter                              b->current_line_modified,
194251881Speter                              b->scratch_pool));
195251881Speter
196251881Speter  return SVN_NO_ERROR;
197251881Speter}
198251881Speter
199251881Speter/* Original/modified match up, but latest differs.
200251881Speter * Copy latest data to the merged file. */
201251881Speterstatic svn_error_t *
202251881Speterfile_merge_output_diff_latest(void *output_baton,
203251881Speter                              apr_off_t original_start,
204251881Speter                              apr_off_t original_length,
205251881Speter                              apr_off_t modified_start,
206251881Speter                              apr_off_t modified_length,
207251881Speter                              apr_off_t latest_start,
208251881Speter                              apr_off_t latest_length)
209251881Speter{
210251881Speter  struct file_merge_baton *b = output_baton;
211251881Speter
212251881Speter  if (b->abort_merge)
213251881Speter    return SVN_NO_ERROR;
214251881Speter
215251881Speter  SVN_ERR(copy_to_merged_file(&b->current_line_latest,
216251881Speter                              b->merged_file,
217251881Speter                              b->latest_file,
218251881Speter                              latest_start,
219251881Speter                              latest_length,
220251881Speter                              b->current_line_latest,
221251881Speter                              b->scratch_pool));
222251881Speter
223251881Speter  return SVN_NO_ERROR;
224251881Speter}
225251881Speter
226251881Speter/* Modified/latest match up, but original differs.
227251881Speter * Copy latest data to the merged file. */
228251881Speterstatic svn_error_t *
229251881Speterfile_merge_output_diff_common(void *output_baton,
230251881Speter                              apr_off_t original_start,
231251881Speter                              apr_off_t original_length,
232251881Speter                              apr_off_t modified_start,
233251881Speter                              apr_off_t modified_length,
234251881Speter                              apr_off_t latest_start,
235251881Speter                              apr_off_t latest_length)
236251881Speter{
237251881Speter  struct file_merge_baton *b = output_baton;
238251881Speter
239251881Speter  if (b->abort_merge)
240251881Speter    return SVN_NO_ERROR;
241251881Speter
242251881Speter  SVN_ERR(copy_to_merged_file(&b->current_line_latest,
243251881Speter                              b->merged_file,
244251881Speter                              b->latest_file,
245251881Speter                              latest_start,
246251881Speter                              latest_length,
247251881Speter                              b->current_line_latest,
248251881Speter                              b->scratch_pool));
249251881Speter  return SVN_NO_ERROR;
250251881Speter}
251251881Speter
252251881Speter
253251881Speter/* Return LEN lines within the diff chunk staring at line START
254251881Speter * in a *LINES array of svn_stringbuf_t* elements.
255251881Speter * Store the resulting current in in *NEW_CURRENT_LINE. */
256251881Speterstatic svn_error_t *
257251881Speterread_diff_chunk(apr_array_header_t **lines,
258251881Speter                svn_linenum_t *new_current_line,
259251881Speter                apr_file_t *file,
260251881Speter                svn_linenum_t current_line,
261251881Speter                apr_off_t start,
262251881Speter                apr_off_t len,
263251881Speter                apr_pool_t *result_pool,
264251881Speter                apr_pool_t *scratch_pool)
265251881Speter{
266251881Speter  svn_stringbuf_t *line;
267251881Speter  const char *eol_str;
268251881Speter  svn_boolean_t eof;
269251881Speter  apr_pool_t *iterpool;
270251881Speter
271251881Speter  *lines = apr_array_make(result_pool, 0, sizeof(svn_stringbuf_t *));
272251881Speter
273251881Speter  /* Skip lines before start of range. */
274251881Speter  iterpool = svn_pool_create(scratch_pool);
275251881Speter  while (current_line < start)
276251881Speter    {
277251881Speter      svn_pool_clear(iterpool);
278251881Speter      SVN_ERR(svn_io_file_readline(file, &line, NULL, &eof, APR_SIZE_MAX,
279251881Speter                                   iterpool, iterpool));
280251881Speter      if (eof)
281251881Speter        return SVN_NO_ERROR;
282251881Speter      current_line++;
283251881Speter    }
284251881Speter  svn_pool_destroy(iterpool);
285251881Speter
286251881Speter  /* Now read the lines. */
287251881Speter  do
288251881Speter    {
289251881Speter      SVN_ERR(svn_io_file_readline(file, &line, &eol_str, &eof, APR_SIZE_MAX,
290251881Speter                                   result_pool, scratch_pool));
291251881Speter      if (eol_str)
292251881Speter        svn_stringbuf_appendcstr(line, eol_str);
293251881Speter      APR_ARRAY_PUSH(*lines, svn_stringbuf_t *) = line;
294251881Speter      if (eof)
295251881Speter        break;
296251881Speter      current_line++;
297251881Speter    }
298251881Speter  while ((*lines)->nelts < len);
299251881Speter
300251881Speter  *new_current_line = current_line;
301251881Speter
302251881Speter  return SVN_NO_ERROR;
303251881Speter}
304251881Speter
305251881Speter/* Return the terminal width in number of columns. */
306251881Speterstatic int
307251881Speterget_term_width(void)
308251881Speter{
309251881Speter  char *columns_env;
310251881Speter#ifdef TIOCGWINSZ
311251881Speter  int fd;
312251881Speter
313251881Speter  fd = open("/dev/tty", O_RDONLY, 0);
314251881Speter  if (fd != -1)
315251881Speter    {
316251881Speter      struct winsize ws;
317251881Speter      int error;
318251881Speter
319251881Speter      error = ioctl(fd, TIOCGWINSZ, &ws);
320251881Speter      close(fd);
321251881Speter      if (error != -1)
322251881Speter        {
323251881Speter          if (ws.ws_col < 80)
324251881Speter            return 80;
325251881Speter          return ws.ws_col;
326251881Speter        }
327251881Speter    }
328251881Speter#elif defined WIN32
329251881Speter  CONSOLE_SCREEN_BUFFER_INFO csbi;
330251881Speter
331251881Speter  if (GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &csbi))
332251881Speter    {
333251881Speter      if (csbi.dwSize.X < 80)
334251881Speter        return 80;
335251881Speter      return csbi.dwSize.X;
336251881Speter    }
337251881Speter#endif
338251881Speter
339251881Speter  columns_env = getenv("COLUMNS");
340251881Speter  if (columns_env)
341251881Speter    {
342251881Speter      svn_error_t *err;
343251881Speter      int cols;
344251881Speter
345251881Speter      err = svn_cstring_atoi(&cols, columns_env);
346251881Speter      if (err)
347251881Speter        {
348251881Speter          svn_error_clear(err);
349251881Speter          return 80;
350251881Speter        }
351251881Speter
352251881Speter      if (cols < 80)
353251881Speter        return 80;
354251881Speter      return cols;
355251881Speter    }
356251881Speter  else
357251881Speter    return 80;
358251881Speter}
359251881Speter
360251881Speter#define LINE_DISPLAY_WIDTH ((get_term_width() / 2) - 2)
361251881Speter
362251881Speter/* Prepare LINE for display, pruning or extending it to an appropriate
363251881Speter * display width, and stripping the EOL marker, if any.
364251881Speter * This function assumes that the data in LINE is encoded in UTF-8. */
365251881Speterstatic const char *
366251881Speterprepare_line_for_display(const char *line, apr_pool_t *pool)
367251881Speter{
368251881Speter  svn_stringbuf_t *buf = svn_stringbuf_create(line, pool);
369251881Speter  size_t width;
370251881Speter  size_t line_width = LINE_DISPLAY_WIDTH;
371251881Speter  apr_pool_t *iterpool;
372251881Speter
373251881Speter  /* Trim EOL. */
374251881Speter  if (buf->len >= 2 &&
375251881Speter      buf->data[buf->len - 2] == '\r' &&
376251881Speter      buf->data[buf->len - 1] == '\n')
377251881Speter    svn_stringbuf_chop(buf, 2);
378251881Speter  else if (buf->len >= 1 &&
379251881Speter           (buf->data[buf->len - 1] == '\n' ||
380251881Speter            buf->data[buf->len - 1] == '\r'))
381251881Speter    svn_stringbuf_chop(buf, 1);
382251881Speter
383251881Speter  /* Determine the on-screen width of the line. */
384251881Speter  width = svn_utf_cstring_utf8_width(buf->data);
385251881Speter  if (width == -1)
386251881Speter    {
387251881Speter      /* Determining the width failed. Try to get rid of unprintable
388251881Speter       * characters in the line buffer. */
389251881Speter      buf = svn_stringbuf_create(svn_xml_fuzzy_escape(buf->data, pool), pool);
390251881Speter      width = svn_utf_cstring_utf8_width(buf->data);
391251881Speter      if (width == -1)
392251881Speter        width = buf->len; /* fallback: buffer length */
393251881Speter    }
394251881Speter
395251881Speter  /* Trim further in case line is still too long, or add padding in case
396251881Speter   * it is too short. */
397251881Speter  iterpool = svn_pool_create(pool);
398251881Speter  while (width > line_width)
399251881Speter    {
400251881Speter      const char *last_valid;
401251881Speter
402251881Speter      svn_pool_clear(iterpool);
403251881Speter
404251881Speter      svn_stringbuf_chop(buf, 1);
405251881Speter
406251881Speter      /* Be careful not to invalidate the UTF-8 string by trimming
407251881Speter       * just part of a character. */
408251881Speter      last_valid = svn_utf__last_valid(buf->data, buf->len);
409251881Speter      if (last_valid < buf->data + buf->len)
410251881Speter        svn_stringbuf_chop(buf, (buf->data + buf->len) - last_valid);
411251881Speter
412251881Speter      width = svn_utf_cstring_utf8_width(buf->data);
413251881Speter      if (width == -1)
414251881Speter        width = buf->len; /* fallback: buffer length */
415251881Speter    }
416251881Speter  svn_pool_destroy(iterpool);
417251881Speter
418251881Speter  while (width == 0 || width < line_width)
419251881Speter    {
420251881Speter      svn_stringbuf_appendbyte(buf, ' ');
421251881Speter      width++;
422251881Speter    }
423251881Speter
424251881Speter  SVN_ERR_ASSERT_NO_RETURN(width == line_width);
425251881Speter  return buf->data;
426251881Speter}
427251881Speter
428251881Speter/* Merge CHUNK1 and CHUNK2 into a new chunk with conflict markers. */
429251881Speterstatic apr_array_header_t *
430251881Spetermerge_chunks_with_conflict_markers(apr_array_header_t *chunk1,
431251881Speter                                   apr_array_header_t *chunk2,
432251881Speter                                   apr_pool_t *result_pool)
433251881Speter{
434251881Speter  apr_array_header_t *merged_chunk;
435251881Speter  int i;
436251881Speter
437251881Speter  merged_chunk = apr_array_make(result_pool, 0, sizeof(svn_stringbuf_t *));
438251881Speter  /* ### would be nice to show filenames next to conflict markers */
439251881Speter  APR_ARRAY_PUSH(merged_chunk, svn_stringbuf_t *) =
440251881Speter    svn_stringbuf_create("<<<<<<<\n", result_pool);
441251881Speter  for (i = 0; i < chunk1->nelts; i++)
442251881Speter    {
443251881Speter      APR_ARRAY_PUSH(merged_chunk, svn_stringbuf_t *) =
444251881Speter        APR_ARRAY_IDX(chunk1, i, svn_stringbuf_t*);
445251881Speter    }
446251881Speter  APR_ARRAY_PUSH(merged_chunk, svn_stringbuf_t *) =
447251881Speter    svn_stringbuf_create("=======\n", result_pool);
448251881Speter  for (i = 0; i < chunk2->nelts; i++)
449251881Speter    {
450251881Speter      APR_ARRAY_PUSH(merged_chunk, svn_stringbuf_t *) =
451251881Speter        APR_ARRAY_IDX(chunk2, i, svn_stringbuf_t*);
452251881Speter    }
453251881Speter  APR_ARRAY_PUSH(merged_chunk, svn_stringbuf_t *) =
454251881Speter    svn_stringbuf_create(">>>>>>>\n", result_pool);
455251881Speter
456251881Speter  return merged_chunk;
457251881Speter}
458251881Speter
459251881Speter/* Edit CHUNK and return the result in *MERGED_CHUNK allocated in POOL. */
460251881Speterstatic svn_error_t *
461251881Speteredit_chunk(apr_array_header_t **merged_chunk,
462251881Speter           apr_array_header_t *chunk,
463251881Speter           const char *editor_cmd,
464251881Speter           apr_hash_t *config,
465251881Speter           apr_pool_t *result_pool,
466251881Speter           apr_pool_t *scratch_pool)
467251881Speter{
468251881Speter  apr_file_t *temp_file;
469251881Speter  const char *temp_file_name;
470251881Speter  int i;
471251881Speter  apr_off_t pos;
472251881Speter  svn_boolean_t eof;
473251881Speter  svn_error_t *err;
474251881Speter  apr_pool_t *iterpool;
475251881Speter
476251881Speter  SVN_ERR(svn_io_open_unique_file3(&temp_file, &temp_file_name, NULL,
477251881Speter                                   svn_io_file_del_on_pool_cleanup,
478251881Speter                                   scratch_pool, scratch_pool));
479251881Speter  iterpool = svn_pool_create(scratch_pool);
480251881Speter  for (i = 0; i < chunk->nelts; i++)
481251881Speter    {
482251881Speter      svn_stringbuf_t *line = APR_ARRAY_IDX(chunk, i, svn_stringbuf_t *);
483251881Speter      apr_size_t bytes_written;
484251881Speter
485251881Speter      svn_pool_clear(iterpool);
486251881Speter
487251881Speter      SVN_ERR(svn_io_file_write_full(temp_file, line->data, line->len,
488251881Speter                                     &bytes_written, iterpool));
489251881Speter      if (line->len != bytes_written)
490251881Speter        return svn_error_create(SVN_ERR_IO_WRITE_ERROR, NULL,
491251881Speter                                _("Could not write data to temporary file"));
492251881Speter    }
493251881Speter  SVN_ERR(svn_io_file_flush_to_disk(temp_file, scratch_pool));
494251881Speter
495251881Speter  err = svn_cmdline__edit_file_externally(temp_file_name, editor_cmd,
496251881Speter                                          config, scratch_pool);
497251881Speter  if (err && (err->apr_err == SVN_ERR_CL_NO_EXTERNAL_EDITOR))
498251881Speter    {
499251881Speter      svn_error_t *root_err = svn_error_root_cause(err);
500251881Speter
501251881Speter      SVN_ERR(svn_cmdline_fprintf(stderr, scratch_pool, "%s\n",
502251881Speter                                  root_err->message ? root_err->message :
503251881Speter                                  _("No editor found.")));
504251881Speter      svn_error_clear(err);
505251881Speter      *merged_chunk = NULL;
506251881Speter      svn_pool_destroy(iterpool);
507251881Speter      return SVN_NO_ERROR;
508251881Speter    }
509251881Speter  else if (err && (err->apr_err == SVN_ERR_EXTERNAL_PROGRAM))
510251881Speter    {
511251881Speter      svn_error_t *root_err = svn_error_root_cause(err);
512251881Speter
513251881Speter      SVN_ERR(svn_cmdline_fprintf(stderr, scratch_pool, "%s\n",
514251881Speter                                  root_err->message ? root_err->message :
515251881Speter                                  _("Error running editor.")));
516251881Speter      svn_error_clear(err);
517251881Speter      *merged_chunk = NULL;
518251881Speter      svn_pool_destroy(iterpool);
519251881Speter      return SVN_NO_ERROR;
520251881Speter    }
521251881Speter  else if (err)
522251881Speter    return svn_error_trace(err);
523251881Speter
524251881Speter  *merged_chunk = apr_array_make(result_pool, 1, sizeof(svn_stringbuf_t *));
525251881Speter  pos = 0;
526251881Speter  SVN_ERR(svn_io_file_seek(temp_file, APR_SET, &pos, scratch_pool));
527251881Speter  do
528251881Speter    {
529251881Speter      svn_stringbuf_t *line;
530251881Speter      const char *eol_str;
531251881Speter
532251881Speter      svn_pool_clear(iterpool);
533251881Speter
534251881Speter      SVN_ERR(svn_io_file_readline(temp_file, &line, &eol_str, &eof,
535251881Speter                                   APR_SIZE_MAX, result_pool, iterpool));
536251881Speter      if (eol_str)
537251881Speter        svn_stringbuf_appendcstr(line, eol_str);
538251881Speter
539251881Speter      APR_ARRAY_PUSH(*merged_chunk, svn_stringbuf_t *) = line;
540251881Speter    }
541251881Speter  while (!eof);
542251881Speter  svn_pool_destroy(iterpool);
543251881Speter
544251881Speter  SVN_ERR(svn_io_file_close(temp_file, scratch_pool));
545251881Speter
546251881Speter  return SVN_NO_ERROR;
547251881Speter}
548251881Speter
549251881Speter/* Create a separator string of the appropriate length. */
550251881Speterstatic const char *
551251881Speterget_sep_string(apr_pool_t *result_pool)
552251881Speter{
553251881Speter  int line_width = LINE_DISPLAY_WIDTH;
554251881Speter  int i;
555251881Speter  svn_stringbuf_t *buf;
556251881Speter
557251881Speter  buf = svn_stringbuf_create_empty(result_pool);
558251881Speter  for (i = 0; i < line_width; i++)
559251881Speter    svn_stringbuf_appendbyte(buf, '-');
560251881Speter  svn_stringbuf_appendbyte(buf, '+');
561251881Speter  for (i = 0; i < line_width; i++)
562251881Speter    svn_stringbuf_appendbyte(buf, '-');
563251881Speter  svn_stringbuf_appendbyte(buf, '\n');
564251881Speter
565251881Speter  return buf->data;
566251881Speter}
567251881Speter
568251881Speter/* Merge chunks CHUNK1 and CHUNK2.
569251881Speter * Each lines array contains elements of type svn_stringbuf_t*.
570251881Speter * Return the result in *MERGED_CHUNK, or set *MERGED_CHUNK to NULL in
571251881Speter * case the user chooses to postpone resolution of this chunk.
572251881Speter * If the user wants to abort the merge, set *ABORT_MERGE to TRUE. */
573251881Speterstatic svn_error_t *
574251881Spetermerge_chunks(apr_array_header_t **merged_chunk,
575251881Speter             svn_boolean_t *abort_merge,
576251881Speter             apr_array_header_t *chunk1,
577251881Speter             apr_array_header_t *chunk2,
578251881Speter             svn_linenum_t current_line1,
579251881Speter             svn_linenum_t current_line2,
580251881Speter             const char *editor_cmd,
581251881Speter             apr_hash_t *config,
582251881Speter             apr_pool_t *result_pool,
583251881Speter             apr_pool_t *scratch_pool)
584251881Speter{
585251881Speter  svn_stringbuf_t *prompt;
586251881Speter  int i;
587251881Speter  int max_chunk_lines;
588251881Speter  apr_pool_t *iterpool;
589251881Speter
590251881Speter  max_chunk_lines = chunk1->nelts > chunk2->nelts ? chunk1->nelts
591251881Speter                                                  : chunk2->nelts;
592251881Speter  *abort_merge = FALSE;
593251881Speter
594251881Speter  /*
595251881Speter   * Prepare the selection prompt.
596251881Speter   */
597251881Speter
598251881Speter  prompt = svn_stringbuf_create(
599251881Speter             apr_psprintf(scratch_pool, "%s\n%s|%s\n%s",
600251881Speter                          _("Conflicting section found during merge:"),
601251881Speter                          prepare_line_for_display(
602251881Speter                            apr_psprintf(scratch_pool,
603251881Speter                                         _("(1) their version (at line %lu)"),
604251881Speter                                         current_line1),
605251881Speter                            scratch_pool),
606251881Speter                          prepare_line_for_display(
607251881Speter                            apr_psprintf(scratch_pool,
608251881Speter                                         _("(2) your version (at line %lu)"),
609251881Speter                                         current_line2),
610251881Speter                            scratch_pool),
611251881Speter                          get_sep_string(scratch_pool)),
612251881Speter             scratch_pool);
613251881Speter
614251881Speter  iterpool = svn_pool_create(scratch_pool);
615251881Speter  for (i = 0; i < max_chunk_lines; i++)
616251881Speter    {
617251881Speter      const char *line1;
618251881Speter      const char *line2;
619251881Speter      const char *prompt_line;
620251881Speter
621251881Speter      svn_pool_clear(iterpool);
622251881Speter
623251881Speter      if (i < chunk1->nelts)
624251881Speter        {
625251881Speter          svn_stringbuf_t *line_utf8;
626251881Speter
627251881Speter          SVN_ERR(svn_utf_stringbuf_to_utf8(&line_utf8,
628251881Speter                                            APR_ARRAY_IDX(chunk1, i,
629251881Speter                                                          svn_stringbuf_t*),
630251881Speter                                            iterpool));
631251881Speter          line1 = prepare_line_for_display(line_utf8->data, iterpool);
632251881Speter        }
633251881Speter      else
634251881Speter        line1 = prepare_line_for_display("", iterpool);
635251881Speter
636251881Speter      if (i < chunk2->nelts)
637251881Speter        {
638251881Speter          svn_stringbuf_t *line_utf8;
639251881Speter
640251881Speter          SVN_ERR(svn_utf_stringbuf_to_utf8(&line_utf8,
641251881Speter                                            APR_ARRAY_IDX(chunk2, i,
642251881Speter                                                          svn_stringbuf_t*),
643251881Speter                                            iterpool));
644251881Speter          line2 = prepare_line_for_display(line_utf8->data, iterpool);
645251881Speter        }
646251881Speter      else
647251881Speter        line2 = prepare_line_for_display("", iterpool);
648251881Speter
649251881Speter      prompt_line = apr_psprintf(iterpool, "%s|%s\n", line1, line2);
650251881Speter
651251881Speter      svn_stringbuf_appendcstr(prompt, prompt_line);
652251881Speter    }
653251881Speter
654251881Speter  svn_stringbuf_appendcstr(prompt, get_sep_string(scratch_pool));
655251881Speter  svn_stringbuf_appendcstr(
656251881Speter    prompt,
657251881Speter    _("Select: (1) use their version, (2) use your version,\n"
658253734Speter      "        (12) their version first, then yours,\n"
659253734Speter      "        (21) your version first, then theirs,\n"
660251881Speter      "        (e1) edit their version and use the result,\n"
661251881Speter      "        (e2) edit your version and use the result,\n"
662251881Speter      "        (eb) edit both versions and use the result,\n"
663251881Speter      "        (p) postpone this conflicting section leaving conflict markers,\n"
664251881Speter      "        (a) abort file merge and return to main menu: "));
665251881Speter
666251881Speter  /* Now let's see what the user wants to do with this conflict. */
667251881Speter  while (TRUE)
668251881Speter    {
669251881Speter      const char *answer;
670251881Speter
671251881Speter      svn_pool_clear(iterpool);
672251881Speter
673251881Speter      SVN_ERR(svn_cmdline_prompt_user2(&answer, prompt->data, NULL, iterpool));
674251881Speter      if (strcmp(answer, "1") == 0)
675251881Speter        {
676251881Speter          *merged_chunk = chunk1;
677251881Speter          break;
678251881Speter        }
679251881Speter      else if (strcmp(answer, "2") == 0)
680251881Speter        {
681251881Speter          *merged_chunk = chunk2;
682251881Speter          break;
683251881Speter        }
684253734Speter      if (strcmp(answer, "12") == 0)
685253734Speter        {
686253734Speter          *merged_chunk = apr_array_make(result_pool,
687253734Speter                                         chunk1->nelts + chunk2->nelts,
688253734Speter                                         sizeof(svn_stringbuf_t *));
689253734Speter          apr_array_cat(*merged_chunk, chunk1);
690253734Speter          apr_array_cat(*merged_chunk, chunk2);
691253734Speter          break;
692253734Speter        }
693253734Speter      if (strcmp(answer, "21") == 0)
694253734Speter        {
695253734Speter          *merged_chunk = apr_array_make(result_pool,
696253734Speter                                         chunk1->nelts + chunk2->nelts,
697253734Speter                                         sizeof(svn_stringbuf_t *));
698253734Speter          apr_array_cat(*merged_chunk, chunk2);
699253734Speter          apr_array_cat(*merged_chunk, chunk1);
700253734Speter          break;
701253734Speter        }
702251881Speter      else if (strcmp(answer, "p") == 0)
703251881Speter        {
704251881Speter          *merged_chunk = NULL;
705251881Speter          break;
706251881Speter        }
707251881Speter      else if (strcmp(answer, "e1") == 0)
708251881Speter        {
709251881Speter          SVN_ERR(edit_chunk(merged_chunk, chunk1, editor_cmd, config,
710251881Speter                             result_pool, iterpool));
711251881Speter          if (*merged_chunk)
712251881Speter            break;
713251881Speter        }
714251881Speter      else if (strcmp(answer, "e2") == 0)
715251881Speter        {
716251881Speter          SVN_ERR(edit_chunk(merged_chunk, chunk2, editor_cmd, config,
717251881Speter                             result_pool, iterpool));
718251881Speter          if (*merged_chunk)
719251881Speter            break;
720251881Speter        }
721251881Speter      else if (strcmp(answer, "eb") == 0)
722251881Speter        {
723251881Speter          apr_array_header_t *conflict_chunk;
724251881Speter
725251881Speter          conflict_chunk = merge_chunks_with_conflict_markers(chunk1, chunk2,
726251881Speter                                                              scratch_pool);
727251881Speter          SVN_ERR(edit_chunk(merged_chunk, conflict_chunk, editor_cmd, config,
728251881Speter                             result_pool, iterpool));
729251881Speter          if (*merged_chunk)
730251881Speter            break;
731251881Speter        }
732251881Speter      else if (strcmp(answer, "a") == 0)
733251881Speter        {
734251881Speter          *abort_merge = TRUE;
735251881Speter          break;
736251881Speter        }
737251881Speter    }
738251881Speter  svn_pool_destroy(iterpool);
739251881Speter
740251881Speter  return SVN_NO_ERROR;
741251881Speter}
742251881Speter
743251881Speter/* Perform a merge of chunks from FILE1 and FILE2, specified by START1/LEN1
744251881Speter * and START2/LEN2, respectively. Append the result to MERGED_FILE.
745251881Speter * The current line numbers for FILE1 and FILE2 are passed in *CURRENT_LINE1
746251881Speter * and *CURRENT_LINE2, and will be updated to new values upon return.
747251881Speter * If the user wants to abort the merge, set *ABORT_MERGE to TRUE. */
748251881Speterstatic svn_error_t *
749251881Spetermerge_file_chunks(svn_boolean_t *remains_in_conflict,
750251881Speter                  svn_boolean_t *abort_merge,
751251881Speter                  apr_file_t *merged_file,
752251881Speter                  apr_file_t *file1,
753251881Speter                  apr_file_t *file2,
754251881Speter                  apr_off_t start1,
755251881Speter                  apr_off_t len1,
756251881Speter                  apr_off_t start2,
757251881Speter                  apr_off_t len2,
758251881Speter                  svn_linenum_t *current_line1,
759251881Speter                  svn_linenum_t *current_line2,
760251881Speter                  const char *editor_cmd,
761251881Speter                  apr_hash_t *config,
762251881Speter                  apr_pool_t *scratch_pool)
763251881Speter{
764251881Speter  apr_array_header_t *chunk1;
765251881Speter  apr_array_header_t *chunk2;
766251881Speter  apr_array_header_t *merged_chunk;
767251881Speter  apr_pool_t *iterpool;
768251881Speter  int i;
769251881Speter
770251881Speter  SVN_ERR(read_diff_chunk(&chunk1, current_line1, file1, *current_line1,
771251881Speter                          start1, len1, scratch_pool, scratch_pool));
772251881Speter  SVN_ERR(read_diff_chunk(&chunk2, current_line2, file2, *current_line2,
773251881Speter                          start2, len2, scratch_pool, scratch_pool));
774251881Speter
775251881Speter  SVN_ERR(merge_chunks(&merged_chunk, abort_merge, chunk1, chunk2,
776251881Speter                       *current_line1, *current_line2,
777251881Speter                       editor_cmd, config,
778251881Speter                       scratch_pool, scratch_pool));
779251881Speter
780251881Speter  if (*abort_merge)
781251881Speter      return SVN_NO_ERROR;
782251881Speter
783251881Speter  /* If the user chose 'postpone' put conflict markers and left/right
784251881Speter   * versions into the merged file. */
785251881Speter  if (merged_chunk == NULL)
786251881Speter    {
787251881Speter      *remains_in_conflict = TRUE;
788251881Speter      merged_chunk = merge_chunks_with_conflict_markers(chunk1, chunk2,
789251881Speter                                                        scratch_pool);
790251881Speter    }
791251881Speter
792251881Speter  iterpool = svn_pool_create(scratch_pool);
793251881Speter  for (i = 0; i < merged_chunk->nelts; i++)
794251881Speter    {
795251881Speter      apr_size_t bytes_written;
796251881Speter      svn_stringbuf_t *line = APR_ARRAY_IDX(merged_chunk, i,
797251881Speter                                            svn_stringbuf_t *);
798251881Speter
799251881Speter      svn_pool_clear(iterpool);
800251881Speter
801251881Speter      SVN_ERR(svn_io_file_write_full(merged_file, line->data, line->len,
802251881Speter                                     &bytes_written, iterpool));
803251881Speter      if (line->len != bytes_written)
804251881Speter        return svn_error_create(SVN_ERR_IO_WRITE_ERROR, NULL,
805251881Speter                                _("Could not write data to merged file"));
806251881Speter    }
807251881Speter  svn_pool_destroy(iterpool);
808251881Speter
809251881Speter  return SVN_NO_ERROR;
810251881Speter}
811251881Speter
812251881Speter/* Original, modified, and latest all differ from one another.
813251881Speter * This is a conflict and we'll need to ask the user to merge it. */
814251881Speterstatic svn_error_t *
815251881Speterfile_merge_output_conflict(void *output_baton,
816251881Speter                           apr_off_t original_start,
817251881Speter                           apr_off_t original_length,
818251881Speter                           apr_off_t modified_start,
819251881Speter                           apr_off_t modified_length,
820251881Speter                           apr_off_t latest_start,
821251881Speter                           apr_off_t latest_length,
822251881Speter                           svn_diff_t *resolved_diff)
823251881Speter{
824251881Speter  struct file_merge_baton *b = output_baton;
825251881Speter
826251881Speter  if (b->abort_merge)
827251881Speter    return SVN_NO_ERROR;
828251881Speter
829251881Speter  SVN_ERR(merge_file_chunks(&b->remains_in_conflict,
830251881Speter                            &b->abort_merge,
831251881Speter                            b->merged_file,
832251881Speter                            b->modified_file,
833251881Speter                            b->latest_file,
834251881Speter                            modified_start,
835251881Speter                            modified_length,
836251881Speter                            latest_start,
837251881Speter                            latest_length,
838251881Speter                            &b->current_line_modified,
839251881Speter                            &b->current_line_latest,
840251881Speter                            b->editor_cmd,
841251881Speter                            b->config,
842251881Speter                            b->scratch_pool));
843251881Speter  return SVN_NO_ERROR;
844251881Speter}
845251881Speter
846251881Speter/* Our collection of diff output functions that get driven during the merge. */
847251881Speterstatic svn_diff_output_fns_t file_merge_diff_output_fns = {
848251881Speter  file_merge_output_common,
849251881Speter  file_merge_output_diff_modified,
850251881Speter  file_merge_output_diff_latest,
851251881Speter  file_merge_output_diff_common,
852251881Speter  file_merge_output_conflict
853251881Speter};
854251881Speter
855251881Spetersvn_error_t *
856251881Spetersvn_cl__merge_file(const char *base_path,
857251881Speter                   const char *their_path,
858251881Speter                   const char *my_path,
859251881Speter                   const char *merged_path,
860251881Speter                   const char *wc_path,
861251881Speter                   const char *path_prefix,
862251881Speter                   const char *editor_cmd,
863251881Speter                   apr_hash_t *config,
864251881Speter                   svn_boolean_t *remains_in_conflict,
865251881Speter                   apr_pool_t *scratch_pool)
866251881Speter{
867251881Speter  svn_diff_t *diff;
868251881Speter  svn_diff_file_options_t *diff_options;
869251881Speter  apr_file_t *original_file;
870251881Speter  apr_file_t *modified_file;
871251881Speter  apr_file_t *latest_file;
872251881Speter  apr_file_t *merged_file;
873251881Speter  const char *merged_file_name;
874251881Speter  struct file_merge_baton fmb;
875251881Speter  svn_boolean_t executable;
876251881Speter  const char *merged_path_local_style;
877251881Speter  const char *merged_rel_path;
878251881Speter  const char *wc_path_local_style;
879251881Speter  const char *wc_rel_path = svn_dirent_skip_ancestor(path_prefix, wc_path);
880251881Speter
881251881Speter  /* PATH_PREFIX may not be an ancestor of WC_PATH, just use the
882251881Speter     full WC_PATH in that case. */
883251881Speter  if (wc_rel_path)
884251881Speter    wc_path_local_style = svn_dirent_local_style(wc_rel_path, scratch_pool);
885251881Speter  else
886251881Speter    wc_path_local_style = svn_dirent_local_style(wc_path, scratch_pool);
887251881Speter
888251881Speter  SVN_ERR(svn_cmdline_printf(scratch_pool, _("Merging '%s'.\n"),
889251881Speter                             wc_path_local_style));
890251881Speter
891251881Speter  SVN_ERR(svn_io_file_open(&original_file, base_path,
892251881Speter                           APR_READ | APR_BUFFERED,
893251881Speter                           APR_OS_DEFAULT, scratch_pool));
894251881Speter  SVN_ERR(svn_io_file_open(&modified_file, their_path,
895251881Speter                           APR_READ | APR_BUFFERED,
896251881Speter                           APR_OS_DEFAULT, scratch_pool));
897251881Speter  SVN_ERR(svn_io_file_open(&latest_file, my_path,
898251881Speter                           APR_READ | APR_BUFFERED,
899251881Speter                           APR_OS_DEFAULT, scratch_pool));
900251881Speter  SVN_ERR(svn_io_open_unique_file3(&merged_file, &merged_file_name,
901251881Speter                                   NULL, svn_io_file_del_none,
902251881Speter                                   scratch_pool, scratch_pool));
903251881Speter
904251881Speter  diff_options = svn_diff_file_options_create(scratch_pool);
905251881Speter  SVN_ERR(svn_diff_file_diff3_2(&diff, base_path, their_path, my_path,
906251881Speter                                diff_options, scratch_pool));
907251881Speter
908251881Speter  fmb.original_file = original_file;
909251881Speter  fmb.modified_file = modified_file;
910251881Speter  fmb.latest_file = latest_file;
911251881Speter  fmb.current_line_original = 0;
912251881Speter  fmb.current_line_modified = 0;
913251881Speter  fmb.current_line_latest = 0;
914251881Speter  fmb.merged_file = merged_file;
915251881Speter  fmb.remains_in_conflict = FALSE;
916251881Speter  fmb.editor_cmd = editor_cmd;
917251881Speter  fmb.config = config;
918251881Speter  fmb.abort_merge = FALSE;
919251881Speter  fmb.scratch_pool = scratch_pool;
920251881Speter
921251881Speter  SVN_ERR(svn_diff_output(diff, &fmb, &file_merge_diff_output_fns));
922251881Speter
923251881Speter  SVN_ERR(svn_io_file_close(original_file, scratch_pool));
924251881Speter  SVN_ERR(svn_io_file_close(modified_file, scratch_pool));
925251881Speter  SVN_ERR(svn_io_file_close(latest_file, scratch_pool));
926251881Speter  SVN_ERR(svn_io_file_close(merged_file, scratch_pool));
927251881Speter
928251881Speter  /* Start out assuming that conflicts remain. */
929251881Speter  if (remains_in_conflict)
930251881Speter    *remains_in_conflict = TRUE;
931251881Speter
932251881Speter  if (fmb.abort_merge)
933251881Speter    {
934251881Speter      SVN_ERR(svn_io_remove_file2(merged_file_name, TRUE, scratch_pool));
935251881Speter      SVN_ERR(svn_cmdline_printf(scratch_pool, _("Merge of '%s' aborted.\n"),
936251881Speter                                 wc_path_local_style));
937251881Speter      return SVN_NO_ERROR;
938251881Speter    }
939251881Speter
940251881Speter  SVN_ERR(svn_io_is_file_executable(&executable, merged_path, scratch_pool));
941251881Speter
942251881Speter  merged_rel_path = svn_dirent_skip_ancestor(path_prefix, merged_path);
943251881Speter  if (merged_rel_path)
944251881Speter    merged_path_local_style = svn_dirent_local_style(merged_rel_path,
945251881Speter                                                     scratch_pool);
946251881Speter  else
947251881Speter    merged_path_local_style = svn_dirent_local_style(merged_path,
948251881Speter                                                     scratch_pool);
949251881Speter
950251881Speter  SVN_ERR_W(svn_io_copy_file(merged_file_name, merged_path, FALSE,
951251881Speter                             scratch_pool),
952251881Speter            apr_psprintf(scratch_pool,
953251881Speter                         _("Could not write merged result to '%s', saved "
954251881Speter                           "instead at '%s'.\n'%s' remains in conflict.\n"),
955251881Speter                         merged_path_local_style,
956251881Speter                         svn_dirent_local_style(merged_file_name,
957251881Speter                                                scratch_pool),
958251881Speter                         wc_path_local_style));
959251881Speter  SVN_ERR(svn_io_set_file_executable(merged_path, executable, FALSE,
960251881Speter                                     scratch_pool));
961251881Speter  SVN_ERR(svn_io_remove_file2(merged_file_name, TRUE, scratch_pool));
962251881Speter
963251881Speter  /* The merge was not aborted and we could install the merged result. The
964251881Speter   * file remains in conflict unless all conflicting sections were resolved. */
965251881Speter  if (remains_in_conflict)
966251881Speter    *remains_in_conflict = fmb.remains_in_conflict;
967251881Speter
968251881Speter  if (fmb.remains_in_conflict)
969251881Speter    SVN_ERR(svn_cmdline_printf(
970251881Speter              scratch_pool,
971251881Speter              _("Merge of '%s' completed (remains in conflict).\n"),
972251881Speter              wc_path_local_style));
973251881Speter  else
974251881Speter    SVN_ERR(svn_cmdline_printf(
975251881Speter              scratch_pool, _("Merge of '%s' completed.\n"),
976251881Speter              wc_path_local_style));
977251881Speter
978251881Speter  return SVN_NO_ERROR;
979251881Speter}
980