Reputation: 359
I have a few files that I have already pushed up publically but only afterwards did I realize that I had some files that say they were changed because of an extra space or some minor change. I want to revert those files back to a few commits ago, but only those specific files.
I've tried git revert <commithash>~4 -- path/to/filename1 path/to/filename2
, where the 4 is for 4 commits before this current one but it didn't seem to work: the changes I was hoping to see go away didn't go away on my files.
The commits are currently only on my branch and haven't been merged with any others. I didn't want to revert the entire commit, but only some files from this bad commit which is where I am running into trouble.
Upvotes: 2
Views: 1187
Reputation: 488183
You probably (probably, this can get complicated) want a particular mode of git checkout
or, in Git 2.23 or later, git restore
, here. You can extract particular files from particular commits this way, without actually changing commits. The details get a little sticky, although the new git restore
probably does exactly what you want, right out of the box, as it were:
git restore <commit-hash> -- path/to/filename1 path/to/filename2
Each commit in a Git repository is (or holds, really) a snapshot of all of your files.
When you use:
git checkout <commit-hash>
(or since Git 2.23, the same thing with git switch
), you ask Git to switch to the given commit as a detached HEAD. This reads the chosen commit into the index—the index, or staging area, holds your proposed next commit so switching to a commit requires filling it in from that commit—and in the process, adjusts the contents of your work-tree to match the commit you've switched-to.
When you use git checkout branch-name
( or again git swtich
), Git does the same thing, except that now the special name HEAD
is attached to the branch name. Git now remembers which branch you're on, and making a new commit will write the new commit's hash ID into that branch name.
Well, that's all well and good, but now you want some particular file out of one particular commit, without switching commits at all. This is where Git 2.23 and later are better, because there's a separate command for this, git restore
. We'll get to that in a moment, but first let's talk more about git checkout
.
The git checkout
command is absurdly complex. It has, depending on how you count, something like four to seven different modes of operation. The one we care about here is the one that checks out particular files from particular commits. That is, we want:
git checkout <commit-hash> -- <paths>
It's important to remember Git's index here, because this kind of git checkout
first writes the files to the index. The paths
argument you give, after the --
, determines which files come out of the chosen commit. The --
itself is only there to make sure that these file names don't look like flags or branch names or whatever. The commit-hash
part can be anything acceptable to git rev-parse
, as long as it names a commit or tree object internally; a commit hash is good here.
Having copied the file(s) you named from the commit you named into the index, this git checkout
goes on to write the files into your work-tree. Note that unlike a regular, safe git checkout
, this particular mode will destroy the current contents of the named files even if they are not saved anywhere else. So if you have used git checkout
a lot and are happy with the fact that it will tell you: no, I can't do that, I'll lose some files you might have forgotten to save, just remember that this form of git checkout
is quite ruthless.
In Git 2.23 and later, there is a separate command, git restore
, that takes over this job. The new restore
command can copy the file separately to the index, or to your work-tree, or both. The default is just to copy to the work-tree, without affecting the index (and also without checking whether you've saved the work-tree file anywhere, so be careful with it!). So:
git restore <commit-hash> -- path/to/filename1 path/to/filename2
will copy those files from that commit into your work-tree. The updated files won't be staged for commit, as your index has not been altered at all. (As before, the --
is just in case a file name is something weird like --staged
. If it's not anything like this, you can omit the --
. It's a good habit to get into though.) If everything looks good, you can git add
and git commit
as usual.
(If, after this, you want to use interactive rebase, or a soft reset and commit, to squash away some of the extra intermediate commits, that's a topic for another question—there are already a lot of answers to those questions.)
Upvotes: 0
Reputation: 2270
I think you will have to undo the commit(s) where you pushed erroneously the wrong files (using git reset
) and then perform a new commit. A commit is like a collection of project changes and has to be treated as a unit, thus single file changes are not regarded independently from the rest of the commit. (Edit: This will only help you as long as your commits have not been merged yet.)
Anyways, there is a way to revert only special files that have been added in a commit. The key word here is Cherry Picking which "appl[ies] the change[s] each [commit] introduces, recording a new commit for each".
Upvotes: 1