尽管涉及两个子部分,但我还是把它作为一个组合问题来问,因为它分解成多个部分的方式并不重要。只要最终结果保留了所有有意义的历史以及查看、研究和构建/测试历史版本的能力,我对实现我想要的目标的不同方法持开放态度。目标是退役hg和迄今为止使用的subrepo模型,并转移到git中的统一树,但不牺牲历史。
我首先要介绍的是一个Mercurial存储库,它由一些顶级代码和一些子存储库组成,其中包含了大量有趣的历史。子站点有一些分支/合并,但没有什么太疯狂的。我想要实现的最终结果是一个单一的git存储库,没有子模块,例如:
-
对于原始顶级hg repo中的每个提交,都有一个git提交,它检查出与您检查出相应的hg提交及其所有引用subrepo提交完全相同的树。
-
这些与连续的顶级hg提交相对应的git提交是彼此的后代,其间的提交与所有相关的subrepo提交相对应。
关于如何实现这一点,我的基本想法是迭代所有顶级hg提交,对于每个更改.hgsubstate
的顶级提交,也迭代子模块从旧版本到新版本的所有路径(可能涉及分支)。每一步:
- 查看顶级和所有子网站的相应hg修订
- 删除git索引中的所有内容
- 将从hg到git索引的所有检出内容暂存
- 使用
git-write-tree
和git-commit-tree
生成具有所需父级的提交,使用来自相应hg提交的作者身份、日期和提交消息 - 记录新的git提交和hg提交之间的对应关系,以便在生成未来提交的父级时使用
这样行吗?有没有更好的方法来实现我想要的,也许先用hg做subrepo崩溃?我不清楚的最大一件事是如何执行所需的迭代,所以关于如何实现它的实用建议会很棒。
一个额外的限制:原始回购涉及无法发布的内容(一旦完成基本转换,这是额外的git-filter-branch
步骤),因此涉及上传回购供第三方处理的解决方案是不可行的。
您所写的内容可能会解决问题,也可能不会解决问题。但这并不简单。主要问题是,您需要按顺序提交,以便您的子回购和主回购保持一致。我在小范围内重新创建了这个问题,并且能够在子站点之间保持一致)。
我的解决方案:
-
使用hg-convert-extension,我将主repo转换为没有子repo(和相关信息)的repo。
cd main awk '{ print $1}' .hgsub | xargs -n 1 echo 'exclude' > ../filemap echo exclude .hgsub >> ../filemap echo exclude .hgsubstate >> ../filemap cd .. hg convert --filemap filemap main mainConv cd mainConv hg update
-
使用--filemap中的rename转换子报表。
cd .. echo rename . subRepo > subFileMap hg convert --filemap main/subRepo subRepoConv cd subRepoConv hg update
-
将子repo拉到已转换的主repo。
cd ../mainConv hg pull -f ../subRepoConv
-
在提取时,您会注意到回购中有多个头(因为subrepo有自己的头)。合并它们:
hg heads hg merge <RevID from subrepo (not main repo)> hg ci -mMergeOfSubRepo
你必须重复3&每个子博客4个。
-
但是提交不会被排序。所以把它们按这里的顺序排列https://stackoverflow.com/a/16012597:
cd .. hg clone -r 0 mainConv mainOrdered cd mainOrdered for REV in `hg log -R ../main -r 'sort(1:tip, date)' --template '{rev}n'` do hg pull ../main -r $REV done
现在使用将这个有序的mercurial repo转换为githttp://repo.or.cz/w/fast-export.git:
cd ..
git clone git://repo.or.cz/fast-export.git
git init mainGit
cd mainGit
../fast-export/hg-fast-export.sh -r ../mainOrdered
git checkout HEAD
是。您最好的选择是使用git commit-tree
手动创建提交。有很多转换工具,但它们永远不会给你想要的东西。另一方面,手写脚本将为您提供所需的所有灵活性。
我写过很多这样的脚本,包括git remote-hg
本身。
不相关主题
我确信,您选择了最糟糕的迁移想法(从Mercurial到Git),但最终是您的选择和责任
迁移过程
我对Git的了解相当薄弱,因此对于Mercurial+subrepo->单片Git,我只能这样看和描述:
Mercurial+子博客->单片Mercurial->单片Git回购
- 为了将子零售历史与包装零售历史合并,您可以(根据alexis的评论进行更正)使用我之前关于转换扩展的问题中的想法
- 具有额外抛光历史的单片Mercurial repo(一个根,没有匿名头,至少没有链接的书签)可以使用hggit轻松地推送到空的Git repo
在我的问题和对可能解决方案的讨论中,我似乎缺少了对所涉及的图论的正确理解。像"迭代从旧修订到新修订的所有路径"这样的想法并没有真正定义明确,或者至少没有反映出我期望它们反映的内容。从更严格的角度来看,我认为我有一种行之有效的方法。
首先,问题是:Subrepo修订只代表历史上某个给定点上它们自己子树的状态。我想将它们映射到表示整个组合树状态的修订。然后,子报表DAG可以以一种有意义的方式与顶级DAG合并。
对于给定的子报表修订版R,我们可以问什么顶级回购(或父回购,如果我们有多个级别的子报表)修订版包括R或R的任何后代。假设只有一个根,这组修订版有一个最低公共祖先(或可能不止一个),这似乎是一个很好的候选者。事实上,如果我们与R一起使用的顶级修订S不是使用R或其子版本的修订的共同祖先(但映射在其他方面是合理的),那么R将有一个子版本R',其相关的顶级修订S'不是S的子版本。换句话说,从子版本派生的历史在顶级树的修订之间会有混淆/无意义的跳跃。
现在,如果我们想选择一个共同的祖先,那么从使这些修订成为可以检查、构建和测试的东西的角度来看,从合理地了解顶级repo(和其他子repo)在子repo中进行更改时的状态来看,最低的祖先是有意义的。整个顶级DAG的根当然也会起作用,但它不会给出有意义的、可用的修订;从可用性的角度来看,选择根相当于一个幼稚的repo合并,每个子repo有一个根,只要顶级repo更新了它正在使用的修订,就从子repo历史中合并。
因此,如果我们可以使用LCA为每个子报表修订R分配一个顶级修订T(R),这如何转化为
每当子报表修订版R的每个父级p的T(R)与T(p)不同时,它就有效地将顶级回购(和其他子报表)的新更改合并到子报表历史中。转换应该将其表示为两个提交:
-
实际的子报表提交R,使用旧的顶级修订版。如果R有一个单亲P(而不是合并提交),那么它就是T(P)。如果R有多个父母,目前还不清楚是否有一个完美的选择来使用哪一个,但任何父母P的T(P)都应该是合理的。
-
合并提交合并回与R相关联的顶层回购提交T(R)的转换C(T(R。
除了引用(1)作为合并父级的C(T(R))之外,转换中对R的所有其他引用都应该使用(2)。这包括使用该子报表的修订版R的顶层repo中T(R)的任何子代的转换,以及R本身的直接子代的转化。
我相信上面的描述(尽管措辞拙劣)详细说明了合并顶级和子级DAG所需的所有内容。每个子报表修订都会获得树的完整版本,并最终通过"合并-提交"(当子报表合并新的关联顶层修订时,以及当顶层合并已更改的子报表修订时)连接到转换后的回购的统一DAG中。
那么,生成git-reo的最后一步就是简单地以拓扑排序的形式或通过深度优先遍历来重播合并的DAG,这样每个git commit-tree
就已经有了它需要的所有父修订。
这就是我为解决类似问题所做的:
- 通过快速导出转换每个mercurial存储库
- 将子存储库的目录添加为父存储库中的远程目录
- 在父repo中,
git checkout -b
为每个子repo存储库命名 - 每个子报表的
git read-tree --prefix=pathsubrepo/ -u subrepobranch
这或多或少是我做的更详细的事情(改编自bash历史…但实际上没有运行)
步骤1
cd ~
git clone git://repo.or.cz/fast-export.git
git init parent_repo
cd parent_repo
~/fast-export/hg-fast-export.sh -r /path/to/old/mercurial/parent
git checkout HEAD
cd ~
git init subrepo1
cd subrepo1
~/fast-export/hg-fast-export.sh -r /path/to/old/mercurial/parent/subrepo1
git checkout HEAD
cd ~
git init subrepo2
cd subrepo2
~/fast-export/hg-fast-export.sh -r /path/to/old/mercurial/parent/subrepo2
git checkout HEAD
步骤2
cd ~/parent_repo
git remote add sub1 $HOME/subrepo1/
git remote add sub2 $HOME/subrepo2/
步骤3
cd ~/parent_repo
git checkout -b sub1master sub1/master
git checkout -b sub2master sub2/master
步骤4
cd ~/parent_repo
git read-tree --prefix=subrepo1/ -u sub1master
git read-tree --prefix=subrepo1/ -u sub2master
一旦完成,您就可以使用git branch -D sub1master
和git branch -D sub2master
,因为您不再需要它们了。
试试Facebook的Hg<->Git转换器:FbShipIt
。您所描述的大部分内容应该都能很好地与这个提交转换器工具配合使用,该工具可以在Mercurial和Git之间复制提交。
FbShipIt
有一个警告:它不理解合并提交,但可以通过git rebase
解决。