git restore vs reset vs revert: the basics

2020-08-09

 | 

~5 min read

 | 

856 words

The restore command for Git is still experimental, but is a more intuitive solution to some of the problems I’ve come across regularly.

Similarly to Git’s reset and revert commands, restore is a way to update the working tree’s files. From the documentation, Reset, restore and revert:

There are three commands with similar names: git reset, git restore and git revert.

  • git-revert is about making a new commit that reverts the changes made by other commits.

  • git-restore is about restoring files in the working tree from either the index or another commit.

    This command does not update your branch.

    The command can also be used to restore files in the index from another commit.

  • git-reset is about updating your branch, moving the tip in order to add or remove commits from the branch. This operation changes the commit history.

    git reset can also be used to restore the index, overlapping with git restore.

Notice that revert and reset are history altering (making a new commit in former, and moving the tip in the latter), where as restore does not modify history.

Let’s consider an example of when you’d want to use restore instead of revert or reset.

Imagine making some changes to your branch for some temporary edits, but now you want to get back to the original state of the branch. In the past I’ve used a custom bash function, gsdf (an abbreviation for “git stash, drop file”) for this purpose. Now, however, this outcome is built directly into Git in the form of the restore command.

Looking at our current status we can get our bearings:

git status

From the output we can see that I’m editing my notes, which I happen to track with Git:

On branch master
Your branch is ahead of 'origin/master' by 1 commit.
  (use "git push" to publish your local commits)

Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git restore <file>..." to discard changes in working directory)
        modified:   drafts/.ideas.md
        modified:   notes/exercise-log.md
        modified:   notes/git-reset-vs-revert-redux.md

Untracked files:
  (use "git add <file>..." to include in what will be committed)
        drafts/git-restore-vs-reset.md
        drafts/temp-untracked.md

no changes added to commit (use "git add" and/or "git commit -a")

The changes to .ideas, however, can be discarded. Additionally, temp-untracked was an experiment that I can discard. Note, in the latter’s case, it was never tracked / committed to the branch.

To clear out the changes to .ideas, I can use restore:

git restore drafts/.ideas.md
git status

Then, printing the results with our git status, we can see .ideas no longer has any changes to be staged:

On branch master
Your branch is ahead of 'origin/master' by 1 commit.
  (use "git push" to publish your local commits)

Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git restore <file>..." to discard changes in working directory)
        modified:   notes/exercise-log.md
        modified:   notes/git-reset-vs-revert-redux.md

Untracked files:
  (use "git add <file>..." to include in what will be committed)
        drafts/git-restore-vs-reset.md
        drafts/temp-untracked.md

no changes added to commit (use "git add" and/or "git commit -a")

I want to restore in this case. There are no commits to the .ideas file that I want to unwind, so revert wouldn’t make sense. Similarly, I don’t want to change my current HEAD, I just want to erase the modifications I’ve made to .ideas, so reset isn’t appropriate.

This same approach to the temp-untracked file, however will not work, because it’s never been committed:

git restore drafts/temp-untracked.md

This throws an error:

error: pathspec 'drafts/temp-untracked.md' did not match any file(s) known to git

This makes sense because what I really want to do in the case of temp-untracked has nothing to do with Git at this point. Git is aware of the file, but it’s not in any commits or logs (which also means revert and reset wouldn’t work either). Instead, getting rid of that file is done by deleting the file:

rm drafts/temp-untracked.md
git status

Which, as we might expect, removes the file and therefore removes the reference from Git

On branch master
Your branch is ahead of 'origin/master' by 1 commit.
  (use "git push" to publish your local commits)

Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git restore <file>..." to discard changes in working directory)
        modified:   notes/exercise-log.md
        modified:   notes/git-reset-vs-revert-redux.md

Untracked files:
  (use "git add <file>..." to include in what will be committed)
        drafts/git-restore-vs-reset.md

For what it’s worth, gsdf would also not work for this file for the exact same reason: Git doesn’t know about it!

Wrap Up

Restore is significantly more powerful than the basic example shown here and I intend to continue to explore different methods of leveraging its power in time. For now, however, it’s always nice to continue to refine my understanding of the differences between reset and revert (which I’ve written about here and here before) and now add a new arrow to the quiver in the form of restore.



Hi there and thanks for reading! My name's Stephen. I live in Chicago with my wife, Kate, and dog, Finn. Want more? See about and get in touch!