Mercurial MQ でバイナリファイルを扱う場合はご用心
MQ でなんの気なしにバイナリファイルを扱うと,バイナリファイル自体を lost します。これはこわい。というか実際にはまりました。
現象
まずバイナリファイルを追加。
$ hg init $ perl -e 'print "\x00"' > bin $ ls bin $ hg addremove adding bin $ hg ci -m "binary file added" $ hg log changeset: 0:90e1a39f0fe7 tag: tip user: dayflower <dayflower@example.com> date: Mon Jun 08 11:31:12 2009 +0900 summary: binary file added
バイナリファイルといっても NUL バイトいっこのファイルだけど。
この changeset を qimport
する。
$ hg qimport -r tip $ hg qseries -s 0.diff: binary file added
んで qpop
。
$ hg qpop -a patch queue now empty $ ls # なにもない(問題ない)
qpop
したから bin
ファイルが消えるのは想定どおり。
ここで qpush
すると bin
ファイルが復活するはずだけど……
$ hg qpush -a applying 0.diff patch 0.diff is empty now at: 0.diff $ ls # ファイルが追加されてない!
復活してない!
なぜ?
パッチファイルを見てみると
$ cat .hg/patches/0.diff # HG changeset patch # User dayflower <dayflower@example.com> # Date 1244428471 -32400 # Node ID cabcbf570d050415463d8541b1045ed8ae497981 # Parent 0000000000000000000000000000000000000000 binary file added diff -r 000000000000 -r cabcbf570d05 bin Binary file bin has changed
「Binary file bin has changed」としか内容がない(バイナリ自体のデータが記載されていない)。
diff の出力をそのままパッチにしたからこうなったんですね。だから,戻しようがない。
対処するには --git
オプションをつける
qimport
からやりなおします。このとき --git
オプションをつけると,パッチ(diff)が GIT 形式になる(後述)。
$ hg qimport -r tip --git $ hg qseries -s 0.diff: binary file added $ hg qpop -a patch queue now empty $ ls # なにもない(問題ない)
さて,qpush
すると……
$ hg qpush -a applying 0.diff now at: 0.diff $ ls bin # ちゃんと復活した!
おお,今回はちゃんと復活しましたよ!
パッチファイルを覗いてみると,
$ cat .hg/patches/0.diff # HG changeset patch # User dayflower <dayflower@example.com> # Date 1244428314 -32400 # Node ID d016fd57d57065401ac2b7d732adbc36ba3089a9 # Parent 0000000000000000000000000000000000000000 binary file added diff --git a/bin b/bin new file mode 100644 index 0000000000000000000000000000000000000000..f76dd238ade08917e6712764a16a22005a50573d GIT binary patch literal 1 Ic${MZ000310RR91
今回は 1 バイト の NUL 文字のファイルだからよくわかんないけど,もっとサイズが大きい場合は,base64 みたいな(どんな形式かは忘れた)テキストによるバイナリエンコーディングの表現が載ります。なので,きちんと復活できるわけです。
バイナリファイルじゃなくても属性まわりでも同じことが
こんどは,ファイルの実行属性をいじって,それを changeset / MQ patch 化してみます。
$ ls -F test.cgi $ hg qnew -m 'added eXecutable bit to test.cgi' add_x $ chmod a+x sample.cgi $ ls -F sample.cgi* # 実行属性を付与した $ hg status M sample.cgi $ hg qrefresh $ hg qpop -a patch queue now empty $ ls -F sample.cgi # 実行属性が消えた(想定どおり)
これで qpush
すると実行属性が再び付与されるはずだけど……
$ hg qpush -a applying add_x patch add_x is empty now at: add_x $ ls -F sample.cgi # 実行属性が戻らない!
やっぱりだめです。
こんどは同じように --git
オプションを使います。qimport
に対してじゃなくて qrefresh
に対してだけど。
$ chmod a+x sample.cgi $ hg status M sample.cgi $ hg qrefresh --git $ ls -F sample.cgi* # 実行属性を付与した $ hg qpop -a patch queue now empty $ ls -F sample.cgi # 実行属性が消えた(想定どおり) $ hg qpush -a applying add_x now at: add_x $ ls -F sample.cgi* # 実行属性がきちんと戻った!
今回はきちんと実行属性についてもハンドリングできました。
なお qnew
などにも --git
オプションをつけることができるけど,これはあくまで一番最初のパッチを GIT 形式にするという意味しかなくて,qrefresh
の際につけわすれると,結局バイナリファイル・属性値の変更履歴は lost します。
まとめ
qimport
や qrefresh
するときは忘れずに --git
オプションをつけよう。絶対。
おわりに
いちおう下記の issue があがってます。
たしかに無言でファイルが消えたり属性が消えたりするのは怖い。--git
をデフォルトにするか,GIT 形式じゃなくてもバイナリなどの変更履歴を保存するようにしてほしいです。