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