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.

下面是这个问题的简化版本:

给定下面的场景,我如何删除提交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(-)

预期的结果是

$ cat myfile
line 1
line 3

以下是我一直试图恢复的一个例子

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

当前回答

我能想到一个很简单的方法

git重置——hard HEAD <你的提交ID>

然后重置远程分支

Git push origin -f

其他回答

你可以使用git rebase删除不需要的提交。 假设您将同事的topic分支的一些提交包含到您的topic分支中,但后来决定不需要这些提交。

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

此时,文本编辑器将打开交互式rebase视图。例如

通过删除它们的行来删除您不想要的提交 保存并退出

如果改基不成功,删除临时分支并尝试其他策略。否则,请继续执行以下说明。

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.

如果将主题分支推到远程,可能需要强制推,因为提交历史已经更改。如果其他人在同一个分支上工作,给他们提个醒。

从这里的其他答案,我有点困惑如何git rebase -i可以用来删除一个提交,所以我希望它可以在这里记下我的测试用例(非常类似于OP)。

下面是一个bash脚本,你可以粘贴在/tmp文件夹中创建一个测试存储库:

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"

现在,我们有了一个包含以下内容的file.txt文件:

aaaa
bbbb
cccc
dddd
eeee

此时,HEAD是第5次提交,HEAD~1是第4次提交,HEAD~4是第1次提交(因此HEAD~5不存在)。假设我们想要删除第三次提交——我们可以在myrepo_git目录下发出这个命令:

git rebase -i HEAD~4

(注意git rebase -i HEAD~5的结果是“fatal:需要一次修订;上游HEAD~5”无效。)一个文本编辑器(参见@Dennis回答中的截图)将打开以下内容:

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

所以我们得到了所有的提交,因为(但不包括)我们所请求的HEAD~4。删除行pick 448c212第三次git提交并保存文件;你会从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

此时,在文本编辑器中打开myrepo_git/folder/file.txt;你会看到它被修改了:

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

基本上,git看到当HEAD第二次提交时,有aaaa + bbbb的内容;然后它有一个补丁添加的cccc+dddd,它不知道如何附加到现有的内容。

所以这里git不能为你决定——你必须做出决定:通过删除第三次提交,你要么保留它所带来的更改(这里是cccc行)——要么不保留。如果你不这样做,只需删除额外的行-包括cccc -在文件夹/file.txt使用文本编辑器,所以它看起来像这样:

aaaa
bbbb
dddd

... 然后保存文件夹/file.txt。现在你可以在myrepo_git目录下执行以下命令:

$ 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

啊-所以为了标记我们已经解决了冲突,我们必须在git rebase——continue之前添加文件夹/file.txt:

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

这里再次打开文本编辑器,显示第4行git commit -这里我们有机会更改提交消息(在这种情况下可以有意义地更改为第4(并删除第3)提交或类似)。假设你不想退出文本编辑器而不保存;一旦你这样做了,你会得到:

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

在这一点上,现在你有一个这样的历史(你也可以用say gitk检查。或其他工具)的/file.txt文件夹的内容(显然,原始提交的时间戳没有改变):

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

如果之前,我们决定保留cccc行(我们删除的第三次git提交的内容),我们会有:

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

好吧,这是我希望我能找到的那种阅读,开始研究git rebase如何在删除提交/修订方面工作;所以希望这也能帮助到其他人…

你的选择是

保留错误并引入修复 删除错误并更改历史记录。

您应该选择(1)如果错误的更改已被其他人拾取,以及(2)如果错误仅限于一个私有的未推送分支。

Git revert是一个自动执行(1)的工具,它创建了一个新的提交,撤销了之前的一些提交。您将在项目历史中看到错误和删除,但是从您的存储库中提取的人在更新时不会遇到问题。在你的例子中,它不是以自动的方式工作的,所以你需要编辑'myfile'(删除第2行),git添加myfile和git提交来处理冲突。然后,历史记录中将有四个提交,提交4将还原提交2。

如果没有人关心你的历史改变,你可以重写它并删除提交2(选择2)。最简单的方法是使用git rebase -i 8230fa3。这将使您进入一个编辑器,您可以通过删除提交来选择不包括错误的提交(并将“pick”放在其他提交消息旁边)。仔细研究一下这样做的后果。

有四种方法:

干净的方式,恢复,但保持日志恢复: Git revert—strategy resolve <commit> 严厉的方式,完全删除只有最后一个提交: git重置-软“头^”

注意:避免git重置——很难,因为它也会丢弃自上次提交以来文件中的所有更改。如果——软的不行,那就试试——混合的或者——保留的。

Rebase(显示最近5次提交的日志并删除你不想要的行,或重新排序,或将多个提交压缩在一个文件中,或做任何你想做的事情,这是一个非常通用的工具): git rebase -i HEAD~5

如果犯了错误:

git rebase --abort

快速rebase:只删除使用id的特定提交: Git rebase—to commit-id^ commit-id 替代方案:你还可以试试: Git选择commit-id 还有另一种选择: Git恢复——不提交 作为最后的手段,如果你需要完全自由的历史编辑(例如,因为git不允许你编辑你想要的内容),你可以使用这个非常快速的开源应用程序:reposurgeon。

注意:当然,所有这些更改都是在本地完成的,你应该在git push之后将更改应用到远程。如果你的repo不想删除提交(“不允许快进”,当你想删除已经提交的提交时就会发生),你可以使用git push -f强制推送更改。

注2:如果在一个分支上工作,你需要强制push,你应该绝对避免git push——force,因为这可能会覆盖其他分支(如果你对它们做了更改,即使你当前的签出是在另一个分支上)。当你强制push时,最好总是指定远程分支:git push——force origin your_branch。

方法1

首先获取需要恢复的提交散列(例如:1406cd61)。简单的修复将在命令之下,

$ git revert 1406cd61

如果你在1406cd61文件提交后提交了更多与1406cd61文件相关的更改,上述简单命令将无法工作。然后你要做下面的步骤,也就是摘樱桃。

方法2

请遵循下面的操作顺序,因为我们正在使用——force,您需要对git回购有管理权限才能做到这一点。

步骤1:找到要删除git日志的提交前的提交

步骤2:签出提交git Checkout <commit hash>

步骤3:使用当前的checkout commit git checkout -b <new branch>创建一个新的分支

第4步:现在你需要在移除commit git cherry-pick <commit hash>之后添加commit

步骤5:现在对所有想要保留的提交重复步骤4。

步骤6:将所有提交添加到新分支并提交之后。检查所有东西是否处于正确的状态并按预期工作。再次检查已提交的所有内容:git状态

步骤7:切换到你的broken branch git checkout <broken branch>

第8步:现在执行一个硬重置,在你想要删除git重置之前的提交分支上——hard <commit hash>

步骤9:将固定的分支合并到这个分支git Merge <分支名称>

步骤10:将合并的更改推回原点。警告:这将覆盖远程回购!Git push——force origin <分支名称>

您可以在不创建新分支的情况下执行该过程,将步骤2和步骤3替换为步骤8,然后不执行步骤7和步骤9。