source: valtobtest/subversion-1.6.2/subversion/libsvn_wc/merge.c @ 3

Last change on this file since 3 was 3, checked in by valtob, 15 years ago

subversion source 1.6.2 as test

File size: 52.2 KB
Line 
1/*
2 * merge.c:  merging changes into a working file
3 *
4 * ====================================================================
5 * Copyright (c) 2000-2006, 2008 CollabNet.  All rights reserved.
6 *
7 * This software is licensed as described in the file COPYING, which
8 * you should have received as part of this distribution.  The terms
9 * are also available at http://subversion.tigris.org/license-1.html.
10 * If newer versions of this license are posted there, you may use a
11 * newer version instead, at your option.
12 *
13 * This software consists of voluntary contributions made by many
14 * individuals.  For exact contribution history, see the revision
15 * history and logs, available at http://subversion.tigris.org/.
16 * ====================================================================
17 */
18
19#include "svn_wc.h"
20#include "svn_diff.h"
21#include "svn_path.h"
22#include "svn_pools.h"
23
24#include "wc.h"
25#include "entries.h"
26#include "adm_files.h"
27#include "translate.h"
28#include "questions.h"
29#include "log.h"
30
31#include "svn_private_config.h"
32
33
34/* Return a pointer to the svn_prop_t structure from PROP_DIFF
35   belonging to PROP_NAME, if any.  NULL otherwise.*/
36static const svn_prop_t *
37get_prop(const apr_array_header_t *prop_diff,
38         const char *prop_name)
39{
40  if (prop_diff)
41    {
42      int i;
43      for (i = 0; i < prop_diff->nelts; i++)
44        {
45          const svn_prop_t *elt = &APR_ARRAY_IDX(prop_diff, i, svn_prop_t);
46          if (strcmp(elt->name,prop_name) == 0)
47            return elt;
48        }
49    }
50
51  return NULL;
52}
53
54
55/* Detranslate a working copy file MERGE_TARGET to achieve the effect of:
56
57   1. Detranslate
58   2. Install new props
59   3. Retranslate
60   4. Detranslate
61
62   in 1 pass to get a file which can be compared with the left and right
63   files which were created with the 'new props' above.
64
65   Property changes make this a little complex though. Changes in
66
67   - svn:mime-type
68   - svn:eol-style
69   - svn:keywords
70   - svn:special
71
72   may change the way a file is translated.
73
74   Effect for svn:mime-type:
75
76     The value for svn:mime-type affects the translation wrt keywords
77     and eol-style settings.
78
79   I) both old and new mime-types are texty
80      -> just do the translation dance (as lined out below)
81
82   II) the old one is texty, the new one is binary
83      -> detranslate with the old eol-style and keywords
84         (the new re+detranslation is a no-op)
85
86   III) the old one is binary, the new one texty
87      -> detranslate with the new eol-style
88         (the old detranslation is a no-op)
89
90   IV) the old and new ones are binary
91      -> don't detranslate, just make a straight copy
92
93
94   Effect for svn:eol-style
95
96   I) On add or change use the new value
97
98   II) otherwise: use the old value (absent means 'no translation')
99
100
101   Effect for svn:keywords
102
103     Always use old settings (re+detranslation are no-op)
104
105
106   Effect for svn:special
107
108     Always use the old settings (same reasons as for svn:keywords)
109
110*/
111static svn_error_t *
112detranslate_wc_file(const char **detranslated_file,
113                    const char *merge_target,
114                    svn_wc_adm_access_t *adm_access,
115                    svn_boolean_t force_copy,
116                    const apr_array_header_t *prop_diff,
117                    apr_pool_t *pool)
118{
119  svn_boolean_t is_binary;
120  const svn_prop_t *prop;
121  svn_subst_eol_style_t style;
122  const char *eol;
123  apr_hash_t *keywords;
124  svn_boolean_t special;
125
126  /* Decide if the merge target currently is a text or binary file. */
127  SVN_ERR(svn_wc_has_binary_prop(&is_binary,
128                                 merge_target, adm_access, pool));
129
130
131  /* See if we need to do a straight copy:
132     - old and new mime-types are binary, or
133     - old mime-type is binary and no new mime-type specified */
134  if (is_binary
135      && (((prop = get_prop(prop_diff, SVN_PROP_MIME_TYPE))
136           && prop->value && svn_mime_type_is_binary(prop->value->data))
137          || prop == NULL))
138    {
139      /* this is case IV above */
140      keywords = NULL;
141      special = FALSE;
142      eol = NULL;
143      style = svn_subst_eol_style_none;
144    }
145  else if ((!is_binary)
146           && (prop = get_prop(prop_diff, SVN_PROP_MIME_TYPE))
147           && prop->value && svn_mime_type_is_binary(prop->value->data))
148    {
149      /* Old props indicate texty, new props indicate binary:
150         detranslate keywords and old eol-style */
151      SVN_ERR(svn_wc__get_keywords(&keywords, merge_target,
152                                   adm_access, NULL, pool));
153      SVN_ERR(svn_wc__get_special(&special, merge_target, adm_access, pool));
154    }
155  else
156    {
157      /* New props indicate texty, regardless of old props */
158
159      /* In case the file used to be special, detranslate specially */
160      SVN_ERR(svn_wc__get_special(&special, merge_target, adm_access, pool));
161
162      if (special)
163        {
164          keywords = NULL;
165          eol = NULL;
166          style = svn_subst_eol_style_none;
167        }
168      else
169        {
170          /* In case a new eol style was set, use that for detranslation */
171          if ((prop = get_prop(prop_diff, SVN_PROP_EOL_STYLE)) && prop->value)
172            {
173              /* Value added or changed */
174              svn_subst_eol_style_from_value(&style, &eol, prop->value->data);
175            }
176          else if (!is_binary)
177            SVN_ERR(svn_wc__get_eol_style(&style, &eol, merge_target,
178                                          adm_access, pool));
179          else
180            {
181              eol = NULL;
182              style = svn_subst_eol_style_none;
183            }
184
185          /* In case there were keywords, detranslate with keywords
186             (iff we were texty) */
187          if (!is_binary)
188            SVN_ERR(svn_wc__get_keywords(&keywords, merge_target,
189                                         adm_access, NULL, pool));
190          else
191            keywords = NULL;
192        }
193    }
194
195  /* Now, detranslate with the settings we created above */
196
197  if (force_copy || keywords || eol || special)
198    {
199      const char *detranslated;
200
201      /* Force a copy into the temporary wc area to avoid having
202         temporary files created below to appear in the actual wc. */
203
204      SVN_ERR(svn_wc_create_tmp_file2
205              (NULL, &detranslated,
206               svn_wc_adm_access_path(adm_access),
207               svn_io_file_del_none, pool));
208
209      /* Always 'repair' EOLs here, so that we can apply a diff that
210         changes from inconsistent newlines and no 'svn:eol-style' to
211         consistent newlines and 'svn:eol-style' set.  */
212
213      if (style == svn_subst_eol_style_native)
214        eol = SVN_SUBST_NATIVE_EOL_STR;
215      else if (style != svn_subst_eol_style_fixed
216               && style != svn_subst_eol_style_none)
217        return svn_error_create(SVN_ERR_IO_UNKNOWN_EOL, NULL, NULL);
218
219      SVN_ERR(svn_subst_copy_and_translate3(merge_target,
220                                            detranslated,
221                                            eol,
222                                            TRUE /* repair */,
223                                            keywords,
224                                            FALSE /* contract keywords */,
225                                            special,
226                                            pool));
227
228      *detranslated_file = detranslated;
229    }
230  else
231    *detranslated_file = merge_target;
232
233  return SVN_NO_ERROR;
234}
235
236/* Updates (by copying and translating) the eol style in
237   OLD_TARGET returning the filename containing the
238   correct eol style in NEW_TARGET, if an eol style
239   change is contained in PROP_DIFF */
240static svn_error_t *
241maybe_update_target_eols(const char **new_target,
242                         const char *old_target,
243                         svn_wc_adm_access_t *adm_access,
244                         const apr_array_header_t *prop_diff,
245                         apr_pool_t *pool)
246{
247  const svn_prop_t *prop = get_prop(prop_diff, SVN_PROP_EOL_STYLE);
248
249  if (prop && prop->value)
250    {
251      const char *eol;
252      const char *tmp_new;
253
254      svn_subst_eol_style_from_value(NULL, &eol, prop->value->data);
255      SVN_ERR(svn_wc_create_tmp_file2(NULL, &tmp_new,
256                                      svn_wc_adm_access_path(adm_access),
257                                      svn_io_file_del_none,
258                                      pool));
259
260      /* Always 'repair' EOLs here, so that we can apply a diff that
261         changes from inconsistent newlines and no 'svn:eol-style' to
262         consistent newlines and 'svn:eol-style' set.  */
263      SVN_ERR(svn_subst_copy_and_translate3(old_target,
264                                            tmp_new,
265                                            eol, TRUE /* repair EOLs */,
266                                            NULL, FALSE,
267                                            FALSE, pool));
268      *new_target = tmp_new;
269    }
270  else
271    *new_target = old_target;
272
273  return SVN_NO_ERROR;
274}
275
276/* Like svn_wc_create_tmp_file2(), but adds TEMPLATE_PATH as the path
277   from which a meaningful-to-humans name is derived. */
278static svn_error_t *
279create_name_preserving_tmp_file(apr_file_t **fp,
280                                const char **new_name,
281                                const char *adm_path,
282                                const char *template_path,
283                                svn_io_file_del_t delete_when,
284                                apr_pool_t *pool)
285{
286  const char *temp_dir;
287  apr_file_t *file;
288  const char *base_name = svn_path_basename(template_path, pool);
289  apr_pool_t *scratch_pool = svn_pool_create(pool);
290
291  SVN_ERR_ASSERT(fp || new_name);
292
293  temp_dir = svn_wc__adm_child(adm_path, SVN_WC__ADM_TMP, pool);
294  SVN_ERR(svn_io_open_uniquely_named(&file, new_name,
295                                     temp_dir, base_name, ".tmp",
296                                     delete_when, pool, scratch_pool));
297  if (fp)
298    *fp = file;
299  else
300    SVN_ERR(svn_io_file_close(file, pool));
301
302  svn_pool_destroy(scratch_pool);
303  return SVN_NO_ERROR;
304}
305
306/* Helper for do_text_merge_internal() below. */
307static void
308init_conflict_markers(const char **target_marker,
309                      const char **left_marker,
310                      const char **right_marker,
311                      const char *target_label,
312                      const char *left_label,
313                      const char *right_label,
314                      apr_pool_t *pool)
315{
316  /* Labels fall back to sensible defaults if not specified. */
317  if (target_label)
318    *target_marker = apr_psprintf(pool, "<<<<<<< %s", target_label);
319  else
320    *target_marker = "<<<<<<< .working";
321
322  if (left_label)
323    *left_marker = apr_psprintf(pool, "||||||| %s", left_label);
324  else
325    *left_marker = "||||||| .old";
326
327  if (right_label)
328    *right_marker = apr_psprintf(pool, ">>>>>>> %s", right_label);
329  else
330    *right_marker = ">>>>>>> .new";
331}
332
333/* Do a 3-way merge of the files at paths LEFT, DETRANSLATED_TARGET,
334 * and RIGHT, using diff options provided in OPTIONS.  Store the merge
335 * result in the file RESULT_F.
336 * If there are conflicts, set *CONTAINS_CONFLICTS to true, and use
337 * TARGET_LABEL, LEFT_LABEL, and RIGHT_LABEL as labels for conflict
338 * markers.  Else, set *CONTAINS_CONFLICTS to false.
339 * Do all allocations in POOL. */
340static svn_error_t*
341do_text_merge(svn_boolean_t *contains_conflicts,
342              apr_file_t *result_f,
343              const char *detranslated_target,
344              const char *left,
345              const char *right,
346              const char *target_label,
347              const char *left_label,
348              const char *right_label,
349              svn_diff_file_options_t *options,
350              apr_pool_t *pool)
351{
352  svn_diff_t *diff;
353  svn_stream_t *ostream;
354  const char *target_marker;
355  const char *left_marker;
356  const char *right_marker;
357
358  init_conflict_markers(&target_marker, &left_marker, &right_marker,
359                        target_label, left_label, right_label, pool);
360
361  SVN_ERR(svn_diff_file_diff3_2(&diff, left, detranslated_target, right,
362                                options, pool));
363
364  ostream = svn_stream_from_aprfile2(result_f, TRUE, pool);
365
366  SVN_ERR(svn_diff_file_output_merge2(ostream, diff,
367                                      left, detranslated_target, right,
368                                      left_marker,
369                                      target_marker,
370                                      right_marker,
371                                      "=======", /* separator */
372                                      svn_diff_conflict_display_modified_latest,
373                                      pool));
374  SVN_ERR(svn_stream_close(ostream));
375
376  *contains_conflicts = svn_diff_contains_conflicts(diff);
377
378  return SVN_NO_ERROR;
379}
380
381/* Same as do_text_merge() above, but use the external diff3
382 * command DIFF3_CMD to perform the merge.  Pass MERGE_OPTIONS
383 * to the diff3 command.  Do all allocations in POOL. */
384static svn_error_t*
385do_text_merge_external(svn_boolean_t *contains_conflicts,
386                            apr_file_t *result_f,
387                            const char *detranslated_target,
388                            const char *left,
389                            const char *right,
390                            const char *target_label,
391                            const char *left_label,
392                            const char *right_label,
393                            const char *diff3_cmd,
394                            const apr_array_header_t *merge_options,
395                            apr_pool_t *pool)
396{
397  int exit_code;
398
399  SVN_ERR(svn_io_run_diff3_2(&exit_code, ".",
400                             detranslated_target, left, right,
401                             target_label, left_label, right_label,
402                             result_f, diff3_cmd,
403                             merge_options, pool));
404
405  *contains_conflicts = exit_code == 1;
406
407  return SVN_NO_ERROR;
408}
409
410/* Loggy-copy the merge result obtained during interactive conflict
411 * resolution to the file RESULT_TARGET. The merge result is expected
412 * in the MERGE_DIRPATH directory with the name MERGE_FILENAME+".edited".
413 * Use LOG_ACCUM as log accumulator.  ADM_ACCESS is an access baton with
414 * a write lock for the directory containing RESULT_TARGET.
415 * Do all allocations in POOL.
416 */
417static svn_error_t*
418save_merge_result(svn_stringbuf_t **log_accum,
419                  svn_wc_adm_access_t *adm_access,
420                  const char *result_target,
421                  const char *merge_dirpath,
422                  const char *merge_filename,
423                  apr_pool_t *pool)
424{
425  const char *edited_copy;
426
427  /* ### Should use preserved-conflict-file-exts. */
428  SVN_ERR(svn_io_open_uniquely_named(NULL,
429                                     &edited_copy,
430                                     merge_dirpath,
431                                     merge_filename,
432                                     ".edited",
433                                     svn_io_file_del_none,
434                                     pool, pool));
435  SVN_ERR(svn_wc__loggy_copy(log_accum, adm_access,
436                             result_target, edited_copy, pool));
437  return SVN_NO_ERROR;
438}
439
440/* Deal with the RESULT of the conflict resolution callback.
441 * LEFT, RIGHT, and MERGE_TARGET are the files involved in
442 * the 3-way merge.  Store the result of the 3-way merge in
443 * MERGE_OUTCOME.  If the callback did not provide the name to
444 * a merged file, use RESULT_TARGET is a fallback.
445 * DETRANSLATED_TARGET is the detranslated version of MERGE_TARGET
446 * (see detranslate_wc_file() above).  OPTIONS are passed to the
447 * diff3 implementation in case a 3-way  merge has to be carried out.
448 * Do all allocations in POOL. */
449static svn_error_t*
450eval_conflict_func_result(enum svn_wc_merge_outcome_t *merge_outcome,
451                          svn_wc_conflict_result_t *result,
452                          svn_stringbuf_t **log_accum,
453                          const char *left,
454                          const char *right,
455                          const char *merge_target,
456                          const char *copyfrom_text,
457                          svn_wc_adm_access_t *adm_access,
458                          const char *result_target,
459                          const char *detranslated_target,
460                          svn_diff_file_options_t *options,
461                          apr_pool_t *pool)
462{
463  switch (result->choice)
464    {
465      /* If the callback wants to use one of the fulltexts
466         to resolve the conflict, so be it.*/
467      case svn_wc_conflict_choose_base:
468        {
469          SVN_ERR(svn_wc__loggy_copy(log_accum, adm_access,
470                                     left, merge_target,
471                                     pool));
472          *merge_outcome = svn_wc_merge_merged;
473          return SVN_NO_ERROR;
474        }
475      case svn_wc_conflict_choose_theirs_full:
476        {
477          SVN_ERR(svn_wc__loggy_copy(log_accum, adm_access,
478                                     right, merge_target,
479                                     pool));
480          *merge_outcome = svn_wc_merge_merged;
481          return SVN_NO_ERROR;
482        }
483      case svn_wc_conflict_choose_mine_full:
484        {
485          /* Do nothing to merge_target, let it live untouched! */
486          *merge_outcome = svn_wc_merge_merged;
487          return SVN_NO_ERROR;
488        }
489      case svn_wc_conflict_choose_theirs_conflict:
490      case svn_wc_conflict_choose_mine_conflict:
491        {
492          apr_file_t *chosen_f;
493          const char *chosen_path;
494          svn_stream_t *chosen_stream;
495          svn_diff_t *diff;
496          svn_diff_conflict_display_style_t style =
497            result->choice == svn_wc_conflict_choose_theirs_conflict
498            ? svn_diff_conflict_display_latest
499            : svn_diff_conflict_display_modified;
500
501          SVN_ERR(svn_wc_create_tmp_file2(&chosen_f,
502                                          &chosen_path,
503                                          svn_wc_adm_access_path(adm_access),
504                                          svn_io_file_del_none,
505                                          pool));
506          chosen_stream = svn_stream_from_aprfile2(chosen_f, FALSE,
507                                                   pool);
508          SVN_ERR(svn_diff_file_diff3_2(&diff,
509                                        left, detranslated_target, right,
510                                        options, pool));
511          SVN_ERR(svn_diff_file_output_merge2(chosen_stream, diff,
512                                              left,
513                                              detranslated_target,
514                                              right,
515                                              /* markers ignored */
516                                              NULL, NULL,
517                                              NULL, NULL,
518                                              style,
519                                              pool));
520          SVN_ERR(svn_stream_close(chosen_stream));
521          SVN_ERR(svn_wc__loggy_copy(log_accum, adm_access,
522                                     chosen_path, merge_target,
523                                     pool));
524          *merge_outcome = svn_wc_merge_merged;
525          return SVN_NO_ERROR;
526        }
527
528        /* For the case of 3-way file merging, we don't
529           really distinguish between these return values;
530           if the callback claims to have "generally
531           resolved" the situation, we still interpret
532           that as "OK, we'll assume the merged version is
533           good to use". */
534      case svn_wc_conflict_choose_merged:
535        {
536          SVN_ERR(svn_wc__loggy_copy(log_accum, adm_access,
537                                     /* Look for callback's own
538                                        merged-file first: */
539                                     result->merged_file
540                                       ? result->merged_file
541                                       : result_target,
542                                     merge_target,
543                                     pool));
544          *merge_outcome = svn_wc_merge_merged;
545          return SVN_NO_ERROR;
546        }
547      case svn_wc_conflict_choose_postpone:
548      default:
549        {
550          /* Issue #3354: We need to install the copyfrom_text,
551           * which now carries conflicts, into ACTUAL, by copying
552           * it to the merge target. */
553          if (copyfrom_text)
554            {
555              SVN_ERR(svn_wc__loggy_copy(log_accum, adm_access,
556                                         copyfrom_text, merge_target,
557                                         pool));
558            }
559
560          /* Assume conflict remains. */
561          return SVN_NO_ERROR;
562        }
563    }
564}
565
566/* Preserve the three pre-merge files, and modify the
567   entry (mark as conflicted, track the preserved files). */
568static svn_error_t*
569preserve_pre_merge_files(svn_stringbuf_t **log_accum,
570                         const char *left,
571                         const char *right,
572                         const char *merge_target,
573                         svn_wc_adm_access_t *adm_access,
574                         const char *left_label,
575                         const char *right_label,
576                         const char *target_label,
577                         const char *merge_dirpath,
578                         const char *merge_filename,
579                         apr_pool_t *pool)
580{
581  const char *left_copy, *right_copy, *target_copy;
582  const char *tmp_left, *tmp_right, *detranslated_target_copy;
583  const char *parent, *target_base;
584  svn_wc_adm_access_t *parent_access;
585  const char *adm_path = svn_wc_adm_access_path(adm_access);
586  svn_wc_entry_t tmp_entry;
587
588  /* I miss Lisp. */
589
590  SVN_ERR(svn_io_open_uniquely_named(NULL,
591                                     &left_copy,
592                                     merge_dirpath,
593                                     merge_filename,
594                                     left_label,
595                                     svn_io_file_del_none,
596                                     pool, pool));
597
598  /* Have I mentioned how much I miss Lisp? */
599
600  SVN_ERR(svn_io_open_uniquely_named(NULL,
601                                     &right_copy,
602                                     merge_dirpath,
603                                     merge_filename,
604                                     right_label,
605                                     svn_io_file_del_none,
606                                     pool, pool));
607
608  /* Why, how much more pleasant to be forced to unroll my loops.
609     If I'd been writing in Lisp, I might have mapped an inline
610     lambda form over a list, or something equally disgusting.
611     Thank goodness C was here to protect me! */
612
613  SVN_ERR(svn_io_open_uniquely_named(NULL,
614                                     &target_copy,
615                                     merge_dirpath,
616                                     merge_filename,
617                                     target_label,
618                                     svn_io_file_del_none,
619                                     pool, pool));
620
621  /* We preserve all the files with keywords expanded and line
622     endings in local (working) form. */
623
624  svn_path_split(target_copy, &parent, &target_base, pool);
625  SVN_ERR(svn_wc_adm_retrieve(&parent_access, adm_access, parent,
626                              pool));
627
628  /* Log files require their paths to be in the subtree
629     relative to the adm_access path they are executed in.
630
631     Make our LEFT and RIGHT files 'local' if they aren't... */
632  if (! svn_path_is_child(adm_path, left, pool))
633    {
634      SVN_ERR(svn_wc_create_tmp_file2
635              (NULL, &tmp_left,
636               adm_path, svn_io_file_del_none, pool));
637      SVN_ERR(svn_io_copy_file(left, tmp_left, TRUE, pool));
638    }
639  else
640    tmp_left = left;
641
642  if (! svn_path_is_child(adm_path, right, pool))
643    {
644      SVN_ERR(svn_wc_create_tmp_file2
645              (NULL, &tmp_right,
646               adm_path, svn_io_file_del_none, pool));
647      SVN_ERR(svn_io_copy_file(right, tmp_right, TRUE, pool));
648    }
649  else
650    tmp_right = right;
651
652  /* NOTE: Callers must ensure that the svn:eol-style and
653     svn:keywords property values are correct in the currently
654     installed props.  With 'svn merge', it's no big deal.  But
655     when 'svn up' calls this routine, it needs to make sure that
656     this routine is using the newest property values that may
657     have been received *during* the update.  Since this routine
658     will be run from within a log-command, merge_file()
659     needs to make sure that a previous log-command to 'install
660     latest props' has already executed first.  Ben and I just
661     checked, and that is indeed the order in which the log items
662     are written, so everything should be fine.  Really.  */
663
664  /* Create LEFT and RIGHT backup files, in expanded form.
665     We use merge_target's current properties to do the translation. */
666  /* Derive the basenames of the 3 backup files. */
667  SVN_ERR(svn_wc__loggy_translated_file(log_accum,
668                                        adm_access,
669                                        left_copy, tmp_left,
670                                        merge_target, pool));
671  SVN_ERR(svn_wc__loggy_translated_file(log_accum,
672                                        adm_access,
673                                        right_copy, tmp_right,
674                                        merge_target, pool));
675
676  /* Back up MERGE_TARGET through detranslation/retranslation:
677     the new translation properties may not match the current ones */
678  SVN_ERR(svn_wc_translated_file2(&detranslated_target_copy,
679                                  merge_target,
680                                  merge_target,
681                                  adm_access,
682                                  SVN_WC_TRANSLATE_TO_NF
683                                  | SVN_WC_TRANSLATE_NO_OUTPUT_CLEANUP,
684                                  pool));
685  SVN_ERR(svn_wc__loggy_translated_file
686          (log_accum, adm_access,
687           target_copy, detranslated_target_copy, merge_target, pool));
688
689  tmp_entry.conflict_old
690    = svn_path_is_child(adm_path, left_copy, pool);
691  tmp_entry.conflict_new
692    = svn_path_is_child(adm_path, right_copy, pool);
693  tmp_entry.conflict_wrk = target_base;
694
695  /* Mark merge_target's entry as "Conflicted", and start tracking
696     the backup files in the entry as well. */
697  SVN_ERR(svn_wc__loggy_entry_modify
698          (log_accum, adm_access,
699           merge_target, &tmp_entry,
700           SVN_WC__ENTRY_MODIFY_CONFLICT_OLD
701           | SVN_WC__ENTRY_MODIFY_CONFLICT_NEW
702           | SVN_WC__ENTRY_MODIFY_CONFLICT_WRK,
703           pool));
704
705  return SVN_NO_ERROR;
706}
707
708/* Helper for maybe_resolve_conflicts() below. */
709static svn_wc_conflict_description_t *
710setup_text_conflict_desc(const char *left,
711                         const char *right,
712                         const char *merge_target,
713                         svn_wc_adm_access_t *adm_access,
714                         svn_wc_conflict_version_t *left_version,
715                         svn_wc_conflict_version_t *right_version,
716                         const char *result_target,
717                         const char *detranslated_target,
718                         const svn_prop_t *mimeprop,
719                         apr_pool_t *pool)
720{
721  svn_wc_conflict_description_t *cdesc;
722
723  cdesc = svn_wc_conflict_description_create_text(merge_target,
724                                                  adm_access,
725                                                  pool);
726  cdesc->is_binary = FALSE;
727  cdesc->mime_type = (mimeprop && mimeprop->value)
728                     ? mimeprop->value->data : NULL,
729  cdesc->base_file = left;
730  cdesc->their_file = right;
731  cdesc->my_file = detranslated_target;
732  cdesc->merged_file = result_target;
733
734  cdesc->src_left_version = left_version;
735  cdesc->src_right_version = right_version;
736
737  return cdesc;
738}
739
740/* XXX Insane amount of parameters... */
741static svn_error_t*
742maybe_resolve_conflicts(svn_stringbuf_t **log_accum,
743                        const char *left,
744                        const char *right,
745                        const char *merge_target,
746                        const char *copyfrom_text,
747                        svn_wc_adm_access_t *adm_access,
748                        const char *left_label,
749                        const char *right_label,
750                        const char *target_label,
751                        svn_wc_conflict_resolver_func_t conflict_func,
752                        void *conflict_baton,
753                        enum svn_wc_merge_outcome_t *merge_outcome,
754                        svn_wc_conflict_version_t *left_version,
755                        svn_wc_conflict_version_t *right_version,
756                        const char *result_target,
757                        const char *detranslated_target,
758                        const svn_prop_t *mimeprop,
759                        const char *merge_dirpath,
760                        const char *merge_filename,
761                        svn_diff_file_options_t *options,
762                        apr_pool_t *pool)
763{
764  svn_wc_conflict_result_t *result = NULL;
765
766  /* Give the conflict resolution callback a chance to clean
767     up the conflicts before we mark the file 'conflicted' */
768  if (!conflict_func)
769    {
770      /* If there is no interactive conflict resolution then we are effectively
771         postponing conflict resolution. */
772      result = svn_wc_create_conflict_result(svn_wc_conflict_choose_postpone,
773                                             NULL, pool);
774    }
775  else
776    {
777      svn_wc_conflict_description_t *cdesc;
778
779      cdesc = setup_text_conflict_desc(left,
780                                      right,
781                                      merge_target,
782                                      adm_access,
783                                      left_version,
784                                      right_version,
785                                      result_target,
786                                      detranslated_target,
787                                      mimeprop,
788                                      pool);
789
790      SVN_ERR(conflict_func(&result, cdesc, conflict_baton, pool));
791      if (result == NULL)
792        return svn_error_create(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE,
793                                NULL, _("Conflict callback violated API:"
794                                        " returned no results"));
795      if (result->save_merged)
796        SVN_ERR(save_merge_result(log_accum,
797                                  adm_access,
798                                  /* Look for callback's own
799                                     merged-file first: */
800                                  result->merged_file
801                                    ? result->merged_file
802                                    : result_target,
803                                  merge_dirpath,
804                                  merge_filename,
805                                  pool));
806    }
807
808  SVN_ERR(eval_conflict_func_result(merge_outcome,
809                                    result,
810                                    log_accum,
811                                    left,
812                                    right,
813                                    merge_target,
814                                    copyfrom_text,
815                                    adm_access,
816                                    result_target,
817                                    detranslated_target,
818                                    options,
819                                    pool));
820
821  if (result->choice != svn_wc_conflict_choose_postpone)
822    /* The conflicts have been dealt with, nothing else
823     * to do for us here. */
824    return SVN_NO_ERROR;
825
826  /* The conflicts have not been dealt with. */
827  SVN_ERR(preserve_pre_merge_files(log_accum,
828                                   left,
829                                   right,
830                                   merge_target,
831                                   adm_access,
832                                   left_label,
833                                   right_label,
834                                   target_label,
835                                   merge_dirpath,
836                                   merge_filename,
837                                   pool));
838
839  *merge_outcome = svn_wc_merge_conflict;
840
841  return SVN_NO_ERROR;
842}
843
844/* XXX Insane amount of parameters... */
845static svn_error_t*
846merge_text_file(const char *left,
847                const char *right,
848                const char *merge_target,
849                svn_wc_adm_access_t *adm_access,
850                const char *left_label,
851                const char *right_label,
852                const char *target_label,
853                svn_boolean_t dry_run,
854                const char *diff3_cmd,
855                const apr_array_header_t *merge_options,
856                svn_wc_conflict_resolver_func_t conflict_func,
857                void *conflict_baton,
858                svn_stringbuf_t **log_accum,
859                enum svn_wc_merge_outcome_t *merge_outcome,
860                svn_wc_conflict_version_t *left_version,
861                svn_wc_conflict_version_t *right_version,
862                const char *copyfrom_text,
863                const char *detranslated_target,
864                const svn_prop_t *mimeprop,
865                const char *merge_dirpath,
866                const char *merge_filename,
867                apr_pool_t *pool)
868{
869  svn_diff_file_options_t *options;
870  svn_boolean_t contains_conflicts;
871  apr_file_t *result_f;
872  const char *result_target;
873
874  /* Open a second temporary file for writing; this is where diff3
875     will write the merged results.  We want to use a tempfile
876     with a name that reflects the original, in case this
877     ultimately winds up in a conflict resolution editor.  */
878  SVN_ERR(create_name_preserving_tmp_file(&result_f, &result_target,
879                                          svn_wc_adm_access_path(adm_access),
880                                          merge_target, svn_io_file_del_none,
881                                          pool));
882
883  options = svn_diff_file_options_create(pool);
884
885  if (merge_options)
886    SVN_ERR(svn_diff_file_options_parse(options, merge_options, pool));
887
888  /* Run an external merge if requested. */
889  if (diff3_cmd)
890      SVN_ERR(do_text_merge_external(&contains_conflicts,
891                                     result_f,
892                                     detranslated_target,
893                                     left,
894                                     right,
895                                     target_label,
896                                     left_label,
897                                     right_label,
898                                     diff3_cmd,
899                                     merge_options,
900                                     pool));
901  else /* Use internal merge. */
902    SVN_ERR(do_text_merge(&contains_conflicts,
903                          result_f,
904                          detranslated_target,
905                          left,
906                          right,
907                          target_label,
908                          left_label,
909                          right_label,
910                          options,
911                          pool));
912
913  /* Close the output file */
914  SVN_ERR(svn_io_file_close(result_f, pool));
915
916  if (contains_conflicts && ! dry_run)
917    {
918      SVN_ERR(maybe_resolve_conflicts(log_accum,
919                                      left,
920                                      right,
921                                      merge_target,
922                                      copyfrom_text,
923                                      adm_access,
924                                      left_label,
925                                      right_label,
926                                      target_label,
927                                      conflict_func,
928                                      conflict_baton,
929                                      merge_outcome,
930                                      left_version,
931                                      right_version,
932                                      result_target,
933                                      detranslated_target,
934                                      mimeprop,
935                                      merge_dirpath,
936                                      merge_filename,
937                                      options,
938                                      pool));
939      if (*merge_outcome == svn_wc_merge_merged)
940        return SVN_NO_ERROR;
941    }
942  else if (contains_conflicts && dry_run)
943      *merge_outcome = svn_wc_merge_conflict;
944  else if (copyfrom_text)
945      *merge_outcome = svn_wc_merge_merged;
946  else
947    {
948      svn_boolean_t same, special;
949      /* If 'special', then use the detranslated form of the
950         target file.  This is so we don't try to follow symlinks,
951         but the same treatment is probably also appropriate for
952         whatever special file types we may invent in the future. */
953      SVN_ERR(svn_wc__get_special(&special, merge_target,
954                                  adm_access, pool));
955      SVN_ERR(svn_io_files_contents_same_p(&same, result_target,
956                                           (special ?
957                                              detranslated_target :
958                                              merge_target),
959                                           pool));
960
961      *merge_outcome = same ? svn_wc_merge_unchanged : svn_wc_merge_merged;
962    }
963
964  if (*merge_outcome != svn_wc_merge_unchanged && ! dry_run)
965    /* replace MERGE_TARGET with the new merged file, expanding. */
966    SVN_ERR(svn_wc__loggy_copy(log_accum,
967                                   adm_access,
968                                   result_target, merge_target,
969                                   pool));
970  return SVN_NO_ERROR;
971}
972
973
974/* XXX Insane amount of parameters... */
975static svn_error_t*
976merge_binary_file(const char *left,
977                  const char *right,
978                  const char *merge_target,
979                  svn_wc_adm_access_t *adm_access,
980                  const char *left_label,
981                  const char *right_label,
982                  const char *target_label,
983                  svn_wc_conflict_resolver_func_t conflict_func,
984                  void *conflict_baton,
985                  svn_stringbuf_t **log_accum,
986                  enum svn_wc_merge_outcome_t *merge_outcome,
987                  svn_wc_conflict_version_t *left_version,
988                  svn_wc_conflict_version_t *right_version,
989                  const char *detranslated_target,
990                  const svn_prop_t *mimeprop,
991                  const char *merge_dirpath,
992                  const char *merge_filename,
993                  apr_pool_t *pool)
994{
995  /* ### when making the binary-file backups, should we be honoring
996     keywords and eol stuff?   */
997  const char *left_copy, *right_copy;
998  const char *parent, *left_base, *right_base;
999  svn_wc_entry_t tmp_entry;
1000
1001  /* Give the conflict resolution callback a chance to clean
1002     up the conflict before we mark the file 'conflicted' */
1003  if (conflict_func)
1004    {
1005      svn_wc_conflict_result_t *result = NULL;
1006      svn_wc_conflict_description_t *cdesc;
1007
1008      cdesc = svn_wc_conflict_description_create_text(merge_target,
1009                                                      adm_access, pool);
1010      cdesc->is_binary = TRUE;
1011      cdesc->mime_type = (mimeprop && mimeprop->value)
1012                         ? mimeprop->value->data : NULL,
1013      cdesc->base_file = left;
1014      cdesc->their_file = right;
1015      cdesc->my_file = detranslated_target;
1016      cdesc->merged_file = NULL;     /* notice there is NO merged file! */
1017
1018      cdesc->src_left_version = left_version;
1019      cdesc->src_right_version = right_version;
1020
1021      SVN_ERR(conflict_func(&result, cdesc, conflict_baton, pool));
1022      if (result == NULL)
1023        return svn_error_create(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE,
1024                                NULL, _("Conflict callback violated API:"
1025                                        " returned no results"));
1026
1027      switch (result->choice)
1028        {
1029          /* For a binary file, there's no merged file to look at,
1030             unless the conflict-callback did the merging itself. */
1031          case svn_wc_conflict_choose_base:
1032            {
1033              SVN_ERR(svn_wc__loggy_copy(log_accum, adm_access,
1034                                         left, merge_target,
1035                                         pool));
1036              *merge_outcome = svn_wc_merge_merged;
1037              return SVN_NO_ERROR;
1038            }
1039          case svn_wc_conflict_choose_theirs_full:
1040            {
1041              SVN_ERR(svn_wc__loggy_copy(log_accum, adm_access,
1042                                         right, merge_target,
1043                                         pool));
1044              *merge_outcome = svn_wc_merge_merged;
1045              return SVN_NO_ERROR;
1046            }
1047            /* For a binary file, if the response is to use the
1048               user's file, we do nothing.  We also do nothing if
1049               the response claims to have already resolved the
1050               problem.*/
1051          case svn_wc_conflict_choose_mine_full:
1052            {
1053              *merge_outcome = svn_wc_merge_merged;
1054              return SVN_NO_ERROR;
1055            }
1056          case svn_wc_conflict_choose_merged:
1057            {
1058              if (! result->merged_file)
1059                {
1060                  /* Callback asked us to choose its own
1061                     merged file, but didn't provide one! */
1062                  return svn_error_create
1063                      (SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE,
1064                       NULL, _("Conflict callback violated API:"
1065                               " returned no merged file"));
1066                }
1067              else
1068                {
1069                  SVN_ERR(svn_wc__loggy_copy(log_accum, adm_access,
1070                                             result->merged_file,
1071                                             merge_target,
1072                                             pool));
1073                  *merge_outcome = svn_wc_merge_merged;
1074                  return SVN_NO_ERROR;
1075                }
1076            }
1077          case svn_wc_conflict_choose_postpone:
1078          default:
1079            {
1080              /* Assume conflict remains, fall through to code below. */
1081            }
1082        }
1083    }
1084
1085  /* reserve names for backups of left and right fulltexts */
1086  SVN_ERR(svn_io_open_uniquely_named(NULL,
1087                                     &left_copy,
1088                                     merge_dirpath,
1089                                     merge_filename,
1090                                     left_label,
1091                                     svn_io_file_del_none,
1092                                     pool, pool));
1093
1094  SVN_ERR(svn_io_open_uniquely_named(NULL,
1095                                     &right_copy,
1096                                     merge_dirpath,
1097                                     merge_filename,
1098                                     right_label,
1099                                     svn_io_file_del_none,
1100                                     pool, pool));
1101
1102  /* create the backup files */
1103  SVN_ERR(svn_io_copy_file(left,
1104                           left_copy, TRUE, pool));
1105  SVN_ERR(svn_io_copy_file(right,
1106                           right_copy, TRUE, pool));
1107
1108  /* Was the merge target detranslated? */
1109  if (merge_target != detranslated_target)
1110    {
1111      /* Create a .mine file too */
1112      const char *mine_copy;
1113
1114      SVN_ERR(svn_io_open_uniquely_named(NULL,
1115                                         &mine_copy,
1116                                         merge_dirpath,
1117                                         merge_filename,
1118                                         target_label,
1119                                         svn_io_file_del_none,
1120                                         pool, pool));
1121      SVN_ERR(svn_wc__loggy_move(log_accum,
1122                                 adm_access,
1123                                 detranslated_target,
1124                                 mine_copy,
1125                                 pool));
1126      mine_copy = svn_path_is_child(svn_wc_adm_access_path(adm_access),
1127                                    mine_copy, pool);
1128      tmp_entry.conflict_wrk = mine_copy;
1129    }
1130  else
1131    tmp_entry.conflict_wrk = NULL;
1132
1133  /* Derive the basenames of the backup files. */
1134  svn_path_split(left_copy, &parent, &left_base, pool);
1135  svn_path_split(right_copy, &parent, &right_base, pool);
1136
1137  /* Mark merge_target's entry as "Conflicted", and start tracking
1138     the backup files in the entry as well. */
1139  tmp_entry.conflict_old = left_base;
1140  tmp_entry.conflict_new = right_base;
1141  SVN_ERR(svn_wc__loggy_entry_modify
1142          (log_accum,
1143           adm_access, merge_target,
1144           &tmp_entry,
1145           SVN_WC__ENTRY_MODIFY_CONFLICT_OLD
1146           | SVN_WC__ENTRY_MODIFY_CONFLICT_NEW
1147           | SVN_WC__ENTRY_MODIFY_CONFLICT_WRK,
1148           pool));
1149
1150  *merge_outcome = svn_wc_merge_conflict; /* a conflict happened */
1151
1152  return SVN_NO_ERROR;
1153}
1154
1155/* XXX Insane amount of parameters... */
1156svn_error_t *
1157svn_wc__merge_internal(svn_stringbuf_t **log_accum,
1158                       enum svn_wc_merge_outcome_t *merge_outcome,
1159                       const char *left,
1160                       svn_wc_conflict_version_t *left_version,
1161                       const char *right,
1162                       svn_wc_conflict_version_t *right_version,
1163                       const char *merge_target,
1164                       const char *copyfrom_text,
1165                       svn_wc_adm_access_t *adm_access,
1166                       const char *left_label,
1167                       const char *right_label,
1168                       const char *target_label,
1169                       svn_boolean_t dry_run,
1170                       const char *diff3_cmd,
1171                       const apr_array_header_t *merge_options,
1172                       const apr_array_header_t *prop_diff,
1173                       svn_wc_conflict_resolver_func_t conflict_func,
1174                       void *conflict_baton,
1175                       apr_pool_t *pool)
1176{
1177  const char *merge_dirpath;
1178  const char *merge_filename;
1179  const char *detranslated_target, *working_text;
1180  svn_boolean_t is_binary = FALSE;
1181  const svn_wc_entry_t *entry;
1182  const svn_prop_t *mimeprop;
1183
1184  /* Sanity check:  the merge target must be under revision control,
1185   * unless the merge target is a copyfrom text, which lives in a
1186   * temporary file and does not exist in ACTUAL yet. */
1187  SVN_ERR(svn_wc_entry(&entry, merge_target, adm_access, FALSE, pool));
1188  if (! entry && ! copyfrom_text)
1189    {
1190      *merge_outcome = svn_wc_merge_no_merge;
1191      return SVN_NO_ERROR;
1192    }
1193
1194  svn_path_split(merge_target, &merge_dirpath, &merge_filename, pool);
1195
1196  /* Decide if the merge target is a text or binary file. */
1197  if ((mimeprop = get_prop(prop_diff, SVN_PROP_MIME_TYPE))
1198      && mimeprop->value)
1199    is_binary = svn_mime_type_is_binary(mimeprop->value->data);
1200  else if (! copyfrom_text)
1201    SVN_ERR(svn_wc_has_binary_prop(&is_binary, merge_target, adm_access, pool));
1202
1203  working_text = copyfrom_text ? copyfrom_text : merge_target;
1204  SVN_ERR(detranslate_wc_file(&detranslated_target, working_text, adm_access,
1205                              (! is_binary) && diff3_cmd != NULL,
1206                              prop_diff, pool));
1207
1208  /* We cannot depend on the left file to contain the same eols as the
1209     right file. If the merge target has mods, this will mark the entire
1210     file as conflicted, so we need to compensate. */
1211  SVN_ERR(maybe_update_target_eols(&left, left, adm_access, prop_diff, pool));
1212
1213  if (is_binary)
1214    {
1215      if (dry_run)
1216        /* in dry-run mode, binary files always conflict */
1217        *merge_outcome = svn_wc_merge_conflict;
1218      else
1219        merge_binary_file(left,
1220                          right,
1221                          merge_target,
1222                          adm_access,
1223                          left_label,
1224                          right_label,
1225                          target_label,
1226                          conflict_func,
1227                          conflict_baton,
1228                          log_accum,
1229                          merge_outcome,
1230                          left_version,
1231                          right_version,
1232                          detranslated_target,
1233                          mimeprop,
1234                          merge_dirpath,
1235                          merge_filename,
1236                          pool);
1237    }
1238  else
1239    SVN_ERR(merge_text_file(left,
1240                            right,
1241                            merge_target,
1242                            adm_access,
1243                            left_label,
1244                            right_label,
1245                            target_label,
1246                            dry_run,
1247                            diff3_cmd,
1248                            merge_options,
1249                            conflict_func,
1250                            conflict_baton,
1251                            log_accum,
1252                            merge_outcome,
1253                            left_version,
1254                            right_version,
1255                            copyfrom_text,
1256                            detranslated_target,
1257                            mimeprop,
1258                            merge_dirpath,
1259                            merge_filename,
1260                            pool));
1261
1262  /* Merging is complete.  Regardless of text or binariness, we might
1263     need to tweak the executable bit on the new working file, and
1264     possibly make it read-only. */
1265  if (! dry_run)
1266    {
1267      SVN_ERR(svn_wc__loggy_maybe_set_executable(log_accum, adm_access,
1268                                                 merge_target, pool));
1269      SVN_ERR(svn_wc__loggy_maybe_set_readonly(log_accum, adm_access,
1270                                               merge_target, pool));
1271    }
1272
1273  return SVN_NO_ERROR;
1274}
1275
1276
1277
1278svn_error_t *
1279svn_wc_merge3(enum svn_wc_merge_outcome_t *merge_outcome,
1280              const char *left,
1281              const char *right,
1282              const char *merge_target,
1283              svn_wc_adm_access_t *adm_access,
1284              const char *left_label,
1285              const char *right_label,
1286              const char *target_label,
1287              svn_boolean_t dry_run,
1288              const char *diff3_cmd,
1289              const apr_array_header_t *merge_options,
1290              const apr_array_header_t *prop_diff,
1291              svn_wc_conflict_resolver_func_t conflict_func,
1292              void *conflict_baton,
1293              apr_pool_t *pool)
1294{
1295  svn_stringbuf_t *log_accum = svn_stringbuf_create("", pool);
1296
1297  /* ### TODO: Pass version info here. */
1298  SVN_ERR(svn_wc__merge_internal(&log_accum, merge_outcome,
1299                                 left, NULL,
1300                                 right, NULL,
1301                                 merge_target,
1302                                 NULL,
1303                                 adm_access,
1304                                 left_label, right_label, target_label,
1305                                 dry_run,
1306                                 diff3_cmd,
1307                                 merge_options,
1308                                 prop_diff,
1309                                 conflict_func, conflict_baton,
1310                                 pool));
1311
1312  /* Write our accumulation of log entries into a log file */
1313  SVN_ERR(svn_wc__write_log(adm_access, 0, log_accum, pool));
1314
1315  return svn_wc__run_log(adm_access, NULL, pool);
1316}
1317
1318
1319svn_error_t *
1320svn_wc_merge2(enum svn_wc_merge_outcome_t *merge_outcome,
1321              const char *left,
1322              const char *right,
1323              const char *merge_target,
1324              svn_wc_adm_access_t *adm_access,
1325              const char *left_label,
1326              const char *right_label,
1327              const char *target_label,
1328              svn_boolean_t dry_run,
1329              const char *diff3_cmd,
1330              const apr_array_header_t *merge_options,
1331              apr_pool_t *pool)
1332{
1333  return svn_wc_merge3(merge_outcome,
1334                       left, right, merge_target, adm_access,
1335                       left_label, right_label, target_label,
1336                       dry_run, diff3_cmd, merge_options, NULL,
1337                       NULL, NULL, pool);
1338}
1339
1340svn_error_t *
1341svn_wc_merge(const char *left,
1342             const char *right,
1343             const char *merge_target,
1344             svn_wc_adm_access_t *adm_access,
1345             const char *left_label,
1346             const char *right_label,
1347             const char *target_label,
1348             svn_boolean_t dry_run,
1349             enum svn_wc_merge_outcome_t *merge_outcome,
1350             const char *diff3_cmd,
1351             apr_pool_t *pool)
1352{
1353  return svn_wc_merge3(merge_outcome,
1354                       left, right, merge_target, adm_access,
1355                       left_label, right_label, target_label,
1356                       dry_run, diff3_cmd, NULL, NULL, NULL,
1357                       NULL, pool);
1358}
1359
1360
1361
1362/* Constructor for the result-structure returned by conflict callbacks. */
1363svn_wc_conflict_result_t *
1364svn_wc_create_conflict_result(svn_wc_conflict_choice_t choice,
1365                              const char *merged_file,
1366                              apr_pool_t *pool)
1367{
1368  svn_wc_conflict_result_t *result = apr_pcalloc(pool, sizeof(*result));
1369  result->choice = choice;
1370  result->merged_file = merged_file;
1371  result->save_merged = FALSE;
1372
1373  /* If we add more fields to svn_wc_conflict_result_t, add them here. */
1374
1375  return result;
1376}
Note: See TracBrowser for help on using the repository browser.