Reputation: 51
I wanted to know if there is a way to go back to the previous change in a file on git. If I create a .txt file, I edit it but I don't "git add" can I run "git checkout - file.txt" and go back, but is there a way to do it after doing "git add"?
Upvotes: 2
Views: 6229
Reputation: 487805
I wanted to know if there is a way to go back to the previous change in a file on git. If I create a .txt file, I edit it but I don't "git add" can I run "git checkout - file.txt" and go back, but is there a way to do it after doing "git add"?
Provided that the file is in Git, the answer is yes.
It's important here to realize that there are not one but two modifiable copies of some already-committed file, plus many copies that cannot be changed. The read-only copies are in each of the commits, because every commit you make saves a complete snapshot of every file.1 But it makes this snapshot not from the copy you see and work with. It makes the snapshot instead from the second modifiable copy, which Git keeps in what Git sometimes calls the index and sometimes calls the staging area.
The names index and staging area refer to the same thing. It's also sometimes called the cache. The gitglossary defines cache as "obsolete for index". One good way to think of the index is that it's the proposed next commit. When you check out some existing commit, Git fills in both the index and the work-tree from that existing commit. When you run git add file
, Git copies the current version of file
into the index, replacing the copy that was in the index. That is, git add
simply updates the proposed new commit.
(If Git made commits directly from the work-tree, you would not need the index at all. But Git doesn't make commits from the work-tree, and the index takes on an expanded role during a conflicted merge, so you are kind of stuck with learning about the index.)
Normally, you use git checkout branchname
to switch to a branch, but you can also write git checkout [--] filename
or git checkout branchname [--] filename
.2 These are both different from git checkout branchname
:
git checkout branchname
means switch branches.git checkout branchname [--] filename
means get the given file from the commit at the tip of the named branch.3git checkout [--] filename
means get the given file from the index / staging area.The way I like to think about this is that as you work on making your new commit, there are three active copies of the file. One is the frozen one in your current (HEAD
or @
) commit, one is the modifiable copy in your proposed next commit in the index, and the third is the ordinary file you can see and work with. Using git checkout -- file.txt
gets, into the work-tree where you can work with it, the middle copy—the index copy—which you already overwrote with your earlier git add
. Hence, you want git checkout HEAD -- file.txt
: this gets the read-only, committed copy. It winds up in the index and in your work-tree, at this point, so both of the modifiable copies have been overwritten by this one command.4 Your proposed new commit has the old version of file.txt
out of the current commit, and your work-tree has the old version of file.txt
out of the current commit. All three copies—HEAD
, index, and work-tree—match again.
1This is true even though git log -p
or git show
shows you the differences between a commit's parent and the commit. That's because the way that git log -p
or git show
finds the differences is that it extracts both the parent commit's copy, and the commit's copy, of the file, and then compares them to see what's different.
Because the committed copies are read-only, every commit that re-uses an old version of the file, doesn't make a new copy, it just refers back to the existing, already-frozen copy. So the fact that, say, README.md
is in a thousand commits, with all the README.md
copies exactly the same, really means that there's one copy, shared across the thousand commits.
2The square brackets in [--]
indicate that the --
is optional. It's a good habit to get into using it when you mean the next part is a file name. Suppose you create a file named master
. Later, you run:
git checkout master
Did you mean: Get me the file named master
, or did you mean: *Switch to branch master`? If you type in:
git checkout -- master
Git knows you meant Get me the file named master
. Whatever comes after --
is assumed to be a file name. If you type this in without the --
, Git thinks you mean switch to branch master
.
3You can put in a commit hash ID, or any other name or string that resolves to a commit, here.
4Git being what it is, there's another way to do all of this. Actually there are many ways, but there is one other main way: you can use git reset [--] filename
to copy from HEAD
to the index, then git checkout [--] filename
to copy from index to work-tree. But git checkout commit-ish -- filename
is shorter and faster.
Upvotes: 2
Reputation: 212
This one is hard to find out there so here it is. If you have an uncommitted change (its only in your working copy) that you wish to revert (in SVN terms) to the copy in your latest commit, do the following:
git checkout filename
This will checkout the file from HEAD, overwriting your change. This command is also used to checkout branches, and you could happen to have a file with the same name as a branch. All is not lost, you will simply need to type:
git checkout -- filename
You can also do this with files from other branches, and such. man git-checkout has the details.
The rest of the Internet will tell you to use git reset --hard, but this resets all uncommitted changes you’ve made in your working copy. Type this with care.
from https://www.norbauer.com/rails-consulting/notes/git-revert-reset-a-single-file
Upvotes: 1