merge.c revision 251881
1/*
2 * merge.c:  merging changes into a working file
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#include "svn_wc.h"
25#include "svn_diff.h"
26#include "svn_dirent_uri.h"
27#include "svn_path.h"
28#include "svn_pools.h"
29
30#include "wc.h"
31#include "adm_files.h"
32#include "conflicts.h"
33#include "translate.h"
34#include "workqueue.h"
35
36#include "private/svn_skel.h"
37
38#include "svn_private_config.h"
39
40/* Contains some information on the merge target before merge, and some
41   information needed for the diff processing. */
42typedef struct merge_target_t
43{
44  svn_wc__db_t *db;                         /* The DB used to access target */
45  const char *local_abspath;                /* The absolute path to target */
46  const char *wri_abspath;                  /* The working copy of target */
47
48  apr_hash_t *old_actual_props;                 /* The set of actual properties
49                                               before merging */
50  const apr_array_header_t *prop_diff;      /* The property changes */
51
52  const char *diff3_cmd;                    /* The diff3 command and options */
53  const apr_array_header_t *merge_options;
54
55} merge_target_t;
56
57
58/* Return a pointer to the svn_prop_t structure from PROP_DIFF
59   belonging to PROP_NAME, if any.  NULL otherwise.*/
60static const svn_prop_t *
61get_prop(const apr_array_header_t *prop_diff,
62         const char *prop_name)
63{
64  if (prop_diff)
65    {
66      int i;
67      for (i = 0; i < prop_diff->nelts; i++)
68        {
69          const svn_prop_t *elt = &APR_ARRAY_IDX(prop_diff, i,
70                                                 svn_prop_t);
71
72          if (strcmp(elt->name, prop_name) == 0)
73            return elt;
74        }
75    }
76
77  return NULL;
78}
79
80
81/* Detranslate a working copy file MERGE_TARGET to achieve the effect of:
82
83   1. Detranslate
84   2. Install new props
85   3. Retranslate
86   4. Detranslate
87
88   in one pass, to get a file which can be compared with the left and right
89   files which are in repository normal form.
90
91   Property changes make this a little complex though. Changes in
92
93   - svn:mime-type
94   - svn:eol-style
95   - svn:keywords
96   - svn:special
97
98   may change the way a file is translated.
99
100   Effect for svn:mime-type:
101
102     If svn:mime-type is considered 'binary', we ignore svn:eol-style (but
103     still translate keywords).
104
105     I) both old and new mime-types are texty
106        -> just do the translation dance (as lined out below)
107           ### actually we do a shortcut with just one translation:
108           detranslate with the old keywords and ... eol-style
109           (the new re+detranslation is a no-op w.r.t. keywords [1])
110
111     II) the old one is texty, the new one is binary
112        -> detranslate with the old eol-style and keywords
113           (the new re+detranslation is a no-op [1])
114
115     III) the old one is binary, the new one texty
116        -> detranslate with the old keywords and new eol-style
117           (the old detranslation is a no-op w.r.t. eol, and
118            the new re+detranslation is a no-op w.r.t. keywords [1])
119
120     IV) the old and new ones are binary
121        -> detranslate with the old keywords
122           (the new re+detranslation is a no-op [1])
123
124   Effect for svn:eol-style
125
126     I) On add or change of svn:eol-style, use the new value
127
128     II) otherwise: use the old value (absent means 'no translation')
129
130   Effect for svn:keywords
131
132     Always use the old settings (re+detranslation are no-op [1]).
133
134     [1] Translation of keywords from repository normal form to WC form and
135         back is normally a no-op, but is not a no-op if text contains a kw
136         that is only enabled by the new props and is present in non-
137         contracted form (such as "$Rev: 1234 $").  If we want to catch this
138         case we should detranslate with both the old & the new keywords
139         together.
140
141   Effect for svn:special
142
143     Always use the old settings (re+detranslation are no-op).
144
145  Sets *DETRANSLATED_ABSPATH to the path to the detranslated file,
146  this may be the same as SOURCE_ABSPATH if FORCE_COPY is FALSE and no
147  translation is required.
148
149  If FORCE_COPY is FALSE and *DETRANSLATED_ABSPATH is a file distinct
150  from SOURCE_ABSPATH then the file will be deleted on RESULT_POOL
151  cleanup.
152
153  If FORCE_COPY is TRUE then *DETRANSLATED_ABSPATH will always be a
154  new file distinct from SOURCE_ABSPATH and it will be the callers
155  responsibility to delete the file.
156
157*/
158static svn_error_t *
159detranslate_wc_file(const char **detranslated_abspath,
160                    const merge_target_t *mt,
161                    svn_boolean_t force_copy,
162                    const char *source_abspath,
163                    svn_cancel_func_t cancel_func,
164                    void *cancel_baton,
165                    apr_pool_t *result_pool,
166                    apr_pool_t *scratch_pool)
167{
168  svn_boolean_t old_is_binary, new_is_binary;
169  svn_subst_eol_style_t style;
170  const char *eol;
171  apr_hash_t *keywords;
172  svn_boolean_t special;
173
174  {
175    const char *old_mime_value
176      = svn_prop_get_value(mt->old_actual_props, SVN_PROP_MIME_TYPE);
177    const svn_prop_t *prop = get_prop(mt->prop_diff, SVN_PROP_MIME_TYPE);
178    const char *new_mime_value
179      = prop ? (prop->value ? prop->value->data : NULL) : old_mime_value;
180
181    old_is_binary = old_mime_value && svn_mime_type_is_binary(old_mime_value);
182    new_is_binary = new_mime_value && svn_mime_type_is_binary(new_mime_value);;
183  }
184
185  /* See what translations we want to do */
186  if (old_is_binary && new_is_binary)
187    {
188      /* Case IV. Old and new props 'binary': detranslate keywords only */
189      SVN_ERR(svn_wc__get_translate_info(NULL, NULL, &keywords, NULL,
190                                         mt->db, mt->local_abspath,
191                                         mt->old_actual_props, TRUE,
192                                         scratch_pool, scratch_pool));
193      /* ### Why override 'special'? Elsewhere it has precedence. */
194      special = FALSE;
195      eol = NULL;
196      style = svn_subst_eol_style_none;
197    }
198  else if (!old_is_binary && new_is_binary)
199    {
200      /* Case II. Old props indicate texty, new props indicate binary:
201         detranslate keywords and old eol-style */
202      SVN_ERR(svn_wc__get_translate_info(&style, &eol,
203                                         &keywords,
204                                         &special,
205                                         mt->db, mt->local_abspath,
206                                         mt->old_actual_props, TRUE,
207                                         scratch_pool, scratch_pool));
208    }
209  else
210    {
211      /* Case I & III. New props indicate texty, regardless of old props */
212
213      /* In case the file used to be special, detranslate specially */
214      SVN_ERR(svn_wc__get_translate_info(&style, &eol,
215                                         &keywords,
216                                         &special,
217                                         mt->db, mt->local_abspath,
218                                         mt->old_actual_props, TRUE,
219                                         scratch_pool, scratch_pool));
220
221      if (special)
222        {
223          keywords = NULL;
224          eol = NULL;
225          style = svn_subst_eol_style_none;
226        }
227      else
228        {
229          const svn_prop_t *prop;
230
231          /* In case a new eol style was set, use that for detranslation */
232          if ((prop = get_prop(mt->prop_diff, SVN_PROP_EOL_STYLE)) && prop->value)
233            {
234              /* Value added or changed */
235              svn_subst_eol_style_from_value(&style, &eol, prop->value->data);
236            }
237          else if (!old_is_binary)
238            {
239              /* Already fetched */
240            }
241          else
242            {
243              eol = NULL;
244              style = svn_subst_eol_style_none;
245            }
246        }
247    }
248
249  /* Now, detranslate with the settings we created above */
250
251  if (force_copy || keywords || eol || special)
252    {
253      const char *temp_dir_abspath;
254      const char *detranslated;
255
256      /* Force a copy into the temporary wc area to avoid having
257         temporary files created below to appear in the actual wc. */
258      SVN_ERR(svn_wc__db_temp_wcroot_tempdir(&temp_dir_abspath, mt->db,
259                                             mt->wri_abspath,
260                                             scratch_pool, scratch_pool));
261
262      /* ### svn_subst_copy_and_translate4() also creates a tempfile
263         ### internally.  Anyway to piggyback on that? */
264      SVN_ERR(svn_io_open_unique_file3(NULL, &detranslated, temp_dir_abspath,
265                                       (force_copy
266                                        ? svn_io_file_del_none
267                                        : svn_io_file_del_on_pool_cleanup),
268                                       result_pool, scratch_pool));
269
270      /* Always 'repair' EOLs here, so that we can apply a diff that
271         changes from inconsistent newlines and no 'svn:eol-style' to
272         consistent newlines and 'svn:eol-style' set.  */
273
274      if (style == svn_subst_eol_style_native)
275        eol = SVN_SUBST_NATIVE_EOL_STR;
276      else if (style != svn_subst_eol_style_fixed
277               && style != svn_subst_eol_style_none)
278        return svn_error_create(SVN_ERR_IO_UNKNOWN_EOL, NULL, NULL);
279
280      SVN_ERR(svn_subst_copy_and_translate4(source_abspath,
281                                            detranslated,
282                                            eol,
283                                            TRUE /* repair */,
284                                            keywords,
285                                            FALSE /* contract keywords */,
286                                            special,
287                                            cancel_func, cancel_baton,
288                                            scratch_pool));
289
290      SVN_ERR(svn_dirent_get_absolute(detranslated_abspath, detranslated,
291                                      result_pool));
292    }
293  else
294    *detranslated_abspath = apr_pstrdup(result_pool, source_abspath);
295
296  return SVN_NO_ERROR;
297}
298
299/* Updates (by copying and translating) the eol style in
300   OLD_TARGET_ABSPATH returning the filename containing the
301   correct eol style in NEW_TARGET_ABSPATH, if an eol style
302   change is contained in PROP_DIFF. */
303static svn_error_t *
304maybe_update_target_eols(const char **new_target_abspath,
305                         const apr_array_header_t *prop_diff,
306                         const char *old_target_abspath,
307                         svn_cancel_func_t cancel_func,
308                         void *cancel_baton,
309                         apr_pool_t *result_pool,
310                         apr_pool_t *scratch_pool)
311{
312  const svn_prop_t *prop = get_prop(prop_diff, SVN_PROP_EOL_STYLE);
313
314  if (prop && prop->value)
315    {
316      const char *eol;
317      const char *tmp_new;
318
319      svn_subst_eol_style_from_value(NULL, &eol, prop->value->data);
320      SVN_ERR(svn_io_open_unique_file3(NULL, &tmp_new, NULL,
321                                       svn_io_file_del_on_pool_cleanup,
322                                       result_pool, scratch_pool));
323
324      /* Always 'repair' EOLs here, so that we can apply a diff that
325         changes from inconsistent newlines and no 'svn:eol-style' to
326         consistent newlines and 'svn:eol-style' set.  */
327      SVN_ERR(svn_subst_copy_and_translate4(old_target_abspath,
328                                            tmp_new,
329                                            eol,
330                                            TRUE /* repair */,
331                                            NULL /* keywords */,
332                                            FALSE /* expand */,
333                                            FALSE /* special */,
334                                            cancel_func, cancel_baton,
335                                            scratch_pool));
336      *new_target_abspath = apr_pstrdup(result_pool, tmp_new);
337    }
338  else
339    *new_target_abspath = apr_pstrdup(result_pool, old_target_abspath);
340
341  return SVN_NO_ERROR;
342}
343
344
345/* Set *TARGET_MARKER, *LEFT_MARKER and *RIGHT_MARKER to strings suitable
346   for delimiting the alternative texts in a text conflict.  Include in each
347   marker a string that may be given by TARGET_LABEL, LEFT_LABEL and
348   RIGHT_LABEL respectively or a default value where any of those are NULL.
349
350   Allocate the results in POOL or statically. */
351static void
352init_conflict_markers(const char **target_marker,
353                      const char **left_marker,
354                      const char **right_marker,
355                      const char *target_label,
356                      const char *left_label,
357                      const char *right_label,
358                      apr_pool_t *pool)
359{
360  /* Labels fall back to sensible defaults if not specified. */
361  if (target_label)
362    *target_marker = apr_psprintf(pool, "<<<<<<< %s", target_label);
363  else
364    *target_marker = "<<<<<<< .working";
365
366  if (left_label)
367    *left_marker = apr_psprintf(pool, "||||||| %s", left_label);
368  else
369    *left_marker = "||||||| .old";
370
371  if (right_label)
372    *right_marker = apr_psprintf(pool, ">>>>>>> %s", right_label);
373  else
374    *right_marker = ">>>>>>> .new";
375}
376
377/* Do a 3-way merge of the files at paths LEFT, DETRANSLATED_TARGET,
378 * and RIGHT, using diff options provided in MERGE_OPTIONS.  Store the merge
379 * result in the file RESULT_F.
380 * If there are conflicts, set *CONTAINS_CONFLICTS to true, and use
381 * TARGET_LABEL, LEFT_LABEL, and RIGHT_LABEL as labels for conflict
382 * markers.  Else, set *CONTAINS_CONFLICTS to false.
383 * Do all allocations in POOL. */
384static svn_error_t *
385do_text_merge(svn_boolean_t *contains_conflicts,
386              apr_file_t *result_f,
387              const apr_array_header_t *merge_options,
388              const char *detranslated_target,
389              const char *left,
390              const char *right,
391              const char *target_label,
392              const char *left_label,
393              const char *right_label,
394              apr_pool_t *pool)
395{
396  svn_diff_t *diff;
397  svn_stream_t *ostream;
398  const char *target_marker;
399  const char *left_marker;
400  const char *right_marker;
401  svn_diff_file_options_t *diff3_options;
402
403  diff3_options = svn_diff_file_options_create(pool);
404
405  if (merge_options)
406    SVN_ERR(svn_diff_file_options_parse(diff3_options,
407                                        merge_options, pool));
408
409
410  init_conflict_markers(&target_marker, &left_marker, &right_marker,
411                        target_label, left_label, right_label, pool);
412
413  SVN_ERR(svn_diff_file_diff3_2(&diff, left, detranslated_target, right,
414                                diff3_options, pool));
415
416  ostream = svn_stream_from_aprfile2(result_f, TRUE, pool);
417
418  SVN_ERR(svn_diff_file_output_merge2(ostream, diff,
419                                      left, detranslated_target, right,
420                                      left_marker,
421                                      target_marker,
422                                      right_marker,
423                                      "=======", /* separator */
424                                      svn_diff_conflict_display_modified_latest,
425                                      pool));
426  SVN_ERR(svn_stream_close(ostream));
427
428  *contains_conflicts = svn_diff_contains_conflicts(diff);
429
430  return SVN_NO_ERROR;
431}
432
433/* Same as do_text_merge() above, but use the external diff3
434 * command DIFF3_CMD to perform the merge.  Pass MERGE_OPTIONS
435 * to the diff3 command.  Do all allocations in POOL. */
436static svn_error_t *
437do_text_merge_external(svn_boolean_t *contains_conflicts,
438                       apr_file_t *result_f,
439                       const char *diff3_cmd,
440                       const apr_array_header_t *merge_options,
441                       const char *detranslated_target,
442                       const char *left_abspath,
443                       const char *right_abspath,
444                       const char *target_label,
445                       const char *left_label,
446                       const char *right_label,
447                       apr_pool_t *scratch_pool)
448{
449  int exit_code;
450
451  SVN_ERR(svn_io_run_diff3_3(&exit_code, ".",
452                             detranslated_target, left_abspath, right_abspath,
453                             target_label, left_label, right_label,
454                             result_f, diff3_cmd,
455                             merge_options, scratch_pool));
456
457  *contains_conflicts = exit_code == 1;
458
459  return SVN_NO_ERROR;
460}
461
462/* Preserve the three pre-merge files.
463
464   Create three empty files, with unique names that each include the
465   basename of TARGET_ABSPATH and one of LEFT_LABEL, RIGHT_LABEL and
466   TARGET_LABEL, in the directory that contains TARGET_ABSPATH.  Typical
467   names are "foo.c.r37" or "foo.c.2.mine".  Set *LEFT_COPY, *RIGHT_COPY and
468   *TARGET_COPY to their absolute paths.
469
470   Set *WORK_ITEMS to a list of new work items that will write copies of
471   LEFT_ABSPATH, RIGHT_ABSPATH and TARGET_ABSPATH into the three files,
472   translated to working-copy form.
473
474   The translation to working-copy form will be done according to the
475   versioned properties of TARGET_ABSPATH that are current when the work
476   queue items are executed.
477
478   If target_abspath is not versioned use detranslated_target_abspath
479   as the target file.
480       ### NOT IMPLEMENTED -- 'detranslated_target_abspath' is not used.
481*/
482static svn_error_t *
483preserve_pre_merge_files(svn_skel_t **work_items,
484                         const char **left_copy,
485                         const char **right_copy,
486                         const char **target_copy,
487                         const merge_target_t *mt,
488                         const char *left_abspath,
489                         const char *right_abspath,
490                         const char *left_label,
491                         const char *right_label,
492                         const char *target_label,
493                         const char *detranslated_target_abspath,
494                         svn_cancel_func_t cancel_func,
495                         void *cancel_baton,
496                         apr_pool_t *result_pool,
497                         apr_pool_t *scratch_pool)
498{
499  const char *tmp_left, *tmp_right, *detranslated_target_copy;
500  const char *dir_abspath, *target_name;
501  const char *wcroot_abspath, *temp_dir_abspath;
502  svn_skel_t *work_item, *last_items = NULL;
503
504  *work_items = NULL;
505
506  svn_dirent_split(&dir_abspath, &target_name, mt->local_abspath,
507                   scratch_pool);
508
509  SVN_ERR(svn_wc__db_get_wcroot(&wcroot_abspath, mt->db, mt->wri_abspath,
510                                scratch_pool, scratch_pool));
511  SVN_ERR(svn_wc__db_temp_wcroot_tempdir(&temp_dir_abspath, mt->db,
512                                         mt->wri_abspath,
513                                         scratch_pool, scratch_pool));
514
515  /* Create three empty files in DIR_ABSPATH, naming them with unique names
516     that each include TARGET_NAME and one of {LEFT,RIGHT,TARGET}_LABEL,
517     and set *{LEFT,RIGHT,TARGET}_COPY to those names. */
518  SVN_ERR(svn_io_open_uniquely_named(
519            NULL, left_copy, dir_abspath, target_name, left_label,
520            svn_io_file_del_none, result_pool, scratch_pool));
521  SVN_ERR(svn_io_open_uniquely_named(
522            NULL, right_copy, dir_abspath, target_name, right_label,
523            svn_io_file_del_none, result_pool, scratch_pool));
524  SVN_ERR(svn_io_open_uniquely_named(
525            NULL, target_copy, dir_abspath, target_name, target_label,
526            svn_io_file_del_none, result_pool, scratch_pool));
527
528  /* We preserve all the files with keywords expanded and line
529     endings in local (working) form. */
530
531  /* The workingqueue requires its paths to be in the subtree
532     relative to the wcroot path they are executed in.
533
534     Make our LEFT and RIGHT files 'local' if they aren't... */
535  if (! svn_dirent_is_ancestor(wcroot_abspath, left_abspath))
536    {
537      SVN_ERR(svn_io_open_unique_file3(NULL, &tmp_left, temp_dir_abspath,
538                                       svn_io_file_del_none,
539                                       scratch_pool, scratch_pool));
540      SVN_ERR(svn_io_copy_file(left_abspath, tmp_left, TRUE, scratch_pool));
541
542      /* And create a wq item to remove the file later */
543      SVN_ERR(svn_wc__wq_build_file_remove(&work_item, mt->db, wcroot_abspath,
544                                           tmp_left,
545                                           result_pool, scratch_pool));
546
547      last_items = svn_wc__wq_merge(last_items, work_item, result_pool);
548    }
549  else
550    tmp_left = left_abspath;
551
552  if (! svn_dirent_is_ancestor(wcroot_abspath, right_abspath))
553    {
554      SVN_ERR(svn_io_open_unique_file3(NULL, &tmp_right, temp_dir_abspath,
555                                       svn_io_file_del_none,
556                                       scratch_pool, scratch_pool));
557      SVN_ERR(svn_io_copy_file(right_abspath, tmp_right, TRUE, scratch_pool));
558
559      /* And create a wq item to remove the file later */
560      SVN_ERR(svn_wc__wq_build_file_remove(&work_item, mt->db, wcroot_abspath,
561                                           tmp_right,
562                                           result_pool, scratch_pool));
563
564      last_items = svn_wc__wq_merge(last_items, work_item, result_pool);
565    }
566  else
567    tmp_right = right_abspath;
568
569  /* NOTE: Callers must ensure that the svn:eol-style and
570     svn:keywords property values are correct in the currently
571     installed props.  With 'svn merge', it's no big deal.  But
572     when 'svn up' calls this routine, it needs to make sure that
573     this routine is using the newest property values that may
574     have been received *during* the update.  Since this routine
575     will be run from within a log-command, merge_file()
576     needs to make sure that a previous log-command to 'install
577     latest props' has already executed first.  Ben and I just
578     checked, and that is indeed the order in which the log items
579     are written, so everything should be fine.  Really.  */
580
581  /* Create LEFT and RIGHT backup files, in expanded form.
582     We use TARGET_ABSPATH's current properties to do the translation. */
583  /* Derive the basenames of the 3 backup files. */
584  SVN_ERR(svn_wc__wq_build_file_copy_translated(&work_item,
585                                                mt->db, mt->local_abspath,
586                                                tmp_left, *left_copy,
587                                                result_pool, scratch_pool));
588  *work_items = svn_wc__wq_merge(*work_items, work_item, result_pool);
589
590  SVN_ERR(svn_wc__wq_build_file_copy_translated(&work_item,
591                                                mt->db, mt->local_abspath,
592                                                tmp_right, *right_copy,
593                                                result_pool, scratch_pool));
594  *work_items = svn_wc__wq_merge(*work_items, work_item, result_pool);
595
596  /* Back up TARGET_ABSPATH through detranslation/retranslation:
597     the new translation properties may not match the current ones */
598  SVN_ERR(detranslate_wc_file(&detranslated_target_copy, mt, TRUE,
599                              mt->local_abspath,
600                              cancel_func, cancel_baton,
601                              scratch_pool, scratch_pool));
602
603  SVN_ERR(svn_wc__wq_build_file_copy_translated(&work_item,
604                                                mt->db, mt->local_abspath,
605                                                detranslated_target_copy,
606                                                *target_copy,
607                                                result_pool, scratch_pool));
608  *work_items = svn_wc__wq_merge(*work_items, work_item, result_pool);
609
610  /* And maybe delete some tempfiles */
611  SVN_ERR(svn_wc__wq_build_file_remove(&work_item, mt->db, wcroot_abspath,
612                                       detranslated_target_copy,
613                                       result_pool, scratch_pool));
614  *work_items = svn_wc__wq_merge(*work_items, work_item, result_pool);
615
616  *work_items = svn_wc__wq_merge(*work_items, last_items, result_pool);
617
618  return SVN_NO_ERROR;
619}
620
621/* Attempt a trivial merge of LEFT_ABSPATH and RIGHT_ABSPATH to
622 * the target file at TARGET_ABSPATH.
623 *
624 * These are the inherently trivial cases:
625 *
626 *   left == right == target         =>  no-op
627 *   left != right, left == target   =>  target := right
628 *
629 * This case is also treated as trivial:
630 *
631 *   left != right, right == target  =>  no-op
632 *
633 *   ### Strictly, this case is a conflict, and the no-op outcome is only
634 *       one of the possible resolutions.
635 *
636 *       TODO: Raise a conflict at this level and implement the 'no-op'
637 *       resolution of that conflict at a higher level, in preparation for
638 *       being able to support stricter conflict detection.
639 *
640 * This case is inherently trivial but not currently handled here:
641 *
642 *   left == right != target         =>  no-op
643 *
644 * The files at LEFT_ABSPATH and RIGHT_ABSPATH are in repository normal
645 * form.  The file at DETRANSLATED_TARGET_ABSPATH is a copy of the target,
646 * 'detranslated' to repository normal form, or may be the target file
647 * itself if no translation is necessary.
648 *
649 * When this function updates the target file, it translates to working copy
650 * form.
651 *
652 * On success, set *MERGE_OUTCOME to SVN_WC_MERGE_MERGED in case the
653 * target was changed, or to SVN_WC_MERGE_UNCHANGED if the target was not
654 * changed. Install work queue items allocated in RESULT_POOL in *WORK_ITEMS.
655 * On failure, set *MERGE_OUTCOME to SVN_WC_MERGE_NO_MERGE.
656 */
657static svn_error_t *
658merge_file_trivial(svn_skel_t **work_items,
659                   enum svn_wc_merge_outcome_t *merge_outcome,
660                   const char *left_abspath,
661                   const char *right_abspath,
662                   const char *target_abspath,
663                   const char *detranslated_target_abspath,
664                   svn_boolean_t dry_run,
665                   svn_wc__db_t *db,
666                   svn_cancel_func_t cancel_func,
667                   void *cancel_baton,
668                   apr_pool_t *result_pool,
669                   apr_pool_t *scratch_pool)
670{
671  svn_skel_t *work_item;
672  svn_boolean_t same_left_right;
673  svn_boolean_t same_right_target;
674  svn_boolean_t same_left_target;
675  svn_node_kind_t kind;
676  svn_boolean_t is_special;
677
678  /* If the target is not a normal file, do not attempt a trivial merge. */
679  SVN_ERR(svn_io_check_special_path(target_abspath, &kind, &is_special,
680                                    scratch_pool));
681  if (kind != svn_node_file || is_special)
682    {
683      *merge_outcome = svn_wc_merge_no_merge;
684      return SVN_NO_ERROR;
685    }
686
687  /* Check the files */
688  SVN_ERR(svn_io_files_contents_three_same_p(&same_left_right,
689                                             &same_right_target,
690                                             &same_left_target,
691                                             left_abspath,
692                                             right_abspath,
693                                             detranslated_target_abspath,
694                                             scratch_pool));
695
696  /* If the LEFT side of the merge is equal to WORKING, then we can
697   * copy RIGHT directly. */
698  if (same_left_target)
699    {
700      /* If the left side equals the right side, there is no change to merge
701       * so we leave the target unchanged. */
702      if (same_left_right)
703        {
704          *merge_outcome = svn_wc_merge_unchanged;
705        }
706      else
707        {
708          *merge_outcome = svn_wc_merge_merged;
709          if (!dry_run)
710            {
711              const char *wcroot_abspath;
712              svn_boolean_t delete_src = FALSE;
713
714              /* The right_abspath might be outside our working copy. In that
715                 case we should copy the file to a safe location before
716                 installing to avoid breaking the workqueue.
717
718                 This matches the behavior in preserve_pre_merge_files */
719
720              SVN_ERR(svn_wc__db_get_wcroot(&wcroot_abspath,
721                                            db, target_abspath,
722                                            scratch_pool, scratch_pool));
723
724              if (!svn_dirent_is_child(wcroot_abspath, right_abspath, NULL))
725                {
726                  svn_stream_t *tmp_src;
727                  svn_stream_t *tmp_dst;
728
729                  SVN_ERR(svn_stream_open_readonly(&tmp_src, right_abspath,
730                                                   scratch_pool,
731                                                   scratch_pool));
732
733                  SVN_ERR(svn_wc__open_writable_base(&tmp_dst, &right_abspath,
734                                                     NULL, NULL,
735                                                     db, target_abspath,
736                                                     scratch_pool,
737                                                     scratch_pool));
738
739                  SVN_ERR(svn_stream_copy3(tmp_src, tmp_dst,
740                                           cancel_func, cancel_baton,
741                                           scratch_pool));
742
743                  delete_src = TRUE;
744                }
745
746              SVN_ERR(svn_wc__wq_build_file_install(
747                        &work_item, db, target_abspath, right_abspath,
748                        FALSE /* use_commit_times */,
749                        FALSE /* record_fileinfo */,
750                        result_pool, scratch_pool));
751              *work_items = svn_wc__wq_merge(*work_items, work_item,
752                                             result_pool);
753
754              if (delete_src)
755                {
756                  SVN_ERR(svn_wc__wq_build_file_remove(
757                                    &work_item, db, wcroot_abspath,
758                                    right_abspath,
759                                    result_pool, scratch_pool));
760                  *work_items = svn_wc__wq_merge(*work_items, work_item,
761                                                 result_pool);
762                }
763            }
764        }
765
766      return SVN_NO_ERROR;
767    }
768  else
769    {
770      /* If the locally existing, changed file equals the incoming 'right'
771       * file, there is no conflict.  For binary files, we historically
772       * conflicted them needlessly, while merge_text_file figured it out
773       * eventually and returned svn_wc_merge_unchanged for them, which
774       * is what we do here. */
775      if (same_right_target)
776        {
777          *merge_outcome = svn_wc_merge_unchanged;
778          return SVN_NO_ERROR;
779        }
780    }
781
782  *merge_outcome = svn_wc_merge_no_merge;
783  return SVN_NO_ERROR;
784}
785
786
787/* Handle a non-trivial merge of 'text' files.  (Assume that a trivial
788 * merge was not possible.)
789 *
790 * Set *WORK_ITEMS, *CONFLICT_SKEL and *MERGE_OUTCOME according to the
791 * result -- to install the merged file, or to indicate a conflict.
792 *
793 * On successful merge, leave the result in a temporary file and set
794 * *WORK_ITEMS to hold work items that will translate and install that
795 * file into its proper form and place (unless DRY_RUN) and delete the
796 * temporary file (in any case).  Set *MERGE_OUTCOME to 'merged' or
797 * 'unchanged'.
798 *
799 * If a conflict occurs, set *MERGE_OUTCOME to 'conflicted', and (unless
800 * DRY_RUN) set *WORK_ITEMS and *CONFLICT_SKEL to record the conflict
801 * and copies of the pre-merge files.  See preserve_pre_merge_files()
802 * for details.
803 *
804 * On entry, all of the output pointers must be non-null and *CONFLICT_SKEL
805 * must either point to an existing conflict skel or be NULL.
806 */
807static svn_error_t*
808merge_text_file(svn_skel_t **work_items,
809                svn_skel_t **conflict_skel,
810                enum svn_wc_merge_outcome_t *merge_outcome,
811                const merge_target_t *mt,
812                const char *left_abspath,
813                const char *right_abspath,
814                const char *left_label,
815                const char *right_label,
816                const char *target_label,
817                svn_boolean_t dry_run,
818                const char *detranslated_target_abspath,
819                svn_cancel_func_t cancel_func,
820                void *cancel_baton,
821                apr_pool_t *result_pool,
822                apr_pool_t *scratch_pool)
823{
824  apr_pool_t *pool = scratch_pool;  /* ### temporary rename  */
825  svn_boolean_t contains_conflicts;
826  apr_file_t *result_f;
827  const char *result_target;
828  const char *base_name;
829  const char *temp_dir;
830  svn_skel_t *work_item;
831
832  *work_items = NULL;
833
834  base_name = svn_dirent_basename(mt->local_abspath, scratch_pool);
835
836  /* Open a second temporary file for writing; this is where diff3
837     will write the merged results.  We want to use a tempfile
838     with a name that reflects the original, in case this
839     ultimately winds up in a conflict resolution editor.  */
840  SVN_ERR(svn_wc__db_temp_wcroot_tempdir(&temp_dir, mt->db, mt->wri_abspath,
841                                         pool, pool));
842  SVN_ERR(svn_io_open_uniquely_named(&result_f, &result_target,
843                                     temp_dir, base_name, ".tmp",
844                                     svn_io_file_del_none, pool, pool));
845
846  /* Run the external or internal merge, as requested. */
847  if (mt->diff3_cmd)
848      SVN_ERR(do_text_merge_external(&contains_conflicts,
849                                     result_f,
850                                     mt->diff3_cmd,
851                                     mt->merge_options,
852                                     detranslated_target_abspath,
853                                     left_abspath,
854                                     right_abspath,
855                                     target_label,
856                                     left_label,
857                                     right_label,
858                                     pool));
859  else /* Use internal merge. */
860    SVN_ERR(do_text_merge(&contains_conflicts,
861                          result_f,
862                          mt->merge_options,
863                          detranslated_target_abspath,
864                          left_abspath,
865                          right_abspath,
866                          target_label,
867                          left_label,
868                          right_label,
869                          pool));
870
871  SVN_ERR(svn_io_file_close(result_f, pool));
872
873  /* Determine the MERGE_OUTCOME, and record any conflict. */
874  if (contains_conflicts && ! dry_run)
875    {
876      *merge_outcome = svn_wc_merge_conflict;
877      if (*merge_outcome == svn_wc_merge_conflict)
878        {
879          const char *left_copy, *right_copy, *target_copy;
880
881          /* Preserve the three conflict files */
882          SVN_ERR(preserve_pre_merge_files(
883                    &work_item,
884                    &left_copy, &right_copy, &target_copy,
885                    mt, left_abspath, right_abspath,
886                    left_label, right_label, target_label,
887                    detranslated_target_abspath,
888                    cancel_func, cancel_baton,
889                    result_pool, scratch_pool));
890          *work_items = svn_wc__wq_merge(*work_items, work_item, result_pool);
891
892          /* Track the conflict marker files in the metadata. */
893
894          if (!*conflict_skel)
895            *conflict_skel = svn_wc__conflict_skel_create(result_pool);
896
897          SVN_ERR(svn_wc__conflict_skel_add_text_conflict(*conflict_skel,
898                                                          mt->db, mt->local_abspath,
899                                                          target_copy,
900                                                          left_copy,
901                                                          right_copy,
902                                                          result_pool,
903                                                          scratch_pool));
904        }
905
906      if (*merge_outcome == svn_wc_merge_merged)
907        goto done;
908    }
909  else if (contains_conflicts && dry_run)
910      *merge_outcome = svn_wc_merge_conflict;
911  else
912    {
913      svn_boolean_t same, special;
914
915      /* If 'special', then use the detranslated form of the
916         target file.  This is so we don't try to follow symlinks,
917         but the same treatment is probably also appropriate for
918         whatever special file types we may invent in the future. */
919      SVN_ERR(svn_wc__get_translate_info(NULL, NULL, NULL,
920                                         &special, mt->db, mt->local_abspath,
921                                         mt->old_actual_props, TRUE,
922                                         pool, pool));
923      SVN_ERR(svn_io_files_contents_same_p(&same, result_target,
924                                           (special ?
925                                              detranslated_target_abspath :
926                                              mt->local_abspath),
927                                           pool));
928
929      *merge_outcome = same ? svn_wc_merge_unchanged : svn_wc_merge_merged;
930    }
931
932  if (*merge_outcome != svn_wc_merge_unchanged && ! dry_run)
933    {
934      /* replace TARGET_ABSPATH with the new merged file, expanding. */
935      SVN_ERR(svn_wc__wq_build_file_install(&work_item,
936                                            mt->db, mt->local_abspath,
937                                            result_target,
938                                            FALSE /* use_commit_times */,
939                                            FALSE /* record_fileinfo */,
940                                            result_pool, scratch_pool));
941      *work_items = svn_wc__wq_merge(*work_items, work_item, result_pool);
942    }
943
944done:
945  /* Remove the tempfile after use */
946  SVN_ERR(svn_wc__wq_build_file_remove(&work_item, mt->db, mt->local_abspath,
947                                       result_target,
948                                       result_pool, scratch_pool));
949
950  *work_items = svn_wc__wq_merge(*work_items, work_item, result_pool);
951
952  return SVN_NO_ERROR;
953}
954
955/* Handle a non-trivial merge of 'binary' files: don't actually merge, just
956 * flag a conflict.  (Assume that a trivial merge was not possible.)
957 *
958 * Copy* the files at LEFT_ABSPATH and RIGHT_ABSPATH into the same directory
959 * as the target file, giving them unique names that start with the target
960 * file's name and end with LEFT_LABEL and RIGHT_LABEL respectively.
961 * If the merge target has been 'detranslated' to repository normal form,
962 * move the detranslated file similarly to a unique name ending with
963 * TARGET_LABEL.
964 *
965 * ### * Why do we copy the left and right temp files when we could (maybe
966 *     not always?) move them?
967 *
968 * On entry, all of the output pointers must be non-null and *CONFLICT_SKEL
969 * must either point to an existing conflict skel or be NULL.
970 *
971 * Set *WORK_ITEMS, *CONFLICT_SKEL and *MERGE_OUTCOME to indicate the
972 * conflict.
973 *
974 * ### Why do we not use preserve_pre_merge_files() in here?  The
975 *     behaviour would be slightly different, more consistent: the
976 *     preserved 'left' and 'right' files would be translated to working
977 *     copy form, which may make a difference when a binary file
978 *     contains keyword expansions or when some versions of the file are
979 *     not 'binary' even though we're merging in 'binary files' mode.
980 */
981static svn_error_t *
982merge_binary_file(svn_skel_t **work_items,
983                  svn_skel_t **conflict_skel,
984                  enum svn_wc_merge_outcome_t *merge_outcome,
985                  const merge_target_t *mt,
986                  const char *left_abspath,
987                  const char *right_abspath,
988                  const char *left_label,
989                  const char *right_label,
990                  const char *target_label,
991                  svn_boolean_t dry_run,
992                  const char *detranslated_target_abspath,
993                  apr_pool_t *result_pool,
994                  apr_pool_t *scratch_pool)
995{
996  apr_pool_t *pool = scratch_pool;  /* ### temporary rename  */
997  /* ### when making the binary-file backups, should we be honoring
998     keywords and eol stuff?   */
999  const char *left_copy, *right_copy;
1000  const char *merge_dirpath, *merge_filename;
1001  const char *conflict_wrk;
1002
1003  *work_items = NULL;
1004
1005  svn_dirent_split(&merge_dirpath, &merge_filename, mt->local_abspath, pool);
1006
1007  if (dry_run)
1008    {
1009      *merge_outcome = svn_wc_merge_conflict;
1010      return SVN_NO_ERROR;
1011    }
1012
1013  /* reserve names for backups of left and right fulltexts */
1014  SVN_ERR(svn_io_open_uniquely_named(NULL,
1015                                     &left_copy,
1016                                     merge_dirpath,
1017                                     merge_filename,
1018                                     left_label,
1019                                     svn_io_file_del_none,
1020                                     pool, pool));
1021
1022  SVN_ERR(svn_io_open_uniquely_named(NULL,
1023                                     &right_copy,
1024                                     merge_dirpath,
1025                                     merge_filename,
1026                                     right_label,
1027                                     svn_io_file_del_none,
1028                                     pool, pool));
1029
1030  /* create the backup files */
1031  SVN_ERR(svn_io_copy_file(left_abspath, left_copy, TRUE, pool));
1032  SVN_ERR(svn_io_copy_file(right_abspath, right_copy, TRUE, pool));
1033
1034  /* Was the merge target detranslated? */
1035  if (strcmp(mt->local_abspath, detranslated_target_abspath) != 0)
1036    {
1037      /* Create a .mine file too */
1038      SVN_ERR(svn_io_open_uniquely_named(NULL,
1039                                         &conflict_wrk,
1040                                         merge_dirpath,
1041                                         merge_filename,
1042                                         target_label,
1043                                         svn_io_file_del_none,
1044                                         pool, pool));
1045      SVN_ERR(svn_wc__wq_build_file_move(work_items, mt->db,
1046                                         mt->local_abspath,
1047                                         detranslated_target_abspath,
1048                                         conflict_wrk,
1049                                         pool, result_pool));
1050    }
1051  else
1052    {
1053      conflict_wrk = NULL;
1054    }
1055
1056  /* Mark target_abspath's entry as "Conflicted", and start tracking
1057     the backup files in the entry as well. */
1058  if (!*conflict_skel)
1059    *conflict_skel = svn_wc__conflict_skel_create(result_pool);
1060
1061  SVN_ERR(svn_wc__conflict_skel_add_text_conflict(*conflict_skel,
1062                                                  mt->db, mt->local_abspath,
1063                                                  conflict_wrk,
1064                                                  left_copy,
1065                                                  right_copy,
1066                                                  result_pool, scratch_pool));
1067
1068  *merge_outcome = svn_wc_merge_conflict; /* a conflict happened */
1069
1070  return SVN_NO_ERROR;
1071}
1072
1073svn_error_t *
1074svn_wc__internal_merge(svn_skel_t **work_items,
1075                       svn_skel_t **conflict_skel,
1076                       enum svn_wc_merge_outcome_t *merge_outcome,
1077                       svn_wc__db_t *db,
1078                       const char *left_abspath,
1079                       const char *right_abspath,
1080                       const char *target_abspath,
1081                       const char *wri_abspath,
1082                       const char *left_label,
1083                       const char *right_label,
1084                       const char *target_label,
1085                       apr_hash_t *old_actual_props,
1086                       svn_boolean_t dry_run,
1087                       const char *diff3_cmd,
1088                       const apr_array_header_t *merge_options,
1089                       const apr_array_header_t *prop_diff,
1090                       svn_cancel_func_t cancel_func,
1091                       void *cancel_baton,
1092                       apr_pool_t *result_pool,
1093                       apr_pool_t *scratch_pool)
1094{
1095  const char *detranslated_target_abspath;
1096  svn_boolean_t is_binary = FALSE;
1097  const svn_prop_t *mimeprop;
1098  svn_skel_t *work_item;
1099  merge_target_t mt;
1100
1101  SVN_ERR_ASSERT(svn_dirent_is_absolute(left_abspath));
1102  SVN_ERR_ASSERT(svn_dirent_is_absolute(right_abspath));
1103  SVN_ERR_ASSERT(svn_dirent_is_absolute(target_abspath));
1104
1105  *work_items = NULL;
1106
1107  /* Fill the merge target baton */
1108  mt.db = db;
1109  mt.local_abspath = target_abspath;
1110  mt.wri_abspath = wri_abspath;
1111  mt.old_actual_props = old_actual_props;
1112  mt.prop_diff = prop_diff;
1113  mt.diff3_cmd = diff3_cmd;
1114  mt.merge_options = merge_options;
1115
1116  /* Decide if the merge target is a text or binary file. */
1117  if ((mimeprop = get_prop(prop_diff, SVN_PROP_MIME_TYPE))
1118      && mimeprop->value)
1119    is_binary = svn_mime_type_is_binary(mimeprop->value->data);
1120  else
1121    {
1122      const char *value = svn_prop_get_value(mt.old_actual_props,
1123                                             SVN_PROP_MIME_TYPE);
1124
1125      is_binary = value && svn_mime_type_is_binary(value);
1126    }
1127
1128  SVN_ERR(detranslate_wc_file(&detranslated_target_abspath, &mt,
1129                              (! is_binary) && diff3_cmd != NULL,
1130                              target_abspath,
1131                              cancel_func, cancel_baton,
1132                              scratch_pool, scratch_pool));
1133
1134  /* We cannot depend on the left file to contain the same eols as the
1135     right file. If the merge target has mods, this will mark the entire
1136     file as conflicted, so we need to compensate. */
1137  SVN_ERR(maybe_update_target_eols(&left_abspath, prop_diff, left_abspath,
1138                                   cancel_func, cancel_baton,
1139                                   scratch_pool, scratch_pool));
1140
1141  SVN_ERR(merge_file_trivial(work_items, merge_outcome,
1142                             left_abspath, right_abspath,
1143                             target_abspath, detranslated_target_abspath,
1144                             dry_run, db, cancel_func, cancel_baton,
1145                             result_pool, scratch_pool));
1146  if (*merge_outcome == svn_wc_merge_no_merge)
1147    {
1148      /* We have a non-trivial merge.  If we classify it as a merge of
1149       * 'binary' files we'll just raise a conflict, otherwise we'll do
1150       * the actual merge of 'text' file contents. */
1151      if (is_binary)
1152        {
1153          /* Raise a text conflict */
1154          SVN_ERR(merge_binary_file(work_items,
1155                                    conflict_skel,
1156                                    merge_outcome,
1157                                    &mt,
1158                                    left_abspath,
1159                                    right_abspath,
1160                                    left_label,
1161                                    right_label,
1162                                    target_label,
1163                                    dry_run,
1164                                    detranslated_target_abspath,
1165                                    result_pool, scratch_pool));
1166        }
1167      else
1168        {
1169          SVN_ERR(merge_text_file(work_items,
1170                                  conflict_skel,
1171                                  merge_outcome,
1172                                  &mt,
1173                                  left_abspath,
1174                                  right_abspath,
1175                                  left_label,
1176                                  right_label,
1177                                  target_label,
1178                                  dry_run,
1179                                  detranslated_target_abspath,
1180                                  cancel_func, cancel_baton,
1181                                  result_pool, scratch_pool));
1182        }
1183    }
1184
1185  /* Merging is complete.  Regardless of text or binariness, we might
1186     need to tweak the executable bit on the new working file, and
1187     possibly make it read-only. */
1188  if (! dry_run)
1189    {
1190      SVN_ERR(svn_wc__wq_build_sync_file_flags(&work_item, db,
1191                                               target_abspath,
1192                                               result_pool, scratch_pool));
1193      *work_items = svn_wc__wq_merge(*work_items, work_item, result_pool);
1194    }
1195
1196  return SVN_NO_ERROR;
1197}
1198
1199
1200svn_error_t *
1201svn_wc_merge5(enum svn_wc_merge_outcome_t *merge_content_outcome,
1202              enum svn_wc_notify_state_t *merge_props_outcome,
1203              svn_wc_context_t *wc_ctx,
1204              const char *left_abspath,
1205              const char *right_abspath,
1206              const char *target_abspath,
1207              const char *left_label,
1208              const char *right_label,
1209              const char *target_label,
1210              const svn_wc_conflict_version_t *left_version,
1211              const svn_wc_conflict_version_t *right_version,
1212              svn_boolean_t dry_run,
1213              const char *diff3_cmd,
1214              const apr_array_header_t *merge_options,
1215              apr_hash_t *original_props,
1216              const apr_array_header_t *prop_diff,
1217              svn_wc_conflict_resolver_func2_t conflict_func,
1218              void *conflict_baton,
1219              svn_cancel_func_t cancel_func,
1220              void *cancel_baton,
1221              apr_pool_t *scratch_pool)
1222{
1223  const char *dir_abspath = svn_dirent_dirname(target_abspath, scratch_pool);
1224  svn_skel_t *work_items;
1225  svn_skel_t *conflict_skel = NULL;
1226  apr_hash_t *pristine_props = NULL;
1227  apr_hash_t *old_actual_props;
1228  apr_hash_t *new_actual_props = NULL;
1229
1230  SVN_ERR_ASSERT(svn_dirent_is_absolute(left_abspath));
1231  SVN_ERR_ASSERT(svn_dirent_is_absolute(right_abspath));
1232  SVN_ERR_ASSERT(svn_dirent_is_absolute(target_abspath));
1233
1234  /* Before we do any work, make sure we hold a write lock.  */
1235  if (!dry_run)
1236    SVN_ERR(svn_wc__write_check(wc_ctx->db, dir_abspath, scratch_pool));
1237
1238  /* Sanity check:  the merge target must be a file under revision control */
1239  {
1240    svn_wc__db_status_t status;
1241    svn_node_kind_t kind;
1242    svn_boolean_t had_props;
1243    svn_boolean_t props_mod;
1244    svn_boolean_t conflicted;
1245
1246    SVN_ERR(svn_wc__db_read_info(&status, &kind, NULL, NULL, NULL, NULL, NULL,
1247                                 NULL, NULL, NULL, NULL, NULL, NULL, NULL,
1248                                 NULL, NULL, NULL, NULL, NULL, NULL,
1249                                 &conflicted, NULL, &had_props, &props_mod,
1250                                 NULL, NULL, NULL,
1251                                 wc_ctx->db, target_abspath,
1252                                 scratch_pool, scratch_pool));
1253
1254    if (kind != svn_node_file || (status != svn_wc__db_status_normal
1255                                  && status != svn_wc__db_status_added))
1256      {
1257        *merge_content_outcome = svn_wc_merge_no_merge;
1258        if (merge_props_outcome)
1259          *merge_props_outcome = svn_wc_notify_state_unchanged;
1260        return SVN_NO_ERROR;
1261      }
1262
1263    if (conflicted)
1264      {
1265        svn_boolean_t text_conflicted;
1266        svn_boolean_t prop_conflicted;
1267        svn_boolean_t tree_conflicted;
1268
1269        SVN_ERR(svn_wc__internal_conflicted_p(&text_conflicted,
1270                                              &prop_conflicted,
1271                                              &tree_conflicted,
1272                                              wc_ctx->db, target_abspath,
1273                                              scratch_pool));
1274
1275        /* We can't install two prop conflicts on a single node, so
1276           avoid even checking that we have to merge it */
1277        if (text_conflicted || prop_conflicted || tree_conflicted)
1278          {
1279            return svn_error_createf(
1280                            SVN_ERR_WC_PATH_UNEXPECTED_STATUS, NULL,
1281                            _("Can't merge into conflicted node '%s'"),
1282                            svn_dirent_local_style(target_abspath,
1283                                                   scratch_pool));
1284          }
1285        /* else: Conflict was resolved by removing markers */
1286      }
1287
1288    if (merge_props_outcome && had_props)
1289      {
1290        SVN_ERR(svn_wc__db_read_pristine_props(&pristine_props,
1291                                               wc_ctx->db, target_abspath,
1292                                               scratch_pool, scratch_pool));
1293      }
1294    else if (merge_props_outcome)
1295      pristine_props = apr_hash_make(scratch_pool);
1296
1297    if (props_mod)
1298      {
1299        SVN_ERR(svn_wc__db_read_props(&old_actual_props,
1300                                      wc_ctx->db, target_abspath,
1301                                      scratch_pool, scratch_pool));
1302      }
1303    else if (pristine_props)
1304      old_actual_props = pristine_props;
1305    else
1306      old_actual_props = apr_hash_make(scratch_pool);
1307  }
1308
1309  /* Merge the properties, if requested.  We merge the properties first
1310   * because the properties can affect the text (EOL style, keywords). */
1311  if (merge_props_outcome)
1312    {
1313      int i;
1314
1315      /* The PROPCHANGES may not have non-"normal" properties in it. If entry
1316         or wc props were allowed, then the following code would install them
1317         into the BASE and/or WORKING properties(!).  */
1318      for (i = prop_diff->nelts; i--; )
1319        {
1320          const svn_prop_t *change = &APR_ARRAY_IDX(prop_diff, i, svn_prop_t);
1321
1322          if (!svn_wc_is_normal_prop(change->name))
1323            return svn_error_createf(SVN_ERR_BAD_PROP_KIND, NULL,
1324                                     _("The property '%s' may not be merged "
1325                                       "into '%s'."),
1326                                     change->name,
1327                                     svn_dirent_local_style(target_abspath,
1328                                                            scratch_pool));
1329        }
1330
1331      SVN_ERR(svn_wc__merge_props(&conflict_skel,
1332                                  merge_props_outcome,
1333                                  &new_actual_props,
1334                                  wc_ctx->db, target_abspath,
1335                                  original_props, pristine_props, old_actual_props,
1336                                  prop_diff,
1337                                  scratch_pool, scratch_pool));
1338    }
1339
1340  /* Merge the text. */
1341  SVN_ERR(svn_wc__internal_merge(&work_items,
1342                                 &conflict_skel,
1343                                 merge_content_outcome,
1344                                 wc_ctx->db,
1345                                 left_abspath,
1346                                 right_abspath,
1347                                 target_abspath,
1348                                 target_abspath,
1349                                 left_label, right_label, target_label,
1350                                 old_actual_props,
1351                                 dry_run,
1352                                 diff3_cmd,
1353                                 merge_options,
1354                                 prop_diff,
1355                                 cancel_func, cancel_baton,
1356                                 scratch_pool, scratch_pool));
1357
1358  /* If this isn't a dry run, then update the DB, run the work, and
1359   * call the conflict resolver callback.  */
1360  if (!dry_run)
1361    {
1362      if (conflict_skel)
1363        {
1364          svn_skel_t *work_item;
1365
1366          SVN_ERR(svn_wc__conflict_skel_set_op_merge(conflict_skel,
1367                                                     left_version,
1368                                                     right_version,
1369                                                     scratch_pool,
1370                                                     scratch_pool));
1371
1372          SVN_ERR(svn_wc__conflict_create_markers(&work_item,
1373                                                  wc_ctx->db, target_abspath,
1374                                                  conflict_skel,
1375                                                  scratch_pool, scratch_pool));
1376
1377          work_items = svn_wc__wq_merge(work_items, work_item, scratch_pool);
1378        }
1379
1380      if (new_actual_props)
1381        SVN_ERR(svn_wc__db_op_set_props(wc_ctx->db, target_abspath,
1382                                        new_actual_props,
1383                                        svn_wc__has_magic_property(prop_diff),
1384                                        conflict_skel, work_items,
1385                                        scratch_pool));
1386      else if (conflict_skel)
1387        SVN_ERR(svn_wc__db_op_mark_conflict(wc_ctx->db, target_abspath,
1388                                            conflict_skel, work_items,
1389                                            scratch_pool));
1390      else if (work_items)
1391        SVN_ERR(svn_wc__db_wq_add(wc_ctx->db, target_abspath, work_items,
1392                                  scratch_pool));
1393
1394      if (work_items)
1395        SVN_ERR(svn_wc__wq_run(wc_ctx->db, target_abspath,
1396                               cancel_func, cancel_baton,
1397                               scratch_pool));
1398
1399      if (conflict_skel && conflict_func)
1400        {
1401          svn_boolean_t text_conflicted, prop_conflicted;
1402
1403          SVN_ERR(svn_wc__conflict_invoke_resolver(
1404                    wc_ctx->db, target_abspath,
1405                    conflict_skel, merge_options,
1406                    conflict_func, conflict_baton,
1407                    cancel_func, cancel_baton,
1408                    scratch_pool));
1409
1410          /* Reset *MERGE_CONTENT_OUTCOME etc. if a conflict was resolved. */
1411          SVN_ERR(svn_wc__internal_conflicted_p(
1412                    &text_conflicted, &prop_conflicted, NULL,
1413                    wc_ctx->db, target_abspath, scratch_pool));
1414          if (*merge_props_outcome == svn_wc_notify_state_conflicted
1415              && ! prop_conflicted)
1416            *merge_props_outcome = svn_wc_notify_state_merged;
1417          if (*merge_content_outcome == svn_wc_merge_conflict
1418              && ! text_conflicted)
1419            *merge_content_outcome = svn_wc_merge_merged;
1420        }
1421    }
1422
1423  return SVN_NO_ERROR;
1424}
1425