$shibayu36->blog;

株式会社はてなでエンジニアをしています。プログラミングや読書のことなどについて書いています。

emacs内でgit grepする方法

コードを書いているとプロジェクト内のコードを参考にしたかったり、一括で置換したかったりします。そんな時emacs内でgrepを使うのですが、プロジェクトがそれなりに大きくなると非常に遅くなります。gitのプロジェクトであれば、gitであればgit grepを使えば高速に検索できるのでそれを使えば良いと思い、やってみました。

letでgrep-find-commandを書き換える

Emacs内からgit-grep - delirious thoughts にletでgrep-find-commandを書き換えた上で、grep-find関数を呼び出すと出来るというふうに書いてあったので、やってみたのですが、なぜかうまくいきませんでした。うーん。

grep-apply-settingを使う

emacsのgrep-commandを変更する - すぎゃーんメモ と同じように、grep-find-commandをgrep-apply-settingコマンドで書き換えるという手もあります。例えば以下のように設定しておいて、M-x grep-findとかするとgit grep出来るようになります。

(grep-apply-setting 'grep-find-command "PAGER='' git grep -I -n -i -e ")

ただ、この方法の場合、globalな変数を置き換えてしまうため、普通にgrepしたいときにできなくなってしまいます。gitのプロジェクト触ってる時にそうじゃないところを検索したいときにできなくなります。また、ディレクトリを気軽に指定できないという問題もありました。

grep関数を使って自由に呼び出してあげる

で、結局自分で定義してみることになりました。grep関数に適切な出力を行うコマンドを与えてあげると、出力結果をgrep-modeで出力してくれるということが分かったので以下のようにしてみました。

まずgitのrootとかを取得する関数。

(defun chomp (str)
  (replace-regexp-in-string "[\n\r]+$" "" str))

;;; git
(defun git-project-p ()
  (string=
   (chomp
    (shell-command-to-string "git rev-parse --is-inside-work-tree"))
   "true"))

(defun git-root-directory ()
  (cond ((git-project-p)
         (chomp
          (shell-command-to-string "git rev-parse --show-toplevel")))
        (t
         "")))

git grepの定義。

(defun git-grep (grep-dir command-args)
  (interactive
   (let ((root (concat (git-root-directory) "/")))
     (list
      (read-file-name
       "Directory for git grep: " root root t)
      (read-shell-command
            "Run git-grep (like this): "
            (format "PAGER='' git grep -I -n -i -e %s"
                    "")
            'git-grep-history))))
  (let ((grep-use-null-device nil)
        (command
         (format (concat
                  "cd %s && "
                  "%s")
                 grep-dir
                 command-args)))
    (grep command)))

これを定義しておいて、M-x git-grepを呼ぶと以下のように最初にgitのプロジェクトルートのディレクトリが表示されて、そこから検索したいディレクトリを指定します。
f:id:shiba_yu36:20130202005335p:plain

そのあとgit grepで検索したい文字を入力します。
f:id:shiba_yu36:20130202005408p:plain

そうするとgit grepで検索された結果がgrep-modeで表示されます。
f:id:shiba_yu36:20130202005446p:plain

これでgit grepemacs上で行えるようになりました。さらにwgrep.elとかを使うと一括置換とかできるので便利ですね。

今回の技術的tips

grep関数にコマンド渡せば色々なgrepを実装できる

今回のようにgrepのような出力をするコマンドをgrep関数に与えてあげれば、自分で好きなgrepを作れます。

read-file-nameを使ってファイル名を読み込む

read-file-nameを使うとファイル名を置換しながら読み込むことが出来ます。使い方は以下のとおり。

(read-file-name prompt &optional directory default existing initial)

promptを指定してあげて、最初に出ているdirectoryを指定して、何も入力がなかった時のdefaultを指定してあげる感じですね。existingをtにしていると、ファイルが存在しているかどうかもチェックします。

今回の場合はgit rootを渡してあげて、存在確認をしています。

(read-file-name
 "Directory for git grep: " root root t)

まとめ

今回はemacs上でgit grepを使う方法をまとめてみました。結構適当にやったのでもしかしたらもう少し上手い方法があるかもしれません。