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