Skip to content

Commit

Permalink
Merge branch 'jc/merge-bases'
Browse files Browse the repository at this point in the history
Optimise the "merge-base" computation a bit, and also update its
users that do not need the full merge-base information to call a
cheaper subset.

* jc/merge-bases:
  reduce_heads(): reimplement on top of remove_redundant()
  merge-base: "--is-ancestor A B"
  get_merge_bases_many(): walk from many tips in parallel
  in_merge_bases(): use paint_down_to_common()
  merge_bases_many(): split out the logic to paint history
  in_merge_bases(): omit unnecessary redundant common ancestor reduction
  http-push: use in_merge_bases() for fast-forward check
  receive-pack: use in_merge_bases() for fast-forward check
  in_merge_bases(): support only one "other" commit
  • Loading branch information
Junio C Hamano committed Sep 11, 2012
2 parents 0083f1d + f37d3c7 commit 34f5130
Show file tree
Hide file tree
Showing 11 changed files with 184 additions and 112 deletions.
28 changes: 28 additions & 0 deletions Documentation/git-merge-base.txt
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ SYNOPSIS
[verse]
'git merge-base' [-a|--all] <commit> <commit>...
'git merge-base' [-a|--all] --octopus <commit>...
'git merge-base' --is-ancestor <commit> <commit>
'git merge-base' --independent <commit>...

DESCRIPTION
Expand Down Expand Up @@ -50,6 +51,12 @@ from linkgit:git-show-branch[1] when used with the `--merge-base` option.
from any other. This mimics the behavior of 'git show-branch
--independent'.

--is-ancestor::
Check if the first <commit> is an ancestor of the second <commit>,
and exit with status 0 if true, or with status 1 if not.
Errors are signaled by a non-zero status that is not 1.


OPTIONS
-------
-a::
Expand Down Expand Up @@ -110,6 +117,27 @@ both '1' and '2' are merge-bases of A and B. Neither one is better than
the other (both are 'best' merge bases). When the `--all` option is not given,
it is unspecified which best one is output.

A common idiom to check "fast-forward-ness" between two commits A
and B is (or at least used to be) to compute the merge base between
A and B, and check if it is the same as A, in which case, A is an
ancestor of B. You will see this idiom used often in older scripts.

A=$(git rev-parse --verify A)
if test "$A" = "$(git merge-base A B)"
then
... A is an ancestor of B ...
fi

In modern git, you can say this in a more direct way:

if git merge-base --is-ancestor A B
then
... A is an ancestor of B ...
fi

instead.


See also
--------
linkgit:git-rev-list[1],
Expand Down
4 changes: 2 additions & 2 deletions builtin/branch.c
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ static int branch_merged(int kind, const char *name,
if (!reference_rev)
reference_rev = head_rev;

merged = in_merge_bases(rev, &reference_rev, 1);
merged = in_merge_bases(rev, reference_rev);

/*
* After the safety valve is fully redefined to "check with
Expand All @@ -140,7 +140,7 @@ static int branch_merged(int kind, const char *name,
* a gentle reminder is in order.
*/
if ((head_rev != reference_rev) &&
in_merge_bases(rev, &head_rev, 1) != merged) {
in_merge_bases(rev, head_rev) != merged) {
if (merged)
warning(_("deleting branch '%s' that has been merged to\n"
" '%s', but not yet merged to HEAD."),
Expand Down
2 changes: 1 addition & 1 deletion builtin/fetch.c
Original file line number Diff line number Diff line change
Expand Up @@ -323,7 +323,7 @@ static int update_local_ref(struct ref *ref,
return r;
}

if (in_merge_bases(current, &updated, 1)) {
if (in_merge_bases(current, updated)) {
char quickref[83];
int r;
strcpy(quickref, find_unique_abbrev(current->object.sha1, DEFAULT_ABBREV));
Expand Down
22 changes: 22 additions & 0 deletions builtin/merge-base.c
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ static const char * const merge_base_usage[] = {
N_("git merge-base [-a|--all] <commit> <commit>..."),
N_("git merge-base [-a|--all] --octopus <commit>..."),
N_("git merge-base --independent <commit>..."),
N_("git merge-base --is-ancestor <commit> <commit>"),
NULL
};

Expand Down Expand Up @@ -70,25 +71,46 @@ static int handle_octopus(int count, const char **args, int reduce, int show_all
return 0;
}

static int handle_is_ancestor(int argc, const char **argv)
{
struct commit *one, *two;

if (argc != 2)
die("--is-ancestor takes exactly two commits");
one = get_commit_reference(argv[0]);
two = get_commit_reference(argv[1]);
if (in_merge_bases(one, two))
return 0;
else
return 1;
}

int cmd_merge_base(int argc, const char **argv, const char *prefix)
{
struct commit **rev;
int rev_nr = 0;
int show_all = 0;
int octopus = 0;
int reduce = 0;
int is_ancestor = 0;

struct option options[] = {
OPT_BOOLEAN('a', "all", &show_all, N_("output all common ancestors")),
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,
N_("is the first one ancestor of the other?")),
OPT_END()
};

git_config(git_default_config, NULL);
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))
die("--is-ancestor cannot be used with other options");
if (is_ancestor)
return handle_is_ancestor(argc, argv);
if (reduce && (show_all || octopus))
die("--independent cannot be used with other options");

Expand Down
8 changes: 1 addition & 7 deletions builtin/receive-pack.c
Original file line number Diff line number Diff line change
Expand Up @@ -480,7 +480,6 @@ static const char *update(struct command *cmd)
!prefixcmp(name, "refs/heads/")) {
struct object *old_object, *new_object;
struct commit *old_commit, *new_commit;
struct commit_list *bases, *ent;

old_object = parse_object(old_sha1);
new_object = parse_object(new_sha1);
Expand All @@ -493,12 +492,7 @@ static const char *update(struct command *cmd)
}
old_commit = (struct commit *)old_object;
new_commit = (struct commit *)new_object;
bases = get_merge_bases(old_commit, new_commit, 1);
for (ent = bases; ent; ent = ent->next)
if (!hashcmp(old_sha1, ent->item->object.sha1))
break;
free_commit_list(bases);
if (!ent) {
if (!in_merge_bases(old_commit, new_commit)) {
rp_error("denying non-fast-forward %s"
" (you should pull first)", name);
return "non-fast-forward";
Expand Down
Loading

0 comments on commit 34f5130

Please sign in to comment.