Skip to content

Commit 54fbf36

Browse files
committed
revision: add --maximal-only option
When inspecting a range of commits from some set of starting references, it is sometimes useful to learn which commits are not reachable from any other commits in the selected range. One such application is in the creation of a sequence of bundles for the bundle URI feature. Creating a stack of bundles representing different slices of time includes defining which references to include. If all references are used, then this may be overwhelming or redundant. Instead, selecting commits that are maximal to the range could help defining a smaller reference set to use in the bundle header. Add a new '--maximal-only' option to restrict the output of a revision range to be only the commits that are not reachable from any other commit in the range, based on the reachability definition of the walk. This is accomplished by adding a new 28th bit flag, CHILD_VISITED, that is set as we walk. This does extend the bit range in object.h, but using an earlier bit may collide with another feature. The tests demonstrate the behavior of the feature with a positive-only range, ranges with negative references, and walk-modifying flags like --first-parent and --exclude-first-parent-only. Since the --boundary option would not increase any results when used with the --maximal-only option, mark them as incompatible. Signed-off-by: Derrick Stolee <[email protected]>
1 parent b5c409c commit 54fbf36

File tree

6 files changed

+110
-5
lines changed

6 files changed

+110
-5
lines changed

Documentation/rev-list-options.adoc

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,10 @@ endif::git-log[]
148148
from the point where it diverged from the remote branch, given
149149
that arbitrary merges can be valid topic branch changes.
150150

151+
`--maximal-only`::
152+
Restrict the output commits to be those that are not reachable
153+
from any other commits in the revision range.
154+
151155
`--not`::
152156
Reverses the meaning of the '{caret}' prefix (or lack thereof)
153157
for all following revision specifiers, up to the next `--not`.

object.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ void object_array_init(struct object_array *array);
6464

6565
/*
6666
* object flag allocation:
67-
* revision.h: 0---------10 15 23------27
67+
* revision.h: 0---------10 15 23--------28
6868
* fetch-pack.c: 01 67
6969
* negotiator/default.c: 2--5
7070
* walker.c: 0-2
@@ -86,7 +86,7 @@ void object_array_init(struct object_array *array);
8686
* builtin/unpack-objects.c: 2021
8787
* pack-bitmap.h: 2122
8888
*/
89-
#define FLAG_BITS 28
89+
#define FLAG_BITS 29
9090

9191
#define TYPE_BITS 3
9292

revision.c

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1150,7 +1150,8 @@ static int process_parents(struct rev_info *revs, struct commit *commit,
11501150
struct commit *p = parent->item;
11511151
parent = parent->next;
11521152
if (p)
1153-
p->object.flags |= UNINTERESTING;
1153+
p->object.flags |= UNINTERESTING |
1154+
CHILD_VISITED;
11541155
if (repo_parse_commit_gently(revs->repo, p, 1) < 0)
11551156
continue;
11561157
if (p->parents)
@@ -1204,7 +1205,7 @@ static int process_parents(struct rev_info *revs, struct commit *commit,
12041205
if (!*slot)
12051206
*slot = *revision_sources_at(revs->sources, commit);
12061207
}
1207-
p->object.flags |= pass_flags;
1208+
p->object.flags |= pass_flags | CHILD_VISITED;
12081209
if (!(p->object.flags & SEEN)) {
12091210
p->object.flags |= (SEEN | NOT_USER_GIVEN);
12101211
if (list)
@@ -2377,6 +2378,8 @@ static int handle_revision_opt(struct rev_info *revs, int argc, const char **arg
23772378
} else if ((argcount = parse_long_opt("until", argv, &optarg))) {
23782379
revs->min_age = approxidate(optarg);
23792380
return argcount;
2381+
} else if (!strcmp(arg, "--maximal-only")) {
2382+
revs->maximal_only = 1;
23802383
} else if (!strcmp(arg, "--first-parent")) {
23812384
revs->first_parent_only = 1;
23822385
} else if (!strcmp(arg, "--exclude-first-parent-only")) {
@@ -3147,6 +3150,9 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, struct s
31473150
!!revs->reverse, "--reverse",
31483151
!!revs->reflog_info, "--walk-reflogs");
31493152

3153+
die_for_incompatible_opt2(!!revs->boundary, "--boundary",
3154+
!!revs->maximal_only, "--maximal-only");
3155+
31503156
if (revs->no_walk && revs->graph)
31513157
die(_("options '%s' and '%s' cannot be used together"), "--no-walk", "--graph");
31523158
if (!revs->reflog_info && revs->grep_filter.use_reflog_filter)
@@ -4125,6 +4131,8 @@ enum commit_action get_commit_action(struct rev_info *revs, struct commit *commi
41254131
{
41264132
if (commit->object.flags & SHOWN)
41274133
return commit_ignore;
4134+
if (revs->maximal_only && (commit->object.flags & CHILD_VISITED))
4135+
return commit_ignore;
41284136
if (revs->unpacked && has_object_pack(revs->repo, &commit->object.oid))
41294137
return commit_ignore;
41304138
if (revs->no_kept_objects) {

revision.h

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,9 @@
5252
#define NOT_USER_GIVEN (1u<<25)
5353
#define TRACK_LINEAR (1u<<26)
5454
#define ANCESTRY_PATH (1u<<27)
55-
#define ALL_REV_FLAGS (((1u<<11)-1) | NOT_USER_GIVEN | TRACK_LINEAR | PULL_MERGE)
55+
#define CHILD_VISITED (1u<<28)
56+
#define ALL_REV_FLAGS (((1u<<11)-1) | NOT_USER_GIVEN | TRACK_LINEAR \
57+
| PULL_MERGE | CHILD_VISITED)
5658

5759
#define DECORATE_SHORT_REFS 1
5860
#define DECORATE_FULL_REFS 2
@@ -189,6 +191,7 @@ struct rev_info {
189191
left_right:1,
190192
left_only:1,
191193
right_only:1,
194+
maximal_only:1,
192195
rewrite_parents:1,
193196
print_parents:1,
194197
show_decorations:1,

t/t6000-rev-list-misc.sh

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -248,4 +248,19 @@ test_expect_success 'rev-list -z --boundary' '
248248
test_cmp expect actual
249249
'
250250

251+
test_expect_success 'rev-list --boundary incompatible with --maximal-only' '
252+
test_when_finished rm -rf repo &&
253+
254+
git init repo &&
255+
test_commit -C repo 1 &&
256+
test_commit -C repo 2 &&
257+
258+
oid1=$(git -C repo rev-parse HEAD~) &&
259+
oid2=$(git -C repo rev-parse HEAD) &&
260+
261+
test_must_fail git -C repo rev-list --boundary --maximal-only \
262+
HEAD~1..HEAD 2>err &&
263+
test_grep "cannot be used together" err
264+
'
265+
251266
test_done

t/t6600-test-reach.sh

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -762,4 +762,79 @@ test_expect_success 'for-each-ref is-base: --sort' '
762762
--sort=refname --sort=-is-base:commit-2-3
763763
'
764764

765+
test_expect_success 'rev-list --maximal-only (all positive)' '
766+
# Only one maximal.
767+
cat >input <<-\EOF &&
768+
refs/heads/commit-1-1
769+
refs/heads/commit-4-2
770+
refs/heads/commit-4-4
771+
refs/heads/commit-8-4
772+
EOF
773+
774+
cat >expect <<-EOF &&
775+
$(git rev-parse refs/heads/commit-8-4)
776+
EOF
777+
run_all_modes git rev-list --maximal-only --stdin &&
778+
779+
# All maximal.
780+
cat >input <<-\EOF &&
781+
refs/heads/commit-5-2
782+
refs/heads/commit-4-3
783+
refs/heads/commit-3-4
784+
refs/heads/commit-2-5
785+
EOF
786+
787+
cat >expect <<-EOF &&
788+
$(git rev-parse refs/heads/commit-5-2)
789+
$(git rev-parse refs/heads/commit-4-3)
790+
$(git rev-parse refs/heads/commit-3-4)
791+
$(git rev-parse refs/heads/commit-2-5)
792+
EOF
793+
run_all_modes git rev-list --maximal-only --stdin &&
794+
795+
# Mix of both.
796+
cat >input <<-\EOF &&
797+
refs/heads/commit-5-2
798+
refs/heads/commit-3-2
799+
refs/heads/commit-2-5
800+
EOF
801+
802+
cat >expect <<-EOF &&
803+
$(git rev-parse refs/heads/commit-5-2)
804+
$(git rev-parse refs/heads/commit-2-5)
805+
EOF
806+
run_all_modes git rev-list --maximal-only --stdin
807+
'
808+
809+
test_expect_success 'rev-list --maximal-only (range)' '
810+
cat >input <<-\EOF &&
811+
refs/heads/commit-1-1
812+
refs/heads/commit-2-5
813+
refs/heads/commit-6-4
814+
^refs/heads/commit-4-5
815+
EOF
816+
817+
cat >expect <<-EOF &&
818+
$(git rev-parse refs/heads/commit-6-4)
819+
EOF
820+
run_all_modes git rev-list --maximal-only --stdin &&
821+
822+
# first-parent changes reachability: the first parent
823+
# reduces the second coordinate to 1 before reducing the
824+
# first coordinate.
825+
cat >input <<-\EOF &&
826+
refs/heads/commit-1-1
827+
refs/heads/commit-2-5
828+
refs/heads/commit-6-4
829+
^refs/heads/commit-4-5
830+
EOF
831+
832+
cat >expect <<-EOF &&
833+
$(git rev-parse refs/heads/commit-6-4)
834+
$(git rev-parse refs/heads/commit-2-5)
835+
EOF
836+
run_all_modes git rev-list --maximal-only --stdin \
837+
--first-parent --exclude-first-parent-only
838+
'
839+
765840
test_done

0 commit comments

Comments
 (0)