将上游分支合并到具有重写历史记录的fork中

时间:2018-08-31 14:20:08

标签: git

是否有任何好的方法来合并HEAD上具有相同文件/文件夹结构但历史记录不同的分叉存储库?不完全自动化的工作流程是不能接受的,因为它不会经常执行-但我希望有比手动复制所有文件和检查差异更好的方法。:)

背景是我们必须从拥有10年历史的TFS存储库迁移到Git。有一个要求保留整个历史记录,但仅限于主分支。从TFS迁移后-我们清理了一下Git存储库,但对于Git来说仍然太大了。

我们对其进行了迁移,并且此分支仍用于当前的生产部署。在该生产分支中还进行了一些修补程序-因此暂时不能放弃它,这些修补程序也很重要。

与此并行,我们正在一个单独的分支中进行主要的重构,该分支中仍使用许多当前的生产代码库,但另一方面,许多历史资料已被删除或移至不同的存储库中。

我想做的是创建一个fork并重写历史记录(例如,使用BFG Repo-Cleaner),以清理所有已删除的项目/对象。

此清理部分效果很好,但是我们还需要合并在当前 production 分支上所做的更改(仅一种方式-从 production cleaned- up 回购)。我尝试通过从旧存储库添加上游分支来实现此目的,但是将上游存储库合并到具有重写历史记录的存储库中-使所有清理工作无用。它会重新添加所有已删除的对象。

有什么办法解决吗?也许可以用完全不同的方式进行清理? 有很多类似的问题,但没有找到我真正需要的。:)

1 个答案:

答案 0 :(得分:2)

更新-阅读评论并查看我的答案后,有些事情可以澄清,一些调整可以使正确使用变得更加容易,还有一两个彻底的错误。对于那个很抱歉;作为文档,最初的答案是“草稿”质量。我将先解决几个问题,但我建议您也浏览一下下面的编辑答案。

上游配置-每个仓库中分支之间的关系是发生此情况的关键。提取refspec将对此进行控制,只要正确设置了它们,就不需要其他“上游”配置。

也就是说,我下面要做的最大更改是将桥接存储库中的清理后的分支移至其自己的clean/*名称空间,这样,对清理库进行正确的引用提取很多< / em>更简单。

BFG删除原始分支-这是正确的,但是在您配置网桥存储库的origin提取引用规范后,随后的fetch will recreate the original branches under the prod / *`名称空间也是如此。 >

关于您的最后评论-我认为您先前的尝试只是原始答案的“粗糙草案”问题引起的错误的受害者。绝对有可能获得正确的结果,我想作为一个对这里的工具和技术完全满意的人,我会自动寻找可以使其正常工作的“即时”更正。但是希望这种重写至少可以使您更接近要执行的操作...


您提到您可能必须将变更 从生产存储库合并到已清理的存储库。这不是一个太严重的问题,但是请注意,如果您需要双向进行更改(即,如果您想通过清理后的存储库中的更改来更新生产分支),将会使情况变得复杂事情,可能会赞成采用其他方法。

此外,如果所有更改都从生产仓库中的单个分支流入干净仓库中,这是最简单的。 (是否在生产仓库中使用分支并不重要,但理想情况下,您希望它们全部合并到一个分支中,这将成为干净仓库中单个分支的更改源。)如果没有,可以应用相同的原理,但是执行起来比较困难。

请注意,任何方法都仅具有将生产中的补丁应用于清理后的代码库的能力。在一定程度上,清理仅包括删除某些文件,这没问题。但是,如果存储库大相径庭,那么无论您尝试什么,应用更改时的冲突都将成为一个日益严重的问题。


对于单向流程(产品仓库->清洁仓库),您可以保留一个具有“原始”和“清洁”历史记录的仓库。这可以是生产仓库本身,也可以是专用的“桥梁存储库”。 (它不能是已清理的存储库,因为它将包含您要从中删除的大历史记录。)

究竟如何从您所在的位置进入该状态,取决于您所在的位置的详细信息。出于说明目的,如果您首先考虑这种方法,则可能会这样:

您的产品仓库位于<prod-url>。您将其克隆,此克隆将用于创建网桥存储库。

$ git clone `<prod-url>` bridge
$ cd bridge

您在bridge中运行BFG,然后对其进行克隆以创建真正的“干净”存储库。然后(再次在bridge中进行配置),重新配置origin,以便其分支可以映射到prod存储库中的bridge名称空间。

$ git config remote.origin.fetch refs/heads/*:refs/heads/prod/*

现在,当您从原始位置获取到桥接存储库时,git会尝试在prod/名称空间中推进一组分支,而不是更新远程跟踪引用。但是,您希望将这些prod/*分支取入您的干净存储库中;最简单的解决方法是将清理后的分支移至clean/命名空间,然后将清理后的存储库重新配置为仅获取clean/*分支。

bridge中,有几种移动分支的方法。如果数量不多,您可以手动进行

$ git checkout master
$ git checkout -b clean/master
$ git branch -D master

对于很多分支,您可以编写脚本(也许使用git for-each-ref来开始)。或者,您可能会以某种方式滥用filter-branch备份引用机制。

无论如何,一旦分支移动,就去干净的仓库并

$ git config remote.origin.fetch +refs/heads/clean/*:refs/remotes/origin/*

现在,与上一条命令不同,现在退后一步,当我在网桥存储库中为origin 提供了一个提取参考规范时,我省略了通常使用的前导+用于获取refspecs;这意味着,如果prod分支经历了历史记录重写,则提取将抱怨,并且您将知道自己可能要解决。稍后再讨论。

因此,接下来,您可以在网桥仓库中运行

$ git fetch origin

这将在prod/名称空间下重新加载原始分支。

现在您既有原始分支(例如refs/heads/prod/master)又有干净的分支(例如refs/heads/clean/master)。可以这样画

A' -- B' -- C' -- D' <--(clean/master)

A -- B -- C -- D <--(prod/master)

历史无关,您需要保持这种状态。但是,您也想通过对clean/master的{​​{1}}提交来“知道” D分支是“最新的”,从而使合并将来的更改变得容易。一种方法是另外创建两个分支-称它们为prod/masterbridge-prod

bridge-clean分支将始终指向我们对bridge-clean进行更改的最后一次提交。新的更改可能会在prod分支本身中进行,但是clean/会记住单独的bridge-clean的清理版本会是什么样。

prod

然后$ git checkout clean/master $ git branch bridge-clean 的工作应与bridge-prod具有相同的内容,直到它收到来自bridge-clean的新更改-之后将被用作更新{{1 }}。

因此,为了初始化它,我们创建了父级为prod/master的{​​{1}}的副本。

bridge-clean

现在有

D'

其中Dgit checkout prod/master git checkout -b bridge-prod git rm -r ':/' git checkout bridge-clean -- ':/' git commit 具有相同的内容(这是A' -- B' -- C' -- D' <--(bridge-clean)(clean/master) D" <--(bridge-prod) / A -- B -- C -- D <--(prod/master) 的“清理”版本)。由于D'D"作为其父项,因此您可以将将来的更改从D合并到D"D将成为合并基础)。所以过了一段时间之后

prod/master

这两个bridge-prod可以包括许多提交,分支,合并等。并没有太大的区别。重要的是D ... x <--(clean/master) / A' -- B' -- C' -- D' <--(bridge-clean) D" <--(bridge-prod) / A -- B -- C -- D ... H <--(prod/master) 仍代表存储库之间的最后一个集成。

因此,接下来您要将...合并到bridge-prod

bridge-clean

您希望prod/master代表bridge-prod的清理状态。为此,有两个条件需要担心:

如果 ... x <--(clean/master) / A' -- B' -- C' -- D' <--(bridge-clean) D" -- H"<--(bridge-prod) / / A -- B -- C -- D ... H <--(prod/master) 分支更新了清理清除的文件,则合并将发生冲突。幸运的是,这些删除是合并的“我们”方面的唯一更改,我们知道我们希望将它们保留在H"对这些文件所做的所有事情上。所以当我们合并时,我们可以说

H

不应将prod/master选项与prod/master混淆。尽管git checkout bridge-prod git merge -X ours prod/master 使用“我们的合并策略”,而完全忽略了-X ours的更改,但-s ours使用默认合并策略和“我们的策略选项”(感谢git, -as-mud命名)。

这是什么意思,该命令将尝试正常合并,但是每次出现冲突时,该大块代码的-s ours版本将占上风。由于prod/master的唯一更改是删除了我们不需要的文件,因此很好。

另一个问题是如果-X ours可能添加了一个新文件,该文件应从清理中排除。如果您知道那不可能发生,那就没问题了。如果可能发生,那么您需要检查一下。例如,在合并之前,您可以说

bridge-prod

,查看干净回购中是否有不需要的新文件。如果是这样,那么为您的合并做

bridge-prod

现在,由于prod/mastergit diff prod/master prod/master^ 的内容相同,这意味着git checkout bridge-prod git merge -X ours --no-commit prod/master # remove the unwanted files git add ':/' git commit 在下一次D"提交中拥有您想要的D'。 / p>

H"

这给你

TREE

bridge-cleangit checkout bridge-clean git rm -r ':/' git checkout bridge-prod -- ':/' git commit 具有相同的内容-这是经过清理的内容,通过 ... x <--(clean/master) / A' -- B' -- C' -- D' -- H' <--(bridge-clean) D" -- H"<--(bridge-prod) / / A -- B -- C -- D ... H <--(prod/master) 更新。另外,H'已清除历史记录(其父目录为H",我们一开始便对其进行了清理),因此可以安全地将其包含在干净的存储库中。您可以将H合并到H',更改转移就完成了。

从概念上讲,这涉及到一点,并且需要一些预先设置(并且可能编写一些脚本以用于每次更改集成)。但是,一旦完成所有设置,它便可以最大程度地减少人工摆弄,并让您充分利用git提供的合并机制。

但是,它是单向桥梁。如果要将D'重新合并到bridge-clean中,几乎可以肯定会删除要保留在master中的文件。

如果必须从原始存储库中进行更改并将其应用于产品存储库,则可以在原始存储库上生成补丁。为了使干净的repo内容是prod repo内容的子集,应使用补丁程序而不会产生太多麻烦。下次您合并从生产到清洁的更改时,这可能会导致一些虚假冲突。

最后一个要点(如上所述,但随后被遗忘)-所有这些都假设您不会(或至少不经常)在bridge-prod回购中进行历史记录重写。如果您要进行这样的重写,则就像另一个用户的克隆无法干净地提取更改一样,该桥也无法正常工作以将更改集成到干净的仓库中。您必须根据具体情况制定一个程序。