Hatena::ブログ(Diary)

彷徨えるフジワラ このページをアンテナに追加 RSSフィード

2011-12-05

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 statushg diff での、作業領域内容と履歴記録の比較
  • hg updatehg revert での、履歴情報の作業領域への取り出し

といった際に、作業領域内容の変換に使用されます。

例えば、前述の **.py = LF 設定記述が有効な場合、.py 拡張子のファイルは、CRLF 改行 (= DOS 改行) で保存したものであっても、コミットの際に LF 改行 (= UNIX 改行) に変換されてから、履歴に記録されます。

あるいは、前述の **.vcproj = CRLF 設定記述が有効な場合、.vcproj 拡張子のファイルは、どの環境における hg updatehg 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 updatehg revert における作業領域へのファイル取り出しの際に、自動的に CRLF 改行に変換されたものが取り出されるわけです。

整理すると以下のようになります。

変換種別履歴記録
(作業領域⇒履歴)
作業領域への取り出し
(履歴⇒作業領域)
備考
UNIX 系環境Windows 環境
LFLF に変換 LF に変換LF で記録/LF で取り出し
CRLFCRLF に変換 CRLF に変換CRLF で記録/CRLF で取り出し
NATIVELF に変換 LF に変換 CRLF に変換 LF で記録/取り出し形式は環境依存
BIN変換無し変換無し----

NATIVE 設定の場合、Windows 環境における作業領域への取り出しが CRLF 改行で実施されます。そのため、CRLF 改行を前提とした Windows の一般的なツール群とも連携可能です。

以上のことから、Windows 環境での作業者と成果物を共有する場合は、LF や CRLF といった固定改行設定ではなく、稼働環境に応じた挙動をとる NATIVE を指定した方が良いと言えます。

.hgeol 変更の適用契機

.hgeol ファイル記述を変更した場合、作業領域中のファイルに変更後の改行変換設定が適用されるのは、次に「作業領域にファイル内容の取り出し」を行った契機となります。

それまでは、.hgeol 記述と矛盾する状態の履歴管理対象ファイルが、作業領域中に存在し続けるかもしれません。

例えば、ある時点で LF 改行/変更なし状態だったファイルに対して:

  • .hgeol に、当該ファイルを NATIVE 設定化するエントリを追加
  • 作業領域中のファイルの改行形式を CRLF に変更

上記の対処を両方とも行ったとしても、hg status は当該ファイルに対して「ファイルが変更された」と表示します。

このような場合に hg status の表示状態を適正化したい場合は、hg debugrebuildstate を実行して、作業領域のファイル管理状態を再構築してください。

なお、hg debugrebuildstate は以下のように振る舞います。

  • 作業領域中の内容は改変されない
    • 既存ファイルへの変更内容はそのまま
  • 未コミットな、除外/追加/改名操作の情報も破棄
    • 新規追加ファイルは、内容はそのまま、状態は「未知」(unknown:?)
    • hg remove 契機で削除されたファイルは、削除されたままで、状態は「不在」(missing:!)
    • 改名ファイルは、削除されたままで、状態は「不在」
    • 改名ファイルは、内容はそのままで、状態は「未知」

作業領域中の内容は改変されないわけですから、.hgeol 記述変更+hg debugrebuildstate の組み合わせでも、作業領域中のファイルの改行形式は、変更されません。

.hgeol の設定変更を、作業領域中のファイルの内容に、早々に反映させたい場合は:

  1. .hgeol の設定変更の実施
  2. 作業領域中の対象ファイルを、Mercurial を経由せずに削除
  3. hg revert 対象ファイル 実行で、対象ファイルの内容を復旧(= eol による改行コード変換の実施)

といった手順を踏む必要があります。

.hgeol の有効範囲

※ ここで説明する内容は、通常は特に必要とはならない事柄です

eol エクステンションによる改行変換は、基本的には、作業領域中の .hgeol ファイルの内容が反映されます。この場合、.hgeol ファイル自体は、必ずしも履歴管理されている必要はありません。

但し、「作業領域中の .hgeol ファイルの内容が反映」されない、例外ケースが2種類あります。

最初の例外ケースは:

hg update の場合には、hg update を実施した時点の作業領域中の .hgeol の内容ではなく、update 先リビジョンにおける .hgeol ファイルの内容が反映される

これは比較的分かりやすいのですが、もう一つの例外ケースはちょっとややこしいです。

作業領域には .hgeol ファイルが無いが、tip リビジョンで .hgeol が履歴管理対象になっている場合は、その内容が反映される

これだけでは分かりづらいでしょうから、リビジョンツリー図を使って説明します。

例えば、ある共有リポジトリにおいて、リビジョン 2 から .hgeol ファイルが追加されたとしましょう。忘れていて後から慌てて追加した、というような状況でしょうか?(笑)

f:id:flying-foozy:20111204230230j:image

ある作業者は、リビジョン 1 までしか無い状況でリポジトリを複製して、作業に着手しているとします。

f:id:flying-foozy:20111204230231j:image

何かの契機で、共有リポジトリからの hg pull だけは行ったとします。

f:id:flying-foozy:20111204230232j:image

この時点で、作業のベースとなっているリビジョン 1 にも、コミット未実施な作業領域にも .hgeol ファイルは無いのですが、tip であるリビジョン 2 には .hgeol ファイルが記録されています。

そのため、この状況で作業領域の成果をコミットすると、2つ目の例外ケースとして、リビジョン 2 での .hgeol ファイルの内容を反映した改行変換が実施されます。

f:id:flying-foozy:20111204230233j:image

ここまでの挙動は「ちょっと便利」程度に考えても良いのですが、この先の作業を考えると注意が必要です。

リビジョン 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 設定があります。

  1. .hgeol ファイルの [repository] セクションで記述する native 設定
  2. .hgrc 等の設定ファイルの [eol] セクションで記述する native 設定

前者は、「作業領域 ⇒ 履歴」方向で適用される改行変換において、NATIVE 設定の場合に適用される改行コードを、後者は逆方向の「履歴 ⇒ 作業領域」での NATIVE 設定に適用される改行変換を指定するものです。

一見すると、同じような内容の設定記述が、複数の場所に散在しているように見えるかもしれません。

しかし、「作業領域 ⇒ 履歴」で適用される改行変換設定は、リポジトリ格納時の一貫性に関わるものですから、複製先リポジトリ間で共有される .hgeol で定義できた方が嬉しい筈です。

その一方で、「履歴 ⇒ 作業領域」で適用される設定は、利用者の環境に応じて任意に設定できる設定ファイル (${HOME}/.hgrc.hg/hgrc) において記述できなければ意味がありません。

つまり、「リポジトリ格納時の一貫性」と「利用環境に応じた作業領域取り出し」という点で、両者は全く異なるレイヤーに属することから、記述箇所が異なるわけです。

ちなみに、.hgeol[repository] セクションにおける native はデフォルト値が LF で固定ですが、設定フィルの [eol] セクションにおけるデフォルト値は、実行環境に応じて異なります (Windows 環境なら CRLF、Unix 系環境なら LF)。

# pyhon の os.linesep 値が使用されます

はてなユーザーのみコメントできます。はてなへログインもしくは新規登録をおこなってください。

トラックバック - http://d.hatena.ne.jp/flying-foozy/20111205/1323065152