This is helpful when examining branches with disjoint roots, for example
because one is periodically merged into a subtree of the other.
With the --merge-child option, "git merge-base" will print a
first-parent ancestor of the first revision given, where the commit
printed is either a merge-base of the supplied revisions or a merge for
which one of its parents (not the first) is a merge-base.
For example, given the history:
A---C---G
\
B-----D---F
\
E
we have:
$ git merge-base F E
B
$ git merge-base --merge-child F E
D
$ git merge-base F G
C
$ git merge-base --merge-child F G
C
$ git log --left-right F...E
< F
< D
< C
< A
> E
$ git log --left-right F...E --not $(git merge-base --merge-child F E)
< F
> E
The git-log case is useful because it allows us to limit the range of
commits that we are examining for patch-identical changes when using
--cherry. For example with git-gui in git.git I know that anything
before the last merge of git-gui is not interesting:
$ time git log --cherry master...git-gui/master >/dev/null
real 0m32.731s
user 0m31.956s
sys 0m0.664s
$ time git log --cherry master...git-gui/master --not \
$(git merge-base --merge-child master git-gui/master) \
>/dev/null
real 0m2.296s
user 0m2.193s
sys 0m0.092s
Signed-off-by: John Keeping <[email protected]>
---
Documentation/git-merge-base.txt | 6 ++++
builtin/merge-base.c | 61 ++++++++++++++++++++++++++++++++++++++--
t/t6010-merge-base.sh | 25 ++++++++++++++--
3 files changed, 88 insertions(+), 4 deletions(-)
diff --git a/Documentation/git-merge-base.txt b/Documentation/git-merge-base.txt
index 87842e3..a1404e1 100644
--- a/Documentation/git-merge-base.txt
+++ b/Documentation/git-merge-base.txt
@@ -10,6 +10,7 @@ SYNOPSIS
--------
[verse]
'git merge-base' [-a|--all] <commit> <commit>...
+'git merge-base' [-a|--all] --merge-child <commit>...
'git merge-base' [-a|--all] --octopus <commit>...
'git merge-base' --is-ancestor <commit> <commit>
'git merge-base' --independent <commit>...
@@ -39,6 +40,11 @@ As a consequence, the 'merge base' is not necessarily
contained in each of the
commit arguments if more than two commits are specified. This is different
from linkgit:git-show-branch[1] when used with the `--merge-base` option.
+--merge-child::
+ Find the first-parent ancestor of the first commit that is either
+ reachable from all of the supplied commits or which has a parent that
+ is.
+
--octopus::
Compute the best common ancestors of all supplied commits,
in preparation for an n-way merge. This mimics the behavior
diff --git a/builtin/merge-base.c b/builtin/merge-base.c
index 1bc7991..0bf9f6d 100644
--- a/builtin/merge-base.c
+++ b/builtin/merge-base.c
@@ -1,7 +1,9 @@
#include "builtin.h"
#include "cache.h"
#include "commit.h"
+#include "diff.h"
#include "parse-options.h"
+#include "revision.h"
static int show_merge_base(struct commit **rev, int rev_nr, int show_all)
{
@@ -24,12 +26,61 @@ static int show_merge_base(struct commit **rev, int rev_nr,
int show_all)
static const char * const merge_base_usage[] = {
N_("git merge-base [-a|--all] <commit> <commit>..."),
+ N_("git merge-base [-a|--all] --merge-child <commit>..."),
N_("git merge-base [-a|--all] --octopus <commit>..."),
N_("git merge-base --independent <commit>..."),
N_("git merge-base --is-ancestor <commit> <commit>"),
NULL
};
+static int handle_merge_child(struct commit **rev, int rev_nr, const char
*prefix, int show_all)
+{
+ struct commit_list *merge_bases;
+ struct rev_info revs;
+ struct commit *commit;
+
+ merge_bases = get_merge_bases_many(rev[0], rev_nr - 1, rev + 1, 0);
+
+ if (!merge_bases)
+ return 1;
+
+ init_revisions(&revs, prefix);
+ revs.first_parent_only = 1;
+
+ clear_commit_marks(rev[0], SEEN | UNINTERESTING | SHOWN | ADDED);
+ add_pending_object(&revs, &rev[0]->object, "rev0");
+ if (prepare_revision_walk(&revs))
+ die(_("revision walk setup failed"));
+
+ while ((commit = get_revision(&revs)) != NULL) {
+ /*
+ * If a merge base is a first-parent ancestor of rev[0] then
+ * we print the commit itself instead of a merge which is its
+ * child.
+ */
+ if (commit_list_contains(merge_bases, commit)) {
+ printf("%s\n", sha1_to_hex(commit->object.sha1));
+ if (!show_all)
+ return 0;
+ }
+
+ struct commit_list *parent;
+ for (parent = commit->parents; parent; parent = parent->next) {
+ /* Skip the first parent */
+ if (parent == commit->parents)
+ continue;
+
+ if (commit_list_contains(merge_bases, parent->item)) {
+ printf("%s\n",
sha1_to_hex(commit->object.sha1));
+ if (!show_all)
+ return 0;
+ }
+ }
+ }
+
+ return 0;
+}
+
static struct commit *get_commit_reference(const char *arg)
{
unsigned char revkey[20];
@@ -93,9 +144,12 @@ int cmd_merge_base(int argc, const char **argv, const char
*prefix)
int octopus = 0;
int reduce = 0;
int is_ancestor = 0;
+ int merge_child = 0;
struct option options[] = {
OPT_BOOLEAN('a', "all", &show_all, N_("output all common
ancestors")),
+ OPT_BOOLEAN(0, "merge-child", &merge_child,
+ N_("find a merge with a parent reachable from
others")),
OPT_BOOLEAN(0, "octopus", &octopus, N_("find ancestors for a
single n-way merge")),
OPT_BOOLEAN(0, "independent", &reduce, N_("list revs not
reachable from others")),
OPT_BOOLEAN(0, "is-ancestor", &is_ancestor,
@@ -107,11 +161,11 @@ int cmd_merge_base(int argc, const char **argv, const
char *prefix)
argc = parse_options(argc, argv, prefix, options, merge_base_usage, 0);
if (!octopus && !reduce && argc < 2)
usage_with_options(merge_base_usage, options);
- if (is_ancestor && (show_all | octopus | reduce))
+ if (is_ancestor && (show_all | octopus | reduce | merge_child))
die("--is-ancestor cannot be used with other options");
if (is_ancestor)
return handle_is_ancestor(argc, argv);
- if (reduce && (show_all || octopus))
+ if (reduce && (show_all || octopus || merge_child))
die("--independent cannot be used with other options");
if (octopus || reduce)
@@ -120,5 +174,8 @@ int cmd_merge_base(int argc, const char **argv, const char
*prefix)
rev = xmalloc(argc * sizeof(*rev));
while (argc-- > 0)
rev[rev_nr++] = get_commit_reference(*argv++);
+
+ if (merge_child)
+ return handle_merge_child(rev, rev_nr, prefix, show_all);
return show_merge_base(rev, rev_nr, show_all);
}
diff --git a/t/t6010-merge-base.sh b/t/t6010-merge-base.sh
index f80bba8..454577e 100755
--- a/t/t6010-merge-base.sh
+++ b/t/t6010-merge-base.sh
@@ -42,12 +42,16 @@ test_expect_success 'setup' '
T=$(git mktree </dev/null)
'
-test_expect_success 'set up G and H' '
+test_expect_success 'set up G, H and L' '
# E---D---C---B---A
# \"-_ \ \
# \ `---------G \
# \ \
# F----------------H
+ # \
+ # I---------------------J---K
+ # \
+ # L
E=$(doit 5 E) &&
D=$(doit 4 D $E) &&
F=$(doit 6 F $E) &&
@@ -55,7 +59,11 @@ test_expect_success 'set up G and H' '
B=$(doit 2 B $C) &&
A=$(doit 1 A $B) &&
G=$(doit 7 G $B $E) &&
- H=$(doit 8 H $A $F)
+ H=$(doit 8 H $A $F) &&
+ I=$(doit 9 I) &&
+ J=$(doit 10 J $H $I) &&
+ K=$(doit 11 K $J) &&
+ L=$(doit 12 L $I)
'
test_expect_success 'merge-base G H' '
@@ -64,6 +72,9 @@ test_expect_success 'merge-base G H' '
MB=$(git merge-base G H) &&
git name-rev "$MB" >actual.single &&
+ MB=$(git merge-base --merge-child G H) &&
+ git name-rev "$MB" >actual.merge_child &&
+
MB=$(git merge-base --all G H) &&
git name-rev "$MB" >actual.all &&
@@ -71,6 +82,7 @@ test_expect_success 'merge-base G H' '
git name-rev "$MB" >actual.sb &&
test_cmp expected actual.single &&
+ test_cmp expected actual.merge_child &&
test_cmp expected actual.all &&
test_cmp expected actual.sb
'
@@ -95,6 +107,15 @@ test_expect_success 'merge-base/show-branch --independent' '
test_cmp expected2 actual2.sb
'
+test_expect_success 'merge-base --merge-child K L' '
+ git name-rev "$J" >expected &&
+
+ MB=$(git merge-base --merge-child K L) &&
+ git name-rev "$MB" >actual &&
+
+ test_cmp expected actual
+'
+
test_expect_success 'unsynchronized clocks' '
# This test is to demonstrate that relying on timestamps in a
distributed
# SCM to provide a _consistent_ partial ordering of commits leads to
--
1.8.3.rc1.289.gcb3647f
--
To unsubscribe from this list: send the line "unsubscribe git" in
the body of a message to [email protected]
More majordomo info at http://vger.kernel.org/majordomo-info.html