Informative diff hunks for Emacs Lisp and Org

Update 2021-01-26 20:30 +0200: Fixed link to dotfiles.
Update 2021-01-27 13:55 +0200: Tweaked Org regexp to avoid bold text at the beginning of the line.
Update 2021-01-27 16:39 +0200: Added Annex.
Update 2023-02-22 20:47 +0200: Hugo suggests to include the #+title keyword for Org.

Today I learnt how to instruct git to read the syntactically relevant beginning of the given context when producing diff hunk headings. My intent is to improve the output for Emacs Lisp and Org mode files.

The diff hunk heading is the text that is appended to the lines of a given change. You must have noticed those:

@@ -210,7 +210,7 @@ TEXT OF THE HEADING

By default, the text for Elisp buffers is not particularly informative. For example:

 emacs/.emacs.d/straight/repos/prot-lisp/prot-simple.el | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/emacs/.emacs.d/straight/repos/prot-lisp/prot-simple.el b/emacs/.emacs.d/straight/repos/prot-lisp/prot-simple.el
index 318bb63e..3ea711f9 100644
--- a/emacs/.emacs.d/straight/repos/prot-lisp/prot-simple.el
+++ b/emacs/.emacs.d/straight/repos/prot-lisp/prot-simple.el
@@ -210,7 +210,7 @@ with the specified date."
   (interactive "P")
   (let* ((date prot-simple-date-specifier)
          (time prot-simple-time-specifier)
-         (format (if arg (format "%s %s" date time) date)))
+         (format (if arg (format "%s %s" date time) date))) ; This is a test
     (when (use-region-p)
       (delete-region (region-beginning) (region-end)))
     (insert (format-time-string format))))

The heading with the specified date." does not really enlighten us as to what function is touched by this change. Whereas with my newfound knowledge I get this:

 emacs/.emacs.d/straight/repos/prot-lisp/prot-simple.el | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/emacs/.emacs.d/straight/repos/prot-lisp/prot-simple.el b/emacs/.emacs.d/straight/repos/prot-lisp/prot-simple.el
index 318bb63e..3ea711f9 100644
--- a/emacs/.emacs.d/straight/repos/prot-lisp/prot-simple.el
+++ b/emacs/.emacs.d/straight/repos/prot-lisp/prot-simple.el
@@ -210,7 +210,7 @@ (defun prot-simple-inset-date (&optional arg)
   (interactive "P")
   (let* ((date prot-simple-date-specifier)
          (time prot-simple-time-specifier)
-         (format (if arg (format "%s %s" date time) date)))
+         (format (if arg (format "%s %s" date time) date))) ; This is a test
     (when (use-region-p)
       (delete-region (region-beginning) (region-end)))
     (insert (format-time-string format))))

The (defun prot-simple-inset-date (&optional arg) is insightful as it lets us understand in more precise terms the context of the relevant change.

Same principle for Org, where it will read the heading under which the diff occurs:

 emacs/.emacs.d/emacs-init.org | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/emacs/.emacs.d/emacs-init.org b/emacs/.emacs.d/emacs-init.org
index efe0d8a7..d71f8f8d 100644
--- a/emacs/.emacs.d/emacs-init.org
+++ b/emacs/.emacs.d/emacs-init.org
@@ -3359,6 +3359,8 @@ *** Version control framework (vc.el and prot-vc.el)
 all basic versioning needs.  It however never stands as Magit's peer
 when it comes to the sheer coverage of Git features.
 
+This is a test.
+
 To my mind, VC and Magit can be used as part of the same setup.  Employ
 the former for common tasks such as viewing diffs and logs, committing
 changes in bulk, pushing and pulling from a remote.  And let Magit

Got the correct heading: *** Version control framework (vc.el and prot-vc.el). Much better!

Git config and attributes files

To get things to work we need to create ~/.config/git/attributes and add at least the following:

*.lisp  diff=lisp
*.el    diff=lisp
*.org   diff=org

Then, in ~/.config/git/config append:

[diff "lisp"]
  xfuncname = "^(\\(.*)$"
[diff "org"]
  xfuncname = "^(\\*+ +.*)$"

And you should be good to go.

This is based on information I got from the manpages. The first version of the regular expressions comes from this page: https://gist.github.com/ruediger/5647207.

My Git config:

[user]
  name = Protesilaos Stavrou
  email = info@protesilaos.com
  signingkey = 99BD6459CD5CA3EA
[core]
  excludesfile = ~/.config/git/ignore
  attributesfile = ~/.config/git/attributes
[commit]
  gpgsign = true
[merge]
  conflictstyle = diff3
[pull]
  rebase = false
[format]
  thread = true
[diff "lisp"]
  xfuncname = "^(\\(.*)$"
[diff "org"]
  xfuncname = "^(\\*+ +.*)$"

Quality-of-life improvements

I am very happy with this otherwise minor tweak and am looking forward to learn more about optimising my computing environment. Git, in particular, is a powerful and comprehensive suite of tools that has a lot to offer. I have been reading through its manpages now that I am not a total novice (with either M-x man or M-x woman) and am discovering new information that can, among others, benefit my Emacs setup.

My git-related configurations are part of my dotfiles. That is where I also keep all my Emacs files: https://gitlab.com/protesilaos/dotfiles.

If some Emacs power user knows how to improve upon this setup, please do contact me: https://protesilaos.com/contact.

Annex

Gustavo Barros contacted me to share another take on the Elisp regexp. I am sharing it with permission. This matches outline-minor-mode comment headings, any top-level form at beginning of line, and some selected forms even when indented:

[diff "lisp"]
  xfuncname = "^(((;;;+ )|\\(|([ \t]+\\(((cl-|el-patch-)?def(un|var|macro|method|custom)|gb/))).*)$"

I like the idea and am excited to see what else we can do with this and other “hidden gems” of Git.


Hugo contacted me in private to suggested the following variant for Org, which captures the #+title keyword (typically placed at the top of the file):

[diff "org"]
   xfuncname = "^(\\*+ +.*|#\\+title:.*)$"

I am sharing this with Hugo’s permission. The relevant part from the message:

I read your article about informative git diff headers for lisp and org files recently. It’s great, but I noticed that changes in org files which are not under a headline (i.e. changes in an introduction or abstract written right at the beginning) have no informative header. I found that matching the title worked quite well: […]