dotemacs

My Emacs configuration
git clone git://git.entf.net/dotemacs
Log | Files | Refs | LICENSE

magit-log.el (75248B)


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