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