彷徨えるフジワラ

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

Mercurial の osutil.listdir() における "skip" の位置付け

完全に内部実装固有の話なので、備忘録以上の意味は無いエントリです(笑)。

Mercurial の実装では、ディレクトリ配下の要素一覧と各要素の stat(2) 情報を一括で取得する機能として、要素一覧しか取得できない Python 標準の os.listdir() とは別に、独自の [http://selenic.com/repo/hg/file/af12f58e2aa0/mercurial/pure/osutil.py#l28:title=osutil.listdir()] 関数を用意しています。

def listdir(path, stat=False, skip=None):
    〜〜〜〜
    names = os.listdir(path)
    names.sort()
    for fn in names:
        st = os.lstat(prefix + fn)
        if fn == skip and statmod.S_ISDIR(st.st_mode):
            return [] # (※) 処理の中断
        if stat:
            result.append((fn, _mode_to_kind(st.st_mode), st))
        else:
            result.append((fn, _mode_to_kind(st.st_mode)))
    return result

osutil.listdir()skip 引数は、てっきり「列挙時に除外したい対象」を指定するものだと思っていたのですが、上記の処理フローを見ると、どうもそういった用途ではないようです。「指定対象の除外」であれば、skip 引数と一致する対象を見つけた場合の処理 (※) は、return [] ではなく continue になる筈ですね。

通常インストール時には、pure Python 版ではなく C 実装版osutil.listdir() が使用されるのですが、そちらでも同様な処理フローになっているので、pure Python 版側の問題というわけでもなさそうです。

        /* quit early? */
        if (skip && kind == S_IFDIR && !strcmp(ent->d_name, skip)) {
            ret = PyList_New(0);
            goto error;
        }

このまま osutil.listdir() 側を眺めていても埒があかないので、今度は osutil.listdir() の呼び出し元側の調査からアプローチしてみましょう。

Mercurialソースコード中で skip 引数を明示している osutil.listdir() 呼び出しは、dirstate.walk() 内における .hg を指定した以下の呼び出しだけでした。

            skip = None
            if nd == '.':
                nd = ''       # (1)
            else:
                skip = '.hg'  # (2)
            try:
                entries = listdir(join(nd), stat=True, skip=skip)

悩む事暫し………あ!わかった!

上記の呼び出し前判定処理では、以下のような処理の切り分けを行っています。

  1. 現行ディレクトリ (= ".") 直下の場合は、skip 指定無し
  2. それ以外(=サブディレクトリ配下)の場合は、skip.hg を指定

後者のケースで、且つ skip 対象である「.hg が指定ディレクトリ配下に 存在」するということは、そのディレクトリは「別の Mercurial リポジトリの作業領域」であることを意味します。

そのようなディレクトリは、当然上位階層側ディレクトリにあるリポジトリの管轄ではありません。作業領域の状態を管理する dirstate にとっては、当該ディレクトリ配下要素の一覧自体が不要です。

但し、呼び出し元での処理は中断する必要がありませんから、例外等での中断は不適切です。結果として、skip との一致を検出した場合の処理は return [] が妥当と言えるでしょう。

結論としては、osutil.listdir()skip 引数は、"skip specified one" ではなく、"skip list up itself if specified one is found" という意味を持っている、といったところでしょうか。