Git 2.56.0 landed on September 28, 2026, tagged by Junio C Hamano with changes from more than 104 contributors, 39 of them new. It is the biggest Git release in a while by contributor count, and the headline items are a new git refs subcommand family, a conflict-resolution-aware git add mode, and several fixes to commands that had become unusably slow on large repositories.

There is one change that can break a script without announcing itself: asking git for help now exits 0 instead of 129. Everything else in the release is additive or opt-in. And if you have been waiting to move off the files-based ref backend, reftable is still not the default — that is a Git 3.0 change, and it is the one to plan for now.

Key takeaways:

  • git add --resolved stages paths whose conflict markers are gone, and refuses to stage anything if any marker survives.
  • git refs create/delete/update/rename gives ref manipulation a stable command surface instead of script-shaped update-ref invocations.
  • Several large-repository fixes are substantial: a Chromium checkout path-limited git diff went from about eight minutes to 0.07 seconds.
  • Help output now exits 0, not 129. If you assert on that exit code anywhere, fix it before you upgrade.
  • Reftable and SHA-256 arrive as defaults in Git 3.0, not in 2.56.

The one change that can break your scripts

Option parsing in git rev-parse --parseopt and most git subcommands now exits 0 instead of 129 when a user asks for help directly with -h or --help. In isolation this is a reasonable cleanup. In practice it is a silent behaviour change for anything that checked the exit status:

# Breaks after upgrading to 2.56 if your helper probes for help
git some-subcommand --help
if [ $? -eq 129 ]; then
  echo "not supported"
fi

If you maintain wrappers around git, this is the one to grep for before you roll the upgrade out.

git add –resolved

Marking a merge conflict as resolved has always meant staging the file manually and hoping. git add --resolved makes the operation conditional on the conflict actually being gone:

git add --resolved <pathspec>

It stages unmerged paths matching the pathspec only when no conflict markers remain in the working tree, including binary files and deletions. If any path in the selection still has leftover markers, the command refuses to stage anything at all and reports which ones:

fatal: the following paths still have conflict markers:

That all-or-nothing behaviour is the point. It is a check, not a convenience. It cannot be combined with -u or -A.

A proper git refs subcommand family

Four new subcommands under git refs give ref manipulation a documented interface:

git refs create [--message=<reason>] [--no-deref] [--create-reflog] <ref> <new-value>
git refs delete [--message=<reason>] [--no-deref] <ref> [<old-value>]
git refs update [--message=<reason>] [--no-deref] [--create-reflog] <ref> <new-value> [<old-value>]
git refs rename [--message=<reason>] <old-ref> <new-ref>

The semantics carry over from git update-ref: in update, an all-zeroes <new-value> deletes the ref, and an all-zeroes <old-value> asserts that the ref does not exist. Supplying the old value is how you make a ref update a compare-and-swap instead of a blind write.

This is a quiet but real improvement. If you have ever wrapped update-ref in a shell function with careful quoting, you can replace it with something whose argument grammar is documented.

Cleaning up merged branches

git branch --delete-merged takes a pattern rather than a list of branch names, which makes it usable directly against remote-tracking refs:

# See what would go, without touching anything
git branch --dry-run --delete-merged 'origin/*' 'topic-*'

It skips a branch when its upstream is gone, when it is checked out in a worktree, when deleting it would update an upstream that still needs it, when it is the local upstream of a branch being kept, or when branch.<name>.deleteMerged is set to false for it. That last one is a per-branch opt-out, and explicit git branch -d is unaffected by it. Branches that are not merged are silently skipped rather than treated as an error; use -D when you really mean force.

Performance: the fixes that matter at scale

Important caveat before the numbers: Git’s own release notes contain no benchmark table. The figures below come from GitHub’s engineering write-up of the release, and describe measured workloads rather than synthetic ones.

ChangeMeasured effect
merge-base early exitLinux kernel, git merge-base --all v4.8 v4.9: 167,441 to 3,887 traversal steps, 0.29s to 0.01s
merge-base early exitOne monorepo traversal: 0.68s to 0.01s. On two production monorepos, roughly 70x faster in one and about 20x on average in the other
Path-walk repackRepacking a recent clone of microsoft/fluentui with deltas forced to recompute: 558.5 MB bitmapped versus 164.4 MB with --path-walk, about 71% smaller
Index cache-scan quadratic fixPath-limited working-tree diff in a Chromium checkout with roughly 500,000 index entries: about 8 minutes to 0.07 seconds
Reftable tombstone handlingIn-tree reftable performance tests: about 13s to 0.2s
Packfile loadingFixed quadratic behaviour when loading packfiles; quoted against a repository with 37,815 packs
Figures reported by GitHub engineering in its Git 2.56 highlights post. These are workload-specific measurements, not a general speedup factor.

The Chromium case is the one worth understanding. It was not slow because git was slow; it was slow because the index cache scan was quadratic in the number of index entries. A 500,000-entry checkout is not exotic — it is roughly what a monorepo checkout looks like — so this single fix removes a failure mode that “git is fine on my laptop” testing never surfaces.

The repack feature for partial clones

git repack --drop-filtered lets a partial clone shed blobs it had already been ignoring, instead of keeping them around indefinitely:

# Requires -a, implies -d, and needs a promisor remote
git repack -a --filter=blob:limit=1m --drop-filtered --dry-run

Only --filter=blob:limit=<n> is accepted, and it cannot be combined with --filter-to or the bitmap index options. --dry-run here is genuinely useful: it lists the object IDs that would be dropped, one per line, so you can see the effect before committing to it.

Smaller changes worth knowing

  • git bisect --reset-when-found[=<where>] returns the checkout to original (the default) or found after a bisection identifies a commit, so scripted bisections stop leaving your tree on some arbitrary commit.
  • git replay --linearize drops merge commits and flattens history, matching git rebase --no-rebase-merges. Experimental, and cannot be combined with multiple branches or --contained.
  • git history drop <commit> removes a commit from history, with --dry-run, --update-refs and --empty handling. Experimental, and does not work on histories containing merges.
  • git cat-file --batch-command gained remote-object-info, which fetches object metadata such as size over protocol v2 without downloading the object.
  • git rev-list --missing-only requires --missing=print or --missing=print-info.
  • git log --graph now indents visual roots by default. Opt out with --no-graph-indent or log.graphIndent=false. Expect golden-file tests to notice.
  • git worktree add and git checkout now error on an ambiguous remote-tracking-branch DWIM instead of guessing. This is a behaviour change that surfaces sloppy branch setups rather than fixing them.

New configuration options

VariableDefaultPurpose
branch.<name>.deleteMergedtrueSet to false to exempt a branch from git branch --delete-merged
log.graphIndenttrueIndentation of visual roots in git log --graph
fetch.followRemoteHEADmirrors the per-remote settingGlobal default for how fetch treats a remote’s HEAD: create, warn, always, never
promisor.acceptFromServerUrlnoneGlob allowlist for server-advertised URLs accepted from a promisor remote
Configuration added or documented in 2.56. Note that the release notes spell the last one promisor.acceptFromServerURL while the configuration documentation uses promisor.acceptFromServerUrl; check your git version before setting it.

What is still coming in Git 3.0

Reftable is available in 2.56. It is not the default. files remains the default ref storage format; reftable becomes the default only in a build with breaking changes enabled, which means Git 3.0. There is no release date set for 3.0.

What 3.0 is planned to change:

  • Default ref storage for newly created repositories: files to reftable
  • Default object hash for new repositories: sha1 to sha256
  • Rust becomes mandatory; the build-time opt-out is removed
  • Default branch name main, and safe.bareRepository default moves from all to explicit
  • Removals including commit grafts, git-pack-redundant, name-rev --stdin, git-whatchanged, core.commentString=auto and core.preferSymlinkRefs=true

Git’s own documentation names the prerequisite: alternative implementations such as JGit, libgit2 and Gitoxide need to support reftable first. That is the honest reason 3.0 is not imminent, and it is worth tracking rather than guessing at. It is also worth keeping in view that a Git installation is a supply chain like any other: the executables, the hooks and the CI jobs around them are all trusted code paths, which is the same surface covered in how AI agents attack software supply chains.

You can still opt in on 2.56 with init.defaultRefFormat=reftable, and migrate an existing repository with git refs migrate --ref-format=reftable. Two documented limits before you try it on anything that matters: it is not possible to migrate a repository that has worktrees, and there is no way to block concurrent writes during an ongoing migration. Unregister any scheduled maintenance first.

Should you upgrade?

If you work in large checkouts, the merge-base, index scan and repack fixes justify the upgrade on their own. If you maintain tooling around git, do the exit-code audit first. If you are planning a reftable migration, 2.56 is a good place to start testing it on a copy of a real repository rather than a toy one, because the worktree limitation and the concurrent-write gap are exactly the kind of thing that only shows up on real repositories. If the repository is shared or driven by automation, review its access control at the same time; protecting a Git repository from automated abuse covers that side.

References

Related Tools

Try these companion tools on DevStackIO Tools: