1

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.

3
  • 1
    "it didn't seem to work." Why not? What happened? Why was that wrong? Commented Jan 16, 2020 at 12:48
  • @underscore_d well the changes I was hoping to see go away didn't go away on my files.
    – PythonSOS
    Commented Jan 16, 2020 at 13:00
  • 1
    git revert does not accept file names; you're reverting an entire commit, or not. There are many ways to achieve what you want, depending on what exactly it is you want...
    – torek
    Commented Jan 16, 2020 at 18:35

2 Answers 2

1

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".

3
  • 2
    However, OP didn't say if said bad commits are on (or already merged on) shared branches. If so, this (reset) is not the way to go. Commented Jan 16, 2020 at 12:54
  • 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.
    – PythonSOS
    Commented Jan 16, 2020 at 12:57
  • 1
    @PythonSOS Thanks for the precision (as a sidenote, it would be better to add it to the question itself for future readers). Commented Jan 16, 2020 at 13:03
0

TL;DR

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

Long

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.)

Not the answer you're looking for? Browse other questions tagged or ask your own question.