删除特定提交

我和一个朋友一起做一个项目,他编辑了一堆不应该编辑的文件。不知何故,我将他的作品合并到我的作品中,无论是在我拉出它时,还是在我试图挑选出我想要的特定文件时。我一直在寻找和玩很长时间,试图弄清楚如何删除包含对这些文件的编辑的提交,这似乎是revert和rebase之间的折腾,并且没有直接的例子,并且文档假设我比我知道的更多。

所以这是问题的简化版本:

鉴于以下情况,我如何删除提交 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.
stack overflow Remove specific commit
原文答案
author avatar

接受的答案

Git 在计算要恢复的差异时使用的算法要求

  1. 被还原的行不会被任何后来的提交修改。
  2. 历史上以后没有任何其他“相邻”提交。

“相邻”的定义基于上下文差异的默认行数,即 3。因此,如果“myfile”的构造如下:

$ 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(-)

然后一切都按预期工作。

第二个答案很有趣。有一个尚未正式发布的功能(尽管它在 Git v1.7.2-rc2 中可用)称为 Revert Strategy。你可以这样调用 git:

git revert --strategy resolve

它应该能更好地理解您的意思。我不知道可用策略列表是什么,也不知道任何策略的定义。


答案:

作者头像

有四种方法可以这样做:

  • 干净的方式,还原但记录还原:

    git revert --strategy resolve <commit>
    
  • 苛刻的方式,只删除最后一次提交:

    git reset --soft "HEAD^"
    

注意:避免 git reset --hard 因为它也会丢弃自上次提交以来文件中的所有更改。如果 --soft 不起作用,请尝试 --mixed--keep

  • Rebase(显示最后 5 次提交的日志并删除您不想要的行,或者重新排序,或者将多个提交合并为一个,或者做任何您想做的事情,这是一个非常通用的工具):

    git rebase -i HEAD~5
    

如果犯了一个错误:

git rebase --abort
  • 快速变基:仅删除使用其 id 的特定提交:

    git rebase --onto commit-id^ commit-id
    
  • 备选方案:您也可以尝试:

    git cherry-pick commit-id
    
  • 另一种选择:

    git revert --no-commit
    
  • 作为最后的手段,如果您需要完全自由的历史编辑(例如,因为 git 不允许您编辑您想要的内容),您可以使用这个 very fast 开源应用程序: reposurgeon

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

注意 2:如果在一个分支上工作并且你需要强制推送,你应该绝对避免 git push --force 因为这可能会覆盖其他分支(如果你对它们进行了更改,即使你当前的结帐是在另一个分支上)。首选在强制推送时始终指定远程分支git push --force origin your_branch

作者头像

这是一个简单的解决方案:

git rebase -i HEAD~x

其中 x 是提交数。

在提交前输入 drop

enter image description here

就是这样,你就完成了。如果你删除的提交已经在远程,你将不得不强制推送。因为 --force 被认为是 harmful ,所以使用 git push --force-with-lease

作者头像

方法一

首先获取您需要还原的提交哈希(例如:1406cd61)。简单的修复将在命令之下,

$ git revert 1406cd61

如果您在 1406cd61 提交后提交了与 1406cd61 文件相关的更多更改,上述简单命令将不起作用。然后你必须做下面的步骤,这是樱桃采摘。

方法 2

请遵循以下操作顺序,因为我们正在使用 --force 你需要对 git repo 拥有管理员权限才能执行此操作。

第 1 步: 在要删除的提交之前找到提交 git log

第 2 步: 签出提交 git checkout <commit hash>

第 3 步: 使用您当前的结帐提交创建一个新分支 git checkout -b <new branch>

第 4 步: 现在您需要在删除的提交后添加提交 git cherry-pick <commit hash>

第 5 步: 现在对您要保留的所有其他提交重复第 4 步。

第 6 步: 一旦所有提交都已添加到您的新分支并已提交。检查一切是否处于正确状态并按预期工作。仔细检查所有内容是否已提交: git status

第 7 步:切换到损坏的分支 git checkout <broken branch>

第 8 步: 现在对损坏的分支执行硬重置,以在您要删除的分支 git reset --hard <commit hash> 之前提交

第 9 步: 将你的固定分支合并到这个分支 git merge <branch name>

第 10 步:将合并的更改推回原点。警告:这将覆盖远程仓库! git push --force origin <branch name>

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

作者头像

你的选择介于

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

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

Git revert 是一个自动化工具来完成 (1),它创建一个新的提交来撤销之前的一些提交。您会在项目历史记录中看到错误和删除,但是从您的存储库中提取的人在更新时不会遇到问题。它在您的示例中没有以自动方式工作,因此您需要编辑“myfile”(删除第 2 行),执行 git add myfilegit commit 来处理冲突。然后,您将在历史记录中得到四次提交,其中提交 4 还原提交 2。

如果没有人关心您的历史更改,您可以重写它并删除提交 2(选择 2)。最简单的方法是使用 git rebase -i 8230fa3 。这将使您进入编辑器,您可以通过删除提交(并在其他提交消息旁边保留“选择”)来选择不包括错误提交。请阅读 consequences of doing this

作者头像

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

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-rebase-todo

1.通过删除他们的行来删除你不想要的提交
2.保存退出

如果 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

( Note that git rebase -i HEAD~5 results with "fatal: Needed a single revision; invalid upstream HEAD~5". ) 文本编辑器(参见 @Dennis' answer 中的屏幕截图)将打开这些内容:

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

所以我们得到所有提交 since (但 not including )我们请求的 HEAD~4。删除 pick 448c212 3rd git commit 行并保存文件;您将从 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 不能为你做决定 - you 必须做出决定:通过删除第 3 次提交,你要么保留它引入的更改(这里, cccc 行)--或者你没有。如果你不这样做,只需使用文本编辑器删除 cccc 中的额外行 - 包括 folder/file.txt -,它看起来像这样:

aaaa
bbbb
dddd

... 然后保存 folder/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

啊 - 所以为了标记我们已经解决了冲突,我们在执行 must 之前 git add folder/file.txt git rebase --continue

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

此处文本编辑器再次打开,显示行 4th git commit - 在这里我们有机会更改提交消息(在这种情况下可以有意义地更改为 4th (and removed 3rd) commit 或类似内容)。假设您不想 - 所以不保存就退出文本编辑器;一旦你这样做,你会得到:

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

在这一点上,现在你有一个像这样的历史记录(你也可以用 gitk . 或其他工具来检查) folder/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 在删除提交/修订方面是如何工作的;所以希望它也能帮助别人......

作者头像

所以听起来错误的提交在某个时候被合并到合并提交中。你的合并提交已经被拉取了吗?如果是,那么您将要使用 git revert ;你必须咬紧牙关,努力解决冲突。如果不是,那么你可以想像地要么 rebase 要么 revert,但你可以这样做 before 合并提交,然后重做合并。

对于第一个案例,我们真的帮不上什么忙。尝试还原并发现自动还原失败后,您必须检查冲突并适当修复它们。这与修复合并冲突的过程完全相同;你可以使用 git status 来查看冲突在哪里,编辑未合并的文件,找到冲突的 hunks,弄清楚如何解决它们,添加冲突的文件,最后提交。如果你单独使用 git commit (没有 -m <message> ),那么在你的编辑器中弹出的消息应该是 git revert 创建的模板消息;您可以添加有关如何解决冲突的注释,然后保存并退出以提交。

对于第二种情况,修复问题 before 您的合并,有两个子情况,具体取决于合并后您是否做了更多的工作。如果还没有,您可以简单地 git reset --hard HEAD^ 取消合并,进行还原,然后重做合并。但我猜你有。所以,你最终会做这样的事情:

  • 在合并之前创建一个临时分支,并检查它
  • 做恢复(或使用 git rebase -i <something before the bad commit> <temporary branch> 删除错误的提交)
  • 重做合并
  • 将您的后续工作重新定位于: git rebase --onto <temporary branch> <old merge commit> <real branch>
  • 移除临时分支
作者头像

所以你做了一些工作并推送了它,让我们称它们为提交 A 和 B。你的同事也做了一些工作,提交 C 和 D。你将你的同事的工作合并到你的(合并提交 E),然后继续工作,承诺,也是(提交 F),并发现您的同事更改了一些他不应该更改的内容。

所以你的提交历史看起来像这样:

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

你真的想摆脱 C、D 和 D'。由于您说您将同事的工作合并到您的工作中,这些提交已经“在那里”,因此使用例如删除提交git rebase 是一个禁忌。相信我,我试过了。

现在,我看到了两条出路:

  • 如果您还没有将 E 和 F 推送给您的同事或其他任何人(通常是您的“原始”服务器),您仍然可以暂时从历史记录中删除它们。这是您要保存的工作。这可以通过一个

    git reset D'
    

    (将 D' 替换为您可以从 git log 获得的实际提交哈希

    此时,提交 E 和 F 消失了,更改再次成为本地工作区中未提交的更改。在这一点上,我会将它们移动到一个分支或将它们变成一个补丁并保存以备后用。现在,使用 git revert 自动或手动恢复您同事的工作。完成后,在此基础上重播您的作品。您可能有合并冲突,但至少它们会出现在 you 编写的代码中,而不是您同事的代码中。

  • 如果您已经在同事提交后推送了您所做的工作,您仍然可以尝试手动或使用 git revert 获得“反向补丁”,但是由于您的工作“妨碍”,可以这么说您可能会遇到更多的合并冲突和更令人困惑的冲突。看起来这就是你最终的结果......

作者头像

git revert --strategy resolve 如果提交是合并: 使用 git revert --strategy resolve -m 1