Skip to content

Highlights from Git 2.23

Take a look at some of the new features in the latest Git release.

Highlights from Git 2.23

The open source Git project just released Git 2.23 with features and bug fixes from over 77 contributors, 26 of them new. Here’s our look at some of the most exciting features and changes introduced since Git 2.22.

Experimental alternatives for git checkout

Git 2.23 brings a new pair of experimental commands to the suite of existing ones: git switch and git restore. These two are meant to eventually provide a better interface for the well-known git checkout. The new commands intend to each have a clear separation, neatly divvying up what the many responsibilities of git checkout, as we’ll show below.

If you’ve tried to list out what’s possible with git checkout, you might have visited the documentation to figure it out. You might have seen the phrase, “switch branches or restore working tree files” and scratched your head. Huh?

Before we specify what exactly git checkout can do, it’s worth being familiar with a few Git-specific bits of terminology—the working copy, the index, and the checked out branch. When someone says “working copy”, they’re referring to the files in your repository as they appear on your hard drive. On the other hand, when someone says “index” (or the less-frequent “staging area” or “cache”), they mean what you have git add-ed, or, what would be committed if you were to run git commit. Finally, when someone says the “checked out branch” they are referring to the branch that you tried to match the contents of in your working copy.

It turns out git checkout can do quite a lot. You can use it to change branches with git checkout <branch> or if you supply --branch, create a new branch (as in git checkout --branch <new-branch>). If you don’t want to switch branches, don’t worry, because git checkout can change individual files, too.  If you write git checkout -- <filename>, you will reset <filename> in your working copy to be equivalent with what’s in your index. If you don’t want to take changes from the index, you can specify an alternative source with git checkout [treeish] -- <filename>.

The new commands, by contrast, aim to clearly separate the responsibilities of git checkout into two narrower categories: operations which change branches and operations which change files. To that end, git switch takes care of the former, and git restore the latter. For our purposes, let’s take a look at each one in more detail.

The simplest invocation of git switch looks like this:

$ git switch my-feature

Switched to branch 'my-feature'

Your branch is up to date with 'origin/my-feature'

In this mode, you can think of git switch as acting like the optionless invocation of git checkout. If you want to replace git checkout --branch (or git checkout -b, for short), you can write:

$ git switch -c my-new-feature

Switched to a new branch 'my-new-feature'

where -c is short for (--create) and can replace your muscle memory for git checkout -b. Or, to start from a designated commit (instead of branching off the currently checked-out commit):

$ git switch -c my-new-feature 0810beaed7

Switched to a new branch 'my-new-feature'

There are a handful of other examples in the documentation, including both more of the above as well as some examples about how to set up tracking branches, orphaned branches, and more.

git restore, on the other hand, makes it much easier to figure out exactly which files will change, what they’ll change to, and where they’ll change. Instead of remembering the tangled semantics of git checkout, git restore provides two options to specify where your restored changes will go. If you pass --worktree (or don’t pass anything at all), the changes go into your working copy. If you pass --staged, they go into your index. Finally, if you pass both, the two are both changed to the same contents.

git restore also makes it easier to understand where the contents you’re restoring come from with the optional --source option. If --source is not specified, the contents are restored from the staging area, otherwise, they’re restored from the specific tree. Note: One other difference between git checkout and git restore is that restore defaults to --no-overlay and will delete tracked files not present in the source tree. See the discussion of --overlay in our 2.22 blog post

For example, if I want a file main.c to have the same contents in both the working copy and the index as it did three commits ago, I could write:

$ git restore --source HEAD~3 --staged --worktree main.c

…and the changes would be applied exactly so. There are lots more examples in the new documentation for git restore.

Keep in mind that these commands are marked as experimental and might make another appearance in a future blog post if they change over the next release. You can read more about each of these in the new switch documentation and restore documentation.



If you’ve ever wanted to write a script around the list of branches your repository has, you might have wrapped an invocation of git branch or git branch <pattern> --list. But, what if your script needs a list of tags or wants to know the commit that those branches point to? For that, you might have turned to git for-each-ref. When run without arguments, git for-each-ref provides a list of all references along with the commits that they point to, like so

$ git for-each-ref
e69a688a234e6cb810c059d25b83de14f88c590a commit refs/heads/master
0810b3ed56098bc240922634e3baa69282fe879d commit refs/heads/my-feature
e69a688a234e6cb810c059d25b83de14f88c590a commit refs/remotes/origin/HEAD

git for-each-ref
can also take multiple patterns, and it shows you the union of all of them: as if you had run git for-each-ref on each, and then dropped all duplicates. Behind the scenes, git for-each-ref tries to figure out a query to determine the list of potentially-matching results, and then it removes any results that are irrelevant.

Until recently, git for-each-ref didn’t know how to turn multiple patterns into a single query, so it resorted to the default empty query, and manually sorted through all of the results. In Git 2.23, this was made significantly faster.


In conjunction with the above, Git can now use references from an alternate as a part of the connectivity check. This happens as part of cloning, fetching, or pushing. What is the connectivity check? After Git receives all of a repository’s objects when cloning, it makes sure that it has all of the objects needed to accept the fetch or push (in other words, that they form a connected graph).

But, if you’re cloning a repository and specify an alternate during the clone, then we already have a set of known-connected objects: those named by the references of that alternate.

Git 2.23 now makes it possible to treat those known-connected objects as a stopping point for the connectivity check. This means that it has to look at fewer objects, which makes it quicker. You can configure the references that are advertised, and use the previous tidbit to advertise more references at a greatly reduced cost.

All of this results in Git 2.23 making it possible to efficiently enumerate the list of references in another repository that are valid “stopping points” for the connectivity check in your repository.


Most of the time, if you need to bring a branch up-to-date with another, you run git merge, Git figures out the right way to merge your branch with the upstream itself, and you go about your day. At other times, Git will ask you for help, and you may find yourself editing conflict markers and resolving merge conflicts manually.

If you’ve ever been in this situation, and wanted to get out of it, you might have rungit merge --abort, which aborts the merge, and your repository is restored to its previous state before the merge started.

But, what if you changed something important, or otherwise want to keep the state of your unresolved merge, while still aborting the merging process itself? Maybe you made an important change that you’d rather apply as a commit before merging, or maybe you want to commit the conflicts as-is without completing the merge successfully.

Now in Git 2.23, you can use the new --quit option with git merge, which acts like --abort in that it declares the merge aborted unsuccessfully, but unlike --abort it leaves the state of your working copy (and index) untouched.


If you’re not so into merges, and are more of a git cherry-pick-er yourself, don’t feel left out: git cherry-pick learned some new tricks in the latest update, too.

If you’re not a regular user ofgit cherry-pick, here’s some quick background material. git cherry-pick applies the changes from one or more commits on top of your current branch. If you’re applying a single commit, you can always abort the cherry-pick; but what if you’re applying multiple commits, and the application fails in the middle of the list?

You could rungit reset && git cherry-pick --continue, but Git 2.23 offers you a more convenient (and memorable) option: git cherry-pick --skip. Now, if you’re ever presented with a conflict in the middle of a cherry-pick sequence and just want to skip the changes and move on, it’s easier than ever to do so.

If you’re a user of git multi-pack-index (or remember our mention of it from a couple of releases ago), you’re in luck! git multi-pack-index learned expire and repack in the latest update.

Note: It’s helpful to be familiar with the contents of a multi-pack-index (MIDX) file before continuing. The high level details are in the blog post, but here’s a primer on the internals. A MIDX file is the aggregation of multiple packfiles: it contains a collection of packfiles and objects within them, as well as helpful book-keeping information about which objects belong to what packfile, and where.

Enough with the details, here are the new tricks: the repack sub-command breaks down the objects contained in your MIDX into several smaller packfiles, and then recreates the MIDX based on the new packs.

Once done, you’ll want to clean up your MIDX. The expire sub-command does just that; it causes the MIDX machinery to clean up references to packfiles which don’t have any objects listed in the MIDX themselves.


If you’re interested in performance details, commit graphs (which you may recall from one of our earlier posts) have been updated, too.

If you have an exceptionally large repository, or it’s exceptionally highly trafficked, commit graphs can speed up history traversals. For more information on why this is the case, here’s some great reading.

But, what do you do when your repository is so big that rewriting the commit graph when you receive new objects is too expensive? Git 2.23 has an answer: commit graph chains.

In the latest version of Git, you can tell Git to write a commit graph chain, which allows a quick append operation to take the place of a full graph rewrite. If your chains become too long, you can tell Git to merge them down. Git can even manage chains that span multiple repositories, which can be useful if your repository uses alternates.

Expect a more in-depth overview soon from Derrick Stolee, the primary author behind commit graph.


You may have used git grep to search for some text in your Git project, just as you may have used git diff to view active changes. What do the two have in common? They both display some contents in your repository, and both have support to show the surrounding function context with -p (short for --show-function) or -W (short for --function-context), respectively.

In Git 2.23, the built-in rules for determining what is a valid function signature and what encloses a valid function context have been extended to cover the languages Octave and Rust.

[source, source]

Here’s one last large repository change. You might have noticed that if your branch tracks a remote, that running git status will sometimes tell you when your branch is behind or ahead of the remote branch it tracks.

On large repositories, computing these values can be time-consuming, and thus they can be turned off since Git 2.17, but you can now specify this by default by setting the configuration variable status.aheadBehind.


Git has long-supported the feature .mailmap, which can be useful in trying to map multiple name-email identities into one. For example, if you change your email and want to take your contributions with you without the need to rewrite history, you can add a .mailmap entry, and Git will display your old contributions with your new email.

However, while git shortlog automatically does this rewriting, git log does not. This discrepancy has been resolved, and git log will automatically apply .mailmap rewriting, making it consistent with the behavior of git shortlog.


Were you hoping to see a feature on this list, but ended up not being able to find it? If so, then we have some good news: a new tutorial has been added which provides a definitive guide on how to get started with writing your first contribution to upstream Git.

Git is an open-source project, and it relies on open-source volunteers adding their own changes, bug fixes and new features to the project. But getting started can be a daunting task. How do you compile Git? What are the style guidelines? Where do you discuss your changes? How do you get help if you need it?

All of these are answered in a new tutorial which describes the process of landing your first change by walking you through a first-person perspective of writing a new command, git psuh. Note: That’s not a typo! If you’re curious to figure out what it means (or, how to implement it), check out the tutorial. If you’ve ever wanted to write a new command, or want to see your name in the release notes, this is certainly worth a read.


Learn more

That’s just a sample of changes from the latest version. Read the release notes for 2.23, or view the release notes for previous versions in the Git repository.

Explore more from GitHub



See what’s happening in the open source community.
GitHub Universe 2024

GitHub Universe 2024

Get tickets to the 10th anniversary of our global developer event on AI, DevEx, and security.
GitHub Copilot

GitHub Copilot

Don't fly solo. Try 30 days for free.
Work at GitHub!

Work at GitHub!

Check out our current job openings.