├── .gitignore ├── README.org ├── dired-git-info.el └── images └── screenshot2.png /.gitignore: -------------------------------------------------------------------------------- 1 | *.elc 2 | *-pkg.el 3 | *-autoloads.el 4 | -------------------------------------------------------------------------------- /README.org: -------------------------------------------------------------------------------- 1 | #+BEGIN_HTML 2 | GNU ELPA 3 | #+END_HTML 4 | 5 | * Description 6 | 7 | This Emacs packages provides a minor mode which shows git information inside 8 | the dired buffer: 9 | 10 | [[./images/screenshot2.png]] 11 | 12 | * Installation 13 | 14 | ** GNU ELPA 15 | 16 | This package is available on [[https://elpa.gnu.org][GNU ELPA]]. You can install it via =M-x package-install RET dired-git-info RET= 17 | 18 | ** Manual 19 | 20 | For manual installation, clone the repository and call: 21 | 22 | #+BEGIN_SRC elisp 23 | (package-install-file "/path/to/dired-git-info.el") 24 | #+END_SRC 25 | 26 | * Config 27 | 28 | ** Bind the minor mode command in dired 29 | 30 | #+BEGIN_SRC elisp 31 | (with-eval-after-load 'dired 32 | (define-key dired-mode-map ")" 'dired-git-info-mode)) 33 | #+END_SRC 34 | 35 | ** Don't hide normal Dired file info 36 | 37 | By default, toggling =dired-git-info-mode= also toggles the built-in 38 | =dired-hide-details-mode=, which hides file details such as ownership, 39 | permissions and size. This behaviour can be disabled by overriding 40 | =dgi-auto-hide-details-p=: 41 | 42 | #+BEGIN_SRC elisp 43 | (setq dgi-auto-hide-details-p nil) 44 | #+END_SRC 45 | 46 | ** Enable automatically in every Dired buffer (if in Git repository) 47 | 48 | To enable =dired-git-info-mode= whenever you navigate to a Git repository, use 49 | the following: 50 | #+BEGIN_SRC elisp 51 | (add-hook 'dired-after-readin-hook 'dired-git-info-auto-enable) 52 | #+END_SRC 53 | 54 | -------------------------------------------------------------------------------- /dired-git-info.el: -------------------------------------------------------------------------------- 1 | ;;; dired-git-info.el --- Show git info in dired -*- lexical-binding: t; -*- 2 | 3 | ;; Copyright (C) 2018-2019 Free Software Foundation, Inc. 4 | 5 | ;; Author: Clemens Radermacher 6 | ;; URL: https://github.com/clemera/dired-git-info 7 | ;; Version: 0.3.1 8 | ;; Package-Requires: ((emacs "25")) 9 | ;; Keywords: dired, files 10 | 11 | ;; This program is free software; you can redistribute it and/or modify 12 | ;; it under the terms of the GNU General Public License as published by 13 | ;; the Free Software Foundation, either version 3 of the License, or 14 | ;; (at your option) any later version. 15 | 16 | ;; This program is distributed in the hope that it will be useful, 17 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of 18 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 19 | ;; GNU General Public License for more details. 20 | 21 | ;; You should have received a copy of the GNU General Public License 22 | ;; along with this program. If not, see . 23 | 24 | ;;; Commentary: 25 | ;; 26 | ;; Minor mode which shows last commit message and date (info shown is 27 | ;; configurable) for git project files in dired. 28 | ;; 29 | 30 | ;;; Code: 31 | 32 | (require 'dired) 33 | 34 | (defgroup dired-git-info nil 35 | "Show git info in dired." 36 | :group 'files 37 | :prefix "dgi-") 38 | 39 | (defface dgi-commit-message-face 40 | '((t (:inherit font-lock-comment-face))) 41 | "Face for commit message overlays.") 42 | 43 | (defcustom dgi-auto-hide-details-p t 44 | "If details should get hidden automatically. 45 | 46 | Uses function `dired-hide-details-mode' to hide details when showing git 47 | info." 48 | :type 'boolean) 49 | 50 | (defcustom dgi-commit-message-format "%s\t%cr" 51 | "Format of the commit messages. 52 | 53 | Entries separated by tabs are aligned. Some common placeholders 54 | are (see git-log PRETTY FORMATS for all): 55 | 56 | · %H: commit hash 57 | 58 | · %h: abbreviated commit hash 59 | 60 | · %P: parent hashes 61 | 62 | · %p: abbreviated parent hashes 63 | 64 | · %an: author name 65 | 66 | · %ae: author email 67 | 68 | · %cd: committer date 69 | 70 | · %cr: committer date, relative 71 | 72 | · %cn: committer name 73 | 74 | · %ce: committer email 75 | 76 | · %s: subject 77 | 78 | · %f: sanitized subject line, suitable for a filename" 79 | :type 'string) 80 | 81 | (defvar-local dgi--commit-ovs nil 82 | "Overlays which show the commit messages.") 83 | 84 | (defvar dgi--restore-no-details nil 85 | "If no details view has to be restored.") 86 | 87 | (defun dgi--command-to-string (program &rest args) 88 | "Execute PROGRAM with arguments ARGS and return output string. 89 | 90 | If program returns non zero exit code return nil." 91 | (let* ((ecode nil) 92 | (output (with-output-to-string 93 | (with-current-buffer standard-output 94 | (setq ecode (apply #'process-file program nil t nil args)))))) 95 | (when (eq ecode 0) 96 | output))) 97 | 98 | 99 | (defun dgi--get-commit-info (&optional file gitf) 100 | "Get commit message info. 101 | 102 | FILE default to current dired file. GITF determines the commit 103 | info format and defaults to `dgi-commit-message-format'." 104 | (let* ((tfile (or file (dired-get-file-for-visit))) 105 | (file (or (file-remote-p tfile 'localname) tfile))) 106 | (when file 107 | (let ((msg (dgi--command-to-string 108 | "git" "log" "-1" 109 | (concat "--pretty=" 110 | (or gitf dgi-commit-message-format)) 111 | file))) 112 | (when (and msg (not (string= "" msg))) 113 | (substring msg 114 | ;; skip newline 115 | 0 -1)))))) 116 | 117 | 118 | (defmacro dgi--save-marked (&rest body) 119 | "Execute BODY and restore marked files afterwards." 120 | `(let ((marked (save-excursion 121 | (goto-char (point-min)) 122 | (dired-get-marked-files))) 123 | (inhibit-message t)) 124 | (save-excursion 125 | (unwind-protect 126 | (progn ,@body) 127 | (dired-unmark-all-marks) 128 | (dolist (file marked) 129 | (dired-goto-file file) 130 | (dired-mark 1)))))) 131 | 132 | 133 | (defun dgi--cleanup () 134 | "Remove commit overlays." 135 | (when dgi--restore-no-details 136 | (setq dgi--restore-no-details nil) 137 | (dired-hide-details-mode -1)) 138 | (dolist (ov dgi--commit-ovs) 139 | (delete-overlay ov)) 140 | (setq dgi--commit-ovs nil)) 141 | 142 | 143 | (defun dgi--get-dired-files-length (files) 144 | "Get list of lengths of all FILES as displayed by dired." 145 | (let ((dnames ())) 146 | (dolist (file files (nreverse dnames)) 147 | (push (dgi--get-dired-file-length file) 148 | dnames)))) 149 | 150 | 151 | (defun dgi--get-dired-file-length (file) 152 | "Get lengths of FILE as displayed by dired." 153 | (save-excursion 154 | (dired-goto-file file) 155 | (let ((opos (point))) 156 | (while (and (not (eolp)) 157 | (or (not dired-hide-details-mode) 158 | (not (get-text-property (point) 'invisible)))) 159 | (forward-char 1)) 160 | (length (buffer-substring opos (point)))))) 161 | 162 | 163 | (defun dgi--get-commit-messages (files) 164 | "Get formatted commit messages for FILES." 165 | (let ((messages ())) 166 | (dolist (file files) 167 | (push (dgi--get-commit-info file) 168 | messages)) 169 | (with-temp-buffer 170 | (dolist (message (nreverse messages)) 171 | (insert (or message "") "\n")) 172 | (let ((indent-tabs-mode nil)) 173 | (align-regexp (point-min) 174 | (point-max) 175 | "\\(\\s-*\\)\t" nil nil t)) 176 | (goto-char (point-min)) 177 | (while (search-forward "\t" nil t) 178 | (replace-match " ")) 179 | (split-string (buffer-string) "\n")))) 180 | 181 | 182 | (defun dgi--format-line-overlay (msg) 183 | "Format message MSG for current dired line." 184 | (let* ((le (line-end-position)) 185 | (aw (- (window-width) 186 | (1+ (save-excursion 187 | (goto-char le) 188 | (current-column)))))) 189 | (if (not (> aw 0)) 190 | "\n" 191 | (concat (dgi--clamp-string msg aw) 192 | "\n")))) 193 | 194 | (defun dgi--clamp-string (str max) 195 | "Return STRING truncated to MAX length if needed." 196 | (propertize 197 | (if (> (length str) max) 198 | (concat (substring str 0 199 | (- max (+ (length str) 3))) 200 | "...") 201 | str) 202 | 'face 'dgi-commit-message-face)) 203 | 204 | 205 | ;;;###autoload 206 | (define-minor-mode dired-git-info-mode 207 | "Toggle git message info in current dired buffer." 208 | :lighter " dgi" 209 | (if (not dired-git-info-mode) 210 | (dgi--cleanup) 211 | (unless (derived-mode-p 'dired-mode) 212 | (user-error "Not in a dired buffer")) 213 | (unless (locate-dominating-file "." ".git") 214 | (user-error "Not inside a git repository")) 215 | (when dgi-auto-hide-details-p 216 | (unless dired-hide-details-mode 217 | (setq dgi--restore-no-details t) 218 | (dired-hide-details-mode 1))) 219 | (let* ((files (dgi--save-marked 220 | (dired-unmark-all-marks) 221 | (dired-toggle-marks) 222 | (dired-get-marked-files))) 223 | (minspc (1+ (apply #'max (dgi--get-dired-files-length files)))) 224 | (messages (dgi--get-commit-messages files))) 225 | (save-excursion 226 | (dolist (file files) 227 | (let ((msg (pop messages))) 228 | (when msg 229 | (dired-goto-file file) 230 | (let ((spc (make-string 231 | (- minspc (dgi--get-dired-file-length file)) 232 | ?\s))) 233 | (goto-char (line-end-position)) 234 | (let ((ov (make-overlay (point) (1+ (point)))) 235 | (ovs (dgi--format-line-overlay (concat spc msg)))) 236 | (push ov dgi--commit-ovs) 237 | ;; I don't use after-string because I didn't get it to work 238 | ;; in combination with hl-line-mode overlay 239 | (overlay-put ov 'display ovs) 240 | ;; hl line mode should have priority 241 | (overlay-put ov 'priority -60)))))))))) 242 | 243 | ;;;###autoload 244 | (defun dired-git-info-auto-enable () 245 | "Enable `dired-git-info-mode' if current dired buffer is in a git repo. 246 | 247 | Add this function to `dired-after-readin-hook' to enable the mode 248 | automatically inside git repos." 249 | (when (locate-dominating-file "." ".git") 250 | (dired-git-info-mode))) 251 | 252 | 253 | (provide 'dired-git-info) 254 | ;;; dired-git-info.el ends here 255 | 256 | 257 | -------------------------------------------------------------------------------- /images/screenshot2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clemera/dired-git-info/91d57e3a4c5104c66a3abc18e281ee55e8979176/images/screenshot2.png --------------------------------------------------------------------------------