Merge branch 'ap/merge-backend-opts'
authorJunio C Hamano <gitster@pobox.com>
Thu, 21 Jan 2010 04:28:50 +0000 (20:28 -0800)
committerJunio C Hamano <gitster@pobox.com>
Thu, 21 Jan 2010 04:28:50 +0000 (20:28 -0800)
* ap/merge-backend-opts:
Document that merge strategies can now take their own options
Extend merge-subtree tests to test -Xsubtree=dir.
Make "subtree" part more orthogonal to the rest of merge-recursive.
pull: Fix parsing of -X<option>
Teach git-pull to pass -X<option> to git-merge
git merge -X<option>
git-merge-file --ours, --theirs

Conflicts:
git-compat-util.h

21 files changed:
Documentation/git-merge-file.txt
Documentation/merge-options.txt
Documentation/merge-strategies.txt
builtin-merge-file.c
builtin-merge-recursive.c
builtin-merge.c
cache.h
contrib/examples/git-merge.sh
git-compat-util.h
git-pull.sh
git.c
ll-merge.c
ll-merge.h
match-trees.c
merge-recursive.c
merge-recursive.h
strbuf.c
t/t6029-merge-subtree.sh
t/t6037-merge-ours-theirs.sh [new file with mode: 0755]
xdiff/xdiff.h
xdiff/xmerge.c
index fa723d0513c5ca825a53508dc82990506f486638..234269ae59234de67ea2cce42edec83cebc400be 100644 (file)
@@ -10,7 +10,8 @@ SYNOPSIS
 --------
 [verse]
 'git merge-file' [-L <current-name> [-L <base-name> [-L <other-name>]]]
-       [-p|--stdout] [-q|--quiet] <current-file> <base-file> <other-file>
+       [--ours|--theirs] [-p|--stdout] [-q|--quiet]
+       <current-file> <base-file> <other-file>
 
 
 DESCRIPTION
@@ -34,7 +35,9 @@ normally outputs a warning and brackets the conflict with lines containing
        >>>>>>> B
 
 If there are conflicts, the user should edit the result and delete one of
-the alternatives.
+the alternatives.  When `--ours` or `--theirs` option is in effect, however,
+these conflicts are resolved favouring lines from `<current-file>` or
+lines from `<other-file>` respectively.
 
 The exit value of this program is negative on error, and the number of
 conflicts otherwise. If the merge was clean, the exit value is 0.
@@ -62,6 +65,11 @@ OPTIONS
 -q::
        Quiet; do not warn about conflicts.
 
+--ours::
+--theirs::
+       Instead of leaving conflicts in the file, resolve conflicts
+       favouring our (or their) side of the lines.
+
 
 EXAMPLES
 --------
index 5064bf83fa20c12cc699e5432e6dd6d29eda22de..3b83dba1a0d8ad1436d15d164783f08593f54357 100644 (file)
@@ -74,3 +74,8 @@ option can be used to override --squash.
 -v::
 --verbose::
        Be verbose.
+
+-X <option>::
+--strategy-option=<option>::
+       Pass merge strategy specific option through to the merge
+       strategy.
index 42910a3d5e0229f471c5bceb0c36f184d17ebb08..a5bc1dbb95b466d0c6e37f683c886663908375e7 100644 (file)
@@ -1,6 +1,11 @@
 MERGE STRATEGIES
 ----------------
 
+The merge mechanism ('git-merge' and 'git-pull' commands) allows the
+backend 'merge strategies' to be chosen with `-s` option.  Some strategies
+can also take their own options, which can be passed by giving `-X<option>`
+arguments to 'git-merge' and/or 'git-pull'.
+
 resolve::
        This can only resolve two heads (i.e. the current branch
        and another branch you pulled from) using a 3-way merge
@@ -20,6 +25,27 @@ recursive::
        Additionally this can detect and handle merges involving
        renames.  This is the default merge strategy when
        pulling or merging one branch.
++
+The 'recursive' strategy can take the following options:
+
+ours;;
+       This option forces conflicting hunks to be auto-resolved cleanly by
+       favoring 'our' version.  Changes from the other tree that do not
+       conflict with our side are reflected to the merge result.
++
+This should not be confused with the 'ours' merge strategy, which does not
+even look at what the other tree contains at all.  It discards everything
+the other tree did, declaring 'our' history contains all that happened in it.
+
+theirs;;
+       This is opposite of 'ours'.
+
+subtree[=path];;
+       This option is a more advanced form of 'subtree' strategy, where
+       the strategy makes a guess on how two trees must be shifted to
+       match with each other when merging.  Instead, the specified path
+       is prefixed (or stripped from the beginning) to make the shape of
+       two trees to match.
 
 octopus::
        This resolves cases with more than two heads, but refuses to do
@@ -33,7 +59,8 @@ ours::
        merge is always that of the current branch head, effectively
        ignoring all changes from all other branches.  It is meant to
        be used to supersede old development history of side
-       branches.
+       branches.  Note that this is different from the -Xours option to
+       the 'recursive' merge strategy.
 
 subtree::
        This is a modified recursive strategy. When merging trees A and
index afd2ea7a732460c431328dbd4c0141a49d638244..1efc4e09bc12c73978789502d041fd596397aeb3 100644 (file)
@@ -27,13 +27,18 @@ int cmd_merge_file(int argc, const char **argv, const char *prefix)
        mmbuffer_t result = {NULL, 0};
        xpparam_t xpp = {XDF_NEED_MINIMAL};
        int ret = 0, i = 0, to_stdout = 0;
-       int merge_level = XDL_MERGE_ZEALOUS_ALNUM;
-       int merge_style = 0, quiet = 0;
+       int level = XDL_MERGE_ZEALOUS_ALNUM;
+       int style = 0, quiet = 0;
+       int favor = 0;
        int nongit;
 
        struct option options[] = {
                OPT_BOOLEAN('p', "stdout", &to_stdout, "send results to standard output"),
-               OPT_SET_INT(0, "diff3", &merge_style, "use a diff3 based merge", XDL_MERGE_DIFF3),
+               OPT_SET_INT(0, "diff3", &style, "use a diff3 based merge", XDL_MERGE_DIFF3),
+               OPT_SET_INT(0, "ours", &favor, "for conflicts, use our version",
+                           XDL_MERGE_FAVOR_OURS),
+               OPT_SET_INT(0, "theirs", &favor, "for conflicts, use their version",
+                           XDL_MERGE_FAVOR_THEIRS),
                OPT__QUIET(&quiet),
                OPT_CALLBACK('L', NULL, names, "name",
                             "set labels for file1/orig_file/file2", &label_cb),
@@ -45,7 +50,7 @@ int cmd_merge_file(int argc, const char **argv, const char *prefix)
                /* Read the configuration file */
                git_config(git_xmerge_config, NULL);
                if (0 <= git_xmerge_style)
-                       merge_style = git_xmerge_style;
+                       style = git_xmerge_style;
        }
 
        argc = parse_options(argc, argv, prefix, options, merge_file_usage, 0);
@@ -68,7 +73,7 @@ int cmd_merge_file(int argc, const char **argv, const char *prefix)
        }
 
        ret = xdl_merge(mmfs + 1, mmfs + 0, names[0], mmfs + 2, names[2],
-                       &xpp, merge_level | merge_style, &result);
+                       &xpp, XDL_MERGE_FLAGS(level, style, favor), &result);
 
        for (i = 0; i < 3; i++)
                free(mmfs[i].ptr);
index 710674c6b2cccec4f1e0baa477a11d78cda72833..d8875d589240e0c78a9e241a7d2bdde1d10ab800 100644 (file)
@@ -25,19 +25,30 @@ int cmd_merge_recursive(int argc, const char **argv, const char *prefix)
        struct commit *result;
 
        init_merge_options(&o);
-       if (argv[0]) {
-               int namelen = strlen(argv[0]);
-               if (8 < namelen &&
-                   !strcmp(argv[0] + namelen - 8, "-subtree"))
-                       o.subtree_merge = 1;
-       }
+       if (argv[0] && !suffixcmp(argv[0], "-subtree"))
+               o.subtree_shift = "";
 
        if (argc < 4)
                usagef("%s <base>... -- <head> <remote> ...", argv[0]);
 
        for (i = 1; i < argc; ++i) {
-               if (!strcmp(argv[i], "--"))
-                       break;
+               const char *arg = argv[i];
+
+               if (!prefixcmp(arg, "--")) {
+                       if (!arg[2])
+                               break;
+                       if (!strcmp(arg+2, "ours"))
+                               o.recursive_variant = MERGE_RECURSIVE_OURS;
+                       else if (!strcmp(arg+2, "theirs"))
+                               o.recursive_variant = MERGE_RECURSIVE_THEIRS;
+                       else if (!strcmp(arg+2, "subtree"))
+                               o.subtree_shift = "";
+                       else if (!prefixcmp(arg+2, "subtree="))
+                               o.subtree_shift = arg + 10;
+                       else
+                               die("Unknown option %s", arg);
+                       continue;
+               }
                if (bases_count < ARRAY_SIZE(bases)-1) {
                        unsigned char *sha = xmalloc(20);
                        if (get_sha1(argv[i], sha))
index 9f60ffa2cd32fd78c04d81135cee42b584627e7c..3aaec7bed76af9efdfe5647be0da64373db2011e 100644 (file)
@@ -51,6 +51,8 @@ static struct commit_list *remoteheads;
 static unsigned char head[20], stash[20];
 static struct strategy **use_strategies;
 static size_t use_strategies_nr, use_strategies_alloc;
+static const char **xopts;
+static size_t xopts_nr, xopts_alloc;
 static const char *branch;
 static int verbosity;
 static int allow_rerere_auto;
@@ -148,6 +150,17 @@ static int option_parse_strategy(const struct option *opt,
        return 0;
 }
 
+static int option_parse_x(const struct option *opt,
+                         const char *arg, int unset)
+{
+       if (unset)
+               return 0;
+
+       ALLOC_GROW(xopts, xopts_nr + 1, xopts_alloc);
+       xopts[xopts_nr++] = xstrdup(arg);
+       return 0;
+}
+
 static int option_parse_n(const struct option *opt,
                          const char *arg, int unset)
 {
@@ -175,6 +188,8 @@ static struct option builtin_merge_options[] = {
        OPT_RERERE_AUTOUPDATE(&allow_rerere_auto),
        OPT_CALLBACK('s', "strategy", &use_strategies, "strategy",
                "merge strategy to use", option_parse_strategy),
+       OPT_CALLBACK('X', "strategy-option", &xopts, "option=value",
+               "option for selected merge strategy", option_parse_x),
        OPT_CALLBACK('m', "message", &merge_msg, "message",
                "message to be used for the merge commit (if any)",
                option_parse_message),
@@ -537,7 +552,7 @@ static int try_merge_strategy(const char *strategy, struct commit_list *common,
                              const char *head_arg)
 {
        const char **args;
-       int i = 0, ret;
+       int i = 0, x = 0, ret;
        struct commit_list *j;
        struct strbuf buf = STRBUF_INIT;
        int index_fd;
@@ -566,7 +581,20 @@ static int try_merge_strategy(const char *strategy, struct commit_list *common,
 
                init_merge_options(&o);
                if (!strcmp(strategy, "subtree"))
-                       o.subtree_merge = 1;
+                       o.subtree_shift = "";
+
+               for (x = 0; x < xopts_nr; x++) {
+                       if (!strcmp(xopts[x], "ours"))
+                               o.recursive_variant = MERGE_RECURSIVE_OURS;
+                       else if (!strcmp(xopts[x], "theirs"))
+                               o.recursive_variant = MERGE_RECURSIVE_THEIRS;
+                       else if (!strcmp(xopts[x], "subtree"))
+                               o.subtree_shift = "";
+                       else if (!prefixcmp(xopts[x], "subtree="))
+                               o.subtree_shift = xopts[x]+8;
+                       else
+                               die("Unknown option for merge-recursive: -X%s", xopts[x]);
+               }
 
                o.branch1 = head_arg;
                o.branch2 = remoteheads->item->util;
@@ -584,10 +612,16 @@ static int try_merge_strategy(const char *strategy, struct commit_list *common,
                rollback_lock_file(lock);
                return clean ? 0 : 1;
        } else {
-               args = xmalloc((4 + commit_list_count(common) +
+               args = xmalloc((4 + xopts_nr + commit_list_count(common) +
                                        commit_list_count(remoteheads)) * sizeof(char *));
                strbuf_addf(&buf, "merge-%s", strategy);
                args[i++] = buf.buf;
+               for (x = 0; x < xopts_nr; x++) {
+                       char *s = xmalloc(strlen(xopts[x])+2+1);
+                       strcpy(s, "--");
+                       strcpy(s+2, xopts[x]);
+                       args[i++] = s;
+               }
                for (j = common; j; j = j->next)
                        args[i++] = xstrdup(sha1_to_hex(j->item->object.sha1));
                args[i++] = "--";
@@ -598,6 +632,8 @@ static int try_merge_strategy(const char *strategy, struct commit_list *common,
                ret = run_command_v_opt(args, RUN_GIT_CMD);
                strbuf_release(&buf);
                i = 1;
+               for (x = 0; x < xopts_nr; x++)
+                       free((void *)args[i++]);
                for (j = common; j; j = j->next)
                        free((void *)args[i++]);
                i += 2;
diff --git a/cache.h b/cache.h
index 5abcb09fcfe8d30f87e90f390b923fc07ec8e6e3..4b15042c08afb2ba34ca6d08ebc5ca9b196ab7c8 100644 (file)
--- a/cache.h
+++ b/cache.h
@@ -1007,6 +1007,7 @@ extern int diff_auto_refresh_index;
 
 /* match-trees.c */
 void shift_tree(const unsigned char *, const unsigned char *, unsigned char *, int);
+void shift_tree_by(const unsigned char *, const unsigned char *, unsigned char *, const char *);
 
 /*
  * whitespace rules.
index 500635fe4bb5a8f60bc76502ef15cd97bd273f74..8f617fcb7089caedce163871c3eb3a97eadb204b 100755 (executable)
@@ -31,10 +31,11 @@ LF='
 '
 
 all_strategies='recur recursive octopus resolve stupid ours subtree'
+all_strategies="$all_strategies recursive-ours recursive-theirs"
 default_twohead_strategies='recursive'
 default_octopus_strategies='octopus'
 no_fast_forward_strategies='subtree ours'
-no_trivial_strategies='recursive recur subtree ours'
+no_trivial_strategies='recursive recur subtree ours recursive-ours recursive-theirs'
 use_strategies=
 
 allow_fast_forward=t
index 60c8432f854f345b77b469d64dd1429cee74d30d..aff627a85a812bb782e68cd27d06eb42d7dac537 100644 (file)
@@ -199,6 +199,7 @@ extern void warning(const char *err, ...) __attribute__((format (printf, 1, 2)))
 extern void set_die_routine(NORETURN_PTR void (*routine)(const char *err, va_list params));
 
 extern int prefixcmp(const char *str, const char *prefix);
+extern int suffixcmp(const char *str, const char *suffix);
 
 static inline const char *skip_prefix(const char *str, const char *prefix)
 {
index 54ce0af2d4c718b2a5d7367219e6572917ce567f..2de4c3aa70c10203aebe4e117b30422e39f41e02 100755 (executable)
@@ -39,6 +39,7 @@ test -f "$GIT_DIR/MERGE_HEAD" && die_merge
 
 strategy_args= diffstat= no_commit= squash= no_ff= ff_only=
 log_arg= verbosity=
+merge_args=
 curr_branch=$(git symbolic-ref -q HEAD)
 curr_branch_short=$(echo "$curr_branch" | sed "s|refs/heads/||")
 rebase=$(git config --bool branch.$curr_branch_short.rebase)
@@ -83,6 +84,18 @@ do
                esac
                strategy_args="${strategy_args}-s $strategy "
                ;;
+       -X*)
+               case "$#,$1" in
+               1,-X)
+                       usage ;;
+               *,-X)
+                       xx="-X $(git rev-parse --sq-quote "$2")"
+                       shift ;;
+               *,*)
+                       xx=$(git rev-parse --sq-quote "$1") ;;
+               esac
+               merge_args="$merge_args$xx "
+               ;;
        -r|--r|--re|--reb|--reba|--rebas|--rebase)
                rebase=true
                ;;
@@ -254,8 +267,15 @@ then
 fi
 
 merge_name=$(git fmt-merge-msg $log_arg <"$GIT_DIR/FETCH_HEAD") || exit
-test true = "$rebase" &&
-       exec git-rebase $diffstat $strategy_args --onto $merge_head \
-       ${oldremoteref:-$merge_head}
-exec git-merge $diffstat $no_commit $squash $no_ff $ff_only $log_arg $strategy_args \
-       "$merge_name" HEAD $merge_head $verbosity
+case "$rebase" in
+true)
+       eval="git-rebase $diffstat $strategy_args $merge_args"
+       eval="$eval --onto $merge_head ${oldremoteref:-$merge_head}"
+       ;;
+*)
+       eval="git-merge $diffstat $no_commit $squash $no_ff $ff_only"
+       eval="$eval  $log_arg $strategy_args $merge_args"
+       eval="$eval \"$merge_name\" HEAD $merge_head $verbosity"
+       ;;
+esac
+eval "exec $eval"
diff --git a/git.c b/git.c
index ad074735fcf4d37041da65653967a482b6070bfe..194471f5b1d96658a9745538e533018f00a4f989 100644 (file)
--- a/git.c
+++ b/git.c
@@ -332,6 +332,8 @@ static void handle_internal_command(int argc, const char **argv)
                { "merge-file", cmd_merge_file },
                { "merge-ours", cmd_merge_ours, RUN_SETUP },
                { "merge-recursive", cmd_merge_recursive, RUN_SETUP | NEED_WORK_TREE },
+               { "merge-recursive-ours", cmd_merge_recursive, RUN_SETUP | NEED_WORK_TREE },
+               { "merge-recursive-theirs", cmd_merge_recursive, RUN_SETUP | NEED_WORK_TREE },
                { "merge-subtree", cmd_merge_recursive, RUN_SETUP | NEED_WORK_TREE },
                { "mktree", cmd_mktree, RUN_SETUP },
                { "mv", cmd_mv, RUN_SETUP | NEED_WORK_TREE },
index 18511e281fe7435c2ae93ba3faf502cc606e62b9..070d66dd402bcb7ba51d69974c21a255aa93558b 100644 (file)
@@ -18,7 +18,7 @@ typedef int (*ll_merge_fn)(const struct ll_merge_driver *,
                           mmfile_t *orig,
                           mmfile_t *src1, const char *name1,
                           mmfile_t *src2, const char *name2,
-                          int virtual_ancestor);
+                          int flag);
 
 struct ll_merge_driver {
        const char *name;
@@ -38,14 +38,14 @@ static int ll_binary_merge(const struct ll_merge_driver *drv_unused,
                           mmfile_t *orig,
                           mmfile_t *src1, const char *name1,
                           mmfile_t *src2, const char *name2,
-                          int virtual_ancestor)
+                          int flag)
 {
        /*
         * The tentative merge result is "ours" for the final round,
         * or common ancestor for an internal merge.  Still return
         * "conflicted merge" status.
         */
-       mmfile_t *stolen = virtual_ancestor ? orig : src1;
+       mmfile_t *stolen = (flag & 01) ? orig : src1;
 
        result->ptr = stolen->ptr;
        result->size = stolen->size;
@@ -59,10 +59,11 @@ static int ll_xdl_merge(const struct ll_merge_driver *drv_unused,
                        mmfile_t *orig,
                        mmfile_t *src1, const char *name1,
                        mmfile_t *src2, const char *name2,
-                       int virtual_ancestor)
+                       int flag)
 {
        xpparam_t xpp;
        int style = 0;
+       int favor = (flag >> 1) & 03;
 
        if (buffer_is_binary(orig->ptr, orig->size) ||
            buffer_is_binary(src1->ptr, src1->size) ||
@@ -72,8 +73,7 @@ static int ll_xdl_merge(const struct ll_merge_driver *drv_unused,
                return ll_binary_merge(drv_unused, result,
                                       path,
                                       orig, src1, name1,
-                                      src2, name2,
-                                      virtual_ancestor);
+                                      src2, name2, flag);
        }
 
        memset(&xpp, 0, sizeof(xpp));
@@ -82,7 +82,7 @@ static int ll_xdl_merge(const struct ll_merge_driver *drv_unused,
        return xdl_merge(orig,
                         src1, name1,
                         src2, name2,
-                        &xpp, XDL_MERGE_ZEALOUS | style,
+                        &xpp, XDL_MERGE_FLAGS(XDL_MERGE_ZEALOUS, style, favor),
                         result);
 }
 
@@ -92,7 +92,7 @@ static int ll_union_merge(const struct ll_merge_driver *drv_unused,
                          mmfile_t *orig,
                          mmfile_t *src1, const char *name1,
                          mmfile_t *src2, const char *name2,
-                         int virtual_ancestor)
+                         int flag)
 {
        char *src, *dst;
        long size;
@@ -104,7 +104,7 @@ static int ll_union_merge(const struct ll_merge_driver *drv_unused,
        git_xmerge_style = 0;
        status = ll_xdl_merge(drv_unused, result, path_unused,
                              orig, src1, NULL, src2, NULL,
-                             virtual_ancestor);
+                             flag);
        git_xmerge_style = saved_style;
        if (status <= 0)
                return status;
@@ -165,7 +165,7 @@ static int ll_ext_merge(const struct ll_merge_driver *fn,
                        mmfile_t *orig,
                        mmfile_t *src1, const char *name1,
                        mmfile_t *src2, const char *name2,
-                       int virtual_ancestor)
+                       int flag)
 {
        char temp[3][50];
        struct strbuf cmd = STRBUF_INIT;
@@ -356,10 +356,11 @@ int ll_merge(mmbuffer_t *result_buf,
             mmfile_t *ancestor,
             mmfile_t *ours, const char *our_label,
             mmfile_t *theirs, const char *their_label,
-            int virtual_ancestor)
+            int flag)
 {
        const char *ll_driver_name;
        const struct ll_merge_driver *driver;
+       int virtual_ancestor = flag & 01;
 
        ll_driver_name = git_path_check_merge(path);
        driver = find_ll_merge_driver(ll_driver_name);
@@ -369,5 +370,5 @@ int ll_merge(mmbuffer_t *result_buf,
        return driver->fn(driver, result_buf, path,
                          ancestor,
                          ours, our_label,
-                         theirs, their_label, virtual_ancestor);
+                         theirs, their_label, flag);
 }
index 5388422d091ede134d42406291989c49553f7428..aaed46dec9dabcd7adc614ded30086f61cdf7b48 100644 (file)
@@ -10,6 +10,6 @@ int ll_merge(mmbuffer_t *result_buf,
             mmfile_t *ancestor,
             mmfile_t *ours, const char *our_label,
             mmfile_t *theirs, const char *their_label,
-            int virtual_ancestor);
+            int flag);
 
 #endif
index 0fd6df7d6ed839eaed536bc332312c2688a6bbad..26f7ed143e0649b6377ea427b5c57ca7ddb663d3 100644 (file)
@@ -185,7 +185,7 @@ static void match_trees(const unsigned char *hash1,
  * tree object by replacing it with another tree "hash2".
  */
 static int splice_tree(const unsigned char *hash1,
-                      char *prefix,
+                      const char *prefix,
                       const unsigned char *hash2,
                       unsigned char *result)
 {
@@ -264,6 +264,13 @@ void shift_tree(const unsigned char *hash1,
        char *del_prefix;
        int add_score, del_score;
 
+       /*
+        * NEEDSWORK: this limits the recursion depth to hardcoded
+        * value '2' to avoid excessive overhead.
+        */
+       if (!depth_limit)
+               depth_limit = 2;
+
        add_score = del_score = score_trees(hash1, hash2);
        add_prefix = xcalloc(1, 1);
        del_prefix = xcalloc(1, 1);
@@ -301,3 +308,63 @@ void shift_tree(const unsigned char *hash1,
 
        splice_tree(hash1, add_prefix, hash2, shifted);
 }
+
+/*
+ * The user says the trees will be shifted by this much.
+ * Unfortunately we cannot fundamentally tell which one to
+ * be prefixed, as recursive merge can work in either direction.
+ */
+void shift_tree_by(const unsigned char *hash1,
+                  const unsigned char *hash2,
+                  unsigned char *shifted,
+                  const char *shift_prefix)
+{
+       unsigned char sub1[20], sub2[20];
+       unsigned mode1, mode2;
+       unsigned candidate = 0;
+
+       /* Can hash2 be a tree at shift_prefix in tree hash1? */
+       if (!get_tree_entry(hash1, shift_prefix, sub1, &mode1) &&
+           S_ISDIR(mode1))
+               candidate |= 1;
+
+       /* Can hash1 be a tree at shift_prefix in tree hash2? */
+       if (!get_tree_entry(hash2, shift_prefix, sub2, &mode2) &&
+           S_ISDIR(mode2))
+               candidate |= 2;
+
+       if (candidate == 3) {
+               /* Both are plausible -- we need to evaluate the score */
+               int best_score = score_trees(hash1, hash2);
+               int score;
+
+               candidate = 0;
+               score = score_trees(sub1, hash2);
+               if (score > best_score) {
+                       candidate = 1;
+                       best_score = score;
+               }
+               score = score_trees(sub2, hash1);
+               if (score > best_score)
+                       candidate = 2;
+       }
+
+       if (!candidate) {
+               /* Neither is plausible -- do not shift */
+               hashcpy(shifted, hash2);
+               return;
+       }
+
+       if (candidate == 1)
+               /*
+                * shift tree2 down by adding shift_prefix above it
+                * to match tree1.
+                */
+               splice_tree(hash1, shift_prefix, hash2, shifted);
+       else
+               /*
+                * shift tree2 up by removing shift_prefix from it
+                * to match tree1.
+                */
+               hashcpy(shifted, sub2);
+}
index dd4fbd0e6bc22f2e5f5667205b47165f8aebbbd0..1239647fc0bcda68e744f5f3040c99314c8b8714 100644 (file)
 #include "merge-recursive.h"
 #include "dir.h"
 
-static struct tree *shift_tree_object(struct tree *one, struct tree *two)
+static struct tree *shift_tree_object(struct tree *one, struct tree *two,
+                                     const char *subtree_shift)
 {
        unsigned char shifted[20];
 
-       /*
-        * NEEDSWORK: this limits the recursion depth to hardcoded
-        * value '2' to avoid excessive overhead.
-        */
-       shift_tree(one->object.sha1, two->object.sha1, shifted, 2);
+       if (!*subtree_shift) {
+               shift_tree(one->object.sha1, two->object.sha1, shifted, 0);
+       } else {
+               shift_tree_by(one->object.sha1, two->object.sha1, shifted,
+                             subtree_shift);
+       }
        if (!hashcmp(two->object.sha1, shifted))
                return two;
        return lookup_tree(shifted);
@@ -625,6 +627,23 @@ static int merge_3way(struct merge_options *o,
        mmfile_t orig, src1, src2;
        char *name1, *name2;
        int merge_status;
+       int favor;
+
+       if (o->call_depth)
+               favor = 0;
+       else {
+               switch (o->recursive_variant) {
+               case MERGE_RECURSIVE_OURS:
+                       favor = XDL_MERGE_FAVOR_OURS;
+                       break;
+               case MERGE_RECURSIVE_THEIRS:
+                       favor = XDL_MERGE_FAVOR_THEIRS;
+                       break;
+               default:
+                       favor = 0;
+                       break;
+               }
+       }
 
        if (strcmp(a->path, b->path)) {
                name1 = xstrdup(mkpath("%s:%s", branch1, a->path));
@@ -640,7 +659,7 @@ static int merge_3way(struct merge_options *o,
 
        merge_status = ll_merge(result_buf, a->path, &orig,
                                &src1, name1, &src2, name2,
-                               o->call_depth);
+                               (!!o->call_depth) | (favor << 1));
 
        free(name1);
        free(name2);
@@ -1201,9 +1220,9 @@ int merge_trees(struct merge_options *o,
 {
        int code, clean;
 
-       if (o->subtree_merge) {
-               merge = shift_tree_object(head, merge);
-               common = shift_tree_object(head, common);
+       if (o->subtree_shift) {
+               merge = shift_tree_object(head, merge, o->subtree_shift);
+               common = shift_tree_object(head, common, o->subtree_shift);
        }
 
        if (sha_eq(common->object.sha1, merge->object.sha1)) {
index d8bc7299ee3a70484b2436afd3799fe6f7608422..be8410ad1803bc10e5dbf74f39eecdfed53469b1 100644 (file)
@@ -6,7 +6,12 @@
 struct merge_options {
        const char *branch1;
        const char *branch2;
-       unsigned subtree_merge : 1;
+       enum {
+               MERGE_RECURSIVE_NORMAL = 0,
+               MERGE_RECURSIVE_OURS,
+               MERGE_RECURSIVE_THEIRS,
+       } recursive_variant;
+       const char *subtree_shift;
        unsigned buffer_output : 1;
        int verbosity;
        int diff_rename_limit;
index 67448b7b07aa9d07b0c8cc250e3e0fad64c8fb5e..bc3a0802ea7e7b1743602972de182391b4bf0b3f 100644 (file)
--- a/strbuf.c
+++ b/strbuf.c
@@ -10,6 +10,15 @@ int prefixcmp(const char *str, const char *prefix)
                        return (unsigned char)*prefix - (unsigned char)*str;
 }
 
+int suffixcmp(const char *str, const char *suffix)
+{
+       int len = strlen(str), suflen = strlen(suffix);
+       if (len < suflen)
+               return -1;
+       else
+               return strcmp(str + len - suflen, suffix);
+}
+
 /*
  * Used as the default ->buf value, so that people can always assume
  * buf is non NULL and ->buf is NUL terminated even for a freshly
index 5bbfa44e8d9ee3eebb18eb07e93380c802741a18..3900d9f61f84a9eab907e87fca7bb9429ea36f04 100755 (executable)
@@ -52,6 +52,7 @@ test_expect_success 'initial merge' '
        git merge -s ours --no-commit gui/master &&
        git read-tree --prefix=git-gui/ -u gui/master &&
        git commit -m "Merge git-gui as our subdirectory" &&
+       git checkout -b work &&
        git ls-files -s >actual &&
        (
                echo "100644 $o1 0      git-gui/git-gui.sh"
@@ -65,9 +66,10 @@ test_expect_success 'merge update' '
        echo git-gui2 > git-gui.sh &&
        o3=$(git hash-object git-gui.sh) &&
        git add git-gui.sh &&
+       git checkout -b master2 &&
        git commit -m "update git-gui" &&
        cd ../git &&
-       git pull -s subtree gui master &&
+       git pull -s subtree gui master2 &&
        git ls-files -s >actual &&
        (
                echo "100644 $o3 0      git-gui/git-gui.sh"
@@ -76,4 +78,47 @@ test_expect_success 'merge update' '
        test_cmp expected actual
 '
 
+test_expect_success 'initial ambiguous subtree' '
+       cd ../git &&
+       git reset --hard master &&
+       git checkout -b master2 &&
+       git merge -s ours --no-commit gui/master &&
+       git read-tree --prefix=git-gui2/ -u gui/master &&
+       git commit -m "Merge git-gui2 as our subdirectory" &&
+       git checkout -b work2 &&
+       git ls-files -s >actual &&
+       (
+               echo "100644 $o1 0      git-gui/git-gui.sh"
+               echo "100644 $o1 0      git-gui2/git-gui.sh"
+               echo "100644 $o2 0      git.c"
+       ) >expected &&
+       test_cmp expected actual
+'
+
+test_expect_success 'merge using explicit' '
+       cd ../git &&
+       git reset --hard master2 &&
+       git pull -Xsubtree=git-gui gui master2 &&
+       git ls-files -s >actual &&
+       (
+               echo "100644 $o3 0      git-gui/git-gui.sh"
+               echo "100644 $o1 0      git-gui2/git-gui.sh"
+               echo "100644 $o2 0      git.c"
+       ) >expected &&
+       test_cmp expected actual
+'
+
+test_expect_success 'merge2 using explicit' '
+       cd ../git &&
+       git reset --hard master2 &&
+       git pull -Xsubtree=git-gui2 gui master2 &&
+       git ls-files -s >actual &&
+       (
+               echo "100644 $o1 0      git-gui/git-gui.sh"
+               echo "100644 $o3 0      git-gui2/git-gui.sh"
+               echo "100644 $o2 0      git.c"
+       ) >expected &&
+       test_cmp expected actual
+'
+
 test_done
diff --git a/t/t6037-merge-ours-theirs.sh b/t/t6037-merge-ours-theirs.sh
new file mode 100755 (executable)
index 0000000..8ab3d61
--- /dev/null
@@ -0,0 +1,64 @@
+#!/bin/sh
+
+test_description='Merge-recursive ours and theirs variants'
+. ./test-lib.sh
+
+test_expect_success setup '
+       for i in 1 2 3 4 5 6 7 8 9
+       do
+               echo "$i"
+       done >file &&
+       git add file &&
+       cp file elif &&
+       git commit -m initial &&
+
+       sed -e "s/1/one/" -e "s/9/nine/" >file <elif &&
+       git commit -a -m ours &&
+
+       git checkout -b side HEAD^ &&
+
+       sed -e "s/9/nueve/" >file <elif &&
+       git commit -a -m theirs &&
+
+       git checkout master^0
+'
+
+test_expect_success 'plain recursive - should conflict' '
+       git reset --hard master &&
+       test_must_fail git merge -s recursive side &&
+       grep nine file &&
+       grep nueve file &&
+       ! grep 9 file &&
+       grep one file &&
+       ! grep 1 file
+'
+
+test_expect_success 'recursive favouring theirs' '
+       git reset --hard master &&
+       git merge -s recursive -Xtheirs side &&
+       ! grep nine file &&
+       grep nueve file &&
+       ! grep 9 file &&
+       grep one file &&
+       ! grep 1 file
+'
+
+test_expect_success 'recursive favouring ours' '
+       git reset --hard master &&
+       git merge -s recursive -X ours side &&
+       grep nine file &&
+       ! grep nueve file &&
+       ! grep 9 file &&
+       grep one file &&
+       ! grep 1 file
+'
+
+test_expect_success 'pull with -X' '
+       git reset --hard master && git pull -s recursive -Xours . side &&
+       git reset --hard master && git pull -s recursive -X ours . side &&
+       git reset --hard master && git pull -s recursive -Xtheirs . side &&
+       git reset --hard master && git pull -s recursive -X theirs . side &&
+       git reset --hard master && ! git pull -s recursive -X bork . side
+'
+
+test_done
index 4da052a3fff6768fca05b2f3399c5d87ec1daa2e..8a0efed3139fdf2f9dccbc0163e2943d66460a4d 100644 (file)
@@ -58,6 +58,12 @@ extern "C" {
 #define XDL_MERGE_ZEALOUS_ALNUM 3
 #define XDL_MERGE_LEVEL_MASK 0x0f
 
+/* merge favor modes */
+#define XDL_MERGE_FAVOR_OURS 1
+#define XDL_MERGE_FAVOR_THEIRS 2
+#define XDL_MERGE_FAVOR(flags) (((flags)>>4) & 3)
+#define XDL_MERGE_FLAGS(level, style, favor) ((level)|(style)|((favor)<<4))
+
 /* merge output styles */
 #define XDL_MERGE_DIFF3 0x8000
 #define XDL_MERGE_STYLE_MASK 0x8000
@@ -110,7 +116,7 @@ int xdl_diff(mmfile_t *mf1, mmfile_t *mf2, xpparam_t const *xpp,
 
 int xdl_merge(mmfile_t *orig, mmfile_t *mf1, const char *name1,
                mmfile_t *mf2, const char *name2,
-               xpparam_t const *xpp, int level, mmbuffer_t *result);
+               xpparam_t const *xpp, int flags, mmbuffer_t *result);
 
 #ifdef __cplusplus
 }
index 1cb65a95166a8cb60af590118f59d78aa4d24b74..b2ddc75376f530a07cb951bc8314c4a635517af5 100644 (file)
@@ -214,11 +214,15 @@ static int fill_conflict_hunk(xdfenv_t *xe1, const char *name1,
 
 static int xdl_fill_merge_buffer(xdfenv_t *xe1, const char *name1,
                                 xdfenv_t *xe2, const char *name2,
+                                int favor,
                                 xdmerge_t *m, char *dest, int style)
 {
        int size, i;
 
        for (size = i = 0; m; m = m->next) {
+               if (favor && !m->mode)
+                       m->mode = favor;
+
                if (m->mode == 0)
                        size = fill_conflict_hunk(xe1, name1, xe2, name2,
                                                  size, i, style, m, dest);
@@ -391,6 +395,7 @@ static int xdl_do_merge(xdfenv_t *xe1, xdchange_t *xscr1, const char *name1,
        int i0, i1, i2, chg0, chg1, chg2;
        int level = flags & XDL_MERGE_LEVEL_MASK;
        int style = flags & XDL_MERGE_STYLE_MASK;
+       int favor = XDL_MERGE_FAVOR(flags);
 
        if (style == XDL_MERGE_DIFF3) {
                /*
@@ -523,14 +528,14 @@ static int xdl_do_merge(xdfenv_t *xe1, xdchange_t *xscr1, const char *name1,
        /* output */
        if (result) {
                int size = xdl_fill_merge_buffer(xe1, name1, xe2, name2,
-                       changes, NULL, style);
+                       favor, changes, NULL, style);
                result->ptr = xdl_malloc(size);
                if (!result->ptr) {
                        xdl_cleanup_merge(changes);
                        return -1;
                }
                result->size = size;
-               xdl_fill_merge_buffer(xe1, name1, xe2, name2, changes,
+               xdl_fill_merge_buffer(xe1, name1, xe2, name2, favor, changes,
                                      result->ptr, style);
        }
        return xdl_cleanup_merge(changes);