Git - 删除中间的提交记录

23

我们的团队正在使用PHP进行多个项目。我们错误地将一个项目的文件夹提交到了另一个项目中。现在,我们想从该项目中删除该特定提交。如果我们从项目中删除该特定文件夹/提交,则对我们的项目没有任何问题。

如果仅删除文件夹并在当前位置发布新的提交,那么文件夹就会被删除,但它仍将保留在Git的历史记录中。因此,我们想要彻底从Git的refs,历史记录和其他方面将其删除。

我们还可以创建一个单独的分支,但作者的提交引用会丢失。我们只想删除那个特定的提交。我们不介意重新编写历史或重新设置基础,但不知道如何做。

在项目中,我们已经完成了136次提交,并希望删除第76个提交。关于SHA的所需信息如下:

5d39775b          //136th commit
a4df5ee9          //135th commit
6971cf35          //134th commit
.....
....
162f833c          //76th commit
32603274          //75th commit
.....
....
9770059          //1st commit
7个回答

31

你可以在你的主分支(master branch)上进行交互式变基(interactively rebase):

git rebase -i 162f833c^

这将以有问题的提交之前的提交为基础进行变基。 现在只需从列表中删除有问题的提交,保存并退出编辑器(* nix平台默认为vi)。

这将使您的分支重新基于问题提交之前的提交,并排除了该问题提交。这似乎是您试图实现的目标。


4
在回答中应该提到,在rebase操作后,“git push -f”应该被提及以反映对上游的更改。 - Krishna Pandey
这给了我以下信息:致命错误:无效的上游'c36e91f' - RegarBoy
@RegarBoy 试试输入完整的提交哈希值,这样对我来说有效。 - undefined

26

我已经尝试了提供的所有方法,即rebase和cherry-pick。我在这里提供关于我尝试过的两种方法以及我所做的事情的完整信息,以帮助更好地理解。

准备工作

为了比较哪个方法更优,我做了以下几件事:

  1. 分叉远程存储库,使原始存储库保持完好无损,易于理解。因此,在确定所完成的操作是否正确之前,请勿对原始存储库进行任何更改。

  2. 首先获取Git Repo的干净副本。通常,每当我们隐藏或执行其他操作时,也会将其存储在Git中作为本地Git数据库。因此,该Git数据库不具有任何局部或临时内容。

  3. 计算文件夹.git占用的总字节数。由于存储库是干净的,因此最小字节应在此处正确。为了进一步深入,我记录下了字节和磁盘上占用的字节数。它们是不同的东西,并且它们是:

bytes - 7,963,769 and size on disk - 8,028,160
  1. 如果您使用的是Windows并安装了任何反病毒软件,则必须禁用Active / Real time模式。因为Git需要快速访问i / o文件检查。当Git创建文件时,活动模式会锁定新创建的文件以进行病毒检查,当Git尝试重新访问该文件时,由于反病毒软件的锁定而失败。如果发生故障,则必须从1重新启动过程。

方法I - Rebase

在re-basing方法中,有两种方法可以完成。一种是通过--onto,另一种是使用-i。

-i方法

我使用以下命令:

 git rebase -i 162f833c^

然后它打开了vim编辑器,我看到的提交列表是从162f833c开始而不是从主分支(master)开始。如果需要,下面提供了以下几行内容。

# Rebase 3260327..5d39775 onto 3260327
#
# Commands:
#  p, pick = use commit
#  r, reword = use commit, but edit the commit message
#  e, edit = use commit, but stop for amending
#  s, squash = use commit, but meld into previous commit
#  f, fixup = like "squash", but discard this commit's log message
#  x, exec = run command (the rest of the line) using shell
#
# If you remove a line here THAT COMMIT WILL BE LOST.
# However, if you remove everything, the rebase will be aborted.
#

我删除了一行代码,因此该提交将被丢失,并保存了文件并退出编辑器,当我退出时,它开始重新进行基础操作,如下所示:


Rebasing ( 1/64) ..(2/64)

Successfully rebased and updated refs/heads/master.

然后,我尝试检查日志以查看提交是否已丢失,在日志中我找不到要删除的提交。这意味着命令已成功完成。然后我尝试通过以下方式检查状态:

# On branch master
# Your branch and 'origin/master' have diverged,
# and have 64 and 65 different commit(s) each, respectively.

由于提交的代码已被删除,但是在其派生的远程仓库中需要进行推送。因此,使用强制方式推送提交以覆盖先前的代码。

git push -f

之后,我删除了本地存储库,并重新获取了存储库,它显示的大小为:

bytes 6,831,765 bytes
size on disk 6,897,664 bytes

--onto 方法

在阅读了许多博客和手册之后,我找到了一个真正让我知道如何使用它的博客这里。所以我使用的命令是:

git rebase --onto 3260327 79504a5~1

输出结果为:

First, rewinding head to replay your work on top of it...
Applying: 77th Message
Applying: 78th Message
Applying: 79th Message
....
Last commit

然后我尝试查看日志,确认提交是否已经丢失。但是在日志中我找不到想要删除的提交。这意味着命令已经成功执行。接下来,我尝试使用以下命令检查状态:

# On branch master
# Your branch and 'origin/master' have diverged,
# and have 64 and 65 different commit(s) each, respectively.

所以,只需要一个命令就可以完成所有的事情,然后我进行了强制推送,以检查字节等信息,就像之前一样。

git push -f

之后我删除了本地仓库,重新拉取了仓库,它显示的大小为:

bytes - 6,831,389
size on disk - 6,893,568    

Rebase -i Vs --onto

在使用rebase方法之后,我非常赞同使用--onto方法删除提交记录,因为它是一条命令且字节数比-i方法略高。真正的优点是我们不需要在--onto方法中执行任何额外的步骤。

方法二 - Cherry-pick

Cherry-pick是一个非常好的方法,但需要运行多个命令,并且在运行这些命令时需要小心。首先,我必须创建一个单独的日志文件:

git log --all --decorate --oneline --graph > 1

这向我展示了存储库的日志,因为我们需要一遍又一遍地查看它。从这个存储库中识别您想要删除的提交并复制SHA密钥,最后一个良好提交的含义是确定我们不想更改任何内容的提交。因此,下一个命令将是:

git checkout 3260327 -b repair

生成输出:

Switched to a new branch 'repair'

因此,在最后一次良好提交之前,我们创建了一个新分支。因此,直到最后一个良好的提交,事情都没有改变。再次运行以下命令以查看所有良好的提交:

git log --decorate --oneline --graph > 2

我已删除了 --all 选项,因为我只想查看 repair 分支的提交记录。它向我展示了正确标记的 Good Commits。现在,下一个命令需要小心使用,因为它将包括坏的提交作为起点和最后一次提交作为终点,命令如下:

git cherry-pick 162f833..5d39775

输出:

[repair 9ed3f18] xxxxxx
x files changed, xxx insertions(+), xx deletions(-)
[repair 7f06d73] xxxxx
xx files changed, xxx insertions(+), xx deletions(-)
.....
...
该命令重新提交所有的提交,从上面提供的第一次提交(162f833)到最后一次提交(5d39775)。因此,SHA提交值会相应地更改,因为它会逐个重新提交提交。现在是查看日志的时候了:
git log --all --decorate --oneline --graph > 3

输出为:

* f61a9a5 (HEAD, repair) xxxxxx
* 25be3b9 xxxxx
* 49be029 xxxxx
 .......
 .......
| * 5d39775 (origin/master, origin/HEAD, master)
| * a4df5ee xxxxx
| * 6971cf3 xxxxxx
| .......
| .......
| * 162f833 xxxx
|/
* 3260327 xxxxx
......
* 9770059 xxxxx

因此,查看图形可以让我们知道它已重新提交除了坏提交以外的所有提交。将向您显示所有旧SHA密钥与新密钥。如果一切正常,我们必须将修复分支作为主分支,并将主分支删除,如下所示:

git checkout master

并输出为:

Switched to branch 'master'

首先,检出主分支,以便我们可以覆盖主分支上的更改。然后

git reset --hard 3260327

并输出为:

HEAD is now at 3260327 xxxxx

它将丢弃好的提交后面的提交,现在我们必须将修复分支与主分支合并:

git merge repair

并输出为:

Updating 3260327..40d290d
Fast-forward

现在,如果您查看日志,它将不会显示错误提交,一切都做完了。 我之前进行了强制推送以检查字节等。

git push -f

之后我删除了本地仓库并重新拉取了仓库,现在它显示的大小为:

bytes - 6,831,556
size on disk - 6,897,664

命令排名

Rebase --onto [第一个]

bytes - 6,831,389
size on disk - 6,893,568    

挑选性提交 [第二次]

bytes - 6,831,556
size on disk - 6,897,664

交互式变基 [第三步]

bytes 6,831,765 bytes
size on disk 6,897,664 bytes

我希望使用 git rebase --onto 命令,因为这样可以用单个命令干净地完成所有事情。


1
Git rebase --onto 的文章可以在这里找到:https://content.pivotal.io/blog/git-rebase-onto - Philippe

4

你可以使用交互式变基。

由于你要删除的提交具有sha1 162f833c,则只需执行git rebase -i 162f833c^

将打开一个文本编辑器并显示一系列提交。 只需删除对应所需删除提交的行,保存并关闭。

作为安全措施,当我执行此类操作时,我喜欢在我的HEAD上打标签,以便如果出现问题,我可以检出该标签并恢复初始状态。


完成此操作后,您的分支和 origin/master 已分叉,并且具有 64 和 65 个不同的提交。 - Vineet1982
@Vineet1982:是的,这就是你要求的(你说“重写历史没有问题”)。“删除”一个提交,rebase 必须创建64个提交,省略你想要删除的底部第65个提交。现在,你将这个新的64个提交链的末尾称为“分支master的顶端”,你只需要让其他人称同一个提交为“master”,并放弃之前的“master”。这就是“重写历史”的意思! - torek
@torek 我想要移除第76个提交,而不是第64或65个。 - Vineet1982
@Vineet1982:啊,我明白了。你确定你在正确的方向上计算提交次数吗?136-76=60,如果此后有一些新的提交(使其更像是140-76),那就差不多了。(另外,如果序列中有任何合并,请注意:rebase通常会将它们平铺掉。) - torek

3
您可以通过以下命令自动删除提交并重写历史记录(其中${ref_to_delete}表示您想要删除的提交,假设它位于master分支上):
git rebase --onto ${ref_to_delete}~ ${ref_to_delete} master

如果你使用远程仓库,你需要使用-f参数进行push操作:
git push -f

2
您可以使用git cherry-pick来实现此操作:
$ g  # g is my alias for git log --all --decorate --oneline --graph --color
* 618f8e5 [2013-12-14 15:13] (HEAD, master) me: good 6
* e27d6d7 [2013-12-14 15:13] me: good 5
* 533f6c3 [2013-12-14 15:13] me: good 4
* 877585f [2013-12-14 15:13] me: bad
* 00c06f3 [2013-12-14 15:12] me: good 3
* e9f80a4 [2013-12-14 15:12] me: good 2
* 3122ba7 [2013-12-14 15:12] me: good 1
* 98da603 [2013-12-14 15:12] me: first
$ git checkout 00c06f3 -b repair
Switched to a new branch 'repair'
$ git cherry-pick 877585f..618f8e5
[repair b340e21] good 4
 1 file changed, 1 insertion(+)
[repair 1d2e0d0] good 5
 1 file changed, 1 insertion(+)
[repair 1ed1d19] good 6
 1 file changed, 1 insertion(+)
$ g
* 1ed1d19 [2013-12-14 15:13] (HEAD, repair) me: good 6
* 1d2e0d0 [2013-12-14 15:13] me: good 5
* b340e21 [2013-12-14 15:13] me: good 4
| * 618f8e5 [2013-12-14 15:13] (master) me: good 6
| * e27d6d7 [2013-12-14 15:13] me: good 5
| * 533f6c3 [2013-12-14 15:13] me: good 4
| * 877585f [2013-12-14 15:13] me: bad
|/  
* 00c06f3 [2013-12-14 15:12] me: good 3
* e9f80a4 [2013-12-14 15:12] me: good 2
* 3122ba7 [2013-12-14 15:12] me: good 1
* 98da603 [2013-12-14 15:12] me: first
$ git checkout master
Switched to branch 'master'
$ git reset --hard repair
HEAD is now at 1ed1d19 good 6
$ git branch -d repair
Deleted branch repair (was 1ed1d19).
$ g
* 1ed1d19 [2013-12-14 15:13] (HEAD, master) me: good 6
* 1d2e0d0 [2013-12-14 15:13] me: good 5
* b340e21 [2013-12-14 15:13] me: good 4
* 00c06f3 [2013-12-14 15:12] me: good 3
* e9f80a4 [2013-12-14 15:12] me: good 2
* 3122ba7 [2013-12-14 15:12] me: good 1
* 98da603 [2013-12-14 15:12] me: first
$ # Done :)

git rebase 可能更好,至少我正在展示 git 中通常有多种方法。毕竟,rebase 功能非常强大,“在 git 中的所有重要操作都可以使用 rebase 命令来表示”(Linus Torvalds)。


2

git rebase -i 32603274..

这将显示一个巨大的提交列表。

选择 162f833c ..

选择 ...

将第一个提交更改为 d 162f833c ..

并保存。

这将单独删除第76个提交。


0

有些仓库会被管理员保护,防止更改历史记录,就像我的情况一样。因此我不得不使用git revert。

git revert 162f833c

这样做也更安全,因为如果您想要恢复它,您仍然拥有错误提交。如果您想要恢复它,只需执行以下操作:

git cherry-pick 162f833c


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接