Skip to content

Commit 8cc5b29

Browse files
apenwarrgitster
authored andcommitted
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 <[email protected]> Signed-off-by: Junio C Hamano <[email protected]>
1 parent 73eb40e commit 8cc5b29

File tree

11 files changed

+162
-26
lines changed

11 files changed

+162
-26
lines changed

builtin-merge-recursive.c

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -26,18 +26,29 @@ int cmd_merge_recursive(int argc, const char **argv, const char *prefix)
2626

2727
init_merge_options(&o);
2828
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;
3331
}
3432

3533
if (argc < 4)
3634
usagef("%s <base>... -- <head> <remote> ...", argv[0]);
3735

3836
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+
}
4152
if (bases_count < ARRAY_SIZE(bases)-1) {
4253
unsigned char *sha = xmalloc(20);
4354
if (get_sha1(argv[i], sha))

builtin-merge.c

Lines changed: 37 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,8 @@ static struct commit_list *remoteheads;
5050
static unsigned char head[20], stash[20];
5151
static struct strategy **use_strategies;
5252
static size_t use_strategies_nr, use_strategies_alloc;
53+
static const char **xopts;
54+
static size_t xopts_nr, xopts_alloc;
5355
static const char *branch;
5456
static int verbosity;
5557

@@ -146,6 +148,17 @@ static int option_parse_strategy(const struct option *opt,
146148
return 0;
147149
}
148150

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+
149162
static int option_parse_n(const struct option *opt,
150163
const char *arg, int unset)
151164
{
@@ -172,6 +185,8 @@ static struct option builtin_merge_options[] = {
172185
"abort if fast-forward is not possible"),
173186
OPT_CALLBACK('s', "strategy", &use_strategies, "strategy",
174187
"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),
175190
OPT_CALLBACK('m', "message", &merge_msg, "message",
176191
"message to be used for the merge commit (if any)",
177192
option_parse_message),
@@ -534,7 +549,7 @@ static int try_merge_strategy(const char *strategy, struct commit_list *common,
534549
const char *head_arg)
535550
{
536551
const char **args;
537-
int i = 0, ret;
552+
int i = 0, x = 0, ret;
538553
struct commit_list *j;
539554
struct strbuf buf = STRBUF_INIT;
540555
int index_fd;
@@ -563,7 +578,18 @@ static int try_merge_strategy(const char *strategy, struct commit_list *common,
563578

564579
init_merge_options(&o);
565580
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+
}
567593

568594
o.branch1 = head_arg;
569595
o.branch2 = remoteheads->item->util;
@@ -581,10 +607,16 @@ static int try_merge_strategy(const char *strategy, struct commit_list *common,
581607
rollback_lock_file(lock);
582608
return clean ? 0 : 1;
583609
} else {
584-
args = xmalloc((4 + commit_list_count(common) +
610+
args = xmalloc((4 + xopts_nr + commit_list_count(common) +
585611
commit_list_count(remoteheads)) * sizeof(char *));
586612
strbuf_addf(&buf, "merge-%s", strategy);
587613
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+
}
588620
for (j = common; j; j = j->next)
589621
args[i++] = xstrdup(sha1_to_hex(j->item->object.sha1));
590622
args[i++] = "--";
@@ -595,6 +627,8 @@ static int try_merge_strategy(const char *strategy, struct commit_list *common,
595627
ret = run_command_v_opt(args, RUN_GIT_CMD);
596628
strbuf_release(&buf);
597629
i = 1;
630+
for (x = 0; x < xopts_nr; x++)
631+
free((void *)args[i++]);
598632
for (j = common; j; j = j->next)
599633
free((void *)args[i++]);
600634
i += 2;

contrib/examples/git-merge.sh

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,10 +31,11 @@ LF='
3131
'
3232

3333
all_strategies='recur recursive octopus resolve stupid ours subtree'
34+
all_strategies="$all_strategies recursive-ours recursive-theirs"
3435
default_twohead_strategies='recursive'
3536
default_octopus_strategies='octopus'
3637
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'
3839
use_strategies=
3940

4041
allow_fast_forward=t

git-compat-util.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,7 @@ extern void warning(const char *err, ...) __attribute__((format (printf, 1, 2)))
198198
extern void set_die_routine(NORETURN_PTR void (*routine)(const char *err, va_list params));
199199

200200
extern int prefixcmp(const char *str, const char *prefix);
201+
extern int suffixcmp(const char *str, const char *suffix);
201202
extern time_t tm_to_time_t(const struct tm *tm);
202203

203204
static inline const char *skip_prefix(const char *str, const char *prefix)

git.c

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -332,6 +332,8 @@ static void handle_internal_command(int argc, const char **argv)
332332
{ "merge-file", cmd_merge_file },
333333
{ "merge-ours", cmd_merge_ours, RUN_SETUP },
334334
{ "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 },
335337
{ "merge-subtree", cmd_merge_recursive, RUN_SETUP | NEED_WORK_TREE },
336338
{ "mktree", cmd_mktree, RUN_SETUP },
337339
{ "mv", cmd_mv, RUN_SETUP | NEED_WORK_TREE },

ll-merge.c

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ typedef int (*ll_merge_fn)(const struct ll_merge_driver *,
1818
mmfile_t *orig,
1919
mmfile_t *src1, const char *name1,
2020
mmfile_t *src2, const char *name2,
21-
int virtual_ancestor);
21+
int flag);
2222

2323
struct ll_merge_driver {
2424
const char *name;
@@ -38,14 +38,14 @@ static int ll_binary_merge(const struct ll_merge_driver *drv_unused,
3838
mmfile_t *orig,
3939
mmfile_t *src1, const char *name1,
4040
mmfile_t *src2, const char *name2,
41-
int virtual_ancestor)
41+
int flag)
4242
{
4343
/*
4444
* The tentative merge result is "ours" for the final round,
4545
* or common ancestor for an internal merge. Still return
4646
* "conflicted merge" status.
4747
*/
48-
mmfile_t *stolen = virtual_ancestor ? orig : src1;
48+
mmfile_t *stolen = (flag & 01) ? orig : src1;
4949

5050
result->ptr = stolen->ptr;
5151
result->size = stolen->size;
@@ -59,10 +59,11 @@ static int ll_xdl_merge(const struct ll_merge_driver *drv_unused,
5959
mmfile_t *orig,
6060
mmfile_t *src1, const char *name1,
6161
mmfile_t *src2, const char *name2,
62-
int virtual_ancestor)
62+
int flag)
6363
{
6464
xpparam_t xpp;
6565
int style = 0;
66+
int favor = (flag >> 1) & 03;
6667

6768
if (buffer_is_binary(orig->ptr, orig->size) ||
6869
buffer_is_binary(src1->ptr, src1->size) ||
@@ -72,8 +73,7 @@ static int ll_xdl_merge(const struct ll_merge_driver *drv_unused,
7273
return ll_binary_merge(drv_unused, result,
7374
path,
7475
orig, src1, name1,
75-
src2, name2,
76-
virtual_ancestor);
76+
src2, name2, flag);
7777
}
7878

7979
memset(&xpp, 0, sizeof(xpp));
@@ -82,7 +82,7 @@ static int ll_xdl_merge(const struct ll_merge_driver *drv_unused,
8282
return xdl_merge(orig,
8383
src1, name1,
8484
src2, name2,
85-
&xpp, XDL_MERGE_ZEALOUS | style,
85+
&xpp, XDL_MERGE_FLAGS(XDL_MERGE_ZEALOUS, style, favor),
8686
result);
8787
}
8888

@@ -92,7 +92,7 @@ static int ll_union_merge(const struct ll_merge_driver *drv_unused,
9292
mmfile_t *orig,
9393
mmfile_t *src1, const char *name1,
9494
mmfile_t *src2, const char *name2,
95-
int virtual_ancestor)
95+
int flag)
9696
{
9797
char *src, *dst;
9898
long size;
@@ -104,7 +104,7 @@ static int ll_union_merge(const struct ll_merge_driver *drv_unused,
104104
git_xmerge_style = 0;
105105
status = ll_xdl_merge(drv_unused, result, path_unused,
106106
orig, src1, NULL, src2, NULL,
107-
virtual_ancestor);
107+
flag);
108108
git_xmerge_style = saved_style;
109109
if (status <= 0)
110110
return status;
@@ -165,7 +165,7 @@ static int ll_ext_merge(const struct ll_merge_driver *fn,
165165
mmfile_t *orig,
166166
mmfile_t *src1, const char *name1,
167167
mmfile_t *src2, const char *name2,
168-
int virtual_ancestor)
168+
int flag)
169169
{
170170
char temp[3][50];
171171
struct strbuf cmd = STRBUF_INIT;
@@ -356,10 +356,11 @@ int ll_merge(mmbuffer_t *result_buf,
356356
mmfile_t *ancestor,
357357
mmfile_t *ours, const char *our_label,
358358
mmfile_t *theirs, const char *their_label,
359-
int virtual_ancestor)
359+
int flag)
360360
{
361361
const char *ll_driver_name;
362362
const struct ll_merge_driver *driver;
363+
int virtual_ancestor = flag & 01;
363364

364365
ll_driver_name = git_path_check_merge(path);
365366
driver = find_ll_merge_driver(ll_driver_name);
@@ -369,5 +370,5 @@ int ll_merge(mmbuffer_t *result_buf,
369370
return driver->fn(driver, result_buf, path,
370371
ancestor,
371372
ours, our_label,
372-
theirs, their_label, virtual_ancestor);
373+
theirs, their_label, flag);
373374
}

ll-merge.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,6 @@ int ll_merge(mmbuffer_t *result_buf,
1010
mmfile_t *ancestor,
1111
mmfile_t *ours, const char *our_label,
1212
mmfile_t *theirs, const char *their_label,
13-
int virtual_ancestor);
13+
int flag);
1414

1515
#endif

merge-recursive.c

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -642,6 +642,23 @@ static int merge_3way(struct merge_options *o,
642642
mmfile_t orig, src1, src2;
643643
char *name1, *name2;
644644
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+
}
645662

646663
if (strcmp(a->path, b->path)) {
647664
name1 = xstrdup(mkpath("%s:%s", branch1, a->path));
@@ -657,7 +674,7 @@ static int merge_3way(struct merge_options *o,
657674

658675
merge_status = ll_merge(result_buf, a->path, &orig,
659676
&src1, name1, &src2, name2,
660-
o->call_depth);
677+
(!!o->call_depth) | (favor << 1));
661678

662679
free(name1);
663680
free(name2);
@@ -1196,7 +1213,7 @@ int merge_trees(struct merge_options *o,
11961213
{
11971214
int code, clean;
11981215

1199-
if (o->subtree_merge) {
1216+
if (o->recursive_variant == MERGE_RECURSIVE_SUBTREE) {
12001217
merge = shift_tree_object(head, merge);
12011218
common = shift_tree_object(head, common);
12021219
}

merge-recursive.h

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,11 @@
66
struct merge_options {
77
const char *branch1;
88
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;
1014
unsigned buffer_output : 1;
1115
int verbosity;
1216
int diff_rename_limit;

strbuf.c

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,15 @@ int prefixcmp(const char *str, const char *prefix)
1010
return (unsigned char)*prefix - (unsigned char)*str;
1111
}
1212

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+
1322
/*
1423
* Used as the default ->buf value, so that people can always assume
1524
* buf is non NULL and ->buf is NUL terminated even for a freshly

0 commit comments

Comments
 (0)