我们有两个并行发展的存储库:一个用于我们的项目代码,另一个用于该项目的测试。我想将这两个存储库合并到一个存储库中,这样一来,当我回顾历史时,我仍然拥有两者目录结构。
假设我们当前的结构如下,其中project
和tests
是两个单独的git存储库:
project
/src
/include
tests
/short
/long
我想最终得到一个git存储库,该存储库具有两个目录project
和tests
。
我不能简单地使用this answer,this one或this site中描述的技术来合并这两个存储库:它们导致合并前具有两个不同历史的存储库,并且在检查过去的提交时,您拥有src
和include
或short
和long
,但是您并没有出现的所有四个时间。
如果我签出4个月前在project
中创建的提交,我希望看到project/src
和project/include
出现在此提交中,但我也想在(当时是分开的)tests/short
存储库中同时具有test/long
和test
。
我知道两个存储库之间的提交顺序仅取决于时间,可能并不十分精确。但这对我来说已经足够了。当然,我知道我无法保留每个存储库中的原始git ID。很好,因为这两个存储库实际上是从另一个RCS导入的,因此在任何地方都没有记录过git id。
应该可行的是,按照存储库中的时间顺序,逐个签出每个存储库中的所有提交,并提交结果文件。已经有可以执行此操作的工具吗?
答案 0 :(得分:3)
编辑:对于一种基于日期的方法,该方法很容易实现,但是假设两个存储库之一将“控制”来自另一个存储库的提交,请参见jthill's answer。您最终得到的提交历史记录与“项目”历史记录完全匹配,可能会挤压一些“测试”历史记录。如果您需要在两个历史记录集之间添加一个前缀,或者想要对它们进行交织(例如,对于同一“项目”提交需要两个不同的“测试”更新),则下面的答案更合适。
phd's answer很好,但是如果我自己进行此操作并希望使其真正整洁,则可以使用其他方法。
如果两个存储库的树不重叠,那么肯定可以做到这一点-通过绕过常规的Git机制,直接转到基础git read-tree
命令,您可以使其自动化。 (这是VonC's recent comment拒绝我的说法,即Git和Mercurial非常相似的地方是对的:如果绕过顶层的Git命令,您会在Mercurial中获得几乎不那么容易得到的东西。)
就像phd's answer中一样,您将通过git fetch
合并两个存储库提交数据库来开始此过程。 (您可以在第三个存储库中执行此操作,我建议您这样做,因为如果您决定要调整一些参数,或者通过将存储库A添加到存储库B或将存储库B添加到存储库B,可以简化从头开始重新启动该过程的操作。回购A。)但此后,一切都发生了分歧。
您现在有两个不连续的提交DAG:
D--...--K
/ \
A--B--C M--N <-- repoA/master
\ /
E--...--L
O--P--Q--...--Z <-- repoB/master
(如果repoA和repoB都具有一个以上的分支提示,则更合适地绘制其提交的简化图。)
您的下一步是使用git rev-list --topo-order --reverse
和您喜欢的其他排序选项来枚举两个不相交的DAG中的所有提交。何时以及是否需要--topo-order
取决于拓扑和其他排序信息,但是通常,您会希望在其任何子项之前列出一个父项。
鉴于提交哈希ID的这两个线性化列表,您现在遇到了困难的部分:构造希望提交的新的组合树图。每个 new 提交都将通过合并两个旧图中的每个提交进行一次提交。如果其中一张图是复杂的(如上述repoA)具有分支和合并,而没有一张(如上述repoB),则可能会特别棘手。
我为此做了自己的设置,其中有一个非常简单的图形:
A--B <-- A/master
O--P <-- B/master
在简化的设置中,我想在新主控上的第一个提交是将C
和A
的树结合在一起的提交O
:
C <-- master
然后,我想在master
和A
(不是P
和A
和{{1}而不是O
和B
),并且作为我的最后一次提交,是O
和B
的组合,所以我最终得到:
P
因此,这里我们处于一个新的空存储库中,除了我们已经阅读了项目A和B:
C--D--E <-- master
with:
C = A+O
D = A+P
E = B+P
(我不小心不给O加上断字,而是对所有其他字符都加了断字。在这种情况下,$ 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
会删除一些对阅读没有帮助的空行。)
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
,所以现在让我们将这两个提交读入索引。如果我们必须在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
现在清空索引,然后读取提交A和P:
Cannot bind.
如果愿意,可以再次使用$ git read-tree --empty
$ git read-tree --prefix= ff40069
$ git read-tree --prefix= 7b9921a
检查结果:
git ls-file --stage
无论如何,它们现在都可以作为新的提交来提交:
$ git ls-files --stage
100644 7a1c6130c652b6ea92f4d19183693727e32c9ac4 0 A/file
100644 d7941926464291df213061d48784da98f8602d6c 0 B/another
100644 f6284744575ecfc520293b33122d4a99548045e4 0 B/start
(您现在可以看到我最终以不一致的连字符:-))。最后,我们通过清空索引,读取两个所需的提交(B + P)并提交结果来重复该过程:
$ git commit -m 'combine A and P'
[master eb8fa3c] combine A and P
1 file changed, 1 insertion(+)
create mode 100644 B/another
(我在这里使用符号名来获取最后两个提交,但是来自$ 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
引用(以及两个遥控器)。这里有一个特殊之处:由于我们直接在索引中完成了所有工作,而无需理会工作树,因此工作树仍然是完全空的:
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
而不是git commit-tree
来进行新的提交。您将编写一个小脚本(以您喜欢的任何语言)运行git commit
来收集要合并的提交的哈希ID。脚本必须检查这些提交(例如,通过查看作者身份和日期,文件内容或其他内容),以决定如何交错提交。然后,在决定了交织以及要提供的分支和合并结构之后,脚本可以开始重复执行以下步骤的过程:
git rev-list
选项,从repo-A的子图中的提交中提取树状树-在您的情况下,这是--prefix
,即空字符串,但是在其他情况下,它将是带有斜杠的目录名称。--prefix=
中,从而在树中形成扬声,因此--prefix
和A
的条目之间没有冲突。 B
来编写树。其输出是下一步的树哈希ID。git write-tree
与适当的git commit-tree
参数一起使用以设置新提交的父对象。向其提供适当的(组合的或类似的)提交消息文本。使用环境变量-p
,GIT_AUTHOR_NAME
,GIT_AUTHOR_EMAIL
,GIT_AUTHOR_DATE
,GIT_COMMITTER_NAME
和GIT_COMMITTER_EMAIL
来控制作者和提交者的名称和日期。 GIT_COMMITTER_DATE
的输出是哈希ID,它是某些后续提交的父级。整个过程完成后,为任何特定分支或一组分支所做的 last 提交就是进入这些分支的哈希ID,因此您现在可以运行:
git commit-tree
每个此类哈希ID。
答案 1 :(得分:3)
[鉴于所有
project
的内容都在src
和include
中,并且所有tests
的内容都在short
和long
中,] >如果我签出4个月前在项目中创建的提交,我希望看到
project/src
和project/include
出现在此提交中,但我也想拥有{{1 }}和tests/short
,与它们在当时(然后是单独的)测试存储库中的时间相同。 […]已经有可以执行此操作的工具吗?
有一个,名为tests/long
。到目前为止,最简单的实现是遍历git filter-branch
的历史并查找对应于project
提交内容的“ the”,这是一个草图:
tests
如果您的“测试”历史记录包含成千上万次提交,它将变得很慢;如果您正在谈论linux repo或类似规模的东西,则它会便宜得多,可以预先生成按日期排序的测试列表并逐步执行
答案 2 :(得分:2)
我认为您应该合并两个存储库以创建2个分支(git fetch
,不合并)。然后以交互方式使一个分支变基,在每次提交时停止,然后git cherry-pick
将相应的提交执行到当前分支中。然后继续交互式基础更改到下一个提交(这将保存“已编辑”的提交而不进行任何修改)。
也许甚至可以实现自动化。您可以使用git rebase --interactive -x
在每次提交之后执行git cherry-pick
来代替交互式变基和手动挑选。问题是如何找出对樱桃采摘的承诺。我认为应该是second-branch~count
。可以在编辑rebase-todo文件时在交互式rebase之前编辑该计数。