Git 2.35: Stash refinements, conflict-solver upgrades, and a peek at reftable

The open source Git project has shipped Git 2.35, incorporating contributions from more than 93 developers, 35 of whom are new to the project. This release touches everything from stash mechanics and merge-conflict presentation to SSH key rotation and a foundational change in how Git could eventually store references. Below are the most interesting changes to land since the 2.34 release.

Stash only what you’ve staged

git stash has long been the default tool for setting aside work-in-progress, but it always operated on the full set of modified tracked files. If you had already staged part of your changes with git add -p and then decided you needed to stash everything, your only option was to interactively re-select hunks with git stash -p.

Git 2.35 adds a --staged mode that stashes exactly what is in the index, leaving working-tree changes alone. The behavior mirrors git commit in that only staged content is captured, but instead of creating a commit it writes a new stash entry. Recovery works as usual with git stash pop.

More control over git describe output in git log

The %(describe) format specifier added in Git 2.33 let users fold git describe output into git log formatting, with options passed as %(describe:match=,exclude=). That specifier gains two new options in this release: %(describe:tags=) to include lightweight tags, and %(describe:abbrev=) to control the hexadecimal abbreviation length of object identifiers.

$ git log -8 --format='%(describe:exclude=*-rc*,abbrev=13)'
v2.34.1-646-gaf4e5f569bc89
v2.34.1-644-g0330edb239c24
v2.33.1-641-g15f002812f858
v2.34.1-643-g2b95d94b056ab
v2.34.1-642-gb56bd95bbc8f7
v2.34.1-203-gffb9f2980902d
v2.34.1-640-gdf3c41adeb212
v2.34.1-639-g36b65715a4132

The result is a cleaner alternative to piping git log into a separate git describe invocation for each commit:

$ git log -8 --format='%H' | xargs git describe --exclude='*-rc*' --abbrev=13

SSH signing: key rotation and broader key types

Following the introduction of SSH object signing in 2.34, this release addresses the practical problem of rotating trusted keys. The allowed signers file stores identities and public keys of those you trust, but swapping a collaborator’s entry to a new key would invalidate signatures made with the old one, and keeping both keys would accept new signatures from the retired key.

Git 2.35 now honors OpenSSH’s valid-before and valid-after directives when verifying an object. Verification checks that the signature was valid at the time the objectwas created, allowing key rotation without retroactively invalidating earlier signed objects.

The release also relaxes the interpretation of the user.signingKey configuration. Previously, a value beginning with “ssh-” was treated as a literal key and anything else as a file path. Git 2.35 lets you specify literal keys whose type doesn’t begin with “ssh-”, including ECDSA keys.

Conflict markers: diff3 gets a more compact sibling

The merge.conflictStyle setting controls how conflict markers are rendered. The default “merge” style shows only the two sides, while “diff3” additionally shows the merge base. Git 2.35 introduces “zdiff3”, which combines diff3’s merge-base context with a zealous trimming of common lines at the start and end of each conflicted hunk.

In this example, merging two branches that both add content after a placeholder comment with the default style shows the familiar conflict block:

1,
foo,
bar,
<<<<<<< HEAD
=======
quux,
woot,
>>>>>>> side
baz,
3,

Switching to diff3 reveals the placeholder comment in the merge base, plus the full changed content from both branches:

1,
<<<<<<< HEAD
foo,
bar,
baz,
||||||| 60c6bd0
# add more here
=======
foo,
bar,
quux,
woot,
baz,
>>>>>>> side
3,

With zdiff3, the lines that both sides added in common (“foo”, “bar”, and “baz”) are moved outside the conflicted region entirely, leaving a smaller and clearer conflict to resolve:

1,
foo,
bar,
<<<<<<< HEAD
||||||| 60c6bd0
# add more here
=======
quux,
woot,
>>>>>>> side
baz,
3,

Performance work in diff and grep-adjacent tools

The --histogram diff algorithm received a substantial performance upgrade in this release, which should make it notably faster in many workloads. The --color-moved-ws option—which controls whitespace handling when --color-moved is active—also saw multiple performance improvements. If you use either option, upgrading to 2.35 should make the operations snappier without any changes to your invocation.

git jump learns to narrow merge conflicts

The git jump contrib script populates Vim’s quickfix list with locations of merge conflicts, grep matches, or diff hunks. In this release, git jump merge accepts a pathspec to limit which conflicts are shown. To focus on just a subdirectory:

$ git jump merge -- foo

Or skip a directory entirely with a negative pathspec:

# Skip any conflicts in the Documentation directory for now.
$ git jump merge -- ':^Documentation'

Filters can finally handle large files on Windows

Git’s clean and smudge filters, which transform content between the working tree and the index, historically represented object lengths with the C unsigned long type. On LLP64 platforms like Windows, that type is only 4 bytes wide, capping files at 4GB. The effort to standardize on the wider size_t type continues in Git 2.35; filters can now address files larger than 4GB even on LLP64 platforms. This matters particularly for tools like Git LFS whose filter implementations operate on large objects.

Applying empty patches with git am

Mail-based workflows that apply patch series with git am may hit a stumbling block when the mailbox includes the cover letter—a mail with no patch content. Previously, git am would drop into an empty state that required manual intervention:

$ git am /path/to/mailbox
Applying: [...]
Patch is empty.
When you have resolved this problem, run "git am --continue".
If you prefer to skip this patch, run "git am --skip" instead.
To restore the original branch and stop patching, run "git am --abort".

Git 2.35 adds an --empty= option to control what happens when the mbox contains a message without a patch. The three modes halt processing, skip the empty message, or apply it as an empty commit while retaining its log message. If you’re already in the middle of an am session and forgot --empty, you can use the new git am --allow-empty to apply the current empty patch and continue.

Sparse index and sparse-checkout updates

The sparse index, which keeps the index compact in partially checked-out repositories, received broader command integration. Newly supported commands include git reset, git diff, git blame, git fetch, git pull, and a new mode of git ls-files, joining the earlier work in git status, git add, and git commit.

The newer git sparse-checkout set subcommand now carries the full options set of the deprecated git sparse-checkout init. For example, enabling cone mode and checking out the foo directory is now a single command:

$ git sparse-checkout set --cone foo

A first step toward reftable

Git has always stored references either loose as individual files under .git/refs or bundled into a single packed file at .git/packed_refs. For repositories holding enormous numbers of refs, a block-oriented format can perform better, and JGit has already shipped a format called reftable for that purpose. Git 2.35 includes the first import of its own reftable backend implementation.

This is an initial import only: the backend is not yet wired into the refs system, so you can’t choose reftable storage yet. But the groundwork is now in place for a future integration, likely to matter most in large-scale environments.

Beyond the headline changes

The features covered above are the most visible additions in Git 2.35, but the release notes list many more fixes and refinements. The full changelog for version 2.35 is available in the Git repository, alongside notes for every prior release if you want to trace how a particular behavior evolved.

One detail worth repeating: some of these changes reached users earlier than expected. Git for Windows shipped them in its 2.34 release, so if you are on that platform you may already have access to parts of this feature set.