aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Documentation/rev-list-options.adoc4
-rw-r--r--object.h4
-rw-r--r--revision.c12
-rw-r--r--revision.h5
-rwxr-xr-xt/t6000-rev-list-misc.sh15
-rwxr-xr-xt/t6600-test-reach.sh75
6 files changed, 110 insertions, 5 deletions
diff --git a/Documentation/rev-list-options.adoc b/Documentation/rev-list-options.adoc
index c4d7a6b989..2d195a1474 100644
--- a/Documentation/rev-list-options.adoc
+++ b/Documentation/rev-list-options.adoc
@@ -148,6 +148,10 @@ endif::git-log[]
from the point where it diverged from the remote branch, given
that arbitrary merges can be valid topic branch changes.
+`--maximal-only`::
+ Restrict the output commits to be those that are not reachable
+ from any other commits in the revision range.
+
`--not`::
Reverses the meaning of the '{caret}' prefix (or lack thereof)
for all following revision specifiers, up to the next `--not`.
diff --git a/object.h b/object.h
index 4bca957b8d..dfe7a1f0ea 100644
--- a/object.h
+++ b/object.h
@@ -64,7 +64,7 @@ void object_array_init(struct object_array *array);
/*
* object flag allocation:
- * revision.h: 0---------10 15 23------27
+ * revision.h: 0---------10 15 23--------28
* fetch-pack.c: 01 67
* negotiator/default.c: 2--5
* walker.c: 0-2
@@ -86,7 +86,7 @@ void object_array_init(struct object_array *array);
* builtin/unpack-objects.c: 2021
* pack-bitmap.h: 2122
*/
-#define FLAG_BITS 28
+#define FLAG_BITS 29
#define TYPE_BITS 3
diff --git a/revision.c b/revision.c
index 29972c3a19..047ff7e458 100644
--- a/revision.c
+++ b/revision.c
@@ -1150,7 +1150,8 @@ static int process_parents(struct rev_info *revs, struct commit *commit,
struct commit *p = parent->item;
parent = parent->next;
if (p)
- p->object.flags |= UNINTERESTING;
+ p->object.flags |= UNINTERESTING |
+ CHILD_VISITED;
if (repo_parse_commit_gently(revs->repo, p, 1) < 0)
continue;
if (p->parents)
@@ -1204,7 +1205,7 @@ static int process_parents(struct rev_info *revs, struct commit *commit,
if (!*slot)
*slot = *revision_sources_at(revs->sources, commit);
}
- p->object.flags |= pass_flags;
+ p->object.flags |= pass_flags | CHILD_VISITED;
if (!(p->object.flags & SEEN)) {
p->object.flags |= (SEEN | NOT_USER_GIVEN);
if (list)
@@ -2377,6 +2378,8 @@ static int handle_revision_opt(struct rev_info *revs, int argc, const char **arg
} else if ((argcount = parse_long_opt("until", argv, &optarg))) {
revs->min_age = approxidate(optarg);
return argcount;
+ } else if (!strcmp(arg, "--maximal-only")) {
+ revs->maximal_only = 1;
} else if (!strcmp(arg, "--first-parent")) {
revs->first_parent_only = 1;
} 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
!!revs->reverse, "--reverse",
!!revs->reflog_info, "--walk-reflogs");
+ die_for_incompatible_opt2(!!revs->boundary, "--boundary",
+ !!revs->maximal_only, "--maximal-only");
+
if (revs->no_walk && revs->graph)
die(_("options '%s' and '%s' cannot be used together"), "--no-walk", "--graph");
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
{
if (commit->object.flags & SHOWN)
return commit_ignore;
+ if (revs->maximal_only && (commit->object.flags & CHILD_VISITED))
+ return commit_ignore;
if (revs->unpacked && has_object_pack(revs->repo, &commit->object.oid))
return commit_ignore;
if (revs->no_kept_objects) {
diff --git a/revision.h b/revision.h
index b36acfc2d9..69242ecb18 100644
--- a/revision.h
+++ b/revision.h
@@ -52,7 +52,9 @@
#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 CHILD_VISITED (1u<<28)
+#define ALL_REV_FLAGS (((1u<<11)-1) | NOT_USER_GIVEN | TRACK_LINEAR \
+ | PULL_MERGE | CHILD_VISITED)
#define DECORATE_SHORT_REFS 1
#define DECORATE_FULL_REFS 2
@@ -189,6 +191,7 @@ struct rev_info {
left_right:1,
left_only:1,
right_only:1,
+ maximal_only:1,
rewrite_parents:1,
print_parents:1,
show_decorations:1,
diff --git a/t/t6000-rev-list-misc.sh b/t/t6000-rev-list-misc.sh
index fec16448cf..d0a2a86610 100755
--- a/t/t6000-rev-list-misc.sh
+++ b/t/t6000-rev-list-misc.sh
@@ -248,4 +248,19 @@ test_expect_success 'rev-list -z --boundary' '
test_cmp expect actual
'
+test_expect_success 'rev-list --boundary incompatible with --maximal-only' '
+ test_when_finished rm -rf repo &&
+
+ git init repo &&
+ test_commit -C repo 1 &&
+ test_commit -C repo 2 &&
+
+ oid1=$(git -C repo rev-parse HEAD~) &&
+ oid2=$(git -C repo rev-parse HEAD) &&
+
+ test_must_fail git -C repo rev-list --boundary --maximal-only \
+ HEAD~1..HEAD 2>err &&
+ test_grep "cannot be used together" err
+'
+
test_done
diff --git a/t/t6600-test-reach.sh b/t/t6600-test-reach.sh
index 6638d1aa1d..2613075894 100755
--- a/t/t6600-test-reach.sh
+++ b/t/t6600-test-reach.sh
@@ -762,4 +762,79 @@ test_expect_success 'for-each-ref is-base: --sort' '
--sort=refname --sort=-is-base:commit-2-3
'
+test_expect_success 'rev-list --maximal-only (all positive)' '
+ # Only one maximal.
+ cat >input <<-\EOF &&
+ refs/heads/commit-1-1
+ refs/heads/commit-4-2
+ refs/heads/commit-4-4
+ refs/heads/commit-8-4
+ EOF
+
+ cat >expect <<-EOF &&
+ $(git rev-parse refs/heads/commit-8-4)
+ EOF
+ run_all_modes git rev-list --maximal-only --stdin &&
+
+ # All maximal.
+ cat >input <<-\EOF &&
+ refs/heads/commit-5-2
+ refs/heads/commit-4-3
+ refs/heads/commit-3-4
+ refs/heads/commit-2-5
+ EOF
+
+ cat >expect <<-EOF &&
+ $(git rev-parse refs/heads/commit-5-2)
+ $(git rev-parse refs/heads/commit-4-3)
+ $(git rev-parse refs/heads/commit-3-4)
+ $(git rev-parse refs/heads/commit-2-5)
+ EOF
+ run_all_modes git rev-list --maximal-only --stdin &&
+
+ # Mix of both.
+ cat >input <<-\EOF &&
+ refs/heads/commit-5-2
+ refs/heads/commit-3-2
+ refs/heads/commit-2-5
+ EOF
+
+ cat >expect <<-EOF &&
+ $(git rev-parse refs/heads/commit-5-2)
+ $(git rev-parse refs/heads/commit-2-5)
+ EOF
+ run_all_modes git rev-list --maximal-only --stdin
+'
+
+test_expect_success 'rev-list --maximal-only (range)' '
+ cat >input <<-\EOF &&
+ refs/heads/commit-1-1
+ refs/heads/commit-2-5
+ refs/heads/commit-6-4
+ ^refs/heads/commit-4-5
+ EOF
+
+ cat >expect <<-EOF &&
+ $(git rev-parse refs/heads/commit-6-4)
+ EOF
+ run_all_modes git rev-list --maximal-only --stdin &&
+
+ # first-parent changes reachability: the first parent
+ # reduces the second coordinate to 1 before reducing the
+ # first coordinate.
+ cat >input <<-\EOF &&
+ refs/heads/commit-1-1
+ refs/heads/commit-2-5
+ refs/heads/commit-6-4
+ ^refs/heads/commit-4-5
+ EOF
+
+ cat >expect <<-EOF &&
+ $(git rev-parse refs/heads/commit-6-4)
+ $(git rev-parse refs/heads/commit-2-5)
+ EOF
+ run_all_modes git rev-list --maximal-only --stdin \
+ --first-parent --exclude-first-parent-only
+'
+
test_done