Re: [PATCH v2 9/9] builtin/history: implement "drop" subcommand
From: Kristoffer Haugsbakk <hidden>
Date: 2026-06-03 19:04:55
On Wed, Jun 3, 2026, at 18:14, Patrick Steinhardt wrote:
quoted hunk ↗ jump to hunk
[snip] --- Documentation/git-history.adoc | 38 ++- builtin/history.c | 187 +++++++++++++++ t/meson.build | 1 + t/t3454-history-drop.sh | 513 +++++++++++++++++++++++++++++++++++++++++ 4 files changed, 738 insertions(+), 1 deletion(-)diff --git a/Documentation/git-history.adocb/Documentation/git-history.adoc index 2ba8121795..4eac732fd2 100644--- a/Documentation/git-history.adoc +++ b/Documentation/git-history.adoc@@ -8,6 +8,7 @@ git-history - EXPERIMENTAL: Rewrite history SYNOPSIS -------- [synopsis] +git history drop <commit> [--dry-run] [--update-refs=(branches|head)][--empty=(drop|keep|abort)] git history fixup <commit> [--dry-run] [--update-refs=(branches|head)] [--reedit-message] [--empty=(drop|keep|abort)] git history reword <commit> [--dry-run] [--update-refs=(branches|head)] git history split <commit> [--dry-run] [--update-refs=(branches|head)] [--] [<pathspec>...]@@ -51,13 +52,28 @@ be stateful operations. The limitation can belifted once (if) Git learns about first-class conflicts. When using `fixup` with `--empty=drop`, dropping the root commit is not yet -supported. +supported. Likewise, `drop` cannot remove the root commit or a merge commit. COMMANDS -------- The following commands are available to rewrite history in different ways: +`drop <commit>`:: + Remove the specified commit from the history. All descendants of the + commit are replayed directly onto its parent. ++ +The root commit cannot be dropped as that may lead to edge cases where refs +end up with no commits anymore. Merge commits cannot be dropped either; see +LIMITATIONS.
Should section names be “bare” or quoted like "LIMITATIONS"? I don’t know. Maybe add “above” since it’s a previous section.
++ +If `HEAD` points at a commit that is to be rewritten, the index and working [snip] +Drop a commit +~~~~~~~~~~~~~ + +---------- +$ git log --oneline +abc1234 (HEAD -> main) third +def5678 second +ghi9012 first + +$ git history drop def5678
I know this is only the most simple example. And I might be dragging in
something beyond the scope of this example. But I recall one
demonstration on the first git-history(1) series which used a lot of
revision expressions and someone saying that they couldn’t imagine a
workflow where this would be more interactive than bringing up the
git-rebase(1) todo editor.
(I couldn’t find back to this right now.)
Although it is slower in terms of machine cycles, the keyboard instinct
for dropping a nearby commit might be to do `git rebase -i @~10`
(sufficiently high number) and navigating quickly in the configured
editor, deleting the line or using the keybind for `drop`. This example
which by implication brings up the log in order to paste the abbreviated
hash isn’t as ergonomic in comparison.
But using a revision expression like searching the subject with
`main^{/second}`, while not quicker probably, does distinguish itself
from git-rebase(1) by being a pretty fast ad hoc invocation that can be
done in one command without futzing with some weird sed(1) editor in
order to navigate to the `second` line and deleting it, or
something. And that’s a small win in isolation, but it segues much more
naturally into letting you script, say, dropping the last commit that
starts with the subject `TEMP`.
Or maybe revision expressions is too much in this context?
quoted hunk ↗ jump to hunk
+ +$ git log --oneline [snip]diff --git a/t/t3454-history-drop.sh b/t/t3454-history-drop.sh new file mode 100755 index 0000000000..37d8413e7e --- /dev/null +++ b/t/t3454-history-drop.sh@@ -0,0 +1,513 @@ +#!/bin/sh + +test_description='tests for git-history drop subcommand' + +. ./test-lib.sh +. "$TEST_DIRECTORY/lib-log-graph.sh" + +expect_graph () { + cat >expect && + lib_test_cmp_graph --format=%s "$@" +} + +expect_log () { + git log --format="%s" "$@" >actual && + cat >expect && + test_cmp expect actual +} + +test_expect_success 'errors on missing commit argument' ' + test_when_finished "rm -rf repo" && + git init repo && + ( + cd repo && + test_commit initial && + test_must_fail git history drop 2>err && + test_grep "command expects a single revision" err
Why not `test_cmp` since it’s a fixed error? Same for a few other tests like `errors on unknown revision`.
+ ) +' [snip] +test_expect_success 'errors with invalid --empty= value' ' + test_when_finished "rm -rf repo" && + git init repo && + test_commit -C repo initial && + test_commit -C repo second && + test_must_fail git -C repo history drop --empty=bogus HEAD 2>err && + test_grep "unrecognized.*--empty.*bogus" err +'
Style related I guess. Most tests here use a subshell but this one uses `git -C`? Why is that?
[snip] +test_expect_success 'updates branches on other lines of descent' ' + test_when_finished "rm -rf repo" && + git init repo && + ( + cd repo && + test_commit base && + test_commit target && + git branch theirs && + test_commit ours && + git switch theirs && + test_commit theirs && + + expect_graph --branches <<-\EOF && + * theirs + | * ours + |/ + * target + * base + EOF
Oh, `expect_graph` is a cool tool.
+ + git history drop target && + + expect_graph --branches <<-\EOF + * ours + | * theirs + |/ + * base + EOF + ) +' [snip]