Skip to content

Commit fcb2a7e

Browse files
committed
Merge branch 'ap/merge-backend-opts'
* 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
2 parents e98f80f + 566c511 commit fcb2a7e

21 files changed

+392
-54
lines changed

Documentation/git-merge-file.txt

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@ SYNOPSIS
1010
--------
1111
[verse]
1212
'git merge-file' [-L <current-name> [-L <base-name> [-L <other-name>]]]
13-
[-p|--stdout] [-q|--quiet] <current-file> <base-file> <other-file>
13+
[--ours|--theirs] [-p|--stdout] [-q|--quiet]
14+
<current-file> <base-file> <other-file>
1415

1516

1617
DESCRIPTION
@@ -34,7 +35,9 @@ normally outputs a warning and brackets the conflict with lines containing
3435
>>>>>>> B
3536

3637
If there are conflicts, the user should edit the result and delete one of
37-
the alternatives.
38+
the alternatives. When `--ours` or `--theirs` option is in effect, however,
39+
these conflicts are resolved favouring lines from `<current-file>` or
40+
lines from `<other-file>` respectively.
3841

3942
The exit value of this program is negative on error, and the number of
4043
conflicts otherwise. If the merge was clean, the exit value is 0.
@@ -62,6 +65,11 @@ OPTIONS
6265
-q::
6366
Quiet; do not warn about conflicts.
6467

68+
--ours::
69+
--theirs::
70+
Instead of leaving conflicts in the file, resolve conflicts
71+
favouring our (or their) side of the lines.
72+
6573

6674
EXAMPLES
6775
--------

Documentation/merge-options.txt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,3 +74,8 @@ option can be used to override --squash.
7474
-v::
7575
--verbose::
7676
Be verbose.
77+
78+
-X <option>::
79+
--strategy-option=<option>::
80+
Pass merge strategy specific option through to the merge
81+
strategy.

Documentation/merge-strategies.txt

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
MERGE STRATEGIES
22
----------------
33

4+
The merge mechanism ('git-merge' and 'git-pull' commands) allows the
5+
backend 'merge strategies' to be chosen with `-s` option. Some strategies
6+
can also take their own options, which can be passed by giving `-X<option>`
7+
arguments to 'git-merge' and/or 'git-pull'.
8+
49
resolve::
510
This can only resolve two heads (i.e. the current branch
611
and another branch you pulled from) using a 3-way merge
@@ -20,6 +25,27 @@ recursive::
2025
Additionally this can detect and handle merges involving
2126
renames. This is the default merge strategy when
2227
pulling or merging one branch.
28+
+
29+
The 'recursive' strategy can take the following options:
30+
31+
ours;;
32+
This option forces conflicting hunks to be auto-resolved cleanly by
33+
favoring 'our' version. Changes from the other tree that do not
34+
conflict with our side are reflected to the merge result.
35+
+
36+
This should not be confused with the 'ours' merge strategy, which does not
37+
even look at what the other tree contains at all. It discards everything
38+
the other tree did, declaring 'our' history contains all that happened in it.
39+
40+
theirs;;
41+
This is opposite of 'ours'.
42+
43+
subtree[=path];;
44+
This option is a more advanced form of 'subtree' strategy, where
45+
the strategy makes a guess on how two trees must be shifted to
46+
match with each other when merging. Instead, the specified path
47+
is prefixed (or stripped from the beginning) to make the shape of
48+
two trees to match.
2349

2450
octopus::
2551
This resolves cases with more than two heads, but refuses to do
@@ -33,7 +59,8 @@ ours::
3359
merge is always that of the current branch head, effectively
3460
ignoring all changes from all other branches. It is meant to
3561
be used to supersede old development history of side
36-
branches.
62+
branches. Note that this is different from the -Xours option to
63+
the 'recursive' merge strategy.
3764

3865
subtree::
3966
This is a modified recursive strategy. When merging trees A and

builtin-merge-file.c

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -27,13 +27,18 @@ int cmd_merge_file(int argc, const char **argv, const char *prefix)
2727
mmbuffer_t result = {NULL, 0};
2828
xpparam_t xpp = {XDF_NEED_MINIMAL};
2929
int ret = 0, i = 0, to_stdout = 0;
30-
int merge_level = XDL_MERGE_ZEALOUS_ALNUM;
31-
int merge_style = 0, quiet = 0;
30+
int level = XDL_MERGE_ZEALOUS_ALNUM;
31+
int style = 0, quiet = 0;
32+
int favor = 0;
3233
int nongit;
3334

3435
struct option options[] = {
3536
OPT_BOOLEAN('p', "stdout", &to_stdout, "send results to standard output"),
36-
OPT_SET_INT(0, "diff3", &merge_style, "use a diff3 based merge", XDL_MERGE_DIFF3),
37+
OPT_SET_INT(0, "diff3", &style, "use a diff3 based merge", XDL_MERGE_DIFF3),
38+
OPT_SET_INT(0, "ours", &favor, "for conflicts, use our version",
39+
XDL_MERGE_FAVOR_OURS),
40+
OPT_SET_INT(0, "theirs", &favor, "for conflicts, use their version",
41+
XDL_MERGE_FAVOR_THEIRS),
3742
OPT__QUIET(&quiet),
3843
OPT_CALLBACK('L', NULL, names, "name",
3944
"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)
4550
/* Read the configuration file */
4651
git_config(git_xmerge_config, NULL);
4752
if (0 <= git_xmerge_style)
48-
merge_style = git_xmerge_style;
53+
style = git_xmerge_style;
4954
}
5055

5156
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)
6873
}
6974

7075
ret = xdl_merge(mmfs + 1, mmfs + 0, names[0], mmfs + 2, names[2],
71-
&xpp, merge_level | merge_style, &result);
76+
&xpp, XDL_MERGE_FLAGS(level, style, favor), &result);
7277

7378
for (i = 0; i < 3; i++)
7479
free(mmfs[i].ptr);

builtin-merge-recursive.c

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -25,19 +25,30 @@ int cmd_merge_recursive(int argc, const char **argv, const char *prefix)
2525
struct commit *result;
2626

2727
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;
33-
}
28+
if (argv[0] && !suffixcmp(argv[0], "-subtree"))
29+
o.subtree_shift = "";
3430

3531
if (argc < 4)
3632
usagef("%s <base>... -- <head> <remote> ...", argv[0]);
3733

3834
for (i = 1; i < argc; ++i) {
39-
if (!strcmp(argv[i], "--"))
40-
break;
35+
const char *arg = argv[i];
36+
37+
if (!prefixcmp(arg, "--")) {
38+
if (!arg[2])
39+
break;
40+
if (!strcmp(arg+2, "ours"))
41+
o.recursive_variant = MERGE_RECURSIVE_OURS;
42+
else if (!strcmp(arg+2, "theirs"))
43+
o.recursive_variant = MERGE_RECURSIVE_THEIRS;
44+
else if (!strcmp(arg+2, "subtree"))
45+
o.subtree_shift = "";
46+
else if (!prefixcmp(arg+2, "subtree="))
47+
o.subtree_shift = arg + 10;
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: 39 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,8 @@ static struct commit_list *remoteheads;
5151
static unsigned char head[20], stash[20];
5252
static struct strategy **use_strategies;
5353
static size_t use_strategies_nr, use_strategies_alloc;
54+
static const char **xopts;
55+
static size_t xopts_nr, xopts_alloc;
5456
static const char *branch;
5557
static int verbosity;
5658
static int allow_rerere_auto;
@@ -148,6 +150,17 @@ static int option_parse_strategy(const struct option *opt,
148150
return 0;
149151
}
150152

153+
static int option_parse_x(const struct option *opt,
154+
const char *arg, int unset)
155+
{
156+
if (unset)
157+
return 0;
158+
159+
ALLOC_GROW(xopts, xopts_nr + 1, xopts_alloc);
160+
xopts[xopts_nr++] = xstrdup(arg);
161+
return 0;
162+
}
163+
151164
static int option_parse_n(const struct option *opt,
152165
const char *arg, int unset)
153166
{
@@ -175,6 +188,8 @@ static struct option builtin_merge_options[] = {
175188
OPT_RERERE_AUTOUPDATE(&allow_rerere_auto),
176189
OPT_CALLBACK('s', "strategy", &use_strategies, "strategy",
177190
"merge strategy to use", option_parse_strategy),
191+
OPT_CALLBACK('X', "strategy-option", &xopts, "option=value",
192+
"option for selected merge strategy", option_parse_x),
178193
OPT_CALLBACK('m', "message", &merge_msg, "message",
179194
"message to be used for the merge commit (if any)",
180195
option_parse_message),
@@ -537,7 +552,7 @@ static int try_merge_strategy(const char *strategy, struct commit_list *common,
537552
const char *head_arg)
538553
{
539554
const char **args;
540-
int i = 0, ret;
555+
int i = 0, x = 0, ret;
541556
struct commit_list *j;
542557
struct strbuf buf = STRBUF_INIT;
543558
int index_fd;
@@ -566,7 +581,20 @@ static int try_merge_strategy(const char *strategy, struct commit_list *common,
566581

567582
init_merge_options(&o);
568583
if (!strcmp(strategy, "subtree"))
569-
o.subtree_merge = 1;
584+
o.subtree_shift = "";
585+
586+
for (x = 0; x < xopts_nr; x++) {
587+
if (!strcmp(xopts[x], "ours"))
588+
o.recursive_variant = MERGE_RECURSIVE_OURS;
589+
else if (!strcmp(xopts[x], "theirs"))
590+
o.recursive_variant = MERGE_RECURSIVE_THEIRS;
591+
else if (!strcmp(xopts[x], "subtree"))
592+
o.subtree_shift = "";
593+
else if (!prefixcmp(xopts[x], "subtree="))
594+
o.subtree_shift = xopts[x]+8;
595+
else
596+
die("Unknown option for merge-recursive: -X%s", xopts[x]);
597+
}
570598

571599
o.branch1 = head_arg;
572600
o.branch2 = remoteheads->item->util;
@@ -584,10 +612,16 @@ static int try_merge_strategy(const char *strategy, struct commit_list *common,
584612
rollback_lock_file(lock);
585613
return clean ? 0 : 1;
586614
} else {
587-
args = xmalloc((4 + commit_list_count(common) +
615+
args = xmalloc((4 + xopts_nr + commit_list_count(common) +
588616
commit_list_count(remoteheads)) * sizeof(char *));
589617
strbuf_addf(&buf, "merge-%s", strategy);
590618
args[i++] = buf.buf;
619+
for (x = 0; x < xopts_nr; x++) {
620+
char *s = xmalloc(strlen(xopts[x])+2+1);
621+
strcpy(s, "--");
622+
strcpy(s+2, xopts[x]);
623+
args[i++] = s;
624+
}
591625
for (j = common; j; j = j->next)
592626
args[i++] = xstrdup(sha1_to_hex(j->item->object.sha1));
593627
args[i++] = "--";
@@ -598,6 +632,8 @@ static int try_merge_strategy(const char *strategy, struct commit_list *common,
598632
ret = run_command_v_opt(args, RUN_GIT_CMD);
599633
strbuf_release(&buf);
600634
i = 1;
635+
for (x = 0; x < xopts_nr; x++)
636+
free((void *)args[i++]);
601637
for (j = common; j; j = j->next)
602638
free((void *)args[i++]);
603639
i += 2;

cache.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1007,6 +1007,7 @@ extern int diff_auto_refresh_index;
10071007

10081008
/* match-trees.c */
10091009
void shift_tree(const unsigned char *, const unsigned char *, unsigned char *, int);
1010+
void shift_tree_by(const unsigned char *, const unsigned char *, unsigned char *, const char *);
10101011

10111012
/*
10121013
* whitespace rules.

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
@@ -199,6 +199,7 @@ extern void warning(const char *err, ...) __attribute__((format (printf, 1, 2)))
199199
extern void set_die_routine(NORETURN_PTR void (*routine)(const char *err, va_list params));
200200

201201
extern int prefixcmp(const char *str, const char *prefix);
202+
extern int suffixcmp(const char *str, const char *suffix);
202203

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

git-pull.sh

Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ test -f "$GIT_DIR/MERGE_HEAD" && die_merge
3939

4040
strategy_args= diffstat= no_commit= squash= no_ff= ff_only=
4141
log_arg= verbosity=
42+
merge_args=
4243
curr_branch=$(git symbolic-ref -q HEAD)
4344
curr_branch_short=$(echo "$curr_branch" | sed "s|refs/heads/||")
4445
rebase=$(git config --bool branch.$curr_branch_short.rebase)
@@ -83,6 +84,18 @@ do
8384
esac
8485
strategy_args="${strategy_args}-s $strategy "
8586
;;
87+
-X*)
88+
case "$#,$1" in
89+
1,-X)
90+
usage ;;
91+
*,-X)
92+
xx="-X $(git rev-parse --sq-quote "$2")"
93+
shift ;;
94+
*,*)
95+
xx=$(git rev-parse --sq-quote "$1") ;;
96+
esac
97+
merge_args="$merge_args$xx "
98+
;;
8699
-r|--r|--re|--reb|--reba|--rebas|--rebase)
87100
rebase=true
88101
;;
@@ -254,8 +267,15 @@ then
254267
fi
255268

256269
merge_name=$(git fmt-merge-msg $log_arg <"$GIT_DIR/FETCH_HEAD") || exit
257-
test true = "$rebase" &&
258-
exec git-rebase $diffstat $strategy_args --onto $merge_head \
259-
${oldremoteref:-$merge_head}
260-
exec git-merge $diffstat $no_commit $squash $no_ff $ff_only $log_arg $strategy_args \
261-
"$merge_name" HEAD $merge_head $verbosity
270+
case "$rebase" in
271+
true)
272+
eval="git-rebase $diffstat $strategy_args $merge_args"
273+
eval="$eval --onto $merge_head ${oldremoteref:-$merge_head}"
274+
;;
275+
*)
276+
eval="git-merge $diffstat $no_commit $squash $no_ff $ff_only"
277+
eval="$eval $log_arg $strategy_args $merge_args"
278+
eval="$eval \"$merge_name\" HEAD $merge_head $verbosity"
279+
;;
280+
esac
281+
eval "exec $eval"

0 commit comments

Comments
 (0)