config

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

magit-refs.el (33737B)


      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   :group 'magit-refs
    298   (hack-dir-local-variables-non-file-buffer)
    299   (setq magit--imenu-group-types '(local remote tags)))
    300 
    301 (defun magit-refs-setup-buffer (ref args)
    302   (magit-setup-buffer #'magit-refs-mode nil
    303     (magit-buffer-upstream ref)
    304     (magit-buffer-arguments args)))
    305 
    306 (defun magit-refs-refresh-buffer ()
    307   (setq magit-set-buffer-margin-refresh (not (magit-buffer-margin-p)))
    308   (unless (magit-rev-verify magit-buffer-upstream)
    309     (setq magit-refs-show-commit-count nil))
    310   (magit-set-header-line-format
    311    (format "%s %s" magit-buffer-upstream
    312            (string-join magit-buffer-arguments " ")))
    313   (magit-insert-section (branchbuf)
    314     (magit-run-section-hook 'magit-refs-sections-hook))
    315   (add-hook 'kill-buffer-hook #'magit-preserve-section-visibility-cache))
    316 
    317 (cl-defmethod magit-buffer-value (&context (major-mode magit-refs-mode))
    318   (cons magit-buffer-upstream magit-buffer-arguments))
    319 
    320 ;;; Commands
    321 
    322 ;;;###autoload (autoload 'magit-show-refs "magit-refs" nil t)
    323 (transient-define-prefix magit-show-refs (&optional transient)
    324   "List and compare references in a dedicated buffer."
    325   :man-page "git-branch"
    326   :value (lambda ()
    327            (magit-show-refs-arguments magit-prefix-use-buffer-arguments))
    328   ["Arguments"
    329    (magit-for-each-ref:--contains)
    330    ("-M" "Merged"               "--merged=" magit-transient-read-revision)
    331    ("-m" "Merged to HEAD"       "--merged")
    332    ("-N" "Not merged"           "--no-merged=" magit-transient-read-revision)
    333    ("-n" "Not merged to HEAD"   "--no-merged")
    334    (magit-for-each-ref:--sort)]
    335   ["Actions"
    336    ("y" "Show refs, comparing them with HEAD"           magit-show-refs-head)
    337    ("c" "Show refs, comparing them with current branch" magit-show-refs-current)
    338    ("o" "Show refs, comparing them with other branch"   magit-show-refs-other)
    339    ("r" "Show refs, changing commit count display"
    340     magit-refs-set-show-commit-count)]
    341   (interactive (list (or (derived-mode-p 'magit-refs-mode)
    342                          current-prefix-arg)))
    343   (if transient
    344       (transient-setup 'magit-show-refs)
    345     (magit-refs-setup-buffer "HEAD" (magit-show-refs-arguments))))
    346 
    347 (defun magit-show-refs-arguments (&optional use-buffer-args)
    348   (unless use-buffer-args
    349     (setq use-buffer-args magit-direct-use-buffer-arguments))
    350   (let (args)
    351     (cond
    352      ((eq transient-current-command 'magit-show-refs)
    353       (setq args (transient-args 'magit-show-refs)))
    354      ((eq major-mode 'magit-refs-mode)
    355       (setq args magit-buffer-arguments))
    356      ((and (memq use-buffer-args '(always selected))
    357            (and-let* ((buffer (magit-get-mode-buffer
    358                                'magit-refs-mode nil
    359                                (eq use-buffer-args 'selected))))
    360              (progn
    361                (setq args (buffer-local-value 'magit-buffer-arguments buffer))
    362                t))))
    363      (t
    364       (setq args (alist-get 'magit-show-refs transient-values))))
    365     args))
    366 
    367 (transient-define-argument magit-for-each-ref:--contains ()
    368   :description "Contains"
    369   :class 'transient-option
    370   :key "-c"
    371   :argument "--contains="
    372   :reader #'magit-transient-read-revision)
    373 
    374 (transient-define-argument magit-for-each-ref:--sort ()
    375   :description "Sort"
    376   :class 'transient-option
    377   :key "-s"
    378   :argument "--sort="
    379   :reader #'magit-read-ref-sort)
    380 
    381 (defun magit-read-ref-sort (prompt initial-input _history)
    382   (magit-completing-read prompt
    383                          '("-committerdate" "-authordate"
    384                            "committerdate" "authordate")
    385                          nil nil initial-input))
    386 
    387 ;;;###autoload
    388 (defun magit-show-refs-head (&optional args)
    389   "List and compare references in a dedicated buffer.
    390 Compared with `HEAD'."
    391   (interactive (list (magit-show-refs-arguments)))
    392   (magit-refs-setup-buffer "HEAD" args))
    393 
    394 ;;;###autoload
    395 (defun magit-show-refs-current (&optional args)
    396   "List and compare references in a dedicated buffer.
    397 Compare with the current branch or `HEAD' if it is detached."
    398   (interactive (list (magit-show-refs-arguments)))
    399   (magit-refs-setup-buffer (magit-get-current-branch) args))
    400 
    401 ;;;###autoload
    402 (defun magit-show-refs-other (&optional ref args)
    403   "List and compare references in a dedicated buffer.
    404 Compared with a branch read from the user."
    405   (interactive (list (magit-read-other-branch "Compare with")
    406                      (magit-show-refs-arguments)))
    407   (magit-refs-setup-buffer ref args))
    408 
    409 (transient-define-suffix magit-refs-set-show-commit-count ()
    410   "Change for which refs the commit count is shown."
    411   :description "Change verbosity"
    412   :key "v"
    413   :transient nil
    414   :if-derived 'magit-refs-mode
    415   (interactive)
    416   (setq-local magit-refs-show-commit-count
    417               (magit-read-char-case "Show commit counts for " nil
    418                 (?a "[a]ll refs" 'all)
    419                 (?b "[b]ranches only" t)
    420                 (?n "[n]othing" nil)))
    421   (magit-refresh))
    422 
    423 (defun magit-visit-ref ()
    424   "Visit the reference or revision at point in another buffer.
    425 If there is no revision at point or with a prefix argument prompt
    426 for a revision.
    427 
    428 This command behaves just like `magit-show-commit', except if
    429 point is on a reference in a `magit-refs-mode' buffer (a buffer
    430 listing branches and tags), in which case the behavior may be
    431 different, but only if you have customized the option
    432 `magit-visit-ref-behavior' (which see).  When invoked from a
    433 menu this command always behaves like `magit-show-commit'."
    434   (interactive)
    435   (if (and (derived-mode-p 'magit-refs-mode)
    436            (magit-section-match '(branch tag))
    437            (not (magit-menu-position)))
    438       (let ((ref (oref (magit-current-section) value)))
    439         (cond (current-prefix-arg
    440                (cond ((memq 'focus-on-ref magit-visit-ref-behavior)
    441                       (magit-refs-setup-buffer ref (magit-show-refs-arguments)))
    442                      (magit-visit-ref-behavior
    443                       ;; Don't prompt for commit to visit.
    444                       (let ((current-prefix-arg nil))
    445                         (call-interactively #'magit-show-commit)))))
    446               ((and (memq 'create-branch magit-visit-ref-behavior)
    447                     (magit-section-match [branch remote]))
    448                (let ((branch (cdr (magit-split-branch-name ref))))
    449                  (if (magit-branch-p branch)
    450                      (if (magit-rev-eq branch ref)
    451                          (magit-call-git "checkout" branch)
    452                        (setq branch (propertize branch 'face 'magit-branch-local))
    453                        (setq ref (propertize ref 'face 'magit-branch-remote))
    454                        (pcase (prog1 (read-char-choice (format (propertize "\
    455 Branch %s already exists.
    456   [c]heckout %s as-is
    457   [r]reset %s to %s and checkout %s
    458   [a]bort " 'face 'minibuffer-prompt) branch branch branch ref branch)
    459                                                        '(?c ?r ?a))
    460                                 (message "")) ; otherwise prompt sticks
    461                          (?c (magit-call-git "checkout" branch))
    462                          (?r (magit-call-git "checkout" "-B" branch ref))
    463                          (?a (user-error "Abort"))))
    464                    (magit-call-git "checkout" "-b" branch ref))
    465                  (setq magit-buffer-upstream branch)
    466                  (magit-refresh)))
    467               ((or (memq 'checkout-any magit-visit-ref-behavior)
    468                    (and (memq 'checkout-branch magit-visit-ref-behavior)
    469                         (magit-section-match [branch local])))
    470                (magit-call-git "checkout" ref)
    471                (setq magit-buffer-upstream ref)
    472                (magit-refresh))
    473               (t
    474                (call-interactively #'magit-show-commit))))
    475     (call-interactively #'magit-show-commit)))
    476 
    477 ;;; Sections
    478 
    479 (defvar-keymap magit-remote-section-map
    480   :doc "Keymap for `remote' sections."
    481   "<remap> <magit-file-rename>"  #'magit-remote-rename
    482   "<remap> <magit-delete-thing>" #'magit-remote-remove
    483   "<2>" (magit-menu-item "Rename %s" #'magit-remote-rename)
    484   "<1>" (magit-menu-item "Remove %m" #'magit-remote-remove))
    485 
    486 (defvar-keymap magit-branch-section-map
    487   :doc "Keymap for `branch' sections."
    488   "<remap> <magit-file-rename>"  #'magit-branch-rename
    489   "<remap> <magit-delete-thing>" #'magit-branch-delete
    490   "<remap> <magit-visit-thing>"  #'magit-visit-ref
    491   "<3>" (magit-menu-item "Rename %s"    #'magit-branch-rename)
    492   "<2>" (magit-menu-item "Delete %m"    #'magit-branch-delete)
    493   "<1>" (magit-menu-item "Visit commit" #'magit-visit-ref))
    494 
    495 (defvar-keymap magit-tag-section-map
    496   :doc "Keymap for `tag' sections."
    497   "<remap> <magit-delete-thing>" #'magit-tag-delete
    498   "<remap> <magit-visit-thing>"  #'magit-visit-ref
    499   "<2>" (magit-menu-item "Delete %m" #'magit-tag-delete)
    500   "<1>" (magit-menu-item "Visit %s"  #'magit-visit-ref))
    501 
    502 (defun magit--painted-branch-as-menu-section (section)
    503   (and-let* ((branch (and (magit-section-match 'commit)
    504                           (magit--painted-branch-at-point))))
    505     (let ((dummy (magit-section :type 'branch :value branch)))
    506       (oset dummy keymap magit-branch-section-map)
    507       (dolist (slot '(start content hidden parent children))
    508         (when (slot-boundp section slot)
    509           (setf (eieio-oref dummy slot)
    510                 (eieio-oref section slot))))
    511       dummy)))
    512 
    513 (add-hook 'magit-menu-alternative-section-hook
    514           #'magit--painted-branch-as-menu-section)
    515 
    516 (defun magit-insert-branch-description ()
    517   "Insert header containing the description of the current branch.
    518 Insert a header line with the name and description of the
    519 current branch.  The description is taken from the Git variable
    520 `branch.<NAME>.description'; if that is undefined then no header
    521 line is inserted at all."
    522   (when-let* ((branch (magit-get-current-branch))
    523               (desc (magit-get "branch" branch "description"))
    524               (desc (split-string desc "\n")))
    525     (when (equal (car (last desc)) "")
    526       (setq desc (butlast desc)))
    527     (magit-insert-section (branchdesc branch t)
    528       (magit-insert-heading branch ": " (car desc))
    529       (when (cdr desc)
    530         (insert (string-join (cdr desc) "\n"))
    531         (insert "\n\n")))))
    532 
    533 (defun magit-insert-tags ()
    534   "Insert sections showing all tags."
    535   (when-let ((tags (magit-git-lines "tag" "--list" "-n" magit-buffer-arguments)))
    536     (let ((_head (magit-rev-parse "HEAD")))
    537       (magit-insert-section (tags)
    538         (magit-insert-heading (length tags) "Tags")
    539         (dolist (tag tags)
    540           (string-match "^\\([^ \t]+\\)[ \t]+\\([^ \t\n].*\\)?" tag)
    541           (let ((tag (match-string 1 tag))
    542                 (msg (match-string 2 tag)))
    543             (when (magit-refs--insert-refname-p tag)
    544               (magit-insert-section (tag tag t)
    545                 (magit-insert-heading
    546                   (magit-refs--format-focus-column tag 'tag)
    547                   (propertize tag 'font-lock-face 'magit-tag)
    548                   (make-string
    549                    (max 1 (- (if (consp magit-refs-primary-column-width)
    550                                  (car magit-refs-primary-column-width)
    551                                magit-refs-primary-column-width)
    552                              (length tag)))
    553                    ?\s)
    554                   (and msg (magit-log-propertize-keywords nil msg)))
    555                 (when (and magit-refs-margin-for-tags (magit-buffer-margin-p))
    556                   (magit-refs--format-margin tag))
    557                 (magit-refs--insert-cherry-commits tag)))))
    558         (insert ?\n)
    559         (magit-make-margin-overlay nil t)))))
    560 
    561 (defun magit-insert-remote-branches ()
    562   "Insert sections showing all remote-tracking branches."
    563   (dolist (remote (magit-list-remotes))
    564     (magit-insert-section (remote remote)
    565       (magit-insert-heading
    566         (let ((pull (magit-get "remote" remote "url"))
    567               (push (magit-get "remote" remote "pushurl")))
    568           (format (propertize "Remote %s (%s):"
    569                               'font-lock-face 'magit-section-heading)
    570                   (propertize remote 'font-lock-face 'magit-branch-remote)
    571                   (concat pull (and pull push ", ") push))))
    572       (let (head)
    573         (dolist (line (magit-git-lines "for-each-ref" "--format=\
    574 %(symref:short)%00%(refname:short)%00%(refname)%00%(subject)"
    575                                        (concat "refs/remotes/" remote)
    576                                        magit-buffer-arguments))
    577           (pcase-let ((`(,head-branch ,branch ,ref ,msg)
    578                        (cl-substitute nil ""
    579                                       (split-string line "\0")
    580                                       :test #'equal)))
    581             (cond
    582              (head-branch
    583               ;; Note: Use `ref' instead of `branch' for the check
    584               ;; below because 'refname:short' shortens the remote
    585               ;; HEAD to '<remote>' instead of '<remote>/HEAD' as of
    586               ;; Git v2.40.0.
    587               (cl-assert
    588                (equal ref (concat "refs/remotes/" remote "/HEAD")))
    589               (setq head head-branch))
    590              ((not (equal ref (concat "refs/remotes/" remote "/HEAD")))
    591               ;; ^ Skip mis-configured remotes where HEAD is not a
    592               ;; symref.  See #5092.
    593               (when (magit-refs--insert-refname-p branch)
    594                 (magit-insert-section (branch branch t)
    595                   (let ((headp (equal branch head))
    596                         (abbrev (if magit-refs-show-remote-prefix
    597                                     branch
    598                                   (substring branch (1+ (length remote))))))
    599                     (magit-insert-heading
    600                       (magit-refs--format-focus-column branch)
    601                       (magit-refs--propertize-branch
    602                        abbrev ref (and headp 'magit-branch-remote-head))
    603                       (make-string
    604                        (max 1 (- (if (consp magit-refs-primary-column-width)
    605                                      (car magit-refs-primary-column-width)
    606                                    magit-refs-primary-column-width)
    607                                  (length abbrev)))
    608                        ?\s)
    609                       (and msg (magit-log-propertize-keywords nil msg))))
    610                   (when (magit-buffer-margin-p)
    611                     (magit-refs--format-margin branch))
    612                   (magit-refs--insert-cherry-commits branch))))))))
    613       (insert ?\n)
    614       (magit-make-margin-overlay nil t))))
    615 
    616 (defun magit-insert-local-branches ()
    617   "Insert sections showing all local branches."
    618   (magit-insert-section (local nil)
    619     (magit-insert-heading t "Branches")
    620     (dolist (line (magit-refs--format-local-branches))
    621       (pcase-let ((`(,branch . ,strings) line))
    622         (magit-insert-section
    623             ((eval (if branch 'branch 'commit))
    624              (or branch (magit-rev-parse "HEAD"))
    625              t)
    626           (apply #'magit-insert-heading strings)
    627           (when (magit-buffer-margin-p)
    628             (magit-refs--format-margin branch))
    629           (magit-refs--insert-cherry-commits branch))))
    630     (insert ?\n)
    631     (magit-make-margin-overlay nil t)))
    632 
    633 (defun magit-refs--format-local-branches ()
    634   (let ((lines (seq-keep #'magit-refs--format-local-branch
    635                          (magit-git-lines
    636                           "for-each-ref"
    637                           (concat "--format=\
    638 %(HEAD)%00%(refname:short)%00%(refname)%00\
    639 %(upstream:short)%00%(upstream)%00%(upstream:track)%00"
    640                                   (if magit-refs-show-push-remote "\
    641 %(push:remotename)%00%(push)%00%(push:track)%00%(subject)"
    642                                  "%00%00%00%(subject)"))
    643                           "refs/heads"
    644                           magit-buffer-arguments))))
    645     (unless (magit-get-current-branch)
    646       (push (magit-refs--format-local-branch
    647              (concat "*\0\0\0\0\0\0\0\0" (magit-rev-format "%s")))
    648             lines))
    649     (setq-local magit-refs-primary-column-width
    650                 (let ((def (default-value 'magit-refs-primary-column-width)))
    651                   (if (atom def)
    652                       def
    653                     (pcase-let ((`(,min . ,max) def))
    654                       (min max (apply #'max min (mapcar #'car lines)))))))
    655     (mapcar (pcase-lambda (`(,_ ,branch ,focus ,branch-desc ,u:ahead ,p:ahead
    656                                 ,u:behind ,upstream ,p:behind ,push ,msg))
    657               (list branch focus branch-desc u:ahead p:ahead
    658                     (make-string (max 1 (- magit-refs-primary-column-width
    659                                            (length (concat branch-desc
    660                                                            u:ahead
    661                                                            p:ahead
    662                                                            u:behind))))
    663                                  ?\s)
    664                     u:behind upstream p:behind push
    665                     msg))
    666             lines)))
    667 
    668 (defun magit-refs--format-local-branch (line)
    669   (pcase-let ((`(,head ,branch ,ref ,upstream ,u:ref ,u:track
    670                        ,push ,p:ref ,p:track ,msg)
    671                (cl-substitute nil "" (split-string line "\0") :test #'equal)))
    672     (when (or (not branch)
    673               (magit-refs--insert-refname-p branch))
    674       (let* ((headp (equal head "*"))
    675              (pushp (and push
    676                          magit-refs-show-push-remote
    677                          (magit-rev-verify p:ref)
    678                          (not (equal p:ref u:ref))))
    679              (branch-desc
    680               (if branch
    681                   (magit-refs--propertize-branch
    682                    branch ref (and headp 'magit-branch-current))
    683                 (magit--propertize-face "(detached)" 'magit-branch-warning)))
    684              (u:ahead  (and u:track
    685                             (string-match "ahead \\([0-9]+\\)" u:track)
    686                             (magit--propertize-face
    687                              (concat (and magit-refs-pad-commit-counts " ")
    688                                      (match-string 1 u:track)
    689                                      ">")
    690                              'magit-dimmed)))
    691              (u:behind (and u:track
    692                             (string-match "behind \\([0-9]+\\)" u:track)
    693                             (magit--propertize-face
    694                              (concat "<"
    695                                      (match-string 1 u:track)
    696                                      (and magit-refs-pad-commit-counts " "))
    697                              'magit-dimmed)))
    698              (p:ahead  (and pushp p:track
    699                             (string-match "ahead \\([0-9]+\\)" p:track)
    700                             (magit--propertize-face
    701                              (concat (match-string 1 p:track)
    702                                      ">"
    703                                      (and magit-refs-pad-commit-counts " "))
    704                              'magit-branch-remote)))
    705              (p:behind (and pushp p:track
    706                             (string-match "behind \\([0-9]+\\)" p:track)
    707                             (magit--propertize-face
    708                              (concat "<"
    709                                      (match-string 1 p:track)
    710                                      (and magit-refs-pad-commit-counts " "))
    711                              'magit-dimmed))))
    712         (list (1+ (length (concat branch-desc u:ahead p:ahead u:behind)))
    713               branch
    714               (magit-refs--format-focus-column branch headp)
    715               branch-desc u:ahead p:ahead u:behind
    716               (and upstream
    717                    (concat (if (equal u:track "[gone]")
    718                                (magit--propertize-face upstream 'error)
    719                              (magit-refs--propertize-branch upstream u:ref))
    720                            " "))
    721               (and pushp
    722                    (concat p:behind
    723                            (magit--propertize-face
    724                             push 'magit-branch-remote)
    725                            " "))
    726               (and msg (magit-log-propertize-keywords nil msg)))))))
    727 
    728 (defun magit-refs--format-focus-column (ref &optional type)
    729   (let ((focus magit-buffer-upstream)
    730         (width (if magit-refs-show-commit-count
    731                    magit-refs-focus-column-width
    732                  1)))
    733     (format
    734      (format "%%%ss " width)
    735      (cond ((or (equal ref focus)
    736                 (and (eq type t)
    737                      (equal focus "HEAD")))
    738             (magit--propertize-face (concat (if (equal focus "HEAD") "@" "*")
    739                                             (make-string (1- width) ?\s))
    740                                     'magit-section-heading))
    741            ((if (eq type 'tag)
    742                 (eq magit-refs-show-commit-count 'all)
    743               magit-refs-show-commit-count)
    744             (pcase-let ((`(,behind ,ahead)
    745                          (magit-rev-diff-count magit-buffer-upstream ref)))
    746               (magit--propertize-face
    747                (cond ((> ahead  0) (concat "<" (number-to-string ahead)))
    748                      ((> behind 0) (concat (number-to-string behind) ">"))
    749                      (t "="))
    750                'magit-dimmed)))
    751            (t "")))))
    752 
    753 (defun magit-refs--propertize-branch (branch ref &optional head-face)
    754   (let ((face (cdr (cl-find-if (pcase-lambda (`(,re . ,_))
    755                                  (string-match-p re ref))
    756                                magit-ref-namespaces))))
    757     (magit--propertize-face
    758      branch (if head-face (list face head-face) face))))
    759 
    760 (defun magit-refs--insert-refname-p (refname)
    761   (if-let ((entry (seq-find (pcase-lambda (`(,key . ,_))
    762                               (if (functionp key)
    763                                   (funcall key refname)
    764                                 (string-match-p key refname)))
    765                             magit-refs-filter-alist)))
    766       (cdr entry)
    767     t))
    768 
    769 (defun magit-refs--insert-cherry-commits (ref)
    770   (magit-insert-section-body
    771     (let ((start (point))
    772           (magit-insert-section--current nil))
    773       (magit-git-wash (apply-partially #'magit-log-wash-log 'cherry)
    774         "cherry" "-v" (magit-abbrev-arg) magit-buffer-upstream ref)
    775       (if (= (point) start)
    776           (message "No cherries for %s" ref)
    777         (magit-make-margin-overlay nil t)))))
    778 
    779 (defun magit-refs--format-margin (commit)
    780   (save-excursion
    781     (goto-char (line-beginning-position 0))
    782     (let ((line (magit-rev-format "%ct%cN" commit)))
    783       (magit-log-format-margin commit
    784                                (substring line 10)
    785                                (substring line 0 10)))))
    786 
    787 ;;; _
    788 (provide 'magit-refs)
    789 ;;; magit-refs.el ends here