config

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

magit-refs.el (33749B)


      1 ;;; magit-refs.el --- Listing references  -*- lexical-binding:t -*-
      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 listing references in a buffer.
     26 
     27 ;;; Code:
     28 
     29 (require 'magit)
     30 
     31 ;;; Options
     32 
     33 (defgroup magit-refs nil
     34   "Inspect and manipulate Git branches and tags."
     35   :link '(info-link "(magit)References Buffer")
     36   :group 'magit-modes)
     37 
     38 (defcustom magit-refs-mode-hook nil
     39   "Hook run after entering Magit-Refs mode."
     40   :package-version '(magit . "2.1.0")
     41   :group 'magit-refs
     42   :type 'hook)
     43 
     44 (defcustom magit-refs-sections-hook
     45   '(magit-insert-error-header
     46     magit-insert-branch-description
     47     magit-insert-local-branches
     48     magit-insert-remote-branches
     49     magit-insert-tags)
     50   "Hook run to insert sections into a references buffer."
     51   :package-version '(magit . "2.1.0")
     52   :group 'magit-refs
     53   :type 'hook)
     54 
     55 (defcustom magit-refs-show-commit-count nil
     56   "Whether to show commit counts in Magit-Refs mode buffers.
     57 
     58 all    Show counts for branches and tags.
     59 branch Show counts for branches only.
     60 nil    Never show counts.
     61 
     62 To change the value in an existing buffer use the command
     63 `magit-refs-set-show-commit-count'."
     64   :package-version '(magit . "2.1.0")
     65   :group 'magit-refs
     66   :safe (lambda (val) (memq val '(all branch nil)))
     67   :type '(choice (const :tag "For branches and tags" all)
     68                  (const :tag "For branches only"     branch)
     69                  (const :tag "Never"                 nil)))
     70 (put 'magit-refs-show-commit-count 'safe-local-variable 'symbolp)
     71 (put 'magit-refs-show-commit-count 'permanent-local t)
     72 
     73 (defcustom magit-refs-pad-commit-counts nil
     74   "Whether to pad all counts on all sides in `magit-refs-mode' buffers.
     75 
     76 If this is nil, then some commit counts are displayed right next
     77 to one of the branches that appear next to the count, without any
     78 space in between.  This might look bad if the branch name faces
     79 look too similar to `magit-dimmed'.
     80 
     81 If this is non-nil, then spaces are placed on both sides of all
     82 commit counts."
     83   :package-version '(magit . "2.12.0")
     84   :group 'magit-refs
     85   :type 'boolean)
     86 
     87 (defvar magit-refs-show-push-remote nil
     88   "Whether to show the push-remotes of local branches.
     89 Also show the commits that the local branch is ahead and behind
     90 the push-target.  Unfortunately there is a bug in Git that makes
     91 this useless (the commits ahead and behind the upstream are
     92 shown), so this isn't enabled yet.")
     93 
     94 (defcustom magit-refs-show-remote-prefix nil
     95   "Whether to show the remote prefix in lists of remote branches.
     96 
     97 This is redundant because the name of the remote is already shown
     98 in the heading preceding the list of its branches."
     99   :package-version '(magit . "2.12.0")
    100   :group 'magit-refs
    101   :type 'boolean)
    102 
    103 (defcustom magit-refs-margin
    104   (list nil
    105         (nth 1 magit-log-margin)
    106         'magit-log-margin-width nil
    107         (nth 4 magit-log-margin))
    108   "Format of the margin in `magit-refs-mode' buffers.
    109 
    110 The value has the form (INIT STYLE WIDTH AUTHOR AUTHOR-WIDTH).
    111 
    112 If INIT is non-nil, then the margin is shown initially.
    113 STYLE controls how to format the author or committer date.
    114   It can be one of `age' (to show the age of the commit),
    115   `age-abbreviated' (to abbreviate the time unit to a character),
    116   or a string (suitable for `format-time-string') to show the
    117   actual date.  Option `magit-log-margin-show-committer-date'
    118   controls which date is being displayed.
    119 WIDTH controls the width of the margin.  This exists for forward
    120   compatibility and currently the value should not be changed.
    121 AUTHOR controls whether the name of the author is also shown by
    122   default.
    123 AUTHOR-WIDTH has to be an integer.  When the name of the author
    124   is shown, then this specifies how much space is used to do so."
    125   :package-version '(magit . "2.9.0")
    126   :group 'magit-refs
    127   :group 'magit-margin
    128   :safe (lambda (val) (memq val '(all branch nil)))
    129   :type magit-log-margin--custom-type
    130   :initialize #'magit-custom-initialize-reset
    131   :set-after '(magit-log-margin)
    132   :set (apply-partially #'magit-margin-set-variable 'magit-refs-mode))
    133 
    134 (defcustom magit-refs-margin-for-tags nil
    135   "Whether to show information about tags in the margin.
    136 
    137 This is disabled by default because it is slow if there are many
    138 tags."
    139   :package-version '(magit . "2.9.0")
    140   :group 'magit-refs
    141   :group 'magit-margin
    142   :type 'boolean)
    143 
    144 (defcustom magit-refs-primary-column-width (cons 16 32)
    145   "Width of the focus column in `magit-refs-mode' buffers.
    146 
    147 The primary column is the column that contains the name of the
    148 branch that the current row is about.
    149 
    150 If this is an integer, then the column is that many columns wide.
    151 Otherwise it has to be a cons-cell of two integers.  The first
    152 specifies the minimal width, the second the maximal width.  In that
    153 case the actual width is determined using the length of the names
    154 of the shown local branches.  (Remote branches and tags are not
    155 taken into account when calculating to optimal width.)"
    156   :package-version '(magit . "2.12.0")
    157   :group 'magit-refs
    158   :type '(choice (integer :tag "Constant wide")
    159                  (cons    :tag "Wide constrains"
    160                           (integer :tag "Minimum")
    161                           (integer :tag "Maximum"))))
    162 
    163 (defcustom magit-refs-focus-column-width 5
    164   "Width of the focus column in `magit-refs-mode' buffers.
    165 
    166 The focus column is the first column, which marks one
    167 branch (usually the current branch) as the focused branch using
    168 \"*\" or \"@\".  For each other reference, this column optionally
    169 shows how many commits it is ahead of the focused branch and \"<\", or
    170 if it isn't ahead then the commits it is behind and \">\", or if it
    171 isn't behind either, then a \"=\".
    172 
    173 This column may also display only \"*\" or \"@\" for the focused
    174 branch, in which case this option is ignored.  Use \"L v\" to
    175 change the verbosity of this column."
    176   :package-version '(magit . "2.12.0")
    177   :group 'magit-refs
    178   :type 'integer)
    179 
    180 (defcustom magit-refs-filter-alist nil
    181   "Alist controlling which refs are omitted from `magit-refs-mode' buffers.
    182 
    183 The purpose of this option is to forgo displaying certain refs
    184 based on their name.  If you want to not display any refs of a
    185 certain type, then you should remove the appropriate function
    186 from `magit-refs-sections-hook' instead.
    187 
    188 All keys are tried in order until one matches.  Then its value
    189 is used and subsequent elements are ignored.  If the value is
    190 non-nil, then the reference is displayed, otherwise it is not.
    191 If no element matches, then the reference is displayed.
    192 
    193 A key can either be a regular expression that the refname has to
    194 match, or a function that takes the refname as only argument and
    195 returns a boolean.  A remote branch such as \"origin/master\" is
    196 displayed as just \"master\", however for this comparison the
    197 former is used."
    198   :package-version '(magit . "2.12.0")
    199   :group 'magit-refs
    200   :type '(alist :key-type   (choice  :tag "Key" regexp function)
    201                 :value-type (boolean :tag "Value"
    202                                      :on  "show (non-nil)"
    203                                      :off "omit (nil)")))
    204 
    205 (defcustom magit-visit-ref-behavior nil
    206   "Control how `magit-visit-ref' behaves in `magit-refs-mode' buffers.
    207 
    208 By default `magit-visit-ref' behaves like `magit-show-commit',
    209 in all buffers, including `magit-refs-mode' buffers.  When the
    210 type of the section at point is `commit' then \"RET\" is bound to
    211 `magit-show-commit', and when the type is either `branch' or
    212 `tag' then it is bound to `magit-visit-ref'.
    213 
    214 \"RET\" is one of Magit's most essential keys and at least by
    215 default it should behave consistently across all of Magit,
    216 especially because users quickly learn that it does something
    217 very harmless; it shows more information about the thing at point
    218 in another buffer.
    219 
    220 However \"RET\" used to behave differently in `magit-refs-mode'
    221 buffers, doing surprising things, some of which cannot really be
    222 described as \"visit this thing\".  If you have grown accustomed
    223 to such inconsistent, but to you useful, behavior, then you can
    224 restore that by adding one or more of the below symbols to the
    225 value of this option.  But keep in mind that by doing so you
    226 don't only introduce inconsistencies, you also lose some
    227 functionality and might have to resort to `M-x magit-show-commit'
    228 to get it back.
    229 
    230 `magit-visit-ref' looks for these symbols in the order in which
    231 they are described here.  If the presence of a symbol applies to
    232 the current situation, then the symbols that follow do not affect
    233 the outcome.
    234 
    235 `focus-on-ref'
    236 
    237   With a prefix argument update the buffer to show commit counts
    238   and lists of cherry commits relative to the reference at point
    239   instead of relative to the current buffer or `HEAD'.
    240 
    241   Instead of adding this symbol, consider pressing \"C-u y o RET\".
    242 
    243 `create-branch'
    244 
    245   If point is on a remote branch, then create a new local branch
    246   with the same name, use the remote branch as its upstream, and
    247   then check out the local branch.
    248 
    249   Instead of adding this symbol, consider pressing \"b c RET RET\",
    250   like you would do in other buffers.
    251 
    252 `checkout-any'
    253 
    254   Check out the reference at point.  If that reference is a tag
    255   or a remote branch, then this results in a detached `HEAD'.
    256 
    257   Instead of adding this symbol, consider pressing \"b b RET\",
    258   like you would do in other buffers.
    259 
    260 `checkout-branch'
    261 
    262   Check out the local branch at point.
    263 
    264   Instead of adding this symbol, consider pressing \"b b RET\",
    265   like you would do in other buffers."
    266   :package-version '(magit . "2.9.0")
    267   :group 'magit-refs
    268   :group 'magit-commands
    269   :options '(focus-on-ref create-branch checkout-any checkout-branch)
    270   :type '(list :convert-widget custom-hook-convert-widget))
    271 
    272 ;;; Mode
    273 
    274 (defvar-keymap magit-refs-mode-map
    275   :doc "Keymap for `magit-refs-mode'."
    276   :parent magit-mode-map
    277   "C-y" #'magit-refs-set-show-commit-count
    278   "L"   #'magit-margin-settings)
    279 
    280 (define-derived-mode magit-refs-mode magit-mode "Magit Refs"
    281   "Mode which lists and compares references.
    282 
    283 This mode is documented in info node `(magit)References Buffer'.
    284 
    285 \\<magit-mode-map>\
    286 Type \\[magit-refresh] to refresh the current buffer.
    287 Type \\[magit-section-toggle] to expand or hide the section at point.
    288 Type \\[magit-visit-thing] or \\[magit-diff-show-or-scroll-up] \
    289 to visit the commit or branch at point.
    290 
    291 Type \\[magit-branch] to see available branch commands.
    292 Type \\[magit-merge] to merge the branch or commit at point.
    293 Type \\[magit-cherry-pick] to apply the commit at point.
    294 Type \\[magit-reset] to reset `HEAD' to the commit at point.
    295 
    296 \\{magit-refs-mode-map}"
    297   :interactive nil
    298   :group 'magit-refs
    299   (magit-hack-dir-local-variables)
    300   (setq magit--imenu-group-types '(local remote tags)))
    301 
    302 (defun magit-refs-setup-buffer (ref args)
    303   (magit-setup-buffer #'magit-refs-mode nil
    304     (magit-buffer-upstream ref)
    305     (magit-buffer-arguments args)))
    306 
    307 (defun magit-refs-refresh-buffer ()
    308   (setq magit-set-buffer-margin-refresh (not (magit-buffer-margin-p)))
    309   (unless (magit-rev-verify magit-buffer-upstream)
    310     (setq magit-refs-show-commit-count nil))
    311   (magit-set-header-line-format
    312    (format "%s %s" magit-buffer-upstream
    313            (string-join magit-buffer-arguments " ")))
    314   (magit-insert-section (branchbuf)
    315     (magit-run-section-hook 'magit-refs-sections-hook))
    316   (add-hook 'kill-buffer-hook #'magit-preserve-section-visibility-cache))
    317 
    318 (cl-defmethod magit-buffer-value (&context (major-mode magit-refs-mode))
    319   (cons magit-buffer-upstream magit-buffer-arguments))
    320 
    321 ;;; Commands
    322 
    323 ;;;###autoload (autoload 'magit-show-refs "magit-refs" nil t)
    324 (transient-define-prefix magit-show-refs (&optional transient)
    325   "List and compare references in a dedicated buffer."
    326   :man-page "git-branch"
    327   :value (lambda ()
    328            (magit-show-refs-arguments magit-prefix-use-buffer-arguments))
    329   ["Arguments"
    330    (magit-for-each-ref:--contains)
    331    ("-M" "Merged"               "--merged=" magit-transient-read-revision)
    332    ("-m" "Merged to HEAD"       "--merged")
    333    ("-N" "Not merged"           "--no-merged=" magit-transient-read-revision)
    334    ("-n" "Not merged to HEAD"   "--no-merged")
    335    (magit-for-each-ref:--sort)]
    336   ["Actions"
    337    ("y" "Show refs, comparing them with HEAD"           magit-show-refs-head)
    338    ("c" "Show refs, comparing them with current branch" magit-show-refs-current)
    339    ("o" "Show refs, comparing them with other branch"   magit-show-refs-other)
    340    ("r" "Show refs, changing commit count display"
    341     magit-refs-set-show-commit-count)]
    342   (interactive (list (or (derived-mode-p 'magit-refs-mode)
    343                          current-prefix-arg)))
    344   (if transient
    345       (transient-setup 'magit-show-refs)
    346     (magit-refs-setup-buffer "HEAD" (magit-show-refs-arguments))))
    347 
    348 (defun magit-show-refs-arguments (&optional use-buffer-args)
    349   (unless use-buffer-args
    350     (setq use-buffer-args magit-direct-use-buffer-arguments))
    351   (let (args)
    352     (cond
    353      ((eq transient-current-command 'magit-show-refs)
    354       (setq args (transient-args 'magit-show-refs)))
    355      ((eq major-mode 'magit-refs-mode)
    356       (setq args magit-buffer-arguments))
    357      ((and (memq use-buffer-args '(always selected))
    358            (and-let* ((buffer (magit-get-mode-buffer
    359                                'magit-refs-mode nil
    360                                (eq use-buffer-args 'selected))))
    361              (progn
    362                (setq args (buffer-local-value 'magit-buffer-arguments buffer))
    363                t))))
    364      (t
    365       (setq args (alist-get 'magit-show-refs transient-values))))
    366     args))
    367 
    368 (transient-define-argument magit-for-each-ref:--contains ()
    369   :description "Contains"
    370   :class 'transient-option
    371   :key "-c"
    372   :argument "--contains="
    373   :reader #'magit-transient-read-revision)
    374 
    375 (transient-define-argument magit-for-each-ref:--sort ()
    376   :description "Sort"
    377   :class 'transient-option
    378   :key "-s"
    379   :argument "--sort="
    380   :reader #'magit-read-ref-sort)
    381 
    382 (defun magit-read-ref-sort (prompt initial-input _history)
    383   (magit-completing-read prompt
    384                          '("-committerdate" "-authordate"
    385                            "committerdate" "authordate")
    386                          nil nil initial-input))
    387 
    388 ;;;###autoload
    389 (defun magit-show-refs-head (&optional args)
    390   "List and compare references in a dedicated buffer.
    391 Compared with `HEAD'."
    392   (interactive (list (magit-show-refs-arguments)))
    393   (magit-refs-setup-buffer "HEAD" args))
    394 
    395 ;;;###autoload
    396 (defun magit-show-refs-current (&optional args)
    397   "List and compare references in a dedicated buffer.
    398 Compare with the current branch or `HEAD' if it is detached."
    399   (interactive (list (magit-show-refs-arguments)))
    400   (magit-refs-setup-buffer (magit-get-current-branch) args))
    401 
    402 ;;;###autoload
    403 (defun magit-show-refs-other (&optional ref args)
    404   "List and compare references in a dedicated buffer.
    405 Compared with a branch read from the user."
    406   (interactive (list (magit-read-other-branch "Compare with")
    407                      (magit-show-refs-arguments)))
    408   (magit-refs-setup-buffer ref args))
    409 
    410 (transient-define-suffix magit-refs-set-show-commit-count ()
    411   "Change for which refs the commit count is shown."
    412   :description "Change verbosity"
    413   :key "v"
    414   :transient nil
    415   :if-derived 'magit-refs-mode
    416   (interactive)
    417   (setq-local magit-refs-show-commit-count
    418               (magit-read-char-case "Show commit counts for " nil
    419                 (?a "[a]ll refs" 'all)
    420                 (?b "[b]ranches only" t)
    421                 (?n "[n]othing" nil)))
    422   (magit-refresh))
    423 
    424 (defun magit-visit-ref ()
    425   "Visit the reference or revision at point in another buffer.
    426 If there is no revision at point or with a prefix argument prompt
    427 for a revision.
    428 
    429 This command behaves just like `magit-show-commit', except if
    430 point is on a reference in a `magit-refs-mode' buffer (a buffer
    431 listing branches and tags), in which case the behavior may be
    432 different, but only if you have customized the option
    433 `magit-visit-ref-behavior' (which see).  When invoked from a
    434 menu this command always behaves like `magit-show-commit'."
    435   (interactive)
    436   (if (and (derived-mode-p 'magit-refs-mode)
    437            (magit-section-match '(branch tag))
    438            (not (magit-menu-position)))
    439       (let ((ref (oref (magit-current-section) value)))
    440         (cond (current-prefix-arg
    441                (cond ((memq 'focus-on-ref magit-visit-ref-behavior)
    442                       (magit-refs-setup-buffer ref (magit-show-refs-arguments)))
    443                      (magit-visit-ref-behavior
    444                       ;; Don't prompt for commit to visit.
    445                       (let ((current-prefix-arg nil))
    446                         (call-interactively #'magit-show-commit)))))
    447               ((and (memq 'create-branch magit-visit-ref-behavior)
    448                     (magit-section-match [branch remote]))
    449                (let ((branch (cdr (magit-split-branch-name ref))))
    450                  (if (magit-branch-p branch)
    451                      (if (magit-rev-eq branch ref)
    452                          (magit-call-git "checkout" branch)
    453                        (setq branch (propertize branch 'face 'magit-branch-local))
    454                        (setq ref (propertize ref 'face 'magit-branch-remote))
    455                        (pcase (prog1 (read-char-choice (format (propertize "\
    456 Branch %s already exists.
    457   [c]heckout %s as-is
    458   [r]reset %s to %s and checkout %s
    459   [a]bort " 'face 'minibuffer-prompt) branch branch branch ref branch)
    460                                                        '(?c ?r ?a))
    461                                 (message "")) ; otherwise prompt sticks
    462                          (?c (magit-call-git "checkout" branch))
    463                          (?r (magit-call-git "checkout" "-B" branch ref))
    464                          (?a (user-error "Abort"))))
    465                    (magit-call-git "checkout" "-b" branch ref))
    466                  (setq magit-buffer-upstream branch)
    467                  (magit-refresh)))
    468               ((or (memq 'checkout-any magit-visit-ref-behavior)
    469                    (and (memq 'checkout-branch magit-visit-ref-behavior)
    470                         (magit-section-match [branch local])))
    471                (magit-call-git "checkout" ref)
    472                (setq magit-buffer-upstream ref)
    473                (magit-refresh))
    474               (t
    475                (call-interactively #'magit-show-commit))))
    476     (call-interactively #'magit-show-commit)))
    477 
    478 ;;; Sections
    479 
    480 (defvar-keymap magit-remote-section-map
    481   :doc "Keymap for `remote' sections."
    482   "<remap> <magit-file-rename>"  #'magit-remote-rename
    483   "<remap> <magit-delete-thing>" #'magit-remote-remove
    484   "<2>" (magit-menu-item "Rename %s" #'magit-remote-rename)
    485   "<1>" (magit-menu-item "Remove %m" #'magit-remote-remove))
    486 
    487 (defvar-keymap magit-branch-section-map
    488   :doc "Keymap for `branch' sections."
    489   "<remap> <magit-file-rename>"  #'magit-branch-rename
    490   "<remap> <magit-delete-thing>" #'magit-branch-delete
    491   "<remap> <magit-visit-thing>"  #'magit-visit-ref
    492   "<3>" (magit-menu-item "Rename %s"    #'magit-branch-rename)
    493   "<2>" (magit-menu-item "Delete %m"    #'magit-branch-delete)
    494   "<1>" (magit-menu-item "Visit commit" #'magit-visit-ref))
    495 
    496 (defvar-keymap magit-tag-section-map
    497   :doc "Keymap for `tag' sections."
    498   "<remap> <magit-delete-thing>" #'magit-tag-delete
    499   "<remap> <magit-visit-thing>"  #'magit-visit-ref
    500   "<2>" (magit-menu-item "Delete %m" #'magit-tag-delete)
    501   "<1>" (magit-menu-item "Visit %s"  #'magit-visit-ref))
    502 
    503 (defun magit--painted-branch-as-menu-section (section)
    504   (and-let* ((branch (and (magit-section-match 'commit)
    505                           (magit--painted-branch-at-point))))
    506     (let ((dummy (magit-section :type 'branch :value branch)))
    507       (oset dummy keymap magit-branch-section-map)
    508       (dolist (slot '(start content hidden parent children))
    509         (when (slot-boundp section slot)
    510           (setf (eieio-oref dummy slot)
    511                 (eieio-oref section slot))))
    512       dummy)))
    513 
    514 (add-hook 'magit-menu-alternative-section-hook
    515           #'magit--painted-branch-as-menu-section)
    516 
    517 (defun magit-insert-branch-description ()
    518   "Insert header containing the description of the current branch.
    519 Insert a header line with the name and description of the
    520 current branch.  The description is taken from the Git variable
    521 `branch.<NAME>.description'; if that is undefined then no header
    522 line is inserted at all."
    523   (when-let* ((branch (magit-get-current-branch))
    524               (desc (magit-get "branch" branch "description"))
    525               (desc (split-string desc "\n")))
    526     (when (equal (car (last desc)) "")
    527       (setq desc (butlast desc)))
    528     (magit-insert-section (branchdesc branch t)
    529       (magit-insert-heading branch ": " (car desc))
    530       (when (cdr desc)
    531         (insert (string-join (cdr desc) "\n"))
    532         (insert "\n\n")))))
    533 
    534 (defun magit-insert-tags ()
    535   "Insert sections showing all tags."
    536   (when-let ((tags (magit-git-lines "tag" "--list" "-n" magit-buffer-arguments)))
    537     (let ((_head (magit-rev-parse "HEAD")))
    538       (magit-insert-section (tags)
    539         (magit-insert-heading (length tags) "Tags")
    540         (dolist (tag tags)
    541           (string-match "^\\([^ \t]+\\)[ \t]+\\([^ \t\n].*\\)?" tag)
    542           (let ((tag (match-string 1 tag))
    543                 (msg (match-string 2 tag)))
    544             (when (magit-refs--insert-refname-p tag)
    545               (magit-insert-section (tag tag t)
    546                 (magit-insert-heading
    547                   (magit-refs--format-focus-column tag 'tag)
    548                   (propertize tag 'font-lock-face 'magit-tag)
    549                   (make-string
    550                    (max 1 (- (if (consp magit-refs-primary-column-width)
    551                                  (car magit-refs-primary-column-width)
    552                                magit-refs-primary-column-width)
    553                              (length tag)))
    554                    ?\s)
    555                   (and msg (magit-log-propertize-keywords nil msg)))
    556                 (when (and magit-refs-margin-for-tags (magit-buffer-margin-p))
    557                   (magit-refs--format-margin tag))
    558                 (magit-refs--insert-cherry-commits tag)))))
    559         (insert ?\n)
    560         (magit-make-margin-overlay nil t)))))
    561 
    562 (defun magit-insert-remote-branches ()
    563   "Insert sections showing all remote-tracking branches."
    564   (dolist (remote (magit-list-remotes))
    565     (magit-insert-section (remote remote)
    566       (magit-insert-heading
    567         (let ((pull (magit-get "remote" remote "url"))
    568               (push (magit-get "remote" remote "pushurl")))
    569           (format (propertize "Remote %s (%s):"
    570                               'font-lock-face 'magit-section-heading)
    571                   (propertize remote 'font-lock-face 'magit-branch-remote)
    572                   (concat pull (and pull push ", ") push))))
    573       (let (head)
    574         (dolist (line (magit-git-lines "for-each-ref" "--format=\
    575 %(symref:short)%00%(refname:short)%00%(refname)%00%(subject)"
    576                                        (concat "refs/remotes/" remote)
    577                                        magit-buffer-arguments))
    578           (pcase-let ((`(,head-branch ,branch ,ref ,msg)
    579                        (cl-substitute nil ""
    580                                       (split-string line "\0")
    581                                       :test #'equal)))
    582             (cond
    583              (head-branch
    584               ;; Note: Use `ref' instead of `branch' for the check
    585               ;; below because 'refname:short' shortens the remote
    586               ;; HEAD to '<remote>' instead of '<remote>/HEAD' as of
    587               ;; Git v2.40.0.
    588               (cl-assert
    589                (equal ref (concat "refs/remotes/" remote "/HEAD")))
    590               (setq head head-branch))
    591              ((not (equal ref (concat "refs/remotes/" remote "/HEAD")))
    592               ;; ^ Skip mis-configured remotes where HEAD is not a
    593               ;; symref.  See #5092.
    594               (when (magit-refs--insert-refname-p branch)
    595                 (magit-insert-section (branch branch t)
    596                   (let ((headp (equal branch head))
    597                         (abbrev (if magit-refs-show-remote-prefix
    598                                     branch
    599                                   (substring branch (1+ (length remote))))))
    600                     (magit-insert-heading
    601                       (magit-refs--format-focus-column branch)
    602                       (magit-refs--propertize-branch
    603                        abbrev ref (and headp 'magit-branch-remote-head))
    604                       (make-string
    605                        (max 1 (- (if (consp magit-refs-primary-column-width)
    606                                      (car magit-refs-primary-column-width)
    607                                    magit-refs-primary-column-width)
    608                                  (length abbrev)))
    609                        ?\s)
    610                       (and msg (magit-log-propertize-keywords nil msg))))
    611                   (when (magit-buffer-margin-p)
    612                     (magit-refs--format-margin branch))
    613                   (magit-refs--insert-cherry-commits branch))))))))
    614       (insert ?\n)
    615       (magit-make-margin-overlay nil t))))
    616 
    617 (defun magit-insert-local-branches ()
    618   "Insert sections showing all local branches."
    619   (magit-insert-section (local nil)
    620     (magit-insert-heading t "Branches")
    621     (dolist (line (magit-refs--format-local-branches))
    622       (pcase-let ((`(,branch . ,strings) line))
    623         (magit-insert-section
    624             ((eval (if branch 'branch 'commit))
    625              (or branch (magit-rev-parse "HEAD"))
    626              t)
    627           (apply #'magit-insert-heading strings)
    628           (when (magit-buffer-margin-p)
    629             (magit-refs--format-margin branch))
    630           (magit-refs--insert-cherry-commits branch))))
    631     (insert ?\n)
    632     (magit-make-margin-overlay nil t)))
    633 
    634 (defun magit-refs--format-local-branches ()
    635   (let ((lines (seq-keep #'magit-refs--format-local-branch
    636                          (magit-git-lines
    637                           "for-each-ref"
    638                           (concat "--format=\
    639 %(HEAD)%00%(refname:short)%00%(refname)%00\
    640 %(upstream:short)%00%(upstream)%00%(upstream:track)%00"
    641                                   (if magit-refs-show-push-remote "\
    642 %(push:remotename)%00%(push)%00%(push:track)%00%(subject)"
    643                                     "%00%00%00%(subject)"))
    644                           "refs/heads"
    645                           magit-buffer-arguments))))
    646     (unless (magit-get-current-branch)
    647       (push (magit-refs--format-local-branch
    648              (concat "*\0\0\0\0\0\0\0\0" (magit-rev-format "%s")))
    649             lines))
    650     (setq-local magit-refs-primary-column-width
    651                 (let ((def (default-value 'magit-refs-primary-column-width)))
    652                   (if (atom def)
    653                       def
    654                     (pcase-let ((`(,min . ,max) def))
    655                       (min max (apply #'max min (mapcar #'car lines)))))))
    656     (mapcar (pcase-lambda (`(,_ ,branch ,focus ,branch-desc ,u:ahead ,p:ahead
    657                                 ,u:behind ,upstream ,p:behind ,push ,msg))
    658               (list branch focus branch-desc u:ahead p:ahead
    659                     (make-string (max 1 (- magit-refs-primary-column-width
    660                                            (length (concat branch-desc
    661                                                            u:ahead
    662                                                            p:ahead
    663                                                            u:behind))))
    664                                  ?\s)
    665                     u:behind upstream p:behind push
    666                     msg))
    667             lines)))
    668 
    669 (defun magit-refs--format-local-branch (line)
    670   (pcase-let ((`(,head ,branch ,ref ,upstream ,u:ref ,u:track
    671                        ,push ,p:ref ,p:track ,msg)
    672                (cl-substitute nil "" (split-string line "\0") :test #'equal)))
    673     (when (or (not branch)
    674               (magit-refs--insert-refname-p branch))
    675       (let* ((headp (equal head "*"))
    676              (pushp (and push
    677                          magit-refs-show-push-remote
    678                          (magit-rev-verify p:ref)
    679                          (not (equal p:ref u:ref))))
    680              (branch-desc
    681               (if branch
    682                   (magit-refs--propertize-branch
    683                    branch ref (and headp 'magit-branch-current))
    684                 (magit--propertize-face "(detached)" 'magit-branch-warning)))
    685              (u:ahead  (and u:track
    686                             (string-match "ahead \\([0-9]+\\)" u:track)
    687                             (magit--propertize-face
    688                              (concat (and magit-refs-pad-commit-counts " ")
    689                                      (match-string 1 u:track)
    690                                      ">")
    691                              'magit-dimmed)))
    692              (u:behind (and u:track
    693                             (string-match "behind \\([0-9]+\\)" u:track)
    694                             (magit--propertize-face
    695                              (concat "<"
    696                                      (match-string 1 u:track)
    697                                      (and magit-refs-pad-commit-counts " "))
    698                              'magit-dimmed)))
    699              (p:ahead  (and pushp p:track
    700                             (string-match "ahead \\([0-9]+\\)" p:track)
    701                             (magit--propertize-face
    702                              (concat (match-string 1 p:track)
    703                                      ">"
    704                                      (and magit-refs-pad-commit-counts " "))
    705                              'magit-branch-remote)))
    706              (p:behind (and pushp p:track
    707                             (string-match "behind \\([0-9]+\\)" p:track)
    708                             (magit--propertize-face
    709                              (concat "<"
    710                                      (match-string 1 p:track)
    711                                      (and magit-refs-pad-commit-counts " "))
    712                              'magit-dimmed))))
    713         (list (1+ (length (concat branch-desc u:ahead p:ahead u:behind)))
    714               branch
    715               (magit-refs--format-focus-column branch headp)
    716               branch-desc u:ahead p:ahead u:behind
    717               (and upstream
    718                    (concat (if (equal u:track "[gone]")
    719                                (magit--propertize-face upstream 'error)
    720                              (magit-refs--propertize-branch upstream u:ref))
    721                            " "))
    722               (and pushp
    723                    (concat p:behind
    724                            (magit--propertize-face
    725                             push 'magit-branch-remote)
    726                            " "))
    727               (and msg (magit-log-propertize-keywords nil msg)))))))
    728 
    729 (defun magit-refs--format-focus-column (ref &optional type)
    730   (let ((focus magit-buffer-upstream)
    731         (width (if magit-refs-show-commit-count
    732                    magit-refs-focus-column-width
    733                  1)))
    734     (format
    735      (format "%%%ss " width)
    736      (cond ((or (equal ref focus)
    737                 (and (eq type t)
    738                      (equal focus "HEAD")))
    739             (magit--propertize-face (concat (if (equal focus "HEAD") "@" "*")
    740                                             (make-string (1- width) ?\s))
    741                                     'magit-section-heading))
    742            ((if (eq type 'tag)
    743                 (eq magit-refs-show-commit-count 'all)
    744               magit-refs-show-commit-count)
    745             (pcase-let ((`(,behind ,ahead)
    746                          (magit-rev-diff-count magit-buffer-upstream ref)))
    747               (magit--propertize-face
    748                (cond ((> ahead  0) (concat "<" (number-to-string ahead)))
    749                      ((> behind 0) (concat (number-to-string behind) ">"))
    750                      (t "="))
    751                'magit-dimmed)))
    752            (t "")))))
    753 
    754 (defun magit-refs--propertize-branch (branch ref &optional head-face)
    755   (let ((face (cdr (cl-find-if (pcase-lambda (`(,re . ,_))
    756                                  (string-match-p re ref))
    757                                magit-ref-namespaces))))
    758     (magit--propertize-face
    759      branch (if head-face (list face head-face) face))))
    760 
    761 (defun magit-refs--insert-refname-p (refname)
    762   (if-let ((entry (seq-find (pcase-lambda (`(,key . ,_))
    763                               (if (functionp key)
    764                                   (funcall key refname)
    765                                 (string-match-p key refname)))
    766                             magit-refs-filter-alist)))
    767       (cdr entry)
    768     t))
    769 
    770 (defun magit-refs--insert-cherry-commits (ref)
    771   (magit-insert-section-body
    772     (let ((start (point))
    773           (magit-insert-section--current nil))
    774       (magit-git-wash (apply-partially #'magit-log-wash-log 'cherry)
    775         "cherry" "-v" (magit-abbrev-arg) magit-buffer-upstream ref)
    776       (if (= (point) start)
    777           (message "No cherries for %s" ref)
    778         (magit-make-margin-overlay nil t)))))
    779 
    780 (defun magit-refs--format-margin (commit)
    781   (save-excursion
    782     (goto-char (line-beginning-position 0))
    783     (let ((line (magit-rev-format "%ct%cN" commit)))
    784       (magit-log-format-margin commit
    785                                (substring line 10)
    786                                (substring line 0 10)))))
    787 
    788 ;;; _
    789 (provide 'magit-refs)
    790 ;;; magit-refs.el ends here