A New Lightweight Way to Edit History

Interactive rebase has long been the go-to tool for rewriting commits, but its full machinery — updating the working tree and index, managing todo lists, pausing for conflict resolution — can feel heavy for small edits. Git 2.54 introduces an experimental git history command built for precisely those cases, supporting two operations: reword and split.

git history reword <commit> opens your editor on the specified commit's message, rewrites it in place, and updates any descendant branches. It skips touching the working tree or index entirely, and it can even run in a bare repository.

git history split <commit> walks you through selecting which hunks to carve out into a new parent commit, using an interface familiar to anyone who has worked with git add -p:

$ git history split HEAD
diff --git a/bar b/bar
new file mode 100644
index 0000000..50810a5
--- /dev/null
+++ b/bar
@@ -0,0 +1 @@
+bar
(1/1) Stage addition [y,n,q,a,d,p,?]? y 

After you make your selections, Git creates a new commit holding those changes, placed as the parent of the original commit (which keeps whatever hunks you didn't select), and rewrites descendant branches to the updated history.

The command is intentionally limited: it won't handle histories containing merge commits, and it refuses any operation that would produce a merge conflict. It's targeted at non-interactive, focused rewrites, not open-ended rebase sessions. Under the hood, it relies on the core machinery of git replay, which has been extracted into a reusable library as part of this work, giving git history the same working-tree-free operation that makes replay suitable for scripting.

Since the command is experimental, expect its interface to evolve. You can try it in Git 2.54 with git history reword and git history split.

Hooks Defined in Configuration

Traditionally, Git hooks have lived only as executable scripts in the hooks directory of your .git folder (or wherever core.hooksPath points). Sharing a hook across repositories meant copying scripts around or pointing every repo at the same directory — none of which lets you mix and match hooks per project.

Git 2.54 adds a configuration-based alternative. Instead of installing a script at .git/hooks/pre-commit, you can define a hook directly in your config:

[hook "linter"]
   event = pre-commit
   command = ~/bin/linter --cpp20 

The hook.<name>.command key specifies what to run, and hook.<name>.event determines which hook event triggers it. Because it's just configuration, the definition can live in your user-level ~/.gitconfig, system-wide /etc/gitconfig, or a repository's local config — letting you define hooks centrally and apply them broadly.

You can also define multiple hooks for the same event. Want both a linter and a secrets scanner before every commit? Configure them independently:

[hook "linter"]
   event = pre-commit
   command = ~/bin/linter --cpp20

[hook "no-leaks"]
   event = pre-commit
   command = ~/bin/leak-detector

Git runs them in the order their configuration appears. The traditional script in $GIT_DIR/hooks continues to work and runs last, so existing setups remain unaffected. Use git hook list to see which hooks are configured and where they come from:

$ git hook list pre-commit
global    linter  ~/bin/linter --cpp20
local    no-leaks    ~/bin/leak-detector 

To disable an individual hook without removing its configuration, set hook.<name>.enabled = false — useful when a system-level hook needs to be turned off for a specific repository.

This change also involved modernizing Git's internal hook invocation. Many built-in hooks previously run through ad-hoc code paths (such as pre-push, post-rewrite, and the various receive-pack hooks) now go through the new hook API, giving them all the same configuration-based capabilities.

Geometric Repacking as the Default Maintenance Strategy

Git 2.52 introduced the geometric strategy within git maintenance, which inspects repository contents and combines packfiles into a geometric progression by object count when possible — condensing storage without a full garbage collection. In 2.52 it was opt-in via maintenance.strategy; in Git 2.54, it becomes the default for manual maintenance runs.

Running git maintenance run without a strategy now uses the geometric approach instead of the traditional gc task. Practically, that means your repositories get maintained more efficiently out of the box: the geometric strategy avoids expensive all-into-one repacks, incrementally combining packs when possible and falling back to a full gc only when consolidating the entire repository into a single pack would be beneficial. It also keeps commit-graph, reflogs, and other auxiliary data structures current throughout.

If you already set maintenance.strategy = geometric, nothing changes. If you hadn't specified a strategy, you'll automatically benefit from the new behavior. The classic gc approach remains available via maintenance.strategy = gc.

Interactive staging and replay updates

git add -p gets two usability tweaks. When you page through hunks with J and K, Git now marks each one with whether you have already accepted or skipped it, so you do not have to keep track. A new --no-auto-advance flag also changes behavior between files: instead of moving forward automatically after you have decided on the final hunk, the session stays on that file, and you can use < and > to switch files manually — handy when you want to review all decisions before committing to them.

The experimental git replay command grows up a little more. It now performs atomic reference updates by default, where previously it printed update-ref commands to stdout. There is also a new --revert mode that reverses a range of commits, support for dropping commits that go empty during replay, and the ability to replay down to the root commit.

HTTP retries and log -L improvements

Git’s HTTP transport now understands HTTP 429 “Too Many Requests”. A 429 used to be an immediate fatal error; now Git can retry, respecting the server’s Retry-After header, with a fallback delay from the new http.retryAfter setting. The http.maxRetries and http.maxRetryTime config keys cap the number and duration of retry attempts.

git log -L, which traces the history of a line range, was previously wired to a bespoke output path that skipped most of the normal diff machinery. That meant options like -S, -G, --word-diff, and --color-moved were silently ignored. In 2.54, -L output goes through the standard diff pipeline, so those options finally work together. For example, tracking strbuf_addstr() in strbuf.c while only showing commits that add or remove len inside that function works as expected:

$ git log -L :strbuf_addstr:strbuf.c -S len --oneline -1
a70f8f19ad2 strbuf: introduce strbuf_addstrings() to repeatedly add a string

diff --git a/strbuf.c b/strbuf.c
--- a/strbuf.c
+++ b/strbuf.c
@@ -316,0 +316,9 @@
+void strbuf_addstrings(struct strbuf *sb, const char *s, size_t n)
+{
+      size_t len = strlen(s);
+
+      strbuf_grow(sb, st_mult(len, n));
+      for (size_t i = 0; i < n; i++)
+              strbuf_add(sb, s, len);
+}

-L scopes the output to the function, and -S narrows it to the commits that change the symbol you are hunting for.

MIDX, status, and signing tweaks

Incremental multi-pack indexes gain a compaction feature. Smaller MIDX layers (and their reachability bitmaps) can be merged together to keep the layer chain from growing without bound, which is a step toward making incremental MIDXs sustainable for long-lived repositories.

git status has a new status.compareBranches option. By default, it reports how your branch relates to its upstream — for example, “ahead of origin/main by 3 commits”. Setting this option lets you compare against your push remote as well, or both, which matters in triangular workflows where fetch and push use different remotes:

[status]
   compareBranches = @{upstream} @{push}

Signing behavior is also adjusted. A signature made with a GPG key that has since expired is still a valid signature — the timestamp of the signature matters, not the key’s current state. Previously Git shaded such signatures red, which implied invalidity. Now they are displayed as good.

Rebase trailers, blame algorithms, and internals

Adding the same trailer to every commit in a series previously required a construct like git rebase -x 'git commit --amend --no-edit --trailer="Reviewed-by: A U Thor <[email protected]>"'. Git 2.54 adds a --trailer option to git rebase itself, so this becomes a single option:

git rebase --trailer "Reviewed-by: A <[email protected]>"

The trailer is appended via the interpret-trailers machinery to each rebased commit.

git blame gains a --diff-algorithm option so you can pick histogram, patience, minimal, or another algorithm for blame computation. Depending on history shape, that can yield meaningfully clearer output. The histogram algorithm itself also gets a fix: during the post-processing “compaction” phase that shifts and merges change groups, Git could move a group across the anchor lines histogram had selected, producing a technically correct but visually redundant diff. Now Git detects that situation and re-diffs the affected region.

Internally, the object database (ODB) API has been refactored to a pluggable backend design. Functions like read_object(), write_object(), and for_each_object() now dispatch through per-source function pointers. There is no user-visible change yet, but the groundwork is laid for alternative storage backends.

Two other experimental features see new capabilities. git backfill, which downloads missing blobs in a partial clone, now accepts revision and pathspec arguments, so you can scope it to, say, git backfill main~100..main or git backfill -- '*.c' instead of always walking everything reachable from HEAD. And alias names are no longer restricted to ASCII letters, digits, and hyphens. A new subsection-based syntax allows alias names like “hämta” or “状態”:

[alias "hämta"]
    command = fetch

That form supports any character except newlines and NUL bytes, is matched case-sensitively as raw bytes, and shell completion has been updated for these aliases. The classic [alias] co = checkout syntax remains for ASCII names.

For the full list of changes, consult the release notes for 2.53 and 2.54, or earlier versions in the Git repository.