diff options
-rw-r--r-- | builtin-merge-recursive.c | 23 | ||||
-rw-r--r-- | builtin-merge.c | 40 | ||||
-rwxr-xr-x | contrib/examples/git-merge.sh | 3 | ||||
-rw-r--r-- | git-compat-util.h | 1 | ||||
-rw-r--r-- | git.c | 2 | ||||
-rw-r--r-- | ll-merge.c | 25 | ||||
-rw-r--r-- | ll-merge.h | 2 | ||||
-rw-r--r-- | merge-recursive.c | 21 | ||||
-rw-r--r-- | merge-recursive.h | 6 | ||||
-rw-r--r-- | strbuf.c | 9 | ||||
-rwxr-xr-x | t/t6037-merge-ours-theirs.sh | 56 |
11 files changed, 162 insertions, 26 deletions
diff --git a/builtin-merge-recursive.c b/builtin-merge-recursive.c index 710674c6b2..1a160d8127 100644 --- a/builtin-merge-recursive.c +++ b/builtin-merge-recursive.c @@ -26,18 +26,29 @@ int cmd_merge_recursive(int argc, const char **argv, const char *prefix) 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 (!suffixcmp(argv[0], "-subtree")) + o.recursive_variant = MERGE_RECURSIVE_SUBTREE; } 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.recursive_variant = MERGE_RECURSIVE_SUBTREE; + 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)) diff --git a/builtin-merge.c b/builtin-merge.c index 855cf6545e..42f19e1fd3 100644 --- a/builtin-merge.c +++ b/builtin-merge.c @@ -50,6 +50,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; @@ -146,6 +148,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) { @@ -172,6 +185,8 @@ static struct option builtin_merge_options[] = { "abort if fast-forward is not possible"), 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), @@ -534,7 +549,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; @@ -563,7 +578,18 @@ 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.recursive_variant = MERGE_RECURSIVE_SUBTREE; + + 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.recursive_variant = MERGE_RECURSIVE_SUBTREE; + else + die("Unknown option for merge-recursive: -X%s", xopts[x]); + } o.branch1 = head_arg; o.branch2 = remoteheads->item->util; @@ -581,10 +607,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++] = "--"; @@ -595,6 +627,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/contrib/examples/git-merge.sh b/contrib/examples/git-merge.sh index 500635fe4b..8f617fcb70 100755 --- a/contrib/examples/git-merge.sh +++ b/contrib/examples/git-merge.sh @@ -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 diff --git a/git-compat-util.h b/git-compat-util.h index 5c596875c2..f64cc454e7 100644 --- a/git-compat-util.h +++ b/git-compat-util.h @@ -198,6 +198,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); extern time_t tm_to_time_t(const struct tm *tm); static inline const char *skip_prefix(const char *str, const char *prefix) @@ -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 }, diff --git a/ll-merge.c b/ll-merge.c index 2d6b6d6cb1..45f7e0190a 100644 --- a/ll-merge.c +++ b/ll-merge.c @@ -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); } diff --git a/ll-merge.h b/ll-merge.h index 5388422d09..aaed46dec9 100644 --- a/ll-merge.h +++ b/ll-merge.h @@ -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 diff --git a/merge-recursive.c b/merge-recursive.c index a91208f295..82f24ac16e 100644 --- a/merge-recursive.c +++ b/merge-recursive.c @@ -642,6 +642,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)); @@ -657,7 +674,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); @@ -1196,7 +1213,7 @@ int merge_trees(struct merge_options *o, { int code, clean; - if (o->subtree_merge) { + if (o->recursive_variant == MERGE_RECURSIVE_SUBTREE) { merge = shift_tree_object(head, merge); common = shift_tree_object(head, common); } diff --git a/merge-recursive.h b/merge-recursive.h index fd138ca140..39de100cd5 100644 --- a/merge-recursive.h +++ b/merge-recursive.h @@ -6,7 +6,11 @@ struct merge_options { const char *branch1; const char *branch2; - unsigned subtree_merge : 1; + enum { + MERGE_RECURSIVE_SUBTREE = 1, + MERGE_RECURSIVE_OURS, + MERGE_RECURSIVE_THEIRS, + } recursive_variant; unsigned buffer_output : 1; int verbosity; int diff_rename_limit; @@ -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 diff --git a/t/t6037-merge-ours-theirs.sh b/t/t6037-merge-ours-theirs.sh new file mode 100755 index 0000000000..08c9f7989a --- /dev/null +++ b/t/t6037-merge-ours-theirs.sh @@ -0,0 +1,56 @@ +#!/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_done |