fordon_greeman / voidfiles

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