编辑:对于基于日期的方法,该方法使这变得非常简单,但假设两个存储库之一将“控制”来自另一个存储库的提交,请参阅杰蒂尔的回答 https://stackoverflow.com/a/55886165/1256452。您最终会得到与“项目”历史记录完全匹配的提交历史记录,可能会压缩一些“测试”历史记录。如果您需要添加前缀,下面的答案更合适both历史集,或者想要交错它们(例如,需要对同一“项目”提交进行两个不同的“测试”更新)。
博士的答案 https://stackoverflow.com/a/55880876/1256452很好,但如果我自己做这件事并且想让它变得非常整洁和干净,我会使用不同的方法。
如果两个存储库的树不重叠,那么当然可以做到这一点 - 并且通过绕过通常的 Git 机制,直接进入底层git read-tree
命令,您可以自动化它。 (这是哪里VonC最近的评论 https://stackoverflow.com/questions/55877484/is-committed-and-unmodified-the-same#comment98417233_55878249拒绝我关于 Git 和 Mercurial 非常相似的说法是正确的:如果您绕过顶级 Git 命令,您将获得在 Mercurial 中几乎无法轻松获得的东西。)
正如在博士的答案 https://stackoverflow.com/a/55880876/1256452,您可以通过组合两个存储库提交数据库来启动此过程git fetch
。 (您可以在第三个存储库中执行此操作,我建议这样做,因为如果您决定要调整某些参数,或者通过将存储库 A 添加到存储库 B,或将存储库 B 添加到存储库,可以更轻松地从头开始重新启动该过程。回购协议 A.) 但在那之后,一切都出现了分歧。
您现在有两个不相交的提交 DAG:
D--...--K
/ \
A--B--C M--N <-- repoA/master
\ /
E--...--L
O--P--Q--...--Z <-- repoB/master
(如果 repoA 和 repoB 都有多个分支提示,请绘制更合适的提交简化图。)
下一步是使用以下命令枚举两个不相交 DAG 中每一个中的所有提交git rev-list --topo-order --reverse
以及您喜欢的任何其他排序选项。何时以及是否--topo-order
是否必需取决于拓扑和其他排序信息,但通常您会希望父提交列在其任何子提交之前。
给定这两个提交哈希 ID 的线性化列表,您现在遇到了困难的部分:构建您希望提交的新组合树的图表。每一个new提交将通过组合两个旧图表中的每一个的一个提交来进行。如果其中一张图很复杂(如上面的 repoA),具有分支和合并,而另一张图则不是(如上面的 repoB),那么这可能会特别棘手。
我为此做了自己的设置,其中有一个非常简单的图表:
A--B <-- A/master
O--P <-- B/master
在我的简化设置中,我想对我的新主人进行第一次提交C
结合了树A
and O
:
C <-- master
然后我想做,作为我的第二次承诺master
, 的组合A
and P
(not A
and O
并不是B
and O
要么),作为我的最后一次提交,组合B
and P
,这样我最终得到:
C--D--E <-- master
with:
C = A+O
D = A+P
E = B+P
因此,这里我们位于一个新的空存储库中,除了我们在项目 A 和 B 中读取的内容之外:
$ git log --all --graph --decorate --format='%h%d %s' --name-status | sed '/^[| ] $/d'
* 7b9921a (B/master) commit-P
| A B/another
* 51955b1 commit O
A B/start
* 69597d3 (A/master) commit-B
| A A/new
* ff40069 commit-A
A A/file
(我不小心没有用连字符连接提交 O,但是却用连字符连接了所有其他提交。sed
在这种情况下,是删除一些对阅读没有真正帮助的空白行。)
$ git status
On branch master
No commits yet
nothing to commit (create/copy files and use "git add" to track)
现在我们使用一次一个来构建新的提交git read-tree
填充索引以进行提交。我们从一个空索引开始(我们现在已经有了):
$ git status
On branch master
No commits yet
nothing to commit (create/copy files and use "git add" to track)
我们希望我们的第一个承诺能够合并A
and O
,现在让我们将这两个提交读入索引。如果我们必须向树添加一个前缀A
我们可以在这里这样做:
$ git read-tree --prefix= ff40069
$ git ls-files --stage
100644 7a1c6130c652b6ea92f4d19183693727e32c9ac4 0 A/file
$ git read-tree --prefix= 51955b1
$ git ls-files --stage
100644 7a1c6130c652b6ea92f4d19183693727e32c9ac4 0 A/file
100644 f6284744575ecfc520293b33122d4a99548045e4 0 B/start
我们现在可以进行我们需要的提交:
$ git commit -m combine-A-and-O
[master (root-commit) 7c629d8] combine-A-and-O
2 files changed, 2 insertions(+)
create mode 100644 A/file
create mode 100644 B/start
现在我们需要进行下一次提交,这意味着我们需要在索引中构建正确的树。为此,我们首先必须将其清理干净;否则下一个git read-tree --prefix
将会失败并抱怨重叠文件Cannot bind.
现在我们清空索引,然后读取提交 A 和 P:
$ git read-tree --empty
$ git read-tree --prefix= ff40069
$ git read-tree --prefix= 7b9921a
如果您愿意,您可以使用以下命令检查结果git ls-file --stage
again:
$ git ls-files --stage
100644 7a1c6130c652b6ea92f4d19183693727e32c9ac4 0 A/file
100644 d7941926464291df213061d48784da98f8602d6c 0 B/another
100644 f6284744575ecfc520293b33122d4a99548045e4 0 B/start
无论如何,它们现在可以作为新的提交提交:
$ git commit -m 'combine A and P'
[master eb8fa3c] combine A and P
1 file changed, 1 insertion(+)
create mode 100644 B/another
(你现在可以看到我是如何得到不一致的连字符的:-))。最后,我们通过清空索引、读入两个所需的提交 (B+P) 并提交结果来重复该过程:
$ git read-tree --empty
$ git read-tree --prefix= A/master
$ git read-tree --prefix= B/master
$ git ls-files --stage
100644 7a1c6130c652b6ea92f4d19183693727e32c9ac4 0 A/file
100644 8e0c97794a6e80c2d371f9bd37174b836351f6b4 0 A/new
100644 d7941926464291df213061d48784da98f8602d6c 0 B/another
100644 f6284744575ecfc520293b33122d4a99548045e4 0 B/start
$ git commit -m 'combine B and P'
[master fad84f8] combine B and P
1 file changed, 1 insertion(+)
create mode 100644 A/new
(我在这里使用符号名称来获取最后两次提交,但哈希 ID 来自git rev-list
当然会很好用。)我们现在可以看到这三个提交,全部都在master
:
$ git log --decorate --oneline --graph
* fad84f8 (HEAD -> master) combine B and P
* eb8fa3c combine A and P
* 7c629d8 combine-A-and-O
现在可以安全删除A/master
and B/master
参考文献(和两个遥控器)。有一个特点:由于我们直接在索引中完成所有工作,而不用担心工作树,因此工作树仍然完全是空的:
$ ls
$ git status -s
D A/file
D A/new
D B/another
D B/start
为了最后解决这个问题,我们应该运行git checkout HEAD -- .
:
$ git checkout HEAD -- .
$ git status -s
$ git status
On branch master
nothing to commit, working tree clean
如何编写自己的自动化脚本
在实践中,您可能想要使用git write-tree
and git commit-tree
, 而不是git commit
,进行新的提交。您可以编写一个小脚本(用您喜欢的任何语言)来运行git rev-list
收集要组合的提交的哈希 ID。脚本必须检查这些提交(例如,通过查看作者身份和日期、或文件内容等)来决定如何交织提交。然后,在做出有关交织以及提供哪些分支合并结构的决定后,脚本可以开始重复执行以下步骤的过程:
- 清空索引。
- 从 repo-A 的子图中的提交中拉入树,无论是什么
--prefix
选项是合适的——根据您的情况,这是--prefix=
,即空字符串,但在其他情况下,它将是带有尾部斜杠的目录名称)。
- 从 repo-B 的子图中的提交中拉入树,并使用另一个适当的
--prefix
,这样来自的条目之间就不会发生冲突A
and B
.
- Use
git write-tree
写树。它的输出是下一步的树哈希 ID。
- Use
git commit-tree
与适当的-p
设置新提交的父级的参数。向其提供适当的(组合的或其他的)提交消息文本。使用环境变量GIT_AUTHOR_NAME
, GIT_AUTHOR_EMAIL
, GIT_AUTHOR_DATE
, GIT_COMMITTER_NAME
, GIT_COMMITTER_EMAIL
, and GIT_COMMITTER_DATE
控制作者和提交者的姓名和日期。输出来自git commit-tree
是哈希 ID,它是某些后续提交的父级。
当整个事情结束后,last为任何特定分支或分支集所做的提交都是进入这些分支的哈希 ID,因此您现在可以运行:
git branch <name> <hash>
对于每个这样的哈希 ID。