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.
There are four ways of doing so:
Clean way, reverting but keep in log the revert: git revert --strategy resolve
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
.
Rebase (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
Alternatives: 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 -f
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). Prefer to always specify the remote branch when you force push: git push --force origin your_branch
.
Here is an easy solution:
git rebase -i HEAD~x
(Note: x
is the number of commits)
upon executing notepad file will open. Enter drop
besides your commit.
If you don’t know Vim, just click on each word pick that you want to edit and then hit the "I" key (for insert mode). Once you’re done typing hit the "esc" key to exit insert mode.
https://i.stack.imgur.com/nvGO3.png
and that's it, you are done... Just sync the git dashboard and the changes will be pushed to remote.
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
.
The algorithm that Git uses when calculating diffs to be reverted requires that
The lines being reverted are not modified by any later commits. 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
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.
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.
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.
Your choice is between
keeping the error and introducing a fix and 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.
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
https://i.stack.imgur.com/Ig79s.png
Remove the commits you don't want by deleting their lines 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.
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...
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
redo the merge
rebase your subsequent work back on: git rebase --onto
remove the temporary branch
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...
git revert --strategy resolve if commit is a merge: use git revert --strategy resolve -m 1
I have a simple solution using a patch to revert all your changes.
Checkout current head branch (e.g. develop)
git checkout develop
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>
Look up in your branch, and find the previous status you want to revert to:
git checkout -b prev-status <previous-commit-id>
Create a patch that can revert all your changes:
git diff your-branch..prev-status > reverts.patch
# the comparing order of branches is important
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"
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:
Get the changes in commit 2: % git show > ~/patches/commit.2.patch Revert the changes: % git apply -R ~/patches/commit.2.patch 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.
Here is a quick example of how to do this with egit:
I want to delete the commit 3 where I added the file 3 Right click on the commit before the one to be deleted and choose "interactive rebase" 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. Click on start rebase. Go in git staging and click on push. In the popup which appears click on force and go forward After this you can see that the commit was deleted.
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
i would see a very simple way
git reset --hard HEAD <YOUR COMMIT ID>
and then reset remote branch
git push origin -f
fatal: Cannot do hard reset with paths.
when i use the 1st command
Success story sharing
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.git revert ...
helped me hold back commits from production branch (e.g.master
) and then with combination ofgit cherry-pick
was able to include back those reverted commits into mydevelop
branch in order to not lose any development work while only deploying what needed to be deployed.