This is a continuation of my post Using Eldoc with Magit where I've shown how to define a function to be added to eldoc-documentation-functions in Magit status or log buffers which shows the author and diffstat of the commit under point. That function has served me well until it started to produce hangs in emacs at work.

The hangs began when I've started working with a branch which implements a major architectural restructuring of great parts of our code-base at work. That branch has been created more than two years ago and it has thousands of commits not in other branches. However, there's a merge cascade so that all fixes to release branches will eventually also be merged into that branch.

As it turned out, git show can become quite slow (up to several seconds) when invoked on a merge commit between very divergent branches. So whenever point was on a merge commit, Eldoc would run my function doing a git show shell command and block until that finished.

Long story short: Eldoc in Emacs 28.1 and above has support for asynchronous documentation functions in terms that a documentation function now receives a callback function which it should call with the documentation string once it's computed. In my last post, I've just ignored it.

So without further ado, here's the workhorse function which given a callback and a commit (which actually might be any object git show can operate on) computes the author/date/diffstat information asynchronously and then calls callback with it.

(defvar th/eldoc-git-show-stat--process nil)
(defun th/eldoc-git-show-stat (callback commit)
  "Compute diffstat for COMMIT asynchronously, then call CALLBACK with it."
  ;; Kill the possibly still running old process and its buffer.
  (when (processp th/eldoc-git-show-stat--process)
    (let ((buf (process-buffer th/eldoc-git-show-stat--process)))
      (when (process-live-p th/eldoc-git-show-stat--process)
        (let (confirm-kill-processes)
          (kill-process th/eldoc-git-show-stat--process)))
      (when (buffer-live-p buf)
        (kill-buffer buf))))

  ;; Spawn a new "git show" process.
  (let* ((cmd (list "git" "--no-pager" "show"
                    "--no-color"
                    ;; Author Name <author@email.com>, <date-and-time>
                    "--format=format:%an <%ae>, %aD"
                    "--stat=80"
                    commit)))
    ;; An async eldoc-documentation-function must also return a non-nil,
    ;; non-string result if it's applicable for computing a documentation
    ;; string, so we set and return the new process here.
    (setq th/eldoc-git-show-stat--process
          (make-process
           :name "eldoc-git-show"
           :buffer (generate-new-buffer " *git-show-stat*")
           :noquery t
           :command cmd
           :sentinel (lambda (proc event)
                       (when (eq (process-status proc) 'exit)
                         (with-current-buffer (process-buffer proc)
                           (goto-char (point-min))
                           (put-text-property (point-min)
                                              (line-end-position)
                                              'face 'bold)
                           (funcall callback (buffer-string)))))))))

To use the above function as an eldoc documentation function in Magit status and log buffers, the following function th/magit-eldoc-for-commit is defined and added buffer-locally to buffers of the respective magit modes.

(defvar th/magit-eldoc-last-commit nil)
(defun th/magit-eldoc-for-commit (callback)
  (let ((commit (magit-commit-at-point)))
    (when (and commit
               (not (equal commit th/magit-eldoc-last-commit)))
      (setq th/magit-eldoc-last-commit commit)
      (th/eldoc-git-show-stat callback commit))))

(defun th/magit-eldoc-setup ()
  (add-hook 'eldoc-documentation-functions
            #'th/magit-eldoc-for-commit nil t))

(add-hook 'magit-status-mode-hook #'th/magit-eldoc-setup)
(add-hook 'magit-log-mode-hook #'th/magit-eldoc-setup)

So with these changes in place, operating in Magit buffers containing merge commits of very diverged branches won't hang my Emacs anymore.

Screenshot of eldoc showing commit author, date, and diffstat

What a relief!