[PATCH v8 00/11] builtin/history: introduce "drop" subcommand
From: Patrick Steinhardt <hidden>
Date: 2026-07-01 11:35:43
Hi,
this small patch series introduces the new "drop" subcommand for
git-history(1). As a reader might guess, the command does exactly that:
given a commit, it will drop that commit from the commit history and
replay descendant branches on top of it.
Changes in v8:
- Pass `RESOLVE_REF_READING` to make `refs_resolve_ref_unsafe()`
return a NULL pointer when it cannot resolve the reference.
- Drop unneeded code that sets `head_target = "HEAD"` on detached
HEAD.
- Add a test case that verifies that we can drop commits with
"--update-refs=head" and a detached HEAD.
- Link to v7: https://patch.msgid.link/20260629-b4-pks-history-drop-v7-0-6e9392a957d8@pks.im
Changes in v7:
- Expose `replay_result_queue_update()` so that we don't have to
duplicate its functionality.
- Add missing SOB.
- Link to v6: https://patch.msgid.link/20260615-b4-pks-history-drop-v6-0-2e329e536d78@pks.im
Changes in v6:
- Fix bad interactions of DRY_RUN with UPDATE_HEAD
- Link to v5: https://patch.msgid.link/20260611-b4-pks-history-drop-v5-0-34d35725559c@pks.im
Changes in v5:
- Reject UPDATE_ORIG_HEAD without UPDATE_HEAD.
- Link to v4: https://patch.msgid.link/20260610-b4-pks-history-drop-v4-0-70d5f0ae8c25@pks.im
Changes in v4:
- Remove the `SKIP_REF_UPDATES` flag in favor of a new `UPDATE_HEAD`
flag, as suggested by Phillip.
- Rename `reset_head()` to `reset_working_tree()`. This better matches
the new scope of the function, and it helps us to catch any
in-flight patches that would now have to set the `UPDATE_HEAD` flag.
- Link to v3: https://patch.msgid.link/20260608-b4-pks-history-drop-v3-0-84ca8e43e937@pks.im
Changes in v3:
- Fix commit message typos.
- Make `update_orig_head` and `skip_ref_updates` mutually exclusive.
- Use fancy revisions to specify the commit to drop in the example
section.
- Detect conflicting changes in the index/working tree in dry-run
mode.
- Consistently use a subshell.
- Rename `RESET_HEAD_ORIG_HEAD` to `RESET_HEAD_UPDATE_ORIG_HEAD`.
- Link to v2: https://patch.msgid.link/20260603-b4-pks-history-drop-v2-0-742cb5b5176d@pks.im
Changes in v2:
- Reworked `update_worktree()` to use `reset_head()`, which required a
bunch of changes to `reset_head()`.
- Consistently mention the commit that cannot be dropped as part of
error messages.
- Adapt error message to not use backticks anymore.
- Drop redundant "--graph" flag in a test helper.
- Link to v1: https://patch.msgid.link/20260601-b4-pks-history-drop-v1-0-643e32340d55@pks.im
Thanks!
Patrick
---
Patrick Steinhardt (11):
read-cache: split out function to drop unmerged entries to stage 0
reset: drop `USE_THE_REPOSITORY_VARIABLE`
reset: rename `reset_head()`
reset: modernize flags passed to `reset_working_tree()`
reset: introduce dry-run mode
reset: introduce ability to skip updating HEAD
reset: allow the caller to specify the current HEAD object
reset: stop assuming that the caller passes in a clean index
replay: expose `replay_result_queue_update()`
builtin/history: split handling of ref updates into two phases
builtin/history: implement "drop" subcommand
Documentation/git-history.adoc | 38 ++-
builtin/history.c | 284 ++++++++++++++++++---
builtin/rebase.c | 41 +--
read-cache-ll.h | 1 +
read-cache.c | 12 +-
replay.c | 8 +-
replay.h | 5 +
reset.c | 102 +++++---
reset.h | 51 ++--
sequencer.c | 17 +-
t/meson.build | 1 +
t/t3454-history-drop.sh | 561 +++++++++++++++++++++++++++++++++++++++++
12 files changed, 1000 insertions(+), 121 deletions(-)
Range-diff versus v7:
1: 4b8702dbff = 1: 8145bb1408 read-cache: split out function to drop unmerged entries to stage 0
2: 1794f27cc4 = 2: 022d65d3db reset: drop `USE_THE_REPOSITORY_VARIABLE`
3: 2cb6c21f6a = 3: c37abd69d3 reset: rename `reset_head()`
4: 55fb296c39 = 4: 84f80ff33b reset: modernize flags passed to `reset_working_tree()`
5: b57d52b5ca = 5: 2465dcf8ec reset: introduce dry-run mode
6: a54dbccb17 = 6: 65b1853ce4 reset: introduce ability to skip updating HEAD
7: f22a1c9b70 = 7: acfc768574 reset: allow the caller to specify the current HEAD object
8: 5ec7867767 = 8: 8e2d3fa7b3 reset: stop assuming that the caller passes in a clean index
9: d8c9548408 = 9: 80015c6cec replay: expose `replay_result_queue_update()`
10: d01ce11892 = 10: b518dcd5ef builtin/history: split handling of ref updates into two phases
11: 1ee3600b98 ! 11: 963636e72c builtin/history: implement "drop" subcommand
@@ builtin/history.c: static int cmd_history_split(int argc,
+
+ *changed = false;
+
-+ head_target = refs_resolve_ref_unsafe(get_main_ref_store(repo),
-+ "HEAD", RESOLVE_REF_NO_RECURSE,
++ head_target = refs_resolve_ref_unsafe(get_main_ref_store(repo), "HEAD",
++ RESOLVE_REF_NO_RECURSE | RESOLVE_REF_READING,
+ NULL, &head_flags);
+ if (!head_target)
+ return error(_("cannot look up HEAD"));
-+ if (!(head_flags & REF_ISSYMREF))
-+ head_target = "HEAD";
+
+ for (size_t i = 0; i < result->updates_nr; i++) {
+ if (!strcmp(result->updates[i].refname, head_target)) {
@@ t/t3454-history-drop.sh (new)
+ )
+'
+
++test_expect_success '--update-refs=head can rewrite detached HEAD' '
++ test_when_finished "rm -rf repo" &&
++ git init repo --initial-branch=main &&
++ (
++ cd repo &&
++ test_commit first &&
++ test_commit second &&
++ test_commit third &&
++ git switch --detach HEAD &&
++
++ git history drop --update-refs=head second &&
++
++ expect_log HEAD <<-\EOF &&
++ third
++ first
++ EOF
++ expect_log main <<-\EOF
++ third
++ second
++ first
++ EOF
++ )
++'
++
+test_expect_success 'conflict with replayed commit aborts cleanly' '
+ test_when_finished "rm -rf repo" &&
+ git init repo &&
---
base-commit: 1666c1265231b0bc5f613fbbf3f0a9896cdef76e
change-id: 20260601-b4-pks-history-drop-28f6c6399e7b