Skip to content

Instantly share code, notes, and snippets.

@scottjacobsen
Last active January 24, 2020 16:53
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save scottjacobsen/4693356 to your computer and use it in GitHub Desktop.
Save scottjacobsen/4693356 to your computer and use it in GitHub Desktop.
A more convenient git-grep. By default this will grep all files in the repository from the root of the git repository. I find these defaults more convenient than those used by vc-git-grep in the emacs version control library.
;; derived from rails-project:root from
;; https://github.com/remvee/emacs-rails
(defun git-root ()
"Return GIT_ROOT if this file is a part of a git repo,
else return nil"
(let ((curdir default-directory)
(max 10)
(found nil))
(while (and (not found) (> max 0))
(progn
(if (file-directory-p (concat curdir ".git"))
(progn
(setq found t))
(progn
(setq curdir (concat curdir "../"))
(setq max (- max 1))))))
(if found (expand-file-name curdir))))
;; Derived from `vc-git-grep'.
(defun git-grep (regexp &optional dir)
"Run git grep, searching for REGEXP in directory DIR.
DIR defaults to the root directory of the git project.
With \\[universal-argument] prefix, you can edit the constructed shell command line
before it is executed.
With two \\[universal-argument] prefixes, directly edit and run `grep-command'.
Collect output in a buffer. While git grep runs asynchronously, you
can use \\[next-error] (M-x next-error), or \\<grep-mode-map>\\[compile-goto-error] \
in the grep output buffer,
to go to the lines where grep found matches.
This command shares argument histories with \\[rgrep] and \\[grep]."
(interactive
(progn
(grep-compute-defaults)
(cond
((equal current-prefix-arg '(16))
(list (read-from-minibuffer "Run: " "git grep"
nil nil 'grep-history)
nil))
(t (let* ((regexp (grep-read-regexp))
(dir (read-directory-name "In directory: "
(git-root) default-directory t)))
(list regexp dir))))))
(require 'grep)
(when (and (stringp regexp) (> (length regexp) 0))
(let ((command regexp))
(if (string= command "git grep")
(setq command nil))
(setq dir (file-name-as-directory (expand-file-name dir)))
(setq command
(grep-expand-template "git grep -n -e <R>"
regexp))
(when command
(if (equal current-prefix-arg '(4))
(setq command
(read-from-minibuffer "Confirm: "
command nil nil 'grep-history))
(add-to-history 'grep-history command)))
(when command
(let ((default-directory dir)
(compilation-environment (cons "PAGER=" compilation-environment)))
;; Setting process-setup-function makes exit-message-function work
;; even when async processes aren't supported.
(compilation-start command 'grep-mode))
(if (eq next-error-last-buffer (current-buffer))
(setq default-directory dir))))))
@pcardune
Copy link

I had to change line 45 (the call to read-directory-name) to pass in (git-root) instead of default-directory. Otherwise if you just hit enter in the mini buffer to accept what was already there, it will use the current files directory rather than the git root.

Thanks for the code though! Super helpful!

@garyo
Copy link

garyo commented Apr 11, 2017

I agree with @pcardune, default-directory on line 45 isn't right. You can also just use nil there.
Seems like vc-git-grep should have an option to behave like this version. Surprising that it doesn't!

@mcallisterjp
Copy link

I also needed to add --no-pager to the git invocation at l. 54, or I get less-type output in the search buffer. Perhaps that's what l. 64 is intended to be for?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment