2013/05/27

Mercurial 運用開始までのメモ

この文書は無保証です。

要約

svn 単独で運用していた環境から、svn をメインリポジトリサーバとして使用したままで、hg を使った 分散リポジトリ環境に移行した。

キーワード

hgsubversion 拡張, largefiles 拡張, hg graft コマンド

環境と前提条件


OShg
PC1Mac OS X 10.6hg 2.5.2
PC2Windows7 Professional SP1 x86 (32bit)Tortoisehg 2.8, Mercurial 2.6
  1. PC1 と PC2 のユーザは別人である。
  2. svn サーバへアクセス出来るのは PC1 のみである (SSH 鍵の都合上)。
  3. PC2 からのコミットを検証して、PC1 から svn サーバに push する。
  4. 透過的コミットは、はなから目的としない。

結論

概要

  1. svn サーバからは必要な svn ブランチのみ clone すること。svn ブランチ間のマージは諦める。(svn でやる)
  2. svn のタグは諦める。
  3. svn との連係は default ブランチ、hg 側は base ブランチと決める。
  4. repoB は連係専用とし、repoB で通常の編集作業は行わない。 これを守ることで、repoB の default と base の間の同期作業で、graft 時にローカル変更との衝突が発生しない。

構築手順(svn ブランチごとに行う)

  1. repoB の構築
    1. hgsubversion 拡張を enable する。
    2. largefiles 拡張を enable する。
    3. hg clone svn://svnserver/repoA/svnbranch repoBS
    4. hg lfconvert repoBS repoB
    5. cd repoB
    6. cp ../repoBS/.hg/hgrc .hg/hgrc
      (済んだら repoBS は消して良い)
    7. hg svn rebuildmeta
    8. hg up
    9. hg branch base
    10. hg commit -m 'create base branch'
    11. hg serve --port 8881
      ポート番号はブランチごとによしなに決める。
  2. repoC の構築
    1. hg clone repoB repoC
    2. hg up -r base
  3. repoD の構築
    1. hgsubversion 拡張を enable する。
    2. largefiles 拡張を enable する。
    3. hg clone http://PC1:8881/ repoD
    4. hg up -r base

運用手順

  1. repoB から svn に push する
    1. hg up -r base
    2. よしなに確認する。
    3. hg up -r default
    4. hg graft -r 'branch(base)'
    5. hg outgoing
      最終確認する
    6. hg push
  2. repoB に svn repoA から pull する
    1. hg up -r default
    2. hg incoming
    3. hg pull
    4. hg up -r base
    5. hg graft -r 'branch(default)'
  3. repoC, repoD での通常作業
    1. base ブランチに対してcommit。base ブランチからさらにブランチしてもよい。 base ブランチの内容だけが svn への push 候補となる。
    2. 普通に push, pull する。

知識とは失敗である、その手順

失敗: largefiles 拡張が必要

まずなにも考えず hg clone で svnリポジトリ全体をクローンする。

  1. PC1
    1. hgsubversion 拡張を enable する。
    2. hg clone svn://svnserver/repo1 repo1
      →成功。2時間ぐらいかかった。
    3. cd repo1
    4. hg serve --port 8881
  2. PC2
    1. hg clone http://PC1:8881/ repo1
      →数十分後に OutOfMemory の Python 例外

調べたり聞いたりしたところによると、hg が想定しているファイルサイズは <10MB だそうで。 ファイルを全部メモリに展開するみたいで、32bit OS では Python のアドレス空間が不足する。

Twitter で愚痴を言ってみたところ、、、

@flyingfoozy さんご親切にありがとうございました。

というわけで largefiles 拡張が要るとのこと。
→やりなおし

失敗: hg lfconvert + hg svn rebuildmeta の罠

  1. PC1
    1. largefiles 拡張を enable する。
    2. hg clone svn://svnserver/repo1 repo1S
    3. hg lfconvert repo1S repo1
    4. cd repo1
    5. hg pull
      →エラー。paths の設定は lfconvert で引き継がれていない、というか hgrc がコピーされない。
    6. cp ../repo1S/.hg/hgrc .hg/hgrc
    7. hg pull
      →svn のリビジョンをもう一度全部 pull して来ようとする。

lfconvert したリポジトリは repo1S とは全く別のリポジトリで、ハッシュ値も全く違うものになる。 なのでリポジトリ内の clone した時のリビジョンと、svn の対応リビジョンの情報が一致しなくなり、 hg pull (incoming) で見える svn 側のリビジョンは別のものとして扱われてしまう。

→hg svn rebuildmeta

失敗: hgsubversion default ブランチの罠

hgsubversion 拡張は default ブランチを svn との連係に使用するが、svn にはマージリビジョンの 概念がないので、マージリビジョンがあると push できない。

さらに

  • うっかりローカル変更があると、svn 側と衝突したときのマージが面倒。
  • commit 後、svn に push するとハッシュ値やユーザ名が svn 側のものに変わる。
    →push するまでハッシュが確定しない。したがってリビジョンを参照する(ログに関連リビジョンのハッシュを記載する)ことは不可能。

世の中を見ると、merge - commit - revert する方法が提案されていて、確かに可能だが、

  • 複数のコミットで構成されるマージの場合、コミットがまとめられてしまう。
  • 一つ一つのコミットを svn に送信したい場合は、一つづつ merge - commit - revert しなければならない。
  • commit ログをもう一回入力しなければならない。
  • リビジョングラフの形が気持ち悪い。
    (イマイチ理解していないのでどこか間違ったのかもしれない)

さらに世の中には rebase する方法も提案されているが、試すより先に graft を思いついたので試していない。

default には merge するな、あそこは svn なのだ

解決策

  • default ブランチは svn との連係専用にしてここで編集しない。
  • repoC, repoD でのコミットは、base ブランチに行う。default は(過去ログ以外)見ない。
  • graft を使う。この場合、default ブランチと base ブランチはリビジョングラフ上で交差しない。
    これは嫌な人には嫌だろうが、今回のケースでは svn に push したコミットの author が書き換えられるため、 同じ変更でも別のコミットに見えてくれる方が都合が良く、怪我の功名。

graft は今回の用途には非常に都合よくできていて

  • マージリビジョンは無視される。
  • graft したリビジョンは記録されていて、何回も graft されるようなことはない。
  • コミットログはそのままコピーされる。

失敗: 非標準レイアウトの罠

非標準レイアウトの場合 hgsubversion は全てのブランチとタグを通常のディレクトリとして svn から取得する。

これは svn の ブランチとタグが只のコピーと区別がつかないせいだが、clone したディレクトリが 恐ろしく肥大化してしまい、何をしても時間がかかるようになってしまう。

解決策

  • 個別のブランチを svn から取り出す。svn ブランチ間マージは hg では実行しない。