eji著

主にプログラミングや山登りの話などを書いていく予定

graft と strip を使って Mercurial の良さを実感したときの話

Mercurial Advent Calendar 2012 - connpass / 24日目のエントリです。

つい最近 Mercurial の graft と strip 機能を知って、「Mercurialって本当に良いツールだなぁ」思ったことがありました。今回はその時の話をしながら、どのバージョン管理システムを導入しようかと悩んでいる方に対して、Mercurial の良さを伝えたいなと思います。

※私自身Mercurial初心者なので、濃い技術話はありません。。。

現在の私の仕事について

現在の私の仕事はPHP等を使ったWEBアプリ開発がメインです。そして開発者が私一人という小規模開発が多いです。

ソースコードの管理はMercurialを使い、以下のように機能ごとにブランチを切って都度マージしていく方法で運用しています。

f:id:eji:20121224014001p:plain

A案件の話

私はAという案件の開発をしていました。その案件は画面数が数ページ程度で、外部サービスと連携しない非常に単純なWEBサイトでした。
※この話はフィクションであり、 実在の団体・案件とは一切関係ありません

PDF出力機能を追加してくれ

ある日お客さんに「PDF出力機能付けてくれ」と頼まれました。
私はさっそくブランチを切り、開発を進めたのでした。

# リリース用のブランチ(default)から新しくブランチ(add_pdf)を作成
$ cd project_a
$ hg up default
$ hg branch add_pdf
$ vim pdf.php
$ hg add .
$ hg ci -m "PDFの修正おわり。"

最近はライブラリの整備がかなり整っていることもあり、予定よりも数日早くリリースできるところまでこぎつけました。
(dompdf は HTML/CSS で作られたページから見た目そのままに PDF を生成してくれるので非常に助かりました)

さっそく私はお客さんに「機能追加完了しました。いつごろリリースしますか?」と確認しました。

さらなる機能追加

ところがお客さんは次のように仰られました。

  • 社内で検討した結果、リリースは◯月X日に延期することが決まった。また、リリースする前にPDFのテンプレートのデザインを修正してくれ
  • PDF出力機能をリリースする前に、GoogleAnalytics用のタグを埋めこんでくれないか?

私はその日意識がもうろうとしていました。実はその日N案件でトラブルがあったのです。私は一切関与していなかった案件なんですが、人手不足のため問題調査に駆り出されたのです。

問題が解決したのはその日の夜でした。意識がもうろうとしている中、それでも私はブランチを切りました。「たいした修正じゃない、すぐ修正してマージしてデプロイして終わりだ」

$ cd project_a
$ hg up add_pdf # <- ブランチ間違えた(あとで気づいた)
$ hg branch add_ga
$ vim ga.php
$ hg add .
$ hg ci -m "GoogleAnalyticsの修正おわり。"

帰ろうと思ったときにPDFのテンプレートの修正もあったなと思い出し、更に修正を加えました。

$ hg up add_pdf
$ vim pdf.php
$ hg ci -m "PDFのテンプレを修正"

マージ

翌日、さっそく私はGoogleAnalyticsのブランチをリリース用のブランチにマージしようとしました。Mercurialのマージ機能は賢いのですんなり終わると思っていました。

$ cd project_a
$ hg up default
$ hg merge add_ga
resolving manifests
getting ga.php
getting pdf.php
2 files updated, 0 files merged, 0 files removed, 0 files unresolved
(branch merge, don't forget to commit)
$

「ん?何か変なファイルもマージされているぞ・・・。pdf.phpってGoogleAnalyticsの修正と関係ないよな・・・」
そう思い私はリビジョングラフを見てみました。

$ hg glog --style compact
o  3[tip]:1   295fd08d1491           # PDF出力用ブランチのトップ
|    PDFのテンプレを修正
|
| @  2   d92b6aa5426f                # GoogleAnalytics追加用ブランチのトップ
|/     GoogleAnalyticsの修正おわり。
|                                    # ブランチが枝分かれした場所
o  1   13189cdbcf0a                  
|    PDFの修正おわり。
|
@  0   59c1d5ab8aa1                  # リリースで使っているブランチのトップ
     initial commit
$

そこにはPDF出力機能ブランチから横に生えているGoogleAnalyticsブランチがありました。
本当はリリース用のブランチからGoogleAnalytics用のブランチを作りたかったのですが、誤ってPDF機能用のブランチから切りだしていたのです。

f:id:eji:20121224020003p:plain

ブランチを多く扱っていると意図しないブランチから新しいブランチを作成してしまうことは良くあることだと思います。私はこの1ヶ月の間に約4個程のブランチの意図しないブランチから作成していました。

Mercurialのマージ

マージは以下のように簡単なコマンドで行えます。

# default (リリース用) ブランチに add_ga (GooogleAnalytics用)ブランチをマージしたい
$ hg up default
$ hg merge add_ga

Mercurialはブランチをマージする時に、マージ元ブランチがマージ先ブランチと枝分かれしたところを探し、枝分かれしたところからの修正を取り込んでくれます。

今回の場合、私としてはGoogleAnalyticsのブランチだけを取り込みたかったのですが、Mercurialから見ればPDF出力機能の修正もリリース用ブランチから枝分かれした後に追加された修正だと判断されたため、マージされてしまったようです。

一人の開発では並行作業できる数が少なくなるので、短期間でブランチが増えることはないのですが、リリース日が伸びて、「伸びたついでに新しい機能も追加してくれ!」なんてことが良くあり、時間が経つとそれなりにブランチの数が増えていきます。そうなるとブランチを切り間違えたり、切り違えたブランチに数十、数百ものファイルの修正をコミットしてしまうことが起きてしまいます。そしていざマージするときになると、どれを取り込んだら良いのか分からなくなりお手上げ状態になります。

間違ったブランチにコミットしてしまったら graft と strip でコミットを移動させる

私はこの問題をどうしようかと悩み id:troter に相談して、 graft を使うのが良いと教えてもらいました。
graft は Mercurial の機能のひとつで、別のブランチの修正を現在のブランチにコピーさせることができます。早速 graft を使ってみました。

$ hg up default
$ hg branch add_ga_2
$ hg graft add_ga
grafting revision 2
resolving manifests
getting ga.php
ga.php
$ hg st
$

ここでリビジョングラフを見てみると・・・

$ hg glog --style compact
@  4[tip]:0   e7835dca3104      # GoogleAnalytics追加用ブランチのトップ(新)
|    GoogleAnalyticsの修正おわり。
|
| o  3:1   295fd08d1491         # PDF出力用ブランチのトップ
| |    PDFのテンプレを修正
| |
| | o  2   d92b6aa5426f         # GoogleAnalytics追加用ブランチのトップ(旧)
| |/     GoogleAnalyticsの修正おわり。
| |
| o  1   13189cdbcf0a   
|/     PDFの修正おわり。
|
o  0   59c1d5ab8aa1             # リリースで使っているブランチのトップ
     initial commit
$

新しく作ったブランチに add_ga ブランチのコミットがコピーされていることが分かります。
しかし、古いブランチが残ってしまって邪魔です。そこで色々調べてみると不要になったブランチの削除には strip という拡張機能を使うと良いということが分かりました。

$ hg strip add_ga
1 changesets found
saved backup bundle to /Users/eji/project_a/.hg/strip-backup/d92b6aa5426f-backup.hg
2 changesets found
adding branch
adding changesets
adding manifests
adding file changes
added 2 changesets with 2 changes to 2 files (+1 heads)
$ hg glog --style compact
@  3[tip]:0   e7835dca3104
|    GoogleAnalyticsの修正おわり。
|
| o  2   295fd08d1491 
| |    PDFのテンプレを修正
| |
| o  1   13189cdbcf0a  
|/     PDFの修正おわり。
|
o  0   59c1d5ab8aa1   
     initial commit
$

枝分かれしていた古いブランチが消えているのが分かります。これで再度ブランチをマージしてみます。

$ hg up default
$ hg merge add_ga_2
resolving manifests
getting ga.php
1 files updated, 0 files merged, 0 files removed, 0 files unresolved
(branch merge, don't forget to commit)

以前はpdf.phpという不要なファイルがマージされていたのですが、今回はそれがなくなっています。問題なさそうなのでコミットします。

$ hg ci -m "merge add_ga_2"     
ga.php
committed changeset 4:91c42085d0ab

ここで再度リビジョングラフを見てみます。 

$ hg glog --style compact
@    4[tip]:0,3   91c42085d0ab 
|\     merge add_ga_2
| |
| o  3:0   e7835dca3104   
|/     GoogleAnalyticsの修正おわり。
|
| o  2   295fd08d1491   
| |    PDFのテンプレを修正
| |
| o  1   13189cdbcf0a   
|/     PDFの修正おわり。
|
o  0   59c1d5ab8aa1   
     initial commit
$

graft と strip を使ったブランチ操作のイメージは以下のような感じです。
f:id:eji:20121224024035p:plain

結果

graft と strip を使ってマージを問題なく済ませることができ、スムーズにリリースすることができました。

さいごに

この件を通して 「graft と strip を使い、間違ったブランチにコミットしてしまっても、あとで適切なブランチにコミットを移動できる」ことの快適さと安心感を身を持って実感できました。

もし、Mercurialの導入で迷っている方がいらっしゃれば迷わず使ってみると良いと思います。特にブランチを良く作成してマージすることが多い方にとって graft や strip は非常に重宝する機能になると思います。

ということで、ダラダラと長い話になってしまい、Mercurialの良さが伝えられたか微妙ですが... graft と strip は Mercurial 様々な便利機能の中でもかなり衝撃を受けた機能だったので、つい話が長くなってしまいました...

来年は Subversion で管理しているプロジェクトを全て Mercurial に移行できたら良いなと思っています。
ありがとうございました。