-
15 votes
-
Make Emacs write (part of) your git commit messages
I was fed up with the chores of writing consistent git commit messages, so a while ago I started developing a hook in Emacs which I used with Magit (actually git-commit-mode) which uses some crude...
I was fed up with the chores of writing consistent git commit messages, so a while ago I started developing a hook in Emacs which I used with Magit (actually
git-commit-mode
) which uses some crude heuristics to fill out theCOMMIT_EDITMSG
buffer for me. Here is what it does (|
stands for the cursor):-
If only a single file modified, insert
<filename>: |
- If can figure out function name, insert
<filename> (<functionname>): |
- If can figure out function name, insert
-
If only a single file added, insert
Add <filename>|
-
If a
TODO
added toReadme.org
, insert; TODO <headline>|
-
If a
TODO
wasDONE
, insert; DONE <headline>|
-
If the files are
Readme.org
andReadme.org_archive
, and no new TODO's were added anywhere, insert; Archive DONE|
-
If the file is
.gitignore
, insert; Ignore |
-
If the file is
TAGS
, insert; Update TAGS|
I extend this when I find new cases where I repeatedly do the same thing. The code is below. It's probably a good idea to use it as a starting point and personalise it because this reflects how I like to write my commit messages (and I like pretending how they do it over at Emacs git repo). It is sloppy and probably buggy, but I don't think it can be destructive.
Final note: I can't figure out how to set this up so that after this takes effect, the buffer is marked as modified. I want to flip the modified bit so that in some cases I can just hit
C-c C-c
and go. But I need to modify the buffer somehow to commit in some cases (I just typeC-o
to open a new line in those cases). Here is the function:(defun gk-git-commit-mode-hook () "Set up git commit buffer." ;; If a single file is modified, prefix the message w/ it. (let ((modified-re "^# modified:") (new-re "^# new file:") (issue-re "^[+\\- ]\\*+ \\(TODO\\|DONE\\) ") current-defun filename addp onlyp issuep) (save-excursion (with-current-buffer "COMMIT_EDITMSG" (goto-char (point-min)) (re-search-forward "^# Changes to be committed:" nil t) (forward-line) (beginning-of-line) (cond ((looking-at modified-re) (re-search-forward ": " nil t) (setf filename (thing-at-point 'filename t))) ((looking-at new-re) (re-search-forward ": " nil t) (setf filename (thing-at-point 'filename t) addp t))) (setq onlyp (progn (forward-line) (not (or (looking-at modified-re) (looking-at new-re))))) (when (and onlyp (equal filename "Readme.org")) (goto-char (point-min)) (when-let* ((pos (re-search-forward issue-re nil t))) (setq issuep (progn (re-search-backward "\\*" nil t) (buffer-substring (1+ (point)) (line-end-position)))))) ;; Try to set ‘current-defun’. (when onlyp (save-excursion (goto-char (point-min)) ;; Error if not found, means verbose diffs ;; not enabled. (re-search-forward "^diff --git") (goto-char (line-beginning-position)) (let ((str (buffer-substring (point) (point-max))) (default-directory (expand-file-name ".."))) (with-temp-buffer (insert str) (diff-mode) (goto-char (point-min)) (setq current-defun (diff-current-defun)))))))) (if onlyp (cond ((and issuep (not addp)) (goto-char (point-min)) (insert ";" issuep)) ((equal filename "TAGS") (goto-char (point-min)) (insert "; Update TAGS")) ((equal filename ".gitignore") (goto-char (point-min)) (insert "; Ignore ")) (filename (goto-char (point-min)) (if addp (insert "Add " filename) (insert filename (if (and current-defun) (format " (%s)" current-defun) "") ": ")))) (when (and (equal filename "Readme.org") (save-excursion (goto-char (point-min)) (re-search-forward (concat modified-re " +Readme.org_archive") nil t)) (save-excursion (goto-char (point-min)) (re-search-forward "\\-\\*+ DONE" nil t)) (not (save-excursion (goto-char (point-min)) (re-search-forward "\\+\\*[\\+\\-] TODO" nil t)))) (goto-char (point-min)) (insert "; Archive DONE"))))) (add-hook 'git-commit-mode-hook #'gk-git-commit-mode-hook)
Hope you find it useful.
12 votes -