456

I was working with a friend on a project, and he edited a bunch of files that shouldn't have been edited. Somehow I merged his work into mine, either when I pulled it, or when I tried to just pick the specific files out that I wanted. I've been looking and playing for a long time, trying to figure out how to remove the commits that contain the edits to those files, it seems to be a toss up between revert and rebase, and there are no straightforward examples, and the docs assume I know more than I do.

So here is a simplified version of the question:

Given the following scenario, how do I remove commit 2?

$ mkdir git_revert_test && cd git_revert_test

$ git init
Initialized empty Git repository in /Users/josh/deleteme/git_revert_test/.git/

$ echo "line 1" > myfile

$ git add -A

$ git commit -m "commit 1"
[master (root-commit) 8230fa3] commit 1
 1 files changed, 1 insertions(+), 0 deletions(-)
 create mode 100644 myfile

$ echo "line 2" >> myfile

$ git commit -am "commit 2"
[master 342f9bb] commit 2
 1 files changed, 1 insertions(+), 0 deletions(-)

$ echo "line 3" >> myfile

$ git commit -am "commit 3"
[master 1bcb872] commit 3
 1 files changed, 1 insertions(+), 0 deletions(-)

The expected result is

$ cat myfile
line 1
line 3

Here is an example of how I have been trying to revert

$ git revert 342f9bb
Automatic revert failed.  After resolving the conflicts,
mark the corrected paths with 'git add <paths>' or 'git rm <paths>'
and commit the result.
tk_
  • 16,415
  • 8
  • 80
  • 90
Joshua Cheek
  • 30,436
  • 16
  • 74
  • 83
  • 17
    If anyone finds this while searching for the same problem, here is what I ended up doing: Copy and paste. Seriously. Spent 6+ hours trying to get the suggested solutions to work, to no avail. In the end, I was out of time, pulled up the original, and just copy/pasted about 20 files. Took under 5 minutes, and things have been fine ever since (even when those files are being merged with changes in other branches that happened before this fiasco). I suggest you take this approach also. Not only is it the simplest, I also suspect it is the only thing that works. – Joshua Cheek Jun 01 '10 at 01:07
  • I faced a similar issue, but perhaps more complex: had a branch with hundreds of commits that I wanted to squash. Unfortunately commits from another branch were merged back into the branch at an intermediate point, so an "unmerge" was needed before I could squash. I went down a similar path as suggested by tk below (cherry picking + using the range notation), but it produced conflicts in some other files. In the end copy & paste + a few manual edits was the easiest and most predictable path forward. Definitely worth a consideration if you find yourself spending too much time on this. – Federico Jan 06 '20 at 23:34
  • 1
    One problem I always have is that I am never the person with the problem commits. I'm the Release Manager and our developers come to me to fix things. I never have the commits in my local to fix, so a lot of the assumptions "ie: if YOU have included the wrong commit" don't really apply. My local clone never has the history to work with. – MarkTO Sep 02 '20 at 18:20

16 Answers16

440

There are four ways of doing so (replace "commid-id" with your commit's hash):

  • Clean way, reverting but keep in log the revert:

      git revert --strategy resolve commit-id
    

    Note: if the commit to remove is a merge commit, you will need to append -m 1 (thanks to @Powertoaster for the tip!).

  • Harsh way, remove altogether only the last commit:

      git reset --soft "HEAD^"
    

    Note: Avoid git reset --hard as it will also discard all changes in files since the last commit. If --soft does not work, rather try --mixed or --keep.

  • Interactive rebase (this will show the log of the last 5 commits and delete the lines you don't want, or reorder, or squash multiple commits in one, or do anything else you want, this is a very versatile tool):

      git rebase -i HEAD~5
    

    And if a mistake is made:

      git rebase --abort
    
  • Quick rebase: remove only a specific commit using its id:

      git rebase --onto commit-id^ commit-id
    
  • Alternative: you could also try:

      git cherry-pick commit-id
    
  • Yet another alternative:

      git revert --no-commit
    
  • As a last resort, if you need full freedom of history editing (eg, because git don't allow you to edit what you want to), you can use this very fast open source application: reposurgeon.

Note: of course, all these changes are done locally, you should git push afterwards to apply the changes to the remote. And in case your repo doesn't want to remove the commit ("no fast-forward allowed", which happens when you want to remove a commit you already pushed), you can use git push --force to force push the changes.

Note2: if working on a branch and you need to force push, you should absolutely avoid git push --force because this may overwrite other branches (if you have made changes in them, even if your current checkout is on another branch). To ensure to avoid that, always specify the remote branch when you force push: git push --force origin your_branch.

gaborous
  • 15,832
  • 10
  • 83
  • 102
  • 1
    Based on suggestions of @gaborous: do a "git rebase -i HEAD~2". Now you have several options. In the vim you can see some commented lines: one of them tells you that you simply can delete a line (which should be the commit you want to get rid of) and this commit will be removed together with it's log in your history. – Ilker Cat Apr 08 '16 at 09:11
  • 2
    git revert --strategy resolve .This command worked for me. thanks :) – Swathin Nov 16 '16 at 10:44
  • 7
    `git rebase -i HEAD~5` worked for me. Then I just removed the commit(s) that I didn't need and I was able to solve the problem in less than 30 seconds. Thank you. – lv10 Oct 31 '17 at 14:52
  • Both God and I bless you. – NeoZoom.lua Jun 25 '21 at 07:12
  • 1
    For what it's worth, the first command `git revert ...` helped me hold back commits from production branch (e.g. `master`) and then with combination of `git cherry-pick` was able to include back those reverted commits into my `develop` branch in order to not lose any development work while only deploying what needed to be deployed. – DrV Jul 07 '21 at 04:13
  • 2
    git revert --strategy resolve worked for me but I had to add a -m flag. Like this: git revert --strategy resolve -m 1 since my commit was a merge. – Powertoaster Jan 18 '22 at 04:02
  • 1
    git revert --strategy resolve was exactly what I needed to remove a previous commit. Thank you - this helped so much! – CamBeeler Sep 21 '22 at 02:48
271

Here is an easy solution:

git rebase -i HEAD~x

Where x is the number of commits.

Enter drop before your commit:

enter image description here

And that's it, you are done. If the commit you drop was already on the remote, you will have to force push. Since --force is considered harmful, use git push --force-with-lease.

anon01
  • 10,618
  • 8
  • 35
  • 58
JD-V
  • 3,336
  • 1
  • 17
  • 20
  • 7
    For cpp-mentality people: x (no. of commits) is inclusive. e.g. HEAD~4 includes the last 4 commits. – Herpes Free Engineer Feb 14 '19 at 14:24
  • 1
    perfect suggestion. just want to add, this also discard the changes from the dropped commit. – celerno Mar 07 '19 at 20:35
  • tried to revert 3 commits: git rebase -i HEAD-3 got error fatal: Needed a single revision invalid upstream 'HEAD-3' – Ustin Nov 18 '19 at 19:15
  • 1
    @Ustin it's ~3 not -3 – JD-V Nov 19 '19 at 05:28
  • 1
    how to "enter drop"? I can't write anything – Eduard Mar 25 '20 at 16:42
  • Answer to my question above: If you don’t know Vim, just click on each word pick that you want to edit and then hit the key (for insert mode). Once you’re done typing hit the key to exit insert mode. – Eduard Mar 25 '20 at 16:43
  • I also really like this solution, eventho it leaves a trace in the GitHub history. after writing drop on the commits i wanted to get rid of i used :w! and then :q to exit vim. – Petra Apr 03 '20 at 19:12
  • This worked best for me, particularly where I wanted to erase changes to a specific file without deleting it entirely. – rotarydial Jun 23 '20 at 19:18
  • Nice of you to explain vim, but git should automatically use the systems default-editor – tankman175 Mar 14 '21 at 22:18
  • This was the perfect answer for me. Cheers – N Altun Mar 15 '22 at 14:48
85

The algorithm that Git uses when calculating diffs to be reverted requires that

  1. The lines being reverted are not modified by any later commits.
  2. There not be any other "adjacent" commits later in history.

The definition of "adjacent" is based on the default number of lines from a context diff, which is 3. So if 'myfile' was constructed like this:

$ cat >myfile <<EOF
line 1
junk
junk
junk
junk
line 2
junk
junk
junk
junk
line 3
EOF
$ git add myfile
$ git commit -m "initial check-in"
 1 files changed, 11 insertions(+), 0 deletions(-)
 create mode 100644 myfile
$ perl -p -i -e 's/line 2/this is the second line/;' myfile
$ git commit -am "changed line 2 to second line"
[master d6cbb19] changed line 2
 1 files changed, 1 insertions(+), 1 deletions(-)
$ perl -p -i -e 's/line 3/this is the third line/;' myfile
$ git commit -am "changed line 3 to third line"
[master dd054fe] changed line 3
 1 files changed, 1 insertions(+), 1 deletions(-)
$ git revert d6cbb19
Finished one revert.
[master 2db5c47] Revert "changed line 2"
 1 files changed, 1 insertions(+), 1 deletions(-)

Then it all works as expected.

The second answer was very interesting. There is a feature that has not yet been officially released (though it is available in Git v1.7.2-rc2) called Revert Strategy. You can invoke git like this:

git revert --strategy resolve <commit>

and it should do a better job of figuring out what you meant. I do not know what the list of available strategies is, nor do I know the definition of any strategy.

Saleh Mahmood
  • 1,823
  • 1
  • 22
  • 30
Hal Eisen
  • 920
  • 6
  • 5
  • 2
    `perl -p` is useful for writing very short (one line) programs which loop and *pass* their input through to the output, similar to sed. `perl -i` is used for editing files *in place*. `perl -e` is how to submit the code to be *evaluated*. – Hal Eisen May 05 '17 at 07:53
60

Approach 1

First get the commit hash(ex:1406cd61) that you need to revert. simple fix will be below command,

$ git revert 1406cd61

if you have commit more changes related to 1406cd61 files after 1406cd61 commit, Above simple command will not work. Then you have to do the below steps, which is cherry picking.

Approach 2

Please follow below sequence of actions, Since we are using --force you need to have admin rights over the git repo to do this.

Step 1: Find the commit before the commit you want to remove git log

Step 2: Checkout that commit git checkout <commit hash>

Step 3: Make a new branch using your current checkout commit git checkout -b <new branch>

Step 4: Now you need to add the commit after the removed commit git cherry-pick <commit hash>

Step 5: Now repeat Step 4 for all other commits you want to keep.

Step 6: Once all commits have been added to your new branch and have been committed. Check that everything is in the correct state and working as intended. Double check everything has been committed: git status

Step 7: Switch to your broken branch git checkout <broken branch>

Step 8: Now perform a hard reset on the broken branch to the commit prior to the one your want to remove git reset --hard <commit hash>

Step 9: Merge your fixed branch into this branch git merge <branch name>

Step 10: Push the merged changes back to origin. WARNING: This will overwrite the remote repo! git push --force origin <branch name>

You can do the process without creating a new branch by replacing Step 2 & 3 with Step 8 then not carry out Step 7 & 9.

angryITguy
  • 9,332
  • 8
  • 54
  • 82
tk_
  • 16,415
  • 8
  • 80
  • 90
  • 1
    The first approach worked like a charm. It includes you a commit specifying that your desired commit has been reverted, which is really nice for tracking purposes. – therealbigpepe Mar 20 '20 at 09:31
41

Your choice is between

  1. keeping the error and introducing a fix and
  2. removing the error and changing the history.

You should choose (1) if the erroneous change has been picked up by anybody else and (2) if the error is limited to a private un-pushed branch.

Git revert is an automated tool to do (1), it creates a new commit undoing some previous commit. You'll see the error and removal in the project history but people who pull from your repository won't run into problems when they update. It's not working in an automated manner in your example so you need to edit 'myfile' (to remove line 2), do git add myfile and git commit to deal with the conflict. You will then end up with four commits in your history, with commit 4 reverting commit 2.

If nobody cares that your history changes, you can rewrite it and remove commit 2 (choice 2). The easy way to do this is to use git rebase -i 8230fa3. This will drop you into an editor and you can choose not to include the erroneous commit by removing the commit (and keeping "pick" next to the other commit messages. Do read up on the consequences of doing this.

CharlesB
  • 86,532
  • 28
  • 194
  • 218
Andrew Walker
  • 2,451
  • 2
  • 18
  • 15
21

You can remove unwanted commits with git rebase. Say you included some commits from a coworker's topic branch into your topic branch, but later decide you don't want those commits.

git checkout -b tmp-branch my-topic-branch  # Use a temporary branch to be safe.
git rebase -i master  # Interactively rebase against master branch.

At this point your text editor will open the interactive rebase view. For example

git-rebase-todo

  1. Remove the commits you don't want by deleting their lines
  2. Save and quit

If the rebase wasn't successful, delete the temporary branch and try another strategy. Otherwise continue with the following instructions.

git checkout my-topic-branch
git reset --hard tmp-branch  # Overwrite your topic branch with the temp branch.
git branch -d tmp-branch  # Delete the temporary branch.

If you're pushing your topic branch to a remote, you may need to force push since the commit history has changed. If others are working on the same branch, give them a heads up.

Dennis
  • 56,821
  • 26
  • 143
  • 139
  • Could you please give an example of 'try another strategy'? – pfabri May 28 '19 at 18:03
  • @pfabri you could for example cherry-pick two ranges of commits where you leave out the bad commit. You could revert the bad commit. You could avoid using a git solution by even manually undoing the changes, or starting from a fresh branch without the bad commit and manually redoing the good changes. If the bad commit contains sensitive data, you'll need a more careful strategy: https://help.github.com/en/articles/removing-sensitive-data-from-a-repository – Dennis Jun 09 '19 at 08:11
12

From other answers here, I was kind of confused with how git rebase -i could be used to remove a commit, so I hope it's OK to jot down my test case here (very similar to the OP).

Here is a bash script that you can paste in to create a test repository in the /tmp folder:

set -x

rm -rf /tmp/myrepo*
cd /tmp

mkdir myrepo_git
cd myrepo_git
git init
git config user.name me
git config user.email me@myself.com

mkdir folder
echo aaaa >> folder/file.txt
git add folder/file.txt
git commit -m "1st git commit"

echo bbbb >> folder/file.txt
git add folder/file.txt
git commit -m "2nd git commit"

echo cccc >> folder/file.txt
git add folder/file.txt
git commit -m "3rd git commit"

echo dddd >> folder/file.txt
git add folder/file.txt
git commit -m "4th git commit"

echo eeee >> folder/file.txt
git add folder/file.txt
git commit -m "5th git commit"

At this point, we have a file.txt with these contents:

aaaa
bbbb
cccc
dddd
eeee

At this point, HEAD is at the 5th commit, HEAD~1 would be the 4th - and HEAD~4 would be the 1st commit (so HEAD~5 wouldn't exist). Let's say we want to remove the 3rd commit - we can issue this command in the myrepo_git directory:

git rebase -i HEAD~4

(Note that git rebase -i HEAD~5 results with "fatal: Needed a single revision; invalid upstream HEAD~5".) A text editor (see screenshot in @Dennis' answer) will open with these contents:

pick 5978582 2nd git commit
pick 448c212 3rd git commit
pick b50213c 4th git commit
pick a9c8fa1 5th git commit

# Rebase b916e7f..a9c8fa1 onto b916e7f
# ...

So we get all commits since (but not including) our requested HEAD~4. Delete the line pick 448c212 3rd git commit and save the file; you'll get this response from git rebase:

error: could not apply b50213c... 4th git commit

When you have resolved this problem run "git rebase --continue".
If you would prefer to skip this patch, instead run "git rebase --skip".
To check out the original branch and stop rebasing run "git rebase --abort".
Could not apply b50213c... 4th git commit

At this point open myrepo_git/folder/file.txt in a text editor; you'll see it has been modified:

aaaa
bbbb
<<<<<<< HEAD
=======
cccc
dddd
>>>>>>> b50213c... 4th git commit

Basically, git sees that when HEAD got to 2nd commit, there was content of aaaa + bbbb; and then it has a patch of added cccc+dddd which it doesn't know how to append to the existing content.

So here git cannot decide for you - it is you who has to make a decision: by removing the 3rd commit, you either keep the changes introduced by it (here, the line cccc) -- or you don't. If you don't, simply remove the extra lines - including the cccc - in folder/file.txt using a text editor, so it looks like this:

aaaa
bbbb
dddd

... and then save folder/file.txt. Now you can issue the following commands in myrepo_git directory:

$ nano folder/file.txt  # text editor - edit, save
$ git rebase --continue
folder/file.txt: needs merge
You must edit all merge conflicts and then
mark them as resolved using git add

Ah - so in order to mark that we've solved the conflict, we must git add the folder/file.txt, before doing git rebase --continue:

$ git add folder/file.txt
$ git rebase --continue

Here a text editor opens again, showing the line 4th git commit - here we have a chance to change the commit message (which in this case could be meaningfully changed to 4th (and removed 3rd) commit or similar). Let's say you don't want to - so just exit the text editor without saving; once you do that, you'll get:

$ git rebase --continue
[detached HEAD b8275fc] 4th git commit
 1 file changed, 1 insertion(+)
Successfully rebased and updated refs/heads/master.

At this point, now you have a history like this (which you could also inspect with say gitk . or other tools) of the contents of folder/file.txt (with, apparently, unchanged timestamps of the original commits):

1st git commit  |  +aaaa
----------------------------------------------
2nd git commit  |   aaaa
                |  +bbbb
----------------------------------------------
4th git commit  |   aaaa
                |   bbbb
                |  +dddd
----------------------------------------------
5th git commit  |   aaaa
                |   bbbb
                |   dddd
                |  +eeee

And if previously, we decided to keep the line cccc (the contents of the 3rd git commit that we removed), we would have had:

1st git commit  |  +aaaa
----------------------------------------------
2nd git commit  |   aaaa
                |  +bbbb
----------------------------------------------
4th git commit  |   aaaa
                |   bbbb
                |  +cccc
                |  +dddd
----------------------------------------------
5th git commit  |   aaaa
                |   bbbb
                |   cccc
                |   dddd
                |  +eeee

Well, this was the kind of reading I hoped I'd have found, to start grokking how git rebase works in terms of deleting commits/revisions; so hope it might help others too...

Community
  • 1
  • 1
sdaau
  • 36,975
  • 46
  • 198
  • 278
  • What an invaluable walk-through - the exact use-case I was struggling with, this helped me immensely. – pfabri May 28 '19 at 18:22
3

So it sounds like the bad commit was incorporated in a merge commit at some point. Has your merge commit been pulled yet? If yes, then you'll want to use git revert; you'll have to grit your teeth and work through the conflicts. If no, then you could conceivably either rebase or revert, but you can do so before the merge commit, then redo the merge.

There's not much help we can give you for the first case, really. After trying the revert, and finding that the automatic one failed, you have to examine the conflicts and fix them appropriately. This is exactly the same process as fixing merge conflicts; you can use git status to see where the conflicts are, edit the unmerged files, find the conflicted hunks, figure out how to resolve them, add the conflicted files, and finally commit. If you use git commit by itself (no -m <message>), the message that pops up in your editor should be the template message created by git revert; you can add a note about how you fixed the conflicts, then save and quit to commit.

For the second case, fixing the problem before your merge, there are two subcases, depending on whether you've done more work since the merge. If you haven't, you can simply git reset --hard HEAD^ to knock off the merge, do the revert, then redo the merge. But I'm guessing you have. So, you'll end up doing something like this:

  • create a temporary branch just before the merge, and check it out
  • do the revert (or use git rebase -i <something before the bad commit> <temporary branch> to remove the bad commit)
  • redo the merge
  • rebase your subsequent work back on: git rebase --onto <temporary branch> <old merge commit> <real branch>
  • remove the temporary branch
Cascabel
  • 479,068
  • 72
  • 370
  • 318
1

So you did some work and pushed it, lets call them commits A and B. Your coworker did some work as well, commits C And D. You merged your coworkers work into yours (merge commit E), then continued working, committed that, too (commit F), and discovered that your coworker changed some things he shouldn't have.

So your commit history looks like this:

A -- B -- C -- D -- D' -- E -- F

You really want to get rid of C, D, and D'. Since you say you merged your coworkers work into yours, these commits already "out there", so removing the commits using e.g. git rebase is a no-no. Believe me, I've tried.

Now, I see two ways out:

  • if you haven't pushed E and F to your coworker or anyone else (typically your "origin" server) yet, you could still remove those from the history for the time being. This is your work that you want to save. This can be done with a

    git reset D'
    

    (replace D' with the actual commit hash that you can obtain from a git log

    At this point, commits E and F are gone and the changes are uncommitted changes in your local workspace again. At this point I would move them to a branch or turn them into a patch and save it for later. Now, revert your coworker's work, either automatically with a git revert or manually. When you've done that, replay your work on top of that. You may have merge conflicts, but at least they'll be in the code you wrote, instead of your coworker's code.

  • If you've already pushed the work you did after your coworker's commits, you can still try and get a "reverse patch" either manually or using git revert, but since your work is "in the way", so to speak you'll probably get more merge conflicts and more confusing ones. Looks like that's what you ended up in...

Frans
  • 3,670
  • 1
  • 31
  • 29
1

git revert --strategy resolve if commit is a merge: use git revert --strategy resolve -m 1

1

I have a simple solution using a patch to revert all your changes.

  1. Checkout current head branch (e.g. develop)
git checkout develop
  1. Lookup your commit-id in the history log, and check out only your changes into a new branch:
git log
git checkout -b your-branch <your-commit-id>
  1. Look up in your branch, and find the previous status you want to revert to:
git checkout -b prev-status <previous-commit-id>
  1. Create a patch that can revert all your changes:
git diff your-branch..prev-status > reverts.patch
# the comparing order of branches is important
  1. checkout current head branch and apply the reverting patch
git checkout origin develop
git apply reverts.patch
git add *
git commit -m "revert all my changes"
Mclamee
  • 31
  • 4
1

Here is a quick example of how to do this with egit:

  1. I want to delete the commit 3 where I added the file 3 enter image description here
  2. Right click on the commit before the one to be deleted and choose "interactive rebase" enter image description here
  3. In the rebase view choose the commit three and click on the icon skip above. On hoover over this icon, it tells you that the commit will be deleted. enter image description here
  4. Click on start rebase. enter image description here
  5. Go in git staging and click on push. enter image description here
  6. In the popup which appears click on force and go forward enter image description here
  7. After this you can see that the commit was deleted. enter image description here
fayabobo
  • 81
  • 1
  • 1
  • 7
1

Recently encountered a similar issue and ended up doing this which felt simpler which can work for few cases.

My scenario:

% git log --oneline

4ad59d6 commit 3
f244533 commit 2
c5b4688 commit 1

What we wanted to do wes to create commit 4 with changes in "commit 2" reverted. This is what we did:

  1. Get the changes in commit 2:

    % git show > ~/patches/commit.2.patch

  2. Revert the changes:

    % git apply -R ~/patches/commit.2.patch

  3. Create new commit:

    % git commit -Am "commit 4 : reverts changes in commit 2"

P.S: This works for cases where we can easily apply revert - if the same lines of code has be amended in course of time, this would not work.

manuskc
  • 784
  • 4
  • 14
0

Simplest way, Just look for the log and have a count which commits and check out the commit-msg which you want to drop.

Replace x with the count, I will show all the commit-msg, just delete the commit-msg which you want to remove.

# git rebase -i HEAD~x

Sushil
  • 121
  • 1
  • 7
0

I am bad at GIT CLI. I needed to remove a couple of commits from a branch. I did it using TortoiseGit. Here are the steps. First, right click Tortoisegit in the File Explorer and open the Rebase... window:

enter image description here

  1. In the Branch dropdown, select the branch.
  2. In the Upstream dropdown, select the main branch.
  3. Select Force Rebase.
  4. In the main window, click on a commit that is to be removed. Inspect the files in the second window to make sure. Then, right click on the commit and select Skip.
  5. Click on the button Start Rebase.
kiatng
  • 2,847
  • 32
  • 39
-2

i would see a very simple way

git reset --hard HEAD <YOUR COMMIT ID>

and then reset remote branch

git push origin -f

Mansur Ul Hasan
  • 2,898
  • 27
  • 24