revision: allow --ancestry-path to take an argument
We have long allowed users to run e.g.
git log --ancestry-path master..seen
which shows all commits which satisfy all three of these criteria:
* are an ancestor of seen
* are not an ancestor of master
* have master as an ancestor
This commit allows another variant:
git log --ancestry-path=$TOPIC master..seen
which shows all commits which satisfy all of these criteria:
* are an ancestor of seen
* are not an ancestor of master
* have $TOPIC in their ancestry-path
that last bullet can be defined as commits meeting any of these
criteria:
* are an ancestor of $TOPIC
* have $TOPIC as an ancestor
* are $TOPIC
This also allows multiple --ancestry-path arguments, which can be
used to find commits with any of the given topics in their ancestry
path.
Signed-off-by: Elijah Newren <newren@gmail.com>
Acked-by: Derrick Stolee <derrickstolee@github.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
Elijah Newren committedAug 19, 2022 at 04:28 UTC257418c59040c13bfa839e01922e21833cda6a52
5 files changed+115-41
Documentation/rev-list-options.txt
+34-8
index 2f85726745..aed486dc30 100644--- a/Documentation/rev-list-options.txt+++ b/Documentation/rev-list-options.txt@@ -389,12 +389,14 @@ Default mode:: merges from the resulting history, as there are no selected commits contributing to this merge.---ancestry-path::+--ancestry-path[=<commit>]:: When given a range of commits to display (e.g. 'commit1..commit2'- or 'commit2 {caret}commit1'), only display commits that exist- directly on the ancestry chain between the 'commit1' and- 'commit2', i.e. commits that are both descendants of 'commit1',- and ancestors of 'commit2'.+ or 'commit2 {caret}commit1'), only display commits in that range+ that are ancestors of <commit>, descendants of <commit>, or+ <commit> itself. If no commit is specified, use 'commit1' (the+ excluded part of the range) as <commit>. Can be passed multiple+ times; if so, a commit is included if it is any of the commits+ given or if it is an ancestor or descendant of one of them. A more detailed explanation follows.@@ -568,11 +570,10 @@ Note the major differences in `N`, `P`, and `Q` over `--full-history`: There is another simplification mode available:---ancestry-path::- Limit the displayed commits to those directly on the ancestry- chain between the ``from'' and ``to'' commits in the given commit- range. I.e. only display commits that are ancestor of the ``to''- commit and descendants of the ``from'' commit.+--ancestry-path[=<commit>]::+ Limit the displayed commits to those which are an ancestor of+ <commit>, or which are a descendant of <commit>, or are <commit>+ itself. + As an example use case, consider the following commit history: +@@ -604,6 +605,29 @@ option does. Applied to the 'D..M' range, it results in: \ L--M -----------------------------------------------------------------------+++We can also use `--ancestry-path=D` instead of `--ancestry-path` which+means the same thing when applied to the 'D..M' range but is just more+explicit.+++If we instead are interested in a given topic within this range, and all+commits affected by that topic, we may only want to view the subset of+`D..M` which contain that topic in their ancestry path. So, using+`--ancestry-path=H D..M` for example would result in:+++-----------------------------------------------------------------------+ E+ \+ G---H---I---J+ \+ L--M+-----------------------------------------------------------------------+++Whereas `--ancestry-path=K D..M` would result in+++-----------------------------------------------------------------------+ K---------------L--M+----------------------------------------------------------------------- Before discussing another option, `--show-pulls`, we need to create a new example history.
index f4eee11cc8..ee702e498a 100644--- a/revision.c+++ b/revision.c@@ -1105,7 +1105,7 @@ static int process_parents(struct rev_info *revs, struct commit *commit, struct commit_list **list, struct prio_queue *queue) { struct commit_list *parent = commit->parents;- unsigned left_flag;+ unsigned pass_flags; if (commit->object.flags & ADDED) return 0;@@ -1160,7 +1160,7 @@ static int process_parents(struct rev_info *revs, struct commit *commit, if (revs->no_walk) return 0;- left_flag = (commit->object.flags & SYMMETRIC_LEFT);+ pass_flags = (commit->object.flags & (SYMMETRIC_LEFT | ANCESTRY_PATH)); for (parent = commit->parents; parent; parent = parent->next) { struct commit *p = parent->item;@@ -1181,7 +1181,7 @@ static int process_parents(struct rev_info *revs, struct commit *commit, if (!*slot) *slot = *revision_sources_at(revs->sources, commit); }- p->object.flags |= left_flag;+ p->object.flags |= pass_flags; if (!(p->object.flags & SEEN)) { p->object.flags |= (SEEN | NOT_USER_GIVEN); if (list)@@ -1304,13 +1304,24 @@ static int still_interesting(struct commit_list *src, timestamp_t date, int slop } /*- * "rev-list --ancestry-path A..B" computes commits that are ancestors- * of B but not ancestors of A but further limits the result to those- * that are descendants of A. This takes the list of bottom commits and- * the result of "A..B" without --ancestry-path, and limits the latter- * further to the ones that can reach one of the commits in "bottom".+ * "rev-list --ancestry-path=C_0 [--ancestry-path=C_1 ...] A..B"+ * computes commits that are ancestors of B but not ancestors of A but+ * further limits the result to those that have any of C in their+ * ancestry path (i.e. are either ancestors of any of C, descendants+ * of any of C, or are any of C). If --ancestry-path is specified with+ * no commit, we use all bottom commits for C.+ *+ * Before this function is called, ancestors of C will have already+ * been marked with ANCESTRY_PATH previously.+ *+ * This takes the list of bottom commits and the result of "A..B"+ * without --ancestry-path, and limits the latter further to the ones+ * that have any of C in their ancestry path. Since the ancestors of C+ * have already been marked (a prerequisite of this function), we just+ * need to mark the descendants, then exclude any commit that does not+ * have any of these marks. */-static void limit_to_ancestry(struct commit_list *bottom, struct commit_list *list)+static void limit_to_ancestry(struct commit_list *bottoms, struct commit_list *list) { struct commit_list *p; struct commit_list *rlist = NULL;@@ -1323,7 +1334,7 @@ static void limit_to_ancestry(struct commit_list *bottom, struct commit_list *li for (p = list; p; p = p->next) commit_list_insert(p->item, &rlist);- for (p = bottom; p; p = p->next)+ for (p = bottoms; p; p = p->next) p->item->object.flags |= TMP_MARK; /*@@ -1356,38 +1367,39 @@ static void limit_to_ancestry(struct commit_list *bottom, struct commit_list *li */ /*- * The ones that are not marked with TMP_MARK are uninteresting+ * The ones that are not marked with either TMP_MARK or+ * ANCESTRY_PATH are uninteresting */ for (p = list; p; p = p->next) { struct commit *c = p->item;- if (c->object.flags & TMP_MARK)+ if (c->object.flags & (TMP_MARK | ANCESTRY_PATH)) continue; c->object.flags |= UNINTERESTING; }- /* We are done with the TMP_MARK */+ /* We are done with TMP_MARK and ANCESTRY_PATH */ for (p = list; p; p = p->next)- p->item->object.flags &= ~TMP_MARK;- for (p = bottom; p; p = p->next)- p->item->object.flags &= ~TMP_MARK;+ p->item->object.flags &= ~(TMP_MARK | ANCESTRY_PATH);+ for (p = bottoms; p; p = p->next)+ p->item->object.flags &= ~(TMP_MARK | ANCESTRY_PATH); free_commit_list(rlist); } /*- * Before walking the history, keep the set of "negative" refs the- * caller has asked to exclude.+ * Before walking the history, add the set of "negative" refs the+ * caller has asked to exclude to the bottom list. * * This is used to compute "rev-list --ancestry-path A..B", as we need * to filter the result of "A..B" further to the ones that can actually * reach A. */-static struct commit_list *collect_bottom_commits(struct commit_list *list)+static void collect_bottom_commits(struct commit_list *list,+ struct commit_list **bottom) {- struct commit_list *elem, *bottom = NULL;+ struct commit_list *elem; for (elem = list; elem; elem = elem->next) if (elem->item->object.flags & BOTTOM)- commit_list_insert(elem->item, &bottom);- return bottom;+ commit_list_insert(elem->item, bottom); } /* Assumes either left_only or right_only is set */@@ -1414,12 +1426,12 @@ static int limit_list(struct rev_info *revs) struct commit_list *original_list = revs->commits; struct commit_list *newlist = NULL; struct commit_list **p = &newlist;- struct commit_list *bottom = NULL; struct commit *interesting_cache = NULL;- if (revs->ancestry_path) {- bottom = collect_bottom_commits(original_list);- if (!bottom)+ if (revs->ancestry_path_implicit_bottoms) {+ collect_bottom_commits(original_list,+ &revs->ancestry_path_bottoms);+ if (!revs->ancestry_path_bottoms) die("--ancestry-path given but there are no bottom commits"); }@@ -1464,9 +1476,8 @@ static int limit_list(struct rev_info *revs) if (revs->left_only || revs->right_only) limit_left_right(newlist, revs);- if (bottom)- limit_to_ancestry(bottom, newlist);- free_commit_list(bottom);+ if (revs->ancestry_path)+ limit_to_ancestry(revs->ancestry_path_bottoms, newlist); /* * Check if any commits have become TREESAME by some of their parents@@ -2213,7 +2224,7 @@ static int handle_revision_opt(struct rev_info *revs, int argc, const char **arg const struct setup_revision_opt* opt) { const char *arg = argv[0];- const char *optarg;+ const char *optarg = NULL; int argcount; const unsigned hexsz = the_hash_algo->hexsz;@@ -2284,6 +2295,23 @@ static int handle_revision_opt(struct rev_info *revs, int argc, const char **arg revs->ancestry_path = 1; revs->simplify_history = 0; revs->limited = 1;+ revs->ancestry_path_implicit_bottoms = 1;+ } else if (skip_prefix(arg, "--ancestry-path=", &optarg)) {+ struct commit *c;+ struct object_id oid;+ const char *msg = _("could not get commit for ancestry-path argument %s");++ revs->ancestry_path = 1;+ revs->simplify_history = 0;+ revs->limited = 1;++ if (repo_get_oid_committish(revs->repo, optarg, &oid))+ return error(msg, optarg);+ get_reference(revs, optarg, &oid, ANCESTRY_PATH);+ c = lookup_commit_reference(revs->repo, &oid);+ if (!c)+ return error(msg, optarg);+ commit_list_insert(c, &revs->ancestry_path_bottoms); } else if (!strcmp(arg, "-g") || !strcmp(arg, "--walk-reflogs")) { init_reflog_walk(&revs->reflog_info); } else if (!strcmp(arg, "--default")) {@@ -2993,6 +3021,7 @@ static void release_revisions_topo_walk_info(struct topo_walk_info *info); void release_revisions(struct rev_info *revs) { free_commit_list(revs->commits);+ free_commit_list(revs->ancestry_path_bottoms); object_array_clear(&revs->pending); object_array_clear(&revs->boundary_commits); release_revisions_cmdline(&revs->cmdline);
revision.h
+9
index bb91e7ed91..61a9b1316b 100644--- a/revision.h+++ b/revision.h@@ -48,6 +48,7 @@ */ #define NOT_USER_GIVEN (1u<<25) #define TRACK_LINEAR (1u<<26)+#define ANCESTRY_PATH (1u<<27) #define ALL_REV_FLAGS (((1u<<11)-1) | NOT_USER_GIVEN | TRACK_LINEAR | PULL_MERGE) #define DECORATE_SHORT_REFS 1@@ -164,6 +165,13 @@ struct rev_info { cherry_mark:1, bisect:1, ancestry_path:1,++ /* True if --ancestry-path was specified without an+ * argument. The bottom revisions are implicitly+ * the arguments in this case.+ */+ ancestry_path_implicit_bottoms:1,+ first_parent_only:1, exclude_first_parent_only:1, line_level_traverse:1,@@ -306,6 +314,7 @@ struct rev_info { struct saved_parents *saved_parents_slab; struct commit_list *previous_parents;+ struct commit_list *ancestry_path_bottoms; const char *break_bar; struct revision_sources *sources;
t/t6019-rev-list-ancestry-path.sh
+12-2
index 5bd787a3c0..738da23628 100755--- a/t/t6019-rev-list-ancestry-path.sh+++ b/t/t6019-rev-list-ancestry-path.sh@@ -8,8 +8,13 @@ test_description='--ancestry-path' # / \ # A-------K---------------L--M #-# D..M == E F G H I J K L M-# --ancestry-path D..M == E F H I J L M+# D..M == E F G H I J K L M+# --ancestry-path D..M == E F H I J L M+# --ancestry-path=F D..M == E F J L M+# --ancestry-path=G D..M == G H I J L M+# --ancestry-path=H D..M == E G H I J L M+# --ancestry-path=K D..M == K L M+# --ancestry-path=K --ancestry-path=F D..M == E F J K L M # # D..M -- M.t == M # --ancestry-path D..M -- M.t == M@@ -70,6 +75,11 @@ test_ancestry () { test_ancestry "D..M" "E F G H I J K L M" test_ancestry "--ancestry-path D..M" "E F H I J L M"+test_ancestry "--ancestry-path=F D..M" "E F J L M"+test_ancestry "--ancestry-path=G D..M" "G H I J L M"+test_ancestry "--ancestry-path=H D..M" "E G H I J L M"+test_ancestry "--ancestry-path=K D..M" "K L M"+test_ancestry "--ancestry-path=F --ancestry-path=K D..M" "E F J K L M" test_ancestry "D..M -- M.t" "M" test_ancestry "--ancestry-path D..M -- M.t" "M"