anythingでgitリポジトリ内のファイルの全列挙をきちんとやる

anythingでgitリポジトリ内のファイルを列挙するなんていうのはやり尽くされている気がするけれど, きちんとやっているものは意外と少なかったので, フルスクラッチで書いた.

特徴

  • 現在開いているファイルと同一のgitリポジトリ内のファイルを列挙する
  • サブモジュール内のファイルも列挙できる
  • 列挙し直さなくていい場合は前に列挙した結果を使い回す
  • ファイルの列挙のためのgitコマンドの呼出しは非同期にやる
  • gitコマンドのエラー処理をきちんとしている

配布場所とインストール

インストールするには(helmではなく)anythingを入れた上で, anything-git-files.elをロードパスの通ったところに置く. el-getを使っている場合は以下のレシピを書いてel-get-install RET anything-git-files RETするのが簡単.

(:name anything-git-files
       :type github
       :pkgname "tarao/anything-git-files-el"
       :depends anything)

設定と使い方

設定ファイルで

(require 'anything-git-files)

とすると, M-x anything-git-filesでgitリポジトリ内のファイルを列挙できるようになる. デフォルトでは, 現在のリポジトリとそのサブモジュールについてそれぞれ, 変更のあったファイル, リポジトリで管理していないファイル, リポジトリ内の全ファイルを列挙する.

列挙に使う情報源を変更したい場合(anythingの他の情報源と組み合わせたい場合など)には, anything-git-files:modified-source, anything-git-files:untracked-source, anything-git-files:all-sourceを情報源として指定する. サブモジュールについてはanything-git-files:submodule-sources関数を(anything-git-files:submodule-sources '(modified untracked all))のように呼び出すと, 情報源のリストが返ってくる.

これらの情報源を使う場合は, gitリポジトリにいないとエラーになるので, 適宜anything-git-files:git-p関数でgitリポジトリにいるかどうかチェックするとよい.

gitリポジトリ情報源と他の情報源を組み合わせた関数の例:

(defun tarao/anything-for-files ()
  (interactive)
  (require 'anything-config)
  (require 'anything-git-files)
  (let* ((git-source (and (anything-git-files:git-p)
                          `(anything-git-files:modified-source
                            anything-git-files:untracked-source
                            anything-git-files:all-source
                            ,@(anything-git-files:submodule-sources 'all))))
         (other-source '(anything-c-source-recentf
                         anything-c-source-bookmarks
                         anything-c-source-files-in-current-dir+
                         anything-c-source-locate))
         (sources `(anything-c-source-buffers+
                    anything-c-source-ffap-line
                    anything-c-source-ffap-guesser
                    ,@git-source
                    ,@other-source)))
    (anything-other-buffer sources "*anything for files*")))

参考にしたやり方

最初のうちはid:yaotti作, id:shiba_yu36改のものをさらに改良したanything-git-project.elを使っていた. しばらく使ってみて, たまにgitコマンドがエラーになったときの挙動がおかしいような気がしたり, id:mechairoianything-git-ls-files.elがサブモジュール内のファイルも列挙できてうらやましくなったりした.

そこでanything-git-project.elを手直ししながらanything-git-ls-files.elの機能を取り込もうと思って実装の詳細を見てみたところ, どちらも毎回git ls-filesしていて非効率なのが気になった. まず, どちらの実装でもcandidates-in-bufferしているので, git ls-filesの結果をどこかのバッファに保持している. 本来はgit statusの出力に変化がない限りgit ls-filesしなおす必要はなく, 前に出力した内容を使い回せる. git statusの結果のハッシュ値リポジトリルートに関連づけて覚えておくことで, ファイルを実際に列挙する回数は大幅に減らすことができる.

細かいところとしては, vc-git.elのvc-git-command関数を使えばエラー処理なども適切にやってくれるし, --no-pagerも自動的につけてくれるので, gitコマンドはすべてvc-git-command経由でやるのがよい.

この辺りのことをanything-git-project.elに組込もうとすると, いちから書いた方が早そうだったので, フルスクラッチで実装した.

追記

現状はsecure-hash関数を使っているのでEmacs 24以降でしか動かないはず. SHA-1をとっているだけなので, secure-hashがなければsha1関数を使うようにすれば古いEmacsでも動くと思うのでそのうち直す. 済(8f19c87)

追記(2013-05-02T16:17+0000)

git ls-filesを非同期に実行するバージョンをこつこつと開発していたのが安定してきたのでマージした.