彷徨えるフジワラ

年がら年中さまよってます

Mercurial 1.7 版以後の backout 挙動

11 月初旬リリースの Mercurial 1.7 に向けてメッセージ翻訳の日々を送っていたところ、backout コマンドのオンラインヘルプではたと筆が止まる。

何やら 1.7 版からは --merge オプションが指定されない場合の挙動が変わるらしいのだが、そもそも原文が --merge 指定有りの場合の挙動に終始していて、無指定時の挙動に関しては今ひとつはっきりしない。

ということで、実地検証をしてみることに。

まずは以前の版における backout コマンドの挙動の確認。

以下の様な履歴構成で、作業領域の親リビジョンが 2 である状態を仮定:

ここでリビジョン 1 の修正を無かったことにする場合:

$ hg backout -m 'backout non tip rev' 1
reverting hoge.txt
created new head
changeset 3:d1d7a3498384 backs out changeset 1:7e177fb5f251
the backout changeset is a new head - do not forget to merge
(use "backout --merge" if you want to auto-merge)
$ hg heads
changeset:   3:d1d7a3498384
tag:         tip
parent:      1:7e177fb5f251
user:        flying-foozy
date:        Fri Oct 29 23:49:27 2010 +0900
summary:     backout non tip rev

changeset:   2:2041f8e3ddb7
user:        flying-foozy
date:        Fri Oct 29 22:56:07 2010 +0900
summary:     third commit

$

というコマンド実行により、履歴ツリーは以下のように改変される。

ここで backout コマンド実行により新たに作成されるリビジョン 3 は、リビジョン 1 における修正内容を打消す内容のもの。「"hg export" 出力を "patch -r" で逆向き適用してコミット」と言った方がわかりやすいかな?

後は、新規作成された打消しリビジョン(この例では「3」)を、再新リビジョン(この例では「2」)と "hg merge" すれば、対象リビジョンでの変更内容は無かったことになる。

# merge 結果の commit により「4」が生成される

で、以前の版では backout コマンドに --merge を指定すると:

  1. 現時点での親リビジョンを記憶(この例では「2」)
  2. 対象リビジョンを打ち消すリビジョンを新規作成(「3」)
  3. 先に記憶しておいた親リビジョン(2)に新規リビジョン(3)をマージ("hg merge" 相当)

というところまでを実施してくれるので、後はマージ内容を確認してコミットするだけ、という挙動だった。

つまり --merge 指定は「新規リビジョンと現親リビジョンとの間で "hg merge" 相当を実施するか否か」を指定するもの。

1.7 版でも、--merge を指定した場合の挙動は変わらないのだが、同じ状況で --merge 指定無しで backout を実施してみると:

$ hg backout -m 'backout non tip rev' 1
.....
$ hg heads
changeset:   2:2041f8e3ddb7
tag:         tip
user:        flying-foozy
date:        Fri Oct 29 22:56:07 2010 +0900
summary:     third commit

$

あれ?新規リビジョンが作成されないぞ?と思って "hg status" を見てみると、何やら作業領域が改変されている。改変内容をつらつらと眺めること暫し ....

あ!わかった!1.7 版で --merge 指定が無い場合、「打ち消し効果を持つリビジョンの作成 〜 親リビジョンとのマージ」結果に相当する改変内容が、直接親リビジョンに適用されるんだ!

つまり 1.7 版での --merge 指定は「打ち消し効果を持つ新規リビジョンを枝分かれさせるか否か」の指定に変わったということらしい。

あー、確かに面倒だもんなぁ > 枝分かれ&マージ@backout

但し、親リビジョン以外を打ち消す場合に、明示的なコミットが必要である点は以前と同じ。いくら枝分かれしないとは言っても、改変内容自体は先述したように「枝分かれ 〜 マージ」結果に相当する内容だからね。

「打ち消し効果を持つリビジョン」と「親リビジョンとマージするリビジョン」の2つが生成されるのは、冗長と言えば冗長な気もするけど、マージ相当内容を1つのリビジョンに入れ込んじゃうのは、後から内容を検証する場合にどうなんだろう?

個人的には「枝別れ〜マージ」の旧来モデルの方がわかり易い気がするけどなぁ。

試しに、unified diff のコンテキスト行に相当する部分を改変する変更を挟んだ場合の挙動を確認してみることに。

changeset:   1:7e177fb5f251
user:        flying-foozy
date:        Fri Oct 29 22:55:49 2010 +0900
summary:     second commit

diff -r a4fa5f7cd6de -r 7e177fb5f251 hoge.txt
--- a/hoge.txt	Fri Oct 29 22:53:59 2010 +0900
+++ b/hoge.txt	Fri Oct 29 22:55:49 2010 +0900
@@ -1,3 +1,3 @@
 preceding context
-target line
+MODIFIED: target line
 succeeding contex

リビジョン 1 におけるコンテキスト行部分("〜 context" 部分)を続くリビジョン 2 において改変。

changeset:   2:2041f8e3ddb7
tag:         tip
user:        flying-foozy
date:        Fri Oct 29 22:56:07 2010 +0900
summary:     third commit

diff -r 7e177fb5f251 -r 2041f8e3ddb7 hoge.txt
--- a/hoge.txt	Fri Oct 29 22:55:49 2010 +0900
+++ b/hoge.txt	Fri Oct 29 22:56:07 2010 +0900
@@ -1,3 +1,3 @@
-preceding context
+MODIFIED: preceding context
 MODIFIED: target line
-succeeding contex
+MODIFIED: succeeding contex

で、この状態から 1.7 版でリビジョン 1 を backout してみたところ、予想通り --merge 無しで実施しても、いわゆる衝突の検出があった。

diff -r 2041f8e3ddb7 hoge.txt
--- a/hoge.txt	Fri Oct 29 22:56:07 2010 +0900
+++ b/hoge.txt	Fri Oct 29 23:08:02 2010 +0900
@@ -1,3 +1,13 @@
+<<<<<<< local
+preceding context
+target line
+succeeding contex
+||||||| base
+preceding context
+MODIFIED: target line
+succeeding contex
+=======
 MODIFIED: preceding context
 MODIFIED: target line
 MODIFIED: succeeding contex
+>>>>>>> other