| 1 | ;;; magit-log.el --- Inspect Git history -*- lexical-binding:t; coding:utf-8 -*- |
| 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 logs, including |
| 26 | ;; special logs like cherry-logs, as well as for selecting a commit |
| 27 | ;; from a log. |
| 28 | |
| 29 | ;;; Code: |
| 30 | |
| 31 | (require 'magit-core) |
| 32 | (require 'magit-diff) |
| 33 | |
| 34 | (declare-function magit-blob-visit "magit-files" (rev file)) |
| 35 | (declare-function magit-cherry-apply "magit-sequence" (commit &optional args)) |
| 36 | (declare-function magit-insert-head-branch-header "magit-status" |
| 37 | (&optional branch)) |
| 38 | (declare-function magit-insert-upstream-branch-header "magit-status" |
| 39 | (&optional branch pull keyword)) |
| 40 | (declare-function magit-read-file-from-rev "magit-files" |
| 41 | (rev prompt &optional default include-dirs)) |
| 42 | (declare-function magit-rebase--get-state-lines "magit-sequence" |
| 43 | (file)) |
| 44 | (declare-function magit-show-commit "magit-diff" |
| 45 | (arg1 &optional arg2 arg3 arg4)) |
| 46 | (declare-function magit-reflog-format-subject "magit-reflog" (subject)) |
| 47 | (defvar magit-refs-focus-column-width) |
| 48 | (defvar magit-refs-margin) |
| 49 | (defvar magit-refs-show-commit-count) |
| 50 | (defvar magit--right-margin-config) |
| 51 | (defvar magit-status-margin) |
| 52 | (defvar magit-status-sections-hook) |
| 53 | (defvar magit-status-use-buffer-arguments) |
| 54 | |
| 55 | (require 'ansi-color) |
| 56 | (require 'crm) |
| 57 | (require 'which-func) |
| 58 | |
| 59 | (make-obsolete-variable 'magit-log-highlight-keywords |
| 60 | 'magit-log-wash-summary-hook |
| 61 | "Magit 4.3.0") |
| 62 | |
| 63 | (make-obsolete-variable 'magit-log-format-message-function |
| 64 | 'magit-log-wash-summary-hook |
| 65 | "Magit 4.3.0") |
| 66 | |
| 67 | ;;; Options |
| 68 | ;;;; Log Mode |
| 69 | |
| 70 | (defgroup magit-log nil |
| 71 | "Inspect and manipulate Git history." |
| 72 | :link '(info-link "(magit)Logging") |
| 73 | :group 'magit-commands |
| 74 | :group 'magit-modes) |
| 75 | |
| 76 | (defcustom magit-log-mode-hook nil |
| 77 | "Hook run after entering Magit-Log mode." |
| 78 | :group 'magit-log |
| 79 | :type 'hook) |
| 80 | |
| 81 | (defcustom magit-log-remove-graph-args '("--follow" "-G" "-S" "-L") |
| 82 | "The log arguments that cause the \"--graph\" argument to be dropped. |
| 83 | |
| 84 | The default value lists the arguments that are incompatible with |
| 85 | \"--graph\" and therefore must be dropped when that is used. You |
| 86 | can add additional arguments that are available in `magit-log', |
| 87 | but I recommend that you don't do that. Nowadays I would define |
| 88 | this as a constant, but I am preserving it as an option, in case |
| 89 | someone actually customized it." |
| 90 | :package-version '(magit . "4.3.7") |
| 91 | :group 'magit-log |
| 92 | :type '(repeat (string :tag "Argument")) |
| 93 | :options '("--follow" "-G" "-S" "-L")) |
| 94 | |
| 95 | (defcustom magit-log-revision-headers-format "\ |
| 96 | %+b%+N |
| 97 | Author: %aN <%aE> |
| 98 | Committer: %cN <%cE>" |
| 99 | "Additional format string used with the `++header' argument." |
| 100 | :package-version '(magit . "3.2.0") |
| 101 | :group 'magit-log |
| 102 | :type 'string) |
| 103 | |
| 104 | (defcustom magit-log-auto-more nil |
| 105 | "Insert more log entries automatically when moving past the last entry. |
| 106 | Only considered when moving past the last entry with |
| 107 | `magit-goto-*-section' commands." |
| 108 | :group 'magit-log |
| 109 | :type 'boolean) |
| 110 | |
| 111 | (defcustom magit-log-margin '(t age magit-log-margin-width t 18) |
| 112 | "Format of the margin in `magit-log-mode' buffers. |
| 113 | |
| 114 | The value has the form (INIT STYLE WIDTH AUTHOR AUTHOR-WIDTH). |
| 115 | |
| 116 | If INIT is non-nil, then the margin is shown initially. |
| 117 | STYLE controls how to format the author or committer date. |
| 118 | It can be one of `age' (to show the age of the commit), |
| 119 | `age-abbreviated' (to abbreviate the time unit to a character), |
| 120 | or a string (suitable for `format-time-string') to show the |
| 121 | actual date. Option `magit-log-margin-show-committer-date' |
| 122 | controls which date is being displayed. |
| 123 | WIDTH controls the width of the margin. This exists for forward |
| 124 | compatibility and currently the value should not be changed. |
| 125 | AUTHOR controls whether the name of the author is also shown by |
| 126 | default. |
| 127 | AUTHOR-WIDTH has to be an integer. When the name of the author |
| 128 | is shown, then this specifies how much space is used to do so." |
| 129 | :package-version '(magit . "2.9.0") |
| 130 | :group 'magit-log |
| 131 | :group 'magit-margin |
| 132 | :type magit-log-margin--custom-type |
| 133 | :initialize #'magit-custom-initialize-reset |
| 134 | :set (apply-partially #'magit-margin-set-variable 'magit-log-mode)) |
| 135 | |
| 136 | (defcustom magit-log-margin-show-committer-date nil |
| 137 | "Whether to show the committer date in the margin. |
| 138 | |
| 139 | This option only controls whether the committer date is displayed |
| 140 | instead of the author date. Whether some date is displayed in |
| 141 | the margin and whether the margin is displayed at all is |
| 142 | controlled by other options." |
| 143 | :package-version '(magit . "3.0.0") |
| 144 | :group 'magit-log |
| 145 | :group 'magit-margin |
| 146 | :type 'boolean) |
| 147 | |
| 148 | (defcustom magit-log-show-refname-after-summary nil |
| 149 | "Whether to show refnames after commit summaries. |
| 150 | This is useful if you use really long branch names." |
| 151 | :package-version '(magit . "2.2.0") |
| 152 | :group 'magit-log |
| 153 | :type 'boolean) |
| 154 | |
| 155 | (defcustom magit-log-wash-summary-hook |
| 156 | (list #'magit-highlight-squash-markers |
| 157 | #'magit-highlight-bracket-keywords) |
| 158 | "Functions used to highlight parts of each individual commit summary. |
| 159 | |
| 160 | These functions are called in order, in a buffer containing the first |
| 161 | line of the commit message. They should set text properties as they see |
| 162 | fit, usually just `font-lock-face'. Before each function is called, |
| 163 | point is at the beginning of the buffer. |
| 164 | |
| 165 | See also the related `magit-revision-wash-message-hook'. You likely |
| 166 | want to use the same functions for both hooks." |
| 167 | :package-version '(magit . "4.3.0") |
| 168 | :group 'magit-log |
| 169 | :type 'hook |
| 170 | :options (list #'magit-highlight-squash-markers |
| 171 | #'magit-highlight-bracket-keywords)) |
| 172 | |
| 173 | (defcustom magit-log-trailer-labels nil |
| 174 | "Whether and how to insert labels, derived from commit message trailers. |
| 175 | |
| 176 | If non-nil, the value has the form (FUNCTION . OPTIONS). FUNCTION is |
| 177 | called with one argument, the trailers as an alist, and should return a |
| 178 | string, which is then inserted in between the refnames and the message, |
| 179 | or nil. OPTIONS specifies additional options for \"%(trailers)\". You |
| 180 | should probably use something like \"key=KEY1,key=KEY2,only=true\". |
| 181 | \"unfold=true,separator=^^,key_value_separator=^_\" is always appended |
| 182 | to the options specified here. See the git-log(1) manpage." |
| 183 | :package-version '(magit . "4.4.1") |
| 184 | :group 'magit-log |
| 185 | :type '(choice (const :tag "Ignore trailers" nil) |
| 186 | (cons :tag "Process trailers" |
| 187 | (function :tag "Formatting function") |
| 188 | (string :tag "Options for %%(trailers)")))) |
| 189 | |
| 190 | (defcustom magit-log-header-line-function #'magit-log-header-line-sentence |
| 191 | "Function used to generate text shown in header line of log buffers." |
| 192 | :package-version '(magit . "2.12.0") |
| 193 | :group 'magit-log |
| 194 | :type `(radio (function-item ,#'magit-log-header-line-arguments) |
| 195 | (function-item ,#'magit-log-header-line-sentence) |
| 196 | function)) |
| 197 | |
| 198 | (defcustom magit-log-trace-definition-function #'magit-which-function |
| 199 | "Function used to determine the function at point. |
| 200 | This is used by the command `magit-log-trace-definition'. |
| 201 | You should prefer `magit-which-function' over `which-function' |
| 202 | because the latter may make use of Imenu's outdated cache." |
| 203 | :package-version '(magit . "3.0.0") |
| 204 | :group 'magit-log |
| 205 | :type `(radio (function-item ,#'magit-which-function) |
| 206 | (function-item ,#'which-function) |
| 207 | (function-item ,#'add-log-current-defun) |
| 208 | function)) |
| 209 | |
| 210 | (defcustom magit-log-color-graph-limit 256 |
| 211 | "Number of commits over which log graphs are not colored. |
| 212 | When showing more commits than specified, then the \"--color\" |
| 213 | argument is silently dropped. This is necessary because the |
| 214 | `ansi-color' library, which is used to turn control sequences |
| 215 | into faces, is just too slow." |
| 216 | :package-version '(magit . "4.0.0") |
| 217 | :group 'magit-log |
| 218 | :type 'number) |
| 219 | |
| 220 | (defcustom magit-log-show-signatures-limit 256 |
| 221 | "Number of commits over which signatures are not verified. |
| 222 | When showing more commits than specified by this option, then the |
| 223 | \"--show-signature\" argument, if specified, is silently dropped. |
| 224 | This is necessary because checking the signature of a large |
| 225 | number of commits is just too slow." |
| 226 | :package-version '(magit . "4.0.0") |
| 227 | :group 'magit-log |
| 228 | :type 'number) |
| 229 | |
| 230 | (defface magit-log-graph |
| 231 | '((((class color) (background light)) :foreground "grey30") |
| 232 | (((class color) (background dark)) :foreground "grey80")) |
| 233 | "Face for the graph part of the log output." |
| 234 | :group 'magit-faces) |
| 235 | |
| 236 | (defface magit-log-author |
| 237 | '((((class color) (background light)) |
| 238 | :foreground "firebrick" |
| 239 | :slant normal |
| 240 | :weight normal) |
| 241 | (((class color) (background dark)) |
| 242 | :foreground "tomato" |
| 243 | :slant normal |
| 244 | :weight normal)) |
| 245 | "Face for the author part of the log output." |
| 246 | :group 'magit-faces) |
| 247 | |
| 248 | (defface magit-log-date |
| 249 | '((((class color) (background light)) |
| 250 | :foreground "grey30" |
| 251 | :slant normal |
| 252 | :weight normal) |
| 253 | (((class color) (background dark)) |
| 254 | :foreground "grey80" |
| 255 | :slant normal |
| 256 | :weight normal)) |
| 257 | "Face for the date part of the log output." |
| 258 | :group 'magit-faces) |
| 259 | |
| 260 | (defface magit-header-line-log-select |
| 261 | '((t :inherit bold)) |
| 262 | "Face for the `header-line' in `magit-log-select-mode'." |
| 263 | :group 'magit-faces) |
| 264 | |
| 265 | ;;;; File Log |
| 266 | |
| 267 | (defcustom magit-log-buffer-file-locked t |
| 268 | "Whether `magit-log-buffer-file-quick' uses a dedicated buffer." |
| 269 | :package-version '(magit . "2.7.0") |
| 270 | :group 'magit-commands |
| 271 | :group 'magit-log |
| 272 | :type 'boolean) |
| 273 | |
| 274 | ;;;; Select Mode |
| 275 | |
| 276 | (defcustom magit-log-select-show-usage 'both |
| 277 | "Whether to show usage information when selecting a commit from a log. |
| 278 | The message can be shown in the `echo-area' or the `header-line', or in |
| 279 | `both' places. If the value isn't one of these symbols, then it should |
| 280 | be nil, in which case no usage information is shown." |
| 281 | :package-version '(magit . "2.1.0") |
| 282 | :group 'magit-log |
| 283 | :type '(choice (const :tag "In echo-area" echo-area) |
| 284 | (const :tag "In header-line" header-line) |
| 285 | (const :tag "In both places" both) |
| 286 | (const :tag "Nowhere"))) |
| 287 | |
| 288 | (defcustom magit-log-select-margin |
| 289 | (list (nth 0 magit-log-margin) |
| 290 | (nth 1 magit-log-margin) |
| 291 | 'magit-log-margin-width t |
| 292 | (nth 4 magit-log-margin)) |
| 293 | "Format of the margin in `magit-log-select-mode' buffers. |
| 294 | |
| 295 | The value has the form (INIT STYLE WIDTH AUTHOR AUTHOR-WIDTH). |
| 296 | |
| 297 | If INIT is non-nil, then the margin is shown initially. |
| 298 | STYLE controls how to format the author or committer date. |
| 299 | It can be one of `age' (to show the age of the commit), |
| 300 | `age-abbreviated' (to abbreviate the time unit to a character), |
| 301 | or a string (suitable for `format-time-string') to show the |
| 302 | actual date. Option `magit-log-margin-show-committer-date' |
| 303 | controls which date is being displayed. |
| 304 | WIDTH controls the width of the margin. This exists for forward |
| 305 | compatibility and currently the value should not be changed. |
| 306 | AUTHOR controls whether the name of the author is also shown by |
| 307 | default. |
| 308 | AUTHOR-WIDTH has to be an integer. When the name of the author |
| 309 | is shown, then this specifies how much space is used to do so." |
| 310 | :package-version '(magit . "2.9.0") |
| 311 | :group 'magit-log |
| 312 | :group 'magit-margin |
| 313 | :type magit-log-margin--custom-type |
| 314 | :initialize #'magit-custom-initialize-reset |
| 315 | :set-after '(magit-log-margin) |
| 316 | :set (apply-partially #'magit-margin-set-variable 'magit-log-select-mode)) |
| 317 | |
| 318 | ;;;; Cherry Mode |
| 319 | |
| 320 | (defcustom magit-cherry-sections-hook |
| 321 | (list #'magit-insert-cherry-headers |
| 322 | #'magit-insert-cherry-commits) |
| 323 | "Hook run to insert sections into the cherry buffer." |
| 324 | :package-version '(magit . "2.1.0") |
| 325 | :group 'magit-log |
| 326 | :type 'hook) |
| 327 | |
| 328 | (defcustom magit-cherry-margin |
| 329 | (list (nth 0 magit-log-margin) |
| 330 | (nth 1 magit-log-margin) |
| 331 | 'magit-log-margin-width t |
| 332 | (nth 4 magit-log-margin)) |
| 333 | "Format of the margin in `magit-cherry-mode' buffers. |
| 334 | |
| 335 | The value has the form (INIT STYLE WIDTH AUTHOR AUTHOR-WIDTH). |
| 336 | |
| 337 | If INIT is non-nil, then the margin is shown initially. |
| 338 | STYLE controls how to format the author or committer date. |
| 339 | It can be one of `age' (to show the age of the commit), |
| 340 | `age-abbreviated' (to abbreviate the time unit to a character), |
| 341 | or a string (suitable for `format-time-string') to show the |
| 342 | actual date. Option `magit-log-margin-show-committer-date' |
| 343 | controls which date is being displayed. |
| 344 | WIDTH controls the width of the margin. This exists for forward |
| 345 | compatibility and currently the value should not be changed. |
| 346 | AUTHOR controls whether the name of the author is also shown by |
| 347 | default. |
| 348 | AUTHOR-WIDTH has to be an integer. When the name of the author |
| 349 | is shown, then this specifies how much space is used to do so." |
| 350 | :package-version '(magit . "2.9.0") |
| 351 | :group 'magit-log |
| 352 | :group 'magit-margin |
| 353 | :type magit-log-margin--custom-type |
| 354 | :initialize #'magit-custom-initialize-reset |
| 355 | :set-after '(magit-log-margin) |
| 356 | :set (apply-partially #'magit-margin-set-variable 'magit-cherry-mode)) |
| 357 | |
| 358 | ;;;; Log Sections |
| 359 | |
| 360 | (defcustom magit-log-section-commit-count 10 |
| 361 | "How many recent commits to show in certain log sections. |
| 362 | How many recent commits `magit-insert-recent-commits' and |
| 363 | `magit-insert-unpushed-to-upstream-or-recent' (provided |
| 364 | the upstream isn't ahead of the current branch) show." |
| 365 | :package-version '(magit . "2.1.0") |
| 366 | :group 'magit-status |
| 367 | :type 'number) |
| 368 | |
| 369 | (defcustom magit-log-merged-commit-count 20 |
| 370 | "How many surrounding commits to show for `magit-log-merged'. |
| 371 | `magit-log-merged' will shows approximately half of this number |
| 372 | commits before and half after." |
| 373 | :package-version '(magit . "3.3.0") |
| 374 | :group 'magit-log |
| 375 | :type 'integer) |
| 376 | |
| 377 | ;;; Variables |
| 378 | |
| 379 | (defvar-local magit-buffer-log-revisions nil) |
| 380 | (defvar-local magit-buffer-log-args nil) |
| 381 | (defvar-local magit-buffer-log-files nil) |
| 382 | |
| 383 | ;;; Arguments |
| 384 | ;;;; Prefix Classes |
| 385 | |
| 386 | (defclass magit-log-prefix (transient-prefix) |
| 387 | ((history-key :initform 'magit-log) |
| 388 | (major-mode :initform 'magit-log-mode))) |
| 389 | |
| 390 | (defclass magit-log-refresh-prefix (magit-log-prefix) |
| 391 | ((history-key :initform 'magit-log) |
| 392 | (major-mode :initform nil))) |
| 393 | |
| 394 | ;;;; Prefix Methods |
| 395 | |
| 396 | (cl-defmethod transient-prefix-value ((obj magit-log-prefix)) |
| 397 | (let ((args (cl-call-next-method obj))) |
| 398 | (list (seq-filter #'atom args) |
| 399 | (cdr (assoc "--" args))))) |
| 400 | |
| 401 | (cl-defmethod transient-init-value ((obj magit-log-prefix)) |
| 402 | (pcase-let ((`(,args ,files) |
| 403 | (magit-log--get-value 'magit-log-mode 'prefix))) |
| 404 | (when-let ((_(not (eq transient-current-command 'magit-dispatch))) |
| 405 | (file (magit-file-relative-name))) |
| 406 | (setq files (list file))) |
| 407 | (oset obj value (if files `(("--" ,@files) ,@args) args)))) |
| 408 | |
| 409 | (cl-defmethod transient-init-value ((obj magit-log-refresh-prefix)) |
| 410 | (oset obj value (if magit-buffer-log-files |
| 411 | `(("--" ,@magit-buffer-log-files) |
| 412 | ,@magit-buffer-log-args) |
| 413 | magit-buffer-log-args))) |
| 414 | |
| 415 | (cl-defmethod transient-set-value ((obj magit-log-prefix)) |
| 416 | (magit-log--set-value obj)) |
| 417 | |
| 418 | (cl-defmethod transient-save-value ((obj magit-log-prefix)) |
| 419 | (magit-log--set-value obj 'save)) |
| 420 | |
| 421 | ;;;; Argument Access |
| 422 | |
| 423 | (defun magit-log-arguments (&optional mode) |
| 424 | "Return the current log arguments." |
| 425 | (if (memq transient-current-command '(magit-log magit-log-refresh)) |
| 426 | (transient-args transient-current-command) |
| 427 | (magit-log--get-value (or mode 'magit-log-mode) 'direct))) |
| 428 | |
| 429 | (defun magit-log--get-value (mode &optional use-buffer-args) |
| 430 | (setq use-buffer-args |
| 431 | (pcase-exhaustive use-buffer-args |
| 432 | ('prefix magit-prefix-use-buffer-arguments) |
| 433 | ('status magit-status-use-buffer-arguments) |
| 434 | ('direct magit-direct-use-buffer-arguments) |
| 435 | ('nil magit-direct-use-buffer-arguments) |
| 436 | ((or 'always 'selected 'current 'never) |
| 437 | use-buffer-args))) |
| 438 | (cond-let |
| 439 | ((and (memq use-buffer-args '(always selected current)) |
| 440 | (eq major-mode mode)) |
| 441 | (list magit-buffer-log-args |
| 442 | magit-buffer-log-files)) |
| 443 | ([_(memq use-buffer-args '(always selected))] |
| 444 | [buffer (magit-get-mode-buffer mode nil (eq use-buffer-args 'selected))] |
| 445 | (list (buffer-local-value 'magit-buffer-log-args buffer) |
| 446 | (buffer-local-value 'magit-buffer-log-files buffer))) |
| 447 | ((plist-member (symbol-plist mode) 'magit-log-current-arguments) |
| 448 | (list (get mode 'magit-log-current-arguments) nil)) |
| 449 | ([elt (assq (intern (format "magit-log:%s" mode)) transient-values)] |
| 450 | (list (cdr elt) nil)) |
| 451 | ((list (get mode 'magit-log-default-arguments) nil)))) |
| 452 | |
| 453 | (defun magit-log--set-value (obj &optional save) |
| 454 | (pcase-let* ((obj (oref obj prototype)) |
| 455 | (mode (or (oref obj major-mode) major-mode)) |
| 456 | (key (intern (format "magit-log:%s" mode))) |
| 457 | (`(,args ,files) (transient-args (oref obj command)))) |
| 458 | (put mode 'magit-log-current-arguments args) |
| 459 | (when save |
| 460 | (setf (alist-get key transient-values) args) |
| 461 | (transient-save-values)) |
| 462 | (transient--history-push obj) |
| 463 | (setq magit-buffer-log-args args) |
| 464 | (unless (derived-mode-p 'magit-log-select-mode) |
| 465 | (setq magit-buffer-log-files files)) |
| 466 | (magit-refresh))) |
| 467 | |
| 468 | ;;; Commands |
| 469 | ;;;; Prefix Commands |
| 470 | |
| 471 | (transient-define-group magit-log-infix-arguments |
| 472 | ;; The grouping in git-log(1) appears to be guided by implementation |
| 473 | ;; details, so our logical grouping only follows it to an extent. |
| 474 | ;; Arguments that are "misplaced" here: |
| 475 | ;; 1. From "Commit Formatting". |
| 476 | ;; 2. From "Common Diff Options". ;FIXME |
| 477 | ;; 3. From unnamed first group. |
| 478 | ;; 4. Implemented by Magit. |
| 479 | ["Commit limiting" |
| 480 | :if magit-log-infix-arguments--show-p |
| 481 | (magit-log:-n) |
| 482 | (magit:--author) |
| 483 | (7 magit-log:--since) |
| 484 | (7 magit-log:--until) |
| 485 | (magit-log:--grep) |
| 486 | (7 "-i" "Search case-insensitive" ("-i" "--regexp-ignore-case")) |
| 487 | (7 "-I" "Invert search pattern" "--invert-grep") |
| 488 | (magit-log:-G) ;2 |
| 489 | (magit-log:-S) ;2 |
| 490 | (magit-log:-L) ;2 |
| 491 | (7 "=M" "Only merges" "--merges") |
| 492 | (7 "=m" "Omit merges" "--no-merges") |
| 493 | (7 "=p" "First parent" "--first-parent")] |
| 494 | ["History simplification" |
| 495 | :if magit-log-infix-arguments--show-p |
| 496 | ( "-D" "Simplify by decoration" "--simplify-by-decoration") |
| 497 | (magit:--) |
| 498 | ( "-f" "Follow renames when showing single-file log" "--follow") ;3 |
| 499 | (6 "/s" "Only commits changing given paths" "--sparse") |
| 500 | (7 "/d" "Only selected commits plus meaningful history" "--dense") |
| 501 | (7 "/a" "Only commits existing directly on ancestry path" "--ancestry-path") |
| 502 | (6 "/f" "Do not prune history" "--full-history") |
| 503 | (7 "/m" "Prune some history" "--simplify-merges")] |
| 504 | ["Commit ordering" |
| 505 | :if magit-log-infix-arguments--show-p |
| 506 | (magit-log:--*-order) |
| 507 | ("-r" "Reverse order" "--reverse")] |
| 508 | ["Formatting" |
| 509 | :if magit-log-infix-arguments--show-p |
| 510 | ("-g" "Show graph" "--graph") ;1 |
| 511 | ("-c" "Show graph in color" "--color") ;2 |
| 512 | (magit-log:--graph-lane-limit :level 5) ;TODO |
| 513 | ("-d" "Show refnames" "--decorate") ;3 |
| 514 | ("=S" "Show signatures" "--show-signature") ;1 |
| 515 | ("-h" "Show header" "++header") ;4 |
| 516 | ("-p" "Show diffs" ("-p" "--patch")) ;2 |
| 517 | ("-s" "Show diffstats" "--stat")]) ;2 |
| 518 | |
| 519 | (defun magit-log-infix-arguments--show-p () |
| 520 | (if (eq (oref (transient-prefix-object) command) 'magit-log-refresh) |
| 521 | (eq major-mode 'magit-log-mode) |
| 522 | t)) |
| 523 | |
| 524 | ;;;###autoload(autoload 'magit-log "magit-log" nil t) |
| 525 | (transient-define-prefix magit-log () |
| 526 | "Show a commit or reference log." |
| 527 | :man-page "git-log" |
| 528 | :class 'magit-log-prefix |
| 529 | :incompatible '(("--merges" "--no-merges")) |
| 530 | 'magit-log-infix-arguments |
| 531 | [["Log" |
| 532 | ("l" magit-log-current) |
| 533 | ("o" "other" magit-log-other) |
| 534 | ("h" "HEAD" magit-log-head :level 0) |
| 535 | ("u" "related" magit-log-related)] |
| 536 | ["" |
| 537 | ("L" "local branches" magit-log-branches) |
| 538 | ("b" "all branches" magit-log-all-branches) |
| 539 | ("a" "all references" magit-log-all) |
| 540 | ("R" "reflog objects" magit-log-reflog :level 0) |
| 541 | ("B" "matching branches" magit-log-matching-branches :level 7) |
| 542 | ("T" "matching tags" magit-log-matching-tags :level 7) |
| 543 | ("m" "merged" magit-log-merged :level 7)] |
| 544 | ["Reflog" |
| 545 | ("r" "current" magit-reflog-current) |
| 546 | ("O" "other" magit-reflog-other) |
| 547 | ("H" "HEAD" magit-reflog-head)] |
| 548 | [:if-non-nil magit-wip-mode |
| 549 | :description "Wiplog" |
| 550 | ("i" "index" magit-wip-log-index) |
| 551 | ("w" "worktree" magit-wip-log-worktree)] |
| 552 | ["Other" |
| 553 | ("s" "shortlog" magit-shortlog)]]) |
| 554 | |
| 555 | ;;;###autoload(autoload 'magit-log-refresh "magit-log" nil t) |
| 556 | (transient-define-prefix magit-log-refresh () |
| 557 | "Change the arguments used for the log(s) in the current buffer." |
| 558 | :man-page "git-log" |
| 559 | :class 'magit-log-refresh-prefix |
| 560 | :incompatible '(("--merges" "--no-merges")) |
| 561 | 'magit-log-infix-arguments |
| 562 | [:if-not-mode magit-log-mode |
| 563 | :description "Arguments" |
| 564 | (magit-log:-n) |
| 565 | (magit-log:--*-order) |
| 566 | ("-g" "Show graph" "--graph") |
| 567 | ("-c" "Show graph in color" "--color") |
| 568 | ("-d" "Show refnames" "--decorate")] |
| 569 | [["Refresh" |
| 570 | ("g" "buffer" magit-log-refresh) |
| 571 | ("s" "buffer and set defaults" transient-set-and-exit) |
| 572 | ("w" "buffer and save defaults" transient-save-and-exit)] |
| 573 | ["Margin" |
| 574 | (magit-toggle-margin) |
| 575 | (magit-cycle-margin-style) |
| 576 | (magit-toggle-margin-details) |
| 577 | (magit-toggle-log-margin-style)] |
| 578 | [:if-mode magit-log-mode |
| 579 | :description "Toggle" |
| 580 | ("b" "buffer lock" magit-toggle-buffer-lock)]] |
| 581 | (interactive) |
| 582 | (cond |
| 583 | ((not (eq transient-current-command 'magit-log-refresh)) |
| 584 | (pcase major-mode |
| 585 | ('magit-reflog-mode |
| 586 | (user-error "Cannot change log arguments in reflog buffers")) |
| 587 | ('magit-cherry-mode |
| 588 | (user-error "Cannot change log arguments in cherry buffers"))) |
| 589 | (transient-setup 'magit-log-refresh)) |
| 590 | (t |
| 591 | (pcase-let ((`(,args ,files) (magit-log-arguments))) |
| 592 | (setq magit-buffer-log-args args) |
| 593 | (unless (derived-mode-p 'magit-log-select-mode) |
| 594 | (setq magit-buffer-log-files files))) |
| 595 | (magit-refresh)))) |
| 596 | |
| 597 | ;;;; Infix Commands |
| 598 | |
| 599 | (transient-define-argument magit-log:-n () |
| 600 | :description "Limit number of commits" |
| 601 | :class 'transient-option |
| 602 | ;; For historic reasons (and because it easy to guess what "-n" |
| 603 | ;; stands for) this is the only argument where we do not use the |
| 604 | ;; long argument ("--max-count"). |
| 605 | :shortarg "-n" |
| 606 | :argument "-n" |
| 607 | :reader #'transient-read-number-N+) |
| 608 | |
| 609 | (transient-define-argument magit-log:--graph-lane-limit () |
| 610 | :description "Show graph lanes" |
| 611 | :class 'transient-option |
| 612 | :key "=g" |
| 613 | :argument "--graph-lane-limit=" |
| 614 | :reader #'transient-read-number-N+ |
| 615 | :if (##magit-git-version>= "2.55")) |
| 616 | |
| 617 | (transient-define-argument magit:--author () |
| 618 | :description "Limit to author" |
| 619 | :class 'transient-option |
| 620 | :key "-A" |
| 621 | :argument "--author=" |
| 622 | :multi-value 'repeat |
| 623 | :reader #'magit-transient-read-persons) |
| 624 | |
| 625 | (transient-define-argument magit-log:--since () |
| 626 | :description "Limit to commits since" |
| 627 | :class 'transient-option |
| 628 | :key "=s" |
| 629 | :argument "--since=" |
| 630 | :reader #'transient-read-date) |
| 631 | |
| 632 | (transient-define-argument magit-log:--until () |
| 633 | :description "Limit to commits until" |
| 634 | :class 'transient-option |
| 635 | :key "=u" |
| 636 | :argument "--until=" |
| 637 | :reader #'transient-read-date) |
| 638 | |
| 639 | (transient-define-argument magit-log:--*-order () |
| 640 | :description "Order commits by" |
| 641 | :class 'transient-switches |
| 642 | :key "-o" |
| 643 | :argument-format "--%s-order" |
| 644 | :argument-regexp "\\(--\\(topo\\|author-date\\|date\\)-order\\)" |
| 645 | :choices '("topo" "author-date" "date")) |
| 646 | |
| 647 | (transient-define-argument magit-log:--grep () |
| 648 | :description "Search messages" |
| 649 | :class 'transient-option |
| 650 | :key "-F" |
| 651 | :argument "--grep=") |
| 652 | |
| 653 | (transient-define-argument magit-log:-G () |
| 654 | :description "Search changes" |
| 655 | :class 'transient-option |
| 656 | :argument "-G") |
| 657 | |
| 658 | (transient-define-argument magit-log:-S () |
| 659 | :description "Search occurrences" |
| 660 | :class 'transient-option |
| 661 | :argument "-S") |
| 662 | |
| 663 | (transient-define-argument magit-log:-L () |
| 664 | :description "Trace line evolution" |
| 665 | :class 'transient-option |
| 666 | :argument "-L" |
| 667 | :reader #'magit-read-file-trace) |
| 668 | |
| 669 | (defun magit-read-file-trace (&rest _ignored) |
| 670 | (let ((file (magit-read-file-from-rev "HEAD" "File")) |
| 671 | (trace (magit-read-string "Trace"))) |
| 672 | (concat trace ":" file))) |
| 673 | |
| 674 | ;;;; Setup Commands |
| 675 | |
| 676 | (defvar-keymap magit-log-read-revs-map |
| 677 | :parent crm-local-completion-map |
| 678 | "SPC" #'self-insert-command) |
| 679 | |
| 680 | (defun magit-log-read-revs (&optional use-current) |
| 681 | (or (and use-current (and$ (magit-get-current-branch) (list $))) |
| 682 | (let ((crm-separator "\\(\\.\\.\\.?\\|[, ]\\)") |
| 683 | (crm-local-completion-map magit-log-read-revs-map)) |
| 684 | (split-string (magit-completing-read-multiple |
| 685 | "Log rev,s: " |
| 686 | (magit-list-refnames nil t) |
| 687 | nil 'any nil 'magit-revision-history |
| 688 | (or (magit-branch-or-commit-at-point) |
| 689 | (and (not use-current) |
| 690 | (magit-get-previous-branch))) |
| 691 | nil t) |
| 692 | "[, ]" t)))) |
| 693 | |
| 694 | (defun magit-log-read-pattern (option) |
| 695 | "Read a string from the user to pass as parameter to OPTION." |
| 696 | (magit-read-string (format "Type a pattern to pass to %s" option))) |
| 697 | |
| 698 | ;;;###autoload(autoload 'magit-log-current "magit-log" nil t) |
| 699 | (transient-define-suffix magit-log-current (&optional args files) |
| 700 | "Show log for the current branch, or `HEAD' if no branch is checked out." |
| 701 | :description (##if (magit-get-current-branch) "current" "HEAD") |
| 702 | (interactive (magit-log-arguments)) |
| 703 | (magit-log-setup-buffer (list (or (magit-get-current-branch) "HEAD")) |
| 704 | args files)) |
| 705 | |
| 706 | ;;;###autoload |
| 707 | (defun magit-log-head (&optional args files) |
| 708 | "Show log for `HEAD'." |
| 709 | (interactive (magit-log-arguments)) |
| 710 | (magit-log-setup-buffer (list "HEAD") args files)) |
| 711 | |
| 712 | ;;;###autoload |
| 713 | (defun magit-log-related (revs &optional args files) |
| 714 | "Show log for the current branch, its upstream and its push target. |
| 715 | When the upstream is a local branch, then also show its own |
| 716 | upstream. When `HEAD' is detached, then show log for that, the |
| 717 | previously checked out branch and its upstream and push-target." |
| 718 | (interactive |
| 719 | (cons (let ((current (magit-get-current-branch)) |
| 720 | head rebase target upstream upup) |
| 721 | (unless current |
| 722 | (setq rebase (magit-rebase--get-state-lines "head-name")) |
| 723 | (cond (rebase |
| 724 | (setq rebase (magit-ref-abbrev rebase)) |
| 725 | (setq current rebase) |
| 726 | (setq head "HEAD")) |
| 727 | ((setq current (magit-get-previous-branch))))) |
| 728 | (cond (current |
| 729 | (setq current |
| 730 | (magit--propertize-face current 'magit-branch-local)) |
| 731 | (setq target (magit-get-push-branch current t)) |
| 732 | (setq upstream (magit-get-upstream-branch current)) |
| 733 | (when upstream |
| 734 | (setq upup (and (magit-local-branch-p upstream) |
| 735 | (magit-get-upstream-branch upstream))))) |
| 736 | ((setq head "HEAD"))) |
| 737 | (delq nil (list current head target upstream upup))) |
| 738 | (magit-log-arguments))) |
| 739 | (magit-log-setup-buffer revs args files)) |
| 740 | |
| 741 | ;;;###autoload |
| 742 | (defun magit-log-other (revs &optional args files) |
| 743 | "Show log for one or more revs read from the minibuffer. |
| 744 | The user can input any revision or revisions separated by a |
| 745 | space, or even ranges, but only branches and tags, and a |
| 746 | representation of the commit at point, are available as |
| 747 | completion candidates." |
| 748 | (interactive (cons (magit-log-read-revs) |
| 749 | (magit-log-arguments))) |
| 750 | (magit-log-setup-buffer revs args files)) |
| 751 | |
| 752 | ;;;###autoload |
| 753 | (defun magit-log-branches (&optional args files) |
| 754 | "Show log for all local branches and `HEAD'." |
| 755 | (interactive (magit-log-arguments)) |
| 756 | (magit-log-setup-buffer (if (magit-get-current-branch) |
| 757 | (list "--branches") |
| 758 | (list "HEAD" "--branches")) |
| 759 | args files)) |
| 760 | |
| 761 | ;;;###autoload |
| 762 | (defun magit-log-matching-branches (pattern &optional args files) |
| 763 | "Show log for all branches matching PATTERN and `HEAD'." |
| 764 | (interactive (cons (magit-log-read-pattern "--branches") |
| 765 | (magit-log-arguments))) |
| 766 | (magit-log-setup-buffer (list "HEAD" (format "--branches=%s" pattern)) |
| 767 | args files)) |
| 768 | |
| 769 | ;;;###autoload |
| 770 | (defun magit-log-matching-tags (pattern &optional args files) |
| 771 | "Show log for all tags matching PATTERN and `HEAD'." |
| 772 | (interactive (cons (magit-log-read-pattern "--tags") |
| 773 | (magit-log-arguments))) |
| 774 | (magit-log-setup-buffer (list "HEAD" (format "--tags=%s" pattern)) |
| 775 | args files)) |
| 776 | |
| 777 | ;;;###autoload |
| 778 | (defun magit-log-all-branches (&optional args files) |
| 779 | "Show log for all local and remote branches and `HEAD'." |
| 780 | (interactive (magit-log-arguments)) |
| 781 | (magit-log-setup-buffer (if (magit-get-current-branch) |
| 782 | (list "--branches" "--remotes") |
| 783 | (list "HEAD" "--branches" "--remotes")) |
| 784 | args files)) |
| 785 | |
| 786 | ;;;###autoload |
| 787 | (defun magit-log-all (&optional args files) |
| 788 | "Show log for all references and `HEAD'." |
| 789 | (interactive (magit-log-arguments)) |
| 790 | (magit-log-setup-buffer (list "--all") args files)) |
| 791 | |
| 792 | ;;;###autoload |
| 793 | (defun magit-log-reflog (&optional args files) |
| 794 | "Show log for all objects mentioned in all reflogs." |
| 795 | (interactive (magit-log-arguments)) |
| 796 | (magit-log-setup-buffer (list "--reflog") args files)) |
| 797 | |
| 798 | ;;;###autoload |
| 799 | (defun magit-log-buffer-file (&optional follow beg end) |
| 800 | "Show log for the blob or file visited in the current buffer. |
| 801 | With a prefix argument or when \"--follow\" is an active log |
| 802 | argument, then follow renames. When the region is active, |
| 803 | restrict the log to the lines that the region touches." |
| 804 | (interactive (cons current-prefix-arg (magit-file-region-line-numbers))) |
| 805 | (require 'magit) |
| 806 | (if-let ((file (magit-file-relative-name))) |
| 807 | (magit-log-setup-buffer |
| 808 | (list (or magit-buffer-revision |
| 809 | (magit-get-current-branch) |
| 810 | "HEAD")) |
| 811 | (let ((args (car (magit-log-arguments)))) |
| 812 | (when (and follow (not (member "--follow" args))) |
| 813 | (push "--follow" args)) |
| 814 | (when (and beg end) |
| 815 | (setq args (cons (format "-L%s,%s:%s" beg end file) |
| 816 | (cl-delete "-L" args :test |
| 817 | #'string-prefix-p))) |
| 818 | (setq file nil)) |
| 819 | args) |
| 820 | (and file (list file)) |
| 821 | magit-log-buffer-file-locked) |
| 822 | (user-error "Buffer isn't visiting a file"))) |
| 823 | |
| 824 | ;;;###autoload |
| 825 | (defun magit-log-trace-definition (file fn commit) |
| 826 | "Show log for the definition at point." |
| 827 | (interactive (list (or (magit-file-relative-name) |
| 828 | (user-error "Buffer isn't visiting a file")) |
| 829 | (or (funcall magit-log-trace-definition-function) |
| 830 | (user-error "No function at point found")) |
| 831 | (or magit-buffer-revision |
| 832 | (magit-get-current-branch) |
| 833 | "HEAD"))) |
| 834 | (require 'magit) |
| 835 | (magit-log-setup-buffer |
| 836 | (list commit) |
| 837 | (cons (format "-L:%s%s:%s" |
| 838 | (string-replace ":" "\\:" (regexp-quote fn)) |
| 839 | (if (derived-mode-p 'lisp-mode 'emacs-lisp-mode) |
| 840 | ;; Git doesn't treat "-" the same way as |
| 841 | ;; "_", leading to false-positives such as |
| 842 | ;; "foo-suffix" being considered a match |
| 843 | ;; for "foo". Wing it. |
| 844 | "\\( \\|$\\)" |
| 845 | ;; We could use "\\b" here, but since Git |
| 846 | ;; already does something equivalent, that |
| 847 | ;; isn't necessary. |
| 848 | "") |
| 849 | file) |
| 850 | (cl-delete "-L" (car (magit-log-arguments)) |
| 851 | :test #'string-prefix-p)) |
| 852 | nil magit-log-buffer-file-locked)) |
| 853 | |
| 854 | (defun magit-diff-trace-definition () |
| 855 | "Show log for the definition at point in a diff." |
| 856 | (interactive) |
| 857 | (pcase-let ((`(,buf ,pos) (magit-diff-visit-file--noselect))) |
| 858 | (magit--with-temp-position buf pos |
| 859 | (call-interactively #'magit-log-trace-definition)))) |
| 860 | |
| 861 | ;;;###autoload |
| 862 | (defun magit-log-merged (commit branch &optional args files) |
| 863 | "Show log for the merge of COMMIT into BRANCH. |
| 864 | |
| 865 | More precisely, find merge commit M that brought COMMIT into |
| 866 | BRANCH, and show the log of the range \"M^1..M\". If COMMIT is |
| 867 | directly on BRANCH, then show approximately |
| 868 | `magit-log-merged-commit-count' surrounding commits instead. |
| 869 | |
| 870 | This command requires git-when-merged, which is available from |
| 871 | https://github.com/mhagger/git-when-merged." |
| 872 | (interactive |
| 873 | (append (let ((commit (magit-read-branch-or-commit "Log merge of commit"))) |
| 874 | (list commit |
| 875 | (magit-read-other-branch "Merged into" commit))) |
| 876 | (magit-log-arguments))) |
| 877 | (unless (magit-git-executable-find "git-when-merged") |
| 878 | (user-error "This command requires git-when-merged (%s)" |
| 879 | "https://github.com/mhagger/git-when-merged")) |
| 880 | (let (exit m) |
| 881 | (with-temp-buffer |
| 882 | (save-excursion |
| 883 | (setq exit (magit-process-git t "when-merged" "-c" |
| 884 | (magit-abbrev-arg) |
| 885 | commit branch))) |
| 886 | (setq m (buffer-substring-no-properties (point) (line-end-position)))) |
| 887 | (if (zerop exit) |
| 888 | (magit-log-setup-buffer (list (format "%s^1..%s" m m)) |
| 889 | args files nil commit) |
| 890 | ;; Output: "<ref><lots of spaces><message>". |
| 891 | ;; This is not the same as `string-trim'. |
| 892 | (setq m (string-trim-left (substring m (string-match " " m)))) |
| 893 | (if (equal m "Commit is directly on this branch.") |
| 894 | (let* ((from (format "%s~%d" commit |
| 895 | (/ magit-log-merged-commit-count 2))) |
| 896 | (to (- (car (magit-rev-diff-count branch commit t)) |
| 897 | (/ magit-log-merged-commit-count 2))) |
| 898 | (to (if (<= to 0) |
| 899 | branch |
| 900 | (format "%s~%s" branch to)))) |
| 901 | (unless (magit-commit-p from) |
| 902 | (setq from (magit-git-string "rev-list" "--max-parents=0" |
| 903 | commit))) |
| 904 | (magit-log-setup-buffer (list (concat from ".." to)) |
| 905 | (cons "--first-parent" args) |
| 906 | files nil commit)) |
| 907 | (user-error "Could not find when %s was merged into %s: %s" |
| 908 | commit branch m))))) |
| 909 | |
| 910 | ;;;###autoload |
| 911 | (defun magit-delete-shelved-branch (branch) |
| 912 | "Delete the shelved BRANCH. |
| 913 | Delete a ref created by `magit-branch-shelve'." |
| 914 | (interactive (list (magit-read-shelved-branch "Delete shelved branch"))) |
| 915 | (magit-run-git "update-ref" "-d" (concat "refs/shelved/" branch))) |
| 916 | |
| 917 | ;;;; Limit Commands |
| 918 | |
| 919 | (defun magit-log-toggle-commit-limit () |
| 920 | "Toggle the number of commits the current log buffer is limited to. |
| 921 | If the number of commits is currently limited, then remove that |
| 922 | limit. Otherwise set it to 256." |
| 923 | (interactive) |
| 924 | (magit-log-set-commit-limit (lambda (&rest _) nil))) |
| 925 | |
| 926 | (defun magit-log-double-commit-limit () |
| 927 | "Double the number of commits the current log buffer is limited to." |
| 928 | (interactive) |
| 929 | (magit-log-set-commit-limit '*)) |
| 930 | |
| 931 | (defun magit-log-half-commit-limit () |
| 932 | "Half the number of commits the current log buffer is limited to." |
| 933 | (interactive) |
| 934 | (magit-log-set-commit-limit '/)) |
| 935 | |
| 936 | (defun magit-log-set-commit-limit (fn) |
| 937 | (let* ((val magit-buffer-log-args) |
| 938 | (arg (seq-find (##string-match "^-n\\([0-9]+\\)?$" %) val)) |
| 939 | (num (and arg (string-to-number (match-str 1 arg)))) |
| 940 | (num (if num (funcall fn num 2) 256))) |
| 941 | (setq val (remove arg val)) |
| 942 | (setq magit-buffer-log-args |
| 943 | (if (and num (> num 0)) |
| 944 | (cons (format "-n%d" num) val) |
| 945 | val))) |
| 946 | (magit-refresh)) |
| 947 | |
| 948 | (defun magit-log-get-commit-limit (&optional args) |
| 949 | (and$ (seq-find (##string-match "^-n\\([0-9]+\\)?$" %) |
| 950 | (or args magit-buffer-log-args)) |
| 951 | (string-to-number (match-str 1 $)))) |
| 952 | |
| 953 | ;;;; Mode Commands |
| 954 | |
| 955 | (defun magit-log-bury-buffer (&optional arg) |
| 956 | "Bury the current buffer or the revision buffer in the same frame. |
| 957 | Like `magit-mode-bury-buffer' (which see) but with a negative |
| 958 | prefix argument instead bury the revision buffer, provided it |
| 959 | is displayed in the current frame." |
| 960 | (interactive "p") |
| 961 | (cond-let* |
| 962 | ((>= arg 0) |
| 963 | (magit-mode-bury-buffer (> arg 1))) |
| 964 | ([buf (magit-get-mode-buffer 'magit-revision-mode)] |
| 965 | [win (get-buffer-window buf (selected-frame))] |
| 966 | (with-selected-window win |
| 967 | (with-current-buffer buf |
| 968 | (magit-mode-bury-buffer (> (abs arg) 1))))) |
| 969 | ((user-error "No revision buffer in this frame")))) |
| 970 | |
| 971 | ;;;###autoload |
| 972 | (defun magit-log-move-to-parent (&optional n) |
| 973 | "Move to the Nth parent of the current commit." |
| 974 | (interactive "p") |
| 975 | (when (and (derived-mode-p 'magit-log-mode) |
| 976 | (magit-section-match 'commit)) |
| 977 | (let* ((section (magit-current-section)) |
| 978 | (parent-rev (format "%s^%s" (oref section value) (or n 1)))) |
| 979 | (if-let ((parent-hash (magit-rev-parse "--short" parent-rev))) |
| 980 | (if-let ((parent (seq-find (##equal (oref % value) parent-hash) |
| 981 | (magit-section-siblings section 'next)))) |
| 982 | (magit-section-goto parent) |
| 983 | (user-error |
| 984 | (substitute-command-keys |
| 985 | (concat "Parent " parent-hash " not found. Try typing " |
| 986 | "\\[magit-log-double-commit-limit] first")))) |
| 987 | (user-error "Parent %s does not exist" parent-rev))))) |
| 988 | |
| 989 | (defun magit-log-move-to-revision (commit) |
| 990 | "Read a commit and move to it in current log buffer. |
| 991 | |
| 992 | If the chosen reference or commit isn't being displayed in |
| 993 | the current log buffer, then inform the user about that and do |
| 994 | nothing else. |
| 995 | |
| 996 | If invoked outside any log buffer, then display the log buffer |
| 997 | of the current repository first; creating it if necessary." |
| 998 | (interactive |
| 999 | (list (magit-completing-read |
| 1000 | "In log, jump to" |
| 1001 | (magit-list-refnames nil t) |
| 1002 | nil 'any nil 'magit-revision-history |
| 1003 | (or (and$ (magit-commit-at-point) |
| 1004 | (magit-rev-fixup-target $)) |
| 1005 | (magit-get-current-branch))))) |
| 1006 | (with-current-buffer |
| 1007 | (cond ((derived-mode-p 'magit-log-mode) |
| 1008 | (current-buffer)) |
| 1009 | ((and$ (magit-get-mode-buffer 'magit-log-mode) |
| 1010 | (pop-to-buffer-same-window $))) |
| 1011 | ((apply #'magit-log-all-branches (magit-log-arguments)))) |
| 1012 | (unless (magit-log-goto-commit-section (magit-rev-abbrev commit)) |
| 1013 | (user-error "%s isn't visible in the current log buffer" commit)))) |
| 1014 | |
| 1015 | ;;;; Shortlog Commands |
| 1016 | |
| 1017 | ;;;###autoload(autoload 'magit-shortlog "magit-log" nil t) |
| 1018 | (transient-define-prefix magit-shortlog () |
| 1019 | "Show a history summary." |
| 1020 | :man-page "git-shortlog" |
| 1021 | :value '("--numbered" "--summary") |
| 1022 | ["Arguments" |
| 1023 | ("-n" "Sort by number of commits" ("-n" "--numbered")) |
| 1024 | ("-s" "Show commit count summary only" ("-s" "--summary")) |
| 1025 | ("-e" "Show email addresses" ("-e" "--email")) |
| 1026 | ("-g" "Group commits by" "--group=" |
| 1027 | :choices ("author" "committer" "trailer:")) |
| 1028 | (7 "-f" "Format string" "--format=") |
| 1029 | (7 "-w" "Linewrap" "-w" :class transient-option)] |
| 1030 | ["Shortlog" |
| 1031 | ("s" "since" magit-shortlog-since) |
| 1032 | ("r" "range" magit-shortlog-range)]) |
| 1033 | |
| 1034 | (defun magit-git-shortlog (rev args) |
| 1035 | (let ((dir default-directory)) |
| 1036 | (with-current-buffer (get-buffer-create "*magit-shortlog*") |
| 1037 | (setq default-directory dir) |
| 1038 | (setq buffer-read-only t) |
| 1039 | (let ((inhibit-read-only t)) |
| 1040 | (erase-buffer) |
| 1041 | (save-excursion |
| 1042 | (magit-git-insert "shortlog" args rev)) |
| 1043 | (switch-to-buffer-other-window (current-buffer)))))) |
| 1044 | |
| 1045 | ;;;###autoload |
| 1046 | (defun magit-shortlog-since (commit args) |
| 1047 | "Show a history summary for commits since REV." |
| 1048 | (interactive |
| 1049 | (list (magit-read-branch-or-commit "Shortlog since" (magit-get-current-tag)) |
| 1050 | (transient-args 'magit-shortlog))) |
| 1051 | (magit-git-shortlog (concat commit "..") args)) |
| 1052 | |
| 1053 | ;;;###autoload |
| 1054 | (defun magit-shortlog-range (rev-or-range args) |
| 1055 | "Show a history summary for commit or range REV-OR-RANGE." |
| 1056 | (interactive |
| 1057 | (list (magit-read-range-or-commit "Shortlog for revision or range") |
| 1058 | (transient-args 'magit-shortlog))) |
| 1059 | (magit-git-shortlog rev-or-range args)) |
| 1060 | |
| 1061 | ;;;; Movement Commands |
| 1062 | |
| 1063 | (defvar magit-reference-movement-faces |
| 1064 | '(magit-tag |
| 1065 | magit-branch-remote |
| 1066 | magit-branch-remote-head |
| 1067 | magit-branch-local |
| 1068 | magit-branch-current |
| 1069 | magit-branch-upstream |
| 1070 | magit-branch-warning |
| 1071 | magit-head |
| 1072 | magit-refname |
| 1073 | magit-refname-stash |
| 1074 | magit-refname-wip |
| 1075 | magit-refname-pullreq)) |
| 1076 | |
| 1077 | (defvar-keymap magit-reference-navigation-repeat-map |
| 1078 | :repeat t |
| 1079 | "p" #'magit-previous-reference |
| 1080 | "n" #'magit-next-reference |
| 1081 | "r" #'magit-next-reference) |
| 1082 | |
| 1083 | (defun magit-previous-reference () |
| 1084 | "Move to the previous Git reference appearing in the current buffer. |
| 1085 | |
| 1086 | Move to the previous location that uses a face appearing in |
| 1087 | `magit-reference-movement-faces'. If `repeat-mode' is enabled, |
| 1088 | this command and its counterpart can be repeated using \ |
| 1089 | \\<magit-reference-navigation-repeat-map>\ |
| 1090 | \\[magit-previous-reference] and \\[magit-next-reference]." |
| 1091 | (interactive) |
| 1092 | (magit-next-reference t)) |
| 1093 | |
| 1094 | (defun magit-next-reference (&optional previous) |
| 1095 | "Move to the next Git reference appearing in the current buffer. |
| 1096 | |
| 1097 | Move to the next location that uses a face appearing in |
| 1098 | `magit-reference-movement-faces'. If `repeat-mode' is enabled, |
| 1099 | this command and its counterpart can be repeated using \ |
| 1100 | \\<magit-reference-navigation-repeat-map>\ |
| 1101 | \\[magit-previous-reference] and \\[magit-next-reference]." |
| 1102 | (interactive) |
| 1103 | (catch 'found |
| 1104 | (let ((pos (point))) |
| 1105 | (while (and (not (eobp)) |
| 1106 | (setq pos (if previous |
| 1107 | (previous-single-property-change pos 'face) |
| 1108 | (next-single-property-change pos 'face)))) |
| 1109 | (when (cl-intersection (ensure-list (get-text-property pos 'face)) |
| 1110 | magit-reference-movement-faces) |
| 1111 | (throw 'found (goto-char pos)))) |
| 1112 | (message "No more references")))) |
| 1113 | |
| 1114 | ;;; Log Mode |
| 1115 | |
| 1116 | (defvar magit-log-disable-graph-hack-args |
| 1117 | '("-G" "--grep" "--author") |
| 1118 | "Arguments which disable the graph speedup hack.") |
| 1119 | |
| 1120 | (defvar-keymap magit-log-mode-map |
| 1121 | :doc "Keymap for `magit-log-mode'." |
| 1122 | :parent magit-mode-map |
| 1123 | "C-c C-b" #'magit-go-backward |
| 1124 | "C-c C-f" #'magit-go-forward |
| 1125 | "C-c C-n" #'magit-log-move-to-parent |
| 1126 | "j" #'magit-log-move-to-revision |
| 1127 | "=" #'magit-log-toggle-commit-limit |
| 1128 | "+" #'magit-log-double-commit-limit |
| 1129 | "-" #'magit-log-half-commit-limit |
| 1130 | "q" #'magit-log-bury-buffer) |
| 1131 | |
| 1132 | (define-derived-mode magit-log-mode magit-mode "Magit Log" |
| 1133 | "Mode for looking at Git log. |
| 1134 | |
| 1135 | This mode is documented in info node `(magit)Log Buffer'. |
| 1136 | |
| 1137 | \\<magit-mode-map>\ |
| 1138 | Type \\[magit-refresh] to refresh the current buffer. |
| 1139 | Type \\[magit-visit-thing] or \\[magit-diff-show-or-scroll-up] \ |
| 1140 | to visit the commit at point. |
| 1141 | |
| 1142 | Type \\[magit-branch] to see available branch commands. |
| 1143 | Type \\[magit-merge] to merge the branch or commit at point. |
| 1144 | Type \\[magit-cherry-pick] to apply the commit at point. |
| 1145 | Type \\[magit-reset] to reset `HEAD' to the commit at point. |
| 1146 | |
| 1147 | \\{magit-log-mode-map}" |
| 1148 | :interactive nil |
| 1149 | :group 'magit-log |
| 1150 | (magit-hack-dir-local-variables) |
| 1151 | (setq magit--imenu-item-types 'commit)) |
| 1152 | |
| 1153 | (put 'magit-log-mode 'magit-log-default-arguments |
| 1154 | '("--graph" "-n256" "--decorate")) |
| 1155 | |
| 1156 | (defun magit-log-setup-buffer (revs args files &optional locked focus) |
| 1157 | (require 'magit) |
| 1158 | (with-current-buffer |
| 1159 | (magit-setup-buffer #'magit-log-mode locked |
| 1160 | (magit-buffer-log-revisions revs) |
| 1161 | (magit-buffer-log-args args) |
| 1162 | (magit-buffer-log-files files)) |
| 1163 | (when (if focus |
| 1164 | (magit-log-goto-commit-section focus) |
| 1165 | (magit-log-goto-same-commit)) |
| 1166 | (magit-section-update-highlight)) |
| 1167 | (current-buffer))) |
| 1168 | |
| 1169 | (defun magit-log-refresh-buffer () |
| 1170 | (let ((revs magit-buffer-log-revisions) |
| 1171 | (args magit-buffer-log-args) |
| 1172 | (files magit-buffer-log-files) |
| 1173 | (limit (magit-log-get-commit-limit))) |
| 1174 | (magit-set-header-line-format |
| 1175 | (funcall magit-log-header-line-function revs args files)) |
| 1176 | (unless (length= files 1) |
| 1177 | (setq args (remove "--follow" args))) |
| 1178 | (when (and (car magit-log-remove-graph-args) |
| 1179 | (let ((re (concat "^" (regexp-opt magit-log-remove-graph-args)))) |
| 1180 | (seq-some (##string-match-p re %) args))) |
| 1181 | (setq args (remove "--graph" args))) |
| 1182 | (setq args (magit-log--maybe-drop-color-graph args limit)) |
| 1183 | (when-let* ((limit limit) |
| 1184 | (limit (* 2 limit)) ; increase odds for complete graph |
| 1185 | (count (and (length= revs 1) |
| 1186 | (> limit 1024) ; otherwise it's fast enough |
| 1187 | (setq revs (car revs)) |
| 1188 | (not (string-search ".." revs)) |
| 1189 | (not (member revs '("--all" "--branches"))) |
| 1190 | (not (seq-some |
| 1191 | (lambda (arg) |
| 1192 | (seq-some (##string-prefix-p % arg) |
| 1193 | magit-log-disable-graph-hack-args)) |
| 1194 | args)) |
| 1195 | (magit-git-string "rev-list" "--count" |
| 1196 | "--first-parent" args revs)))) |
| 1197 | (setq revs (if (< (string-to-number count) limit) |
| 1198 | revs |
| 1199 | (format "%s~%s..%s" revs limit revs)))) |
| 1200 | (let ((delay (cl-find-if (##member % '("++header" "--patch" "--stat")) |
| 1201 | args))) |
| 1202 | (setq magit-section-inhibit-markers (if delay 'delay t)) |
| 1203 | (setq magit-section-insert-in-reverse (not delay))) |
| 1204 | (magit-insert-section (logbuf) |
| 1205 | (magit--insert-log t revs args files)))) |
| 1206 | |
| 1207 | (defvar-local magit-log--color-graph nil) |
| 1208 | |
| 1209 | (defun magit-log--maybe-drop-color-graph (args limit) |
| 1210 | (cond ((not (member "--color" args)) |
| 1211 | (setq magit-log--color-graph nil)) |
| 1212 | ((cond ((not (member "--graph" args)) t) |
| 1213 | ((not magit-log-color-graph-limit) nil) |
| 1214 | ((not limit) |
| 1215 | (message "Dropping --color because -n isn't set (see %s)" |
| 1216 | 'magit-log-color-graph-limit)) |
| 1217 | ((> limit magit-log-color-graph-limit) |
| 1218 | (message "Dropping --color because -n is larger than %s" |
| 1219 | 'magit-log-color-graph-limit))) |
| 1220 | (setq args (remove "--color" args)) |
| 1221 | (setq magit-log--color-graph nil)) |
| 1222 | ((setq magit-log--color-graph t))) |
| 1223 | args) |
| 1224 | |
| 1225 | (cl-defmethod magit-buffer-value (&context (major-mode magit-log-mode)) |
| 1226 | (append magit-buffer-log-revisions |
| 1227 | (if (and magit-buffer-log-revisions magit-buffer-log-files) |
| 1228 | (cons "--" magit-buffer-log-files) |
| 1229 | magit-buffer-log-files))) |
| 1230 | |
| 1231 | (defun magit-log-header-line-arguments (revs args files) |
| 1232 | "Return string describing some of the used arguments." |
| 1233 | (mapconcat (##if (string-search " " %) (prin1 %) %) |
| 1234 | `("git" "log" ,@args ,@revs "--" ,@files) |
| 1235 | " ")) |
| 1236 | |
| 1237 | (defun magit-log-header-line-sentence (revs args files) |
| 1238 | "Return string containing all arguments." |
| 1239 | (concat "Commits in " |
| 1240 | (string-join revs " ") |
| 1241 | (and (member "--reverse" args) |
| 1242 | " in reverse") |
| 1243 | (and files (concat " touching " |
| 1244 | (string-join files " "))) |
| 1245 | (seq-some (##and (string-prefix-p "-L" %) |
| 1246 | (concat " " %)) |
| 1247 | args))) |
| 1248 | |
| 1249 | (defun magit-insert-log (revs &optional args files) |
| 1250 | (declare (obsolete magit--insert-log "Magit 4.0.0")) |
| 1251 | (magit--insert-log nil revs args files)) |
| 1252 | |
| 1253 | (defconst magit-log-heading-format |
| 1254 | ;; See `magit-log-heading-re'. |
| 1255 | (concat "--format=" |
| 1256 | "%s" ; 4 graph --graph |
| 1257 | "%%h%%x0c" ; 1 %h hash |
| 1258 | "%s%%x0c" ; 3 %D refs --decorate |
| 1259 | "%s%%x0c" ; 7 %G? gpg --show-signature |
| 1260 | "%%aN%%x0c" ; 5 %aN author |
| 1261 | "%s%%x0c" ; 6 %at date magit-log-margin-show-committer-date |
| 1262 | "%s%%x0c" ; 12 %() trailers magit-log-trailer-labels |
| 1263 | "%%s" ; 2 %s msg |
| 1264 | "%s")) ; \n .. headers magit-log-revision-headers-format |
| 1265 | |
| 1266 | (defun magit--insert-log (keep-error revs &optional args files) |
| 1267 | "Insert a log section. |
| 1268 | Do not add this to a hook variable." |
| 1269 | (declare (indent defun)) |
| 1270 | (setq magit-section-preserve-visibility t) ; TODO do it here? |
| 1271 | (let ((magit-git-global-arguments |
| 1272 | (remove "--literal-pathspecs" magit-git-global-arguments))) |
| 1273 | (magit--git-wash (apply-partially #'magit-log-wash-log 'log) keep-error |
| 1274 | "log" |
| 1275 | (format magit-log-heading-format |
| 1276 | (if (and (member "--left-right" args) |
| 1277 | (not (member "--graph" args))) |
| 1278 | "%m " |
| 1279 | "") |
| 1280 | (if (member "--decorate" args) "%D" "") |
| 1281 | (if (not (member "--show-signature" args)) |
| 1282 | "" |
| 1283 | (setq args (remove "--show-signature" args)) |
| 1284 | (let ((limit (magit-log-get-commit-limit args))) |
| 1285 | (cond |
| 1286 | ((not limit) |
| 1287 | (message |
| 1288 | "Dropping --show-signature because -n isn't set (see %s)" |
| 1289 | 'magit-log-show-signatures-limit) |
| 1290 | "") |
| 1291 | ((> limit magit-log-show-signatures-limit) |
| 1292 | (message |
| 1293 | "Dropping --show-signature because -n is larger than %s" |
| 1294 | 'magit-log-show-signatures-limit) |
| 1295 | "") |
| 1296 | ("%G?")))) |
| 1297 | (if magit-log-margin-show-committer-date "%ct" "%at") |
| 1298 | (if magit-log-trailer-labels |
| 1299 | (format "%%(trailers:%s%s)" |
| 1300 | (if (not (equal (cdr magit-log-trailer-labels) "")) |
| 1301 | (concat (cdr magit-log-trailer-labels) ",") |
| 1302 | "") |
| 1303 | "unfold=true,separator=,key_value_separator=") |
| 1304 | "") |
| 1305 | (if (member "++header" args) |
| 1306 | (if (member "--graph" (setq args (remove "++header" args))) |
| 1307 | (concat "\n" magit-log-revision-headers-format "\n") |
| 1308 | (concat "\n" magit-log-revision-headers-format "\n")) |
| 1309 | "")) |
| 1310 | (progn |
| 1311 | (when-let ((order (seq-find (##string-match "^\\+\\+order=\\(.+\\)$" %) |
| 1312 | args))) |
| 1313 | (setq args (cons (format "--%s-order" (match-str 1 order)) |
| 1314 | (remove order args)))) |
| 1315 | (when (member "--decorate" args) |
| 1316 | (setq args (cons "--decorate=full" (remove "--decorate" args)))) |
| 1317 | (when (member "--reverse" args) |
| 1318 | (setq args (remove "--graph" args))) |
| 1319 | (setq args (magit-diff--maybe-add-stat-arguments args)) |
| 1320 | args) |
| 1321 | "--use-mailmap" "--no-prefix" revs "--" files))) |
| 1322 | |
| 1323 | (cl-defmethod magit-menu-common-value ((_section magit-commit-section)) |
| 1324 | (or (magit-diff--region-range) |
| 1325 | (oref (magit-current-section) value))) |
| 1326 | |
| 1327 | (defvar-keymap magit-commit-section-map |
| 1328 | :doc "Keymap for `commit' sections." |
| 1329 | "<remap> <magit-visit-thing>" #'magit-show-commit |
| 1330 | "<3>" (magit-menu-item "Apply %x" #'magit-cherry-apply) |
| 1331 | "<2>" (magit-menu-item "Show commit %x" #'magit-show-commit |
| 1332 | '(:visible (not (region-active-p)))) |
| 1333 | "<1>" (magit-menu-item "Diff %x" #'magit-diff-range |
| 1334 | '(:visible (region-active-p)))) |
| 1335 | |
| 1336 | (defvar-keymap magit-module-commit-section-map |
| 1337 | :doc "Keymap for `module-commit' sections." |
| 1338 | :parent magit-commit-section-map) |
| 1339 | |
| 1340 | (defconst magit-log-heading-re |
| 1341 | ;; Use a form feed instead of a null byte as the delimiter because using |
| 1342 | ;; the latter interferes with the graph prefix when ++header is used. |
| 1343 | (concat "^" |
| 1344 | "\\(?4:[-_/|\\*o<>. ]*\\)" ; graph |
| 1345 | "\\(?1:[0-9a-fA-F]+\\)?" ; hash |
| 1346 | "\\(?3:[^\n]+\\)?" ; refs |
| 1347 | "\\(?7:[BGUXYREN]\\)?" ; gpg |
| 1348 | "\\(?5:[^\n]*\\)" ; author |
| 1349 | ;; Prior to Git v2.19.0, "git rebase -i --root" corrupted the |
| 1350 | ;; root's author date. Keep date optional because even though |
| 1351 | ;; we no longer support such old releases, the roots they create |
| 1352 | ;; may live on. |
| 1353 | "\\(?6:[^\n]*\\)" ; date |
| 1354 | "\\(?12:[^\n]+\\)?" ; trailers |
| 1355 | "\\(?2:.*\\)$")) ; msg |
| 1356 | |
| 1357 | (defconst magit-log-cherry-re |
| 1358 | (concat "^" |
| 1359 | "\\(?8:[-+]\\) " ; cherry |
| 1360 | "\\(?1:[0-9a-fA-F]+\\) " ; hash |
| 1361 | "\\(?2:.*\\)$")) ; msg |
| 1362 | |
| 1363 | (defconst magit-log-module-re |
| 1364 | (concat "^" |
| 1365 | "\\(?:\\(?11:[<>]\\) \\)?" ; side |
| 1366 | "\\(?1:[0-9a-fA-F]+\\) " ; hash |
| 1367 | "\\(?2:.*\\)$")) ; msg |
| 1368 | |
| 1369 | (defconst magit-log-bisect-vis-re |
| 1370 | (concat "^" |
| 1371 | "\\(?4:[-_/|\\*o<>. ]*\\)" ; graph |
| 1372 | "\\(?1:[0-9a-fA-F]+\\)?\0" ; hash |
| 1373 | "\\(?3:[^\0\n]+\\)?\0" ; refs |
| 1374 | "\\(?2:.*\\)$")) ; msg |
| 1375 | |
| 1376 | (defconst magit-log-bisect-log-re |
| 1377 | (concat "^# " |
| 1378 | "\\(?3:[^: \n]+:\\) " ; "refs" |
| 1379 | "\\[\\(?1:[^]\n]+\\)\\] " ; hash |
| 1380 | "\\(?2:.*\\)$")) ; msg |
| 1381 | |
| 1382 | (defconst magit-log-reflog-re |
| 1383 | (concat "^" |
| 1384 | "\\(?1:[^\0\n]+\\)\0" ; hash |
| 1385 | "\\(?5:[^\0\n]*\\)\0" ; author |
| 1386 | "\\(?:\\(?:[^@\n]+@{\\(?6:[^}\n]+\\)}\0" ; date |
| 1387 | ;;; refsub |
| 1388 | "\\(?10:merge \\|autosave \\|restart \\|rewritten \\|[^:\n]+: \\)?" |
| 1389 | "\\(?2:.*\\)\\)\\|\0\\)$")) ; msg |
| 1390 | |
| 1391 | (defconst magit-reflog-subject-re |
| 1392 | (concat "\\(?1:[^ ]+\\) ?" ; command |
| 1393 | "\\(?2:\\(?: ?-[^ ]+\\)+\\)?" ; option |
| 1394 | "\\(?: ?(\\(?3:[^)]+\\))\\)?")) ; type |
| 1395 | |
| 1396 | (defconst magit-log-stash-re |
| 1397 | (concat "^" |
| 1398 | "\\(?1:[^\0\n]+\\)\0" ; "hash" |
| 1399 | "\\(?5:[^\0\n]*\\)\0" ; author |
| 1400 | "\\(?6:[^\0\n]+\\)\0" ; date |
| 1401 | "\\(?2:.*\\)$")) ; msg |
| 1402 | |
| 1403 | (defvar magit-log-count nil) |
| 1404 | |
| 1405 | (defun magit-log-wash-log (style args) |
| 1406 | (setq args (flatten-tree args)) |
| 1407 | (when (if (derived-mode-p 'magit-log-mode) |
| 1408 | magit-log--color-graph |
| 1409 | (and (member "--graph" args) |
| 1410 | (member "--color" args))) |
| 1411 | (let ((ansi-color-apply-face-function |
| 1412 | (lambda (beg end face) |
| 1413 | (put-text-property beg end 'font-lock-face |
| 1414 | (or face 'magit-log-graph))))) |
| 1415 | (ansi-color-apply-on-region (point-min) (point-max)))) |
| 1416 | (when (eq style 'cherry) |
| 1417 | (reverse-region (point-min) (point-max))) |
| 1418 | (let ((magit-log-count 0)) |
| 1419 | (when (looking-at "^\\.\\.\\.") |
| 1420 | (magit-delete-line)) |
| 1421 | (magit-wash-sequence (apply-partially #'magit-log-wash-rev style |
| 1422 | (magit-abbrev-length))) |
| 1423 | (if (derived-mode-p 'magit-log-mode 'magit-reflog-mode) |
| 1424 | (when (eq magit-log-count (magit-log-get-commit-limit)) |
| 1425 | (magit-insert-section (longer) |
| 1426 | (insert-text-button |
| 1427 | (substitute-command-keys |
| 1428 | (format "Type \\<%s>\\[%s] to show more history" |
| 1429 | 'magit-log-mode-map |
| 1430 | 'magit-log-double-commit-limit)) |
| 1431 | 'action (lambda (_button) |
| 1432 | (magit-log-double-commit-limit)) |
| 1433 | 'follow-link t |
| 1434 | 'mouse-face 'magit-section-highlight))) |
| 1435 | (insert ?\n)))) |
| 1436 | |
| 1437 | (cl-defun magit-log-wash-rev (style abbrev) |
| 1438 | (when (derived-mode-p 'magit-log-mode 'magit-reflog-mode) |
| 1439 | (incf magit-log-count)) |
| 1440 | (looking-at (pcase style |
| 1441 | ('log magit-log-heading-re) |
| 1442 | ('cherry magit-log-cherry-re) |
| 1443 | ('module magit-log-module-re) |
| 1444 | ('reflog magit-log-reflog-re) |
| 1445 | ('stash magit-log-stash-re) |
| 1446 | ('bisect-vis magit-log-bisect-vis-re) |
| 1447 | ('bisect-log magit-log-bisect-log-re))) |
| 1448 | (let* ((hash (match-str 1)) |
| 1449 | (msg (match-str 2)) |
| 1450 | (refs (match-str 3)) |
| 1451 | (refs (and refs (magit-format-ref-labels refs))) |
| 1452 | (graph (match-string 4)) |
| 1453 | (author (match-str 5)) |
| 1454 | (date (match-str 6)) |
| 1455 | (gpg (match-str 7)) |
| 1456 | (cherry (match-str 8)) |
| 1457 | (refsub (match-str 10)) |
| 1458 | (side (match-str 11)) |
| 1459 | (trailers (match-str 12)) |
| 1460 | (trailers (and trailers |
| 1461 | (funcall (car magit-log-trailer-labels) |
| 1462 | (mapcar (##split-string % "") |
| 1463 | (split-string trailers ""))))) |
| 1464 | (align (or (eq style 'cherry) |
| 1465 | (not (member "--stat" magit-buffer-log-args)))) |
| 1466 | (non-graph-re (if (eq style 'bisect-vis) |
| 1467 | magit-log-bisect-vis-re |
| 1468 | magit-log-heading-re))) |
| 1469 | (magit-delete-line) |
| 1470 | ;; If the reflog entries have been pruned, the output of `git |
| 1471 | ;; reflog show' includes a partial line that refers to the hash |
| 1472 | ;; of the youngest expired reflog entry. |
| 1473 | (when (and (eq style 'reflog) (not date)) |
| 1474 | (cl-return-from magit-log-wash-rev t)) |
| 1475 | (magit-insert-section |
| 1476 | ((eval (pcase style |
| 1477 | ('stash 'stash) |
| 1478 | ('module 'module-commit) |
| 1479 | (_ 'commit))) |
| 1480 | hash) |
| 1481 | (setq hash (propertize (if (eq style 'bisect-log) |
| 1482 | (magit-rev-parse "--short" hash) |
| 1483 | hash) |
| 1484 | 'font-lock-face |
| 1485 | (pcase (and gpg (aref gpg 0)) |
| 1486 | (?G 'magit-signature-good) |
| 1487 | (?B 'magit-signature-bad) |
| 1488 | (?U 'magit-signature-untrusted) |
| 1489 | (?X 'magit-signature-expired) |
| 1490 | (?Y 'magit-signature-expired-key) |
| 1491 | (?R 'magit-signature-revoked) |
| 1492 | (?E 'magit-signature-error) |
| 1493 | (?N 'magit-hash) |
| 1494 | (_ 'magit-hash)))) |
| 1495 | (when cherry |
| 1496 | (when (and (derived-mode-p 'magit-refs-mode) |
| 1497 | magit-refs-show-commit-count) |
| 1498 | (insert (make-string (1- magit-refs-focus-column-width) ?\s))) |
| 1499 | (insert (propertize cherry 'font-lock-face |
| 1500 | (if (string= cherry "-") |
| 1501 | 'magit-cherry-equivalent |
| 1502 | 'magit-cherry-unmatched))) |
| 1503 | (insert ?\s)) |
| 1504 | (when side |
| 1505 | (insert (propertize side 'font-lock-face |
| 1506 | (if (string= side "<") |
| 1507 | 'magit-cherry-equivalent |
| 1508 | 'magit-cherry-unmatched))) |
| 1509 | (insert ?\s)) |
| 1510 | (when align |
| 1511 | (insert hash ?\s)) |
| 1512 | (when graph |
| 1513 | (insert graph)) |
| 1514 | (unless align |
| 1515 | (insert hash ?\s)) |
| 1516 | (unless magit-log-show-refname-after-summary |
| 1517 | (when refs |
| 1518 | (insert refs ?\s)) |
| 1519 | (when trailers |
| 1520 | (insert trailers ?\s))) |
| 1521 | (when (eq style 'reflog) |
| 1522 | (insert (format "%-2s " (1- magit-log-count))) |
| 1523 | (when refsub |
| 1524 | (insert (magit-reflog-format-subject |
| 1525 | (substring refsub 0 |
| 1526 | (if (string-search ":" refsub) -2 -1)))))) |
| 1527 | (insert (magit-log--wash-summary msg)) |
| 1528 | (when magit-log-show-refname-after-summary |
| 1529 | (when refs |
| 1530 | (insert ?\s refs)) |
| 1531 | (when trailers |
| 1532 | (insert ?\s trailers))) |
| 1533 | (insert ?\n) |
| 1534 | (when (memq style '(log reflog stash)) |
| 1535 | (goto-char (line-beginning-position)) |
| 1536 | (when (and refsub |
| 1537 | (string-match "\\`\\([^ ]\\) \\+\\(..\\)\\(..\\)" date)) |
| 1538 | (setq date (+ (string-to-number (match-str 1 date)) |
| 1539 | (* (string-to-number (match-str 2 date)) 60 60) |
| 1540 | (* (string-to-number (match-str 3 date)) 60)))) |
| 1541 | (magit-log-format-margin hash author date)) |
| 1542 | (when (and (eq style 'cherry) |
| 1543 | (magit--right-margin-active)) |
| 1544 | (apply #'magit-log-format-margin hash |
| 1545 | (split-string (magit-rev-format "%aN%x00%ct" hash) "\0"))) |
| 1546 | (when (and graph |
| 1547 | (not (eobp)) |
| 1548 | (not (looking-at non-graph-re))) |
| 1549 | (when (looking-at "") |
| 1550 | (magit-insert-heading) |
| 1551 | (delete-char 1) |
| 1552 | (magit-insert-section (commit-header) |
| 1553 | (forward-line) |
| 1554 | (magit-insert-heading) |
| 1555 | (re-search-forward "") |
| 1556 | (delete-char -1) |
| 1557 | (forward-char) |
| 1558 | (insert ?\n)) |
| 1559 | (delete-char 1)) |
| 1560 | (if (looking-at "^\\(---\\|\n\s\\|\ndiff\\)") |
| 1561 | (let ((limit (save-excursion |
| 1562 | (and (re-search-forward non-graph-re nil t) |
| 1563 | (match-beginning 0))))) |
| 1564 | (unless (oref magit-insert-section--current content) |
| 1565 | (magit-insert-heading)) |
| 1566 | (delete-char (if (looking-at "\n") 1 4)) |
| 1567 | (magit-diff-wash-diffs (list "--stat") limit)) |
| 1568 | (when align |
| 1569 | (setq align (make-string (1+ abbrev) ?\s))) |
| 1570 | (when (and (not (eobp)) (not (looking-at non-graph-re))) |
| 1571 | (when align |
| 1572 | (setq align (make-string (1+ abbrev) ?\s))) |
| 1573 | (while (and (not (eobp)) (not (looking-at non-graph-re))) |
| 1574 | (when align |
| 1575 | (save-excursion (insert align))) |
| 1576 | (forward-line) |
| 1577 | (magit-make-margin-overlay)) |
| 1578 | ;; When "--format" is used and its value isn't one of the |
| 1579 | ;; predefined formats, then "git log" does not insert a |
| 1580 | ;; separator line. |
| 1581 | (save-excursion |
| 1582 | (forward-line -1) |
| 1583 | (looking-at "[-_/|\\*o<>. ]*")) |
| 1584 | (setq graph (match-string 0)) |
| 1585 | (unless (string-match-p "[/\\.]" graph) |
| 1586 | (insert graph ?\n))))))) |
| 1587 | t) |
| 1588 | |
| 1589 | (defun magit-log--wash-summary (summary) |
| 1590 | (with-temp-buffer |
| 1591 | (save-excursion (insert summary)) |
| 1592 | (run-hook-wrapped 'magit-log-wash-summary-hook |
| 1593 | (lambda (fn) (prog1 nil (save-excursion (funcall fn))))) |
| 1594 | (buffer-string))) |
| 1595 | |
| 1596 | (defun magit-log-maybe-show-more-commits (section) |
| 1597 | "When point is at the end of a log buffer, insert more commits. |
| 1598 | |
| 1599 | Log buffers end with a button \"Type + to show more history\". |
| 1600 | When the use of a section movement command puts point on that |
| 1601 | button, then automatically show more commits, without the user |
| 1602 | having to press \"+\". |
| 1603 | |
| 1604 | This function is called by `magit-section-movement-hook' and |
| 1605 | exists mostly for backward compatibility reasons." |
| 1606 | (when (and (eq (oref section type) 'longer) |
| 1607 | magit-log-auto-more) |
| 1608 | (magit-log-double-commit-limit) |
| 1609 | (forward-line -1) |
| 1610 | (magit-section-forward))) |
| 1611 | |
| 1612 | (add-hook 'magit-section-movement-hook #'magit-log-maybe-show-more-commits) |
| 1613 | (add-hook 'magit-mouse-set-point-hook #'magit-log-maybe-show-more-commits) |
| 1614 | |
| 1615 | (defvar magit--update-revision-buffer nil) |
| 1616 | |
| 1617 | (defun magit-log-maybe-update-revision-buffer (&optional _) |
| 1618 | "When moving in a log or cherry buffer, update the revision buffer. |
| 1619 | If there is no revision buffer in the same frame, then do nothing. |
| 1620 | See also info node `(magit)Section Movement'." |
| 1621 | (when (derived-mode-p 'magit-log-mode 'magit-cherry-mode 'magit-reflog-mode) |
| 1622 | (magit--maybe-update-revision-buffer))) |
| 1623 | |
| 1624 | (add-hook 'magit-section-movement-hook #'magit-log-maybe-update-revision-buffer) |
| 1625 | (add-hook 'magit-mouse-set-point-hook #'magit-log-maybe-update-revision-buffer) |
| 1626 | |
| 1627 | (defun magit--maybe-update-revision-buffer () |
| 1628 | (when-let* ((commit (magit-section-value-if 'commit)) |
| 1629 | (buffer (magit-get-mode-buffer 'magit-revision-mode nil t))) |
| 1630 | (if magit--update-revision-buffer |
| 1631 | (setq magit--update-revision-buffer (list commit buffer)) |
| 1632 | (setq magit--update-revision-buffer (list commit buffer)) |
| 1633 | (run-with-idle-timer |
| 1634 | magit-update-other-window-delay nil |
| 1635 | (let ((args (let ((magit-direct-use-buffer-arguments 'selected)) |
| 1636 | (magit-show-commit--arguments)))) |
| 1637 | (lambda () |
| 1638 | (pcase-let ((`(,rev ,buf) magit--update-revision-buffer)) |
| 1639 | (setq magit--update-revision-buffer nil) |
| 1640 | (when (buffer-live-p buf) |
| 1641 | (let ((magit-display-buffer-noselect t)) |
| 1642 | (apply #'magit-show-commit rev args)))) |
| 1643 | (setq magit--update-revision-buffer nil))))))) |
| 1644 | |
| 1645 | (defvar magit--update-blob-buffer nil) |
| 1646 | |
| 1647 | (defun magit-log-maybe-update-blob-buffer (&optional _) |
| 1648 | "When moving in a log or cherry buffer, update the blob buffer. |
| 1649 | If there is no blob buffer in the same frame, then do nothing. |
| 1650 | See also info node `(magit)Section Movement'." |
| 1651 | (when (derived-mode-p 'magit-log-mode 'magit-cherry-mode 'magit-reflog-mode) |
| 1652 | (magit--maybe-update-blob-buffer))) |
| 1653 | |
| 1654 | (defun magit--maybe-update-blob-buffer () |
| 1655 | (when-let* ((commit (magit-section-value-if 'commit)) |
| 1656 | (buffer (seq-find (##with-current-buffer % |
| 1657 | (eq revert-buffer-function |
| 1658 | 'magit-revert-rev-file-buffer)) |
| 1659 | (mapcar #'window-buffer (window-list))))) |
| 1660 | (if magit--update-blob-buffer |
| 1661 | (setq magit--update-blob-buffer (list commit buffer)) |
| 1662 | (setq magit--update-blob-buffer (list commit buffer)) |
| 1663 | (run-with-idle-timer |
| 1664 | magit-update-other-window-delay nil |
| 1665 | (lambda () |
| 1666 | (pcase-let ((`(,rev ,buf) magit--update-blob-buffer)) |
| 1667 | (setq magit--update-blob-buffer nil) |
| 1668 | (when (buffer-live-p buf) |
| 1669 | (with-selected-window (get-buffer-window buf) |
| 1670 | (with-current-buffer buf |
| 1671 | (save-excursion |
| 1672 | (magit-blob-visit (magit-rev-parse rev) |
| 1673 | (magit-file-relative-name |
| 1674 | magit-buffer-file-name)))))))))))) |
| 1675 | |
| 1676 | (defun magit-log-goto-commit-section (rev) |
| 1677 | (let ((abbrev (magit-rev-format "%h" rev))) |
| 1678 | (when-let ((section (seq-find (##equal (oref % value) abbrev) |
| 1679 | (oref magit-root-section children)))) |
| 1680 | (goto-char (oref section start))))) |
| 1681 | |
| 1682 | (defun magit-log-goto-same-commit () |
| 1683 | (when (and magit-previous-section |
| 1684 | (magit-section-match '(commit branch) |
| 1685 | magit-previous-section)) |
| 1686 | (magit-log-goto-commit-section (oref magit-previous-section value)))) |
| 1687 | |
| 1688 | ;;; Log Margin |
| 1689 | |
| 1690 | (defvar-local magit-log-margin-show-shortstat nil) |
| 1691 | |
| 1692 | (transient-define-suffix magit-toggle-log-margin-style () |
| 1693 | "Toggle between the regular and the shortstat margin style. |
| 1694 | The shortstat style is experimental and rather slow." |
| 1695 | :description "Toggle shortstat" |
| 1696 | :key "x" |
| 1697 | :transient t |
| 1698 | (interactive) |
| 1699 | (setq magit-log-margin-show-shortstat |
| 1700 | (not magit-log-margin-show-shortstat)) |
| 1701 | (magit-set-buffer-margins nil t)) |
| 1702 | |
| 1703 | (defun magit-log-format-margin (rev author date) |
| 1704 | (when (magit--right-margin-option) |
| 1705 | (if magit-log-margin-show-shortstat |
| 1706 | (magit-log-format-shortstat-margin rev) |
| 1707 | (magit-log-format-author-margin author date)))) |
| 1708 | |
| 1709 | (defun magit-log-format-author-margin (author date) |
| 1710 | (pcase-let ((`(,_ ,style ,width ,details ,details-width) |
| 1711 | (or magit--right-margin-config |
| 1712 | (symbol-value (magit--right-margin-option)) |
| 1713 | (error "No margin format specified for %s" major-mode)))) |
| 1714 | (magit-make-margin-overlay |
| 1715 | (concat (and details |
| 1716 | (concat (magit--propertize-face |
| 1717 | (truncate-string-to-width |
| 1718 | (or author "") |
| 1719 | details-width |
| 1720 | nil ?\s |
| 1721 | (magit--ellipsis 'margin)) |
| 1722 | 'magit-log-author) |
| 1723 | " ")) |
| 1724 | (magit--propertize-face |
| 1725 | (if (stringp style) |
| 1726 | (format-time-string |
| 1727 | style |
| 1728 | (seconds-to-time (string-to-number date))) |
| 1729 | (pcase-let* ((abbr (eq style 'age-abbreviated)) |
| 1730 | (`(,cnt ,unit) (magit--age date abbr))) |
| 1731 | (format (format (if abbr "%%2d%%-%dc" "%%2d %%-%ds") |
| 1732 | (- width (if details (1+ details-width) 0))) |
| 1733 | cnt unit))) |
| 1734 | 'magit-log-date))))) |
| 1735 | |
| 1736 | (defun magit-log-format-shortstat-margin (rev) |
| 1737 | (magit-make-margin-overlay |
| 1738 | (if-let ((line (and rev (magit-git-string |
| 1739 | "show" "--format=" "--shortstat" rev)))) |
| 1740 | (if (string-match "\ |
| 1741 | \\([0-9]+\\) files? changed, \ |
| 1742 | \\(?:\\([0-9]+\\) insertions?(\\+)\\)?\ |
| 1743 | \\(?:\\(?:, \\)?\\([0-9]+\\) deletions?(-)\\)?\\'" line) |
| 1744 | (magit-bind-match-strings (files add del) line |
| 1745 | (format |
| 1746 | "%5s %5s%4s" |
| 1747 | (if add |
| 1748 | (magit--propertize-face (format "%s+" add) |
| 1749 | 'magit-diffstat-added) |
| 1750 | "") |
| 1751 | (if del |
| 1752 | (magit--propertize-face (format "%s-" del) |
| 1753 | 'magit-diffstat-removed) |
| 1754 | "") |
| 1755 | files)) |
| 1756 | "") |
| 1757 | ""))) |
| 1758 | |
| 1759 | (defun magit-log-margin-width (style details details-width) |
| 1760 | (if magit-log-margin-show-shortstat |
| 1761 | 16 |
| 1762 | (+ (if details (1+ details-width) 0) |
| 1763 | (if (stringp style) |
| 1764 | (length (format-time-string style)) |
| 1765 | (+ 2 ; two digits |
| 1766 | 1 ; trailing space |
| 1767 | (if (eq style 'age-abbreviated) |
| 1768 | 1 ; single character |
| 1769 | (+ 1 ; gap after digits |
| 1770 | (apply #'max (mapcar (##max (length (nth 1 %)) |
| 1771 | (length (nth 2 %))) |
| 1772 | magit--age-spec))))))))) |
| 1773 | |
| 1774 | ;;; Select Mode |
| 1775 | |
| 1776 | (defvar-keymap magit-log-select-mode-map |
| 1777 | :doc "Keymap for `magit-log-select-mode'." |
| 1778 | :parent magit-log-mode-map |
| 1779 | "C-c C-b" #'undefined |
| 1780 | "C-c C-f" #'undefined |
| 1781 | "." #'magit-log-select-pick |
| 1782 | "e" #'magit-log-select-pick |
| 1783 | "C-c C-c" #'magit-log-select-pick |
| 1784 | "q" #'magit-log-select-quit |
| 1785 | "C-c C-k" #'magit-log-select-quit) |
| 1786 | (put 'magit-log-select-pick :advertised-binding [?\C-c ?\C-c]) |
| 1787 | (put 'magit-log-select-quit :advertised-binding [?\C-c ?\C-k]) |
| 1788 | |
| 1789 | (define-derived-mode magit-log-select-mode magit-log-mode "Magit Select" |
| 1790 | "Mode for selecting a commit from history. |
| 1791 | |
| 1792 | This mode is documented in info node `(magit)Select from Log'. |
| 1793 | |
| 1794 | \\<magit-mode-map>\ |
| 1795 | Type \\[magit-refresh] to refresh the current buffer. |
| 1796 | Type \\[magit-visit-thing] or \\[magit-diff-show-or-scroll-up] \ |
| 1797 | to visit the commit at point. |
| 1798 | |
| 1799 | \\<magit-log-select-mode-map>\ |
| 1800 | Type \\[magit-log-select-pick] to select the commit at point. |
| 1801 | Type \\[magit-log-select-quit] to abort without selecting a commit." |
| 1802 | :group 'magit-log |
| 1803 | (magit-hack-dir-local-variables)) |
| 1804 | |
| 1805 | (put 'magit-log-select-mode 'magit-log-default-arguments |
| 1806 | '("--graph" "-n256" "--decorate")) |
| 1807 | |
| 1808 | (defun magit-log-select-setup-buffer (revs args select-section) |
| 1809 | (magit-setup-buffer #'magit-log-select-mode nil |
| 1810 | :select-section select-section |
| 1811 | (magit-buffer-log-revisions revs) |
| 1812 | (magit-buffer-log-args args))) |
| 1813 | |
| 1814 | (defun magit-log-select-refresh-buffer () |
| 1815 | (setq magit-section-inhibit-markers t) |
| 1816 | (setq magit-section-insert-in-reverse t) |
| 1817 | (magit-insert-section (logbuf) |
| 1818 | (magit--insert-log t magit-buffer-log-revisions |
| 1819 | (magit-log--maybe-drop-color-graph |
| 1820 | magit-buffer-log-args |
| 1821 | (magit-log-get-commit-limit))))) |
| 1822 | |
| 1823 | (cl-defmethod magit-buffer-value (&context (major-mode magit-log-select-mode)) |
| 1824 | magit-buffer-log-revisions) |
| 1825 | |
| 1826 | (defvar-local magit-log-select-pick-function nil) |
| 1827 | (defvar-local magit-log-select-quit-function nil) |
| 1828 | |
| 1829 | (defun magit-log-select (pick &optional msg quit branch args initial) |
| 1830 | (declare (indent defun)) |
| 1831 | (unless initial |
| 1832 | (setq initial (magit-commit-at-point))) |
| 1833 | (magit-log-select-setup-buffer |
| 1834 | (or branch (magit-get-current-branch) "HEAD") |
| 1835 | (append args |
| 1836 | (car (magit-log--get-value 'magit-log-select-mode 'direct))) |
| 1837 | (lambda () |
| 1838 | (if initial |
| 1839 | (magit-log-goto-commit-section initial) |
| 1840 | (while-let* ((rev (magit-section-value-if 'commit)) |
| 1841 | (_(string-match-p "\\`\\(squash!\\|fixup!\\|amend!\\)" |
| 1842 | (magit-rev-format "%s" rev))) |
| 1843 | (section (magit-current-section)) |
| 1844 | (next (car (magit-section-siblings section 'next)))) |
| 1845 | (magit-section-goto next))))) |
| 1846 | (setq magit-log-select-pick-function pick) |
| 1847 | (setq magit-log-select-quit-function quit) |
| 1848 | (when magit-log-select-show-usage |
| 1849 | (let ((pick (propertize (substitute-command-keys |
| 1850 | "\\[magit-log-select-pick]") |
| 1851 | 'font-lock-face |
| 1852 | 'magit-header-line-key)) |
| 1853 | (quit (propertize (substitute-command-keys |
| 1854 | "\\[magit-log-select-quit]") |
| 1855 | 'font-lock-face |
| 1856 | 'magit-header-line-key))) |
| 1857 | (setq msg (format-spec |
| 1858 | (if msg |
| 1859 | (if (string-suffix-p "," msg) |
| 1860 | (concat msg " or %q to abort") |
| 1861 | msg) |
| 1862 | "Type %p to select commit at point, or %q to abort") |
| 1863 | `((?p . ,pick) |
| 1864 | (?q . ,quit))))) |
| 1865 | (magit--add-face-text-property |
| 1866 | 0 (length msg) 'magit-header-line-log-select t msg) |
| 1867 | (when (memq magit-log-select-show-usage '(both header-line)) |
| 1868 | (magit-set-header-line-format msg)) |
| 1869 | (when (memq magit-log-select-show-usage '(both echo-area)) |
| 1870 | (message "%s" (substring-no-properties msg))))) |
| 1871 | |
| 1872 | (defun magit-log-select-pick () |
| 1873 | "Select the commit at point and act on it. |
| 1874 | Call `magit-log-select-pick-function' with the selected |
| 1875 | commit as argument." |
| 1876 | (interactive) |
| 1877 | (let ((fun magit-log-select-pick-function) |
| 1878 | (rev (magit-commit-at-point))) |
| 1879 | (magit-mode-bury-buffer 'kill) |
| 1880 | (funcall fun rev))) |
| 1881 | |
| 1882 | (defun magit-log-select-quit () |
| 1883 | "Abort selecting a commit, don't act on any commit. |
| 1884 | Call `magit-log-select-quit-function' if set." |
| 1885 | (interactive) |
| 1886 | (let ((fun magit-log-select-quit-function)) |
| 1887 | (magit-mode-bury-buffer 'kill) |
| 1888 | (when fun (funcall fun)))) |
| 1889 | |
| 1890 | ;;; Cherry Mode |
| 1891 | |
| 1892 | (defvar-keymap magit-cherry-mode-map |
| 1893 | :doc "Keymap for `magit-cherry-mode'." |
| 1894 | :parent magit-mode-map |
| 1895 | "q" #'magit-log-bury-buffer |
| 1896 | "L" #'magit-margin-settings) |
| 1897 | |
| 1898 | (define-derived-mode magit-cherry-mode magit-mode "Magit Cherry" |
| 1899 | "Mode for looking at commits not merged upstream. |
| 1900 | |
| 1901 | \\<magit-mode-map>\ |
| 1902 | Type \\[magit-refresh] to refresh the current buffer. |
| 1903 | Type \\[magit-visit-thing] or \\[magit-diff-show-or-scroll-up] \ |
| 1904 | to visit the commit at point. |
| 1905 | |
| 1906 | Type \\[magit-cherry-pick] to apply the commit at point. |
| 1907 | |
| 1908 | \\{magit-cherry-mode-map}" |
| 1909 | :interactive nil |
| 1910 | :group 'magit-log |
| 1911 | (magit-hack-dir-local-variables) |
| 1912 | (setq magit--imenu-group-types 'cherries)) |
| 1913 | |
| 1914 | (defvar-local magit-buffer-cherry-upstream nil) |
| 1915 | (defvar-local magit-buffer-cherry-range nil) |
| 1916 | |
| 1917 | (defun magit-cherry-setup-buffer (head upstream) |
| 1918 | (magit-setup-buffer #'magit-cherry-mode nil |
| 1919 | (magit-buffer-refname head) |
| 1920 | (magit-buffer-cherry-upstream upstream) |
| 1921 | (magit-buffer-cherry-range (concat upstream ".." head)))) |
| 1922 | |
| 1923 | (defun magit-cherry-refresh-buffer () |
| 1924 | (setq magit-section-insert-in-reverse t) |
| 1925 | (magit-insert-section (cherry) |
| 1926 | (magit-run-section-hook 'magit-cherry-sections-hook))) |
| 1927 | |
| 1928 | (cl-defmethod magit-buffer-value (&context (major-mode magit-cherry-mode)) |
| 1929 | magit-buffer-cherry-range) |
| 1930 | |
| 1931 | ;;;###autoload |
| 1932 | (defun magit-cherry (head upstream) |
| 1933 | "Show commits in a branch that are not merged in the upstream branch." |
| 1934 | (interactive |
| 1935 | (let ((head (magit-read-branch "Cherry head"))) |
| 1936 | (list head (magit-read-other-branch "Cherry upstream" head |
| 1937 | (magit-get-upstream-branch head))))) |
| 1938 | (require 'magit) |
| 1939 | (magit-cherry-setup-buffer head upstream)) |
| 1940 | |
| 1941 | (defun magit-insert-cherry-headers () |
| 1942 | "Insert headers appropriate for `magit-cherry-mode' buffers." |
| 1943 | (let ((branch (magit-branch-set-face magit-buffer-refname)) |
| 1944 | (upstream (magit-branch-set-face magit-buffer-cherry-upstream))) |
| 1945 | (magit-insert-head-branch-header branch) |
| 1946 | (magit-insert-upstream-branch-header branch upstream "Upstream: ") |
| 1947 | (insert ?\n))) |
| 1948 | |
| 1949 | (defun magit-insert-cherry-commits () |
| 1950 | "Insert commit sections into a `magit-cherry-mode' buffer." |
| 1951 | (magit-insert-section (cherries) |
| 1952 | (magit-insert-heading t "Cherry commits") |
| 1953 | (magit-git-wash (apply-partially #'magit-log-wash-log 'cherry) |
| 1954 | "cherry" "-v" "--abbrev" |
| 1955 | magit-buffer-cherry-upstream |
| 1956 | magit-buffer-refname))) |
| 1957 | |
| 1958 | ;;; Log Sections |
| 1959 | ;;;; Standard Log Sections |
| 1960 | |
| 1961 | (defvar-keymap magit-log-section-map |
| 1962 | :doc "Keymap for log sections. |
| 1963 | The classes `magit-{unpulled,unpushed,unmerged}-section' derive |
| 1964 | from the abstract `magit-log-section' class. Accordingly this |
| 1965 | keymap is the parent of their keymaps." |
| 1966 | "<remap> <magit-visit-thing>" #'magit-diff-dwim |
| 1967 | "<1>" (magit-menu-item "Visit diff" #'magit-diff-dwim)) |
| 1968 | |
| 1969 | (cl-defmethod magit-section-ident-value ((section magit-unpulled-section)) |
| 1970 | "Return \"..@{push}\". |
| 1971 | \"..@{push}\" cannot be used as the value because that is ambiguous |
| 1972 | if `push.default' does not allow a 1:1 mapping, and many commands |
| 1973 | would fail because of that. But here that does not matter and we |
| 1974 | need an unique value, so we use that string in the pushremote case." |
| 1975 | (let ((value (oref section value))) |
| 1976 | (if (equal value "..@{upstream}") value "..@{push}"))) |
| 1977 | |
| 1978 | (magit-define-section-jumper magit-jump-to-unpulled-from-upstream |
| 1979 | "Unpulled from @{upstream}" unpulled "..@{upstream}" |
| 1980 | magit-insert-unpulled-from-upstream) |
| 1981 | |
| 1982 | (defun magit-insert-unpulled-from-upstream () |
| 1983 | "Insert commits that haven't been pulled from the upstream yet." |
| 1984 | (when-let ((upstream (magit-get-upstream-branch))) |
| 1985 | (magit-insert-section (unpulled "..@{upstream}" t) |
| 1986 | (magit-insert-heading |
| 1987 | (format (propertize "Unpulled from %s." |
| 1988 | 'font-lock-face 'magit-section-heading) |
| 1989 | upstream)) |
| 1990 | (magit--insert-log nil "..@{upstream}" magit-buffer-log-args) |
| 1991 | (magit-log-insert-child-count)))) |
| 1992 | |
| 1993 | (magit-define-section-jumper magit-jump-to-unpulled-from-pushremote |
| 1994 | "Unpulled from <push-remote>" unpulled "..@{push}" |
| 1995 | magit-insert-unpulled-from-pushremote) |
| 1996 | |
| 1997 | (defun magit-insert-unpulled-from-pushremote () |
| 1998 | "Insert commits that haven't been pulled from the push-remote yet." |
| 1999 | (when-let* ((target (magit-get-push-branch)) |
| 2000 | (range (concat ".." target)) |
| 2001 | (_(magit--insert-pushremote-log-p))) |
| 2002 | (magit-insert-section (unpulled range t) |
| 2003 | (magit-insert-heading |
| 2004 | (format (propertize "Unpulled from %s." |
| 2005 | 'font-lock-face 'magit-section-heading) |
| 2006 | (propertize target 'font-lock-face 'magit-branch-remote))) |
| 2007 | (magit--insert-log nil range magit-buffer-log-args) |
| 2008 | (magit-log-insert-child-count)))) |
| 2009 | |
| 2010 | (cl-defmethod magit-section-ident-value ((section magit-unpushed-section)) |
| 2011 | "Return \"..@{push}\". |
| 2012 | \"..@{push}\" cannot be used as the value because that is ambiguous |
| 2013 | if `push.default' does not allow a 1:1 mapping, and many commands |
| 2014 | would fail because of that. But here that does not matter and we |
| 2015 | need an unique value, so we use that string in the pushremote case." |
| 2016 | (let ((value (oref section value))) |
| 2017 | (if (equal value "@{upstream}..") value "@{push}.."))) |
| 2018 | |
| 2019 | (magit-define-section-jumper magit-jump-to-unpushed-to-upstream |
| 2020 | "Unpushed to @{upstream}" unpushed "@{upstream}.." nil |
| 2021 | :if (lambda () |
| 2022 | (or (memq 'magit-insert-unpushed-to-upstream-or-recent |
| 2023 | magit-status-sections-hook) |
| 2024 | (memq 'magit-insert-unpushed-to-upstream |
| 2025 | magit-status-sections-hook))) |
| 2026 | :description (lambda () |
| 2027 | (let ((upstream (magit-get-upstream-branch))) |
| 2028 | (if (or (not upstream) |
| 2029 | (magit-rev-ancestor-p "HEAD" upstream)) |
| 2030 | "Recent commits" |
| 2031 | "Unmerged into upstream")))) |
| 2032 | |
| 2033 | (defun magit-insert-unpushed-to-upstream-or-recent () |
| 2034 | "Insert section showing unpushed or other recent commits. |
| 2035 | If an upstream is configured for the current branch and it is |
| 2036 | behind of the current branch, then show the commits that have |
| 2037 | not yet been pushed into the upstream branch. If no upstream is |
| 2038 | configured or if the upstream is not behind of the current branch, |
| 2039 | then show the last `magit-log-section-commit-count' commits." |
| 2040 | (let ((upstream (magit-get-upstream-branch))) |
| 2041 | (if (or (not upstream) |
| 2042 | (magit-rev-ancestor-p "HEAD" upstream)) |
| 2043 | (magit-insert-recent-commits 'unpushed "@{upstream}..") |
| 2044 | (magit-insert-unpushed-to-upstream)))) |
| 2045 | |
| 2046 | (defun magit-insert-unpushed-to-upstream () |
| 2047 | "Insert commits that haven't been pushed to the upstream yet." |
| 2048 | (when (magit-git-success "rev-parse" "@{upstream}") |
| 2049 | (magit-insert-section (unpushed "@{upstream}..") |
| 2050 | (magit-insert-heading |
| 2051 | (format (propertize "Unmerged into %s." |
| 2052 | 'font-lock-face 'magit-section-heading) |
| 2053 | (magit-get-upstream-branch))) |
| 2054 | (magit--insert-log nil "@{upstream}.." magit-buffer-log-args) |
| 2055 | (magit-log-insert-child-count)))) |
| 2056 | |
| 2057 | (defun magit-insert-recent-commits (&optional type value) |
| 2058 | "Insert section showing recent commits. |
| 2059 | Show the last `magit-log-section-commit-count' commits." |
| 2060 | (let* ((start (format "HEAD~%s" magit-log-section-commit-count)) |
| 2061 | (range (and (magit-rev-verify start) |
| 2062 | (concat start "..HEAD")))) |
| 2063 | (magit-insert-section ((eval (or type 'recent)) |
| 2064 | (or value range) |
| 2065 | t) |
| 2066 | (magit-insert-heading "Recent commits") |
| 2067 | (magit--insert-log nil |
| 2068 | (and (member "--graph" magit-buffer-log-args) range) |
| 2069 | (cons (format "-n%d" magit-log-section-commit-count) |
| 2070 | (seq-remove (##string-prefix-p "-n" %) |
| 2071 | magit-buffer-log-args)))))) |
| 2072 | |
| 2073 | (magit-define-section-jumper magit-jump-to-unpushed-to-pushremote |
| 2074 | "Unpushed to <push-remote>" unpushed "@{push}.." |
| 2075 | magit-insert-unpushed-to-pushremote) |
| 2076 | |
| 2077 | (defun magit-insert-unpushed-to-pushremote () |
| 2078 | "Insert commits that haven't been pushed to the push-remote yet." |
| 2079 | (when-let* ((target (magit-get-push-branch)) |
| 2080 | (range (concat target "..")) |
| 2081 | (_(magit--insert-pushremote-log-p))) |
| 2082 | (magit-insert-section (unpushed range t) |
| 2083 | (magit-insert-heading |
| 2084 | (format (propertize "Unpushed to %s." |
| 2085 | 'font-lock-face 'magit-section-heading) |
| 2086 | (propertize target 'font-lock-face 'magit-branch-remote))) |
| 2087 | (magit--insert-log nil range magit-buffer-log-args) |
| 2088 | (magit-log-insert-child-count)))) |
| 2089 | |
| 2090 | (defun magit--insert-pushremote-log-p () |
| 2091 | (magit--with-refresh-cache |
| 2092 | (cons default-directory 'magit--insert-pushremote-log-p) |
| 2093 | (not (and (equal (magit-get-push-branch) |
| 2094 | (magit-get-upstream-branch)) |
| 2095 | (or (memq 'magit-insert-unpulled-from-upstream |
| 2096 | magit-status-sections-hook) |
| 2097 | (memq 'magit-insert-unpulled-from-upstream-or-recent |
| 2098 | magit-status-sections-hook)))))) |
| 2099 | |
| 2100 | (defun magit-log-insert-child-count () |
| 2101 | (when magit-section-show-child-count |
| 2102 | (let ((count (length (oref magit-insert-section--current children)))) |
| 2103 | (when (> count 0) |
| 2104 | (when (eq count (magit-log-get-commit-limit)) |
| 2105 | (setq count (format "%s+" count))) |
| 2106 | (save-excursion |
| 2107 | (goto-char (- (oref magit-insert-section--current content) 2)) |
| 2108 | (insert (format " (%s)" count)) |
| 2109 | (delete-char 1)))))) |
| 2110 | |
| 2111 | ;;;; Auxiliary Log Sections |
| 2112 | |
| 2113 | (defun magit-insert-unpulled-cherries () |
| 2114 | "Insert section showing unpulled commits. |
| 2115 | Like `magit-insert-unpulled-from-upstream' but prefix each commit |
| 2116 | which has not been applied yet (i.e., a commit with a patch-id |
| 2117 | not shared with any local commit) with \"+\", and all others with |
| 2118 | \"-\"." |
| 2119 | (when (magit-git-success "rev-parse" "@{upstream}") |
| 2120 | (magit-insert-section (unpulled "..@{upstream}") |
| 2121 | (magit-insert-heading t "Unpulled commits") |
| 2122 | (magit-git-wash (apply-partially #'magit-log-wash-log 'cherry) |
| 2123 | "cherry" "-v" (magit-abbrev-arg) |
| 2124 | (magit-get-current-branch) "@{upstream}")))) |
| 2125 | |
| 2126 | (defun magit-insert-unpushed-cherries () |
| 2127 | "Insert section showing unpushed commits. |
| 2128 | Like `magit-insert-unpushed-to-upstream' but prefix each commit |
| 2129 | which has not been applied to upstream yet (i.e., a commit with |
| 2130 | a patch-id not shared with any upstream commit) with \"+\", and |
| 2131 | all others with \"-\"." |
| 2132 | (when (magit-git-success "rev-parse" "@{upstream}") |
| 2133 | (magit-insert-section (unpushed "@{upstream}..") |
| 2134 | (magit-insert-heading t "Unpushed commits") |
| 2135 | (magit-git-wash (apply-partially #'magit-log-wash-log 'cherry) |
| 2136 | "cherry" "-v" (magit-abbrev-arg) "@{upstream}")))) |
| 2137 | |
| 2138 | ;;; _ |
| 2139 | (provide 'magit-log) |
| 2140 | ;; Local Variables: |
| 2141 | ;; read-symbol-shorthands: ( |
| 2142 | ;; ("and$" . "cond-let--and$") |
| 2143 | ;; ("thread$" . "cond-let--thread$") |
| 2144 | ;; ("when$" . "cond-let--when$") |
| 2145 | ;; ("and-let*" . "cond-let--and-let*") |
| 2146 | ;; ("and-let" . "cond-let--and-let") |
| 2147 | ;; ("if-let*" . "cond-let--if-let*") |
| 2148 | ;; ("if-let" . "cond-let--if-let") |
| 2149 | ;; ("when-let*" . "cond-let--when-let*") |
| 2150 | ;; ("when-let" . "cond-let--when-let") |
| 2151 | ;; ("while-let*" . "cond-let--while-let*") |
| 2152 | ;; ("while-let" . "cond-let--while-let") |
| 2153 | ;; ("match-string" . "match-string") |
| 2154 | ;; ("match-str" . "match-string-no-properties")) |
| 2155 | ;; End: |
| 2156 | ;;; magit-log.el ends here |