Skip to content

A less-known way to undo/restore changes with git

Abstract

Introducing git checkout’s optional second argument, and the newer/safer alternatives to git checkout, git restore and git switch.

If you use git, then you probably know that you switch branches using the git checkout command:

git checkout my-branch

What not everybody knows is that you can pass the git checkout command a filename as a second argument. That way you can change the file to how it looked at the time of the first argument (a branch or a commit).

Example#

Imagine you’re on a branch, working on some new thing, and you changed several files. After committing your changes, you realize that you actually don’t like the changes to one of the files but do like the changes to all other files.

Sure, you could git revert your last commit (say, cd3f081) in its entirety and re-do those changes you liked. For example, by reverting the git revert (and thus re-applying the original changes) with the --no-commit option (that is, git revert cd3f081 followed by git revert HEAD --no-commit) which let’s you then discard the unwanted changes to some-file.txt before you commit the (partial) re-application of cd3f081. Or, worse, by doing all your previous work completely manually, again. 😱 Or, if you’re not scared by git reset, using that.

Much faster though is to just provide the hash of the commit before your last commit (say, 0fe3768) and the name of the one file that you want to restore to its earlier state:

git checkout 0fe3768 some-file.txt

Now you undid the changes you made to some-file.txt with your last commit (cd3f081). The file looks again like it looked when you made the commit (0fe3768) before your last commit.

Newer alternatives: git switch and git restore#

A newer, safer (but slightly less convenient to type) way to achieve the same thing just shown is this command:

git restore --source=0fe3768 some-file.txt

Motivation#

There are edge cases when using git checkout can lead to data loss.

The two functions of git checkout separated into their own commands#

To fix this problem of irretrievably losing work in edge cases, newer versions of git split the two purposes of the git checkout command into two separate commands: git switch (a synonymous command for the purpose you already know) and git restore (a synonymous command for the purpose described here in my blog post). The familiar command git checkout continues to be provided.

By using the command git switch instead of the command git checkout, you make it clear that you actually want to switch branches and don’t mean the other function of the git checkout command, even in the case somewhere in your project there happens to exist a file with the same name as the branch you just created for yourself.

I think the git restore syntax is a bit cumbersome to write, so I still write git checkout 0fe3768 some-file.txt, but whenever I want to switch or create branches, I use the new and safer git switch command.

An alias for git restore#

What you can do is create an alias in git’s configuration file that makes typing the git restore more convenient. Just add this to ~/.gitconfig:

rs = "!cmd() { git restore --source=${1} ${2} ; }; cmd"

This allows you to write

git rs 0fe3768 some-file.txt

instead of git checkout 0fe3768 some-file.txt but still use the new git restore command under the hood.

Git doesn’t allow overriding its commands with aliases1, so there is no (easy) way to just write git restore 0fe3768 some-file.txt instead of git restore --source=0fe3768 some-file.txt. Your alias needs a name different from restore. I chose rs for this example.

I also use an alias that shortens git checkout to git co. You could keep that alias co (due to muscle memory) but have it use git switch under the hood. If you're interested in my current configuration, you can examine my repository named dotfiles on the pages linked in the footer.

Final words#

Today I learned that git checkout has an optional second argument. And that git checkout has two purposes, not just one, which have been separated into two individual commands, git switch and git restore, as safer alternatives to the "all-in-one" git checkout to prevent accidental data loss. Hopefully, you could learn something from this post too. In any case, be well.

Buy Me A Coffee

  1. From git’s documentation: "To avoid confusion and troubles with script usage, aliases that hide existing Git commands are ignored." 

Comments