From mboxrd@z Thu Jan 1 00:00:00 1970 Received: from mail.delayed.space (delayed.space [195.231.85.169]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 40E1A1F5834 for ; Mon, 27 Apr 2026 00:25:47 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=195.231.85.169 ARC-Seal:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1777249549; cv=none; b=FziqRt43mYgnKIPg6HQ+MDCbsMydOhPPMNnLHi/8CPgn66dibGJ1i9yTb9lfMHMBAoR+RClNq2UOlQ2z45/hLzVd5rwRwua8k0D0KRkFf8EhT2DEV6wSLZ+XLXCiZHDqgk9LWzA84yIEZxnS5Q6DpQWv9NNat+hIc8HVb+fu2K8= ARC-Message-Signature:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1777249549; c=relaxed/simple; bh=XLxqLT5POXRf7Ar/9HxlEBsZftQ2qd8flh7XG4WbfJ0=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=fkNHV9OO6c6k/EbDCgUIWQZbfjVxAnOxfpKRQsqUufluARrs9TCORQyi2Hjx2dfzui55aH+xTy9+ffTVVCCx2xtKyrgqhHpV1y3W3f4Mwvde0qOXSQIL0ex7Zc5Fvn55XbW/WkhC+h8fnBBayIRdifCMnGHwMGZrSNeIZRAGJiE= ARC-Authentication-Results:i=1; smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=delayed.space; spf=pass smtp.mailfrom=delayed.space; dkim=pass (2048-bit key) header.d=delayed.space header.i=@delayed.space header.b=lKwEMnEo; arc=none smtp.client-ip=195.231.85.169 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=delayed.space Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=delayed.space Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=delayed.space header.i=@delayed.space header.b="lKwEMnEo" From: Mirko Faina DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=delayed.space; s=dkim; t=1777249539; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=3fybBiJYwVtkkCFuvomSGy4KTPk+Wi12/nhLDhBYnws=; b=lKwEMnEoS7XaUkpilRpqZnmwFNagURf+StTXuS55CE3PCNUvARQY4um6o80zu+Fsk8oRdf gItZYtyioVrNDAxMGdWvef8/9ononbYaTvkCdU9pyzhNaP9yaNv3ufzlHUUbNKQlF9Olyv uz3dNzQSO6IpP03Ka1Ig+yBSWXC+kPiivnGusyqATZePyIYDjPICA9IxIhbwTYo0wb776M u5OPG3aan6cGU5zEObUjxm3+VL6fAUVFTKyX88fFdA1e+cXzPaY1Booa/wfaYhZqnvaoR0 NxiRnRsoL+rKgyGHiTT+sDZXVLnrcEt1+MPgIl8cVAzx8d8Pm4MM71e9sjDiVA== Authentication-Results: mail.delayed.space; auth=pass smtp.mailfrom=mroik@delayed.space To: git@vger.kernel.org Cc: Mirko Faina , Junio C Hamano , Jeff King , =?UTF-8?q?Jean-No=C3=ABl=20Avila?= , Patrick Steinhardt , Tian Yuchen , Ben Knoble Subject: [PATCH v4 1/2] revision.c: implement --reverse=before for walks Date: Mon, 27 Apr 2026 02:24:57 +0200 Message-ID: <4864ac46dd8ef4b704c29efc96c45f4e1412373b.1777249165.git.mroik@delayed.space> In-Reply-To: References: Precedence: bulk X-Mailing-List: git@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 X-Developer-Signature: v=1; a=openpgp-sha256; l=7205; i=mroik@delayed.space; h=from:subject:message-id; bh=XLxqLT5POXRf7Ar/9HxlEBsZftQ2qd8flh7XG4WbfJ0=; b=owEBbQKS/ZANAwAKAUh5fqGcGb7RAcsmYgBp7qzY1EEI4w30MNBGMwtLTPhVsUB/C62gE52TO +ys8v5Lb5eJAjMEAAEKAB0WIQT/Ky37K0pSwmwsybZIeX6hnBm+0QUCae6s2AAKCRBIeX6hnBm+ 0TJrD/9xLQpSt2mTHius3J4V4SbsX8opl+WJsrbBshPBds+FuaSOgLi3mEWYUCViwmLIO0q5w1f gdm5ZfLmckfLiEAl1kb5zndJ4UWOS7guwJaCqyZ80K730GuXi4UuDL0jNRqYg6EmRPC+/JWYRKj czuvZwFrQVqHbR3NbpBYABCR6zbnRu00cv9NIuC3A9+/+B6TfHXYEIsSgrkmDCI6x92QkrAaPIq x25fgRXmGm3JGKMFLDmg4xIu0fHFZ0FkndexC/OkbcbEMVUm3mN4hqxHnvlo5x3eEBsUTMEcP6Z zN40zNirDBkMBb3PE5iD5sKXItPiDFEHtxAbuBHW8AmDyjMXcZ96vCgM7qnd2JZl/WnPP80rPSH nqfaruJQGeinB430shxRIZjoFfJsRUahkydPTVcFJu/ST6tHsL192c9pu1qWVnAjesy2a866FF4 1slb8KRthpN3AYq70uCKrR4MMwKRmOp5l+HR27kNy58JHPbhB30Tftjzu8aMxwPmL3rEMX6TSRX rLtmr6O7VyP2CoQ1gZdZ9eWxP0UHWRL9vxeWzOyMgChOgiDbdpv00lPCIZJhtp04D3Dc64+IlNI Y5iR2hoqV88JS0dQ88k4ZoWJdk/A6qvK74bijXH7qoBUER5c1fr2Ew43MtvvNne+7ZXsjVDpu4C GErSapXJ6 6mTBuw== X-Developer-Key: i=mroik@delayed.space; a=openpgp; fpr=FF2B2DFB2B4A52C26C2CC9B648797EA19C19BED1 Content-Transfer-Encoding: 8bit X-Spamd-Bar: ---- In a revision walk `--reverse` can only be applied after any commit limiting option. This makes getting a limited amount of commits from the tail impossible. E.g. git log --reverse --max-count=3 Some would expect this to give back the first 3 commits of the project. Instead it returns the last 3 but in reversed order. Teach `get_revision()` to accpet an argument `(after|before)` from the CLI, and apply the reversal before or after the commit limiting options based on this argument. If no argument is provided default to the current behaviour, applying `--reverse` after the commit limiting options. Signed-off-by: Mirko Faina --- Documentation/rev-list-options.adoc | 16 +++++-- revision.c | 31 ++++++++++++-- revision.h | 8 +++- t/t4202-log.sh | 66 +++++++++++++++++++++++++++++ 4 files changed, 113 insertions(+), 8 deletions(-) diff --git a/Documentation/rev-list-options.adoc b/Documentation/rev-list-options.adoc index 2d195a1474..e97f6f2aff 100644 --- a/Documentation/rev-list-options.adoc +++ b/Documentation/rev-list-options.adoc @@ -914,10 +914,18 @@ With `--topo-order`, they would show 8 6 5 3 7 4 2 1 (or 8 7 4 2 6 5 avoid showing the commits from two parallel development track mixed together. -`--reverse`:: - Output the commits chosen to be shown (see 'Commit Limiting' - section above) in reverse order. Cannot be combined with - `--walk-reflogs`. +`--[no-]reverse[=(after|before)]`:: + Accepts `after` or `before`. Cannot be combined with + `--walk-reflogs`. If `after`, output the commits chosen to be + shown (see 'Commit Limiting' section above) in reverse order. If + `before`, reverse the commits before filtering with `Commit + Limiting` options. When multiple `--reverse=` options are given, + the final option overrides any previous options. The `--reverse` + option (with no specifier) behaves as `--reverse=after`, except + that, for historical reasons, it negates any previous reversed + state (so `--reverse --reverse` does nothing, nor does + `--reverse=before --reverse`. Note that `--reverse=before + --reverse --reverse` is the same as `--reverse=after`). endif::git-shortlog[] ifndef::git-shortlog[] diff --git a/revision.c b/revision.c index 599b3a66c3..d581f5e38e 100644 --- a/revision.c +++ b/revision.c @@ -2686,7 +2686,16 @@ static int handle_revision_opt(struct rev_info *revs, int argc, const char **arg git_log_output_encoding = xstrdup(""); return argcount; } else if (!strcmp(arg, "--reverse")) { - revs->reverse ^= 1; + revs->reverse = !revs->reverse; + } else if (skip_prefix(arg, "--reverse=", &optarg)) { + if (!strcmp(optarg, "after")) + revs->reverse = REVERSE_AFTER; + else if(!strcmp(optarg, "before")) + revs->reverse = REVERSE_BEFORE; + else + die(_("unknown value for --reverse: %s"), optarg); + } else if (!strcmp(arg, "--no-reverse")) { + revs->reverse = NO_REVERSE; } else if (!strcmp(arg, "--children")) { revs->children.name = "children"; revs->limited = 1; @@ -4525,19 +4534,35 @@ struct commit *get_revision(struct rev_info *revs) { struct commit *c; struct commit_list *reversed; + int max_count = revs->max_count; + + if (revs->reverse && !revs->reverse_output_stage) { + if (revs->reverse == 3) { + BUG("allowed values for reverse are 0, 1 and 2"); + revs->reverse = 1; + } + + if (revs->reverse == REVERSE_BEFORE) + revs->max_count = -1; - if (revs->reverse) { reversed = NULL; while ((c = get_revision_internal(revs))) commit_list_insert(c, &reversed); commit_list_free(revs->commits); revs->commits = reversed; - revs->reverse = 0; revs->reverse_output_stage = 1; + + if (revs->reverse == REVERSE_BEFORE) + revs->max_count = max_count; } if (revs->reverse_output_stage) { + if (revs->reverse == REVERSE_BEFORE && revs->max_count == 0) + return NULL; + c = pop_commit(&revs->commits); + if (revs->reverse == REVERSE_BEFORE) + revs->max_count--; if (revs->track_linear) revs->linear = !!(c && c->object.flags & TRACK_LINEAR); return c; diff --git a/revision.h b/revision.h index 584f1338b5..02881577dc 100644 --- a/revision.h +++ b/revision.h @@ -121,6 +121,12 @@ struct ref_exclusions { struct oidset; struct topo_walk_info; +enum rev_reverse { + NO_REVERSE = 0, + REVERSE_AFTER = 1, + REVERSE_BEFORE = 2, +}; + struct rev_info { /* Starting list */ struct commit_list *commits; @@ -167,6 +173,7 @@ struct rev_info { ignore_missing_links:1; /* Traversal flags */ + enum rev_reverse reverse:2; unsigned int dense:1, prune:1, no_walk:1, @@ -196,7 +203,6 @@ struct rev_info { rewrite_parents:1, print_parents:1, show_decorations:1, - reverse:1, reverse_output_stage:1, cherry_pick:1, cherry_mark:1, diff --git a/t/t4202-log.sh b/t/t4202-log.sh index 05cee9e41b..3bfe2c99b8 100755 --- a/t/t4202-log.sh +++ b/t/t4202-log.sh @@ -1882,6 +1882,72 @@ test_expect_success 'log --graph with --name-status' ' test_cmp_graph --name-status tangle..reach ' +cat >expect <<-\EOF +c3f451c Merge tag 'reach' +046b221 to remove +EOF + +test_expect_success 'log --reverse --oneline --max-count=2' ' + test_when_finished git reset --hard HEAD~1 && + touch to_remove && + git add to_remove && + git commit -m "to remove" && + git log --reverse --oneline --max-count=2 >actual && + test_cmp expect actual +' + +test_expect_success 'log --reverse --reverse --reverse --oneline --max-count=2' ' + test_when_finished git reset --hard HEAD~1 && + touch to_remove && + git add to_remove && + git commit -m "to remove" && + git log --reverse --reverse --reverse --oneline --max-count=2 >actual && + test_cmp expect actual +' + +test_expect_success 'log --reverse=after --oneline --max-count=2' ' + test_when_finished git reset --hard HEAD~1 && + touch to_remove && + git add to_remove && + git commit -m "to remove" && + git log --reverse=after --oneline --max-count=2 >actual && + test_cmp expect actual +' + +cat >expect <<-\EOF +3a2fdcb initial +f7dab8e second +EOF + +test_expect_success 'log --reverse=before --oneline --max-count=2' ' + test_when_finished rm actual && + git log --reverse=before --oneline --max-count=2 >actual && + test_cmp expect actual +' + +cat >expect <<-\EOF +046b221 to remove +c3f451c Merge tag 'reach' +EOF + +test_expect_success 'log --reverse --reverse --oneline --max-count=2' ' + test_when_finished git reset --hard HEAD~1 && + touch to_remove && + git add to_remove && + git commit -m "to remove" && + git log --reverse --reverse --oneline --max-count=2 >actual && + test_cmp expect actual +' + +test_expect_success 'log --reverse --no-reverse --oneline --max-count=2' ' + test_when_finished git reset --hard HEAD~1 && + touch to_remove && + git add to_remove && + git commit -m "to remove" && + git log --reverse --no-reverse --oneline --max-count=2 >actual && + test_cmp expect actual +' + cat >expect <<-\EOF * reach | -- 2.54.0