hg to git conversion and subrepo merge

时间:2016-05-10 17:43:59

标签: git version-control mercurial mercurial-subrepos

尽管涉及两个子部分,但我认为这是一个综合问题,因为它被分解成部分的方式并不重要。只要最终结果保留了检查,研究和构建/测试历史版本的所有有意义的历史和能力,我就可以通过不同的方式实现我想要的目标。目标是退出hg和迄今为止使用过的subrepo模型,然后转移到git中的统一树,但不会牺牲历史。

我开始使用的是一个Mercurial存储库,它包含一些顶级代码和许多有趣历史所在的子存储库。 subrepos有一些分支/合并,但没有什么太疯狂。我想要实现的最终结果是单个git存储库,没有子模块,例如:

  • 对于原始顶级hg repo中的每个提交,都有一个git提交,它会检出完全相同的树,并且检查出相应的hg提交及其所有引用subrepo提交。

  • 这些对应于连续顶级hg提交的git提交是彼此的后代,其提交对应于其间的所有相关子提交。

我有关如何实现这一点的基本思想是迭代所有顶级hg提交,并且对于每个更改.hgsubstate的顶级提交,也迭代从旧版本到旧版本的所有路径子模块的新版本(可能涉及分支)。在每一步:

  • 查看顶级和所有子目录的相应hg修订版。
  • 从git索引中删除所有内容。
  • 将从hg检出的所有内容转移到git索引。
  • 使用git-write-treegit-commit-tree使用相应的hg提交中的author,3nd,date和commit消息,使用所需的父级生成提交。
  • 记录新git提交和hg提交之间的对应关系,以用于生成将来的提交'父母。

这应该有用吗?有没有更好的方法来实现我想要的,也许首先用hg做subrepo崩溃?我不清楚的最重要的事情是如何执行所需的迭代,因此如何实现它的实用建议会很棒。

一个额外的约束:原始回购涉及无法发布的内容(这是基本转换完成后的另一个git-filter-branch步骤)所以涉及上传回购以供第三方处理的解决方案不可行。

6 个答案:

答案 0 :(得分:6)

您所写的内容可能会或可能不会解决问题。但这并不简单。主要问题是您需要按顺序提交,以便您的子目录和主仓库保持一致。我以小规模重新创建了这个问题,并且能够在subrepos之间保持一致。)

我的解决方案:

  1. 使用hg convert扩展,我将主仓库转换为没有子目录的仓库(以及相关信息)。

    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
    
  2. 使用--filemap中的重命名转换subrepo。

    cd ..
    echo rename . subRepo > subFileMap
    hg convert --filemap main/subRepo subRepoConv
    cd subRepoConv
    hg update
    
  3. 将子回版拉到已转换的主仓库。

    cd ../mainConv
    hg pull -f ../subRepoConv
    
  4. 在拉动时你会注意到回购中的多个头(因为subrepo有自己的头)。合并他们:

     hg heads
     hg merge <RevID from subrepo (not main repo)>
     hg ci -mMergeOfSubRepo
    
  5. 你必须重复3&amp;每个子流程都有4个。

    1. 但是承诺不会被排序。所以按照这里的顺序排列它们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
      
    2. 现在使用http://repo.or.cz/w/fast-export.git将此有序的mercurial repo转换为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
      

答案 1 :(得分:4)

是。您最好的选择是使用git commit-tree手动创建提交。有许多转换工具,但它们永远不会给你你想要的。另一方面,手写脚本将为您提供所需的所有灵活性。

我已写过许多这些脚本,包括git remote-hg本身。

答案 2 :(得分:2)

不相关的offtopic

我确定,你选择了最糟糕的迁移理念(从Mercurial到Git),但它是你的选择和你的责任

迁移课程

我对Git的了解相当薄弱,因此对于Mercurial + subrepo - &gt;单片Git我只能看到和描述这样的方式:

Mercurial + subrepo - &gt;单片Mercurial - &gt;单片Git回购

  • 为了将子目录历史与包装回购历史合并,您可以(通过亚历克西斯的评论更正)使用my idea from earlier question关于转换扩展
  • Monolithic Mercurial repo具有额外的抛光历史(一个根,没有至少链接书签的匿名头)可以轻松推送到空Git-repo,使用hg-git

答案 3 :(得分:1)

这就是我为解决类似问题所做的工作:

  1. 使用fast-export
  2. 转换每个mercurial存储库
  3. 将子存储库的目录添加为父存储库中的远程存储库
  4. 在父回购git checkout -b中为每个subrepo存储库命名
  5. 每个子目录
  6. git read-tree --prefix=pathsubrepo/ -u subrepobranch
  7. 这或多或少是我做的更详细一点(改编自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 sub1mastergit branch -D sub2master,因为您不再需要它们了。

答案 4 :(得分:1)

似乎我在问题中遗漏的问题和对可能解决方案的讨论是对所涉及的图论的正确理解。像&#34;这样的想法遍历从旧版本到新版本的所有路径&#34;没有真正明确的定义,或者至少没有反映我期望他们反映的内容。从更严格的角度来看,我认为我有一种方法可行。

首先,问题是:子版本修订仅代表历史中给定点的子树状态。我想将它们映射到代表整个组合树状态的修订版。然后可以以有意义的方式将子汇率DAG与顶级DAG合并。

对于给定的subrepo修订版R,我们可以询问顶级repo(或者父级repo,如果我们有多个subrepos级别)修订包括R或R的任何后代。假设一个根,这一组修订版有一个Lowest Common Ancestor(或者可能不止一个),这似乎是一个很好的候选人。实际上,如果我们与R一起使用的顶级修订版S不是使用R或其后代的修订版的共同祖先(但映射是合理的),那么R将具有后代R&#39;其相关的顶级修订版S&#39;不是S的后代。换句话说,从subrepo派生的历史将在顶级树的修订之间产生令人困惑/无意义的跳跃。

现在,如果我们想要选择一个共同的祖先,那么从使这些修订版本可以检出,构建和测试的角度来看,最低的一个是有意义的,并且从给出一个合理的想法是什么的角度来看顶级回购(和其他子目录)的状态是在subrepo的变化时。整个顶级DAG的根本当然也可以工作,但它不会提供可以检查的有意义的,可用的修订;选择根将是等效的(从可用性的角度来看)到一个天真的repo-merge,每个subrepo有一个根,只要顶级repo更新它使用的修订版,就会从subrepo历史中合并。

因此,如果我们可以使用LCA为每个子版本修订版R分配顶级修订版T(R),那么它将如何转化为

每当子版本修订版R对于R的每个父P都具有与T(P)不同的T(R)时,它有效地将来自顶级回购(和其他子版本)的新变化合并到子历史记录中。转换应将此表示为两个提交:

  1. 实际的subrepo提交R,使用旧的顶级修订。如果R具有单个父P(不是合并提交),则这将是T(P)。如果R有多个父母,那么不清楚是否可以选择使用哪一个父母,但任何父母P的T(P)应该合理。

  2. 合并提交合并返回与R关联的顶级repo提交T(R)的转换C(T(R)),其中C(T(R))本身刚刚合并(1)上方。

  3. 除了引用(1)作为合并父项的C(T(R))之外,转换中对R的所有其他引用应使用(2)。这包括顶级仓库中T(R)的任何后代的转换,这些后代使用此子目录的修订版R,以及R本身的直接子代的转换。

    我认为上述(尽管措辞不当)描述指定了合并顶级和子级别DAG所需的所有内容。每个子版本修订都获得树的完整版本,最终通过&#34; merge commit&#34;连接到转换后的repo的统一DAG中。 (当subrepo合并新的关联顶级修订时,以及顶级合并已更改的子版本修订时)。

    然后,生成git repo的最后一步是简单地以拓扑排序的形式或通过深度优先步行重放合并的DAG,这样每个git commit-tree已经拥有它需要的所有父修订版本本。

答案 5 :(得分:-1)

尝试Facebook的Hg&lt; - &gt; Git转换器:FbShipIt。您所描述的大多数内容应该适用于此提交转换器工具,该工具可复制Mercurial和Git之间的提交。

FbShipIt有一个警告:它不了解合并提交,但可以通过git rebase解决。