我已经找到了如何从git的历史记录中删除文件,但是当我尝试删除已被另一个文件(根目录中的^
替换为根文件)的文件(根String[] output = input.split("\\^{3,}");
)时, ,method I used也删除了替换项。
这是我使用的确切命令:
Makefile
是否有任何方法可以在不移除替换件的情况下进行?如果可以,怎么办?
注意:我正在询问保留替换文件历史记录的方法。我确实可以选择删除两者并从备份中还原当前文件,这会丢失历史记录。如果没有其他选择,我会这样做,因为我希望原始文件消失。
答案 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/)