Jon Snow
Jon Snow

Reputation: 11892

In GIT, how do you remove a file delete from a commit which contains other changes?

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

Answers (5)

torek
torek

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:

  1. retract broken pull request, replace bad set of commits with new set, and make new pull request; or
  2. admit error for all time, leaving bad commits around, and make new additional commit(s) to undo the mistake(s).

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

How to take either path

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:

"Rewriting history"

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

HomerPlata
HomerPlata

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

Keshan Nageswaran
Keshan Nageswaran

Reputation: 8188

  1. Use git log --diff-filter=D --summary to get all the commits which have deleted files

Lets break this command to see how it works --diff-filter=D flag helps you to search for summaries mentioning delete mode

  1. Use 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

shanfeng
shanfeng

Reputation: 515

If all you want to do is undo the deletion, it is quit easy to handler

  1. Find the commit id which did the deletion
  2. Execute cmd below to checkout the file
    git checkout commitid filename

After that, the file is in your working directory

Upvotes: 1

afrose
afrose

Reputation: 169

You can revert a specific commitId from your branch

git revert commitid

The commit id can be found from gitk

Upvotes: 0

Related Questions