ChatGPT解决这个技术问题 Extra ChatGPT

Remove specific commit

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

p
pfabri

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.


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.
git revert --strategy resolve .This command worked for me. thanks :)
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.
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.
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.
E
Eduard

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.


For cpp-mentality people: x (no. of commits) is inclusive. e.g. HEAD~4 includes the last 4 commits.
perfect suggestion. just want to add, this also discard the changes from the dropped commit.
@Ustin it's ~3 not -3
how to "enter drop"? I can't write anything
This is an amazing solution.
S
Saleh Mahmood

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.
a
angryITguy

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.


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.
C
CharlesB

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.


Rebase may be tricky, since it sounds like there's been a merge.
git rebase -i 8230fa3, and delete the commit's line worked great for me with my local-only changes. Thanks!
D
Dennis

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.


Could you please give an example of 'try another strategy'?
@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: help.github.com/en/articles/…
C
Community

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


What an invaluable walk-through - the exact use-case I was struggling with, this helped me immensely.
C
Cascabel

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 to remove the bad commit)

redo the merge

rebase your subsequent work back on: git rebase --onto

remove the temporary branch


F
Frans

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


S
ShaDow RiDeR

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


M
Mclamee

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"

m
manuskc

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.


f
fayabobo

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.


S
Sushil

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


M
Mansur Ul Hasan

i would see a very simple way

git reset --hard HEAD <YOUR COMMIT ID>

and then reset remote branch

git push origin -f


getting fatal: Cannot do hard reset with paths. when i use the 1st command
it looks like you resetting with paths instead of commit hash