mirror of
https://github.com/git/git.git
synced 2024-11-23 18:05:29 +08:00
Merge branch 'jc/merge-bases'
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
This commit is contained in:
commit
34f5130af8
@ -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
|
||||
@ -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::
|
||||
@ -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],
|
||||
|
@ -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
|
||||
@ -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."),
|
||||
|
@ -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));
|
||||
|
@ -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
|
||||
};
|
||||
|
||||
@ -70,6 +71,20 @@ 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;
|
||||
@ -77,11 +92,14 @@ int cmd_merge_base(int argc, const char **argv, const char *prefix)
|
||||
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()
|
||||
};
|
||||
|
||||
@ -89,6 +107,10 @@ 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))
|
||||
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");
|
||||
|
||||
|
@ -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);
|
||||
@ -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";
|
||||
|
213
commit.c
213
commit.c
@ -607,28 +607,12 @@ static struct commit *interesting(struct commit_list *list)
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static struct commit_list *merge_bases_many(struct commit *one, int n, struct commit **twos)
|
||||
static struct commit_list *paint_down_to_common(struct commit *one, int n, struct commit **twos)
|
||||
{
|
||||
struct commit_list *list = NULL;
|
||||
struct commit_list *result = NULL;
|
||||
int i;
|
||||
|
||||
for (i = 0; i < n; i++) {
|
||||
if (one == twos[i])
|
||||
/*
|
||||
* We do not mark this even with RESULT so we do not
|
||||
* have to clean it up.
|
||||
*/
|
||||
return commit_list_insert(one, &result);
|
||||
}
|
||||
|
||||
if (parse_commit(one))
|
||||
return NULL;
|
||||
for (i = 0; i < n; i++) {
|
||||
if (parse_commit(twos[i]))
|
||||
return NULL;
|
||||
}
|
||||
|
||||
one->object.flags |= PARENT1;
|
||||
commit_list_insert_by_date(one, &list);
|
||||
for (i = 0; i < n; i++) {
|
||||
@ -669,9 +653,34 @@ static struct commit_list *merge_bases_many(struct commit *one, int n, struct co
|
||||
}
|
||||
}
|
||||
|
||||
/* Clean up the result to remove stale ones */
|
||||
free_commit_list(list);
|
||||
list = result; result = NULL;
|
||||
return result;
|
||||
}
|
||||
|
||||
static struct commit_list *merge_bases_many(struct commit *one, int n, struct commit **twos)
|
||||
{
|
||||
struct commit_list *list = NULL;
|
||||
struct commit_list *result = NULL;
|
||||
int i;
|
||||
|
||||
for (i = 0; i < n; i++) {
|
||||
if (one == twos[i])
|
||||
/*
|
||||
* We do not mark this even with RESULT so we do not
|
||||
* have to clean it up.
|
||||
*/
|
||||
return commit_list_insert(one, &result);
|
||||
}
|
||||
|
||||
if (parse_commit(one))
|
||||
return NULL;
|
||||
for (i = 0; i < n; i++) {
|
||||
if (parse_commit(twos[i]))
|
||||
return NULL;
|
||||
}
|
||||
|
||||
list = paint_down_to_common(one, n, twos);
|
||||
|
||||
while (list) {
|
||||
struct commit_list *next = list->next;
|
||||
if (!(list->item->object.flags & STALE))
|
||||
@ -709,6 +718,60 @@ struct commit_list *get_octopus_merge_bases(struct commit_list *in)
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int remove_redundant(struct commit **array, int cnt)
|
||||
{
|
||||
/*
|
||||
* Some commit in the array may be an ancestor of
|
||||
* another commit. Move such commit to the end of
|
||||
* the array, and return the number of commits that
|
||||
* are independent from each other.
|
||||
*/
|
||||
struct commit **work;
|
||||
unsigned char *redundant;
|
||||
int *filled_index;
|
||||
int i, j, filled;
|
||||
|
||||
work = xcalloc(cnt, sizeof(*work));
|
||||
redundant = xcalloc(cnt, 1);
|
||||
filled_index = xmalloc(sizeof(*filled_index) * (cnt - 1));
|
||||
|
||||
for (i = 0; i < cnt; i++) {
|
||||
struct commit_list *common;
|
||||
|
||||
if (redundant[i])
|
||||
continue;
|
||||
for (j = filled = 0; j < cnt; j++) {
|
||||
if (i == j || redundant[j])
|
||||
continue;
|
||||
filled_index[filled] = j;
|
||||
work[filled++] = array[j];
|
||||
}
|
||||
common = paint_down_to_common(array[i], filled, work);
|
||||
if (array[i]->object.flags & PARENT2)
|
||||
redundant[i] = 1;
|
||||
for (j = 0; j < filled; j++)
|
||||
if (work[j]->object.flags & PARENT1)
|
||||
redundant[filled_index[j]] = 1;
|
||||
clear_commit_marks(array[i], all_flags);
|
||||
for (j = 0; j < filled; j++)
|
||||
clear_commit_marks(work[j], all_flags);
|
||||
free_commit_list(common);
|
||||
}
|
||||
|
||||
/* Now collect the result */
|
||||
memcpy(work, array, sizeof(*array) * cnt);
|
||||
for (i = filled = 0; i < cnt; i++)
|
||||
if (!redundant[i])
|
||||
array[filled++] = work[i];
|
||||
for (j = filled, i = 0; i < cnt; i++)
|
||||
if (redundant[i])
|
||||
array[j++] = work[i];
|
||||
free(work);
|
||||
free(redundant);
|
||||
free(filled_index);
|
||||
return filled;
|
||||
}
|
||||
|
||||
struct commit_list *get_merge_bases_many(struct commit *one,
|
||||
int n,
|
||||
struct commit **twos,
|
||||
@ -717,7 +780,7 @@ struct commit_list *get_merge_bases_many(struct commit *one,
|
||||
struct commit_list *list;
|
||||
struct commit **rslt;
|
||||
struct commit_list *result;
|
||||
int cnt, i, j;
|
||||
int cnt, i;
|
||||
|
||||
result = merge_bases_many(one, n, twos);
|
||||
for (i = 0; i < n; i++) {
|
||||
@ -748,28 +811,11 @@ struct commit_list *get_merge_bases_many(struct commit *one,
|
||||
clear_commit_marks(one, all_flags);
|
||||
for (i = 0; i < n; i++)
|
||||
clear_commit_marks(twos[i], all_flags);
|
||||
for (i = 0; i < cnt - 1; i++) {
|
||||
for (j = i+1; j < cnt; j++) {
|
||||
if (!rslt[i] || !rslt[j])
|
||||
continue;
|
||||
result = merge_bases_many(rslt[i], 1, &rslt[j]);
|
||||
clear_commit_marks(rslt[i], all_flags);
|
||||
clear_commit_marks(rslt[j], all_flags);
|
||||
for (list = result; list; list = list->next) {
|
||||
if (rslt[i] == list->item)
|
||||
rslt[i] = NULL;
|
||||
if (rslt[j] == list->item)
|
||||
rslt[j] = NULL;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Surviving ones in rslt[] are the independent results */
|
||||
cnt = remove_redundant(rslt, cnt);
|
||||
result = NULL;
|
||||
for (i = 0; i < cnt; i++) {
|
||||
if (rslt[i])
|
||||
commit_list_insert_by_date(rslt[i], &result);
|
||||
}
|
||||
for (i = 0; i < cnt; i++)
|
||||
commit_list_insert_by_date(rslt[i], &result);
|
||||
free(rslt);
|
||||
return result;
|
||||
}
|
||||
@ -780,6 +826,9 @@ struct commit_list *get_merge_bases(struct commit *one, struct commit *two,
|
||||
return get_merge_bases_many(one, 1, &two, cleanup);
|
||||
}
|
||||
|
||||
/*
|
||||
* Is "commit" a decendant of one of the elements on the "with_commit" list?
|
||||
*/
|
||||
int is_descendant_of(struct commit *commit, struct commit_list *with_commit)
|
||||
{
|
||||
if (!with_commit)
|
||||
@ -789,28 +838,28 @@ int is_descendant_of(struct commit *commit, struct commit_list *with_commit)
|
||||
|
||||
other = with_commit->item;
|
||||
with_commit = with_commit->next;
|
||||
if (in_merge_bases(other, &commit, 1))
|
||||
if (in_merge_bases(other, commit))
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int in_merge_bases(struct commit *commit, struct commit **reference, int num)
|
||||
/*
|
||||
* Is "commit" an ancestor of (i.e. reachable from) the "reference"?
|
||||
*/
|
||||
int in_merge_bases(struct commit *commit, struct commit *reference)
|
||||
{
|
||||
struct commit_list *bases, *b;
|
||||
struct commit_list *bases;
|
||||
int ret = 0;
|
||||
|
||||
if (num == 1)
|
||||
bases = get_merge_bases(commit, *reference, 1);
|
||||
else
|
||||
die("not yet");
|
||||
for (b = bases; b; b = b->next) {
|
||||
if (!hashcmp(commit->object.sha1, b->item->object.sha1)) {
|
||||
ret = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (parse_commit(commit) || parse_commit(reference))
|
||||
return ret;
|
||||
|
||||
bases = paint_down_to_common(commit, 1, &reference);
|
||||
if (commit->object.flags & PARENT2)
|
||||
ret = 1;
|
||||
clear_commit_marks(commit, all_flags);
|
||||
clear_commit_marks(reference, all_flags);
|
||||
free_commit_list(bases);
|
||||
return ret;
|
||||
}
|
||||
@ -819,51 +868,31 @@ struct commit_list *reduce_heads(struct commit_list *heads)
|
||||
{
|
||||
struct commit_list *p;
|
||||
struct commit_list *result = NULL, **tail = &result;
|
||||
struct commit **other;
|
||||
size_t num_head, num_other;
|
||||
struct commit **array;
|
||||
int num_head, i;
|
||||
|
||||
if (!heads)
|
||||
return NULL;
|
||||
|
||||
/* Avoid unnecessary reallocations */
|
||||
for (p = heads, num_head = 0; p; p = p->next)
|
||||
num_head++;
|
||||
other = xcalloc(sizeof(*other), num_head);
|
||||
|
||||
/* For each commit, see if it can be reached by others */
|
||||
for (p = heads; p; p = p->next) {
|
||||
struct commit_list *q, *base;
|
||||
|
||||
/* Do we already have this in the result? */
|
||||
for (q = result; q; q = q->next)
|
||||
if (p->item == q->item)
|
||||
break;
|
||||
if (q)
|
||||
/* Uniquify */
|
||||
for (p = heads; p; p = p->next)
|
||||
p->item->object.flags &= ~STALE;
|
||||
for (p = heads, num_head = 0; p; p = p->next) {
|
||||
if (p->item->object.flags & STALE)
|
||||
continue;
|
||||
|
||||
num_other = 0;
|
||||
for (q = heads; q; q = q->next) {
|
||||
if (p->item == q->item)
|
||||
continue;
|
||||
other[num_other++] = q->item;
|
||||
}
|
||||
if (num_other)
|
||||
base = get_merge_bases_many(p->item, num_other, other, 1);
|
||||
else
|
||||
base = NULL;
|
||||
/*
|
||||
* If p->item does not have anything common with other
|
||||
* commits, there won't be any merge base. If it is
|
||||
* reachable from some of the others, p->item will be
|
||||
* the merge base. If its history is connected with
|
||||
* others, but p->item is not reachable by others, we
|
||||
* will get something other than p->item back.
|
||||
*/
|
||||
if (!base || (base->item != p->item))
|
||||
tail = &(commit_list_insert(p->item, tail)->next);
|
||||
free_commit_list(base);
|
||||
p->item->object.flags |= STALE;
|
||||
num_head++;
|
||||
}
|
||||
free(other);
|
||||
array = xcalloc(sizeof(*array), num_head);
|
||||
for (p = heads, i = 0; p; p = p->next) {
|
||||
if (p->item->object.flags & STALE) {
|
||||
array[i++] = p->item;
|
||||
p->item->object.flags &= ~STALE;
|
||||
}
|
||||
}
|
||||
num_head = remove_redundant(array, num_head);
|
||||
for (i = 0; i < num_head; i++)
|
||||
tail = &commit_list_insert(array[i], tail)->next;
|
||||
return result;
|
||||
}
|
||||
|
||||
|
2
commit.h
2
commit.h
@ -171,7 +171,7 @@ extern struct commit_list *get_shallow_commits(struct object_array *heads,
|
||||
int depth, int shallow_flag, int not_shallow_flag);
|
||||
|
||||
int is_descendant_of(struct commit *, struct commit_list *);
|
||||
int in_merge_bases(struct commit *, struct commit **, int);
|
||||
int in_merge_bases(struct commit *, struct commit *);
|
||||
|
||||
extern int interactive_add(int argc, const char **argv, const char *prefix, int patch);
|
||||
extern int run_add_interactive(const char *revision, const char *patch_mode,
|
||||
|
@ -96,7 +96,7 @@ static int update_local_ref(const char *name,
|
||||
strcpy(oldh, find_unique_abbrev(current->object.sha1, DEFAULT_ABBREV));
|
||||
strcpy(newh, find_unique_abbrev(sha1_new, DEFAULT_ABBREV));
|
||||
|
||||
if (in_merge_bases(current, &updated, 1)) {
|
||||
if (in_merge_bases(current, updated)) {
|
||||
fprintf(stderr, "* %s: fast-forward to %s\n",
|
||||
name, note);
|
||||
fprintf(stderr, " old..new: %s..%s\n", oldh, newh);
|
||||
|
@ -1691,7 +1691,7 @@ static int update_branch(struct branch *b)
|
||||
return error("Branch %s is missing commits.", b->name);
|
||||
}
|
||||
|
||||
if (!in_merge_bases(old_cmit, &new_cmit, 1)) {
|
||||
if (!in_merge_bases(old_cmit, new_cmit)) {
|
||||
unlock_ref(lock);
|
||||
warning("Not updating %s"
|
||||
" (new tip %s does not contain %s)",
|
||||
|
@ -1610,9 +1610,8 @@ static int verify_merge_base(unsigned char *head_sha1, struct ref *remote)
|
||||
{
|
||||
struct commit *head = lookup_commit_or_die(head_sha1, "HEAD");
|
||||
struct commit *branch = lookup_commit_or_die(remote->old_sha1, remote->name);
|
||||
struct commit_list *merge_bases = get_merge_bases(head, branch, 1);
|
||||
|
||||
return (merge_bases && !merge_bases->next && merge_bases->item == branch);
|
||||
return in_merge_bases(branch, head);
|
||||
}
|
||||
|
||||
static int delete_remote_branch(const char *pattern, int force)
|
||||
|
12
submodule.c
12
submodule.c
@ -788,7 +788,7 @@ static int find_first_merges(struct object_array *result, const char *path,
|
||||
die("revision walk setup failed");
|
||||
while ((commit = get_revision(&revs)) != NULL) {
|
||||
struct object *o = &(commit->object);
|
||||
if (in_merge_bases(b, &commit, 1))
|
||||
if (in_merge_bases(b, commit))
|
||||
add_object_array(o, NULL, &merges);
|
||||
}
|
||||
reset_revision_walk();
|
||||
@ -803,7 +803,7 @@ static int find_first_merges(struct object_array *result, const char *path,
|
||||
contains_another = 0;
|
||||
for (j = 0; j < merges.nr; j++) {
|
||||
struct commit *m2 = (struct commit *) merges.objects[j].item;
|
||||
if (i != j && in_merge_bases(m2, &m1, 1)) {
|
||||
if (i != j && in_merge_bases(m2, m1)) {
|
||||
contains_another = 1;
|
||||
break;
|
||||
}
|
||||
@ -865,18 +865,18 @@ int merge_submodule(unsigned char result[20], const char *path,
|
||||
}
|
||||
|
||||
/* check whether both changes are forward */
|
||||
if (!in_merge_bases(commit_base, &commit_a, 1) ||
|
||||
!in_merge_bases(commit_base, &commit_b, 1)) {
|
||||
if (!in_merge_bases(commit_base, commit_a) ||
|
||||
!in_merge_bases(commit_base, commit_b)) {
|
||||
MERGE_WARNING(path, "commits don't follow merge-base");
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Case #1: a is contained in b or vice versa */
|
||||
if (in_merge_bases(commit_a, &commit_b, 1)) {
|
||||
if (in_merge_bases(commit_a, commit_b)) {
|
||||
hashcpy(result, b);
|
||||
return 1;
|
||||
}
|
||||
if (in_merge_bases(commit_b, &commit_a, 1)) {
|
||||
if (in_merge_bases(commit_b, commit_a)) {
|
||||
hashcpy(result, a);
|
||||
return 1;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user