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