読者です 読者をやめる 読者になる 読者になる

Mercurial の commit --amend を使ってみた

2012/06/04 追記
以下の障害は 2.2.2 で解消されました。
注意! 2012/05/16 追記
「名前付きブランチ上で commit --amend するとブランチ名が破棄されることがある」という障害が報告されています。
修正版がリリースされるまで commit --amend の使用を控えるか、ユースケース的に該当しない場合のみ使うようにしましょう *1

名前付きブランチ上で --amend 付きで commit した場合、ブランチ名が破棄されてしまい、default ブランチ上のリビジョンになってしまう

http://groups.google.com/group/mercurial-ja/browse_thread/thread/19684a3415235d33

3431 – Commit --amend option loses the named branch

わくわくしながら使ってみた

先日リリースされた Mercurial 2.2 で、ついに commit に --amend オプションが実装されたので使ってみた。

This is a regularly-scheduled feature release. The most notable feature is a new safe '--amend' option for commit using our new phases infrastructure. There are also a number of signficant performance improvements for large repositories and improvements for case-folding filesystems. See UpgradeNotes for minor compatibility notes.

WhatsNew - Mercurial

これでもうこんな↓ことをしなくても済む!と涙を流す Mercurial ユーザーも多いのではないかと思う。

$ hg commit -m "Fixed path separator error"

(修正もれに気がついた!)

$ hg rollback
$ vim path.js
$ hg commit -m "Fixed path separator error"

これは GitConcepts - Mercurial にも書かれていて良く使われる方法だと思うけど、はっきりいって面倒くさい。
おまけにロールバックしてしまうのでコミットがなかったことになり、修正作業でミスするとすごく面倒くさい。なにより怖い。

そこに満を持して登場したのが Git ではおなじみの --amend オプション。
これは使ってみるしかないよね、ってことで使ってみた。

$ hg log --template "{rev}:{node|short} {desc|firstline}"
0:2eb822b70400 add a.txt

$ vim a.txt
$ hg commit --amend -m "add a.txt"
saved backup bundle to /Users/ebi/hg-amend/.hg/strip-backup/2eb822b70400-amend-backup.hg 

$ hg log --template "{rev}:{node|short} {desc|firstline}"
0:70c2e380137e add a.txt

おお、いい感じ! *2
よーし commit --amend ガンガン使っちゃうぞー!といきたいところだけど、Git との相違点を少し見てみたい。

とりあえず目に入ってきたのは以下2つ。

まずはハッシュ値
commit --amend 前がこうなっていて、

$ hg log --template "{rev}:{node|short} {desc|firstline}"
0:2eb822b70400 add a.txt

commit --amend 後がこう。

$ hg log --template "{rev}:{node|short} {desc|firstline}"
0:70c2e380137e add a.txt

commit --amend 前後でハッシュ値が変わっているので、この2つのリビジョンは明らかに別物であることが分かる。
このあたりは Git も同じ。

そしてもう一つ、「saved backup 〜」というメッセージ。

$ hg commit --amend -m "add a.txt"
saved backup bundle to /Users/ebi/hg-amend/.hg/strip-backup/2eb822b70400-amend-backup.hg 

これは hg help commit に書いてあるとおり、commit --amend は元々のリビジョンをリポジトリから削除してしまうため、Mercurial が自動的にバックアップを取ってくれている、ということのようだ *3

$ hg help commit
    (省略)
    The --amend flag can be used to amend the parent of the working directory
    with a new commit that contains the changes in the parent in addition to
    those currently reported by "hg status", if there are any. The old commit
    is stored in a backup bundle in ".hg/strip-backup" (see "hg help bundle"
    and "hg help unbundle" on how to restore it).
    (省略)

ためしにバンドルされたバックアップからリカバリーしてみたところ、「元々のリビジョン」「commit --amend したリビジョン」がバッチリと戻されていてとてもイイ感じ。
リビジョン1 のハッシュ値が、リビジョン0 とも commit --amend したリビジョンとも異なるけど実運用上は問題ない…かな?

$ hg strip 0 --no-backup
$ hg unbundle /Users/ebi/hg-amend/.hg/strip-backup/2eb822b70400-amend-backup.hg
adding changesets
adding manifests
adding file changes
added 2 changesets with 2 changes to 1 files
(run 'hg update' to get a working copy)

$ hg log --template "{rev}:{node|short} {desc|firstline}"
1:6a7940ded309 add a.txt
0:2eb822b70400 add a.txt

今のところ commit --amend 時の自動バックアップを無効にすることは出来ないようだけど、リビジョンを削除すると本当にリポジトリから消えてしまう Mercurial の場合、バックアップの強制は非常に重要なことだと思う。
この強制的な自動バックアップのおかげで、失敗を怖れずにリビジョンを修正できる *4

…のだけどこのあたりは Git とは動きが違っているので、Git から来た人は少し戸惑うかもしれない。
Git の場合は自動バックアップがないけど、そもそも元々のコミットが削除されるわけではないので必要ない、と言ったところか。

$ git log --pretty=format:"%h %s"
da780fd add a.txt

$ git reflog
da780fd HEAD@{0}: commit (initial): add a.txt

$ vim a.txt
$ git commit --amend -C HEAD

$ git log --pretty=format:"%h %s"
03a2d7f add a.txt

$ git reflog
03a2d7f HEAD@{0}: commit (amend): add a.txt
da780fd HEAD@{1}: commit (initial): add a.txt

ハッシュ値が da780fdgit のコミットは git commit --amend 後の git log では表示されないが、git reflog するとちゃんと残っている。
このあたりは 個人的には Git のほうが安心かつ楽に使えるように思うが、好みの問題かもしれない。

hg commit --amend 出来ないリビジョン

以下のような場合には commit --amend 出来ないので、原因を解消してから再度試すか素直に諦める *5

  • サブリポジトリがあり、サブリポジトリへの再帰的コミットを設定している *6
  • 親リビジョンの phase が public
  • 親リビジョンが マージリビジョン
  • 作業領域でマージ中
  • 親リビジョンがすでに子リビジョンを持っている

結論

commit --amend は超便利かつ安全なので、どんどん使っていきたい。

2012/06/04 追記
以下の障害は 2.2.2 で解消されました。
注意! 2012/05/16 追記
「名前付きブランチ上で commit --amend するとブランチ名が破棄されることがある」という障害が報告されています。
修正版がリリースされるまで commit --amend の使用を控えるか、ユースケース的に該当しない場合のみ使うようにしましょう *7

名前付きブランチ上で --amend 付きで commit した場合、ブランチ名が破棄されてしまい、default ブランチ上のリビジョンになってしまう

http://groups.google.com/group/mercurial-ja/browse_thread/thread/19684a3415235d33

3431 – Commit --amend option loses the named branch

*1:「気をつけて使う」というのは何かにつけ危ないと思う

*2:commit --amend 時にコミットメッセージを指定しない場合は、元々のコミットメッセージをデフォルトとしてエディターが起動する。このあたりは git commit --amend -C HEAD 等と出来る Git のほうが楽な気がする

*3:実際、commit --amend した時の commit はしっかりと strip している

*4:共有しているリビジョンを修正するのは厳禁

*5:どれもこれも当たり前という感じはするが

*6:.hgrc の ui セクションに commitsubrepos = True が設定されている場合

*7:「気をつけて使う」というのは何かにつけ危ないと思う