彷徨えるフジワラ

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

.hgignore における『無視対象に対する例外条件』指定の記述

Twitter 上で去年見かけた以下のエントリに関して:

正規表現で合致(=無視対象)パターンを事細かに書くのが面倒なのは、非常に共感できる話です。

年末時点の個人的見解としては、無視判定周りの実装が:

  1. .hgignore 等からパターンの読み込み
  2. 各パターン毎に正規表現コンパイル
  3. コンパイル済みパターンでループを回す
  4. 合致が見つかった時点で無視対象とみなす

といった感じであることを想定していたので、例えば "!" で始まるパターンを例外条件扱いにして:

  1. .hgignore 等からパターンの読み込み
  2. 各パターン毎に正規表現コンパイル
  3. コンパイル済みパターンでループを回す
  4. 合致が見つかったなら判定処理終了
    • "!" で始まるパターンなら『無視対象ではない』とみなす
    • それ以外なら『無視対象』とみなす

みたいな対応をするのが妥当かなぁ?などと考えていました。
しかし、改めて実装を確認してみると:

  1. .hgignore 等からパターンの読み込み
  2. 全てのパターンを OR 結合して単一正規表現パターン化
  3. 単一正規表現パターンのコンパイル
  4. 単一のコンパイル済みパターンで合致が検出されたなら、無視対象とみなす

という具合。

まぁ、指定パターンのループを Python 側で回すよりは、正規表現処理の内部で OR 判定した方が最適化が効き易い、という理由からであろうことは理解できます。

しかし、全ての指定パターンが OR 結合されることで、正規表現の合致判定が一括で実施されるため、特定のパターンへの合致をもって判定処理を打ち切る、などいう実装はできそうにありません。

もっとも、『冒頭 "!" で始まるパターンは例外条件扱い』 的な、新規文法を導入するとなると、旧バージョンの hg コマンドや、リポジトリ資産に対する互換性を考えて、.hgaccept とか .hgrecognize みたいな、別ファイルの導入を提案する必要があるので、これまたパッチを受け取ってもらえなさそうな予感……

というわけで、既存機能の範囲で『無視対象に対する例外条件』を指定するには、正規表現で頑張るしかないという結論になります。

合致パターンを正規表現で事細かに書くのは面倒だけど、付加的な例外条件の記述であれば、"(?!...)" 表記を使うことで、そこそこ妥当な複雑さの範囲で記述できそうな気がします

syntax: re
(?!^doc/).*\.txt$

上記の記述では、無視対象の判定条件は以下のようになります

  • ".*\.txt$" に合致し、且つ
  • "^doc/" に合致しない

".*\.txt$" に合致するパスに対する判定例を以下に示します。

パス無視
foo.txtする
src/doc/foo.txtする
doc/foo.txtしない
doc/bar/baz/foo.txtしない

2つ目のパスは、doc/ が冒頭にないために、例外条件である "^doc/" に合致しないことから、無視対象となります。

単一のパターンに例外条件を複数指定したい場合は、以下のように "|" を使った OR 結合で記述すれば良いでしょう。

syntax: re
(?!^(src|include)/).*\.[ch]$

資源効率を考えた場合、"(src|include)" ではなく "(?:src|include)" の方が良いのかな?

なお、"(?!....)" 表記は『先読み (look ahead)』扱いとなり、合致したとしても、判定位置は移動しません。例えば:

syntax: re
^a/(?!b00/)b[0-9][0-9]/.*\.txt$

上記の記述では、無視対象の判定条件は以下のようになります。

  • "^a/b[0-9][0-9]/.*\.txt$ と合致し、且つ
  • "^a/b00/" に合致しない

感覚的には以下のように理解しておけば良いのかな?

  • "(?!....)" 表記部分を除外した正規表現に合致し、且つ
  • "(?!....)" 表記の条件に合致しない

あくまで正規表現ベースなので、glob 系のパターン記述ほどは簡単ではないけれど、とりあえずは許容可能な範囲と言えそうです > "(?!....)" 表記

なお、.hgignore の記述は、リポジトリルートからの相対パスに対する部分一致で判定されるため、ディレクトリに対する条件記述の場合は、必要に応じて "^" を付けるのを忘れないようにしてください。詳細は以下を参照。