diff options
author | peter <peter@FreeBSD.org> | 2013-06-18 02:07:41 +0000 |
---|---|---|
committer | peter <peter@FreeBSD.org> | 2013-06-18 02:07:41 +0000 |
commit | d25dac7fcc6acc838b71bbda8916fd9665c709ab (patch) | |
tree | 135691142dc0e75a5e5d97b5074d03436435b8e0 /subversion/libsvn_wc/merge.c | |
download | FreeBSD-src-d25dac7fcc6acc838b71bbda8916fd9665c709ab.zip FreeBSD-src-d25dac7fcc6acc838b71bbda8916fd9665c709ab.tar.gz |
Import trimmed svn-1.8.0-rc3
Diffstat (limited to 'subversion/libsvn_wc/merge.c')
-rw-r--r-- | subversion/libsvn_wc/merge.c | 1424 |
1 files changed, 1424 insertions, 0 deletions
diff --git a/subversion/libsvn_wc/merge.c b/subversion/libsvn_wc/merge.c new file mode 100644 index 0000000..7cff3e4 --- /dev/null +++ b/subversion/libsvn_wc/merge.c @@ -0,0 +1,1424 @@ +/* + * merge.c: merging changes into a working file + * + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + */ + +#include "svn_wc.h" +#include "svn_diff.h" +#include "svn_dirent_uri.h" +#include "svn_path.h" +#include "svn_pools.h" + +#include "wc.h" +#include "adm_files.h" +#include "conflicts.h" +#include "translate.h" +#include "workqueue.h" + +#include "private/svn_skel.h" + +#include "svn_private_config.h" + +/* Contains some information on the merge target before merge, and some + information needed for the diff processing. */ +typedef struct merge_target_t +{ + svn_wc__db_t *db; /* The DB used to access target */ + const char *local_abspath; /* The absolute path to target */ + const char *wri_abspath; /* The working copy of target */ + + apr_hash_t *old_actual_props; /* The set of actual properties + before merging */ + const apr_array_header_t *prop_diff; /* The property changes */ + + const char *diff3_cmd; /* The diff3 command and options */ + const apr_array_header_t *merge_options; + +} merge_target_t; + + +/* Return a pointer to the svn_prop_t structure from PROP_DIFF + belonging to PROP_NAME, if any. NULL otherwise.*/ +static const svn_prop_t * +get_prop(const apr_array_header_t *prop_diff, + const char *prop_name) +{ + if (prop_diff) + { + int i; + for (i = 0; i < prop_diff->nelts; i++) + { + const svn_prop_t *elt = &APR_ARRAY_IDX(prop_diff, i, + svn_prop_t); + + if (strcmp(elt->name, prop_name) == 0) + return elt; + } + } + + return NULL; +} + + +/* Detranslate a working copy file MERGE_TARGET to achieve the effect of: + + 1. Detranslate + 2. Install new props + 3. Retranslate + 4. Detranslate + + in one pass, to get a file which can be compared with the left and right + files which are in repository normal form. + + Property changes make this a little complex though. Changes in + + - svn:mime-type + - svn:eol-style + - svn:keywords + - svn:special + + may change the way a file is translated. + + Effect for svn:mime-type: + + If svn:mime-type is considered 'binary', we ignore svn:eol-style (but + still translate keywords). + + I) both old and new mime-types are texty + -> just do the translation dance (as lined out below) + ### actually we do a shortcut with just one translation: + detranslate with the old keywords and ... eol-style + (the new re+detranslation is a no-op w.r.t. keywords [1]) + + II) the old one is texty, the new one is binary + -> detranslate with the old eol-style and keywords + (the new re+detranslation is a no-op [1]) + + III) the old one is binary, the new one texty + -> detranslate with the old keywords and new eol-style + (the old detranslation is a no-op w.r.t. eol, and + the new re+detranslation is a no-op w.r.t. keywords [1]) + + IV) the old and new ones are binary + -> detranslate with the old keywords + (the new re+detranslation is a no-op [1]) + + Effect for svn:eol-style + + I) On add or change of svn:eol-style, use the new value + + II) otherwise: use the old value (absent means 'no translation') + + Effect for svn:keywords + + Always use the old settings (re+detranslation are no-op [1]). + + [1] Translation of keywords from repository normal form to WC form and + back is normally a no-op, but is not a no-op if text contains a kw + that is only enabled by the new props and is present in non- + contracted form (such as "$Rev: 1234 $"). If we want to catch this + case we should detranslate with both the old & the new keywords + together. + + Effect for svn:special + + Always use the old settings (re+detranslation are no-op). + + Sets *DETRANSLATED_ABSPATH to the path to the detranslated file, + this may be the same as SOURCE_ABSPATH if FORCE_COPY is FALSE and no + translation is required. + + If FORCE_COPY is FALSE and *DETRANSLATED_ABSPATH is a file distinct + from SOURCE_ABSPATH then the file will be deleted on RESULT_POOL + cleanup. + + If FORCE_COPY is TRUE then *DETRANSLATED_ABSPATH will always be a + new file distinct from SOURCE_ABSPATH and it will be the callers + responsibility to delete the file. + +*/ +static svn_error_t * +detranslate_wc_file(const char **detranslated_abspath, + const merge_target_t *mt, + svn_boolean_t force_copy, + const char *source_abspath, + svn_cancel_func_t cancel_func, + void *cancel_baton, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_boolean_t old_is_binary, new_is_binary; + svn_subst_eol_style_t style; + const char *eol; + apr_hash_t *keywords; + svn_boolean_t special; + + { + const char *old_mime_value + = svn_prop_get_value(mt->old_actual_props, SVN_PROP_MIME_TYPE); + const svn_prop_t *prop = get_prop(mt->prop_diff, SVN_PROP_MIME_TYPE); + const char *new_mime_value + = prop ? (prop->value ? prop->value->data : NULL) : old_mime_value; + + old_is_binary = old_mime_value && svn_mime_type_is_binary(old_mime_value); + new_is_binary = new_mime_value && svn_mime_type_is_binary(new_mime_value);; + } + + /* See what translations we want to do */ + if (old_is_binary && new_is_binary) + { + /* Case IV. Old and new props 'binary': detranslate keywords only */ + SVN_ERR(svn_wc__get_translate_info(NULL, NULL, &keywords, NULL, + mt->db, mt->local_abspath, + mt->old_actual_props, TRUE, + scratch_pool, scratch_pool)); + /* ### Why override 'special'? Elsewhere it has precedence. */ + special = FALSE; + eol = NULL; + style = svn_subst_eol_style_none; + } + else if (!old_is_binary && new_is_binary) + { + /* Case II. Old props indicate texty, new props indicate binary: + detranslate keywords and old eol-style */ + SVN_ERR(svn_wc__get_translate_info(&style, &eol, + &keywords, + &special, + mt->db, mt->local_abspath, + mt->old_actual_props, TRUE, + scratch_pool, scratch_pool)); + } + else + { + /* Case I & III. New props indicate texty, regardless of old props */ + + /* In case the file used to be special, detranslate specially */ + SVN_ERR(svn_wc__get_translate_info(&style, &eol, + &keywords, + &special, + mt->db, mt->local_abspath, + mt->old_actual_props, TRUE, + scratch_pool, scratch_pool)); + + if (special) + { + keywords = NULL; + eol = NULL; + style = svn_subst_eol_style_none; + } + else + { + const svn_prop_t *prop; + + /* In case a new eol style was set, use that for detranslation */ + if ((prop = get_prop(mt->prop_diff, SVN_PROP_EOL_STYLE)) && prop->value) + { + /* Value added or changed */ + svn_subst_eol_style_from_value(&style, &eol, prop->value->data); + } + else if (!old_is_binary) + { + /* Already fetched */ + } + else + { + eol = NULL; + style = svn_subst_eol_style_none; + } + } + } + + /* Now, detranslate with the settings we created above */ + + if (force_copy || keywords || eol || special) + { + const char *temp_dir_abspath; + const char *detranslated; + + /* Force a copy into the temporary wc area to avoid having + temporary files created below to appear in the actual wc. */ + SVN_ERR(svn_wc__db_temp_wcroot_tempdir(&temp_dir_abspath, mt->db, + mt->wri_abspath, + scratch_pool, scratch_pool)); + + /* ### svn_subst_copy_and_translate4() also creates a tempfile + ### internally. Anyway to piggyback on that? */ + SVN_ERR(svn_io_open_unique_file3(NULL, &detranslated, temp_dir_abspath, + (force_copy + ? svn_io_file_del_none + : svn_io_file_del_on_pool_cleanup), + result_pool, scratch_pool)); + + /* Always 'repair' EOLs here, so that we can apply a diff that + changes from inconsistent newlines and no 'svn:eol-style' to + consistent newlines and 'svn:eol-style' set. */ + + if (style == svn_subst_eol_style_native) + eol = SVN_SUBST_NATIVE_EOL_STR; + else if (style != svn_subst_eol_style_fixed + && style != svn_subst_eol_style_none) + return svn_error_create(SVN_ERR_IO_UNKNOWN_EOL, NULL, NULL); + + SVN_ERR(svn_subst_copy_and_translate4(source_abspath, + detranslated, + eol, + TRUE /* repair */, + keywords, + FALSE /* contract keywords */, + special, + cancel_func, cancel_baton, + scratch_pool)); + + SVN_ERR(svn_dirent_get_absolute(detranslated_abspath, detranslated, + result_pool)); + } + else + *detranslated_abspath = apr_pstrdup(result_pool, source_abspath); + + return SVN_NO_ERROR; +} + +/* Updates (by copying and translating) the eol style in + OLD_TARGET_ABSPATH returning the filename containing the + correct eol style in NEW_TARGET_ABSPATH, if an eol style + change is contained in PROP_DIFF. */ +static svn_error_t * +maybe_update_target_eols(const char **new_target_abspath, + const apr_array_header_t *prop_diff, + const char *old_target_abspath, + svn_cancel_func_t cancel_func, + void *cancel_baton, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + const svn_prop_t *prop = get_prop(prop_diff, SVN_PROP_EOL_STYLE); + + if (prop && prop->value) + { + const char *eol; + const char *tmp_new; + + svn_subst_eol_style_from_value(NULL, &eol, prop->value->data); + SVN_ERR(svn_io_open_unique_file3(NULL, &tmp_new, NULL, + svn_io_file_del_on_pool_cleanup, + result_pool, scratch_pool)); + + /* Always 'repair' EOLs here, so that we can apply a diff that + changes from inconsistent newlines and no 'svn:eol-style' to + consistent newlines and 'svn:eol-style' set. */ + SVN_ERR(svn_subst_copy_and_translate4(old_target_abspath, + tmp_new, + eol, + TRUE /* repair */, + NULL /* keywords */, + FALSE /* expand */, + FALSE /* special */, + cancel_func, cancel_baton, + scratch_pool)); + *new_target_abspath = apr_pstrdup(result_pool, tmp_new); + } + else + *new_target_abspath = apr_pstrdup(result_pool, old_target_abspath); + + return SVN_NO_ERROR; +} + + +/* Set *TARGET_MARKER, *LEFT_MARKER and *RIGHT_MARKER to strings suitable + for delimiting the alternative texts in a text conflict. Include in each + marker a string that may be given by TARGET_LABEL, LEFT_LABEL and + RIGHT_LABEL respectively or a default value where any of those are NULL. + + Allocate the results in POOL or statically. */ +static void +init_conflict_markers(const char **target_marker, + const char **left_marker, + const char **right_marker, + const char *target_label, + const char *left_label, + const char *right_label, + apr_pool_t *pool) +{ + /* Labels fall back to sensible defaults if not specified. */ + if (target_label) + *target_marker = apr_psprintf(pool, "<<<<<<< %s", target_label); + else + *target_marker = "<<<<<<< .working"; + + if (left_label) + *left_marker = apr_psprintf(pool, "||||||| %s", left_label); + else + *left_marker = "||||||| .old"; + + if (right_label) + *right_marker = apr_psprintf(pool, ">>>>>>> %s", right_label); + else + *right_marker = ">>>>>>> .new"; +} + +/* Do a 3-way merge of the files at paths LEFT, DETRANSLATED_TARGET, + * and RIGHT, using diff options provided in MERGE_OPTIONS. Store the merge + * result in the file RESULT_F. + * If there are conflicts, set *CONTAINS_CONFLICTS to true, and use + * TARGET_LABEL, LEFT_LABEL, and RIGHT_LABEL as labels for conflict + * markers. Else, set *CONTAINS_CONFLICTS to false. + * Do all allocations in POOL. */ +static svn_error_t * +do_text_merge(svn_boolean_t *contains_conflicts, + apr_file_t *result_f, + const apr_array_header_t *merge_options, + const char *detranslated_target, + const char *left, + const char *right, + const char *target_label, + const char *left_label, + const char *right_label, + apr_pool_t *pool) +{ + svn_diff_t *diff; + svn_stream_t *ostream; + const char *target_marker; + const char *left_marker; + const char *right_marker; + svn_diff_file_options_t *diff3_options; + + diff3_options = svn_diff_file_options_create(pool); + + if (merge_options) + SVN_ERR(svn_diff_file_options_parse(diff3_options, + merge_options, pool)); + + + init_conflict_markers(&target_marker, &left_marker, &right_marker, + target_label, left_label, right_label, pool); + + SVN_ERR(svn_diff_file_diff3_2(&diff, left, detranslated_target, right, + diff3_options, pool)); + + ostream = svn_stream_from_aprfile2(result_f, TRUE, pool); + + SVN_ERR(svn_diff_file_output_merge2(ostream, diff, + left, detranslated_target, right, + left_marker, + target_marker, + right_marker, + "=======", /* separator */ + svn_diff_conflict_display_modified_latest, + pool)); + SVN_ERR(svn_stream_close(ostream)); + + *contains_conflicts = svn_diff_contains_conflicts(diff); + + return SVN_NO_ERROR; +} + +/* Same as do_text_merge() above, but use the external diff3 + * command DIFF3_CMD to perform the merge. Pass MERGE_OPTIONS + * to the diff3 command. Do all allocations in POOL. */ +static svn_error_t * +do_text_merge_external(svn_boolean_t *contains_conflicts, + apr_file_t *result_f, + const char *diff3_cmd, + const apr_array_header_t *merge_options, + const char *detranslated_target, + const char *left_abspath, + const char *right_abspath, + const char *target_label, + const char *left_label, + const char *right_label, + apr_pool_t *scratch_pool) +{ + int exit_code; + + SVN_ERR(svn_io_run_diff3_3(&exit_code, ".", + detranslated_target, left_abspath, right_abspath, + target_label, left_label, right_label, + result_f, diff3_cmd, + merge_options, scratch_pool)); + + *contains_conflicts = exit_code == 1; + + return SVN_NO_ERROR; +} + +/* Preserve the three pre-merge files. + + Create three empty files, with unique names that each include the + basename of TARGET_ABSPATH and one of LEFT_LABEL, RIGHT_LABEL and + TARGET_LABEL, in the directory that contains TARGET_ABSPATH. Typical + names are "foo.c.r37" or "foo.c.2.mine". Set *LEFT_COPY, *RIGHT_COPY and + *TARGET_COPY to their absolute paths. + + Set *WORK_ITEMS to a list of new work items that will write copies of + LEFT_ABSPATH, RIGHT_ABSPATH and TARGET_ABSPATH into the three files, + translated to working-copy form. + + The translation to working-copy form will be done according to the + versioned properties of TARGET_ABSPATH that are current when the work + queue items are executed. + + If target_abspath is not versioned use detranslated_target_abspath + as the target file. + ### NOT IMPLEMENTED -- 'detranslated_target_abspath' is not used. +*/ +static svn_error_t * +preserve_pre_merge_files(svn_skel_t **work_items, + const char **left_copy, + const char **right_copy, + const char **target_copy, + const merge_target_t *mt, + const char *left_abspath, + const char *right_abspath, + const char *left_label, + const char *right_label, + const char *target_label, + const char *detranslated_target_abspath, + svn_cancel_func_t cancel_func, + void *cancel_baton, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + const char *tmp_left, *tmp_right, *detranslated_target_copy; + const char *dir_abspath, *target_name; + const char *wcroot_abspath, *temp_dir_abspath; + svn_skel_t *work_item, *last_items = NULL; + + *work_items = NULL; + + svn_dirent_split(&dir_abspath, &target_name, mt->local_abspath, + scratch_pool); + + SVN_ERR(svn_wc__db_get_wcroot(&wcroot_abspath, mt->db, mt->wri_abspath, + scratch_pool, scratch_pool)); + SVN_ERR(svn_wc__db_temp_wcroot_tempdir(&temp_dir_abspath, mt->db, + mt->wri_abspath, + scratch_pool, scratch_pool)); + + /* Create three empty files in DIR_ABSPATH, naming them with unique names + that each include TARGET_NAME and one of {LEFT,RIGHT,TARGET}_LABEL, + and set *{LEFT,RIGHT,TARGET}_COPY to those names. */ + SVN_ERR(svn_io_open_uniquely_named( + NULL, left_copy, dir_abspath, target_name, left_label, + svn_io_file_del_none, result_pool, scratch_pool)); + SVN_ERR(svn_io_open_uniquely_named( + NULL, right_copy, dir_abspath, target_name, right_label, + svn_io_file_del_none, result_pool, scratch_pool)); + SVN_ERR(svn_io_open_uniquely_named( + NULL, target_copy, dir_abspath, target_name, target_label, + svn_io_file_del_none, result_pool, scratch_pool)); + + /* We preserve all the files with keywords expanded and line + endings in local (working) form. */ + + /* The workingqueue requires its paths to be in the subtree + relative to the wcroot path they are executed in. + + Make our LEFT and RIGHT files 'local' if they aren't... */ + if (! svn_dirent_is_ancestor(wcroot_abspath, left_abspath)) + { + SVN_ERR(svn_io_open_unique_file3(NULL, &tmp_left, temp_dir_abspath, + svn_io_file_del_none, + scratch_pool, scratch_pool)); + SVN_ERR(svn_io_copy_file(left_abspath, tmp_left, TRUE, scratch_pool)); + + /* And create a wq item to remove the file later */ + SVN_ERR(svn_wc__wq_build_file_remove(&work_item, mt->db, wcroot_abspath, + tmp_left, + result_pool, scratch_pool)); + + last_items = svn_wc__wq_merge(last_items, work_item, result_pool); + } + else + tmp_left = left_abspath; + + if (! svn_dirent_is_ancestor(wcroot_abspath, right_abspath)) + { + SVN_ERR(svn_io_open_unique_file3(NULL, &tmp_right, temp_dir_abspath, + svn_io_file_del_none, + scratch_pool, scratch_pool)); + SVN_ERR(svn_io_copy_file(right_abspath, tmp_right, TRUE, scratch_pool)); + + /* And create a wq item to remove the file later */ + SVN_ERR(svn_wc__wq_build_file_remove(&work_item, mt->db, wcroot_abspath, + tmp_right, + result_pool, scratch_pool)); + + last_items = svn_wc__wq_merge(last_items, work_item, result_pool); + } + else + tmp_right = right_abspath; + + /* NOTE: Callers must ensure that the svn:eol-style and + svn:keywords property values are correct in the currently + installed props. With 'svn merge', it's no big deal. But + when 'svn up' calls this routine, it needs to make sure that + this routine is using the newest property values that may + have been received *during* the update. Since this routine + will be run from within a log-command, merge_file() + needs to make sure that a previous log-command to 'install + latest props' has already executed first. Ben and I just + checked, and that is indeed the order in which the log items + are written, so everything should be fine. Really. */ + + /* Create LEFT and RIGHT backup files, in expanded form. + We use TARGET_ABSPATH's current properties to do the translation. */ + /* Derive the basenames of the 3 backup files. */ + SVN_ERR(svn_wc__wq_build_file_copy_translated(&work_item, + mt->db, mt->local_abspath, + tmp_left, *left_copy, + result_pool, scratch_pool)); + *work_items = svn_wc__wq_merge(*work_items, work_item, result_pool); + + SVN_ERR(svn_wc__wq_build_file_copy_translated(&work_item, + mt->db, mt->local_abspath, + tmp_right, *right_copy, + result_pool, scratch_pool)); + *work_items = svn_wc__wq_merge(*work_items, work_item, result_pool); + + /* Back up TARGET_ABSPATH through detranslation/retranslation: + the new translation properties may not match the current ones */ + SVN_ERR(detranslate_wc_file(&detranslated_target_copy, mt, TRUE, + mt->local_abspath, + cancel_func, cancel_baton, + scratch_pool, scratch_pool)); + + SVN_ERR(svn_wc__wq_build_file_copy_translated(&work_item, + mt->db, mt->local_abspath, + detranslated_target_copy, + *target_copy, + result_pool, scratch_pool)); + *work_items = svn_wc__wq_merge(*work_items, work_item, result_pool); + + /* And maybe delete some tempfiles */ + SVN_ERR(svn_wc__wq_build_file_remove(&work_item, mt->db, wcroot_abspath, + detranslated_target_copy, + result_pool, scratch_pool)); + *work_items = svn_wc__wq_merge(*work_items, work_item, result_pool); + + *work_items = svn_wc__wq_merge(*work_items, last_items, result_pool); + + return SVN_NO_ERROR; +} + +/* Attempt a trivial merge of LEFT_ABSPATH and RIGHT_ABSPATH to + * the target file at TARGET_ABSPATH. + * + * These are the inherently trivial cases: + * + * left == right == target => no-op + * left != right, left == target => target := right + * + * This case is also treated as trivial: + * + * left != right, right == target => no-op + * + * ### Strictly, this case is a conflict, and the no-op outcome is only + * one of the possible resolutions. + * + * TODO: Raise a conflict at this level and implement the 'no-op' + * resolution of that conflict at a higher level, in preparation for + * being able to support stricter conflict detection. + * + * This case is inherently trivial but not currently handled here: + * + * left == right != target => no-op + * + * The files at LEFT_ABSPATH and RIGHT_ABSPATH are in repository normal + * form. The file at DETRANSLATED_TARGET_ABSPATH is a copy of the target, + * 'detranslated' to repository normal form, or may be the target file + * itself if no translation is necessary. + * + * When this function updates the target file, it translates to working copy + * form. + * + * On success, set *MERGE_OUTCOME to SVN_WC_MERGE_MERGED in case the + * target was changed, or to SVN_WC_MERGE_UNCHANGED if the target was not + * changed. Install work queue items allocated in RESULT_POOL in *WORK_ITEMS. + * On failure, set *MERGE_OUTCOME to SVN_WC_MERGE_NO_MERGE. + */ +static svn_error_t * +merge_file_trivial(svn_skel_t **work_items, + enum svn_wc_merge_outcome_t *merge_outcome, + const char *left_abspath, + const char *right_abspath, + const char *target_abspath, + const char *detranslated_target_abspath, + svn_boolean_t dry_run, + svn_wc__db_t *db, + svn_cancel_func_t cancel_func, + void *cancel_baton, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_skel_t *work_item; + svn_boolean_t same_left_right; + svn_boolean_t same_right_target; + svn_boolean_t same_left_target; + svn_node_kind_t kind; + svn_boolean_t is_special; + + /* If the target is not a normal file, do not attempt a trivial merge. */ + SVN_ERR(svn_io_check_special_path(target_abspath, &kind, &is_special, + scratch_pool)); + if (kind != svn_node_file || is_special) + { + *merge_outcome = svn_wc_merge_no_merge; + return SVN_NO_ERROR; + } + + /* Check the files */ + SVN_ERR(svn_io_files_contents_three_same_p(&same_left_right, + &same_right_target, + &same_left_target, + left_abspath, + right_abspath, + detranslated_target_abspath, + scratch_pool)); + + /* If the LEFT side of the merge is equal to WORKING, then we can + * copy RIGHT directly. */ + if (same_left_target) + { + /* If the left side equals the right side, there is no change to merge + * so we leave the target unchanged. */ + if (same_left_right) + { + *merge_outcome = svn_wc_merge_unchanged; + } + else + { + *merge_outcome = svn_wc_merge_merged; + if (!dry_run) + { + const char *wcroot_abspath; + svn_boolean_t delete_src = FALSE; + + /* The right_abspath might be outside our working copy. In that + case we should copy the file to a safe location before + installing to avoid breaking the workqueue. + + This matches the behavior in preserve_pre_merge_files */ + + SVN_ERR(svn_wc__db_get_wcroot(&wcroot_abspath, + db, target_abspath, + scratch_pool, scratch_pool)); + + if (!svn_dirent_is_child(wcroot_abspath, right_abspath, NULL)) + { + svn_stream_t *tmp_src; + svn_stream_t *tmp_dst; + + SVN_ERR(svn_stream_open_readonly(&tmp_src, right_abspath, + scratch_pool, + scratch_pool)); + + SVN_ERR(svn_wc__open_writable_base(&tmp_dst, &right_abspath, + NULL, NULL, + db, target_abspath, + scratch_pool, + scratch_pool)); + + SVN_ERR(svn_stream_copy3(tmp_src, tmp_dst, + cancel_func, cancel_baton, + scratch_pool)); + + delete_src = TRUE; + } + + SVN_ERR(svn_wc__wq_build_file_install( + &work_item, db, target_abspath, right_abspath, + FALSE /* use_commit_times */, + FALSE /* record_fileinfo */, + result_pool, scratch_pool)); + *work_items = svn_wc__wq_merge(*work_items, work_item, + result_pool); + + if (delete_src) + { + SVN_ERR(svn_wc__wq_build_file_remove( + &work_item, db, wcroot_abspath, + right_abspath, + result_pool, scratch_pool)); + *work_items = svn_wc__wq_merge(*work_items, work_item, + result_pool); + } + } + } + + return SVN_NO_ERROR; + } + else + { + /* If the locally existing, changed file equals the incoming 'right' + * file, there is no conflict. For binary files, we historically + * conflicted them needlessly, while merge_text_file figured it out + * eventually and returned svn_wc_merge_unchanged for them, which + * is what we do here. */ + if (same_right_target) + { + *merge_outcome = svn_wc_merge_unchanged; + return SVN_NO_ERROR; + } + } + + *merge_outcome = svn_wc_merge_no_merge; + return SVN_NO_ERROR; +} + + +/* Handle a non-trivial merge of 'text' files. (Assume that a trivial + * merge was not possible.) + * + * Set *WORK_ITEMS, *CONFLICT_SKEL and *MERGE_OUTCOME according to the + * result -- to install the merged file, or to indicate a conflict. + * + * On successful merge, leave the result in a temporary file and set + * *WORK_ITEMS to hold work items that will translate and install that + * file into its proper form and place (unless DRY_RUN) and delete the + * temporary file (in any case). Set *MERGE_OUTCOME to 'merged' or + * 'unchanged'. + * + * If a conflict occurs, set *MERGE_OUTCOME to 'conflicted', and (unless + * DRY_RUN) set *WORK_ITEMS and *CONFLICT_SKEL to record the conflict + * and copies of the pre-merge files. See preserve_pre_merge_files() + * for details. + * + * On entry, all of the output pointers must be non-null and *CONFLICT_SKEL + * must either point to an existing conflict skel or be NULL. + */ +static svn_error_t* +merge_text_file(svn_skel_t **work_items, + svn_skel_t **conflict_skel, + enum svn_wc_merge_outcome_t *merge_outcome, + const merge_target_t *mt, + const char *left_abspath, + const char *right_abspath, + const char *left_label, + const char *right_label, + const char *target_label, + svn_boolean_t dry_run, + const char *detranslated_target_abspath, + svn_cancel_func_t cancel_func, + void *cancel_baton, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + apr_pool_t *pool = scratch_pool; /* ### temporary rename */ + svn_boolean_t contains_conflicts; + apr_file_t *result_f; + const char *result_target; + const char *base_name; + const char *temp_dir; + svn_skel_t *work_item; + + *work_items = NULL; + + base_name = svn_dirent_basename(mt->local_abspath, scratch_pool); + + /* Open a second temporary file for writing; this is where diff3 + will write the merged results. We want to use a tempfile + with a name that reflects the original, in case this + ultimately winds up in a conflict resolution editor. */ + SVN_ERR(svn_wc__db_temp_wcroot_tempdir(&temp_dir, mt->db, mt->wri_abspath, + pool, pool)); + SVN_ERR(svn_io_open_uniquely_named(&result_f, &result_target, + temp_dir, base_name, ".tmp", + svn_io_file_del_none, pool, pool)); + + /* Run the external or internal merge, as requested. */ + if (mt->diff3_cmd) + SVN_ERR(do_text_merge_external(&contains_conflicts, + result_f, + mt->diff3_cmd, + mt->merge_options, + detranslated_target_abspath, + left_abspath, + right_abspath, + target_label, + left_label, + right_label, + pool)); + else /* Use internal merge. */ + SVN_ERR(do_text_merge(&contains_conflicts, + result_f, + mt->merge_options, + detranslated_target_abspath, + left_abspath, + right_abspath, + target_label, + left_label, + right_label, + pool)); + + SVN_ERR(svn_io_file_close(result_f, pool)); + + /* Determine the MERGE_OUTCOME, and record any conflict. */ + if (contains_conflicts && ! dry_run) + { + *merge_outcome = svn_wc_merge_conflict; + if (*merge_outcome == svn_wc_merge_conflict) + { + const char *left_copy, *right_copy, *target_copy; + + /* Preserve the three conflict files */ + SVN_ERR(preserve_pre_merge_files( + &work_item, + &left_copy, &right_copy, &target_copy, + mt, left_abspath, right_abspath, + left_label, right_label, target_label, + detranslated_target_abspath, + cancel_func, cancel_baton, + result_pool, scratch_pool)); + *work_items = svn_wc__wq_merge(*work_items, work_item, result_pool); + + /* Track the conflict marker files in the metadata. */ + + if (!*conflict_skel) + *conflict_skel = svn_wc__conflict_skel_create(result_pool); + + SVN_ERR(svn_wc__conflict_skel_add_text_conflict(*conflict_skel, + mt->db, mt->local_abspath, + target_copy, + left_copy, + right_copy, + result_pool, + scratch_pool)); + } + + if (*merge_outcome == svn_wc_merge_merged) + goto done; + } + else if (contains_conflicts && dry_run) + *merge_outcome = svn_wc_merge_conflict; + else + { + svn_boolean_t same, special; + + /* If 'special', then use the detranslated form of the + target file. This is so we don't try to follow symlinks, + but the same treatment is probably also appropriate for + whatever special file types we may invent in the future. */ + SVN_ERR(svn_wc__get_translate_info(NULL, NULL, NULL, + &special, mt->db, mt->local_abspath, + mt->old_actual_props, TRUE, + pool, pool)); + SVN_ERR(svn_io_files_contents_same_p(&same, result_target, + (special ? + detranslated_target_abspath : + mt->local_abspath), + pool)); + + *merge_outcome = same ? svn_wc_merge_unchanged : svn_wc_merge_merged; + } + + if (*merge_outcome != svn_wc_merge_unchanged && ! dry_run) + { + /* replace TARGET_ABSPATH with the new merged file, expanding. */ + SVN_ERR(svn_wc__wq_build_file_install(&work_item, + mt->db, mt->local_abspath, + result_target, + FALSE /* use_commit_times */, + FALSE /* record_fileinfo */, + result_pool, scratch_pool)); + *work_items = svn_wc__wq_merge(*work_items, work_item, result_pool); + } + +done: + /* Remove the tempfile after use */ + SVN_ERR(svn_wc__wq_build_file_remove(&work_item, mt->db, mt->local_abspath, + result_target, + result_pool, scratch_pool)); + + *work_items = svn_wc__wq_merge(*work_items, work_item, result_pool); + + return SVN_NO_ERROR; +} + +/* Handle a non-trivial merge of 'binary' files: don't actually merge, just + * flag a conflict. (Assume that a trivial merge was not possible.) + * + * Copy* the files at LEFT_ABSPATH and RIGHT_ABSPATH into the same directory + * as the target file, giving them unique names that start with the target + * file's name and end with LEFT_LABEL and RIGHT_LABEL respectively. + * If the merge target has been 'detranslated' to repository normal form, + * move the detranslated file similarly to a unique name ending with + * TARGET_LABEL. + * + * ### * Why do we copy the left and right temp files when we could (maybe + * not always?) move them? + * + * On entry, all of the output pointers must be non-null and *CONFLICT_SKEL + * must either point to an existing conflict skel or be NULL. + * + * Set *WORK_ITEMS, *CONFLICT_SKEL and *MERGE_OUTCOME to indicate the + * conflict. + * + * ### Why do we not use preserve_pre_merge_files() in here? The + * behaviour would be slightly different, more consistent: the + * preserved 'left' and 'right' files would be translated to working + * copy form, which may make a difference when a binary file + * contains keyword expansions or when some versions of the file are + * not 'binary' even though we're merging in 'binary files' mode. + */ +static svn_error_t * +merge_binary_file(svn_skel_t **work_items, + svn_skel_t **conflict_skel, + enum svn_wc_merge_outcome_t *merge_outcome, + const merge_target_t *mt, + const char *left_abspath, + const char *right_abspath, + const char *left_label, + const char *right_label, + const char *target_label, + svn_boolean_t dry_run, + const char *detranslated_target_abspath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + apr_pool_t *pool = scratch_pool; /* ### temporary rename */ + /* ### when making the binary-file backups, should we be honoring + keywords and eol stuff? */ + const char *left_copy, *right_copy; + const char *merge_dirpath, *merge_filename; + const char *conflict_wrk; + + *work_items = NULL; + + svn_dirent_split(&merge_dirpath, &merge_filename, mt->local_abspath, pool); + + if (dry_run) + { + *merge_outcome = svn_wc_merge_conflict; + return SVN_NO_ERROR; + } + + /* reserve names for backups of left and right fulltexts */ + SVN_ERR(svn_io_open_uniquely_named(NULL, + &left_copy, + merge_dirpath, + merge_filename, + left_label, + svn_io_file_del_none, + pool, pool)); + + SVN_ERR(svn_io_open_uniquely_named(NULL, + &right_copy, + merge_dirpath, + merge_filename, + right_label, + svn_io_file_del_none, + pool, pool)); + + /* create the backup files */ + SVN_ERR(svn_io_copy_file(left_abspath, left_copy, TRUE, pool)); + SVN_ERR(svn_io_copy_file(right_abspath, right_copy, TRUE, pool)); + + /* Was the merge target detranslated? */ + if (strcmp(mt->local_abspath, detranslated_target_abspath) != 0) + { + /* Create a .mine file too */ + SVN_ERR(svn_io_open_uniquely_named(NULL, + &conflict_wrk, + merge_dirpath, + merge_filename, + target_label, + svn_io_file_del_none, + pool, pool)); + SVN_ERR(svn_wc__wq_build_file_move(work_items, mt->db, + mt->local_abspath, + detranslated_target_abspath, + conflict_wrk, + pool, result_pool)); + } + else + { + conflict_wrk = NULL; + } + + /* Mark target_abspath's entry as "Conflicted", and start tracking + the backup files in the entry as well. */ + if (!*conflict_skel) + *conflict_skel = svn_wc__conflict_skel_create(result_pool); + + SVN_ERR(svn_wc__conflict_skel_add_text_conflict(*conflict_skel, + mt->db, mt->local_abspath, + conflict_wrk, + left_copy, + right_copy, + result_pool, scratch_pool)); + + *merge_outcome = svn_wc_merge_conflict; /* a conflict happened */ + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_wc__internal_merge(svn_skel_t **work_items, + svn_skel_t **conflict_skel, + enum svn_wc_merge_outcome_t *merge_outcome, + svn_wc__db_t *db, + const char *left_abspath, + const char *right_abspath, + const char *target_abspath, + const char *wri_abspath, + const char *left_label, + const char *right_label, + const char *target_label, + apr_hash_t *old_actual_props, + svn_boolean_t dry_run, + const char *diff3_cmd, + const apr_array_header_t *merge_options, + const apr_array_header_t *prop_diff, + svn_cancel_func_t cancel_func, + void *cancel_baton, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + const char *detranslated_target_abspath; + svn_boolean_t is_binary = FALSE; + const svn_prop_t *mimeprop; + svn_skel_t *work_item; + merge_target_t mt; + + SVN_ERR_ASSERT(svn_dirent_is_absolute(left_abspath)); + SVN_ERR_ASSERT(svn_dirent_is_absolute(right_abspath)); + SVN_ERR_ASSERT(svn_dirent_is_absolute(target_abspath)); + + *work_items = NULL; + + /* Fill the merge target baton */ + mt.db = db; + mt.local_abspath = target_abspath; + mt.wri_abspath = wri_abspath; + mt.old_actual_props = old_actual_props; + mt.prop_diff = prop_diff; + mt.diff3_cmd = diff3_cmd; + mt.merge_options = merge_options; + + /* Decide if the merge target is a text or binary file. */ + if ((mimeprop = get_prop(prop_diff, SVN_PROP_MIME_TYPE)) + && mimeprop->value) + is_binary = svn_mime_type_is_binary(mimeprop->value->data); + else + { + const char *value = svn_prop_get_value(mt.old_actual_props, + SVN_PROP_MIME_TYPE); + + is_binary = value && svn_mime_type_is_binary(value); + } + + SVN_ERR(detranslate_wc_file(&detranslated_target_abspath, &mt, + (! is_binary) && diff3_cmd != NULL, + target_abspath, + cancel_func, cancel_baton, + scratch_pool, scratch_pool)); + + /* We cannot depend on the left file to contain the same eols as the + right file. If the merge target has mods, this will mark the entire + file as conflicted, so we need to compensate. */ + SVN_ERR(maybe_update_target_eols(&left_abspath, prop_diff, left_abspath, + cancel_func, cancel_baton, + scratch_pool, scratch_pool)); + + SVN_ERR(merge_file_trivial(work_items, merge_outcome, + left_abspath, right_abspath, + target_abspath, detranslated_target_abspath, + dry_run, db, cancel_func, cancel_baton, + result_pool, scratch_pool)); + if (*merge_outcome == svn_wc_merge_no_merge) + { + /* We have a non-trivial merge. If we classify it as a merge of + * 'binary' files we'll just raise a conflict, otherwise we'll do + * the actual merge of 'text' file contents. */ + if (is_binary) + { + /* Raise a text conflict */ + SVN_ERR(merge_binary_file(work_items, + conflict_skel, + merge_outcome, + &mt, + left_abspath, + right_abspath, + left_label, + right_label, + target_label, + dry_run, + detranslated_target_abspath, + result_pool, scratch_pool)); + } + else + { + SVN_ERR(merge_text_file(work_items, + conflict_skel, + merge_outcome, + &mt, + left_abspath, + right_abspath, + left_label, + right_label, + target_label, + dry_run, + detranslated_target_abspath, + cancel_func, cancel_baton, + result_pool, scratch_pool)); + } + } + + /* Merging is complete. Regardless of text or binariness, we might + need to tweak the executable bit on the new working file, and + possibly make it read-only. */ + if (! dry_run) + { + SVN_ERR(svn_wc__wq_build_sync_file_flags(&work_item, db, + target_abspath, + result_pool, scratch_pool)); + *work_items = svn_wc__wq_merge(*work_items, work_item, result_pool); + } + + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_wc_merge5(enum svn_wc_merge_outcome_t *merge_content_outcome, + enum svn_wc_notify_state_t *merge_props_outcome, + svn_wc_context_t *wc_ctx, + const char *left_abspath, + const char *right_abspath, + const char *target_abspath, + const char *left_label, + const char *right_label, + const char *target_label, + const svn_wc_conflict_version_t *left_version, + const svn_wc_conflict_version_t *right_version, + svn_boolean_t dry_run, + const char *diff3_cmd, + const apr_array_header_t *merge_options, + apr_hash_t *original_props, + const apr_array_header_t *prop_diff, + svn_wc_conflict_resolver_func2_t conflict_func, + void *conflict_baton, + svn_cancel_func_t cancel_func, + void *cancel_baton, + apr_pool_t *scratch_pool) +{ + const char *dir_abspath = svn_dirent_dirname(target_abspath, scratch_pool); + svn_skel_t *work_items; + svn_skel_t *conflict_skel = NULL; + apr_hash_t *pristine_props = NULL; + apr_hash_t *old_actual_props; + apr_hash_t *new_actual_props = NULL; + + SVN_ERR_ASSERT(svn_dirent_is_absolute(left_abspath)); + SVN_ERR_ASSERT(svn_dirent_is_absolute(right_abspath)); + SVN_ERR_ASSERT(svn_dirent_is_absolute(target_abspath)); + + /* Before we do any work, make sure we hold a write lock. */ + if (!dry_run) + SVN_ERR(svn_wc__write_check(wc_ctx->db, dir_abspath, scratch_pool)); + + /* Sanity check: the merge target must be a file under revision control */ + { + svn_wc__db_status_t status; + svn_node_kind_t kind; + svn_boolean_t had_props; + svn_boolean_t props_mod; + svn_boolean_t conflicted; + + SVN_ERR(svn_wc__db_read_info(&status, &kind, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, + &conflicted, NULL, &had_props, &props_mod, + NULL, NULL, NULL, + wc_ctx->db, target_abspath, + scratch_pool, scratch_pool)); + + if (kind != svn_node_file || (status != svn_wc__db_status_normal + && status != svn_wc__db_status_added)) + { + *merge_content_outcome = svn_wc_merge_no_merge; + if (merge_props_outcome) + *merge_props_outcome = svn_wc_notify_state_unchanged; + return SVN_NO_ERROR; + } + + if (conflicted) + { + svn_boolean_t text_conflicted; + svn_boolean_t prop_conflicted; + svn_boolean_t tree_conflicted; + + SVN_ERR(svn_wc__internal_conflicted_p(&text_conflicted, + &prop_conflicted, + &tree_conflicted, + wc_ctx->db, target_abspath, + scratch_pool)); + + /* We can't install two prop conflicts on a single node, so + avoid even checking that we have to merge it */ + if (text_conflicted || prop_conflicted || tree_conflicted) + { + return svn_error_createf( + SVN_ERR_WC_PATH_UNEXPECTED_STATUS, NULL, + _("Can't merge into conflicted node '%s'"), + svn_dirent_local_style(target_abspath, + scratch_pool)); + } + /* else: Conflict was resolved by removing markers */ + } + + if (merge_props_outcome && had_props) + { + SVN_ERR(svn_wc__db_read_pristine_props(&pristine_props, + wc_ctx->db, target_abspath, + scratch_pool, scratch_pool)); + } + else if (merge_props_outcome) + pristine_props = apr_hash_make(scratch_pool); + + if (props_mod) + { + SVN_ERR(svn_wc__db_read_props(&old_actual_props, + wc_ctx->db, target_abspath, + scratch_pool, scratch_pool)); + } + else if (pristine_props) + old_actual_props = pristine_props; + else + old_actual_props = apr_hash_make(scratch_pool); + } + + /* Merge the properties, if requested. We merge the properties first + * because the properties can affect the text (EOL style, keywords). */ + if (merge_props_outcome) + { + int i; + + /* The PROPCHANGES may not have non-"normal" properties in it. If entry + or wc props were allowed, then the following code would install them + into the BASE and/or WORKING properties(!). */ + for (i = prop_diff->nelts; i--; ) + { + const svn_prop_t *change = &APR_ARRAY_IDX(prop_diff, i, svn_prop_t); + + if (!svn_wc_is_normal_prop(change->name)) + return svn_error_createf(SVN_ERR_BAD_PROP_KIND, NULL, + _("The property '%s' may not be merged " + "into '%s'."), + change->name, + svn_dirent_local_style(target_abspath, + scratch_pool)); + } + + SVN_ERR(svn_wc__merge_props(&conflict_skel, + merge_props_outcome, + &new_actual_props, + wc_ctx->db, target_abspath, + original_props, pristine_props, old_actual_props, + prop_diff, + scratch_pool, scratch_pool)); + } + + /* Merge the text. */ + SVN_ERR(svn_wc__internal_merge(&work_items, + &conflict_skel, + merge_content_outcome, + wc_ctx->db, + left_abspath, + right_abspath, + target_abspath, + target_abspath, + left_label, right_label, target_label, + old_actual_props, + dry_run, + diff3_cmd, + merge_options, + prop_diff, + cancel_func, cancel_baton, + scratch_pool, scratch_pool)); + + /* If this isn't a dry run, then update the DB, run the work, and + * call the conflict resolver callback. */ + if (!dry_run) + { + if (conflict_skel) + { + svn_skel_t *work_item; + + SVN_ERR(svn_wc__conflict_skel_set_op_merge(conflict_skel, + left_version, + right_version, + scratch_pool, + scratch_pool)); + + SVN_ERR(svn_wc__conflict_create_markers(&work_item, + wc_ctx->db, target_abspath, + conflict_skel, + scratch_pool, scratch_pool)); + + work_items = svn_wc__wq_merge(work_items, work_item, scratch_pool); + } + + if (new_actual_props) + SVN_ERR(svn_wc__db_op_set_props(wc_ctx->db, target_abspath, + new_actual_props, + svn_wc__has_magic_property(prop_diff), + conflict_skel, work_items, + scratch_pool)); + else if (conflict_skel) + SVN_ERR(svn_wc__db_op_mark_conflict(wc_ctx->db, target_abspath, + conflict_skel, work_items, + scratch_pool)); + else if (work_items) + SVN_ERR(svn_wc__db_wq_add(wc_ctx->db, target_abspath, work_items, + scratch_pool)); + + if (work_items) + SVN_ERR(svn_wc__wq_run(wc_ctx->db, target_abspath, + cancel_func, cancel_baton, + scratch_pool)); + + if (conflict_skel && conflict_func) + { + svn_boolean_t text_conflicted, prop_conflicted; + + SVN_ERR(svn_wc__conflict_invoke_resolver( + wc_ctx->db, target_abspath, + conflict_skel, merge_options, + conflict_func, conflict_baton, + cancel_func, cancel_baton, + scratch_pool)); + + /* Reset *MERGE_CONTENT_OUTCOME etc. if a conflict was resolved. */ + SVN_ERR(svn_wc__internal_conflicted_p( + &text_conflicted, &prop_conflicted, NULL, + wc_ctx->db, target_abspath, scratch_pool)); + if (*merge_props_outcome == svn_wc_notify_state_conflicted + && ! prop_conflicted) + *merge_props_outcome = svn_wc_notify_state_merged; + if (*merge_content_outcome == svn_wc_merge_conflict + && ! text_conflicted) + *merge_content_outcome = svn_wc_merge_merged; + } + } + + return SVN_NO_ERROR; +} |