删除特定的提交


287

我和一个朋友一起在一个项目上工作,他编辑了一堆本不应该编辑的文件。我以某种方式将他的作品合并到我的作品中,或者当我将其合并时,或者当我试图只选择想要的特定文件时。我一直在寻找和玩耍很长时间,试图弄清楚如何删除包含对这些文件所做的修改的提交,这似乎是在还原和重新设置之间的一次折腾,并且没有简单的示例,并且文档假设我比我知道的更多。

因此,这是问题的简化版本:

在以下情况下,如何删除提交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.

4
如果有人在搜索同一问题时发现此问题,这就是我最终要做的事情:复制并粘贴。说真的 花费了6个多小时才能尝试获得建议的解决方案,但无济于事。最后,我没时间了,拿起原始文件,只复制/粘贴了大约20个文件。花费了不到5分钟的时间,从那以后一切都很好(即使这些文件与此惨败之前发生的其他分支的更改合并在一起)。我建议您也采用这种方法。它不仅最简单,而且我怀疑它是唯一有效的方法。
约书亚脸颊

我遇到了类似的问题,但也许更复杂:我的分支有数百个我想压榨的提交。不幸的是,来自另一个分支的提交在中间点被合并回到了分支中,因此在压扁之前需要“取消合并”。我沿着下面的tk建议的方式走了类似的路(樱桃采摘+使用范围表示法),但是它在其他一些文件中产生了冲突。最后,复制粘贴+一些手动编辑是最简单,最可预测的前进方向。如果您发现自己花太多时间在此上,绝对值得考虑。
Federico

Answers:


73

Git在计算要还原的差异时使用的算法要求

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

“ adjacent”的定义基于上下文差异的默认行数,即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中可用)称为恢复策略。您可以像这样调用git:

git revert-策略解决 <commit>

并且应该可以更好地弄清您的意思。我不知道可用策略的清单是什么,也不知道任何策略的定义。


1
perl -p是用于写入很短(一行)的方案,其环的和有用的传递通过其输入到输出,类似的sed。 perl -i用于就地编辑文件。 perl -e是如何提交要评估的代码。
哈尔·艾森

281

有四种方法:

  • 干净的方法,还原但保留日志中的还原:

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

    git reset --soft "HEAD^"
    

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

  • 重新设置基准(显示最近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不允许您编辑想要的内容),则可以使用此非常快速的开源应用程序:reposurgeon

注意:当然,所有这些更改都是在本地完成的,git push此后您应该将更改应用到遥控器。并且如果您的仓库不想删除提交(“不允许快速转发”,当您想要删除已经推送的提交时发生),您可以使用git push -f强制推送更改。

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


基于@gaborous的建议:执行“ git rebase -i HEAD〜2”。现在您有几个选择。在vim中,您可以看到一些注释行:其中之一告诉您,您只需删除一行即可(该行应该是您要删除的提交),并且此提交将与历史记录一起被删除。
Ilker Cat

git revert --strategy resolve <commit>。这个命令对我有用。谢谢:)
Swathin

2
git rebase -i HEAD~5为我工作。然后,我只是删除了不需要的提交,因此能够在不到30秒的时间内解决问题。谢谢。
lv10

117

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

git rebase -i HEAD~x

(注意: x是提交次数)

执行记事本文件将打开。输入drop您的提交。
如果您不了解Vim,只需单击要编辑的每个单词选择,然后按“ I”键(用于插入模式)。输入完毕后,按“ esc”键退出插入模式。



在此处输入图片说明

就是这样,您就完成了...只需同步git仪表板,更改将被推送到远程。

如果您丢弃的提交已经在远程上,则必须强制执行推送。由于--force被认为是 有害的,请使用git push --force-with-lease


如果您只知道相关提交的哈希值,是否有一种很好的方法来找出应为“ x”的内容?
jlewkovich

1
对于具有cpp意识的人:x(提交数)包括在内。例如HEAD〜4包括最后4次提交。
疱疹免费工程师

完美的建议。只是要添加,这也会丢弃已删除提交中的更改。
celerno

尝试还原3次提交:git rebase -i HEAD-3错误致命:需要单个修订版无效上游'HEAD-
3'–乌斯丁

1
@Ustin大约是3而不是-3
JD-V

37

您的选择介于

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

您应该选择(1)如果其他任何人都接受了错误的更改,则选择(2)如果错误仅限于未按下的私有分支。

Git恢复是一种自动执行的工具(1),它会创建一个新的提交来撤消某些先前的提交。您将在项目历史记录中看到错误和删除,但是从您的存储库中拉出的人员在进行更新时不会遇到问题。在您的示例中,它不是自动运行的,因此您需要编辑“ myfile”(删除第2行),执行git add myfilegit commit处理冲突。然后,您将在历史记录中获得四次提交,而提交4将还原提交2。

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


Rebase可能很棘手,因为听起来好像已经合并了。
卡斯卡贝尔

3
git rebase -i 8230fa3,并且删除提交的行对我来说非常有用,因为我只在本地进行了更改。谢谢!
塞缪尔

26

方法1

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

$ git revert 1406cd61

如果在提交1406cd61之后提交了更多与1406cd61文件相关的更改,上述简单命令将不起作用。然后,您必须执行以下步骤,即摘樱桃。

方法2

请按照下面的操作顺序进行操作,由于我们使用的是--force,因此您需要对git repo 具有管理员权限

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

步骤2:检出该提交git checkout <commit hash>

步骤3:使用您当前的Checkout提交创建一个新分支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
第一种方法就像魅力。它包含一个提交,指定您已还原了所需的提交,这对于跟踪而言确实很不错。
suarsenegger

18

您可以使用删除不需要的提交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. 保存并退出

如果重新设置失败,则删除临时分支并尝试其他策略。否则,请继续以下说明。

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.

如果要将主题分支推送到远程,则由于提交历史记录已更改,您可能需要强制推送。如果其他人在同一分支上工作,请给他们一些提示。


您能否举一个“尝试另一种策略”的例子?
pfabri '19

@pfabri例如,您可以选择两个提交范围,而忽略错误的提交。您可以还原错误的提交。您甚至可以通过手动撤消更改来避免使用git解决方案,或者从没有错误提交的新分支开始并手动重做良好的更改来避免使用git解决方案。如果错误的提交包含敏感数据,则需要采取更谨慎的策略:help.github.com/en/articles/…–
Dennis,

8

从这里的其他答案中,我对如何 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将是第一个提交(因此HEAD〜5将不存在)。假设我们要删除第三次提交-我们可以在myrepo_git目录中发出以下命令:

git rebase -i HEAD~4

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

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

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

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

因此,这里git无法为您做出决定-是必须做出决定:通过删除第三次提交,您可以保留它所引入的更改(此处为line 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 addfolder/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.

至此,您已经有了一个这样的历史记录(也可以使用say 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 commit的内容),我们将拥有:

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删除提交/修订的工作方式。所以希望它也可以帮助其他人...


这是一个非常宝贵的演练-我正在努力解决的确切用例,这极大地帮助了我。
pfabri '19

2

因此,听起来好像错误的提交在某个时候已合并到合并提交中。您的合并提交被拉了吗?如果是,那么您将要使用git 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推给您的同事或其他任何人(通常是“原始”服务器),您仍然可以暂时将其从历史记录中删除。这是您要保存的工作。这可以通过

    git reset D'
    

    (将D'替换为您可以从 git log

    这时,提交E和F已经消失,并且更改不再是您本地工作空间中的未提交更改。在这一点上,我将它们移到一个分支上或将其变成一个补丁并保存以供以后使用。现在,使用a自动git revert或手动还原同事的工作。完成此操作后,请重播您的工作。您可能有合并冲突,但是至少它们会出现在编写的代码中,而不是您同事的代码中。

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


0

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

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.