dotemacs

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

magit-refs.el (32288B)


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