mirror of
https://github.com/git/git
synced 2024-09-20 17:01:35 +00:00
Merge branch 'kz/merge-tree-merge-base'
"merge-tree" learns a new `--merge-base` option. * kz/merge-tree-merge-base: docs: fix description of the `--merge-base` option merge-tree.c: allow specifying the merge-base when --stdin is passed merge-tree.c: add --merge-base=<commit> option
This commit is contained in:
commit
7576e512ce
|
@ -64,6 +64,11 @@ OPTIONS
|
||||||
share no common history. This flag can be given to override that
|
share no common history. This flag can be given to override that
|
||||||
check and make the merge proceed anyway.
|
check and make the merge proceed anyway.
|
||||||
|
|
||||||
|
--merge-base=<commit>::
|
||||||
|
Instead of finding the merge-bases for <branch1> and <branch2>,
|
||||||
|
specify a merge-base for the merge, and specifying multiple bases is
|
||||||
|
currently not supported. This option is incompatible with `--stdin`.
|
||||||
|
|
||||||
[[OUTPUT]]
|
[[OUTPUT]]
|
||||||
OUTPUT
|
OUTPUT
|
||||||
------
|
------
|
||||||
|
@ -216,6 +221,17 @@ with linkgit:git-merge[1]:
|
||||||
* any messages that would have been printed to stdout (the
|
* any messages that would have been printed to stdout (the
|
||||||
<<IM,Informational messages>>)
|
<<IM,Informational messages>>)
|
||||||
|
|
||||||
|
INPUT FORMAT
|
||||||
|
------------
|
||||||
|
'git merge-tree --stdin' input format is fully text based. Each line
|
||||||
|
has this format:
|
||||||
|
|
||||||
|
[<base-commit> -- ]<branch1> <branch2>
|
||||||
|
|
||||||
|
If one line is separated by `--`, the string before the separator is
|
||||||
|
used for specifying a merge-base for the merge and the string after
|
||||||
|
the separator describes the branches to be merged.
|
||||||
|
|
||||||
MISTAKES TO AVOID
|
MISTAKES TO AVOID
|
||||||
-----------------
|
-----------------
|
||||||
|
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
#include "tree-walk.h"
|
#include "tree-walk.h"
|
||||||
#include "xdiff-interface.h"
|
#include "xdiff-interface.h"
|
||||||
#include "help.h"
|
#include "help.h"
|
||||||
|
#include "commit.h"
|
||||||
#include "commit-reach.h"
|
#include "commit-reach.h"
|
||||||
#include "merge-ort.h"
|
#include "merge-ort.h"
|
||||||
#include "object-store.h"
|
#include "object-store.h"
|
||||||
|
@ -406,6 +407,7 @@ struct merge_tree_options {
|
||||||
};
|
};
|
||||||
|
|
||||||
static int real_merge(struct merge_tree_options *o,
|
static int real_merge(struct merge_tree_options *o,
|
||||||
|
const char *merge_base,
|
||||||
const char *branch1, const char *branch2,
|
const char *branch1, const char *branch2,
|
||||||
const char *prefix)
|
const char *prefix)
|
||||||
{
|
{
|
||||||
|
@ -432,16 +434,31 @@ static int real_merge(struct merge_tree_options *o,
|
||||||
opt.branch1 = branch1;
|
opt.branch1 = branch1;
|
||||||
opt.branch2 = branch2;
|
opt.branch2 = branch2;
|
||||||
|
|
||||||
/*
|
if (merge_base) {
|
||||||
* Get the merge bases, in reverse order; see comment above
|
struct commit *base_commit;
|
||||||
* merge_incore_recursive in merge-ort.h
|
struct tree *base_tree, *parent1_tree, *parent2_tree;
|
||||||
*/
|
|
||||||
merge_bases = get_merge_bases(parent1, parent2);
|
base_commit = lookup_commit_reference_by_name(merge_base);
|
||||||
if (!merge_bases && !o->allow_unrelated_histories)
|
if (!base_commit)
|
||||||
die(_("refusing to merge unrelated histories"));
|
die(_("could not lookup commit %s"), merge_base);
|
||||||
merge_bases = reverse_commit_list(merge_bases);
|
|
||||||
|
opt.ancestor = merge_base;
|
||||||
|
base_tree = get_commit_tree(base_commit);
|
||||||
|
parent1_tree = get_commit_tree(parent1);
|
||||||
|
parent2_tree = get_commit_tree(parent2);
|
||||||
|
merge_incore_nonrecursive(&opt, base_tree, parent1_tree, parent2_tree, &result);
|
||||||
|
} else {
|
||||||
|
/*
|
||||||
|
* Get the merge bases, in reverse order; see comment above
|
||||||
|
* merge_incore_recursive in merge-ort.h
|
||||||
|
*/
|
||||||
|
merge_bases = get_merge_bases(parent1, parent2);
|
||||||
|
if (!merge_bases && !o->allow_unrelated_histories)
|
||||||
|
die(_("refusing to merge unrelated histories"));
|
||||||
|
merge_bases = reverse_commit_list(merge_bases);
|
||||||
|
merge_incore_recursive(&opt, merge_bases, parent1, parent2, &result);
|
||||||
|
}
|
||||||
|
|
||||||
merge_incore_recursive(&opt, merge_bases, parent1, parent2, &result);
|
|
||||||
if (result.clean < 0)
|
if (result.clean < 0)
|
||||||
die(_("failure to merge"));
|
die(_("failure to merge"));
|
||||||
|
|
||||||
|
@ -487,6 +504,7 @@ int cmd_merge_tree(int argc, const char **argv, const char *prefix)
|
||||||
struct merge_tree_options o = { .show_messages = -1 };
|
struct merge_tree_options o = { .show_messages = -1 };
|
||||||
int expected_remaining_argc;
|
int expected_remaining_argc;
|
||||||
int original_argc;
|
int original_argc;
|
||||||
|
const char *merge_base = NULL;
|
||||||
|
|
||||||
const char * const merge_tree_usage[] = {
|
const char * const merge_tree_usage[] = {
|
||||||
N_("git merge-tree [--write-tree] [<options>] <branch1> <branch2>"),
|
N_("git merge-tree [--write-tree] [<options>] <branch1> <branch2>"),
|
||||||
|
@ -515,6 +533,10 @@ int cmd_merge_tree(int argc, const char **argv, const char *prefix)
|
||||||
&o.use_stdin,
|
&o.use_stdin,
|
||||||
N_("perform multiple merges, one per line of input"),
|
N_("perform multiple merges, one per line of input"),
|
||||||
PARSE_OPT_NONEG),
|
PARSE_OPT_NONEG),
|
||||||
|
OPT_STRING(0, "merge-base",
|
||||||
|
&merge_base,
|
||||||
|
N_("commit"),
|
||||||
|
N_("specify a merge-base for the merge")),
|
||||||
OPT_END()
|
OPT_END()
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -529,16 +551,35 @@ int cmd_merge_tree(int argc, const char **argv, const char *prefix)
|
||||||
|
|
||||||
if (o.mode == MODE_TRIVIAL)
|
if (o.mode == MODE_TRIVIAL)
|
||||||
die(_("--trivial-merge is incompatible with all other options"));
|
die(_("--trivial-merge is incompatible with all other options"));
|
||||||
|
if (merge_base)
|
||||||
|
die(_("--merge-base is incompatible with --stdin"));
|
||||||
line_termination = '\0';
|
line_termination = '\0';
|
||||||
while (strbuf_getline_lf(&buf, stdin) != EOF) {
|
while (strbuf_getline_lf(&buf, stdin) != EOF) {
|
||||||
struct strbuf **split;
|
struct strbuf **split;
|
||||||
int result;
|
int result;
|
||||||
|
const char *input_merge_base = NULL;
|
||||||
|
|
||||||
split = strbuf_split(&buf, ' ');
|
split = strbuf_split(&buf, ' ');
|
||||||
if (!split[0] || !split[1] || split[2])
|
if (!split[0] || !split[1])
|
||||||
die(_("malformed input line: '%s'."), buf.buf);
|
die(_("malformed input line: '%s'."), buf.buf);
|
||||||
strbuf_rtrim(split[0]);
|
strbuf_rtrim(split[0]);
|
||||||
result = real_merge(&o, split[0]->buf, split[1]->buf, prefix);
|
strbuf_rtrim(split[1]);
|
||||||
|
|
||||||
|
/* parse the merge-base */
|
||||||
|
if (!strcmp(split[1]->buf, "--")) {
|
||||||
|
input_merge_base = split[0]->buf;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (input_merge_base && split[2] && split[3] && !split[4]) {
|
||||||
|
strbuf_rtrim(split[2]);
|
||||||
|
strbuf_rtrim(split[3]);
|
||||||
|
result = real_merge(&o, input_merge_base, split[2]->buf, split[3]->buf, prefix);
|
||||||
|
} else if (!input_merge_base && !split[2]) {
|
||||||
|
result = real_merge(&o, NULL, split[0]->buf, split[1]->buf, prefix);
|
||||||
|
} else {
|
||||||
|
die(_("malformed input line: '%s'."), buf.buf);
|
||||||
|
}
|
||||||
|
|
||||||
if (result < 0)
|
if (result < 0)
|
||||||
die(_("merging cannot continue; got unclean result of %d"), result);
|
die(_("merging cannot continue; got unclean result of %d"), result);
|
||||||
strbuf_list_free(split);
|
strbuf_list_free(split);
|
||||||
|
@ -581,7 +622,7 @@ int cmd_merge_tree(int argc, const char **argv, const char *prefix)
|
||||||
|
|
||||||
/* Do the relevant type of merge */
|
/* Do the relevant type of merge */
|
||||||
if (o.mode == MODE_REAL)
|
if (o.mode == MODE_REAL)
|
||||||
return real_merge(&o, argv[0], argv[1], prefix);
|
return real_merge(&o, merge_base, argv[0], argv[1], prefix);
|
||||||
else
|
else
|
||||||
return trivial_merge(argv[0], argv[1], argv[2]);
|
return trivial_merge(argv[0], argv[1], argv[2]);
|
||||||
}
|
}
|
||||||
|
|
|
@ -860,4 +860,66 @@ test_expect_success '--stdin with both a successful and a conflicted merge' '
|
||||||
test_cmp expect actual
|
test_cmp expect actual
|
||||||
'
|
'
|
||||||
|
|
||||||
|
|
||||||
|
test_expect_success '--merge-base is incompatible with --stdin' '
|
||||||
|
test_must_fail git merge-tree --merge-base=side1 --stdin 2>expect &&
|
||||||
|
|
||||||
|
grep "^fatal: --merge-base is incompatible with --stdin" expect
|
||||||
|
'
|
||||||
|
|
||||||
|
# specify merge-base as parent of branch2
|
||||||
|
# git merge-tree --write-tree --merge-base=c2 c1 c3
|
||||||
|
# Commit c1: add file1
|
||||||
|
# Commit c2: add file2 after c1
|
||||||
|
# Commit c3: add file3 after c2
|
||||||
|
# Expected: add file3, and file2 does NOT appear
|
||||||
|
|
||||||
|
test_expect_success 'specify merge-base as parent of branch2' '
|
||||||
|
# Setup
|
||||||
|
test_when_finished "rm -rf base-b2-p" &&
|
||||||
|
git init base-b2-p &&
|
||||||
|
test_commit -C base-b2-p c1 file1 &&
|
||||||
|
test_commit -C base-b2-p c2 file2 &&
|
||||||
|
test_commit -C base-b2-p c3 file3 &&
|
||||||
|
|
||||||
|
# Testing
|
||||||
|
TREE_OID=$(git -C base-b2-p merge-tree --write-tree --merge-base=c2 c1 c3) &&
|
||||||
|
|
||||||
|
q_to_tab <<-EOF >expect &&
|
||||||
|
100644 blob $(git -C base-b2-p rev-parse c1:file1)Qfile1
|
||||||
|
100644 blob $(git -C base-b2-p rev-parse c3:file3)Qfile3
|
||||||
|
EOF
|
||||||
|
|
||||||
|
git -C base-b2-p ls-tree $TREE_OID >actual &&
|
||||||
|
test_cmp expect actual
|
||||||
|
'
|
||||||
|
|
||||||
|
# Since the earlier tests have verified that individual merge-tree calls
|
||||||
|
# are doing the right thing, this test case is only used to verify that
|
||||||
|
# we can also trigger merges via --stdin, and that when we do we get
|
||||||
|
# the same answer as running a bunch of separate merges.
|
||||||
|
|
||||||
|
test_expect_success 'check the input format when --stdin is passed' '
|
||||||
|
test_when_finished "rm -rf repo" &&
|
||||||
|
git init repo &&
|
||||||
|
test_commit -C repo c1 &&
|
||||||
|
test_commit -C repo c2 &&
|
||||||
|
test_commit -C repo c3 &&
|
||||||
|
printf "c1 c3\nc2 -- c1 c3\nc2 c3" | git -C repo merge-tree --stdin >actual &&
|
||||||
|
|
||||||
|
printf "1\0" >expect &&
|
||||||
|
git -C repo merge-tree --write-tree -z c1 c3 >>expect &&
|
||||||
|
printf "\0" >>expect &&
|
||||||
|
|
||||||
|
printf "1\0" >>expect &&
|
||||||
|
git -C repo merge-tree --write-tree -z --merge-base=c2 c1 c3 >>expect &&
|
||||||
|
printf "\0" >>expect &&
|
||||||
|
|
||||||
|
printf "1\0" >>expect &&
|
||||||
|
git -C repo merge-tree --write-tree -z c2 c3 >>expect &&
|
||||||
|
printf "\0" >>expect &&
|
||||||
|
|
||||||
|
test_cmp expect actual
|
||||||
|
'
|
||||||
|
|
||||||
test_done
|
test_done
|
||||||
|
|
Loading…
Reference in a new issue