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
This commit is contained in:
commit
fcb2a7e4a3
@ -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
|
||||
--------
|
||||
|
@ -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.
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
|
@ -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))
|
||||
|
@ -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;
|
||||
|
1
cache.h
1
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.
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
{
|
||||
|
30
git-pull.sh
30
git-pull.sh
@ -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"
|
||||
|
2
git.c
2
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 },
|
||||
|
25
ll-merge.c
25
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);
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -21,15 +21,17 @@
|
||||
#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)) {
|
||||
|
@ -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;
|
||||
|
9
strbuf.c
9
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
|
||||
|
@ -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
|
||||
|
64
t/t6037-merge-ours-theirs.sh
Executable file
64
t/t6037-merge-ours-theirs.sh
Executable file
@ -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
|
@ -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
|
||||
}
|
||||
|
@ -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);
|
||||
|
Loading…
Reference in New Issue
Block a user