Thread (119 messages) 119 messages, 6 authors, 15h ago

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.adoc
b/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 be
lifted 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]
Keyboard shortcuts
hback out one level
jnext message in thread
kprevious message in thread
ldrill in
Escclose help / fold thread tree
?toggle this help