Reputation: 11892
I have made a GIT commit. This commit contains several changes, including a file delete.
I have since made other commits.
All the commits have been pushed, with a pull request open for review (not yet approved or merged).
Now I would like to remove the file delete from the previous commit. That's the ONLY thing I want to undo. That is, I want to keep all the other changes in the previous commits.
How do I do this?
Upvotes: 0
Views: 129
Reputation: 489333
Since you have pushed this, you have already allowed the deletion to be seen by other people.
This won't prevent you from discarding this set of commits in favor of a new set, but if you do so, any of those other people who have grabbed those commits must also discard this set of commits in favor of a new set.
You also mentioned a pull request in that same comment. A pull request is essentially an email or other communication message saying: "hey, I have some commits I have made available, please take them now." If the people who could take them, have taken them, you are now in this problem situation. But if they haven't taken them, you can attempt to retract your pull request (and maybe succeed). If so, you are back out of the problem situation.
Thus, there are two paths to choose from:
The first path is the best all around if it's not too late. The second path is usually the best if someone else has picked up the bad commits, although in some cases, if you can find everyone who has done so, and they all agree, you can have them all discard the bad commits and take good ones instead.
Remember, all Git repositories contain commits, and each commit forms part of the history in the repository. Each commit has its own unique hash ID—badf00d...
and the like—and contains the unique ID of a previous commit. It's these chains of IDs that are the history:
A <- B <- C <- D <-- master
The name master
lets you (and Git) locate the most recent commit, which we call the tip of the branch. Here, that's commit D
, and commit D
says "my previous commit is commit C
". So the name master
"points to" D
, and D
points back to C
. C
, of course, points back to B
, and so on—though when we reach the very first commit ever made, it doesn't point anywhere, because it's the first commit. That's how we know to stop looking.
You made some wrong commits, so let's draw a commit chain with a bad commit in it:
...--N <-- origin/branch
\
O--X--Q--R <-- (your pull request)
Here X
is the bad commit. We'd like to replace X
with good commit P
, which is like the bad commit, only it doesn't remove the file. Let's not worry about how you make the good commit yet, but just assume you can make P
:
...--N <-- origin/branch
\
O--X--Q--R <-- (your pull request), branch
\
P
We can't use Q
though, because it points to X
. Now that we've made P
we need to make a fixed-up Q'
that points to P
(and also doesn't have the file deleted). Let's draw that:
...--N <-- origin/branch
\
O--X--Q--R <-- (your pull request)
\
P--Q' <-- branch
And now we need a copy of R
as well, which points back to Q'
(and likewise does not have the file deleted in its snapshot):
...--N <-- origin/branch
\
O--X--Q--R <-- (your pull request)
\
P--Q'--R' <-- branch
Now you can throw away your old pull request and make a new one, pointing to R'
.
We'll start with the second path: admit error for all time, and just fix the mess. That's because it's the easiest way to get to the first path!
Again, this is more or less what you have now:
...--N <-- origin/branch
\
O--X--Q--R <-- branch, (your pull request)
The "replace pull request" thing only works if those other people haven't already taken your bad commits ending in R
. If they have, you would have to get them to drop the X-Q-R
sequence in favor of the new P-Q'-R'
sequence. If they can't or won't drop it, your best bet is to make a single new fix-up commit S
. Commit S
simply adds the deleted file back: its snapshot is the same as that of R
but with the file restored. Here's how you can make S
now. It takes just two Git commands:
git checkout origin/branch -- path/to/file
This extracts the version of path/to/file
from the commit to which origin/branch
points, i.e., from commit N
. (Name any other commit, e.g., by its hash ID, to get the version of the file from that particular commit.) Then:
# you can probably come up with a better commit message!
git commit -m "fix-up: restore accidentally-deleted file"
to commit the result (the git checkout
has the side effect of "pre-adding" the commit, so we do not need a separate git add
this time). That makes your S
, and now you have this:
...--N <-- origin/branch
\
O--X--Q--R--S <-- branch
You can stop now, by making a new pull request, giving your upstream repository your new commit S
and asking them to pull that. But if they haven't pulled R
yet, and you can retract your old pull request ... well:
Now let's see how to make P
, and then Q'
and R'
, given that you already have S
. We use an interactive rebase:
git rebase -i
(this assumes that branch
has origin/branch
set as its upstream, which it should). That brings up your editor on a set of five pick
commands:
pick 1234567 commit subject for O # the numbers will vary
pick c0ffee1 commit subject for X # these are the hash IDs
pick badf00d commit subject for Q # of each commit
pick cafedad commit subject for R
pick ....... fix-up: restore accidentally-deleted file
Note that the last line has our commit subject from our fix-up commit S
. Now we move that line to come right after the pick
command for bad commit X
, then change the command on the left, replacing pick
with squash
:
pick 1234567 commit subject for O
pick c0ffee1 commit subject for X
squash ....... fix-up: restore accidentally-deleted file
pick badf00d commit subject for Q
pick cafedad commit subject for R
Write out this command-file and exit the editor, and git rebase
will combine commit X
with commit S
(and give you a chance to write the resulting new commit's message). That's the corrected commit P
. When you write out the new message and exit the editor again, Git will copy Q
to Q'
and R
to R'
, and now you really do have this:
...--N <-- origin/branch
\
O--X--Q--R <-- (your old pull request)
\ \
\ S [abandoned]
\
P--Q'--R' <-- branch
Note that Q'
and R'
have different hash IDs from Q
and R
: that's how Git knows they're different, and can even have them at all in the first place. You have "rewritten history" because your branch-name branch
now says "go look at commit R'
", when it used to say "go look at commit S
". Now you really can discard your old pull request and make a new one, saying "please take commit R'
".
Upvotes: 1
Reputation: 1797
If you've not pushed, try this:
git reset --soft {commit tag or hash}
Where {commit tag or hash} is the commit BEFORE the problematic one. You can then basically re-do the whole commit, undoing as you see fit, before committing again.
I hope you haven't pushed!
Upvotes: 0
Reputation: 8188
git log --diff-filter=D --summary
to get all the commits which have deleted filesLets break this command to see how it works --diff-filter=D
flag helps you to search for summaries mentioning delete mode
git checkout $commit~1 filename
to restore the deleted file.$commit~1
means you should add the name of the commit. Something like 1d0c9ef6eb4e39488490543570c31c2ff594426c
where $commit is nth grandchild of the named commit - Link
Hope this will help
Upvotes: 2
Reputation: 515
If all you want to do is undo the deletion, it is quit easy to handler
- Find the commit id which did the deletion
- Execute cmd below to checkout the file
git checkout commitid filename
After that, the file is in your working directory
Upvotes: 1
Reputation: 169
You can revert a specific commitId from your branch
git revert commitid
The commit id can be found from gitk
Upvotes: 0