Skip to content

Commit 9d505b7

Browse files
jerry-skydiogitster
authored andcommitted
git-rev-list: add --exclude-first-parent-only flag
It is useful to know when a branch first diverged in history from some integration branch in order to be able to enumerate the user's local changes. However, these local changes can include arbitrary merges, so it is necessary to ignore this merge structure when finding the divergence point. In order to do this, teach the "rev-list" family to accept "--exclude-first-parent-only", which restricts the traversal of excluded commits to only follow first parent links. -A-----E-F-G--main \ / / B-C-D--topic In this example, the goal is to return the set {B, C, D} which represents a topic branch that has been merged into main branch. `git rev-list topic ^main` will end up returning no commits since excluding main will end up traversing the commits on topic as well. `git rev-list --exclude-first-parent-only topic ^main` however will return {B, C, D} as desired. Add docs for the new flag, and clarify the doc for --first-parent to indicate that it applies to traversing the set of included commits only. Signed-off-by: Jerry Zhang <[email protected]> Signed-off-by: Junio C Hamano <[email protected]>
1 parent 90d242d commit 9d505b7

File tree

6 files changed

+51
-26
lines changed

6 files changed

+51
-26
lines changed

Documentation/rev-list-options.txt

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -122,19 +122,27 @@ again. Equivalent forms are `--min-parents=0` (any commit has 0 or more
122122
parents) and `--max-parents=-1` (negative numbers denote no upper limit).
123123

124124
--first-parent::
125-
Follow only the first parent commit upon seeing a merge
126-
commit. This option can give a better overview when
127-
viewing the evolution of a particular topic branch,
128-
because merges into a topic branch tend to be only about
129-
adjusting to updated upstream from time to time, and
130-
this option allows you to ignore the individual commits
131-
brought in to your history by such a merge.
125+
When finding commits to include, follow only the first
126+
parent commit upon seeing a merge commit. This option
127+
can give a better overview when viewing the evolution of
128+
a particular topic branch, because merges into a topic
129+
branch tend to be only about adjusting to updated upstream
130+
from time to time, and this option allows you to ignore
131+
the individual commits brought in to your history by such
132+
a merge.
132133
ifdef::git-log[]
133134
+
134135
This option also changes default diff format for merge commits
135136
to `first-parent`, see `--diff-merges=first-parent` for details.
136137
endif::git-log[]
137138

139+
--exclude-first-parent-only::
140+
When finding commits to exclude (with a '{caret}'), follow only
141+
the first parent commit upon seeing a merge commit.
142+
This can be used to find the set of changes in a topic branch
143+
from the point where it diverged from the remote branch, given
144+
that arbitrary merges can be valid topic branch changes.
145+
138146
--not::
139147
Reverses the meaning of the '{caret}' prefix (or lack thereof)
140148
for all following revision specifiers, up to the next `--not`.

blame.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2615,7 +2615,7 @@ void assign_blame(struct blame_scoreboard *sb, int opt)
26152615
else {
26162616
commit->object.flags |= UNINTERESTING;
26172617
if (commit->object.parsed)
2618-
mark_parents_uninteresting(commit);
2618+
mark_parents_uninteresting(sb->revs, commit);
26192619
}
26202620
/* treat root commit as boundary */
26212621
if (!commit->parents && !sb->show_root)

revision.c

Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -273,7 +273,7 @@ static void commit_stack_clear(struct commit_stack *stack)
273273
stack->nr = stack->alloc = 0;
274274
}
275275

276-
static void mark_one_parent_uninteresting(struct commit *commit,
276+
static void mark_one_parent_uninteresting(struct rev_info *revs, struct commit *commit,
277277
struct commit_stack *pending)
278278
{
279279
struct commit_list *l;
@@ -290,20 +290,26 @@ static void mark_one_parent_uninteresting(struct commit *commit,
290290
* wasn't uninteresting), in which case we need
291291
* to mark its parents recursively too..
292292
*/
293-
for (l = commit->parents; l; l = l->next)
293+
for (l = commit->parents; l; l = l->next) {
294294
commit_stack_push(pending, l->item);
295+
if (revs && revs->exclude_first_parent_only)
296+
break;
297+
}
295298
}
296299

297-
void mark_parents_uninteresting(struct commit *commit)
300+
void mark_parents_uninteresting(struct rev_info *revs, struct commit *commit)
298301
{
299302
struct commit_stack pending = COMMIT_STACK_INIT;
300303
struct commit_list *l;
301304

302-
for (l = commit->parents; l; l = l->next)
303-
mark_one_parent_uninteresting(l->item, &pending);
305+
for (l = commit->parents; l; l = l->next) {
306+
mark_one_parent_uninteresting(revs, l->item, &pending);
307+
if (revs && revs->exclude_first_parent_only)
308+
break;
309+
}
304310

305311
while (pending.nr > 0)
306-
mark_one_parent_uninteresting(commit_stack_pop(&pending),
312+
mark_one_parent_uninteresting(revs, commit_stack_pop(&pending),
307313
&pending);
308314

309315
commit_stack_clear(&pending);
@@ -441,7 +447,7 @@ static struct commit *handle_commit(struct rev_info *revs,
441447
if (repo_parse_commit(revs->repo, commit) < 0)
442448
die("unable to parse commit %s", name);
443449
if (flags & UNINTERESTING) {
444-
mark_parents_uninteresting(commit);
450+
mark_parents_uninteresting(revs, commit);
445451

446452
if (!revs->topo_order || !generation_numbers_enabled(the_repository))
447453
revs->limited = 1;
@@ -1124,14 +1130,16 @@ static int process_parents(struct rev_info *revs, struct commit *commit,
11241130
if (repo_parse_commit_gently(revs->repo, p, 1) < 0)
11251131
continue;
11261132
if (p->parents)
1127-
mark_parents_uninteresting(p);
1133+
mark_parents_uninteresting(revs, p);
11281134
if (p->object.flags & SEEN)
11291135
continue;
11301136
p->object.flags |= (SEEN | NOT_USER_GIVEN);
11311137
if (list)
11321138
commit_list_insert_by_date(p, list);
11331139
if (queue)
11341140
prio_queue_put(queue, p);
1141+
if (revs->exclude_first_parent_only)
1142+
break;
11351143
}
11361144
return 0;
11371145
}
@@ -1422,7 +1430,7 @@ static int limit_list(struct rev_info *revs)
14221430
if (process_parents(revs, commit, &original_list, NULL) < 0)
14231431
return -1;
14241432
if (obj->flags & UNINTERESTING) {
1425-
mark_parents_uninteresting(commit);
1433+
mark_parents_uninteresting(revs, commit);
14261434
slop = still_interesting(original_list, date, slop, &interesting_cache);
14271435
if (slop)
14281436
continue;
@@ -2223,6 +2231,8 @@ static int handle_revision_opt(struct rev_info *revs, int argc, const char **arg
22232231
return argcount;
22242232
} else if (!strcmp(arg, "--first-parent")) {
22252233
revs->first_parent_only = 1;
2234+
} else if (!strcmp(arg, "--exclude-first-parent-only")) {
2235+
revs->exclude_first_parent_only = 1;
22262236
} else if (!strcmp(arg, "--ancestry-path")) {
22272237
revs->ancestry_path = 1;
22282238
revs->simplify_history = 0;
@@ -3345,7 +3355,7 @@ static void explore_walk_step(struct rev_info *revs)
33453355
return;
33463356

33473357
if (c->object.flags & UNINTERESTING)
3348-
mark_parents_uninteresting(c);
3358+
mark_parents_uninteresting(revs, c);
33493359

33503360
for (p = c->parents; p; p = p->next)
33513361
test_flag_and_insert(&info->explore_queue, p->item, TOPO_WALK_EXPLORED);

revision.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,7 @@ struct rev_info {
158158
bisect:1,
159159
ancestry_path:1,
160160
first_parent_only:1,
161+
exclude_first_parent_only:1,
161162
line_level_traverse:1,
162163
tree_blobs_in_commit_order:1,
163164

@@ -398,7 +399,7 @@ const char *get_revision_mark(const struct rev_info *revs,
398399
void put_revision_mark(const struct rev_info *revs,
399400
const struct commit *commit);
400401

401-
void mark_parents_uninteresting(struct commit *commit);
402+
void mark_parents_uninteresting(struct rev_info *revs, struct commit *commit);
402403
void mark_tree_uninteresting(struct repository *r, struct tree *tree);
403404
void mark_trees_uninteresting_sparse(struct repository *r, struct oidset *trees);
404405

shallow.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -603,7 +603,7 @@ static int mark_uninteresting(const char *refname, const struct object_id *oid,
603603
if (!commit)
604604
return 0;
605605
commit->object.flags |= UNINTERESTING;
606-
mark_parents_uninteresting(commit);
606+
mark_parents_uninteresting(NULL, commit);
607607
return 0;
608608
}
609609

t/t6012-rev-list-simplify.sh

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,12 @@ unnote () {
1616
}
1717

1818
#
19-
# Create a test repo with interesting commit graph:
19+
# Create a test repo with an interesting commit graph:
2020
#
21-
# A--B----------G--H--I--K--L
22-
# \ \ / /
23-
# \ \ / /
24-
# C------E---F J
25-
# \_/
21+
# A-----B-----G--H--I--K--L
22+
# \ \ / /
23+
# \ \ / /
24+
# C--D--E--F J
2625
#
2726
# The commits are laid out from left-to-right starting with
2827
# the root commit A and terminating at the tip commit L.
@@ -142,6 +141,13 @@ check_result 'I B A' --author-date-order -- file
142141
check_result 'H' --first-parent -- another-file
143142
check_result 'H' --first-parent --topo-order -- another-file
144143

144+
check_result 'L K I H G B A' --first-parent L
145+
check_result 'F E D C' --exclude-first-parent-only F ^L
146+
check_result '' F ^L
147+
check_result 'L K I H G J' L ^F
148+
check_result 'L K I H G B J' --exclude-first-parent-only L ^F
149+
check_result 'L K I H G B' --exclude-first-parent-only --first-parent L ^F
150+
145151
check_result 'E C B A' --full-history E -- lost
146152
test_expect_success 'full history simplification without parent' '
147153
printf "%s\n" E C B A >expect &&

0 commit comments

Comments
 (0)