从仅包含已删除文件的git存储库中删除所有提交

时间:2018-08-03 11:15:39

标签: git

多年来,我们庞大的回购协议之一已经有机地增长到包含两个项目。这些项目现在相去甚远,以至于我们决定将它们放在单独的存储库中。

拆分它们是没有问题的(复制存储库,删除存储库1中的项目A并删除存储库2中的项目B),也没有将项目移至存储库根目录而不是repo / projectX(git filter-branch --subdirectory-filter)。

但是,我们有8000多个提交,并且其中绝大多数只涉及项目之一,而不是两者。理想情况下,我们希望从repo2中清除对项目A的提交,反之亦然。

是否存在执行此类操作的脚本或工具?从逻辑上讲,这似乎很简单:

for each commit {
  if all files startwith '/projectA' delete commit
}

1 个答案:

答案 0 :(得分:0)

不可能更改有关任何提交的任何内容。

同时,每个提交都记录其前身( parent )提交的哈希ID(直接:原始哈希ID)。

将这两个事实放在一起时,您会发现不可能从链的中间删除提交。这是一个简单的玩具示例,整个存储库中只有三个提交:

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

在这里,名称master包含第三次提交C的ID。这是最后的提交:这是Git开始工作的地方。 Git从提交C中读取 second 提交B的哈希ID。 Git从提交B中读取 first 提交A的哈希ID。提交A具有 no 父项,因此操作停止,并且我们刚刚看到了历史记录。

您现在决定要删除落实B。提交C 不能更改,但是有 您可以执行以下操作:您可以提取提交C,使进行一些更改,然后重新提交结果进行新的提交,我们可以将其称为D,但我们可以将其称为C'

A--B--C
 \
  C'

我们想在进行C'之前进行的更改是消除两件事:

  • 提交B对源树的效果:无论B中发生了什么变化,我们都以某种方式退回。

  • 新提交C' parent 应该是A,而不是B

完成此过程后,我们将拥有一个新的历史记录,其中从未出现提交B。但是,名称master现在必须指向新的不同提交C'

  B--C   [abandoned]
 /
A--C'  <-- master

如果我们有一个更大的存储库,其中包含更多提交,并且想跳过其中两个提交,则过程将非常相似:

  B--C--D--E--F--G--H   [abandoned]
 /
A--C'-F'-G'-H'   <-- master

请注意,被放弃的提交在存储库中保留了一段时间,但最终通过提供的通过Git的“垃圾收集”过程(git gc)被丢弃了。 em>,例如,通过将提交H与提交H'合并,可以确保您永远不会重新连接到旧提交。

任何仍然具有master指向H的克隆都必须被视为具有放射性,以免您不慎将H合并到H'中并把所有这些提交带回。 / p>

Git过滤器分支

Git附带了一个工具,使您可以执行上述操作。但是,此工具就像瑞士军队的电锯一样,没有防护措施可防止割伤手,脚,头等。它并不是特别容易使用,尤其是对于您在此处设置的任务。

它的作用是简单地枚举存储库中的可到达的提交(请参阅Think Like (a) Git,并阅读Git对图论的使用)。然后运行一个循环:

  • 将每次提交都提取到一个临时区域
  • 允许您对提取的提交执行任意工作
  • 重新提交结果,或者(通过--commit-filter)允许您跳过提交结果

并一路保持“原始哈希ID⟶新哈希ID”的映射,以映射父哈希ID。您将想了解在跳过这样的提交时“重映射到祖先”是如何工作的。

重新复制存储库中的每个提交都很慢。在某些情况下,例如使用--tree-filter从字面上提取每个提交时,它的运行速度非常慢。结果,filter-branch有许多过滤器选项可以尝试加快处理速度。请记住,这些选项本质上只是优化技巧:作为打算使用filter-branch作为工具的人,您应该首先明确定义问题并提出正确的解决方案(即,如何修改源树快照和提交)。 ),然后查看是否可以使用优化路径(例如--index-filter)进行快照编辑。

如果您的存储库中有标签,请记住,除非提供--tag-name-filter,否则filter-branch不会重写标签以指向新的提交哈希。

最后,请记住,如果您选择走这条路-即使您使用自己编写的工具而不是使用git filter-branch-您正在做的是对某些子集进行复制原始提交。新的存储库不再与旧版本的 any 现有克隆兼容!