git merge -X<option>
Teach "-X <option>" command line argument to "git merge" that is passed to strategy implementations. "ours" and "theirs" autoresolution introduced by the previous commit can be asked to the recursive strategy. Signed-off-by: Avery Pennarun <apenwarr@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>
Avery Pennarun committed
Nov 25, 2009 at 21:23 UTC
8cc5b29065e19267cbc08b39c34674b02c2e3d59
11 files changed
+162
-26
builtin-merge-recursive.c
+17
-6
@@ -26,18 +26,29 @@ int cmd_merge_recursive(int argc, const char **argv, const char *prefix)
26
27
init_merge_options(&o);
28
if (argv[0]) {
29
- int namelen = strlen(argv[0]);
30
- if (8 < namelen &&
31
- !strcmp(argv[0] + namelen - 8, "-subtree"))
32
- o.subtree_merge = 1;
29
+ if (!suffixcmp(argv[0], "-subtree"))
30
+ o.recursive_variant = MERGE_RECURSIVE_SUBTREE;
31
}
32
33
if (argc < 4)
34
usagef("%s <base>... -- <head> <remote> ...", argv[0]);
35
36
for (i = 1; i < argc; ++i) {
39
- if (!strcmp(argv[i], "--"))
40
- break;
37
+ const char *arg = argv[i];
38
+
39
+ if (!prefixcmp(arg, "--")) {
40
+ if (!arg[2])
41
+ break;
42
+ if (!strcmp(arg+2, "ours"))
43
+ o.recursive_variant = MERGE_RECURSIVE_OURS;
44
+ else if (!strcmp(arg+2, "theirs"))
45
+ o.recursive_variant = MERGE_RECURSIVE_THEIRS;
46
+ else if (!strcmp(arg+2, "subtree"))
47
+ o.recursive_variant = MERGE_RECURSIVE_SUBTREE;
48
+ else
49
+ die("Unknown option %s", arg);
50
+ continue;
51
+ }
52
if (bases_count < ARRAY_SIZE(bases)-1) {
53
unsigned char *sha = xmalloc(20);
54
if (get_sha1(argv[i], sha))
builtin-merge.c
+37
-3
@@ -50,6 +50,8 @@ static struct commit_list *remoteheads;
50
static unsigned char head[20], stash[20];
51
static struct strategy **use_strategies;
52
static size_t use_strategies_nr, use_strategies_alloc;
53
+static const char **xopts;
54
+static size_t xopts_nr, xopts_alloc;
55
static const char *branch;
56
static int verbosity;
57
@@ -146,6 +148,17 @@ static int option_parse_strategy(const struct option *opt,
148
return 0;
149
}
150
151
+static int option_parse_x(const struct option *opt,
152
+ const char *arg, int unset)
153
+{
154
+ if (unset)
155
+ return 0;
156
+
157
+ ALLOC_GROW(xopts, xopts_nr + 1, xopts_alloc);
158
+ xopts[xopts_nr++] = xstrdup(arg);
159
+ return 0;
160
+}
161
+
162
static int option_parse_n(const struct option *opt,
163
const char *arg, int unset)
164
{
@@ -172,6 +185,8 @@ static struct option builtin_merge_options[] = {
185
"abort if fast-forward is not possible"),
186
OPT_CALLBACK('s', "strategy", &use_strategies, "strategy",
187
"merge strategy to use", option_parse_strategy),
188
+ OPT_CALLBACK('X', "strategy-option", &xopts, "option=value",
189
+ "option for selected merge strategy", option_parse_x),
190
OPT_CALLBACK('m', "message", &merge_msg, "message",
191
"message to be used for the merge commit (if any)",
192
option_parse_message),
@@ -534,7 +549,7 @@ static int try_merge_strategy(const char *strategy, struct commit_list *common,
549
const char *head_arg)
550
{
551
const char **args;
537
- int i = 0, ret;
552
+ int i = 0, x = 0, ret;
553
struct commit_list *j;
554
struct strbuf buf = STRBUF_INIT;
555
int index_fd;
@@ -563,7 +578,18 @@ static int try_merge_strategy(const char *strategy, struct commit_list *common,
578
579
init_merge_options(&o);
580
if (!strcmp(strategy, "subtree"))
566
- o.subtree_merge = 1;
581
+ o.recursive_variant = MERGE_RECURSIVE_SUBTREE;
582
+
583
+ for (x = 0; x < xopts_nr; x++) {
584
+ if (!strcmp(xopts[x], "ours"))
585
+ o.recursive_variant = MERGE_RECURSIVE_OURS;
586
+ else if (!strcmp(xopts[x], "theirs"))
587
+ o.recursive_variant = MERGE_RECURSIVE_THEIRS;
588
+ else if (!strcmp(xopts[x], "subtree"))
589
+ o.recursive_variant = MERGE_RECURSIVE_SUBTREE;
590
+ else
591
+ die("Unknown option for merge-recursive: -X%s", xopts[x]);
592
+ }
593
594
o.branch1 = head_arg;
595
o.branch2 = remoteheads->item->util;
@@ -581,10 +607,16 @@ static int try_merge_strategy(const char *strategy, struct commit_list *common,
607
rollback_lock_file(lock);
608
return clean ? 0 : 1;
609
} else {
584
- args = xmalloc((4 + commit_list_count(common) +
610
+ args = xmalloc((4 + xopts_nr + commit_list_count(common) +
611
commit_list_count(remoteheads)) * sizeof(char *));
612
strbuf_addf(&buf, "merge-%s", strategy);
613
args[i++] = buf.buf;
614
+ for (x = 0; x < xopts_nr; x++) {
615
+ char *s = xmalloc(strlen(xopts[x])+2+1);
616
+ strcpy(s, "--");
617
+ strcpy(s+2, xopts[x]);
618
+ args[i++] = s;
619
+ }
620
for (j = common; j; j = j->next)
621
args[i++] = xstrdup(sha1_to_hex(j->item->object.sha1));
622
args[i++] = "--";
@@ -595,6 +627,8 @@ static int try_merge_strategy(const char *strategy, struct commit_list *common,
627
ret = run_command_v_opt(args, RUN_GIT_CMD);
628
strbuf_release(&buf);
629
i = 1;
630
+ for (x = 0; x < xopts_nr; x++)
631
+ free((void *)args[i++]);
632
for (j = common; j; j = j->next)
633
free((void *)args[i++]);
634
i += 2;
contrib/examples/git-merge.sh
+2
-1
@@ -31,10 +31,11 @@ LF='
31
'
32
33
all_strategies='recur recursive octopus resolve stupid ours subtree'
34
+all_strategies="$all_strategies recursive-ours recursive-theirs"
35
default_twohead_strategies='recursive'
36
default_octopus_strategies='octopus'
37
no_fast_forward_strategies='subtree ours'
37
-no_trivial_strategies='recursive recur subtree ours'
38
+no_trivial_strategies='recursive recur subtree ours recursive-ours recursive-theirs'
39
use_strategies=
40
41
allow_fast_forward=t
git-compat-util.h
+1
@@ -198,6 +198,7 @@ extern void warning(const char *err, ...) __attribute__((format (printf, 1, 2)))
198
extern void set_die_routine(NORETURN_PTR void (*routine)(const char *err, va_list params));
199
200
extern int prefixcmp(const char *str, const char *prefix);
201
+extern int suffixcmp(const char *str, const char *suffix);
202
extern time_t tm_to_time_t(const struct tm *tm);
203
204
static inline const char *skip_prefix(const char *str, const char *prefix)
git.c
+2
@@ -332,6 +332,8 @@ static void handle_internal_command(int argc, const char **argv)
332
{ "merge-file", cmd_merge_file },
333
{ "merge-ours", cmd_merge_ours, RUN_SETUP },
334
{ "merge-recursive", cmd_merge_recursive, RUN_SETUP | NEED_WORK_TREE },
335
+ { "merge-recursive-ours", cmd_merge_recursive, RUN_SETUP | NEED_WORK_TREE },
336
+ { "merge-recursive-theirs", cmd_merge_recursive, RUN_SETUP | NEED_WORK_TREE },
337
{ "merge-subtree", cmd_merge_recursive, RUN_SETUP | NEED_WORK_TREE },
338
{ "mktree", cmd_mktree, RUN_SETUP },
339
{ "mv", cmd_mv, RUN_SETUP | NEED_WORK_TREE },
ll-merge.c
+13
-12
@@ -18,7 +18,7 @@ typedef int (*ll_merge_fn)(const struct ll_merge_driver *,
18
mmfile_t *orig,
19
mmfile_t *src1, const char *name1,
20
mmfile_t *src2, const char *name2,
21
- int virtual_ancestor);
21
+ int flag);
22
23
struct ll_merge_driver {
24
const char *name;
@@ -38,14 +38,14 @@ static int ll_binary_merge(const struct ll_merge_driver *drv_unused,
38
mmfile_t *orig,
39
mmfile_t *src1, const char *name1,
40
mmfile_t *src2, const char *name2,
41
- int virtual_ancestor)
41
+ int flag)
42
{
43
/*
44
* The tentative merge result is "ours" for the final round,
45
* or common ancestor for an internal merge. Still return
46
* "conflicted merge" status.
47
*/
48
- mmfile_t *stolen = virtual_ancestor ? orig : src1;
48
+ mmfile_t *stolen = (flag & 01) ? orig : src1;
49
50
result->ptr = stolen->ptr;
51
result->size = stolen->size;
@@ -59,10 +59,11 @@ static int ll_xdl_merge(const struct ll_merge_driver *drv_unused,
59
mmfile_t *orig,
60
mmfile_t *src1, const char *name1,
61
mmfile_t *src2, const char *name2,
62
- int virtual_ancestor)
62
+ int flag)
63
{
64
xpparam_t xpp;
65
int style = 0;
66
+ int favor = (flag >> 1) & 03;
67
68
if (buffer_is_binary(orig->ptr, orig->size) ||
69
buffer_is_binary(src1->ptr, src1->size) ||
@@ -72,8 +73,7 @@ static int ll_xdl_merge(const struct ll_merge_driver *drv_unused,
73
return ll_binary_merge(drv_unused, result,
74
path,
75
orig, src1, name1,
75
- src2, name2,
76
- virtual_ancestor);
76
+ src2, name2, flag);
77
}
78
79
memset(&xpp, 0, sizeof(xpp));
@@ -82,7 +82,7 @@ static int ll_xdl_merge(const struct ll_merge_driver *drv_unused,
82
return xdl_merge(orig,
83
src1, name1,
84
src2, name2,
85
- &xpp, XDL_MERGE_ZEALOUS | style,
85
+ &xpp, XDL_MERGE_FLAGS(XDL_MERGE_ZEALOUS, style, favor),
86
result);
87
}
88
@@ -92,7 +92,7 @@ static int ll_union_merge(const struct ll_merge_driver *drv_unused,
92
mmfile_t *orig,
93
mmfile_t *src1, const char *name1,
94
mmfile_t *src2, const char *name2,
95
- int virtual_ancestor)
95
+ int flag)
96
{
97
char *src, *dst;
98
long size;
@@ -104,7 +104,7 @@ static int ll_union_merge(const struct ll_merge_driver *drv_unused,
104
git_xmerge_style = 0;
105
status = ll_xdl_merge(drv_unused, result, path_unused,
106
orig, src1, NULL, src2, NULL,
107
- virtual_ancestor);
107
+ flag);
108
git_xmerge_style = saved_style;
109
if (status <= 0)
110
return status;
@@ -165,7 +165,7 @@ static int ll_ext_merge(const struct ll_merge_driver *fn,
165
mmfile_t *orig,
166
mmfile_t *src1, const char *name1,
167
mmfile_t *src2, const char *name2,
168
- int virtual_ancestor)
168
+ int flag)
169
{
170
char temp[3][50];
171
struct strbuf cmd = STRBUF_INIT;
@@ -356,10 +356,11 @@ int ll_merge(mmbuffer_t *result_buf,
356
mmfile_t *ancestor,
357
mmfile_t *ours, const char *our_label,
358
mmfile_t *theirs, const char *their_label,
359
- int virtual_ancestor)
359
+ int flag)
360
{
361
const char *ll_driver_name;
362
const struct ll_merge_driver *driver;
363
+ int virtual_ancestor = flag & 01;
364
365
ll_driver_name = git_path_check_merge(path);
366
driver = find_ll_merge_driver(ll_driver_name);
@@ -369,5 +370,5 @@ int ll_merge(mmbuffer_t *result_buf,
370
return driver->fn(driver, result_buf, path,
371
ancestor,
372
ours, our_label,
372
- theirs, their_label, virtual_ancestor);
373
+ theirs, their_label, flag);
374
}
ll-merge.h
+1
-1
@@ -10,6 +10,6 @@ int ll_merge(mmbuffer_t *result_buf,
10
mmfile_t *ancestor,
11
mmfile_t *ours, const char *our_label,
12
mmfile_t *theirs, const char *their_label,
13
- int virtual_ancestor);
13
+ int flag);
14
15
#endif
merge-recursive.c
+19
-2
@@ -642,6 +642,23 @@ static int merge_3way(struct merge_options *o,
642
mmfile_t orig, src1, src2;
643
char *name1, *name2;
644
int merge_status;
645
+ int favor;
646
+
647
+ if (o->call_depth)
648
+ favor = 0;
649
+ else {
650
+ switch (o->recursive_variant) {
651
+ case MERGE_RECURSIVE_OURS:
652
+ favor = XDL_MERGE_FAVOR_OURS;
653
+ break;
654
+ case MERGE_RECURSIVE_THEIRS:
655
+ favor = XDL_MERGE_FAVOR_THEIRS;
656
+ break;
657
+ default:
658
+ favor = 0;
659
+ break;
660
+ }
661
+ }
662
663
if (strcmp(a->path, b->path)) {
664
name1 = xstrdup(mkpath("%s:%s", branch1, a->path));
@@ -657,7 +674,7 @@ static int merge_3way(struct merge_options *o,
674
675
merge_status = ll_merge(result_buf, a->path, &orig,
676
&src1, name1, &src2, name2,
660
- o->call_depth);
677
+ (!!o->call_depth) | (favor << 1));
678
679
free(name1);
680
free(name2);
@@ -1196,7 +1213,7 @@ int merge_trees(struct merge_options *o,
1213
{
1214
int code, clean;
1215
1199
- if (o->subtree_merge) {
1216
+ if (o->recursive_variant == MERGE_RECURSIVE_SUBTREE) {
1217
merge = shift_tree_object(head, merge);
1218
common = shift_tree_object(head, common);
1219
}
merge-recursive.h
+5
-1
@@ -6,7 +6,11 @@
6
struct merge_options {
7
const char *branch1;
8
const char *branch2;
9
- unsigned subtree_merge : 1;
9
+ enum {
10
+ MERGE_RECURSIVE_SUBTREE = 1,
11
+ MERGE_RECURSIVE_OURS,
12
+ MERGE_RECURSIVE_THEIRS,
13
+ } recursive_variant;
14
unsigned buffer_output : 1;
15
int verbosity;
16
int diff_rename_limit;
strbuf.c
+9
@@ -10,6 +10,15 @@ int prefixcmp(const char *str, const char *prefix)
10
return (unsigned char)*prefix - (unsigned char)*str;
11
}
12
13
+int suffixcmp(const char *str, const char *suffix)
14
+{
15
+ int len = strlen(str), suflen = strlen(suffix);
16
+ if (len < suflen)
17
+ return -1;
18
+ else
19
+ return strcmp(str + len - suflen, suffix);
20
+}
21
+
22
/*
23
* Used as the default ->buf value, so that people can always assume
24
* buf is non NULL and ->buf is NUL terminated even for a freshly
t/t6037-merge-ours-theirs.sh
new
+56
@@ -0,0 +1,56 @@
1
+#!/bin/sh
2
+
3
+test_description='Merge-recursive ours and theirs variants'
4
+. ./test-lib.sh
5
+
6
+test_expect_success setup '
7
+ for i in 1 2 3 4 5 6 7 8 9
8
+ do
9
+ echo "$i"
10
+ done >file &&
11
+ git add file &&
12
+ cp file elif &&
13
+ git commit -m initial &&
14
+
15
+ sed -e "s/1/one/" -e "s/9/nine/" >file <elif &&
16
+ git commit -a -m ours &&
17
+
18
+ git checkout -b side HEAD^ &&
19
+
20
+ sed -e "s/9/nueve/" >file <elif &&
21
+ git commit -a -m theirs &&
22
+
23
+ git checkout master^0
24
+'
25
+
26
+test_expect_success 'plain recursive - should conflict' '
27
+ git reset --hard master &&
28
+ test_must_fail git merge -s recursive side &&
29
+ grep nine file &&
30
+ grep nueve file &&
31
+ ! grep 9 file &&
32
+ grep one file &&
33
+ ! grep 1 file
34
+'
35
+
36
+test_expect_success 'recursive favouring theirs' '
37
+ git reset --hard master &&
38
+ git merge -s recursive -Xtheirs side &&
39
+ ! grep nine file &&
40
+ grep nueve file &&
41
+ ! grep 9 file &&
42
+ grep one file &&
43
+ ! grep 1 file
44
+'
45
+
46
+test_expect_success 'recursive favouring ours' '
47
+ git reset --hard master &&
48
+ git merge -s recursive -X ours side &&
49
+ grep nine file &&
50
+ ! grep nueve file &&
51
+ ! grep 9 file &&
52
+ grep one file &&
53
+ ! grep 1 file
54
+'
55
+
56
+test_done