Mercurial での改行コード
※ NATIVE 設定周りと、.hgeol
設定反映契機に関する記述を改善しました@2013-12-22
このエントリは、Mercurial Advent Calendar 2011 の5日目です。
Windows/Unix/Linux/Mac OS などなど、複数の環境における作業成果を、共有リポジトリ等を用いて共有する場合、環境毎にデフォルト値が異なる改行コードと文字コードは、かなりの確率でトラブルの原因となります。
このエントリでは、Mercurial における改行コードに関して説明しようと思います。
# 改行コードの話が思いのほか長くなったので、文字コードは別エントリで .... (^ ^;;)
Mercurial では 1.5.4 から、リポジトリ単位で改行コードを管理できる eol エクステンションが同梱されるようになりました。
改行コードの管理には、この eol エクステンションを利用します。
なお、hg help
で表示されるオンラインヘルプを日本語化するには、言語設定が必要です(オンラインヘルプの翻訳版は、ウェブ上で参照することも可能です)。
eol エクステンションの有効化
まずは eol エクステンションを有効にする必要があります。1.5.4 以前の Mercurial を使う必要が無いのであれば、${HOME}/.hgrc
や %USERPROFILE%\Mercurial.ini
などのユーザ毎設定ファイルに、以下のような記述を追加しましょう。
[extensions] hgext.eol =
hg showconfig extensions
の出力に hgext.eol
行が含まれていれば、設定は完了です。
私自身の経験上、セクション名 extensions
の末尾の "s" が抜けている、というミスが結構な頻度で発生するので、記述の際には注意した方が良いでしょう(笑)。
TortoiseHg の GUI 経由であれば、「ファイル」⇒「設定」⇒「〜のユーザ設定」において「エクステンション」を選択し、「eol」のチェックボックスをチェックします。
改行設定ファイルの記述
改行変換処理は、eol エクステンションを有効にしただけでは機能しません。
リポジトリ毎に .hgeol
ファイルを記述する必要があります。
.hgeol
ファイルは、.hgignore
ファイルと同様に、作業領域のルート直下に配置します。
.hgeol
の基本的な文法は .hgrc
等の設定ファイルと同様です。とりあえずは、[patterns]
セクションの記述形式を理解しておけば良いでしょう。
[patterns] **.py = LF **.vcproj = CRLF **.txt = LF Makefile = LF **.jpg = BIN
各行の等号("=")の左辺はファイルの「パターン指定」、右辺は「改行種別」を記述します。
「パターン指定」記述の詳細に関しては、hg help patterns
で表示されるオンラインヘルプ等を参照してください。
なお、.hgeol
では glob
形式のパターンしか記述できませんので注意してください。
「改行種別」には、以下のものが指定できます。
改行種別 | 意味 |
---|---|
LF | 0x0a (LF: line feed) の1バイトで改行。いわゆる「UNIX 改行」 |
CRLF | 0x0d (CR: carriage return) + 0x0a (LF) の2バイトで改行。いわゆる「DOS 改行」 |
BIN | 改行コードの変換を行わない = バイナリファイル向け |
NATIVE | 稼動環境に応じた改行 (※ 詳細は後述) |
前述の設定例は、以下のような変換設定を意味します。
.py
、.txt
拡張子のファイルおよびMakefile
は LF 改行(= UNIX 改行).vcproj
拡張子のファイルは CRLF 改行(= DOS 改行).jpg
拡張子のファイルは変換無し
eol エクステンションでは、パターンに合致しない場合のデフォルト挙動は、BIN 設定相当 (= 変換処理無し) となります。
そうなると、前述の **.jpg=BIN
は書いても意味が無いように思われるかもしれません。
ここで、eol エクステンションのオンラインヘルプ(hg help eol
)を良く見ると、以下のような記述があります。
先に合致したパターンが採用されますので、 より特徴的なパターンほど、 より先頭で記述してください。
(中略)
BIN (改行変換無し) は、 Mercurial のデフォルトの挙動です: 一般的なパターン指定に、 意図せず合致してしまうのを回避したい場合に、 当該パターンよりも先に合致させる場合にのみ有用です。
つまり、「特定のディレクトリ配下は全て LF 改行」というような設定も併記する (or 今後併記する可能性がある) ような場合に備えた設定と言えます。
さて、.hgeol
で記述された変換設定は:
hg commit
での、作業領域内容から履歴への記録hg status
やhg diff
での、作業領域内容と履歴記録の比較hg update
やhg revert
での、履歴情報の作業領域への取り出し
といった際に、作業領域内容の変換に使用されます。
例えば、前述の **.py = LF
設定記述が有効な場合、.py
拡張子のファイルは、CRLF 改行 (= DOS 改行) で保存したものであっても、コミットの際に LF 改行 (= UNIX 改行) に変換されてから、履歴に記録されます。
あるいは、前述の **.vcproj = CRLF
設定記述が有効な場合、.vcproj
拡張子のファイルは、どの環境における hg update
や hg revert
でも、必ず CRLF 改行 (= DOS 改行) に変換された上で作業領域に取り出されます。
なお、コミットによる履歴記録時点で、eol による改行コード変換の設定が有効だった場合、履歴には改行コード変換後の内容が記録されます。また、eol エクステンションの有効化や、.hgeol
記述内容を変更したからといって、履歴記録済みファイルの改行コードは変換されません。
あくまで、「作業領域と履歴情報との間でのやりとり」に割り込んで改行コードを変換するだけである、という点に留意してください。
ちなみに .hgeol
ファイル自体は、改行コードの変換対照から除外 (= BIN 設定相当) されます。
同様に、名前が .hg
で始まる各種管理用ファイル (例: .hgtags
や .hgignore
) も変換対象から除外されます (.hgeol
に明示的に改行コード変換設定を記述しても無視されます)。
NATIVE 種別での挙動
説明を後回しにした NATIVE 種別ですが、これは「稼動環境に応じた改行変換を行う」という便利な種別です。
.hgeol
ファイルの [patterns]
セクション記述において、変換主t別として LF や CRLF を書く代わりに、NATIVE と記述することで、リポジトリには常に LF 改行で記録するようになります。
「リポジトリには常に LF 改行で記録」というのは、一見すると、「変換種別に LF 改行を記述」した場合と差異が無いように思うかもしれません。
NATIVE 指定のミソは、「リポジトリへの記録」が常に LF 改行である一方で、「作業領域への取り出し」には稼働環境に応じた改行コードを使用する、という点にあります。
つまり、Windows 環境では、hg update
や hg revert
における作業領域へのファイル取り出しの際に、自動的に CRLF 改行に変換されたものが取り出されるわけです。
整理すると以下のようになります。
変換種別 | 履歴記録 (作業領域⇒履歴) |
作業領域への取り出し (履歴⇒作業領域) | 備考 | |
---|---|---|---|---|
UNIX 系環境 | Windows 環境 | |||
LF | LF に変換 | LF に変換 | LF で記録/LF で取り出し | |
CRLF | CRLF に変換 | CRLF に変換 | CRLF で記録/CRLF で取り出し | |
NATIVE | LF に変換 | LF に変換 | CRLF に変換 | LF で記録/取り出し形式は環境依存 |
BIN | 変換無し | 変換無し | ---- |
.hgeol 変更の適用契機
.hgeol
ファイル記述を変更した場合、作業領域中のファイルに変更後の改行変換設定が適用されるのは、次に「作業領域にファイル内容の取り出し」を行った契機となります。
それまでは、.hgeol
記述と矛盾する状態の履歴管理対象ファイルが、作業領域中に存在し続けるかもしれません。
例えば、ある時点で LF 改行/変更なし状態だったファイルに対して:
.hgeol
に、当該ファイルを NATIVE 設定化するエントリを追加- 作業領域中のファイルの改行形式を CRLF に変更
上記の対処を両方とも行ったとしても、hg status
は当該ファイルに対して「ファイルが変更された」と表示します。
このような場合に hg status
の表示状態を適正化したい場合は、hg debugrebuildstate
を実行して、作業領域のファイル管理状態を再構築してください。
なお、hg debugrebuildstate
は以下のように振る舞います。
- 作業領域中の内容は改変されない
- 既存ファイルへの変更内容はそのまま
- 未コミットな、除外/追加/改名操作の情報も破棄
- 新規追加ファイルは、内容はそのまま、状態は「未知」(unknown:
?
) hg remove
契機で削除されたファイルは、削除されたままで、状態は「不在」(missing:!
)- 改名元ファイルは、削除されたままで、状態は「不在」
- 改名先ファイルは、内容はそのままで、状態は「未知」
- 新規追加ファイルは、内容はそのまま、状態は「未知」(unknown:
作業領域中の内容は改変されないわけですから、.hgeol
記述変更+hg debugrebuildstate
の組み合わせでも、作業領域中のファイルの改行形式は、変更されません。
.hgeol
の設定変更を、作業領域中のファイルの内容に、早々に反映させたい場合は:
.hgeol
の設定変更の実施- 作業領域中の対象ファイルを、Mercurial を経由せずに削除
hg revert 対象ファイル
実行で、対象ファイルの内容を復旧(= eol による改行コード変換の実施)
といった手順を踏む必要があります。
.hgeol の有効範囲
※ ここで説明する内容は、通常は特に必要とはならない事柄です
eol エクステンションによる改行変換は、基本的には、作業領域中の .hgeol
ファイルの内容が反映されます。この場合、.hgeol
ファイル自体は、必ずしも履歴管理されている必要はありません。
但し、「作業領域中の .hgeol
ファイルの内容が反映」されない、例外ケースが2種類あります。
最初の例外ケースは:
hg update
の場合には、hg update
を実施した時点の作業領域中の.hgeol
の内容ではなく、update 先リビジョンにおける.hgeol
ファイルの内容が反映される
これは比較的分かりやすいのですが、もう一つの例外ケースはちょっとややこしいです。
作業領域には
.hgeol
ファイルが無いが、tip リビジョンで.hgeol
が履歴管理対象になっている場合は、その内容が反映される
これだけでは分かりづらいでしょうから、リビジョンツリー図を使って説明します。
例えば、ある共有リポジトリにおいて、リビジョン 2 から .hgeol
ファイルが追加されたとしましょう。忘れていて後から慌てて追加した、というような状況でしょうか?(笑)
ある作業者は、リビジョン 1 までしか無い状況でリポジトリを複製して、作業に着手しているとします。
何かの契機で、共有リポジトリからの hg pull
だけは行ったとします。
この時点で、作業のベースとなっているリビジョン 1 にも、コミット未実施な作業領域にも .hgeol
ファイルは無いのですが、tip であるリビジョン 2 には .hgeol
ファイルが記録されています。
そのため、この状況で作業領域の成果をコミットすると、2つ目の例外ケースとして、リビジョン 2 での .hgeol
ファイルの内容を反映した改行変換が実施されます。
ここまでの挙動は「ちょっと便利」程度に考えても良いのですが、この先の作業を考えると注意が必要です。
リビジョン 3 がコミットされてしまうと、その作業者のリポジトリでの tip はリビジョン 3 になってしまうため、次のコミットとなるリビジョン 4 では:
- 作業領域には
.hgeol
が無い - tip リビジョン (= リビジョン 3) では
.hgeol
が履歴管理されていない
ということから、以後のコミット等における改行変換処理は実施されません!
もっとも、見て分かるように、これは明らかにマルチプルヘッドとなりますので、本来であれば早々にマージすべき状況ですから、現実的にはあまり問題にはならないでしょう。
但し、このような状況を回避する意味から、空でも良いので、履歴の浅い段階で .hgeol
を登録してしまうことをお勧めします。
native 設定と歴史的経緯
※ ここで説明する内容は、通常は特に必要とはならない事柄です
元々 Mercurial には、改行コード変換を行う win32text エクステンションが同梱されていました。
これが eol エクステンションによって代替されたのは:
改行コードの一貫性は、リポジトリ単位で管理したい
という部分が大きいと思います。
win32text エクステンションで改行コード変換を行う場合、変換対象ファイルのパターン等は、設定ファイルの [encode]
や [deocde]
セクションに記述する必要がありました。
しかし、この設定記述は、分散したリポジトリ間で共有することができません。
つまり、設定ファイルの記述漏れによって、リポジトリ内容の一貫性が損なわれる可能性があるわけです。
# ここでは eol エクステンションの有効化漏れの可能性は除外します
かといって、複数のリポジトリを併用する際の [encode]
/[deocde]
セクション記述漏れを防ぐために、ユーザ毎設定やホスト毎等で [encode]
/[deocde]
セクションを記述した場合は、由来の異なる (= 別プロジェクト) リポジトリ間で、設定の矛盾が生じてしまう可能性があります。
つまりは、改行コードの一貫性は、リポジトリ単位で管理したいわけです。
設定ファイルがリポジトリ内で履歴管理対象となっていれば、履歴情報伝播の一環で、改行設定も他のリポジトリに伝播させることができます。
そのような経緯から、.hgeol
というファイルが導入されたわけです。
さて、このような経緯を踏まえた上で、これまで触れてこなかった「native 設定」について見てみましょう。
eol エクステンションには、2つの native 設定があります。
.hgeol
ファイルの[repository]
セクションで記述する native 設定.hgrc
等の設定ファイルの[eol]
セクションで記述する native 設定
前者は、「作業領域 ⇒ 履歴」方向で適用される改行変換において、NATIVE 設定の場合に適用される改行コードを、後者は逆方向の「履歴 ⇒ 作業領域」での NATIVE 設定に適用される改行変換を指定するものです。
一見すると、同じような内容の設定記述が、複数の場所に散在しているように見えるかもしれません。
しかし、「作業領域 ⇒ 履歴」で適用される改行変換設定は、リポジトリ格納時の一貫性に関わるものですから、複製先リポジトリ間で共有される .hgeol
で定義できた方が嬉しい筈です。
その一方で、「履歴 ⇒ 作業領域」で適用される設定は、利用者の環境に応じて任意に設定できる設定ファイル (${HOME}/.hgrc
や .hg/hgrc
) において記述できなければ意味がありません。
つまり、「リポジトリ格納時の一貫性」と「利用環境に応じた作業領域取り出し」という点で、両者は全く異なるレイヤーに属することから、記述箇所が異なるわけです。
ちなみに、.hgeol
の [repository]
セクションにおける native はデフォルト値が LF で固定ですが、設定フィルの [eol]
セクションにおけるデフォルト値は、実行環境に応じて異なります (Windows 環境なら CRLF、Unix 系環境なら LF)。
# pyhon の os.linesep
値が使用されます