删除特定提交

问题描述 投票:226回答:9

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

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

鉴于以下情况,如何删除提交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 commit revert cherry-pick
9个回答
56
投票

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

它应该做得更好,弄清楚你的意思。我不知道可用策略列表是什么,也不知道任何策略的定义。


238
投票

有四种方法:

  • 干净的方式,恢复但保持在日志恢复: 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
  • 快速rebase:使用其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


77
投票

非常简单的方法

git rebase -i HEAD~x

(x =提交数量)

在执行notepad文件时,除了你的commitenter image description here之外,还会打开'put'

这就是你的完成...只需同步git仪表板,更改将被推送到远程。如果您提交的提交已经在遥控器上,则必须强制进行更新。由于--force被认为是harmful,请使用git push --force-with-lease


36
投票

你的选择介于

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

您应该选择(1)其他人是否已接收到错误更改,以及(2)如果错误仅限于私人未推送分支。

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

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


21
投票

反对派1

首先获取需要还原的提交哈希值(例如: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。


17
投票

您可以使用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视图。例如

  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.

如果您将主题分支推送到远程,则可能需要强制推送,因为提交历史记录已更改。如果其他人在同一个分支机构工作,请给他们一个抬头。


7
投票

从这里的其他答案,我有点混淆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 [email protected]

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将不存在)。假设我们要删除第3次提交 - 我们可以在myrepo_git目录中发出以下命令:

git rebase -i HEAD~4

(请注意,git rebase -i HEAD~5结果为“致命:需要单个修订;无效的上游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
# ...

所以我们得到了所有提交(但不包括)我们要求的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不能为你决定 - 你必须做出决定:通过删除第三次提交,你要么保留它所引入的更改(这里是行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

啊 - 所以为了表明我们已经解决了冲突,我们必须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(我们删除的第3个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如何在删除提交/修订方面工作;所以希望它也可以帮助别人......


2
投票

所以听起来糟糕的提交在某些时候被合并到提交中。你的合并提交已被撤销了吗?如果是,那么你会想要使用git revert;你必须抓紧牙关,解决冲突。如果不是,那么你可以设想rebase或revert,但你可以在合并提交之前这样做,然后重做合并。

真的,我们可以为第一个案例提供帮助。在尝试恢复并发现自动恢复失败后,您必须检查冲突并适当地修复它们。这与修复合并冲突的过程完全相同;您可以使用git status查看冲突的位置,编辑未合并的文件,查找冲突的数据库,找出解决方法,添加冲突的文件,最后提交。如果您自己使用git commit(没有-m <message>),编辑器中弹出的消息应该是git revert创建的模板消息;您可以添加有关如何修复冲突的注释,然后保存并退出提交。

对于第二种情况,在合并之前解决问题,有两个子句,取决于您是否在合并后完成了更多工作。如果你还没有,你可以简单地git reset --hard HEAD^敲掉合并,做恢复,然后重做合并。但我猜你有。所以,你最终会做这样的事情:

  • 在合并之前创建一个临时分支,然后检查它
  • 做还原(或使用git rebase -i <something before the bad commit> <temporary branch>删除错误的提交)
  • 重做合并
  • 重新开始你的后续工作:git rebase --onto <temporary branch> <old merge commit> <real branch>
  • 删除临时分支

1
投票

所以你做了一些工作并推动它,让我们称他们为A和B提交。你的同事做了一些工作,提交C和D.你把你的同事工作合并到你的(合并提交E),然后继续工作,承诺, (承诺F),发现你的同事改变了一些他不应该做的事情。

所以你的提交历史是这样的:

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

你真的想摆脱C,D和D'。既然你说你把你的同事工作合并到了你的工作中,那么这些提交已经“在那里”了,所以删除提交的工具使用例如git rebase是禁忌。相信我,我试过了。

现在,我看到两种方法:

  • 如果您尚未将E和F推送给您的同事或其他任何人(通常是您的“原始”服务器),您仍然可以暂时删除历史记录中的那些。这是您要保存的工作。这可以用a来完成 git reset D' (将D'替换为您可以从git log获得的实际提交哈希 此时,提交E和F已消失,更改是本地工作空间中未提交的更改。此时我会将它们移动到分支或将它们转换为补丁并保存以供日后使用。现在,使用git revert或手动自动恢复同事的工作。当你完成后,重温你的工作。您可能有合并冲突,但至少它们将在您编写的代码中,而不是您的同事的代码。
  • 如果你已经推动了你在同事提交后所做的工作,你仍然可以尝试手动或使用git revert获得“反向补丁”,但由于你的工作“在路上”,所以你可能会说获得更多的合并冲突和更令人困惑的冲突。看起来这就是你最终的结果......
© www.soinside.com 2019 - 2024. All rights reserved.