diff --git a/Documentation/git-rev-list.txt b/Documentation/git-rev-list.txt index ad6d14c55a..6c370e1bef 100644 --- a/Documentation/git-rev-list.txt +++ b/Documentation/git-rev-list.txt @@ -15,6 +15,7 @@ SYNOPSIS [ \--sparse ] [ \--no-merges ] [ \--remove-empty ] + [ \--not ] [ \--all ] [ \--topo-order ] [ \--parents ] @@ -37,6 +38,14 @@ not in 'baz'". A special notation .. can be used as a short-hand for {caret} . +Another special notation is ... which is useful for +merges. The resulting set of commits is the symmetric difference +between the two operands. The following two commands are equivalent: + +------------ +$ git-rev-list A B --not $(git-merge-base --all A B) +$ git-rev-list A...B +------------ OPTIONS ------- @@ -93,6 +102,11 @@ OPTIONS --remove-empty:: Stop when a given path disappears from the tree. +--not:: + Reverses the meaning of the '{caret}' prefix (or lack + thereof) for all following revision specifiers, up to + the next `--not`. + --all:: Pretend as if all the refs in `$GIT_DIR/refs/` are listed on the command line as . diff --git a/commit.c b/commit.c index 0431027ef2..a608faf232 100644 --- a/commit.c +++ b/commit.c @@ -397,13 +397,12 @@ void clear_commit_marks(struct commit *commit, unsigned int mark) { struct commit_list *parents; + if (!commit) + return; parents = commit->parents; commit->object.flags &= ~mark; while (parents) { - struct commit *parent = parents->item; - if (parent && parent->object.parsed && - (parent->object.flags & mark)) - clear_commit_marks(parent, mark); + clear_commit_marks(parents->item, mark); parents = parents->next; } } @@ -849,16 +848,17 @@ void sort_in_topological_order_fn(struct commit_list ** list, int lifo, /* merge-rebase stuff */ -#define PARENT1 1 -#define PARENT2 2 -#define UNINTERESTING 4 +/* bits #0..7 in revision.h */ +#define PARENT1 (1u<< 8) +#define PARENT2 (1u<< 9) +#define STALE (1u<<10) static struct commit *interesting(struct commit_list *list) { while (list) { struct commit *commit = list->item; list = list->next; - if (commit->object.flags & UNINTERESTING) + if (commit->object.flags & STALE) continue; return commit; } @@ -920,17 +920,17 @@ static struct commit *interesting(struct commit_list *list) * * Next, we pop B and something very interesting happens. It has flags==3 * so it is also placed on the result list, and its parents are marked - * uninteresting, retroactively, and placed back on the list: + * stale, retroactively, and placed back on the list: * * list=C(7), result=C(7) B(3) * * Now, list does not have any interesting commit. So we find the newest - * commit from the result list that is not marked uninteresting. Which is + * commit from the result list that is not marked stale. Which is * commit B. * * * Another pathological example how this thing used to fail to mark an - * ancestor of a merge base as UNINTERESTING before we introduced the + * ancestor of a merge base as STALE before we introduced the * postprocessing phase (mark_reachable_commits). * * 2 @@ -960,8 +960,8 @@ static struct commit *interesting(struct commit_list *list) * C7 2 3 7 1 3 2 1 2 * * At this point, unfortunately, everybody in the list is - * uninteresting, so we fail to complete the following two - * steps to fully marking uninteresting commits. + * stale, so we fail to complete the following two + * steps to fully marking stale commits. * * D7 2 3 7 7 3 2 1 2 * E7 2 3 7 7 7 2 1 2 @@ -981,10 +981,10 @@ static void mark_reachable_commits(struct commit_list *result, */ for (tmp = result; tmp; tmp = tmp->next) { struct commit *c = tmp->item; - /* Reinject uninteresting ones to list, + /* Reinject stale ones to list, * so we can scan their parents. */ - if (c->object.flags & UNINTERESTING) + if (c->object.flags & STALE) commit_list_insert(c, &list); } while (list) { @@ -995,8 +995,8 @@ static void mark_reachable_commits(struct commit_list *result, list = list->next; free(tmp); - /* Anything taken out of the list is uninteresting, so - * mark all its parents uninteresting. We do not + /* Anything taken out of the list is stale, so + * mark all its parents stale. We do not * parse new ones (we already parsed all the relevant * ones). */ @@ -1004,15 +1004,16 @@ static void mark_reachable_commits(struct commit_list *result, while (parents) { struct commit *p = parents->item; parents = parents->next; - if (!(p->object.flags & UNINTERESTING)) { - p->object.flags |= UNINTERESTING; + if (!(p->object.flags & STALE)) { + p->object.flags |= STALE; commit_list_insert(p, &list); } } } } -struct commit_list *get_merge_bases(struct commit *rev1, struct commit *rev2) +struct commit_list *get_merge_bases(struct commit *rev1, struct commit *rev2, + int cleanup) { struct commit_list *list = NULL; struct commit_list *result = NULL; @@ -1033,7 +1034,7 @@ struct commit_list *get_merge_bases(struct commit *rev1, struct commit *rev2) struct commit *commit = list->item; struct commit_list *parents; int flags = commit->object.flags - & (PARENT1 | PARENT2 | UNINTERESTING); + & (PARENT1 | PARENT2 | STALE); tmp = list; list = list->next; @@ -1041,8 +1042,8 @@ struct commit_list *get_merge_bases(struct commit *rev1, struct commit *rev2) if (flags == (PARENT1 | PARENT2)) { insert_by_date(commit, &result); - /* Mark parents of a found merge uninteresting */ - flags |= UNINTERESTING; + /* Mark parents of a found merge stale */ + flags |= STALE; } parents = commit->parents; while (parents) { @@ -1066,7 +1067,7 @@ struct commit_list *get_merge_bases(struct commit *rev1, struct commit *rev2) for (tmp = result, list = NULL; tmp; ) { struct commit *commit = tmp->item; struct commit_list *next = tmp->next; - if (commit->object.flags & UNINTERESTING) { + if (commit->object.flags & STALE) { if (list != NULL) list->next = next; free(tmp); @@ -1074,15 +1075,16 @@ struct commit_list *get_merge_bases(struct commit *rev1, struct commit *rev2) if (list == NULL) result = tmp; list = tmp; - commit->object.flags |= UNINTERESTING; + commit->object.flags |= STALE; } tmp = next; } - /* reset flags */ - clear_commit_marks(rev1, PARENT1 | PARENT2 | UNINTERESTING); - clear_commit_marks(rev2, PARENT1 | PARENT2 | UNINTERESTING); + if (cleanup) { + clear_commit_marks(rev1, PARENT1 | PARENT2 | STALE); + clear_commit_marks(rev2, PARENT1 | PARENT2 | STALE); + } return result; } diff --git a/commit.h b/commit.h index 89b9dad7cc..779ed82ed0 100644 --- a/commit.h +++ b/commit.h @@ -105,6 +105,6 @@ struct commit_graft *read_graft_line(char *buf, int len); int register_commit_graft(struct commit_graft *, int); int read_graft_file(const char *graft_file); -extern struct commit_list *get_merge_bases(struct commit *rev1, struct commit *rev2); +extern struct commit_list *get_merge_bases(struct commit *rev1, struct commit *rev2, int cleanup); #endif /* COMMIT_H */ diff --git a/merge-base.c b/merge-base.c index b41f76cb72..59f723f404 100644 --- a/merge-base.c +++ b/merge-base.c @@ -6,7 +6,7 @@ static int show_all = 0; static int merge_base(struct commit *rev1, struct commit *rev2) { - struct commit_list *result = get_merge_bases(rev1, rev2); + struct commit_list *result = get_merge_bases(rev1, rev2, 0); if (!result) return 1; diff --git a/revision.c b/revision.c index ebee05a698..7865fa4968 100644 --- a/revision.c +++ b/revision.c @@ -537,6 +537,18 @@ void init_revisions(struct rev_info *revs) diff_setup(&revs->diffopt); } +static void add_pending_commit_list(struct rev_info *revs, + struct commit_list *commit_list, + unsigned int flags) +{ + while (commit_list) { + struct object *object = &commit_list->item->object; + object->flags |= flags; + add_pending_object(revs, object, sha1_to_hex(object->sha1)); + commit_list = commit_list->next; + } +} + /* * Parse revision information, filling in the "rev_info" structure, * and removing the used arguments from the argument list. @@ -772,27 +784,45 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, const ch unsigned char from_sha1[20]; const char *next = dotdot + 2; const char *this = arg; + int symmetric = *next == '.'; + unsigned int flags_exclude = flags ^ UNINTERESTING; + *dotdot = 0; + next += symmetric; + if (!*next) next = "HEAD"; if (dotdot == arg) this = "HEAD"; if (!get_sha1(this, from_sha1) && !get_sha1(next, sha1)) { - struct object *exclude; - struct object *include; + struct commit *a, *b; + struct commit_list *exclude; - exclude = get_reference(revs, this, from_sha1, flags ^ UNINTERESTING); - include = get_reference(revs, next, sha1, flags); - if (!exclude || !include) - die("Invalid revision range %s..%s", arg, next); + a = lookup_commit_reference(from_sha1); + b = lookup_commit_reference(sha1); + if (!a || !b) { + die(symmetric ? + "Invalid symmetric difference expression %s...%s" : + "Invalid revision range %s..%s", + arg, next); + } if (!seen_dashdash) { *dotdot = '.'; verify_non_filename(revs->prefix, arg); } - add_pending_object(revs, exclude, this); - add_pending_object(revs, include, next); + + if (symmetric) { + exclude = get_merge_bases(a, b, 1); + add_pending_commit_list(revs, exclude, + flags_exclude); + a->object.flags |= flags; + } else + a->object.flags |= flags_exclude; + b->object.flags |= flags; + add_pending_object(revs, &a->object, this); + add_pending_object(revs, &b->object, next); continue; } *dotdot = '.';