是否可以使用交互式 git rebase 从历史记录中删除文件?

Is it possible to remove a file from history using interactive git rebase?

我的本地存储库中有一个旧提交添加了一些文件,包括一个名为 "unwanted.txt" 的文件。在随后的提交中,该文件与其他文件一起被修改。是否可以使用交互式 git rebase 从历史记录中完全删除文件 "unwanted.txt"? 我知道使用 "git filter-branch" 可以实现这一点,但是由于我正在学习 git 并且我想了解 "git rebase -i" 的全部潜力,我想知道这个命令是否可以用于这样的操作。

您应该可以通过编辑有问题的提交(即 rebase 待办事项列表中提交前面的 eedit)来完成此操作,然后像这样删除文件:

git rm unwanted.txt
git commit --amend
git rebase --continue

这可能会给您在以后更改文件的提交中带来冲突,但这应该通过再次删除文件并继续变基来轻松解决。

编辑: 您很可能还必须确保没有分支指向任何仍然存在不需要的文件的提交,并且 运行 git gc 以清除存储库中的未引用 blob。如果它是一个不与其他任何人共享的纯私人回购,这应该不是问题。

理论上是可行的,但在实践中通常太痛苦了。

rebase和filter-branch方法是一样的。如果您意识到交互式变基的所有内容,可以说是 git cherry-pick 类固醇,这可能会有所帮助; git filter-branch 只是一个跨多个分支的自动 extra-complicated 变基并保留合并。

与 git 一样,它主要归结为操纵提交图,并添加看起来像现有提交但有一些变化的新提交——在这种情况下,这些提交附加了树。 (一旦一次提交不同,它就会获得不同的 SHA-1,这意味着所有 后续 提交也必须更改,以列出突然出现的不同 SHA-1 作为新图表增长。)

要了解它是如何工作的,请从绘制提交图开始。您将需要一个相当完整的图表,具体取决于您需要返回多远才能停止查看 unwanted.txt 文件。但我只画一个简单的图,只有一个命名分支,master:

I - A - B - C - F   <-- master
      \       /
        D - E

这里I是初始提交;为简单起见,假设它 没有 有不需要的文件。假设这个文件是在提交 A 中引入并在 CE.

中修改的

我们需要做的是:

  1. 复制所有提交 I(保留提交作者和提交者,以及日期戳等),同时删除不需要的文件,即,如果需要,更改附加到 I 的源代码树.这只是让我们提交 I 回来,所以我们保留了它原来的 SHA-1。
  2. 复制所有提交 A,同时删除不需要的文件。这导致了一个新的、不同的提交 A',因为我们将 A 的树更改为删除了文件的新树。我们得到一个新的 SHA-1 加密校验和,因为新的提交与旧的不同。所以我们在地图中保存一个条目,上面写着“旧提交 A 被新提交 A' 替换。
  3. 复制所有提交 B,同时删除不需要的文件。这会更改树(请记住,每个提交都有整个源的完整快照,因此不需要的文件位于原始 B 中)。进行一个新的提交 B',其中包含已更改的树 并且 将提交 A' 作为其 parent ID。
  4. 复制所有提交 C,同时删除不需要的文件,导致 C'
  5. 复制所有提交 D 以及我们的更改,结果是 D'。 (请注意,我们无法复制 F,直到我们复制了图中的所有前辈,在本例中为 CE。)
  6. 使用我们的更改复制所有提交 E
  7. 复制所有提交 F 以及我们的更改。新提交 F'C'E' 作为它的两个 parent;我们使用一直构建的 SHA-1 映射找到这些。
  8. 最后,更改master指向提交F',放弃原来的提交F

这会生成如下图:

    A - B - C - F    [abandoned]
   /  \       /
  /     D - E
 /
I - A' - B' - C' - F'   <-- master
       \         /
         D' - E'

使用 --preserve-merges 的交互式变基可以处理这种特殊情况。但是,如果有多个分支,则必须根据需要使用 --onto 小心地重新设置其他分支的基数,以利用新提交,您必须将新提交与旧提交相匹配,最有可能使用 SHA- 1 个您在构建过程中手动构建的地图文件。

还有一个问题,即 git commit 默认情况下拒绝进行 "empty" 提交,其中 "empty" 被定义为 "has the same tree as the previous commit"(并且不是合并). filter-branch 脚本会自动为您处理此问题,如果您选择删除空提交( 修改不需要的文件的提交变为空,则将多个新提交映射到单个旧提交当以前的和新的提交都放弃不需要的文件时)。交互式变基在保留合并时不能很好地处理这个问题,因此会带来更多痛苦。

还有一些其他的细微差别:例如,当 rebase "abandons" 一个提交链时,它们保留在 "reflog" 的分支以及 reflog 中HEAD。 filter-branch 脚本使用不同的方法:它将所有引用复制到 sub-name-space、refs/original/。当你到了想要清除旧的、被遗弃的提交的地步时,这一切都很重要:使用 rebase,你 "expire" 旧引用,但是使用 filter-branch,你强行删除了原件。

在我的例子中,我必须 'add' 文件才能在其上使用 git rm -f。那是因为当通过提交删除执行变基时,目录中不再存在文件。

git add path/to/file
git rm -f path/to/file