merge.c revision 251895
1251881Speter/*
2251881Speter * merge.c:  merging changes into a working file
3251881Speter *
4251881Speter * ====================================================================
5251881Speter *    Licensed to the Apache Software Foundation (ASF) under one
6251881Speter *    or more contributor license agreements.  See the NOTICE file
7251881Speter *    distributed with this work for additional information
8251881Speter *    regarding copyright ownership.  The ASF licenses this file
9251881Speter *    to you under the Apache License, Version 2.0 (the
10251881Speter *    "License"); you may not use this file except in compliance
11251881Speter *    with the License.  You may obtain a copy of the License at
12251881Speter *
13251881Speter *      http://www.apache.org/licenses/LICENSE-2.0
14251881Speter *
15251881Speter *    Unless required by applicable law or agreed to in writing,
16251881Speter *    software distributed under the License is distributed on an
17251881Speter *    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
18251881Speter *    KIND, either express or implied.  See the License for the
19251881Speter *    specific language governing permissions and limitations
20251881Speter *    under the License.
21251881Speter * ====================================================================
22251881Speter */
23251881Speter
24251881Speter#include "svn_wc.h"
25251881Speter#include "svn_diff.h"
26251881Speter#include "svn_dirent_uri.h"
27251881Speter#include "svn_path.h"
28251881Speter#include "svn_pools.h"
29251881Speter
30251881Speter#include "wc.h"
31251881Speter#include "adm_files.h"
32251881Speter#include "conflicts.h"
33251881Speter#include "translate.h"
34251881Speter#include "workqueue.h"
35251881Speter
36251881Speter#include "private/svn_skel.h"
37251881Speter
38251881Speter#include "svn_private_config.h"
39251881Speter
40251881Speter/* Contains some information on the merge target before merge, and some
41251881Speter   information needed for the diff processing. */
42251881Spetertypedef struct merge_target_t
43251881Speter{
44251881Speter  svn_wc__db_t *db;                         /* The DB used to access target */
45251881Speter  const char *local_abspath;                /* The absolute path to target */
46251881Speter  const char *wri_abspath;                  /* The working copy of target */
47251881Speter
48251881Speter  apr_hash_t *old_actual_props;                 /* The set of actual properties
49251881Speter                                               before merging */
50251881Speter  const apr_array_header_t *prop_diff;      /* The property changes */
51251881Speter
52251881Speter  const char *diff3_cmd;                    /* The diff3 command and options */
53251881Speter  const apr_array_header_t *merge_options;
54251881Speter
55251881Speter} merge_target_t;
56251881Speter
57251881Speter
58251881Speter/* Return a pointer to the svn_prop_t structure from PROP_DIFF
59251881Speter   belonging to PROP_NAME, if any.  NULL otherwise.*/
60251881Speterstatic const svn_prop_t *
61251881Speterget_prop(const apr_array_header_t *prop_diff,
62251881Speter         const char *prop_name)
63251881Speter{
64251881Speter  if (prop_diff)
65251881Speter    {
66251881Speter      int i;
67251881Speter      for (i = 0; i < prop_diff->nelts; i++)
68251881Speter        {
69251881Speter          const svn_prop_t *elt = &APR_ARRAY_IDX(prop_diff, i,
70251881Speter                                                 svn_prop_t);
71251881Speter
72251881Speter          if (strcmp(elt->name, prop_name) == 0)
73251881Speter            return elt;
74251881Speter        }
75251881Speter    }
76251881Speter
77251881Speter  return NULL;
78251881Speter}
79251881Speter
80251881Speter
81251881Speter/* Detranslate a working copy file MERGE_TARGET to achieve the effect of:
82251881Speter
83251881Speter   1. Detranslate
84251881Speter   2. Install new props
85251881Speter   3. Retranslate
86251881Speter   4. Detranslate
87251881Speter
88251881Speter   in one pass, to get a file which can be compared with the left and right
89251881Speter   files which are in repository normal form.
90251881Speter
91251881Speter   Property changes make this a little complex though. Changes in
92251881Speter
93251881Speter   - svn:mime-type
94251881Speter   - svn:eol-style
95251881Speter   - svn:keywords
96251881Speter   - svn:special
97251881Speter
98251881Speter   may change the way a file is translated.
99251881Speter
100251881Speter   Effect for svn:mime-type:
101251881Speter
102251881Speter     If svn:mime-type is considered 'binary', we ignore svn:eol-style (but
103251881Speter     still translate keywords).
104251881Speter
105251881Speter     I) both old and new mime-types are texty
106251881Speter        -> just do the translation dance (as lined out below)
107251881Speter           ### actually we do a shortcut with just one translation:
108251881Speter           detranslate with the old keywords and ... eol-style
109251881Speter           (the new re+detranslation is a no-op w.r.t. keywords [1])
110251881Speter
111251881Speter     II) the old one is texty, the new one is binary
112251881Speter        -> detranslate with the old eol-style and keywords
113251881Speter           (the new re+detranslation is a no-op [1])
114251881Speter
115251881Speter     III) the old one is binary, the new one texty
116251881Speter        -> detranslate with the old keywords and new eol-style
117251881Speter           (the old detranslation is a no-op w.r.t. eol, and
118251881Speter            the new re+detranslation is a no-op w.r.t. keywords [1])
119251881Speter
120251881Speter     IV) the old and new ones are binary
121251881Speter        -> detranslate with the old keywords
122251881Speter           (the new re+detranslation is a no-op [1])
123251881Speter
124251881Speter   Effect for svn:eol-style
125251881Speter
126251881Speter     I) On add or change of svn:eol-style, use the new value
127251881Speter
128251881Speter     II) otherwise: use the old value (absent means 'no translation')
129251881Speter
130251881Speter   Effect for svn:keywords
131251881Speter
132251881Speter     Always use the old settings (re+detranslation are no-op [1]).
133251881Speter
134251881Speter     [1] Translation of keywords from repository normal form to WC form and
135251881Speter         back is normally a no-op, but is not a no-op if text contains a kw
136251881Speter         that is only enabled by the new props and is present in non-
137251881Speter         contracted form (such as "$Rev: 1234 $").  If we want to catch this
138251881Speter         case we should detranslate with both the old & the new keywords
139251881Speter         together.
140251881Speter
141251881Speter   Effect for svn:special
142251881Speter
143251881Speter     Always use the old settings (re+detranslation are no-op).
144251881Speter
145251881Speter  Sets *DETRANSLATED_ABSPATH to the path to the detranslated file,
146251881Speter  this may be the same as SOURCE_ABSPATH if FORCE_COPY is FALSE and no
147251881Speter  translation is required.
148251881Speter
149251881Speter  If FORCE_COPY is FALSE and *DETRANSLATED_ABSPATH is a file distinct
150251881Speter  from SOURCE_ABSPATH then the file will be deleted on RESULT_POOL
151251881Speter  cleanup.
152251881Speter
153251881Speter  If FORCE_COPY is TRUE then *DETRANSLATED_ABSPATH will always be a
154251881Speter  new file distinct from SOURCE_ABSPATH and it will be the callers
155251881Speter  responsibility to delete the file.
156251881Speter
157251881Speter*/
158251881Speterstatic svn_error_t *
159251881Speterdetranslate_wc_file(const char **detranslated_abspath,
160251881Speter                    const merge_target_t *mt,
161251881Speter                    svn_boolean_t force_copy,
162251881Speter                    const char *source_abspath,
163251881Speter                    svn_cancel_func_t cancel_func,
164251881Speter                    void *cancel_baton,
165251881Speter                    apr_pool_t *result_pool,
166251881Speter                    apr_pool_t *scratch_pool)
167251881Speter{
168251881Speter  svn_boolean_t old_is_binary, new_is_binary;
169251881Speter  svn_subst_eol_style_t style;
170251881Speter  const char *eol;
171251881Speter  apr_hash_t *keywords;
172251881Speter  svn_boolean_t special;
173251881Speter
174251881Speter  {
175251881Speter    const char *old_mime_value
176251881Speter      = svn_prop_get_value(mt->old_actual_props, SVN_PROP_MIME_TYPE);
177251881Speter    const svn_prop_t *prop = get_prop(mt->prop_diff, SVN_PROP_MIME_TYPE);
178251881Speter    const char *new_mime_value
179251881Speter      = prop ? (prop->value ? prop->value->data : NULL) : old_mime_value;
180251881Speter
181251881Speter    old_is_binary = old_mime_value && svn_mime_type_is_binary(old_mime_value);
182251881Speter    new_is_binary = new_mime_value && svn_mime_type_is_binary(new_mime_value);;
183251881Speter  }
184251881Speter
185251881Speter  /* See what translations we want to do */
186251881Speter  if (old_is_binary && new_is_binary)
187251881Speter    {
188251881Speter      /* Case IV. Old and new props 'binary': detranslate keywords only */
189251881Speter      SVN_ERR(svn_wc__get_translate_info(NULL, NULL, &keywords, NULL,
190251881Speter                                         mt->db, mt->local_abspath,
191251881Speter                                         mt->old_actual_props, TRUE,
192251881Speter                                         scratch_pool, scratch_pool));
193251881Speter      /* ### Why override 'special'? Elsewhere it has precedence. */
194251881Speter      special = FALSE;
195251881Speter      eol = NULL;
196251881Speter      style = svn_subst_eol_style_none;
197251881Speter    }
198251881Speter  else if (!old_is_binary && new_is_binary)
199251881Speter    {
200251881Speter      /* Case II. Old props indicate texty, new props indicate binary:
201251881Speter         detranslate keywords and old eol-style */
202251881Speter      SVN_ERR(svn_wc__get_translate_info(&style, &eol,
203251881Speter                                         &keywords,
204251881Speter                                         &special,
205251881Speter                                         mt->db, mt->local_abspath,
206251881Speter                                         mt->old_actual_props, TRUE,
207251881Speter                                         scratch_pool, scratch_pool));
208251881Speter    }
209251881Speter  else
210251881Speter    {
211251881Speter      /* Case I & III. New props indicate texty, regardless of old props */
212251881Speter
213251881Speter      /* In case the file used to be special, detranslate specially */
214251881Speter      SVN_ERR(svn_wc__get_translate_info(&style, &eol,
215251881Speter                                         &keywords,
216251881Speter                                         &special,
217251881Speter                                         mt->db, mt->local_abspath,
218251881Speter                                         mt->old_actual_props, TRUE,
219251881Speter                                         scratch_pool, scratch_pool));
220251881Speter
221251881Speter      if (special)
222251881Speter        {
223251881Speter          keywords = NULL;
224251881Speter          eol = NULL;
225251881Speter          style = svn_subst_eol_style_none;
226251881Speter        }
227251881Speter      else
228251881Speter        {
229251881Speter          const svn_prop_t *prop;
230251881Speter
231251881Speter          /* In case a new eol style was set, use that for detranslation */
232251881Speter          if ((prop = get_prop(mt->prop_diff, SVN_PROP_EOL_STYLE)) && prop->value)
233251881Speter            {
234251881Speter              /* Value added or changed */
235251881Speter              svn_subst_eol_style_from_value(&style, &eol, prop->value->data);
236251881Speter            }
237251881Speter          else if (!old_is_binary)
238251881Speter            {
239251881Speter              /* Already fetched */
240251881Speter            }
241251881Speter          else
242251881Speter            {
243251881Speter              eol = NULL;
244251881Speter              style = svn_subst_eol_style_none;
245251881Speter            }
246251881Speter        }
247251881Speter    }
248251881Speter
249251881Speter  /* Now, detranslate with the settings we created above */
250251881Speter
251251881Speter  if (force_copy || keywords || eol || special)
252251881Speter    {
253251881Speter      const char *temp_dir_abspath;
254251881Speter      const char *detranslated;
255251881Speter
256251881Speter      /* Force a copy into the temporary wc area to avoid having
257251881Speter         temporary files created below to appear in the actual wc. */
258251881Speter      SVN_ERR(svn_wc__db_temp_wcroot_tempdir(&temp_dir_abspath, mt->db,
259251881Speter                                             mt->wri_abspath,
260251881Speter                                             scratch_pool, scratch_pool));
261251881Speter
262251881Speter      /* ### svn_subst_copy_and_translate4() also creates a tempfile
263251881Speter         ### internally.  Anyway to piggyback on that? */
264251881Speter      SVN_ERR(svn_io_open_unique_file3(NULL, &detranslated, temp_dir_abspath,
265251881Speter                                       (force_copy
266251881Speter                                        ? svn_io_file_del_none
267251881Speter                                        : svn_io_file_del_on_pool_cleanup),
268251881Speter                                       result_pool, scratch_pool));
269251881Speter
270251881Speter      /* Always 'repair' EOLs here, so that we can apply a diff that
271251881Speter         changes from inconsistent newlines and no 'svn:eol-style' to
272251881Speter         consistent newlines and 'svn:eol-style' set.  */
273251881Speter
274251881Speter      if (style == svn_subst_eol_style_native)
275251881Speter        eol = SVN_SUBST_NATIVE_EOL_STR;
276251881Speter      else if (style != svn_subst_eol_style_fixed
277251881Speter               && style != svn_subst_eol_style_none)
278251881Speter        return svn_error_create(SVN_ERR_IO_UNKNOWN_EOL, NULL, NULL);
279251881Speter
280251881Speter      SVN_ERR(svn_subst_copy_and_translate4(source_abspath,
281251881Speter                                            detranslated,
282251881Speter                                            eol,
283251881Speter                                            TRUE /* repair */,
284251881Speter                                            keywords,
285251881Speter                                            FALSE /* contract keywords */,
286251881Speter                                            special,
287251881Speter                                            cancel_func, cancel_baton,
288251881Speter                                            scratch_pool));
289251881Speter
290251881Speter      SVN_ERR(svn_dirent_get_absolute(detranslated_abspath, detranslated,
291251881Speter                                      result_pool));
292251881Speter    }
293251881Speter  else
294251881Speter    *detranslated_abspath = apr_pstrdup(result_pool, source_abspath);
295251881Speter
296251881Speter  return SVN_NO_ERROR;
297251881Speter}
298251881Speter
299251881Speter/* Updates (by copying and translating) the eol style in
300251881Speter   OLD_TARGET_ABSPATH returning the filename containing the
301251881Speter   correct eol style in NEW_TARGET_ABSPATH, if an eol style
302251881Speter   change is contained in PROP_DIFF. */
303251881Speterstatic svn_error_t *
304251881Spetermaybe_update_target_eols(const char **new_target_abspath,
305251881Speter                         const apr_array_header_t *prop_diff,
306251881Speter                         const char *old_target_abspath,
307251881Speter                         svn_cancel_func_t cancel_func,
308251881Speter                         void *cancel_baton,
309251881Speter                         apr_pool_t *result_pool,
310251881Speter                         apr_pool_t *scratch_pool)
311251881Speter{
312251881Speter  const svn_prop_t *prop = get_prop(prop_diff, SVN_PROP_EOL_STYLE);
313251881Speter
314251881Speter  if (prop && prop->value)
315251881Speter    {
316251881Speter      const char *eol;
317251881Speter      const char *tmp_new;
318251881Speter
319251881Speter      svn_subst_eol_style_from_value(NULL, &eol, prop->value->data);
320251881Speter      SVN_ERR(svn_io_open_unique_file3(NULL, &tmp_new, NULL,
321251881Speter                                       svn_io_file_del_on_pool_cleanup,
322251881Speter                                       result_pool, scratch_pool));
323251881Speter
324251881Speter      /* Always 'repair' EOLs here, so that we can apply a diff that
325251881Speter         changes from inconsistent newlines and no 'svn:eol-style' to
326251881Speter         consistent newlines and 'svn:eol-style' set.  */
327251881Speter      SVN_ERR(svn_subst_copy_and_translate4(old_target_abspath,
328251881Speter                                            tmp_new,
329251881Speter                                            eol,
330251881Speter                                            TRUE /* repair */,
331251881Speter                                            NULL /* keywords */,
332251881Speter                                            FALSE /* expand */,
333251881Speter                                            FALSE /* special */,
334251881Speter                                            cancel_func, cancel_baton,
335251881Speter                                            scratch_pool));
336251881Speter      *new_target_abspath = apr_pstrdup(result_pool, tmp_new);
337251881Speter    }
338251881Speter  else
339251881Speter    *new_target_abspath = apr_pstrdup(result_pool, old_target_abspath);
340251881Speter
341251881Speter  return SVN_NO_ERROR;
342251881Speter}
343251881Speter
344251881Speter
345251881Speter/* Set *TARGET_MARKER, *LEFT_MARKER and *RIGHT_MARKER to strings suitable
346251881Speter   for delimiting the alternative texts in a text conflict.  Include in each
347251881Speter   marker a string that may be given by TARGET_LABEL, LEFT_LABEL and
348251881Speter   RIGHT_LABEL respectively or a default value where any of those are NULL.
349251881Speter
350251881Speter   Allocate the results in POOL or statically. */
351251881Speterstatic void
352251881Speterinit_conflict_markers(const char **target_marker,
353251881Speter                      const char **left_marker,
354251881Speter                      const char **right_marker,
355251881Speter                      const char *target_label,
356251881Speter                      const char *left_label,
357251881Speter                      const char *right_label,
358251881Speter                      apr_pool_t *pool)
359251881Speter{
360251881Speter  /* Labels fall back to sensible defaults if not specified. */
361251881Speter  if (target_label)
362251881Speter    *target_marker = apr_psprintf(pool, "<<<<<<< %s", target_label);
363251881Speter  else
364251881Speter    *target_marker = "<<<<<<< .working";
365251881Speter
366251881Speter  if (left_label)
367251881Speter    *left_marker = apr_psprintf(pool, "||||||| %s", left_label);
368251881Speter  else
369251881Speter    *left_marker = "||||||| .old";
370251881Speter
371251881Speter  if (right_label)
372251881Speter    *right_marker = apr_psprintf(pool, ">>>>>>> %s", right_label);
373251881Speter  else
374251881Speter    *right_marker = ">>>>>>> .new";
375251881Speter}
376251881Speter
377251881Speter/* Do a 3-way merge of the files at paths LEFT, DETRANSLATED_TARGET,
378251881Speter * and RIGHT, using diff options provided in MERGE_OPTIONS.  Store the merge
379251881Speter * result in the file RESULT_F.
380251881Speter * If there are conflicts, set *CONTAINS_CONFLICTS to true, and use
381251881Speter * TARGET_LABEL, LEFT_LABEL, and RIGHT_LABEL as labels for conflict
382251881Speter * markers.  Else, set *CONTAINS_CONFLICTS to false.
383251881Speter * Do all allocations in POOL. */
384251881Speterstatic svn_error_t *
385251881Speterdo_text_merge(svn_boolean_t *contains_conflicts,
386251881Speter              apr_file_t *result_f,
387251881Speter              const apr_array_header_t *merge_options,
388251881Speter              const char *detranslated_target,
389251881Speter              const char *left,
390251881Speter              const char *right,
391251881Speter              const char *target_label,
392251881Speter              const char *left_label,
393251881Speter              const char *right_label,
394251881Speter              apr_pool_t *pool)
395251881Speter{
396251881Speter  svn_diff_t *diff;
397251881Speter  svn_stream_t *ostream;
398251881Speter  const char *target_marker;
399251881Speter  const char *left_marker;
400251881Speter  const char *right_marker;
401251881Speter  svn_diff_file_options_t *diff3_options;
402251881Speter
403251881Speter  diff3_options = svn_diff_file_options_create(pool);
404251881Speter
405251881Speter  if (merge_options)
406251881Speter    SVN_ERR(svn_diff_file_options_parse(diff3_options,
407251881Speter                                        merge_options, pool));
408251881Speter
409251881Speter
410251881Speter  init_conflict_markers(&target_marker, &left_marker, &right_marker,
411251881Speter                        target_label, left_label, right_label, pool);
412251881Speter
413251881Speter  SVN_ERR(svn_diff_file_diff3_2(&diff, left, detranslated_target, right,
414251881Speter                                diff3_options, pool));
415251881Speter
416251881Speter  ostream = svn_stream_from_aprfile2(result_f, TRUE, pool);
417251881Speter
418251881Speter  SVN_ERR(svn_diff_file_output_merge2(ostream, diff,
419251881Speter                                      left, detranslated_target, right,
420251881Speter                                      left_marker,
421251881Speter                                      target_marker,
422251881Speter                                      right_marker,
423251881Speter                                      "=======", /* separator */
424251895Speter                                      svn_diff_conflict_display_modified_original_latest,
425251881Speter                                      pool));
426251881Speter  SVN_ERR(svn_stream_close(ostream));
427251881Speter
428251881Speter  *contains_conflicts = svn_diff_contains_conflicts(diff);
429251881Speter
430251881Speter  return SVN_NO_ERROR;
431251881Speter}
432251881Speter
433251881Speter/* Same as do_text_merge() above, but use the external diff3
434251881Speter * command DIFF3_CMD to perform the merge.  Pass MERGE_OPTIONS
435251881Speter * to the diff3 command.  Do all allocations in POOL. */
436251881Speterstatic svn_error_t *
437251881Speterdo_text_merge_external(svn_boolean_t *contains_conflicts,
438251881Speter                       apr_file_t *result_f,
439251881Speter                       const char *diff3_cmd,
440251881Speter                       const apr_array_header_t *merge_options,
441251881Speter                       const char *detranslated_target,
442251881Speter                       const char *left_abspath,
443251881Speter                       const char *right_abspath,
444251881Speter                       const char *target_label,
445251881Speter                       const char *left_label,
446251881Speter                       const char *right_label,
447251881Speter                       apr_pool_t *scratch_pool)
448251881Speter{
449251881Speter  int exit_code;
450251881Speter
451251881Speter  SVN_ERR(svn_io_run_diff3_3(&exit_code, ".",
452251881Speter                             detranslated_target, left_abspath, right_abspath,
453251881Speter                             target_label, left_label, right_label,
454251881Speter                             result_f, diff3_cmd,
455251881Speter                             merge_options, scratch_pool));
456251881Speter
457251881Speter  *contains_conflicts = exit_code == 1;
458251881Speter
459251881Speter  return SVN_NO_ERROR;
460251881Speter}
461251881Speter
462251881Speter/* Preserve the three pre-merge files.
463251881Speter
464251881Speter   Create three empty files, with unique names that each include the
465251881Speter   basename of TARGET_ABSPATH and one of LEFT_LABEL, RIGHT_LABEL and
466251881Speter   TARGET_LABEL, in the directory that contains TARGET_ABSPATH.  Typical
467251881Speter   names are "foo.c.r37" or "foo.c.2.mine".  Set *LEFT_COPY, *RIGHT_COPY and
468251881Speter   *TARGET_COPY to their absolute paths.
469251881Speter
470251881Speter   Set *WORK_ITEMS to a list of new work items that will write copies of
471251881Speter   LEFT_ABSPATH, RIGHT_ABSPATH and TARGET_ABSPATH into the three files,
472251881Speter   translated to working-copy form.
473251881Speter
474251881Speter   The translation to working-copy form will be done according to the
475251881Speter   versioned properties of TARGET_ABSPATH that are current when the work
476251881Speter   queue items are executed.
477251881Speter
478251881Speter   If target_abspath is not versioned use detranslated_target_abspath
479251881Speter   as the target file.
480251881Speter       ### NOT IMPLEMENTED -- 'detranslated_target_abspath' is not used.
481251881Speter*/
482251881Speterstatic svn_error_t *
483251881Speterpreserve_pre_merge_files(svn_skel_t **work_items,
484251881Speter                         const char **left_copy,
485251881Speter                         const char **right_copy,
486251881Speter                         const char **target_copy,
487251881Speter                         const merge_target_t *mt,
488251881Speter                         const char *left_abspath,
489251881Speter                         const char *right_abspath,
490251881Speter                         const char *left_label,
491251881Speter                         const char *right_label,
492251881Speter                         const char *target_label,
493251881Speter                         const char *detranslated_target_abspath,
494251881Speter                         svn_cancel_func_t cancel_func,
495251881Speter                         void *cancel_baton,
496251881Speter                         apr_pool_t *result_pool,
497251881Speter                         apr_pool_t *scratch_pool)
498251881Speter{
499251881Speter  const char *tmp_left, *tmp_right, *detranslated_target_copy;
500251881Speter  const char *dir_abspath, *target_name;
501251881Speter  const char *wcroot_abspath, *temp_dir_abspath;
502251881Speter  svn_skel_t *work_item, *last_items = NULL;
503251881Speter
504251881Speter  *work_items = NULL;
505251881Speter
506251881Speter  svn_dirent_split(&dir_abspath, &target_name, mt->local_abspath,
507251881Speter                   scratch_pool);
508251881Speter
509251881Speter  SVN_ERR(svn_wc__db_get_wcroot(&wcroot_abspath, mt->db, mt->wri_abspath,
510251881Speter                                scratch_pool, scratch_pool));
511251881Speter  SVN_ERR(svn_wc__db_temp_wcroot_tempdir(&temp_dir_abspath, mt->db,
512251881Speter                                         mt->wri_abspath,
513251881Speter                                         scratch_pool, scratch_pool));
514251881Speter
515251881Speter  /* Create three empty files in DIR_ABSPATH, naming them with unique names
516251881Speter     that each include TARGET_NAME and one of {LEFT,RIGHT,TARGET}_LABEL,
517251881Speter     and set *{LEFT,RIGHT,TARGET}_COPY to those names. */
518251881Speter  SVN_ERR(svn_io_open_uniquely_named(
519251881Speter            NULL, left_copy, dir_abspath, target_name, left_label,
520251881Speter            svn_io_file_del_none, result_pool, scratch_pool));
521251881Speter  SVN_ERR(svn_io_open_uniquely_named(
522251881Speter            NULL, right_copy, dir_abspath, target_name, right_label,
523251881Speter            svn_io_file_del_none, result_pool, scratch_pool));
524251881Speter  SVN_ERR(svn_io_open_uniquely_named(
525251881Speter            NULL, target_copy, dir_abspath, target_name, target_label,
526251881Speter            svn_io_file_del_none, result_pool, scratch_pool));
527251881Speter
528251881Speter  /* We preserve all the files with keywords expanded and line
529251881Speter     endings in local (working) form. */
530251881Speter
531251881Speter  /* The workingqueue requires its paths to be in the subtree
532251881Speter     relative to the wcroot path they are executed in.
533251881Speter
534251881Speter     Make our LEFT and RIGHT files 'local' if they aren't... */
535251881Speter  if (! svn_dirent_is_ancestor(wcroot_abspath, left_abspath))
536251881Speter    {
537251881Speter      SVN_ERR(svn_io_open_unique_file3(NULL, &tmp_left, temp_dir_abspath,
538251881Speter                                       svn_io_file_del_none,
539251881Speter                                       scratch_pool, scratch_pool));
540251881Speter      SVN_ERR(svn_io_copy_file(left_abspath, tmp_left, TRUE, scratch_pool));
541251881Speter
542251881Speter      /* And create a wq item to remove the file later */
543251881Speter      SVN_ERR(svn_wc__wq_build_file_remove(&work_item, mt->db, wcroot_abspath,
544251881Speter                                           tmp_left,
545251881Speter                                           result_pool, scratch_pool));
546251881Speter
547251881Speter      last_items = svn_wc__wq_merge(last_items, work_item, result_pool);
548251881Speter    }
549251881Speter  else
550251881Speter    tmp_left = left_abspath;
551251881Speter
552251881Speter  if (! svn_dirent_is_ancestor(wcroot_abspath, right_abspath))
553251881Speter    {
554251881Speter      SVN_ERR(svn_io_open_unique_file3(NULL, &tmp_right, temp_dir_abspath,
555251881Speter                                       svn_io_file_del_none,
556251881Speter                                       scratch_pool, scratch_pool));
557251881Speter      SVN_ERR(svn_io_copy_file(right_abspath, tmp_right, TRUE, scratch_pool));
558251881Speter
559251881Speter      /* And create a wq item to remove the file later */
560251881Speter      SVN_ERR(svn_wc__wq_build_file_remove(&work_item, mt->db, wcroot_abspath,
561251881Speter                                           tmp_right,
562251881Speter                                           result_pool, scratch_pool));
563251881Speter
564251881Speter      last_items = svn_wc__wq_merge(last_items, work_item, result_pool);
565251881Speter    }
566251881Speter  else
567251881Speter    tmp_right = right_abspath;
568251881Speter
569251881Speter  /* NOTE: Callers must ensure that the svn:eol-style and
570251881Speter     svn:keywords property values are correct in the currently
571251881Speter     installed props.  With 'svn merge', it's no big deal.  But
572251881Speter     when 'svn up' calls this routine, it needs to make sure that
573251881Speter     this routine is using the newest property values that may
574251881Speter     have been received *during* the update.  Since this routine
575251881Speter     will be run from within a log-command, merge_file()
576251881Speter     needs to make sure that a previous log-command to 'install
577251881Speter     latest props' has already executed first.  Ben and I just
578251881Speter     checked, and that is indeed the order in which the log items
579251881Speter     are written, so everything should be fine.  Really.  */
580251881Speter
581251881Speter  /* Create LEFT and RIGHT backup files, in expanded form.
582251881Speter     We use TARGET_ABSPATH's current properties to do the translation. */
583251881Speter  /* Derive the basenames of the 3 backup files. */
584251881Speter  SVN_ERR(svn_wc__wq_build_file_copy_translated(&work_item,
585251881Speter                                                mt->db, mt->local_abspath,
586251881Speter                                                tmp_left, *left_copy,
587251881Speter                                                result_pool, scratch_pool));
588251881Speter  *work_items = svn_wc__wq_merge(*work_items, work_item, result_pool);
589251881Speter
590251881Speter  SVN_ERR(svn_wc__wq_build_file_copy_translated(&work_item,
591251881Speter                                                mt->db, mt->local_abspath,
592251881Speter                                                tmp_right, *right_copy,
593251881Speter                                                result_pool, scratch_pool));
594251881Speter  *work_items = svn_wc__wq_merge(*work_items, work_item, result_pool);
595251881Speter
596251881Speter  /* Back up TARGET_ABSPATH through detranslation/retranslation:
597251881Speter     the new translation properties may not match the current ones */
598251881Speter  SVN_ERR(detranslate_wc_file(&detranslated_target_copy, mt, TRUE,
599251881Speter                              mt->local_abspath,
600251881Speter                              cancel_func, cancel_baton,
601251881Speter                              scratch_pool, scratch_pool));
602251881Speter
603251881Speter  SVN_ERR(svn_wc__wq_build_file_copy_translated(&work_item,
604251881Speter                                                mt->db, mt->local_abspath,
605251881Speter                                                detranslated_target_copy,
606251881Speter                                                *target_copy,
607251881Speter                                                result_pool, scratch_pool));
608251881Speter  *work_items = svn_wc__wq_merge(*work_items, work_item, result_pool);
609251881Speter
610251881Speter  /* And maybe delete some tempfiles */
611251881Speter  SVN_ERR(svn_wc__wq_build_file_remove(&work_item, mt->db, wcroot_abspath,
612251881Speter                                       detranslated_target_copy,
613251881Speter                                       result_pool, scratch_pool));
614251881Speter  *work_items = svn_wc__wq_merge(*work_items, work_item, result_pool);
615251881Speter
616251881Speter  *work_items = svn_wc__wq_merge(*work_items, last_items, result_pool);
617251881Speter
618251881Speter  return SVN_NO_ERROR;
619251881Speter}
620251881Speter
621251881Speter/* Attempt a trivial merge of LEFT_ABSPATH and RIGHT_ABSPATH to
622251881Speter * the target file at TARGET_ABSPATH.
623251881Speter *
624251881Speter * These are the inherently trivial cases:
625251881Speter *
626251881Speter *   left == right == target         =>  no-op
627251881Speter *   left != right, left == target   =>  target := right
628251881Speter *
629251881Speter * This case is also treated as trivial:
630251881Speter *
631251881Speter *   left != right, right == target  =>  no-op
632251881Speter *
633251881Speter *   ### Strictly, this case is a conflict, and the no-op outcome is only
634251881Speter *       one of the possible resolutions.
635251881Speter *
636251881Speter *       TODO: Raise a conflict at this level and implement the 'no-op'
637251881Speter *       resolution of that conflict at a higher level, in preparation for
638251881Speter *       being able to support stricter conflict detection.
639251881Speter *
640251881Speter * This case is inherently trivial but not currently handled here:
641251881Speter *
642251881Speter *   left == right != target         =>  no-op
643251881Speter *
644251881Speter * The files at LEFT_ABSPATH and RIGHT_ABSPATH are in repository normal
645251881Speter * form.  The file at DETRANSLATED_TARGET_ABSPATH is a copy of the target,
646251881Speter * 'detranslated' to repository normal form, or may be the target file
647251881Speter * itself if no translation is necessary.
648251881Speter *
649251881Speter * When this function updates the target file, it translates to working copy
650251881Speter * form.
651251881Speter *
652251881Speter * On success, set *MERGE_OUTCOME to SVN_WC_MERGE_MERGED in case the
653251881Speter * target was changed, or to SVN_WC_MERGE_UNCHANGED if the target was not
654251881Speter * changed. Install work queue items allocated in RESULT_POOL in *WORK_ITEMS.
655251881Speter * On failure, set *MERGE_OUTCOME to SVN_WC_MERGE_NO_MERGE.
656251881Speter */
657251881Speterstatic svn_error_t *
658251881Spetermerge_file_trivial(svn_skel_t **work_items,
659251881Speter                   enum svn_wc_merge_outcome_t *merge_outcome,
660251881Speter                   const char *left_abspath,
661251881Speter                   const char *right_abspath,
662251881Speter                   const char *target_abspath,
663251881Speter                   const char *detranslated_target_abspath,
664251881Speter                   svn_boolean_t dry_run,
665251881Speter                   svn_wc__db_t *db,
666251881Speter                   svn_cancel_func_t cancel_func,
667251881Speter                   void *cancel_baton,
668251881Speter                   apr_pool_t *result_pool,
669251881Speter                   apr_pool_t *scratch_pool)
670251881Speter{
671251881Speter  svn_skel_t *work_item;
672251881Speter  svn_boolean_t same_left_right;
673251881Speter  svn_boolean_t same_right_target;
674251881Speter  svn_boolean_t same_left_target;
675251881Speter  svn_node_kind_t kind;
676251881Speter  svn_boolean_t is_special;
677251881Speter
678251881Speter  /* If the target is not a normal file, do not attempt a trivial merge. */
679251881Speter  SVN_ERR(svn_io_check_special_path(target_abspath, &kind, &is_special,
680251881Speter                                    scratch_pool));
681251881Speter  if (kind != svn_node_file || is_special)
682251881Speter    {
683251881Speter      *merge_outcome = svn_wc_merge_no_merge;
684251881Speter      return SVN_NO_ERROR;
685251881Speter    }
686251881Speter
687251881Speter  /* Check the files */
688251881Speter  SVN_ERR(svn_io_files_contents_three_same_p(&same_left_right,
689251881Speter                                             &same_right_target,
690251881Speter                                             &same_left_target,
691251881Speter                                             left_abspath,
692251881Speter                                             right_abspath,
693251881Speter                                             detranslated_target_abspath,
694251881Speter                                             scratch_pool));
695251881Speter
696251881Speter  /* If the LEFT side of the merge is equal to WORKING, then we can
697251881Speter   * copy RIGHT directly. */
698251881Speter  if (same_left_target)
699251881Speter    {
700251881Speter      /* If the left side equals the right side, there is no change to merge
701251881Speter       * so we leave the target unchanged. */
702251881Speter      if (same_left_right)
703251881Speter        {
704251881Speter          *merge_outcome = svn_wc_merge_unchanged;
705251881Speter        }
706251881Speter      else
707251881Speter        {
708251881Speter          *merge_outcome = svn_wc_merge_merged;
709251881Speter          if (!dry_run)
710251881Speter            {
711251881Speter              const char *wcroot_abspath;
712251881Speter              svn_boolean_t delete_src = FALSE;
713251881Speter
714251881Speter              /* The right_abspath might be outside our working copy. In that
715251881Speter                 case we should copy the file to a safe location before
716251881Speter                 installing to avoid breaking the workqueue.
717251881Speter
718251881Speter                 This matches the behavior in preserve_pre_merge_files */
719251881Speter
720251881Speter              SVN_ERR(svn_wc__db_get_wcroot(&wcroot_abspath,
721251881Speter                                            db, target_abspath,
722251881Speter                                            scratch_pool, scratch_pool));
723251881Speter
724251881Speter              if (!svn_dirent_is_child(wcroot_abspath, right_abspath, NULL))
725251881Speter                {
726251881Speter                  svn_stream_t *tmp_src;
727251881Speter                  svn_stream_t *tmp_dst;
728251881Speter
729251881Speter                  SVN_ERR(svn_stream_open_readonly(&tmp_src, right_abspath,
730251881Speter                                                   scratch_pool,
731251881Speter                                                   scratch_pool));
732251881Speter
733251881Speter                  SVN_ERR(svn_wc__open_writable_base(&tmp_dst, &right_abspath,
734251881Speter                                                     NULL, NULL,
735251881Speter                                                     db, target_abspath,
736251881Speter                                                     scratch_pool,
737251881Speter                                                     scratch_pool));
738251881Speter
739251881Speter                  SVN_ERR(svn_stream_copy3(tmp_src, tmp_dst,
740251881Speter                                           cancel_func, cancel_baton,
741251881Speter                                           scratch_pool));
742251881Speter
743251881Speter                  delete_src = TRUE;
744251881Speter                }
745251881Speter
746251881Speter              SVN_ERR(svn_wc__wq_build_file_install(
747251881Speter                        &work_item, db, target_abspath, right_abspath,
748251881Speter                        FALSE /* use_commit_times */,
749251881Speter                        FALSE /* record_fileinfo */,
750251881Speter                        result_pool, scratch_pool));
751251881Speter              *work_items = svn_wc__wq_merge(*work_items, work_item,
752251881Speter                                             result_pool);
753251881Speter
754251881Speter              if (delete_src)
755251881Speter                {
756251881Speter                  SVN_ERR(svn_wc__wq_build_file_remove(
757251881Speter                                    &work_item, db, wcroot_abspath,
758251881Speter                                    right_abspath,
759251881Speter                                    result_pool, scratch_pool));
760251881Speter                  *work_items = svn_wc__wq_merge(*work_items, work_item,
761251881Speter                                                 result_pool);
762251881Speter                }
763251881Speter            }
764251881Speter        }
765251881Speter
766251881Speter      return SVN_NO_ERROR;
767251881Speter    }
768251881Speter  else
769251881Speter    {
770251881Speter      /* If the locally existing, changed file equals the incoming 'right'
771251881Speter       * file, there is no conflict.  For binary files, we historically
772251881Speter       * conflicted them needlessly, while merge_text_file figured it out
773251881Speter       * eventually and returned svn_wc_merge_unchanged for them, which
774251881Speter       * is what we do here. */
775251881Speter      if (same_right_target)
776251881Speter        {
777251881Speter          *merge_outcome = svn_wc_merge_unchanged;
778251881Speter          return SVN_NO_ERROR;
779251881Speter        }
780251881Speter    }
781251881Speter
782251881Speter  *merge_outcome = svn_wc_merge_no_merge;
783251881Speter  return SVN_NO_ERROR;
784251881Speter}
785251881Speter
786251881Speter
787251881Speter/* Handle a non-trivial merge of 'text' files.  (Assume that a trivial
788251881Speter * merge was not possible.)
789251881Speter *
790251881Speter * Set *WORK_ITEMS, *CONFLICT_SKEL and *MERGE_OUTCOME according to the
791251881Speter * result -- to install the merged file, or to indicate a conflict.
792251881Speter *
793251881Speter * On successful merge, leave the result in a temporary file and set
794251881Speter * *WORK_ITEMS to hold work items that will translate and install that
795251881Speter * file into its proper form and place (unless DRY_RUN) and delete the
796251881Speter * temporary file (in any case).  Set *MERGE_OUTCOME to 'merged' or
797251881Speter * 'unchanged'.
798251881Speter *
799251881Speter * If a conflict occurs, set *MERGE_OUTCOME to 'conflicted', and (unless
800251881Speter * DRY_RUN) set *WORK_ITEMS and *CONFLICT_SKEL to record the conflict
801251881Speter * and copies of the pre-merge files.  See preserve_pre_merge_files()
802251881Speter * for details.
803251881Speter *
804251881Speter * On entry, all of the output pointers must be non-null and *CONFLICT_SKEL
805251881Speter * must either point to an existing conflict skel or be NULL.
806251881Speter */
807251881Speterstatic svn_error_t*
808251881Spetermerge_text_file(svn_skel_t **work_items,
809251881Speter                svn_skel_t **conflict_skel,
810251881Speter                enum svn_wc_merge_outcome_t *merge_outcome,
811251881Speter                const merge_target_t *mt,
812251881Speter                const char *left_abspath,
813251881Speter                const char *right_abspath,
814251881Speter                const char *left_label,
815251881Speter                const char *right_label,
816251881Speter                const char *target_label,
817251881Speter                svn_boolean_t dry_run,
818251881Speter                const char *detranslated_target_abspath,
819251881Speter                svn_cancel_func_t cancel_func,
820251881Speter                void *cancel_baton,
821251881Speter                apr_pool_t *result_pool,
822251881Speter                apr_pool_t *scratch_pool)
823251881Speter{
824251881Speter  apr_pool_t *pool = scratch_pool;  /* ### temporary rename  */
825251881Speter  svn_boolean_t contains_conflicts;
826251881Speter  apr_file_t *result_f;
827251881Speter  const char *result_target;
828251881Speter  const char *base_name;
829251881Speter  const char *temp_dir;
830251881Speter  svn_skel_t *work_item;
831251881Speter
832251881Speter  *work_items = NULL;
833251881Speter
834251881Speter  base_name = svn_dirent_basename(mt->local_abspath, scratch_pool);
835251881Speter
836251881Speter  /* Open a second temporary file for writing; this is where diff3
837251881Speter     will write the merged results.  We want to use a tempfile
838251881Speter     with a name that reflects the original, in case this
839251881Speter     ultimately winds up in a conflict resolution editor.  */
840251881Speter  SVN_ERR(svn_wc__db_temp_wcroot_tempdir(&temp_dir, mt->db, mt->wri_abspath,
841251881Speter                                         pool, pool));
842251881Speter  SVN_ERR(svn_io_open_uniquely_named(&result_f, &result_target,
843251881Speter                                     temp_dir, base_name, ".tmp",
844251881Speter                                     svn_io_file_del_none, pool, pool));
845251881Speter
846251881Speter  /* Run the external or internal merge, as requested. */
847251881Speter  if (mt->diff3_cmd)
848251881Speter      SVN_ERR(do_text_merge_external(&contains_conflicts,
849251881Speter                                     result_f,
850251881Speter                                     mt->diff3_cmd,
851251881Speter                                     mt->merge_options,
852251881Speter                                     detranslated_target_abspath,
853251881Speter                                     left_abspath,
854251881Speter                                     right_abspath,
855251881Speter                                     target_label,
856251881Speter                                     left_label,
857251881Speter                                     right_label,
858251881Speter                                     pool));
859251881Speter  else /* Use internal merge. */
860251881Speter    SVN_ERR(do_text_merge(&contains_conflicts,
861251881Speter                          result_f,
862251881Speter                          mt->merge_options,
863251881Speter                          detranslated_target_abspath,
864251881Speter                          left_abspath,
865251881Speter                          right_abspath,
866251881Speter                          target_label,
867251881Speter                          left_label,
868251881Speter                          right_label,
869251881Speter                          pool));
870251881Speter
871251881Speter  SVN_ERR(svn_io_file_close(result_f, pool));
872251881Speter
873251881Speter  /* Determine the MERGE_OUTCOME, and record any conflict. */
874251881Speter  if (contains_conflicts && ! dry_run)
875251881Speter    {
876251881Speter      *merge_outcome = svn_wc_merge_conflict;
877251881Speter      if (*merge_outcome == svn_wc_merge_conflict)
878251881Speter        {
879251881Speter          const char *left_copy, *right_copy, *target_copy;
880251881Speter
881251881Speter          /* Preserve the three conflict files */
882251881Speter          SVN_ERR(preserve_pre_merge_files(
883251881Speter                    &work_item,
884251881Speter                    &left_copy, &right_copy, &target_copy,
885251881Speter                    mt, left_abspath, right_abspath,
886251881Speter                    left_label, right_label, target_label,
887251881Speter                    detranslated_target_abspath,
888251881Speter                    cancel_func, cancel_baton,
889251881Speter                    result_pool, scratch_pool));
890251881Speter          *work_items = svn_wc__wq_merge(*work_items, work_item, result_pool);
891251881Speter
892251881Speter          /* Track the conflict marker files in the metadata. */
893251881Speter
894251881Speter          if (!*conflict_skel)
895251881Speter            *conflict_skel = svn_wc__conflict_skel_create(result_pool);
896251881Speter
897251881Speter          SVN_ERR(svn_wc__conflict_skel_add_text_conflict(*conflict_skel,
898251881Speter                                                          mt->db, mt->local_abspath,
899251881Speter                                                          target_copy,
900251881Speter                                                          left_copy,
901251881Speter                                                          right_copy,
902251881Speter                                                          result_pool,
903251881Speter                                                          scratch_pool));
904251881Speter        }
905251881Speter
906251881Speter      if (*merge_outcome == svn_wc_merge_merged)
907251881Speter        goto done;
908251881Speter    }
909251881Speter  else if (contains_conflicts && dry_run)
910251881Speter      *merge_outcome = svn_wc_merge_conflict;
911251881Speter  else
912251881Speter    {
913251881Speter      svn_boolean_t same, special;
914251881Speter
915251881Speter      /* If 'special', then use the detranslated form of the
916251881Speter         target file.  This is so we don't try to follow symlinks,
917251881Speter         but the same treatment is probably also appropriate for
918251881Speter         whatever special file types we may invent in the future. */
919251881Speter      SVN_ERR(svn_wc__get_translate_info(NULL, NULL, NULL,
920251881Speter                                         &special, mt->db, mt->local_abspath,
921251881Speter                                         mt->old_actual_props, TRUE,
922251881Speter                                         pool, pool));
923251881Speter      SVN_ERR(svn_io_files_contents_same_p(&same, result_target,
924251881Speter                                           (special ?
925251881Speter                                              detranslated_target_abspath :
926251881Speter                                              mt->local_abspath),
927251881Speter                                           pool));
928251881Speter
929251881Speter      *merge_outcome = same ? svn_wc_merge_unchanged : svn_wc_merge_merged;
930251881Speter    }
931251881Speter
932251881Speter  if (*merge_outcome != svn_wc_merge_unchanged && ! dry_run)
933251881Speter    {
934251881Speter      /* replace TARGET_ABSPATH with the new merged file, expanding. */
935251881Speter      SVN_ERR(svn_wc__wq_build_file_install(&work_item,
936251881Speter                                            mt->db, mt->local_abspath,
937251881Speter                                            result_target,
938251881Speter                                            FALSE /* use_commit_times */,
939251881Speter                                            FALSE /* record_fileinfo */,
940251881Speter                                            result_pool, scratch_pool));
941251881Speter      *work_items = svn_wc__wq_merge(*work_items, work_item, result_pool);
942251881Speter    }
943251881Speter
944251881Speterdone:
945251881Speter  /* Remove the tempfile after use */
946251881Speter  SVN_ERR(svn_wc__wq_build_file_remove(&work_item, mt->db, mt->local_abspath,
947251881Speter                                       result_target,
948251881Speter                                       result_pool, scratch_pool));
949251881Speter
950251881Speter  *work_items = svn_wc__wq_merge(*work_items, work_item, result_pool);
951251881Speter
952251881Speter  return SVN_NO_ERROR;
953251881Speter}
954251881Speter
955251881Speter/* Handle a non-trivial merge of 'binary' files: don't actually merge, just
956251881Speter * flag a conflict.  (Assume that a trivial merge was not possible.)
957251881Speter *
958251881Speter * Copy* the files at LEFT_ABSPATH and RIGHT_ABSPATH into the same directory
959251881Speter * as the target file, giving them unique names that start with the target
960251881Speter * file's name and end with LEFT_LABEL and RIGHT_LABEL respectively.
961251881Speter * If the merge target has been 'detranslated' to repository normal form,
962251881Speter * move the detranslated file similarly to a unique name ending with
963251881Speter * TARGET_LABEL.
964251881Speter *
965251881Speter * ### * Why do we copy the left and right temp files when we could (maybe
966251881Speter *     not always?) move them?
967251881Speter *
968251881Speter * On entry, all of the output pointers must be non-null and *CONFLICT_SKEL
969251881Speter * must either point to an existing conflict skel or be NULL.
970251881Speter *
971251881Speter * Set *WORK_ITEMS, *CONFLICT_SKEL and *MERGE_OUTCOME to indicate the
972251881Speter * conflict.
973251881Speter *
974251881Speter * ### Why do we not use preserve_pre_merge_files() in here?  The
975251881Speter *     behaviour would be slightly different, more consistent: the
976251881Speter *     preserved 'left' and 'right' files would be translated to working
977251881Speter *     copy form, which may make a difference when a binary file
978251881Speter *     contains keyword expansions or when some versions of the file are
979251881Speter *     not 'binary' even though we're merging in 'binary files' mode.
980251881Speter */
981251881Speterstatic svn_error_t *
982251881Spetermerge_binary_file(svn_skel_t **work_items,
983251881Speter                  svn_skel_t **conflict_skel,
984251881Speter                  enum svn_wc_merge_outcome_t *merge_outcome,
985251881Speter                  const merge_target_t *mt,
986251881Speter                  const char *left_abspath,
987251881Speter                  const char *right_abspath,
988251881Speter                  const char *left_label,
989251881Speter                  const char *right_label,
990251881Speter                  const char *target_label,
991251881Speter                  svn_boolean_t dry_run,
992251881Speter                  const char *detranslated_target_abspath,
993251881Speter                  apr_pool_t *result_pool,
994251881Speter                  apr_pool_t *scratch_pool)
995251881Speter{
996251881Speter  apr_pool_t *pool = scratch_pool;  /* ### temporary rename  */
997251881Speter  /* ### when making the binary-file backups, should we be honoring
998251881Speter     keywords and eol stuff?   */
999251881Speter  const char *left_copy, *right_copy;
1000251881Speter  const char *merge_dirpath, *merge_filename;
1001251881Speter  const char *conflict_wrk;
1002251881Speter
1003251881Speter  *work_items = NULL;
1004251881Speter
1005251881Speter  svn_dirent_split(&merge_dirpath, &merge_filename, mt->local_abspath, pool);
1006251881Speter
1007251881Speter  if (dry_run)
1008251881Speter    {
1009251881Speter      *merge_outcome = svn_wc_merge_conflict;
1010251881Speter      return SVN_NO_ERROR;
1011251881Speter    }
1012251881Speter
1013251881Speter  /* reserve names for backups of left and right fulltexts */
1014251881Speter  SVN_ERR(svn_io_open_uniquely_named(NULL,
1015251881Speter                                     &left_copy,
1016251881Speter                                     merge_dirpath,
1017251881Speter                                     merge_filename,
1018251881Speter                                     left_label,
1019251881Speter                                     svn_io_file_del_none,
1020251881Speter                                     pool, pool));
1021251881Speter
1022251881Speter  SVN_ERR(svn_io_open_uniquely_named(NULL,
1023251881Speter                                     &right_copy,
1024251881Speter                                     merge_dirpath,
1025251881Speter                                     merge_filename,
1026251881Speter                                     right_label,
1027251881Speter                                     svn_io_file_del_none,
1028251881Speter                                     pool, pool));
1029251881Speter
1030251881Speter  /* create the backup files */
1031251881Speter  SVN_ERR(svn_io_copy_file(left_abspath, left_copy, TRUE, pool));
1032251881Speter  SVN_ERR(svn_io_copy_file(right_abspath, right_copy, TRUE, pool));
1033251881Speter
1034251881Speter  /* Was the merge target detranslated? */
1035251881Speter  if (strcmp(mt->local_abspath, detranslated_target_abspath) != 0)
1036251881Speter    {
1037251881Speter      /* Create a .mine file too */
1038251881Speter      SVN_ERR(svn_io_open_uniquely_named(NULL,
1039251881Speter                                         &conflict_wrk,
1040251881Speter                                         merge_dirpath,
1041251881Speter                                         merge_filename,
1042251881Speter                                         target_label,
1043251881Speter                                         svn_io_file_del_none,
1044251881Speter                                         pool, pool));
1045251881Speter      SVN_ERR(svn_wc__wq_build_file_move(work_items, mt->db,
1046251881Speter                                         mt->local_abspath,
1047251881Speter                                         detranslated_target_abspath,
1048251881Speter                                         conflict_wrk,
1049251881Speter                                         pool, result_pool));
1050251881Speter    }
1051251881Speter  else
1052251881Speter    {
1053251881Speter      conflict_wrk = NULL;
1054251881Speter    }
1055251881Speter
1056251881Speter  /* Mark target_abspath's entry as "Conflicted", and start tracking
1057251881Speter     the backup files in the entry as well. */
1058251881Speter  if (!*conflict_skel)
1059251881Speter    *conflict_skel = svn_wc__conflict_skel_create(result_pool);
1060251881Speter
1061251881Speter  SVN_ERR(svn_wc__conflict_skel_add_text_conflict(*conflict_skel,
1062251881Speter                                                  mt->db, mt->local_abspath,
1063251881Speter                                                  conflict_wrk,
1064251881Speter                                                  left_copy,
1065251881Speter                                                  right_copy,
1066251881Speter                                                  result_pool, scratch_pool));
1067251881Speter
1068251881Speter  *merge_outcome = svn_wc_merge_conflict; /* a conflict happened */
1069251881Speter
1070251881Speter  return SVN_NO_ERROR;
1071251881Speter}
1072251881Speter
1073251881Spetersvn_error_t *
1074251881Spetersvn_wc__internal_merge(svn_skel_t **work_items,
1075251881Speter                       svn_skel_t **conflict_skel,
1076251881Speter                       enum svn_wc_merge_outcome_t *merge_outcome,
1077251881Speter                       svn_wc__db_t *db,
1078251881Speter                       const char *left_abspath,
1079251881Speter                       const char *right_abspath,
1080251881Speter                       const char *target_abspath,
1081251881Speter                       const char *wri_abspath,
1082251881Speter                       const char *left_label,
1083251881Speter                       const char *right_label,
1084251881Speter                       const char *target_label,
1085251881Speter                       apr_hash_t *old_actual_props,
1086251881Speter                       svn_boolean_t dry_run,
1087251881Speter                       const char *diff3_cmd,
1088251881Speter                       const apr_array_header_t *merge_options,
1089251881Speter                       const apr_array_header_t *prop_diff,
1090251881Speter                       svn_cancel_func_t cancel_func,
1091251881Speter                       void *cancel_baton,
1092251881Speter                       apr_pool_t *result_pool,
1093251881Speter                       apr_pool_t *scratch_pool)
1094251881Speter{
1095251881Speter  const char *detranslated_target_abspath;
1096251881Speter  svn_boolean_t is_binary = FALSE;
1097251881Speter  const svn_prop_t *mimeprop;
1098251881Speter  svn_skel_t *work_item;
1099251881Speter  merge_target_t mt;
1100251881Speter
1101251881Speter  SVN_ERR_ASSERT(svn_dirent_is_absolute(left_abspath));
1102251881Speter  SVN_ERR_ASSERT(svn_dirent_is_absolute(right_abspath));
1103251881Speter  SVN_ERR_ASSERT(svn_dirent_is_absolute(target_abspath));
1104251881Speter
1105251881Speter  *work_items = NULL;
1106251881Speter
1107251881Speter  /* Fill the merge target baton */
1108251881Speter  mt.db = db;
1109251881Speter  mt.local_abspath = target_abspath;
1110251881Speter  mt.wri_abspath = wri_abspath;
1111251881Speter  mt.old_actual_props = old_actual_props;
1112251881Speter  mt.prop_diff = prop_diff;
1113251881Speter  mt.diff3_cmd = diff3_cmd;
1114251881Speter  mt.merge_options = merge_options;
1115251881Speter
1116251881Speter  /* Decide if the merge target is a text or binary file. */
1117251881Speter  if ((mimeprop = get_prop(prop_diff, SVN_PROP_MIME_TYPE))
1118251881Speter      && mimeprop->value)
1119251881Speter    is_binary = svn_mime_type_is_binary(mimeprop->value->data);
1120251881Speter  else
1121251881Speter    {
1122251881Speter      const char *value = svn_prop_get_value(mt.old_actual_props,
1123251881Speter                                             SVN_PROP_MIME_TYPE);
1124251881Speter
1125251881Speter      is_binary = value && svn_mime_type_is_binary(value);
1126251881Speter    }
1127251881Speter
1128251881Speter  SVN_ERR(detranslate_wc_file(&detranslated_target_abspath, &mt,
1129251881Speter                              (! is_binary) && diff3_cmd != NULL,
1130251881Speter                              target_abspath,
1131251881Speter                              cancel_func, cancel_baton,
1132251881Speter                              scratch_pool, scratch_pool));
1133251881Speter
1134251881Speter  /* We cannot depend on the left file to contain the same eols as the
1135251881Speter     right file. If the merge target has mods, this will mark the entire
1136251881Speter     file as conflicted, so we need to compensate. */
1137251881Speter  SVN_ERR(maybe_update_target_eols(&left_abspath, prop_diff, left_abspath,
1138251881Speter                                   cancel_func, cancel_baton,
1139251881Speter                                   scratch_pool, scratch_pool));
1140251881Speter
1141251881Speter  SVN_ERR(merge_file_trivial(work_items, merge_outcome,
1142251881Speter                             left_abspath, right_abspath,
1143251881Speter                             target_abspath, detranslated_target_abspath,
1144251881Speter                             dry_run, db, cancel_func, cancel_baton,
1145251881Speter                             result_pool, scratch_pool));
1146251881Speter  if (*merge_outcome == svn_wc_merge_no_merge)
1147251881Speter    {
1148251881Speter      /* We have a non-trivial merge.  If we classify it as a merge of
1149251881Speter       * 'binary' files we'll just raise a conflict, otherwise we'll do
1150251881Speter       * the actual merge of 'text' file contents. */
1151251881Speter      if (is_binary)
1152251881Speter        {
1153251881Speter          /* Raise a text conflict */
1154251881Speter          SVN_ERR(merge_binary_file(work_items,
1155251881Speter                                    conflict_skel,
1156251881Speter                                    merge_outcome,
1157251881Speter                                    &mt,
1158251881Speter                                    left_abspath,
1159251881Speter                                    right_abspath,
1160251881Speter                                    left_label,
1161251881Speter                                    right_label,
1162251881Speter                                    target_label,
1163251881Speter                                    dry_run,
1164251881Speter                                    detranslated_target_abspath,
1165251881Speter                                    result_pool, scratch_pool));
1166251881Speter        }
1167251881Speter      else
1168251881Speter        {
1169251881Speter          SVN_ERR(merge_text_file(work_items,
1170251881Speter                                  conflict_skel,
1171251881Speter                                  merge_outcome,
1172251881Speter                                  &mt,
1173251881Speter                                  left_abspath,
1174251881Speter                                  right_abspath,
1175251881Speter                                  left_label,
1176251881Speter                                  right_label,
1177251881Speter                                  target_label,
1178251881Speter                                  dry_run,
1179251881Speter                                  detranslated_target_abspath,
1180251881Speter                                  cancel_func, cancel_baton,
1181251881Speter                                  result_pool, scratch_pool));
1182251881Speter        }
1183251881Speter    }
1184251881Speter
1185251881Speter  /* Merging is complete.  Regardless of text or binariness, we might
1186251881Speter     need to tweak the executable bit on the new working file, and
1187251881Speter     possibly make it read-only. */
1188251881Speter  if (! dry_run)
1189251881Speter    {
1190251881Speter      SVN_ERR(svn_wc__wq_build_sync_file_flags(&work_item, db,
1191251881Speter                                               target_abspath,
1192251881Speter                                               result_pool, scratch_pool));
1193251881Speter      *work_items = svn_wc__wq_merge(*work_items, work_item, result_pool);
1194251881Speter    }
1195251881Speter
1196251881Speter  return SVN_NO_ERROR;
1197251881Speter}
1198251881Speter
1199251881Speter
1200251881Spetersvn_error_t *
1201251881Spetersvn_wc_merge5(enum svn_wc_merge_outcome_t *merge_content_outcome,
1202251881Speter              enum svn_wc_notify_state_t *merge_props_outcome,
1203251881Speter              svn_wc_context_t *wc_ctx,
1204251881Speter              const char *left_abspath,
1205251881Speter              const char *right_abspath,
1206251881Speter              const char *target_abspath,
1207251881Speter              const char *left_label,
1208251881Speter              const char *right_label,
1209251881Speter              const char *target_label,
1210251881Speter              const svn_wc_conflict_version_t *left_version,
1211251881Speter              const svn_wc_conflict_version_t *right_version,
1212251881Speter              svn_boolean_t dry_run,
1213251881Speter              const char *diff3_cmd,
1214251881Speter              const apr_array_header_t *merge_options,
1215251881Speter              apr_hash_t *original_props,
1216251881Speter              const apr_array_header_t *prop_diff,
1217251881Speter              svn_wc_conflict_resolver_func2_t conflict_func,
1218251881Speter              void *conflict_baton,
1219251881Speter              svn_cancel_func_t cancel_func,
1220251881Speter              void *cancel_baton,
1221251881Speter              apr_pool_t *scratch_pool)
1222251881Speter{
1223251881Speter  const char *dir_abspath = svn_dirent_dirname(target_abspath, scratch_pool);
1224251881Speter  svn_skel_t *work_items;
1225251881Speter  svn_skel_t *conflict_skel = NULL;
1226251881Speter  apr_hash_t *pristine_props = NULL;
1227251881Speter  apr_hash_t *old_actual_props;
1228251881Speter  apr_hash_t *new_actual_props = NULL;
1229251881Speter
1230251881Speter  SVN_ERR_ASSERT(svn_dirent_is_absolute(left_abspath));
1231251881Speter  SVN_ERR_ASSERT(svn_dirent_is_absolute(right_abspath));
1232251881Speter  SVN_ERR_ASSERT(svn_dirent_is_absolute(target_abspath));
1233251881Speter
1234251881Speter  /* Before we do any work, make sure we hold a write lock.  */
1235251881Speter  if (!dry_run)
1236251881Speter    SVN_ERR(svn_wc__write_check(wc_ctx->db, dir_abspath, scratch_pool));
1237251881Speter
1238251881Speter  /* Sanity check:  the merge target must be a file under revision control */
1239251881Speter  {
1240251881Speter    svn_wc__db_status_t status;
1241251881Speter    svn_node_kind_t kind;
1242251881Speter    svn_boolean_t had_props;
1243251881Speter    svn_boolean_t props_mod;
1244251881Speter    svn_boolean_t conflicted;
1245251881Speter
1246251881Speter    SVN_ERR(svn_wc__db_read_info(&status, &kind, NULL, NULL, NULL, NULL, NULL,
1247251881Speter                                 NULL, NULL, NULL, NULL, NULL, NULL, NULL,
1248251881Speter                                 NULL, NULL, NULL, NULL, NULL, NULL,
1249251881Speter                                 &conflicted, NULL, &had_props, &props_mod,
1250251881Speter                                 NULL, NULL, NULL,
1251251881Speter                                 wc_ctx->db, target_abspath,
1252251881Speter                                 scratch_pool, scratch_pool));
1253251881Speter
1254251881Speter    if (kind != svn_node_file || (status != svn_wc__db_status_normal
1255251881Speter                                  && status != svn_wc__db_status_added))
1256251881Speter      {
1257251881Speter        *merge_content_outcome = svn_wc_merge_no_merge;
1258251881Speter        if (merge_props_outcome)
1259251881Speter          *merge_props_outcome = svn_wc_notify_state_unchanged;
1260251881Speter        return SVN_NO_ERROR;
1261251881Speter      }
1262251881Speter
1263251881Speter    if (conflicted)
1264251881Speter      {
1265251881Speter        svn_boolean_t text_conflicted;
1266251881Speter        svn_boolean_t prop_conflicted;
1267251881Speter        svn_boolean_t tree_conflicted;
1268251881Speter
1269251881Speter        SVN_ERR(svn_wc__internal_conflicted_p(&text_conflicted,
1270251881Speter                                              &prop_conflicted,
1271251881Speter                                              &tree_conflicted,
1272251881Speter                                              wc_ctx->db, target_abspath,
1273251881Speter                                              scratch_pool));
1274251881Speter
1275251881Speter        /* We can't install two prop conflicts on a single node, so
1276251881Speter           avoid even checking that we have to merge it */
1277251881Speter        if (text_conflicted || prop_conflicted || tree_conflicted)
1278251881Speter          {
1279251881Speter            return svn_error_createf(
1280251881Speter                            SVN_ERR_WC_PATH_UNEXPECTED_STATUS, NULL,
1281251881Speter                            _("Can't merge into conflicted node '%s'"),
1282251881Speter                            svn_dirent_local_style(target_abspath,
1283251881Speter                                                   scratch_pool));
1284251881Speter          }
1285251881Speter        /* else: Conflict was resolved by removing markers */
1286251881Speter      }
1287251881Speter
1288251881Speter    if (merge_props_outcome && had_props)
1289251881Speter      {
1290251881Speter        SVN_ERR(svn_wc__db_read_pristine_props(&pristine_props,
1291251881Speter                                               wc_ctx->db, target_abspath,
1292251881Speter                                               scratch_pool, scratch_pool));
1293251881Speter      }
1294251881Speter    else if (merge_props_outcome)
1295251881Speter      pristine_props = apr_hash_make(scratch_pool);
1296251881Speter
1297251881Speter    if (props_mod)
1298251881Speter      {
1299251881Speter        SVN_ERR(svn_wc__db_read_props(&old_actual_props,
1300251881Speter                                      wc_ctx->db, target_abspath,
1301251881Speter                                      scratch_pool, scratch_pool));
1302251881Speter      }
1303251881Speter    else if (pristine_props)
1304251881Speter      old_actual_props = pristine_props;
1305251881Speter    else
1306251881Speter      old_actual_props = apr_hash_make(scratch_pool);
1307251881Speter  }
1308251881Speter
1309251881Speter  /* Merge the properties, if requested.  We merge the properties first
1310251881Speter   * because the properties can affect the text (EOL style, keywords). */
1311251881Speter  if (merge_props_outcome)
1312251881Speter    {
1313251881Speter      int i;
1314251881Speter
1315251881Speter      /* The PROPCHANGES may not have non-"normal" properties in it. If entry
1316251881Speter         or wc props were allowed, then the following code would install them
1317251881Speter         into the BASE and/or WORKING properties(!).  */
1318251881Speter      for (i = prop_diff->nelts; i--; )
1319251881Speter        {
1320251881Speter          const svn_prop_t *change = &APR_ARRAY_IDX(prop_diff, i, svn_prop_t);
1321251881Speter
1322251881Speter          if (!svn_wc_is_normal_prop(change->name))
1323251881Speter            return svn_error_createf(SVN_ERR_BAD_PROP_KIND, NULL,
1324251881Speter                                     _("The property '%s' may not be merged "
1325251881Speter                                       "into '%s'."),
1326251881Speter                                     change->name,
1327251881Speter                                     svn_dirent_local_style(target_abspath,
1328251881Speter                                                            scratch_pool));
1329251881Speter        }
1330251881Speter
1331251881Speter      SVN_ERR(svn_wc__merge_props(&conflict_skel,
1332251881Speter                                  merge_props_outcome,
1333251881Speter                                  &new_actual_props,
1334251881Speter                                  wc_ctx->db, target_abspath,
1335251881Speter                                  original_props, pristine_props, old_actual_props,
1336251881Speter                                  prop_diff,
1337251881Speter                                  scratch_pool, scratch_pool));
1338251881Speter    }
1339251881Speter
1340251881Speter  /* Merge the text. */
1341251881Speter  SVN_ERR(svn_wc__internal_merge(&work_items,
1342251881Speter                                 &conflict_skel,
1343251881Speter                                 merge_content_outcome,
1344251881Speter                                 wc_ctx->db,
1345251881Speter                                 left_abspath,
1346251881Speter                                 right_abspath,
1347251881Speter                                 target_abspath,
1348251881Speter                                 target_abspath,
1349251881Speter                                 left_label, right_label, target_label,
1350251881Speter                                 old_actual_props,
1351251881Speter                                 dry_run,
1352251881Speter                                 diff3_cmd,
1353251881Speter                                 merge_options,
1354251881Speter                                 prop_diff,
1355251881Speter                                 cancel_func, cancel_baton,
1356251881Speter                                 scratch_pool, scratch_pool));
1357251881Speter
1358251881Speter  /* If this isn't a dry run, then update the DB, run the work, and
1359251881Speter   * call the conflict resolver callback.  */
1360251881Speter  if (!dry_run)
1361251881Speter    {
1362251881Speter      if (conflict_skel)
1363251881Speter        {
1364251881Speter          svn_skel_t *work_item;
1365251881Speter
1366251881Speter          SVN_ERR(svn_wc__conflict_skel_set_op_merge(conflict_skel,
1367251881Speter                                                     left_version,
1368251881Speter                                                     right_version,
1369251881Speter                                                     scratch_pool,
1370251881Speter                                                     scratch_pool));
1371251881Speter
1372251881Speter          SVN_ERR(svn_wc__conflict_create_markers(&work_item,
1373251881Speter                                                  wc_ctx->db, target_abspath,
1374251881Speter                                                  conflict_skel,
1375251881Speter                                                  scratch_pool, scratch_pool));
1376251881Speter
1377251881Speter          work_items = svn_wc__wq_merge(work_items, work_item, scratch_pool);
1378251881Speter        }
1379251881Speter
1380251881Speter      if (new_actual_props)
1381251881Speter        SVN_ERR(svn_wc__db_op_set_props(wc_ctx->db, target_abspath,
1382251881Speter                                        new_actual_props,
1383251881Speter                                        svn_wc__has_magic_property(prop_diff),
1384251881Speter                                        conflict_skel, work_items,
1385251881Speter                                        scratch_pool));
1386251881Speter      else if (conflict_skel)
1387251881Speter        SVN_ERR(svn_wc__db_op_mark_conflict(wc_ctx->db, target_abspath,
1388251881Speter                                            conflict_skel, work_items,
1389251881Speter                                            scratch_pool));
1390251881Speter      else if (work_items)
1391251881Speter        SVN_ERR(svn_wc__db_wq_add(wc_ctx->db, target_abspath, work_items,
1392251881Speter                                  scratch_pool));
1393251881Speter
1394251881Speter      if (work_items)
1395251881Speter        SVN_ERR(svn_wc__wq_run(wc_ctx->db, target_abspath,
1396251881Speter                               cancel_func, cancel_baton,
1397251881Speter                               scratch_pool));
1398251881Speter
1399251881Speter      if (conflict_skel && conflict_func)
1400251881Speter        {
1401251881Speter          svn_boolean_t text_conflicted, prop_conflicted;
1402251881Speter
1403251881Speter          SVN_ERR(svn_wc__conflict_invoke_resolver(
1404251881Speter                    wc_ctx->db, target_abspath,
1405251881Speter                    conflict_skel, merge_options,
1406251881Speter                    conflict_func, conflict_baton,
1407251881Speter                    cancel_func, cancel_baton,
1408251881Speter                    scratch_pool));
1409251881Speter
1410251881Speter          /* Reset *MERGE_CONTENT_OUTCOME etc. if a conflict was resolved. */
1411251881Speter          SVN_ERR(svn_wc__internal_conflicted_p(
1412251881Speter                    &text_conflicted, &prop_conflicted, NULL,
1413251881Speter                    wc_ctx->db, target_abspath, scratch_pool));
1414251881Speter          if (*merge_props_outcome == svn_wc_notify_state_conflicted
1415251881Speter              && ! prop_conflicted)
1416251881Speter            *merge_props_outcome = svn_wc_notify_state_merged;
1417251881Speter          if (*merge_content_outcome == svn_wc_merge_conflict
1418251881Speter              && ! text_conflicted)
1419251881Speter            *merge_content_outcome = svn_wc_merge_merged;
1420251881Speter        }
1421251881Speter    }
1422251881Speter
1423251881Speter  return SVN_NO_ERROR;
1424251881Speter}
1425