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