Git 2.55 arrives with incremental repacking and a new fixup workflow

The open source Git project released Git 2.55 with features and bug fixes from over 100 contributors, 33 of them new. Highlights include a new type of incremental repacking that can cut metadata maintenance costs, plus a subcommand that lets you fold uncommitted changes into an earlier commit without reaching for rebase.

Incremental multi-pack index chains meet geometric repacking

Git stores repository contents as objects—commits, trees, and blobs—which usually live in compressed packfiles. Large repositories accrue many packs over time, so a multi-pack index (MIDX) gives Git a single index over several packs at once, allowing it to quickly locate objects without opening each pack's individual index.

Prior releases introduced incremental MIDX format, which stores the MIDX as a chain of layers rather than as one file that covers every pack. This matters for upkeep: a single-file MIDX is simple to read, but updating it requires a write that covers the whole repository, even for small changes. An incremental chain lets Git append a new layer for a newly created pack without invalidating older layers.

Git 2.55 now lets git repack write those chains directly. By default, this mode is append-only:

$ git repack --write-midx=incremental 

That alone is useful for minimizing metadata writes during a maintenance run, but an append-only chain cannot grow without bound. If every run adds a new layer, the chain itself eventually becomes the thing needing maintenance. Git 2.55 therefore supports combining --write-midx=incremental with geometric repacking:

$ git repack --write-midx=incremental --geometric=2 -d

Used together, each repack writes a new tip layer and then decides whether adjacent layers can be compacted. The default rule comes from repack.midxSplitFactor: if the object count in the newer layers grows large enough relative to the next older layer, Git merges those layers into a single replacement. Otherwise the older layers are untouched.

Broadly, the algorithm proceeds like this:

  1. Pick un-MIDX'd packs as geometric repacking candidates. If the tip MIDX layer has at least repack.midxNewLayerThreshold packs, include those too.
  2. Apply the normal geometric repacking rule to the candidate set, writing a new tip MIDX layer over the resulting packs.
  3. Compact adjacent MIDX layers while the newer layer(s) hold more than a fraction (governed by repack.midxSplitFactor) of the next deeper layer's object count.

A typical scenario: the repository already has an incremental MIDX chain with older layers at the left and the tip at the right. Routine activity produces new packs that are not yet covered, so the next maintenance run both repacks and decides how much of the chain to rewrite.

Diagram showing a chain of MIDX layers with newly written packs.

If the tip layer has fewer than the threshold number of packs, the un-MIDX'd packs alone are the repacking candidates. Git writes a new pack and a new tip MIDX layer, leaving existing layers alone. The more interesting case arises after the tip layer has accumulated enough packs to hit repack.midxNewLayerThreshold; at that point tip-layer packs can join the candidates.

Geometric repacking then applies its usual local rule. Git examines whether a suffix of the candidate packs can be rolled up while preserving the geometric progression. The first attempt might fail—for instance, if it grabs only the smallest tip-layer pack plus the new packs, leaving a pack to the left that is far too small to preserve the invariant.

Diagram indicating the first geometric split is too small.

Once the split point satisfies the geometric condition (a newer pack immediately to the left of the selected suffix is at least twice its size, for the default factor), Git rolls up exactly those packs into one new pack:

Diagram showing how moving the split left finds a geometric roll-up.

With that pack written, Git creates a new tip MIDX layer over the surviving old pack and the new roll-up. But compacting packfiles does not guarantee the MIDX chain is tidy—adjacent small layers may still accumulate. Git runs the same newer-vs-older heuristic on the MIDX layers themselves, compacting metadata when a layer is large enough relative to its neighbor:

Diagram showing the new tip layer cannot compact with its neighbor.

That compaction is deliberately metadata-only. Git does not recopy objects from the underlying packs; it writes a new MIDX layer that covers the same set of packfiles, then checks the next older layer. When the compacted layer is no longer large enough to justify merging further, tightening stops, and the deeper layers remain untouched. That property is what makes the maintenance incremental.

Diagram showing compaction stops before rewriting the deeper layer.

The final result balances two extremes. A single-file MIDX keeps lookups simple but forces expensive full-file rewrites on small updates. A purely append-only chain keeps each write cheap but grows without bound. This geometric scheme keeps the number of layers logarithmic relative to total objects, while rewriting newer, smaller layers more freely than older, larger ones.

The feature integrates with existing repack machinery: packs created during the run that are not yet covered are always candidates for the geometric repack; deeper layers are never touched unless the tip layer's pack threshold is met. For repositories receiving an ongoing stream of objects, routine maintenance can now update pack metadata incrementally instead of rewriting a whole-repository MIDX each time.

A safer git history fixup

When you notice while polishing a series that a working tree change belongs in an earlier commit, the traditional route is to create a fixup commit and later autosquash it into place:

$ git commit --fixup=<commit> 
$ git rebase --autosquash <commit>^

That works, but it spells out the mechanics rather than the intent. Git 2.55 builds on the experimental git history command introduced in the previous release with a fixup subcommand that folds the current staged changes straight into an earlier commit:

$ git history fixup <commit>

In a concrete example, the first commit added a pancake recipe, followed by additional commits. When the recipe turns out to be missing maple syrup, staging that one-line change and running git history fixup <commit> applies the fix to the original recipe commit and replays the descendants on top.

An animated gif showing Here is a small example. The first commit introduced a pancake recipe, followed by a few more commits on top. Later, we realize that the recipe was missing maple syrup. After staging that one-line change, git history fixup <commit> folds it into the original recipe commit and replays the descendant commits on top.

The staged change lands inside the target commit itself. The target keeps its original message and authorship by default—unless told otherwise with --reedit-message—and Git rewrites the remaining commits so the branch ends at an equivalent history with the fix in the right spot.

The command is intentionally cautious. Since it reads from the index, it requires a working tree and will not run in a bare repository, and if applying the staged change triggers a conflict, it stops rather than interrupting the rewrite in an error state.

Parallel hooks, faster status, and safer merges

Git 2.55 continues to build on the config-based hooks feature introduced in 2.54. Previously, hooks defined in Git configuration ran sequentially by default. Now, compatible hooks can execute in parallel when each declares hook.<name>.parallel = true. A project with independent pre-commit hooks for linting and testing, for instance, can have both run concurrently. The number of simultaneous jobs is configurable via hook.jobs, hook.<event>.jobs, or the command-line flag git hook run -j. Hooks that depend on shared state, such as those inspecting the index or working tree, continue to run serially.

The built-in filesystem monitor, previously limited to macOS and Windows, now works on Linux. When core.fsmonitor is enabled, commands like git status can query a long-running daemon for changed paths instead of scanning the entire working tree. The Linux implementation relies on inotify, which needs no elevated privileges but requires one watch per directory. Repositories with very large directory trees may need to increase the fs.inotify.max_user_watches system limit. As with other platforms, network-mounted repositories remain opt-in.

Faster bitmaps, smarter packing

Generating reachability bitmaps has historically been a slow part of maintenance tasks like git repack --write-midx-bitmaps. Git 2.55 speeds up bitmap generation by avoiding unnecessary tree recursion, reusing already-computed selected bitmaps, caching object positions, and sorting bitmaps before XORing them. In benchmarks from the patch series, one large repository’s bitmap generation time dropped from roughly 612 seconds to 294 seconds.

The same work improves pseudo-merge bitmaps, which group related refs so traversals can combine precomputed bit arrays. Pseudo-merges had previously been shown to make a full git rev-list --objects --use-bitmap-index traversal nearly 20 times faster, but at the cost of nearly doubling generation time. After these changes, they retain most of their traversal speedup while adding far less overhead to generation.

For partial clones and filtered packs, git pack-objects --path-walk now works with filters including blob:none, blob:limit=<n>, tree:0, object:type=<type>, sparse:<oid>, and compatible combine: filters. The path-walk mode groups objects by path before a second compression pass, which can produce better deltas. In one benchmark on Git’s own repository, a blob-less path-walk repack produced a pack roughly 16% smaller, with slower fresh-delta computation as the tradeoff.

New command: git format-rev

An experimental git format-rev command is new in this release. Unlike git log, which walks a history range, this command is designed for formatting commits as they are encountered one at a time or embedded in other text. For example, you can pipe output from git last-modified directly into git format-rev to replace commit IDs with author names, without spawning a new Git process per row:

$ git last-modified | perl -F'\t' -lane ' 
  chomp($F[0] = qx(git show -s --format=%an $F[0])); 
  print join "\t", @F 
' 
Junio C Hamano	builtin/commit.c 
[...]
$ git last-modified | 
  git format-rev --stdin-mode=text --format=%an 
Junio C Hamano	builtin/commit.c 
[...]

The command’s text mode can also rewrite full commit object names in freeform text, which makes it useful in commit-message hooks and scripting workflows.

Security and workflow refinements

During fetch and push, Git multiplexes multiple streams over one connection: pack data, progress messages printed to stderr, and errors from the remote. Progress messages come from the remote side, so a malicious server could previously include arbitrary terminal control sequences that move the cursor or erase text. Git 2.55 now masks most of those control characters by default while still permitting ANSI color sequences, so colored progress output remains intact.

Merging with local edits gets safer. If you are mid-edit on a file and switch branches with git checkout -m <branch>, and the target branch also modified that path, Git now internally uses an autostash. The conflicted local changes are saved as a stash entry, which you can resolve immediately or reapply later, rather than being presented with a single immediate conflict resolution window.

Projects that publish the same branch to multiple remotes can now use remote groups with push. Groups, configured with remotes.<name> as a whitespace-separated list, were previously available only to git fetch. Pushing to a group is equivalent to pushing to each remote in sequence. Since atomicity requires a single transport connection, --atomic is not supported for group pushes.

For wide histories, git log --graph gains --graph-lane-limit=<n>. Lanes beyond the limit are replaced with ~, keeping output readable when many parallel branches would otherwise flood the terminal.

Selecting the oldest commits in a range no longer requires post-processing. The new --max-count-oldest=<n> option for git rev-list and the git log family selects the oldest n commits directly. Previously, getting the ten oldest commits meant reading and formatting the entire range just to discard most of it with something like tail.

Finally, fetch negotiation gets new include and restrict controls, along with corresponding remote.* configuration. These allow users to require certain refs to be advertised as have lines, or to limit negotiation to a specific set of refs, which helps in repositories where the standard algorithm might skip a ref important for finding common history.

For a complete list of changes, consult the release notes for Git 2.55 or any previous version in the Git repository.