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