Skip to content

Commit 7576e51

Browse files
committed
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
2 parents bee6e7a + 4cc9eb3 commit 7576e51

File tree

3 files changed

+131
-12
lines changed

3 files changed

+131
-12
lines changed

Documentation/git-merge-tree.txt

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,11 @@ OPTIONS
6464
share no common history. This flag can be given to override that
6565
check and make the merge proceed anyway.
6666

67+
--merge-base=<commit>::
68+
Instead of finding the merge-bases for <branch1> and <branch2>,
69+
specify a merge-base for the merge, and specifying multiple bases is
70+
currently not supported. This option is incompatible with `--stdin`.
71+
6772
[[OUTPUT]]
6873
OUTPUT
6974
------
@@ -216,6 +221,17 @@ with linkgit:git-merge[1]:
216221
* any messages that would have been printed to stdout (the
217222
<<IM,Informational messages>>)
218223

224+
INPUT FORMAT
225+
------------
226+
'git merge-tree --stdin' input format is fully text based. Each line
227+
has this format:
228+
229+
[<base-commit> -- ]<branch1> <branch2>
230+
231+
If one line is separated by `--`, the string before the separator is
232+
used for specifying a merge-base for the merge and the string after
233+
the separator describes the branches to be merged.
234+
219235
MISTAKES TO AVOID
220236
-----------------
221237

builtin/merge-tree.c

Lines changed: 53 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
#include "tree-walk.h"
44
#include "xdiff-interface.h"
55
#include "help.h"
6+
#include "commit.h"
67
#include "commit-reach.h"
78
#include "merge-ort.h"
89
#include "object-store.h"
@@ -406,6 +407,7 @@ struct merge_tree_options {
406407
};
407408

408409
static int real_merge(struct merge_tree_options *o,
410+
const char *merge_base,
409411
const char *branch1, const char *branch2,
410412
const char *prefix)
411413
{
@@ -432,16 +434,31 @@ static int real_merge(struct merge_tree_options *o,
432434
opt.branch1 = branch1;
433435
opt.branch2 = branch2;
434436

435-
/*
436-
* Get the merge bases, in reverse order; see comment above
437-
* merge_incore_recursive in merge-ort.h
438-
*/
439-
merge_bases = get_merge_bases(parent1, parent2);
440-
if (!merge_bases && !o->allow_unrelated_histories)
441-
die(_("refusing to merge unrelated histories"));
442-
merge_bases = reverse_commit_list(merge_bases);
437+
if (merge_base) {
438+
struct commit *base_commit;
439+
struct tree *base_tree, *parent1_tree, *parent2_tree;
440+
441+
base_commit = lookup_commit_reference_by_name(merge_base);
442+
if (!base_commit)
443+
die(_("could not lookup commit %s"), merge_base);
444+
445+
opt.ancestor = merge_base;
446+
base_tree = get_commit_tree(base_commit);
447+
parent1_tree = get_commit_tree(parent1);
448+
parent2_tree = get_commit_tree(parent2);
449+
merge_incore_nonrecursive(&opt, base_tree, parent1_tree, parent2_tree, &result);
450+
} else {
451+
/*
452+
* Get the merge bases, in reverse order; see comment above
453+
* merge_incore_recursive in merge-ort.h
454+
*/
455+
merge_bases = get_merge_bases(parent1, parent2);
456+
if (!merge_bases && !o->allow_unrelated_histories)
457+
die(_("refusing to merge unrelated histories"));
458+
merge_bases = reverse_commit_list(merge_bases);
459+
merge_incore_recursive(&opt, merge_bases, parent1, parent2, &result);
460+
}
443461

444-
merge_incore_recursive(&opt, merge_bases, parent1, parent2, &result);
445462
if (result.clean < 0)
446463
die(_("failure to merge"));
447464

@@ -487,6 +504,7 @@ int cmd_merge_tree(int argc, const char **argv, const char *prefix)
487504
struct merge_tree_options o = { .show_messages = -1 };
488505
int expected_remaining_argc;
489506
int original_argc;
507+
const char *merge_base = NULL;
490508

491509
const char * const merge_tree_usage[] = {
492510
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)
515533
&o.use_stdin,
516534
N_("perform multiple merges, one per line of input"),
517535
PARSE_OPT_NONEG),
536+
OPT_STRING(0, "merge-base",
537+
&merge_base,
538+
N_("commit"),
539+
N_("specify a merge-base for the merge")),
518540
OPT_END()
519541
};
520542

@@ -529,16 +551,35 @@ int cmd_merge_tree(int argc, const char **argv, const char *prefix)
529551

530552
if (o.mode == MODE_TRIVIAL)
531553
die(_("--trivial-merge is incompatible with all other options"));
554+
if (merge_base)
555+
die(_("--merge-base is incompatible with --stdin"));
532556
line_termination = '\0';
533557
while (strbuf_getline_lf(&buf, stdin) != EOF) {
534558
struct strbuf **split;
535559
int result;
560+
const char *input_merge_base = NULL;
536561

537562
split = strbuf_split(&buf, ' ');
538-
if (!split[0] || !split[1] || split[2])
563+
if (!split[0] || !split[1])
539564
die(_("malformed input line: '%s'."), buf.buf);
540565
strbuf_rtrim(split[0]);
541-
result = real_merge(&o, split[0]->buf, split[1]->buf, prefix);
566+
strbuf_rtrim(split[1]);
567+
568+
/* parse the merge-base */
569+
if (!strcmp(split[1]->buf, "--")) {
570+
input_merge_base = split[0]->buf;
571+
}
572+
573+
if (input_merge_base && split[2] && split[3] && !split[4]) {
574+
strbuf_rtrim(split[2]);
575+
strbuf_rtrim(split[3]);
576+
result = real_merge(&o, input_merge_base, split[2]->buf, split[3]->buf, prefix);
577+
} else if (!input_merge_base && !split[2]) {
578+
result = real_merge(&o, NULL, split[0]->buf, split[1]->buf, prefix);
579+
} else {
580+
die(_("malformed input line: '%s'."), buf.buf);
581+
}
582+
542583
if (result < 0)
543584
die(_("merging cannot continue; got unclean result of %d"), result);
544585
strbuf_list_free(split);
@@ -581,7 +622,7 @@ int cmd_merge_tree(int argc, const char **argv, const char *prefix)
581622

582623
/* Do the relevant type of merge */
583624
if (o.mode == MODE_REAL)
584-
return real_merge(&o, argv[0], argv[1], prefix);
625+
return real_merge(&o, merge_base, argv[0], argv[1], prefix);
585626
else
586627
return trivial_merge(argv[0], argv[1], argv[2]);
587628
}

t/t4301-merge-tree-write-tree.sh

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -860,4 +860,66 @@ test_expect_success '--stdin with both a successful and a conflicted merge' '
860860
test_cmp expect actual
861861
'
862862

863+
864+
test_expect_success '--merge-base is incompatible with --stdin' '
865+
test_must_fail git merge-tree --merge-base=side1 --stdin 2>expect &&
866+
867+
grep "^fatal: --merge-base is incompatible with --stdin" expect
868+
'
869+
870+
# specify merge-base as parent of branch2
871+
# git merge-tree --write-tree --merge-base=c2 c1 c3
872+
# Commit c1: add file1
873+
# Commit c2: add file2 after c1
874+
# Commit c3: add file3 after c2
875+
# Expected: add file3, and file2 does NOT appear
876+
877+
test_expect_success 'specify merge-base as parent of branch2' '
878+
# Setup
879+
test_when_finished "rm -rf base-b2-p" &&
880+
git init base-b2-p &&
881+
test_commit -C base-b2-p c1 file1 &&
882+
test_commit -C base-b2-p c2 file2 &&
883+
test_commit -C base-b2-p c3 file3 &&
884+
885+
# Testing
886+
TREE_OID=$(git -C base-b2-p merge-tree --write-tree --merge-base=c2 c1 c3) &&
887+
888+
q_to_tab <<-EOF >expect &&
889+
100644 blob $(git -C base-b2-p rev-parse c1:file1)Qfile1
890+
100644 blob $(git -C base-b2-p rev-parse c3:file3)Qfile3
891+
EOF
892+
893+
git -C base-b2-p ls-tree $TREE_OID >actual &&
894+
test_cmp expect actual
895+
'
896+
897+
# Since the earlier tests have verified that individual merge-tree calls
898+
# are doing the right thing, this test case is only used to verify that
899+
# we can also trigger merges via --stdin, and that when we do we get
900+
# the same answer as running a bunch of separate merges.
901+
902+
test_expect_success 'check the input format when --stdin is passed' '
903+
test_when_finished "rm -rf repo" &&
904+
git init repo &&
905+
test_commit -C repo c1 &&
906+
test_commit -C repo c2 &&
907+
test_commit -C repo c3 &&
908+
printf "c1 c3\nc2 -- c1 c3\nc2 c3" | git -C repo merge-tree --stdin >actual &&
909+
910+
printf "1\0" >expect &&
911+
git -C repo merge-tree --write-tree -z c1 c3 >>expect &&
912+
printf "\0" >>expect &&
913+
914+
printf "1\0" >>expect &&
915+
git -C repo merge-tree --write-tree -z --merge-base=c2 c1 c3 >>expect &&
916+
printf "\0" >>expect &&
917+
918+
printf "1\0" >>expect &&
919+
git -C repo merge-tree --write-tree -z c2 c3 >>expect &&
920+
printf "\0" >>expect &&
921+
922+
test_cmp expect actual
923+
'
924+
863925
test_done

0 commit comments

Comments
 (0)