Codebase list magit / e23d5cf
magit-refs: new library Jonas Bernoulli 7 years ago
4 changed file(s) with 579 addition(s) and 530 deletion(s). Raw diff Collapse all Expand all
6060 ELS += magit-apply.el
6161 ELS += magit.el
6262 ELS += magit-status.el
63 ELS += magit-refs.el
6364 ELS += magit-obsolete.el
6465 ELS += magit-sequence.el
6566 ELS += magit-commit.el
2828 magit.elc: git-commit.elc magit-core.elc magit-diff.elc \
2929 magit-apply.elc magit-log.elc
3030 magit-status.elc: magit.elc
31 magit-refs.elc: magit.elc
3132 magit-obsolete.elc: magit.elc
3233 magit-sequence.elc: magit.elc
3334 magit-commit.elc: magit.elc magit-sequence.elc
0 ;;; magit-refs.el --- listing references -*- lexical-binding: t -*-
1
2 ;; Copyright (C) 2010-2016 The Magit Project Contributors
3 ;;
4 ;; You should have received a copy of the AUTHORS.md file which
5 ;; lists all contributors. If not, see http://magit.vc/authors.
6
7 ;; Author: Jonas Bernoulli <jonas@bernoul.li>
8 ;; Maintainer: Jonas Bernoulli <jonas@bernoul.li>
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, or (at your option)
13 ;; 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 http://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-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 all :tag "For branches and tags")
68 (const branch :tag "For branches only")
69 (const nil :tag "Never")))
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-margin
74 (list nil
75 (nth 1 magit-log-margin)
76 'magit-log-margin-width nil
77 (nth 4 magit-log-margin))
78 "Format of the margin in `magit-refs-mode' buffers.
79
80 The value has the form (INIT STYLE WIDTH AUTHOR AUTHOR-WIDTH).
81
82 If INIT is non-nil, then the margin is shown initially.
83 STYLE controls how to format the committer date. It can be one
84 of `age' (to show the age of the commit), `age-abbreviated' (to
85 abbreviate the time unit to a character), or a string (suitable
86 for `format-time-string') to show the actual date.
87 WIDTH controls the width of the margin. This exists for forward
88 compatibility and currently the value should not be changed.
89 AUTHOR controls whether the name of the author is also shown by
90 default.
91 AUTHOR-WIDTH has to be an integer. When the name of the author
92 is shown, then this specifies how much space is used to do so."
93 :package-version '(magit . "2.9.0")
94 :group 'magit-refs
95 :group 'magit-margin
96 :safe (lambda (val) (memq val '(all branch nil)))
97 :type magit-log-margin--custom-type
98 :initialize 'magit-custom-initialize-reset
99 :set-after '(magit-log-margin)
100 :set (apply-partially #'magit-margin-set-variable 'magit-refs-mode))
101
102 (defcustom magit-refs-margin-for-tags nil
103 "Whether to show information about tags in the margin.
104
105 This is disabled by default because it is slow if there are many
106 tags."
107 :package-version '(magit . "2.9.0")
108 :group 'magit-refs
109 :group 'magit-margin
110 :type 'boolean)
111
112 (defcustom magit-visit-ref-behavior nil
113 "Control how `magit-visit-ref' behaves in `magit-refs-mode' buffers.
114
115 By default `magit-visit-ref' behaves like `magit-show-commits',
116 in all buffers, including `magit-refs-mode' buffers. When the
117 type of the section at point is `commit' then \"RET\" is bound to
118 `magit-show-commit', and when the type is either `branch' or
119 `tag' then it is bound to `magit-visit-ref'.
120
121 \"RET\" is one of Magit's most essential keys and at least by
122 default it should behave consistently across all of Magit,
123 especially because users quickly learn that it does something
124 very harmless; it shows more information about the thing at point
125 in another buffer.
126
127 However \"RET\" used to behave differently in `magit-refs-mode'
128 buffers, doing surprising things, some of which cannot really be
129 described as \"visit this thing\". If you have grown accustomed
130 to such inconsistent, but to you useful, behavior then you can
131 restore that by adding one or more of the below symbols to the
132 value of this option. But keep in mind that by doing so you
133 don't only introduce inconsistencies, you also lose some
134 functionality and might have to resort to `M-x magit-show-commit'
135 to get it back.
136
137 `magit-visit-ref' looks for these symbols in the order in which
138 they are described here. If the presence of a symbol applies to
139 the current situation, then the symbols that follow do not affect
140 the outcome.
141
142 `focus-on-ref'
143
144 With a prefix argument update the buffer to show commit counts
145 and lists of cherry commits relative to the reference at point
146 instead of relative to the current buffer or HEAD.
147
148 Instead of adding this symbol, consider pressing \"C-u y o RET\".
149
150 `create-branch'
151
152 If point is on a remote branch, then create a new local branch
153 with the same name, use the remote branch as its upstream, and
154 then check out the local branch.
155
156 Instead of adding this symbol, consider pressing \"b c RET RET\",
157 like you would do in other buffers.
158
159 `checkout-any'
160
161 Check out the reference at point. If that reference is a tag
162 or a remote branch, then this results in a detached HEAD.
163
164 Instead of adding this symbol, consider pressing \"b b RET\",
165 like you would do in other buffers.
166
167 `checkout-branch'
168
169 Check out the local branch at point.
170
171 Instead of adding this symbol, consider pressing \"b b RET\",
172 like you would do in other buffers."
173 :package-version '(magit . "2.9.0")
174 :group 'magit-refs
175 :group 'magit-commands
176 :options '(focus-on-ref create-branch checkout-any checkout-branch)
177 :type '(list :convert-widget custom-hook-convert-widget))
178
179 ;;; Mode
180
181 (defvar magit-refs-mode-map
182 (let ((map (make-sparse-keymap)))
183 (set-keymap-parent map magit-mode-map)
184 (define-key map "\C-y" 'magit-refs-set-show-commit-count)
185 (define-key map "L" 'magit-margin-popup)
186 map)
187 "Keymap for `magit-refs-mode'.")
188
189 (define-derived-mode magit-refs-mode magit-mode "Magit Refs"
190 "Mode which lists and compares references.
191
192 This mode is documented in info node `(magit)References buffer'.
193
194 \\<magit-mode-map>\
195 Type \\[magit-refresh] to refresh the current buffer.
196 Type \\[magit-section-toggle] to expand or hide the section at point.
197 Type \\[magit-visit-thing] or \\[magit-diff-show-or-scroll-up] \
198 to visit the commit or branch at point.
199
200 Type \\[magit-branch-popup] to see available branch commands.
201 Type \\[magit-merge-popup] to merge the branch or commit at point.
202 Type \\[magit-cherry-pick-popup] to apply the commit at point.
203 Type \\[magit-reset] to reset HEAD to the commit at point.
204
205 \\{magit-refs-mode-map}"
206 :group 'magit-refs
207 (hack-dir-local-variables-non-file-buffer))
208
209 (defun magit-refs-refresh-buffer (&rest _ignore)
210 (setq magit-set-buffer-margin-refresh (not (magit-buffer-margin-p)))
211 (unless (magit-rev-verify (or (car magit-refresh-args) "HEAD"))
212 (setq magit-refs-show-commit-count nil))
213 (magit-insert-section (branchbuf)
214 (run-hooks 'magit-refs-sections-hook)))
215
216 ;;; Commands
217
218 ;;;###autoload (autoload 'magit-show-refs-popup "magit" nil t)
219 (magit-define-popup magit-show-refs-popup
220 "Popup console for `magit-show-refs'."
221 :man-page "git-branch"
222 :switches '((?m "Merged to HEAD" "--merged")
223 (?M "Merged to master" "--merged=master")
224 (?n "Not merged to HEAD" "--no-merged")
225 (?N "Not merged to master" "--no-merged=master"))
226 :options '((?c "Contains" "--contains=" magit-read-branch-or-commit)
227 (?m "Merged" "--merged=" magit-read-branch-or-commit)
228 (?n "Not merged" "--no-merged=" magit-read-branch-or-commit)
229 (?s "Sort" "--sort=" magit-read-ref-sort))
230 :actions '((?y "Show refs, comparing them with HEAD"
231 magit-show-refs-head)
232 (?c "Show refs, comparing them with current branch"
233 magit-show-refs-current)
234 (?o "Show refs, comparing them with other branch"
235 magit-show-refs))
236 :default-action 'magit-show-refs-head
237 :use-prefix 'popup)
238
239 (defun magit-read-ref-sort (prompt initial-input)
240 (magit-completing-read prompt
241 '("-committerdate" "-authordate"
242 "committerdate" "authordate")
243 nil nil initial-input))
244
245 ;;;###autoload
246 (defun magit-show-refs-head (&optional args)
247 "List and compare references in a dedicated buffer.
248 Refs are compared with `HEAD'."
249 (interactive (list (magit-show-refs-arguments)))
250 (magit-show-refs nil args))
251
252 ;;;###autoload
253 (defun magit-show-refs-current (&optional args)
254 "List and compare references in a dedicated buffer.
255 Refs are compared with the current branch or `HEAD' if
256 it is detached."
257 (interactive (list (magit-show-refs-arguments)))
258 (magit-show-refs (magit-get-current-branch) args))
259
260 ;;;###autoload
261 (defun magit-show-refs (&optional ref args)
262 "List and compare references in a dedicated buffer.
263 Refs are compared with a branch read from the user."
264 (interactive (list (magit-read-other-branch "Compare with")
265 (magit-show-refs-arguments)))
266 (magit-mode-setup #'magit-refs-mode ref args))
267
268 (defun magit-refs-set-show-commit-count ()
269 "Change for which refs the commit count is shown."
270 (interactive)
271 (setq-local magit-refs-show-commit-count
272 (magit-read-char-case "Show commit counts for " nil
273 (?a "[a]ll refs" 'all)
274 (?b "[b]ranches only" t)
275 (?n "[n]othing" nil)))
276 (magit-refresh))
277
278 (defun magit-visit-ref ()
279 "Visit the reference or revision at point in another buffer.
280 If there is no revision at point or with a prefix argument prompt
281 for a revision.
282
283 This command behaves just like `magit-show-commit', except if
284 point is on a reference in a `magit-refs-mode' buffer (a buffer
285 listing branches and tags), in which case the behavior may be
286 different, but only if you have customized the option
287 `magit-visit-ref-behavior' (which see)."
288 (interactive)
289 (if (and (derived-mode-p 'magit-refs-mode)
290 (magit-section-match '(branch tag)))
291 (let ((ref (magit-section-value (magit-current-section))))
292 (cond ((and (memq 'focus-on-ref magit-visit-ref-behavior)
293 current-prefix-arg)
294 (magit-show-refs ref))
295 ((and (memq 'create-branch magit-visit-ref-behavior)
296 (magit-section-match [branch remote]))
297 (let ((branch (cdr (magit-split-branch-name ref))))
298 (if (magit-branch-p branch)
299 (if (yes-or-no-p
300 (format "Branch %s already exists. Reset it to %s?"
301 branch ref))
302 (magit-call-git "checkout" "-B" branch ref)
303 (user-error "Abort"))
304 (magit-call-git "checkout" "-b" branch ref))
305 (setcar magit-refresh-args branch)
306 (magit-refresh)))
307 ((or (memq 'checkout-any magit-visit-ref-behavior)
308 (and (memq 'checkout-branch magit-visit-ref-behavior)
309 (magit-section-match [branch local])))
310 (magit-call-git "checkout" ref)
311 (setcar magit-refresh-args ref)
312 (magit-refresh))
313 (t
314 (call-interactively #'magit-show-commit))))
315 (call-interactively #'magit-show-commit)))
316
317 ;;; Sections
318 ;;;; Section Keymaps
319
320 (defvar magit-branch-section-map
321 (let ((map (make-sparse-keymap)))
322 (define-key map [remap magit-visit-thing] 'magit-visit-ref)
323 (define-key map [remap magit-delete-thing] 'magit-branch-delete)
324 (define-key map "R" 'magit-branch-rename)
325 map)
326 "Keymap for `branch' sections.")
327
328 (defvar magit-remote-section-map
329 (let ((map (make-sparse-keymap)))
330 (define-key map [remap magit-delete-thing] 'magit-remote-remove)
331 (define-key map "R" 'magit-remote-rename)
332 map)
333 "Keymap for `remote' sections.")
334
335 (defvar magit-tag-section-map
336 (let ((map (make-sparse-keymap)))
337 (define-key map [remap magit-visit-thing] 'magit-visit-ref)
338 (define-key map [remap magit-delete-thing] 'magit-tag-delete)
339 map)
340 "Keymap for `tag' sections.")
341
342 ;;;; Section Variables
343
344 (defconst magit-refs-branch-line-re
345 (concat "^"
346 "\\(?:[ \\*]\\) "
347 "\\(?1:([^)]+)\\|[^ ]+?\\)" ; branch
348 "\\(?: +\\)"
349 "\\(?2:[0-9a-fA-F]+\\) " ; sha1
350 "\\(?:\\["
351 "\\(?4:[^:]+\\)" ; upstream
352 "\\(?:: \\(?:"
353 "\\(?7:gone\\)\\|" ; gone
354 "\\(?:ahead \\(?5:[0-9]+\\)\\)?" ; ahead
355 "\\(?:, \\)?"
356 "\\(?:behind \\(?6:[0-9]+\\)\\)?" ; behind
357 "\\)\\)?"
358 "\\] \\)?"
359 "\\(?3:.*\\)")) ; message
360
361 (defconst magit-refs-symref-line-re "^ \\([^ ]+\\) +-> \\(.+\\)")
362
363 (defvar magit-refs-local-branch-format "%4c %-25n %U%m\n"
364 "Format used for local branches in refs buffers.")
365 (defvar magit-refs-remote-branch-format "%4c %-25n %m\n"
366 "Format used for remote branches in refs buffers.")
367 (defvar magit-refs-symref-format "%4c %-25n -> %m\n"
368 "Format used for symrefs in refs buffers.")
369 (defvar magit-refs-tags-format "%4c %-25n %m\n"
370 "Format used for tags in refs buffers.")
371 (defvar magit-refs-indent-cherry-lines 3
372 "Indentation of cherries in refs buffers.")
373
374 ;;;; Branch Sections
375
376 (defun magit-insert-branch-description ()
377 "Insert header containing the description of the current branch.
378 Insert a header line with the name and description of the
379 current branch. The description is taken from the Git variable
380 `branch.<NAME>.description'; if that is undefined then no header
381 line is inserted at all."
382 (let ((branch (magit-get-current-branch)))
383 (--when-let (magit-git-lines
384 "config" (format "branch.%s.description" branch))
385 (magit-insert-section (branchdesc branch t)
386 (magit-insert-heading branch ": " (car it))
387 (insert (mapconcat 'identity (cdr it) "\n"))
388 (insert "\n\n")))))
389
390 (defun magit-insert-local-branches ()
391 "Insert sections showing all local branches."
392 (magit-insert-section (local nil)
393 (magit-insert-heading "Branches:")
394 (let ((current (magit-get-current-branch))
395 (branches (magit-list-local-branch-names)))
396 (dolist (line (magit-git-lines "branch" "-vv"
397 (cadr magit-refresh-args)))
398 (cond
399 ((string-match magit-refs-branch-line-re line)
400 (magit-bind-match-strings
401 (branch hash message upstream ahead behind gone) line
402 (when (string-match-p "(HEAD detached" branch)
403 (setq branch nil))
404 (magit-insert-branch
405 branch magit-refs-local-branch-format current branches
406 'magit-branch-local hash message upstream ahead behind gone)))
407 ((string-match magit-refs-symref-line-re line)
408 (magit-bind-match-strings (symref ref) line
409 (magit-insert-symref symref ref 'magit-branch-local))))))
410 (insert ?\n)
411 (magit-make-margin-overlay nil t)))
412
413 (defun magit-insert-remote-branches ()
414 "Insert sections showing all remote-tracking branches."
415 (dolist (remote (magit-list-remotes))
416 (magit-insert-section (remote remote)
417 (magit-insert-heading
418 (let ((pull (magit-get "remote" remote "url"))
419 (push (magit-get "remote" remote "pushurl")))
420 (format "%s (%s):" (capitalize remote)
421 (concat pull (and pull push ", ") push))))
422 (let ((current (magit-get-current-branch))
423 (branches (magit-list-local-branch-names)))
424 (dolist (line (magit-git-lines "branch" "-vvr"
425 (cadr magit-refresh-args)))
426 (cond
427 ((string-match magit-refs-branch-line-re line)
428 (magit-bind-match-strings (branch hash message) line
429 (when (string-match-p (format "^%s/" remote) branch)
430 (magit-insert-branch
431 branch magit-refs-remote-branch-format current branches
432 'magit-branch-remote hash message))))
433 ((string-match magit-refs-symref-line-re line)
434 (magit-bind-match-strings (symref ref) line
435 (magit-insert-symref symref ref 'magit-branch-remote))))))
436 (insert ?\n)
437 (magit-make-margin-overlay nil t))))
438
439 (defun magit-insert-branch (branch format &rest args)
440 "For internal use, don't add to a hook."
441 (unless magit-refs-show-commit-count
442 (setq format (replace-regexp-in-string "%[0-9]\\([cC]\\)" "%1\\1" format t)))
443 (if (equal branch "HEAD")
444 (magit-insert-section it (commit (magit-rev-parse "HEAD") t)
445 (apply #'magit-insert-branch-1 it nil format args))
446 (magit-insert-section it (branch branch t)
447 (apply #'magit-insert-branch-1 it branch format args))))
448
449 (defun magit-insert-branch-1
450 (section branch format current branches face
451 &optional hash message upstream ahead behind gone)
452 "For internal use, don't add to a hook."
453 (let* ((head (or (car magit-refresh-args) current "HEAD"))
454 (count (and branch
455 (magit-refs-format-commit-count branch head format)))
456 (mark (cond ((or (equal branch head)
457 (and (not branch) (equal head "HEAD")))
458 (if (equal branch current)
459 (propertize "@" 'face 'magit-head)
460 (propertize "#" 'face 'magit-tag)))
461 ((equal branch current)
462 (propertize "." 'face 'magit-head)))))
463 (when upstream
464 (setq upstream (propertize upstream 'face
465 (if (member upstream branches)
466 'magit-branch-local
467 'magit-branch-remote))))
468 (magit-insert-heading
469 (format-spec
470 format
471 `((?a . ,(or ahead ""))
472 (?b . ,(or behind ""))
473 (?c . ,(or mark count ""))
474 (?C . ,(or mark " "))
475 (?h . ,(or (propertize hash 'face 'magit-hash) ""))
476 (?m . ,(or message ""))
477 (?n . ,(propertize (or branch "(detached)") 'face face))
478 (?u . ,(or upstream ""))
479 (?U . ,(if upstream
480 (format (propertize "[%s%s] " 'face 'magit-dimmed)
481 upstream
482 (cond
483 (gone
484 (concat ": " (propertize gone 'face 'error)))
485 ((or ahead behind)
486 (concat ": "
487 (and ahead (format "ahead %s" ahead))
488 (and ahead behind ", ")
489 (and behind (format "behind %s" behind))))
490 (t "")))
491 "")))))
492 (when (magit-buffer-margin-p)
493 (magit-refs-format-margin branch))
494 (magit-refs-insert-cherry-commits head branch section)))
495
496 (defun magit-insert-symref (symref ref face)
497 "For internal use, don't add to a hook."
498 (magit-insert-section (commit symref)
499 (insert
500 (format-spec (if magit-refs-show-commit-count
501 magit-refs-symref-format
502 (replace-regexp-in-string "%[0-9]\\([cC]\\)" "%1\\1"
503 magit-refs-symref-format t))
504 `((?c . "")
505 (?n . ,(propertize symref 'face face))
506 (?m . ,(propertize ref 'face face)))))))
507
508 (defun magit-refs-format-commit-count (ref head format &optional tag-p)
509 (and (string-match-p "%-?[0-9]+c" format)
510 (if tag-p
511 (eq magit-refs-show-commit-count 'all)
512 magit-refs-show-commit-count)
513 (let ((count (cadr (magit-rev-diff-count head ref))))
514 (and (> count 0)
515 (propertize (number-to-string count) 'face 'magit-dimmed)))))
516
517 ;;;; Tag Sections
518
519 (defun magit-insert-tags ()
520 "Insert sections showing all tags."
521 (-when-let (tags (magit-git-lines "tag" "-l" "-n"))
522 (magit-insert-section (tags)
523 (magit-insert-heading "Tags:")
524 (let ((head (or (car magit-refresh-args)
525 (magit-get-current-branch)
526 "HEAD"))
527 (format (if magit-refs-show-commit-count
528 magit-refs-tags-format
529 (replace-regexp-in-string
530 "%[0-9]\\([cC]\\)" "%1\\1" magit-refs-tags-format t))))
531 (dolist (tag (nreverse tags))
532 (string-match "^\\([^ \t]+\\)[ \t]+\\([^ \t\n].*\\)?" tag)
533 (let* ((message (match-string 2 tag))
534 (tag (match-string 1 tag))
535 (count (magit-refs-format-commit-count tag head format t))
536 (mark (and (equal tag head)
537 (propertize "#" 'face 'magit-tag))))
538 (magit-insert-section section (tag tag t)
539 (magit-insert-heading
540 (format-spec format
541 `((?n . ,(propertize tag 'face 'magit-tag))
542 (?c . ,(or mark count ""))
543 (?m . ,(or message "")))))
544 (when (and (magit-buffer-margin-p)
545 magit-refs-margin-for-tags)
546 (magit-refs-format-margin (concat tag "^{commit}")))
547 (magit-refs-insert-cherry-commits head tag section)))))
548 (insert ?\n))))
549
550 ;;;; Cherry Sections
551
552 (defun magit-refs-insert-cherry-commits (head ref section)
553 (if (magit-section-hidden section)
554 (setf (magit-section-washer section)
555 (apply-partially #'magit-refs-insert-cherry-commits-1
556 head ref section))
557 (magit-refs-insert-cherry-commits-1 head ref section)))
558
559 (defun magit-refs-insert-cherry-commits-1 (head ref section)
560 (let ((start (point)))
561 (magit-git-wash (apply-partially 'magit-log-wash-log 'cherry)
562 "cherry" "-v" "--abbrev" head ref magit-refresh-args)
563 (unless (= (point) start)
564 (insert (propertize "\n" 'magit-section section))
565 (magit-make-margin-overlay nil t))))
566
567 (defun magit-refs-format-margin (commit)
568 (save-excursion
569 (goto-char (line-beginning-position 0))
570 (let ((line (magit-rev-format "%ct%cN" commit)))
571 (magit-log-format-margin (substring line 10)
572 (substring line 0 10)))))
573
574 (provide 'magit-refs)
575 ;;; magit-refs.el ends here
7070 (defconst magit--minimal-emacs "24.4")
7171
7272 ;;; Options
73 ;;;; Refs Mode
74
75 (defgroup magit-refs nil
76 "Inspect and manipulate Git branches and tags."
77 :link '(info-link "(magit)References Buffer")
78 :group 'magit-modes)
79
80 (defcustom magit-refs-mode-hook nil
81 "Hook run after entering Magit-Refs mode."
82 :package-version '(magit . "2.1.0")
83 :group 'magit-refs
84 :type 'hook)
85
86 (defcustom magit-refs-sections-hook
87 '(magit-insert-error-header
88 magit-insert-branch-description
89 magit-insert-local-branches
90 magit-insert-remote-branches
91 magit-insert-tags)
92 "Hook run to insert sections into a references buffer."
93 :package-version '(magit . "2.1.0")
94 :group 'magit-refs
95 :type 'hook)
96
97 (defcustom magit-refs-show-commit-count nil
98 "Whether to show commit counts in Magit-Refs mode buffers.
99
100 all Show counts for branches and tags.
101 branch Show counts for branches only.
102 nil Never show counts.
103
104 To change the value in an existing buffer use the command
105 `magit-refs-show-commit-count'"
106 :package-version '(magit . "2.1.0")
107 :group 'magit-refs
108 :safe (lambda (val) (memq val '(all branch nil)))
109 :type '(choice (const all :tag "For branches and tags")
110 (const branch :tag "For branches only")
111 (const nil :tag "Never")))
112 (put 'magit-refs-show-commit-count 'safe-local-variable 'symbolp)
113 (put 'magit-refs-show-commit-count 'permanent-local t)
114
115 (defcustom magit-refs-margin
116 (list nil
117 (nth 1 magit-log-margin)
118 'magit-log-margin-width nil
119 (nth 4 magit-log-margin))
120 "Format of the margin in `magit-refs-mode' buffers.
121
122 The value has the form (INIT STYLE WIDTH AUTHOR AUTHOR-WIDTH).
123
124 If INIT is non-nil, then the margin is shown initially.
125 STYLE controls how to format the committer date. It can be one
126 of `age' (to show the age of the commit), `age-abbreviated' (to
127 abbreviate the time unit to a character), or a string (suitable
128 for `format-time-string') to show the actual date.
129 WIDTH controls the width of the margin. This exists for forward
130 compatibility and currently the value should not be changed.
131 AUTHOR controls whether the name of the author is also shown by
132 default.
133 AUTHOR-WIDTH has to be an integer. When the name of the author
134 is shown, then this specifies how much space is used to do so."
135 :package-version '(magit . "2.9.0")
136 :group 'magit-refs
137 :group 'magit-margin
138 :safe (lambda (val) (memq val '(all branch nil)))
139 :type magit-log-margin--custom-type
140 :initialize 'magit-custom-initialize-reset
141 :set-after '(magit-log-margin)
142 :set (apply-partially #'magit-margin-set-variable 'magit-refs-mode))
143
144 (defcustom magit-refs-margin-for-tags nil
145 "Whether to show information about tags in the margin.
146
147 This is disabled by default because it is slow if there are many
148 tags."
149 :package-version '(magit . "2.9.0")
150 :group 'magit-refs
151 :group 'magit-margin
152 :type 'boolean)
153
154 (defcustom magit-visit-ref-behavior nil
155 "Control how `magit-visit-ref' behaves in `magit-refs-mode' buffers.
156
157 By default `magit-visit-ref' behaves like `magit-show-commits',
158 in all buffers, including `magit-refs-mode' buffers. When the
159 type of the section at point is `commit' then \"RET\" is bound to
160 `magit-show-commit', and when the type is either `branch' or
161 `tag' then it is bound to `magit-visit-ref'.
162
163 \"RET\" is one of Magit's most essential keys and at least by
164 default it should behave consistently across all of Magit,
165 especially because users quickly learn that it does something
166 very harmless; it shows more information about the thing at point
167 in another buffer.
168
169 However \"RET\" used to behave differently in `magit-refs-mode'
170 buffers, doing surprising things, some of which cannot really be
171 described as \"visit this thing\". If you have grown accustomed
172 to such inconsistent, but to you useful, behavior then you can
173 restore that by adding one or more of the below symbols to the
174 value of this option. But keep in mind that by doing so you
175 don't only introduce inconsistencies, you also lose some
176 functionality and might have to resort to `M-x magit-show-commit'
177 to get it back.
178
179 `magit-visit-ref' looks for these symbols in the order in which
180 they are described here. If the presence of a symbol applies to
181 the current situation, then the symbols that follow do not affect
182 the outcome.
183
184 `focus-on-ref'
185
186 With a prefix argument update the buffer to show commit counts
187 and lists of cherry commits relative to the reference at point
188 instead of relative to the current buffer or HEAD.
189
190 Instead of adding this symbol, consider pressing \"C-u y o RET\".
191
192 `create-branch'
193
194 If point is on a remote branch, then create a new local branch
195 with the same name, use the remote branch as its upstream, and
196 then check out the local branch.
197
198 Instead of adding this symbol, consider pressing \"b c RET RET\",
199 like you would do in other buffers.
200
201 `checkout-any'
202
203 Check out the reference at point. If that reference is a tag
204 or a remote branch, then this results in a detached HEAD.
205
206 Instead of adding this symbol, consider pressing \"b b RET\",
207 like you would do in other buffers.
208
209 `checkout-branch'
210
211 Check out the local branch at point.
212
213 Instead of adding this symbol, consider pressing \"b b RET\",
214 like you would do in other buffers."
215 :package-version '(magit . "2.9.0")
216 :group 'magit-refs
217 :group 'magit-commands
218 :options '(focus-on-ref create-branch checkout-any checkout-branch)
219 :type '(list :convert-widget custom-hook-convert-widget))
220
22173 ;;;; Miscellaneous
22274
22375 (defcustom magit-branch-read-upstream-first t
521373 :group 'magit-faces)
522374
523375 ;;; Inspect
524 ;;;; Refs Mode
525
526 (defvar magit-refs-mode-map
527 (let ((map (make-sparse-keymap)))
528 (set-keymap-parent map magit-mode-map)
529 (define-key map "\C-y" 'magit-refs-set-show-commit-count)
530 (define-key map "L" 'magit-margin-popup)
531 map)
532 "Keymap for `magit-refs-mode'.")
533
534 (define-derived-mode magit-refs-mode magit-mode "Magit Refs"
535 "Mode which lists and compares references.
536
537 This mode is documented in info node `(magit)References buffer'.
538
539 \\<magit-mode-map>\
540 Type \\[magit-refresh] to refresh the current buffer.
541 Type \\[magit-section-toggle] to expand or hide the section at point.
542 Type \\[magit-visit-thing] or \\[magit-diff-show-or-scroll-up] \
543 to visit the commit or branch at point.
544
545 Type \\[magit-branch-popup] to see available branch commands.
546 Type \\[magit-merge-popup] to merge the branch or commit at point.
547 Type \\[magit-cherry-pick-popup] to apply the commit at point.
548 Type \\[magit-reset] to reset HEAD to the commit at point.
549
550 \\{magit-refs-mode-map}"
551 :group 'magit-refs
552 (hack-dir-local-variables-non-file-buffer))
553
554 ;;;###autoload (autoload 'magit-show-refs-popup "magit" nil t)
555 (magit-define-popup magit-show-refs-popup
556 "Popup console for `magit-show-refs'."
557 :man-page "git-branch"
558 :switches '((?m "Merged to HEAD" "--merged")
559 (?M "Merged to master" "--merged=master")
560 (?n "Not merged to HEAD" "--no-merged")
561 (?N "Not merged to master" "--no-merged=master"))
562 :options '((?c "Contains" "--contains=" magit-read-branch-or-commit)
563 (?m "Merged" "--merged=" magit-read-branch-or-commit)
564 (?n "Not merged" "--no-merged=" magit-read-branch-or-commit)
565 (?s "Sort" "--sort=" magit-read-ref-sort))
566 :actions '((?y "Show refs, comparing them with HEAD"
567 magit-show-refs-head)
568 (?c "Show refs, comparing them with current branch"
569 magit-show-refs-current)
570 (?o "Show refs, comparing them with other branch"
571 magit-show-refs))
572 :default-action 'magit-show-refs-head
573 :use-prefix 'popup)
574
575 (defun magit-read-ref-sort (prompt initial-input)
576 (magit-completing-read prompt
577 '("-committerdate" "-authordate"
578 "committerdate" "authordate")
579 nil nil initial-input))
580
581 ;;;###autoload
582 (defun magit-show-refs-head (&optional args)
583 "List and compare references in a dedicated buffer.
584 Refs are compared with `HEAD'."
585 (interactive (list (magit-show-refs-arguments)))
586 (magit-show-refs nil args))
587
588 ;;;###autoload
589 (defun magit-show-refs-current (&optional args)
590 "List and compare references in a dedicated buffer.
591 Refs are compared with the current branch or `HEAD' if
592 it is detached."
593 (interactive (list (magit-show-refs-arguments)))
594 (magit-show-refs (magit-get-current-branch) args))
595
596 ;;;###autoload
597 (defun magit-show-refs (&optional ref args)
598 "List and compare references in a dedicated buffer.
599 Refs are compared with a branch read from the user."
600 (interactive (list (magit-read-other-branch "Compare with")
601 (magit-show-refs-arguments)))
602 (magit-mode-setup #'magit-refs-mode ref args))
603
604 (defun magit-refs-refresh-buffer (&rest _ignore)
605 (setq magit-set-buffer-margin-refresh (not (magit-buffer-margin-p)))
606 (unless (magit-rev-verify (or (car magit-refresh-args) "HEAD"))
607 (setq magit-refs-show-commit-count nil))
608 (magit-insert-section (branchbuf)
609 (run-hooks 'magit-refs-sections-hook)))
610
611 (defun magit-insert-branch-description ()
612 "Insert header containing the description of the current branch.
613 Insert a header line with the name and description of the
614 current branch. The description is taken from the Git variable
615 `branch.<NAME>.description'; if that is undefined then no header
616 line is inserted at all."
617 (let ((branch (magit-get-current-branch)))
618 (--when-let (magit-git-lines
619 "config" (format "branch.%s.description" branch))
620 (magit-insert-section (branchdesc branch t)
621 (magit-insert-heading branch ": " (car it))
622 (insert (mapconcat 'identity (cdr it) "\n"))
623 (insert "\n\n")))))
624
625 (defconst magit-refs-branch-line-re
626 (concat "^"
627 "\\(?:[ \\*]\\) "
628 "\\(?1:([^)]+)\\|[^ ]+?\\)" ; branch
629 "\\(?: +\\)"
630 "\\(?2:[0-9a-fA-F]+\\) " ; sha1
631 "\\(?:\\["
632 "\\(?4:[^:]+\\)" ; upstream
633 "\\(?:: \\(?:"
634 "\\(?7:gone\\)\\|" ; gone
635 "\\(?:ahead \\(?5:[0-9]+\\)\\)?" ; ahead
636 "\\(?:, \\)?"
637 "\\(?:behind \\(?6:[0-9]+\\)\\)?" ; behind
638 "\\)\\)?"
639 "\\] \\)?"
640 "\\(?3:.*\\)")) ; message
641
642 (defconst magit-refs-symref-line-re "^ \\([^ ]+\\) +-> \\(.+\\)")
643
644 (defvar magit-refs-local-branch-format "%4c %-25n %U%m\n"
645 "Format used for local branches in refs buffers.")
646 (defvar magit-refs-remote-branch-format "%4c %-25n %m\n"
647 "Format used for remote branches in refs buffers.")
648 (defvar magit-refs-symref-format "%4c %-25n -> %m\n"
649 "Format used for symrefs in refs buffers.")
650 (defvar magit-refs-tags-format "%4c %-25n %m\n"
651 "Format used for tags in refs buffers.")
652 (defvar magit-refs-indent-cherry-lines 3
653 "Indentation of cherries in refs buffers.")
654
655 (defvar magit-branch-section-map
656 (let ((map (make-sparse-keymap)))
657 (define-key map [remap magit-visit-thing] 'magit-visit-ref)
658 (define-key map [remap magit-delete-thing] 'magit-branch-delete)
659 (define-key map "R" 'magit-branch-rename)
660 map)
661 "Keymap for `branch' sections.")
662
663 (defvar magit-remote-section-map
664 (let ((map (make-sparse-keymap)))
665 (define-key map [remap magit-delete-thing] 'magit-remote-remove)
666 (define-key map "R" 'magit-remote-rename)
667 map)
668 "Keymap for `remote' sections.")
669
670 (defun magit-refs-set-show-commit-count ()
671 "Change for which refs the commit count is shown."
672 (interactive)
673 (setq-local magit-refs-show-commit-count
674 (magit-read-char-case "Show commit counts for " nil
675 (?a "[a]ll refs" 'all)
676 (?b "[b]ranches only" t)
677 (?n "[n]othing" nil)))
678 (magit-refresh))
679
680 (defun magit-visit-ref ()
681 "Visit the reference or revision at point in another buffer.
682 If there is no revision at point or with a prefix argument prompt
683 for a revision.
684
685 This command behaves just like `magit-show-commit', except if
686 point is on a reference in a `magit-refs-mode' buffer (a buffer
687 listing branches and tags), in which case the behavior may be
688 different, but only if you have customized the option
689 `magit-visit-ref-behavior' (which see)."
690 (interactive)
691 (if (and (derived-mode-p 'magit-refs-mode)
692 (magit-section-match '(branch tag)))
693 (let ((ref (magit-section-value (magit-current-section))))
694 (cond ((and (memq 'focus-on-ref magit-visit-ref-behavior)
695 current-prefix-arg)
696 (magit-show-refs ref))
697 ((and (memq 'create-branch magit-visit-ref-behavior)
698 (magit-section-match [branch remote]))
699 (let ((branch (cdr (magit-split-branch-name ref))))
700 (if (magit-branch-p branch)
701 (if (yes-or-no-p
702 (format "Branch %s already exists. Reset it to %s?"
703 branch ref))
704 (magit-call-git "checkout" "-B" branch ref)
705 (user-error "Abort"))
706 (magit-call-git "checkout" "-b" branch ref))
707 (setcar magit-refresh-args branch)
708 (magit-refresh)))
709 ((or (memq 'checkout-any magit-visit-ref-behavior)
710 (and (memq 'checkout-branch magit-visit-ref-behavior)
711 (magit-section-match [branch local])))
712 (magit-call-git "checkout" ref)
713 (setcar magit-refresh-args ref)
714 (magit-refresh))
715 (t
716 (call-interactively #'magit-show-commit))))
717 (call-interactively #'magit-show-commit)))
718
719 (defun magit-insert-local-branches ()
720 "Insert sections showing all local branches."
721 (magit-insert-section (local nil)
722 (magit-insert-heading "Branches:")
723 (let ((current (magit-get-current-branch))
724 (branches (magit-list-local-branch-names)))
725 (dolist (line (magit-git-lines "branch" "-vv"
726 (cadr magit-refresh-args)))
727 (cond
728 ((string-match magit-refs-branch-line-re line)
729 (magit-bind-match-strings
730 (branch hash message upstream ahead behind gone) line
731 (when (string-match-p "(HEAD detached" branch)
732 (setq branch nil))
733 (magit-insert-branch
734 branch magit-refs-local-branch-format current branches
735 'magit-branch-local hash message upstream ahead behind gone)))
736 ((string-match magit-refs-symref-line-re line)
737 (magit-bind-match-strings (symref ref) line
738 (magit-insert-symref symref ref 'magit-branch-local))))))
739 (insert ?\n)
740 (magit-make-margin-overlay nil t)))
741
742 (defun magit-insert-remote-branches ()
743 "Insert sections showing all remote-tracking branches."
744 (dolist (remote (magit-list-remotes))
745 (magit-insert-section (remote remote)
746 (magit-insert-heading
747 (let ((pull (magit-get "remote" remote "url"))
748 (push (magit-get "remote" remote "pushurl")))
749 (format "%s (%s):" (capitalize remote)
750 (concat pull (and pull push ", ") push))))
751 (let ((current (magit-get-current-branch))
752 (branches (magit-list-local-branch-names)))
753 (dolist (line (magit-git-lines "branch" "-vvr"
754 (cadr magit-refresh-args)))
755 (cond
756 ((string-match magit-refs-branch-line-re line)
757 (magit-bind-match-strings (branch hash message) line
758 (when (string-match-p (format "^%s/" remote) branch)
759 (magit-insert-branch
760 branch magit-refs-remote-branch-format current branches
761 'magit-branch-remote hash message))))
762 ((string-match magit-refs-symref-line-re line)
763 (magit-bind-match-strings (symref ref) line
764 (magit-insert-symref symref ref 'magit-branch-remote))))))
765 (insert ?\n)
766 (magit-make-margin-overlay nil t))))
767
768 (defun magit-insert-branch (branch format &rest args)
769 "For internal use, don't add to a hook."
770 (unless magit-refs-show-commit-count
771 (setq format (replace-regexp-in-string "%[0-9]\\([cC]\\)" "%1\\1" format t)))
772 (if (equal branch "HEAD")
773 (magit-insert-section it (commit (magit-rev-parse "HEAD") t)
774 (apply #'magit-insert-branch-1 it nil format args))
775 (magit-insert-section it (branch branch t)
776 (apply #'magit-insert-branch-1 it branch format args))))
777
778 (defun magit-insert-branch-1
779 (section branch format current branches face
780 &optional hash message upstream ahead behind gone)
781 "For internal use, don't add to a hook."
782 (let* ((head (or (car magit-refresh-args) current "HEAD"))
783 (count (and branch
784 (magit-refs-format-commit-count branch head format)))
785 (mark (cond ((or (equal branch head)
786 (and (not branch) (equal head "HEAD")))
787 (if (equal branch current)
788 (propertize "@" 'face 'magit-head)
789 (propertize "#" 'face 'magit-tag)))
790 ((equal branch current)
791 (propertize "." 'face 'magit-head)))))
792 (when upstream
793 (setq upstream (propertize upstream 'face
794 (if (member upstream branches)
795 'magit-branch-local
796 'magit-branch-remote))))
797 (magit-insert-heading
798 (format-spec
799 format
800 `((?a . ,(or ahead ""))
801 (?b . ,(or behind ""))
802 (?c . ,(or mark count ""))
803 (?C . ,(or mark " "))
804 (?h . ,(or (propertize hash 'face 'magit-hash) ""))
805 (?m . ,(or message ""))
806 (?n . ,(propertize (or branch "(detached)") 'face face))
807 (?u . ,(or upstream ""))
808 (?U . ,(if upstream
809 (format (propertize "[%s%s] " 'face 'magit-dimmed)
810 upstream
811 (cond
812 (gone
813 (concat ": " (propertize gone 'face 'error)))
814 ((or ahead behind)
815 (concat ": "
816 (and ahead (format "ahead %s" ahead))
817 (and ahead behind ", ")
818 (and behind (format "behind %s" behind))))
819 (t "")))
820 "")))))
821 (when (magit-buffer-margin-p)
822 (magit-refs-format-margin branch))
823 (magit-refs-insert-cherry-commits head branch section)))
824
825 (defun magit-insert-symref (symref ref face)
826 "For internal use, don't add to a hook."
827 (magit-insert-section (commit symref)
828 (insert
829 (format-spec (if magit-refs-show-commit-count
830 magit-refs-symref-format
831 (replace-regexp-in-string "%[0-9]\\([cC]\\)" "%1\\1"
832 magit-refs-symref-format t))
833 `((?c . "")
834 (?n . ,(propertize symref 'face face))
835 (?m . ,(propertize ref 'face face)))))))
836
837 (defvar magit-tag-section-map
838 (let ((map (make-sparse-keymap)))
839 (define-key map [remap magit-visit-thing] 'magit-visit-ref)
840 (define-key map [remap magit-delete-thing] 'magit-tag-delete)
841 map)
842 "Keymap for `tag' sections.")
843
844 (defun magit-insert-tags ()
845 "Insert sections showing all tags."
846 (-when-let (tags (magit-git-lines "tag" "-l" "-n"))
847 (magit-insert-section (tags)
848 (magit-insert-heading "Tags:")
849 (let ((head (or (car magit-refresh-args)
850 (magit-get-current-branch)
851 "HEAD"))
852 (format (if magit-refs-show-commit-count
853 magit-refs-tags-format
854 (replace-regexp-in-string
855 "%[0-9]\\([cC]\\)" "%1\\1" magit-refs-tags-format t))))
856 (dolist (tag (nreverse tags))
857 (string-match "^\\([^ \t]+\\)[ \t]+\\([^ \t\n].*\\)?" tag)
858 (let* ((message (match-string 2 tag))
859 (tag (match-string 1 tag))
860 (count (magit-refs-format-commit-count tag head format t))
861 (mark (and (equal tag head)
862 (propertize "#" 'face 'magit-tag))))
863 (magit-insert-section section (tag tag t)
864 (magit-insert-heading
865 (format-spec format
866 `((?n . ,(propertize tag 'face 'magit-tag))
867 (?c . ,(or mark count ""))
868 (?m . ,(or message "")))))
869 (when (and (magit-buffer-margin-p)
870 magit-refs-margin-for-tags)
871 (magit-refs-format-margin (concat tag "^{commit}")))
872 (magit-refs-insert-cherry-commits head tag section)))))
873 (insert ?\n))))
874
875 (defun magit-refs-insert-cherry-commits (head ref section)
876 (if (magit-section-hidden section)
877 (setf (magit-section-washer section)
878 (apply-partially #'magit-refs-insert-cherry-commits-1
879 head ref section))
880 (magit-refs-insert-cherry-commits-1 head ref section)))
881
882 (defun magit-refs-insert-cherry-commits-1 (head ref section)
883 (let ((start (point)))
884 (magit-git-wash (apply-partially 'magit-log-wash-log 'cherry)
885 "cherry" "-v" "--abbrev" head ref magit-refresh-args)
886 (unless (= (point) start)
887 (insert (propertize "\n" 'magit-section section))
888 (magit-make-margin-overlay nil t))))
889
890 (defun magit-refs-format-commit-count (ref head format &optional tag-p)
891 (and (string-match-p "%-?[0-9]+c" format)
892 (if tag-p
893 (eq magit-refs-show-commit-count 'all)
894 magit-refs-show-commit-count)
895 (let ((count (cadr (magit-rev-diff-count head ref))))
896 (and (> count 0)
897 (propertize (number-to-string count) 'face 'magit-dimmed)))))
898
899 (defun magit-refs-format-margin (commit)
900 (save-excursion
901 (goto-char (line-beginning-position 0))
902 (let ((line (magit-rev-format "%ct%cN" commit)))
903 (magit-log-format-margin (substring line 10)
904 (substring line 0 10)))))
905
906376 ;;;; Files
907377
908378 ;;;###autoload
31482618
31492619 (cl-eval-when (load eval)
31502620 (require 'magit-status)
2621 (require 'magit-refs)
31512622 (require 'magit-sequence)
31522623 (require 'magit-commit)
31532624 (require 'magit-remote)