(or emacs irrelevant)

Grep in a git repository using ivy

Just got this request a few minutes ago, and now this feature is in the swiper repository (available as counsel from MELPA).

The motivation was to write an ivy equivalent of helm-git-grep. I didn't use this feature before, but the only thing that I needed to get me started was this shell command:

git --no-pager grep --full-name -n --no-color -i -e foobar

The rest of the code (just 20 lines) followed all by itself:

(defun counsel-git-grep-function (string &optional _pred &rest _u)
  "Grep in the current git repository for STRING."
  (split-string
   (shell-command-to-string
    (format
     "git --no-pager grep --full-name -n --no-color -i -e \"%s\""
     string))
   "\n"
   t))

(defun counsel-git-grep ()
  "Grep for a string in the current git repository."
  (interactive)
  (let ((default-directory (locate-dominating-file
                             default-directory ".git"))
        (val (ivy-read "pattern: " 'counsel-git-grep-function))
        lst)
    (when val
      (setq lst (split-string val ":"))
      (find-file (car lst))
      (goto-char (point-min))
      (forward-line (1- (string-to-number (cadr lst)))))))

Thanks to the push from Stefan Monnier, ivy-read also supports a function to be passed instead of a static collection of strings. In this case, it's counsel-git-grep-function that basically takes one argument: the thing that we're looking for.

After this, shell-command-to-string is my go-to function to quickly bring some shell output into Elisp. As you can see, it's enough to pass it a shell command in a string form to get a string response. I transform the response into a list of line strings with split-string, making sure to pass the t argument to avoid empty strings.

One final trick that you can learn for your own Elisp programming is the let / default-directory / (locate-dominating-file default-directory ".git") combo. It's quite useful for dealing with git shell commands. And that's it, it only remains to open a file and jump to the selected line.

I think that counsel-git-grep might complement and slightly displace rgrep or ag in my setup. So I've given it a nice binding:

(global-set-key (kbd "C-c j") 'counsel-git-grep)

I hope that I've made a good case of how easy it is to quickly write something in Elisp, especially if it's just a shell command wrapper. So if you're on the fence of whether to learn Elisp or not, do yourself a favor and learn it: it pays off quickly and is a lot of fun.

Side note: I've mentioned \bfun\b in this blog 182 times, mostly as a variable representing a function (courtesy of counsel-git-grep).