从Git历史记录中删除文件是否还会删除其替换文件?

时间:2018-08-28 19:02:24

标签: git git-filter-branch

我已经找到了如何从git的历史记录中删除文件,但是当我尝试删除已被另一个文件(根目录中的^替换为根文件)的文件(根String[] output = input.split("\\^{3,}"); )时, ,method I used也删除了替换项。

这是我使用的确切命令:

Makefile

是否有任何方法可以在不移除替换件的情况下进行?如果可以,怎么办?

注意:我正在询问保留替换文件历史记录的方法。我确实可以选择删除两者并从备份中还原当前文件,这会丢失历史记录。如果没有其他选择,我会这样做,因为我希望原始文件消失。

2 个答案:

答案 0 :(得分:1)

  

注意:我正在询问保留替换文件历史记录的方法。

您需要安排仅删除原始文件。如何做到这一点有些棘手。

了解问题的关键很简单: Git没有文件历史记录。没有这样的东西! (另请参见Missing deletion of lines in file history (git)

Git的历史记录是 commit history 。更准确地说,提交是 的历史记录:每个提交都有指向其父提交的反向链接。 (对于合并提交,向后链接至少指向两个父级。)每个提交都包含所有文件的完整快照,因此,通过让Git遍历 commit 历史记录,并询问有关哪些文件是< em>每次提交中,您都可以让Git合成一个伪装的文件历史记录。但这实际上不是那里,而是根据那里的 来计算的。

Git存储库中的所有提交都是完全只读的。这包括它们的向后链接:该链接包含父提交的“真实名称”(哈希ID),而提交的真实名称取决于其内容。这使得不可能更改历史记录。 Git的filter-branch甚至没有尝试这样做。相反,它的作用很简单:在应用您指定的任何过滤器的同时,复制每个提交(嗯,您告诉它要复制的每个提交)。对于每个现有的提交,Git:

  • 将提交内容提取到临时工作区;
  • 应用您的过滤器;和
  • 将结果作为新的提交提交。

如果新提交是100%,与原始提交逐位相同,则您将使用原始哈希ID返回原始提交。但是,一旦发生任何更改,您将获得具有不同哈希ID的不同提交。 filter-branch内部的主要聪明之处在于,它定义了从原始哈希ID(原始提交)到新哈希ID(复制的提交)的映射,并且在复制时始终替换 parent 哈希ID及其映射版本。

这意味着您可以拍摄一个漂亮的简单图形,例如:

A  <-B  <-C   <--master

(每个大写字母代表实际的提交哈希ID,箭头是每次提交或名称master中存储的哈希ID)并进行过滤。如果您更改有关提交A的任何内容,则会得到一个新的不同的提交A',并且B的副本将指向A'而不是{{1} }。 A的副本将指向C'。即使您在复制B'B时也进行了更改,这也是正确的。结果是:

C

filter-branch所做的最后一件事是将名称从原始提交链中剔除,并使其指向新的链:

A  <-B  <-C   <--master
A' <-B' <-C'

运行A <-B <-C [refs/original/refs/heads/master] A' <-B' <-C' <-- master 或任何东西都会显示提交-历史-现在从git log开始并向后工作。显示或综合的历史记录来自复制的新提交。

在最初的提交系列中,有些提交包含名为C'的文件,而您希望包含这些文件。然后,您还有一系列其他提交,其中包含要做要包含的名为Makefile的文件。在过滤器中,您的工作是区分这两组提交。代替:

Makefile

您想要的,例如:

git filter-branch --index-filter 'git rm --cached --ignore-unmatch Makefile'

(以及您喜欢的任何其他选项 1 )。困难的部分是确定此git filter-branch --index-filter magic-script 中的内容,因为您想要的是:“如果要复制的提交具有错误的Makefile,请删除它,否则请删除它”。但是您将如何测试呢?

有多个答案,包括使用magic-script而不是--tree-filter:一个树过滤器(速度很慢(很多))从字面上提取每个提交,因此您可以检查其中的文件,并从提取的文件中构建新的提交。

索引过滤器将提取的提交保留在索引中(这是一个特殊的过滤器分支临时索引,但是您通常不需要关心它)。这就是为什么您使用--index-filter的原因:从索引中删除名为git rm --cached --ignore-unmatch Makefile的文件,然后filter-branch从索引中构建新的提交。在特殊的仅Git文件中进行索引操作比常规文件系统操作快得多。但是他们不允许您检查名为Makefile的文件,以便对其做出决定。

不过,还有另一种处理方式。假设,在上面理想的三提交存储库Makefile中,您将Makefile固定在提交A-B-C上,然后添加了另外几个提交C或其他内容。在这种情况下,您想要做的是使用以下形式的测试:

  • 如果当前正在过滤的提交是D-E-F-G或其任何祖先,例如B,请删除名为A的文件。
  • 否则(当前提交为Makefile或更高版本),保留名为C的文件。

事实证明,这可能是 plumbing命令 Makefile执行这种祖先测试,可在shell脚本git merge-base --is-ancestor测试中使用:

if

(“是祖先”测试包括相等性,因此这里的if git merge-base --is-ancestor $GIT_COMMIT <hash>; then git rm --cached --ignore-unmatch Makefile; fi 将是提交<hash>的原义哈希ID)。将整个内容放在单引号中,并使用适当的哈希ID,您可能会想要使用过滤器。

(可能会出错的地方是,在很多情况下应该删除B。如果您有足够的时间和/或基于RAM的文件系统有足够的空间,则可以使用Makefile并检查实际的--tree-filter,或者,您可能会非常喜欢并使用管道命令来检查其哈希ID存储在索引中的Git对象,并使用Makefile,但这是有点棘手。)


1 您可能仍需要--index-filter,还有-f--tag-name-filter cat之类的东西。请注意,存在-- --all来告诉filter-branch,如果先前的filter-branch留在了-f名称空间的后面,则可以销毁它。最好在存储库的副本上运行这些操作(一个克隆:也许是用refs/original/进行的复制),以防万一,在这种情况下,git clone --mirror东西是不必要的警告:您已经使用了所有必要的警告!

答案 1 :(得分:1)

为此,首先需要确定包含旧文件(要删除的文件)的最后一次提交,以及包含要保留的新文件的最早的提交。然后,您可以将过滤器仅应用于较早的提交。

例如,如果您有

o -- o -- o -- o -- A -- x -- x -- x <--(master)

其中旧文件位于标记为o的提交中,但是提交A将较新的文件(您要保留)移到了根目录:然后您想离开A并且x保持不变。

要使用filter-branch来做到这一点,您需要一个过滤器“知道”它正在编辑的提交,或者只需要将过滤器应用于o提交。后者比较容易,但是在这种情况下,您会得到拆分历史记录

o -- o -- o -- o -- A -- x -- x -- x <--(master)

o' -- o' -- o' -- o'

,那么您必须通过“重定位” A到最后的o'提交来跟进。也可以使用filter-branch(使用--parent-filter来完成),但这仍然只处理一行历史记录-或至少在切换文件的位置执行一次“转换”提交。如果您有多个提交“引入”文件之间的更改(例如,由于更改通过合并传播到分支之间),那么此过程将变得越来越复杂。

一个更好的解决方案是考虑使用BFG回购清洁器。它专门用于删除不需要的历史记录,因此(1)更快,并且(2)通常更容易。可以将其配置为“保护”某些提交而仅编辑其他提交。请查看项目的页面和文档以获取更多详细信息(https://rtyley.github.io/bfg-repo-cleaner/