config

Personal configuration.
git clone git://code.dwrz.net/config
Log | Files | Refs

magit-log.el (82471B)


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