| 1 | ;;; magit-reflog.el --- Inspect ref history -*- lexical-binding:t -*- |
| 2 | |
| 3 | ;; Copyright (C) 2008-2026 The Magit Project Contributors |
| 4 | |
| 5 | ;; Author: Jonas Bernoulli <emacs.magit@jonas.bernoulli.dev> |
| 6 | ;; Maintainer: Jonas Bernoulli <emacs.magit@jonas.bernoulli.dev> |
| 7 | |
| 8 | ;; SPDX-License-Identifier: GPL-3.0-or-later |
| 9 | |
| 10 | ;; Magit is free software: you can redistribute it and/or modify it |
| 11 | ;; under the terms of the GNU General Public License as published by |
| 12 | ;; the Free Software Foundation, either version 3 of the License, or |
| 13 | ;; (at your option) any later version. |
| 14 | ;; |
| 15 | ;; Magit is distributed in the hope that it will be useful, but WITHOUT |
| 16 | ;; ANY WARRANTY; without even the implied warranty of MERCHANTABILITY |
| 17 | ;; or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public |
| 18 | ;; License for more details. |
| 19 | ;; |
| 20 | ;; You should have received a copy of the GNU General Public License |
| 21 | ;; along with Magit. If not, see <https://www.gnu.org/licenses/>. |
| 22 | |
| 23 | ;;; Commentary: |
| 24 | |
| 25 | ;; This library implements support for looking at Git reflogs. |
| 26 | |
| 27 | ;;; Code: |
| 28 | |
| 29 | (require 'magit-core) |
| 30 | (require 'magit-log) |
| 31 | |
| 32 | ;;; Options |
| 33 | |
| 34 | (defcustom magit-reflog-limit 256 |
| 35 | "Maximal number of entries initially shown in reflog buffers. |
| 36 | The limit in the current buffer can be changed using \"+\" |
| 37 | and \"-\"." |
| 38 | :package-version '(magit . "3.0.0") |
| 39 | :group 'magit-commands |
| 40 | :type 'number) |
| 41 | |
| 42 | (defcustom magit-reflog-margin |
| 43 | (list (nth 0 magit-log-margin) |
| 44 | (nth 1 magit-log-margin) |
| 45 | 'magit-log-margin-width nil |
| 46 | (nth 4 magit-log-margin)) |
| 47 | "Format of the margin in `magit-reflog-mode' buffers. |
| 48 | |
| 49 | The value has the form (INIT STYLE WIDTH AUTHOR AUTHOR-WIDTH). |
| 50 | |
| 51 | If INIT is non-nil, then the margin is shown initially. |
| 52 | STYLE controls how to format the author or committer date. |
| 53 | It can be one of `age' (to show the age of the commit), |
| 54 | `age-abbreviated' (to abbreviate the time unit to a character), |
| 55 | or a string (suitable for `format-time-string') to show the |
| 56 | actual date. Option `magit-log-margin-show-committer-date' |
| 57 | controls which date is being displayed. |
| 58 | WIDTH controls the width of the margin. This exists for forward |
| 59 | compatibility and currently the value should not be changed. |
| 60 | AUTHOR controls whether the name of the author is also shown by |
| 61 | default. |
| 62 | AUTHOR-WIDTH has to be an integer. When the name of the author |
| 63 | is shown, then this specifies how much space is used to do so." |
| 64 | :package-version '(magit . "2.9.0") |
| 65 | :group 'magit-log |
| 66 | :group 'magit-margin |
| 67 | :type magit-log-margin--custom-type |
| 68 | :initialize #'magit-custom-initialize-reset |
| 69 | :set-after '(magit-log-margin) |
| 70 | :set (apply-partially #'magit-margin-set-variable 'magit-reflog-mode)) |
| 71 | |
| 72 | ;;; Faces |
| 73 | |
| 74 | (defface magit-reflog-commit '((t :foreground "green")) |
| 75 | "Face for commit commands in reflogs." |
| 76 | :group 'magit-faces) |
| 77 | |
| 78 | (defface magit-reflog-amend '((t :foreground "magenta")) |
| 79 | "Face for amend commands in reflogs." |
| 80 | :group 'magit-faces) |
| 81 | |
| 82 | (defface magit-reflog-merge '((t :foreground "green")) |
| 83 | "Face for merge, checkout and branch commands in reflogs." |
| 84 | :group 'magit-faces) |
| 85 | |
| 86 | (defface magit-reflog-checkout '((t :foreground "blue")) |
| 87 | "Face for checkout commands in reflogs." |
| 88 | :group 'magit-faces) |
| 89 | |
| 90 | (defface magit-reflog-reset '((t :foreground "red")) |
| 91 | "Face for reset commands in reflogs." |
| 92 | :group 'magit-faces) |
| 93 | |
| 94 | (defface magit-reflog-rebase '((t :foreground "magenta")) |
| 95 | "Face for rebase commands in reflogs." |
| 96 | :group 'magit-faces) |
| 97 | |
| 98 | (defface magit-reflog-cherry-pick '((t :foreground "green")) |
| 99 | "Face for cherry-pick commands in reflogs." |
| 100 | :group 'magit-faces) |
| 101 | |
| 102 | (defface magit-reflog-remote '((t :foreground "cyan")) |
| 103 | "Face for pull and clone commands in reflogs." |
| 104 | :group 'magit-faces) |
| 105 | |
| 106 | (defface magit-reflog-other '((t :foreground "cyan")) |
| 107 | "Face for other commands in reflogs." |
| 108 | :group 'magit-faces) |
| 109 | |
| 110 | ;;; Commands |
| 111 | |
| 112 | ;;;###autoload |
| 113 | (defun magit-reflog-current () |
| 114 | "Display the reflog of the current branch. |
| 115 | If `HEAD' is detached, then show the reflog for that instead." |
| 116 | (interactive) |
| 117 | (magit-reflog-setup-buffer (or (magit-get-current-branch) "HEAD"))) |
| 118 | |
| 119 | ;;;###autoload |
| 120 | (defun magit-reflog-other (ref) |
| 121 | "Display the reflog of a branch or another ref." |
| 122 | (interactive (list (magit-read-local-branch-or-ref "Show reflog for"))) |
| 123 | (magit-reflog-setup-buffer ref)) |
| 124 | |
| 125 | ;;;###autoload |
| 126 | (defun magit-reflog-head () |
| 127 | "Display the `HEAD' reflog." |
| 128 | (interactive) |
| 129 | (magit-reflog-setup-buffer "HEAD")) |
| 130 | |
| 131 | ;;; Mode |
| 132 | |
| 133 | (defvar-keymap magit-reflog-mode-map |
| 134 | :doc "Keymap for `magit-reflog-mode'." |
| 135 | :parent magit-log-mode-map |
| 136 | "C-c C-n" #'undefined |
| 137 | "L" #'magit-margin-settings) |
| 138 | |
| 139 | (define-derived-mode magit-reflog-mode magit-mode "Magit Reflog" |
| 140 | "Mode for looking at Git reflog. |
| 141 | |
| 142 | This mode is documented in info node `(magit)Reflog'. |
| 143 | |
| 144 | \\<magit-mode-map>\ |
| 145 | Type \\[magit-refresh] to refresh the current buffer. |
| 146 | Type \\[magit-visit-thing] or \\[magit-diff-show-or-scroll-up] \ |
| 147 | to visit the commit at point. |
| 148 | |
| 149 | Type \\[magit-cherry-pick] to apply the commit at point. |
| 150 | Type \\[magit-reset] to reset `HEAD' to the commit at point. |
| 151 | |
| 152 | \\{magit-reflog-mode-map}" |
| 153 | :interactive nil |
| 154 | :group 'magit-log |
| 155 | (magit-hack-dir-local-variables) |
| 156 | (setq magit--imenu-item-types 'commit)) |
| 157 | |
| 158 | (defun magit-reflog-setup-buffer (ref) |
| 159 | (require 'magit) |
| 160 | (magit-setup-buffer #'magit-reflog-mode nil |
| 161 | (magit-buffer-refname ref) |
| 162 | (magit-buffer-log-args (list (format "-n%s" magit-reflog-limit))))) |
| 163 | |
| 164 | (defun magit-reflog-refresh-buffer () |
| 165 | (magit-set-header-line-format (concat "Reflog for " magit-buffer-refname)) |
| 166 | (magit-insert-section (reflogbuf) |
| 167 | (magit-git-wash (apply-partially #'magit-log-wash-log 'reflog) |
| 168 | "reflog" "show" "--format=%h%x00%aN%x00%gd%x00%gs" "--date=raw" |
| 169 | magit-buffer-log-args magit-buffer-refname "--"))) |
| 170 | |
| 171 | (cl-defmethod magit-buffer-value (&context (major-mode magit-reflog-mode)) |
| 172 | magit-buffer-refname) |
| 173 | |
| 174 | (defvar magit-reflog-labels |
| 175 | '(("commit" . magit-reflog-commit) |
| 176 | ("amend" . magit-reflog-amend) |
| 177 | ("merge" . magit-reflog-merge) |
| 178 | ("checkout" . magit-reflog-checkout) |
| 179 | ("branch" . magit-reflog-checkout) |
| 180 | ("reset" . magit-reflog-reset) |
| 181 | ("rebase" . magit-reflog-rebase) |
| 182 | ("rewritten" . magit-reflog-rebase) |
| 183 | ("cherry-pick" . magit-reflog-cherry-pick) |
| 184 | ("initial" . magit-reflog-commit) |
| 185 | ("pull" . magit-reflog-remote) |
| 186 | ("clone" . magit-reflog-remote) |
| 187 | ("autosave" . magit-reflog-commit) |
| 188 | ("restart" . magit-reflog-reset))) |
| 189 | |
| 190 | (defun magit-reflog-format-subject (subject) |
| 191 | (let* ((match (string-match magit-reflog-subject-re subject)) |
| 192 | (command (and match (match-str 1 subject))) |
| 193 | (option (and match (match-str 2 subject))) |
| 194 | (type (and match (match-str 3 subject))) |
| 195 | (label (if (string= command "commit") |
| 196 | (or type command) |
| 197 | command)) |
| 198 | (text (if (string= command "commit") |
| 199 | label |
| 200 | (string-join (delq nil (list command option type)) " ")))) |
| 201 | (format "%-16s " |
| 202 | (magit--propertize-face |
| 203 | text (or (cdr (assoc label magit-reflog-labels)) |
| 204 | 'magit-reflog-other))))) |
| 205 | |
| 206 | ;;; _ |
| 207 | (provide 'magit-reflog) |
| 208 | ;; Local Variables: |
| 209 | ;; read-symbol-shorthands: ( |
| 210 | ;; ("and$" . "cond-let--and$") |
| 211 | ;; ("thread$" . "cond-let--thread$") |
| 212 | ;; ("when$" . "cond-let--when$") |
| 213 | ;; ("and-let*" . "cond-let--and-let*") |
| 214 | ;; ("and-let" . "cond-let--and-let") |
| 215 | ;; ("if-let*" . "cond-let--if-let*") |
| 216 | ;; ("if-let" . "cond-let--if-let") |
| 217 | ;; ("when-let*" . "cond-let--when-let*") |
| 218 | ;; ("when-let" . "cond-let--when-let") |
| 219 | ;; ("while-let*" . "cond-let--while-let*") |
| 220 | ;; ("while-let" . "cond-let--while-let") |
| 221 | ;; ("match-string" . "match-string") |
| 222 | ;; ("match-str" . "match-string-no-properties")) |
| 223 | ;; End: |
| 224 | ;;; magit-reflog.el ends here |