How can I revert a single file to a previous version? [duplicate]
Asked Answered
A

5

823

Is there a way to go through different commits on a file. Say I modified a file 5 times and I want to go back to change 2, after I already committed and pushed to a repository.

In my understanding the only way is to keep many branches, have I got that right? If I'm right I'm gonna have hundreds of branches in a few days, so I'm probably not understanding it really.

Could anyone clear that up please?

Asis answered 28/4, 2010 at 23:44 Comment(3)
Do you want to revert the file back to change two permanently, or do you temporarily want to pull out change 2 to see what the file looked like at the time?Explicable
I still use SVN daily and git only a little so I can't help with your question - but thought you might be interested in this resource - the book 'Pro Git' is available online here - progit.org/book I own and have read the book - it is excellent, too bad I don't get to use git at the moment sounds like you just need to get a historical version of your file and then recommit the old version, not branch?Duhamel
@Jim Hurne: Yes, I want to revert the file back permantently @house9: Thanks I'll take a look at that book. The historical bit sounds good, how is it done?Asis
G
1132

Let's start with a qualitative description of what we want to do (much of this is said in Ben Straub's answer). We've made some number of commits, five of which changed a given file, and we want to revert the file to one of the previous versions. First of all, git doesn't keep version numbers for individual files. It just tracks content - a commit is essentially a snapshot of the work tree, along with some metadata (e.g. commit message). So, we have to know which commit has the version of the file we want. Once we know that, we'll need to make a new commit reverting the file to that state. (We can't just muck around with history, because we've already pushed this content, and editing history messes with everyone else.)

So let's start with finding the right commit. You can see the commits which have made modifications to given file(s) very easily:

git log path/to/file

If your commit messages aren't good enough, and you need to see what was done to the file in each commit, use the -p/--patch option:

git log -p path/to/file

Or, if you prefer the graphical view of gitk

gitk path/to/file

You can also do this once you've started gitk through the view menu; one of the options for a view is a list of paths to include.

Either way, you'll be able to find the SHA1 (hash) of the commit with the version of the file you want. Now, all you have to do is this:

# get the version of the file from the given commit
git checkout <commit> path/to/file
# and commit this modification
git commit

(The checkout command first reads the file into the index, then copies it into the work tree, so there's no need to use git add to add it to the index in preparation for committing.)

If your file may not have a simple history (e.g. renames and copies), see VonC's excellent comment. git can be directed to search more carefully for such things, at the expense of speed. If you're confident the history's simple, you needn't bother.

Gynaecocracy answered 29/4, 2010 at 0:26 Comment(11)
Shouldn't you use also -C or --find-copies-harder to detect renames and copies, plus --follow to continue listing the history of a file beyond renames? Given the emphasis of your answer on file content, it might be good to complete it by mentioning its natural consequence: the inference aspect (see #613080)Blackcap
Fantastic explanation. Not only did you answer the question perfectly, you also furthered my understanding. I'd never seen gitk before either - amazing!Lowder
One nice thing to add is that if the change you're reverting is a file rename, you'll need a different workflow. You'll need something like git log --diff-filter=D --summary in order to find the desired commit, and remove the file with the new name.Coulombe
To automate this entire process of searching through the history of a file and checking out the appropriate commit-id into a single git command, use git prevision.Titanomachy
@Titanomachy While it's nice to have an alias for the easy case where you know you just want a commit or two back, this is about the general case, where you probably have no idea how many commits back to go. And if you're going to have to count commits, you might as well just save yourself the time and use the SHA1 you already have. So, useful alias, not an answer to the whole question, and certainly doesn't automate everything going on here.Gynaecocracy
@Jefromi The OP in the question seems to know exactly how many revisions he wants to go back. You are right though, this alias is not a silver-bullet for everything. Just excited to share something i cooked-up yesterday night that saves me a lot of time with the common use-case of git prevision -1 <file>Titanomachy
Will git checkout head~3 filename not work?Aurelie
@EdRandall Sure, HEAD~3 will work, if the commit you want is the one three before HEAD. In general you may not be totally confident, so I covered how to figure out which commit you actually want.Gynaecocracy
best guide to replacing a file to a previous version. remember this is not for removing changes a file made for a specific commit (for that you will need to use git revert), but rather for reverting a file back to a particular stateStrophanthin
Can I restore file content from "before specific commit" without committing or even staging changes? Just want to quickly see if it solves the issue or not.Edita
hi, just wonder is there a way to get path/to/file fast instead of typing the path in? let's say I have a long path what I'm doing is just typing the whole path with tab suggestion (e.g.: /d/folderA/src/folderB/components/folderC/etc...) This is a bit time consuming..Relative
E
120

Git is very flexible. You shouldn't need hundreds of branches to do what you are asking. If you want to revert the state all the way back to the 2nd change (and it is indeed a change that was already committed and pushed), use git revert. Something like:

git revert a4r9593432 

where a4r9593432 is the starting characters of the hash of the commit you want to back out.

If the commit contains changes to many files, but you just want to revert just one of the files, you can use git reset (the 2nd or 3rd form):

git reset a4r9593432 -- path/to/file.txt
# the reverted state is added to the staging area, ready for commit
git diff --cached path/to/file.txt        # view the changes
git commit
git checkout HEAD path/to/file.txt        # make the working tree match HEAD           

But this is pretty complex, and git reset is dangerous. Use git checkout <hash> <file path> instead, as Jefromi suggests.

If you just want to view what the file looked like in commit x, you can use git show:

git show a4r9593432:path/to/file.txt

For all of the commands, there are many ways to refer to a commit other than via the commit hash (see Naming Commits in the Git User Manual).

Explicable answered 29/4, 2010 at 0:29 Comment(6)
Won't the git revert just back out one change though -- not "revert the state all the way back to the 2nd change"?Retrograde
@Alex - Yes, you are correct. Good catch! Only the changes introduced in the given commit are backed out. It is painful, but you could use multiple calls to get revert to back out all of the commits to a certain point in time. Or you can do something like what you suggest in your answer with git diff and git apply.Explicable
Also, git revert is not for one specific file, but for the whole repository.Melliemelliferous
@PaŭloEbermann Yes, but it will only revert the files affected in the specified commit.Greengrocery
Can we have our cake and eat it to? Is it possible to revert a single commit from a single file and leave other future changes in place?Congener
Wondering the same as @AdamPlocher - there has to be an option to do something like git-revert on a single file?Sickler
M
11

Git doesn't think in terms of file versions. A version in git is a snapshot of the entire tree.

Given this, what you really want is a tree that has the latest content of most files, but with the contents of one file the same as it was 5 commits ago. This will take the form of a new commit on top of the old ones, and the latest version of the tree will have what you want.

I don't know if there's a one-liner that will revert a single file to the contents of 5 commits ago, but the lo-fi solution should work: checkout master~5, copy the file somewhere else, checkout master, copy the file back, then commit.

Merat answered 29/4, 2010 at 0:8 Comment(1)
The one-liner you're asking for is checkout on a single file instead of the entire work tree; see my answer.Gynaecocracy
R
8

You can take a diff that undoes the changes you want and commit that.

E.g. If you want to undo the changes in the range from..to, do the following

git diff to..from > foo.diff  # get a reverse diff
patch < foo.diff
git commit -a -m "Undid changes from..to".
Retrograde answered 29/4, 2010 at 0:16 Comment(6)
A few problems. If you want to create a patch, it needs to be the patch for only the file in question (git diff to..from path/to/file). To apply a patch, you should use git apply instead of patch. And there's no need to use a patch at all in this case; see my answer.Gynaecocracy
Still helpful, as you can edit the diff (ie. revert only part of changes).Bureaucracy
@Jefromi There is absolutely a reason to use a patch, because your answer is NOT equivalent to revert: it will obliterate all changes since the offending commit, not just undo the changes of that one commit as git revert would do. A file- and commit- specific patch is a far more precise and semantically correct change.Easterling
@JohnWhitley This question is explicitly about reverting a file to a previous version. It's in the title. I totally agree that if you want to undo just a single commit of many, my answer is wrong, but fortunately that wasn't the question I was answering here.Gynaecocracy
@Jefromi This answer doesn't do the same as your answer. This answer reverts one commit. Your answer reverts one file. This one happens to be exactly what I needed.Feints
To elaborate on this answer I would suggest this one-liner: git diff -R <revision>^! -- path/to/file | git apply.Acknowledge
L
8

Extracted from here: http://git.661346.n2.nabble.com/Revert-a-single-commit-in-a-single-file-td6064050.html

 git revert <commit> 
 git reset 
 git add <path> 
 git commit ... 
 git reset --hard # making sure you didn't have uncommited changes earlier 

It worked very fine to me.

Lorenalorene answered 22/10, 2013 at 21:10 Comment(4)
Intuitively, before reading your answer, I did git revert, git reset and git add, then I searched before commiting to see if I was doing right. +1 for your answer!Asiatic
Do you know if reverting a single file that way, git recognizes I'm actually reverting a file?Asiatic
gitk shows as new fileAsiatic
@AntonioViniciusMenezesMedei: It doesn't work, as you discovered. It shows you reverting all the changes and adding all the other files as if you did them fresh.Jacynth

© 2022 - 2024 — McMap. All rights reserved.