fordon_greeman / voidfiles

Branch: 2079ec5
Switch branches/tags
main
theidioticdev add emacs 2079ec5 22h ago
887 lines · 35.5 KB Raw Blame History
1 ;;; magit-status.el --- The grand overview -*- 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 the status buffer.
26
27 ;;; Code:
28
29 (require 'magit)
30
31 (declare-function magit-dired-jump "magit-dired" (&optional other-window))
32
33 ;;; Options
34
35 (defgroup magit-status nil
36 "Inspect and manipulate Git repositories."
37 :link '(info-link "(magit)Status Buffer")
38 :group 'magit-modes)
39
40 (defcustom magit-status-mode-hook nil
41 "Hook run after entering Magit-Status mode."
42 :group 'magit-status
43 :type 'hook)
44
45 (defcustom magit-status-headers-hook
46 (list #'magit-insert-error-header
47 #'magit-insert-diff-filter-header
48 #'magit-insert-head-branch-header
49 #'magit-insert-upstream-branch-header
50 #'magit-insert-push-branch-header
51 #'magit-insert-tags-header)
52 "Hook run to insert headers into the status buffer.
53
54 This hook is run by `magit-insert-status-headers', which in turn
55 has to be a member of `magit-status-sections-hook' to be used at
56 all."
57 :package-version '(magit . "2.1.0")
58 :group 'magit-status
59 :type 'hook
60 :options (list #'magit-insert-error-header
61 #'magit-insert-diff-filter-header
62 #'magit-insert-repo-header
63 #'magit-insert-remote-header
64 #'magit-insert-head-branch-header
65 #'magit-insert-upstream-branch-header
66 #'magit-insert-push-branch-header
67 #'magit-insert-tags-header))
68
69 (defcustom magit-status-sections-hook
70 (list #'magit-insert-status-headers
71 #'magit-insert-merge-log
72 #'magit-insert-rebase-sequence
73 #'magit-insert-am-sequence
74 #'magit-insert-sequencer-sequence
75 #'magit-insert-bisect-output
76 #'magit-insert-bisect-rest
77 #'magit-insert-bisect-log
78 #'magit-insert-untracked-files
79 #'magit-insert-unstaged-changes
80 #'magit-insert-staged-changes
81 #'magit-insert-stashes
82 #'magit-insert-unpushed-to-pushremote
83 #'magit-insert-unpushed-to-upstream-or-recent
84 #'magit-insert-unpulled-from-pushremote
85 #'magit-insert-unpulled-from-upstream)
86 "Hook run to insert sections into a status buffer."
87 :package-version '(magit . "2.12.0")
88 :group 'magit-status
89 :type 'hook)
90
91 (defcustom magit-status-initial-section '(1)
92 "The section point is placed on when a status buffer is created.
93
94 When such a buffer is merely being refreshed or being shown again
95 after it was merely buried, then this option has no effect.
96
97 If this is nil, then point remains on the very first section as
98 usual. Otherwise it has to be a list of integers and section
99 identity lists. The members of that list are tried in order
100 until a matching section is found.
101
102 An integer means to jump to the nth section, 1 for example
103 jumps over the headings. To get a section's \"identity list\"
104 use \\[universal-argument] \\[magit-describe-section-briefly].
105
106 If, for example, you want to jump to the commits that haven't
107 been pulled from the upstream, or else the second section, then
108 use: (((unpulled . \"..@{upstream}\") (status)) 1).
109
110 See option `magit-section-initial-visibility-alist' for how to
111 control the initial visibility of the jumped to section."
112 :package-version '(magit . "2.90.0")
113 :group 'magit-status
114 :type '(choice (const :tag "As usual" nil)
115 (repeat (choice (number :tag "Nth top-level section")
116 (sexp :tag "Section identity")))))
117
118 (defcustom magit-status-goto-file-position nil
119 "Whether to go to position corresponding to file position.
120
121 If this is non-nil and the current buffer is visiting a file,
122 then `magit-status' tries to go to the position in the status
123 buffer that corresponds to the position in the file-visiting
124 buffer. This jumps into either the diff of unstaged changes
125 or the diff of staged changes.
126
127 If the previously current buffer does not visit a file, or if
128 the file has neither unstaged nor staged changes then this has
129 no effect.
130
131 The command `magit-status-here' tries to go to that position,
132 regardless of the value of this option."
133 :package-version '(magit . "3.0.0")
134 :group 'magit-status
135 :type 'boolean)
136
137 (defcustom magit-status-show-hashes-in-headers nil
138 "Whether headers in the status buffer show hashes.
139 The functions which respect this option are
140 `magit-insert-head-branch-header',
141 `magit-insert-upstream-branch-header', and
142 `magit-insert-push-branch-header'."
143 :package-version '(magit . "2.4.0")
144 :group 'magit-status
145 :type 'boolean)
146
147 (defcustom magit-status-show-untracked-files t
148 "Whether to list untracked files in the status buffer.
149
150 - If nil, do not list any untracked files.
151 - If t, list untracked files, but if a directory does not contain any
152 tracked files, then only list that directory, not the contained
153 untracked files.
154 - If all, then list each individual untracked files. This is can be
155 very slow and is discouraged.
156
157 The corresponding values for the Git variable are \"no\", \"normal\"
158 and \"all\".
159
160 To disable listing untracked files in a specific repository only, add
161 the following to \".dir-locals.el\":
162
163 ((magit-status-mode
164 (magit-status-show-untracked-files . \"no\")))
165
166 Alternatively (and mostly for historic reasons), it is possible to use
167 `git-config' to set the repository-local value:
168
169 git config set --local status.showUntrackedFiles no
170
171 This does *not* override the (if any) local value of this Lisp variable,
172 but it does override its global value.
173
174 See the last section in the git-status(1) manpage, to speed up the part
175 of the work Git is responsible for. Turning that list into sections is
176 also not free, so Magit only lists `magit-status-file-list-limit' files."
177 :package-version '(magit . "4.3.0")
178 :group 'magit-status
179 :type '(choice (const :tag "Do not list untracked files" nil)
180 (const :tag "List mixture of files and directories" t)
181 (const :tag "List individual files (slow)" all))
182 :safe (##memq % '(nil t all)))
183
184 (defcustom magit-status-file-list-limit 100
185 "How many files to list in file list sections in the status buffer.
186 For performance reasons, it is recommended that you do not
187 increase this limit."
188 :package-version '(magit . "4.3.0")
189 :group 'magit-status
190 :type 'natnum)
191
192 (defcustom magit-status-margin
193 (list nil
194 (nth 1 magit-log-margin)
195 'magit-log-margin-width nil
196 (nth 4 magit-log-margin))
197 "Format of the margin in `magit-status-mode' buffers.
198
199 The value has the form (INIT STYLE WIDTH AUTHOR AUTHOR-WIDTH).
200
201 If INIT is non-nil, then the margin is shown initially.
202 STYLE controls how to format the author or committer date.
203 It can be one of `age' (to show the age of the commit),
204 `age-abbreviated' (to abbreviate the time unit to a character),
205 or a string (suitable for `format-time-string') to show the
206 actual date. Option `magit-log-margin-show-committer-date'
207 controls which date is being displayed.
208 WIDTH controls the width of the margin. This exists for forward
209 compatibility and currently the value should not be changed.
210 AUTHOR controls whether the name of the author is also shown by
211 default.
212 AUTHOR-WIDTH has to be an integer. When the name of the author
213 is shown, then this specifies how much space is used to do so."
214 :package-version '(magit . "2.9.0")
215 :group 'magit-status
216 :group 'magit-margin
217 :type magit-log-margin--custom-type
218 :initialize #'magit-custom-initialize-reset
219 :set-after '(magit-log-margin)
220 :set (apply-partially #'magit-margin-set-variable 'magit-status-mode))
221
222 (defcustom magit-status-use-buffer-arguments 'selected
223 "Whether `magit-status' reuses arguments when the buffer already exists.
224
225 This option has no effect when merely refreshing the status
226 buffer using `magit-refresh'.
227
228 Valid values are:
229
230 `always': Always use the set of arguments that is currently
231 active in the status buffer, provided that buffer exists
232 of course.
233 `selected': Use the set of arguments from the status
234 buffer, but only if it is displayed in a window of the
235 current frame. This is the default.
236 `current': Use the set of arguments from the status buffer,
237 but only if it is the current buffer.
238 `never': Never use the set of arguments from the status
239 buffer."
240 :package-version '(magit . "3.0.0")
241 :group 'magit-buffers
242 :group 'magit-commands
243 :type '(choice
244 (const :tag "Always use args from buffer" always)
245 (const :tag "Use args from buffer if displayed in frame" selected)
246 (const :tag "Use args from buffer if it is current" current)
247 (const :tag "Never use args from buffer" never)))
248
249 ;;; Commands
250
251 ;;;###autoload
252 (defun magit-init (directory)
253 "Initialize a Git repository, then show its status.
254
255 If the directory is below an existing repository, then the user
256 has to confirm that a new one should be created inside. If the
257 directory is the root of the existing repository, then the user
258 has to confirm that it should be reinitialized.
259
260 Non-interactively DIRECTORY is (re-)initialized unconditionally."
261 (interactive
262 (let ((directory (file-name-as-directory
263 (expand-file-name
264 (read-directory-name "Create repository in: ")))))
265 (when-let ((toplevel (magit-toplevel directory)))
266 (setq toplevel (expand-file-name toplevel))
267 (unless (y-or-n-p (if (file-equal-p toplevel directory)
268 (format "Reinitialize existing repository %s? "
269 directory)
270 (format "%s is a repository. Create another in %s? "
271 toplevel directory)))
272 (user-error "Abort")))
273 (list directory)))
274 ;; "git init" does not understand the meaning of "~"!
275 (magit-call-git "init" (magit-convert-filename-for-git
276 (expand-file-name directory)))
277 (magit-status-setup-buffer directory))
278
279 ;;;###autoload
280 (defun magit-status (&optional directory cache)
281 "Show the status of the current Git repository in a buffer.
282
283 If the current directory isn't located within a Git repository,
284 then prompt for an existing repository or an arbitrary directory,
285 depending on option `magit-repository-directories', and show the
286 status of the selected repository instead.
287
288 * If that option specifies any existing repositories, then offer
289 those for completion and show the status buffer for the
290 selected one.
291
292 * Otherwise read an arbitrary directory using regular file-name
293 completion. If the selected directory is the top-level of an
294 existing working tree, then show the status buffer for that.
295
296 * Otherwise offer to initialize the selected directory as a new
297 repository. After creating the repository show its status
298 buffer.
299
300 These fallback behaviors can also be forced using one or more
301 prefix arguments:
302
303 * With two prefix arguments (or more precisely a numeric prefix
304 value of 16 or greater) read an arbitrary directory and act on
305 it as described above. The same could be accomplished using
306 the command `magit-init'.
307
308 * With a single prefix argument read an existing repository, or
309 if none can be found based on `magit-repository-directories',
310 then fall back to the same behavior as with two prefix
311 arguments."
312 (interactive
313 (let ((magit--refresh-cache (list (cons 0 0))))
314 (list (and (or current-prefix-arg (not (magit-toplevel)))
315 (progn (magit--assert-usable-git)
316 (magit-read-repository
317 (>= (prefix-numeric-value current-prefix-arg) 16))))
318 magit--refresh-cache)))
319 (let ((magit--refresh-cache (or cache (list (cons 0 0)))))
320 (if directory
321 (let ((toplevel (magit-toplevel directory)))
322 (setq directory (file-name-as-directory
323 (expand-file-name directory)))
324 (if (and toplevel (file-equal-p directory toplevel))
325 (magit-status-setup-buffer directory)
326 (when (y-or-n-p
327 (if toplevel
328 (format "%s is a repository. Create another in %s? "
329 toplevel directory)
330 (format "Create repository in %s? " directory)))
331 ;; Creating a new repository invalidates cached values.
332 (setq magit--refresh-cache nil)
333 (magit-init directory))))
334 (magit-status-setup-buffer default-directory))))
335
336 (put 'magit-status 'interactive-only 'magit-status-setup-buffer)
337
338 ;;;###autoload
339 (defalias 'magit #'magit-status
340 "Begin using Magit.
341
342 This alias for `magit-status' exists for better discoverability.
343
344 Instead of invoking this alias for `magit-status' using
345 \"M-x magit RET\", you should bind a key to `magit-status'
346 and read the info node `(magit)Getting Started', which
347 also contains other useful hints.")
348
349 ;;;###autoload
350 (defun magit-status-here ()
351 "Like `magit-status' but with non-nil `magit-status-goto-file-position'.
352 Before doing so, save all file-visiting buffers belonging to the current
353 repository without prompting."
354 (interactive)
355 (let ((magit-inhibit-refresh t))
356 (magit-save-repository-buffers t))
357 (let ((magit-status-goto-file-position t))
358 (call-interactively #'magit-status)))
359
360 (put 'magit-status-here 'interactive-only 'magit-status-setup-buffer)
361
362 ;;;###autoload
363 (defun magit-status-quick (&optional directory)
364 "Show the status of the current Git repository, maybe without refreshing.
365
366 If the status buffer of the current Git repository exists but
367 isn't being displayed in the selected frame, then display it
368 without refreshing it.
369
370 If the status buffer is being displayed in the selected frame,
371 then also refresh it.
372
373 Prefix arguments have the same meaning as for `magit-status',
374 and additionally cause the buffer to be refresh.
375
376 To use this function instead of `magit-status', add this to your
377 init file: (global-set-key (kbd \"C-x g\") \\='magit-status-quick).
378
379 Non-interactively another repository can be specified using the
380 optional DIRECTORY argument, which must then name the top-level
381 directory of an existing repository."
382 (interactive)
383 (let ((default-directory default-directory))
384 (when-let*
385 ((_ directory)
386 (toplevel (magit-toplevel directory))
387 (directory (file-name-as-directory (expand-file-name directory)))
388 (_(file-equal-p directory toplevel)))
389 (setq default-directory directory))
390 (if-let ((buffer
391 (and (not current-prefix-arg)
392 (not (magit-get-mode-buffer 'magit-status-mode nil 'selected))
393 (magit-get-mode-buffer 'magit-status-mode))))
394 (magit-display-buffer buffer)
395 (call-interactively #'magit-status))))
396
397 (defun magit-parent-status ()
398 "Show the status of the parent repository of the current Git repository.
399 If the current repository is a submodule, show the status buffer
400 of its super-repository. Otherwise step outside the worktree of
401 the current repository, and if that takes us to another repository,
402 consider that the parent repository."
403 (interactive)
404 (cond-let*
405 [[topdir (magit-toplevel)]]
406 ((not topdir)
407 (user-error "Not inside a Git repository"))
408 [[gitdir (magit-gitdir)]
409 [parent (magit-toplevel (file-name-parent-directory gitdir))]]
410 ((and parent (not (file-equal-p parent topdir)))
411 (magit-status-setup-buffer parent))
412 [[default-directory (file-name-parent-directory topdir)]]
413 ([parent (magit-toplevel)]
414 (magit-status-setup-buffer parent))
415 ((user-error "No parent repository"))))
416
417 ;;; Mode
418
419 (defvar-keymap magit-status-mode-map
420 :doc "Keymap for `magit-status-mode'."
421 :parent magit-mode-map
422 "j" #'magit-status-jump
423 "<remap> <dired-jump>" #'magit-dired-jump)
424
425 (transient-define-prefix magit-status-jump ()
426 "In a Magit-Status buffer, jump to a section."
427 [["Jump to"
428 ("z " magit-jump-to-stashes)
429 ("t " magit-jump-to-tracked)
430 ("n " magit-jump-to-untracked)
431 ("i " magit-jump-to-ignored)
432 ("u " magit-jump-to-unstaged)
433 ("s " magit-jump-to-staged)]
434 [""
435 ("fu" magit-jump-to-unpulled-from-upstream)
436 ("fp" magit-jump-to-unpulled-from-pushremote)
437 ("pu" magit-jump-to-unpushed-to-upstream)
438 ("pp" magit-jump-to-unpushed-to-pushremote)
439 ("a " magit-jump-to-assume-unchanged)
440 ("w " magit-jump-to-skip-worktree)]
441 ["Jump using"
442 ("j" "Imenu" imenu)]])
443
444 (define-derived-mode magit-status-mode magit-mode "Magit"
445 "Mode for looking at Git status.
446
447 This mode is documented in info node `(magit)Status Buffer'.
448
449 \\<magit-mode-map>\
450 Type \\[magit-refresh] to refresh the current buffer.
451 Type \\[magit-section-toggle] to expand or hide the section at point.
452 Type \\[magit-visit-thing] to visit the change or commit at point.
453
454 Type \\[magit-dispatch] to invoke major commands.
455
456 Staging and applying changes is documented in info node
457 `(magit)Staging and Unstaging' and info node `(magit)Applying'.
458
459 \\<magit-hunk-section-map>Type \
460 \\[magit-apply] to apply the change at point, \
461 \\[magit-stage] to stage,
462 \\[magit-unstage] to unstage, \
463 \\[magit-discard] to discard, or \
464 \\[magit-reverse] to reverse it.
465
466 \\<magit-status-mode-map>\
467 Type \\[magit-commit] to create a commit.
468
469 \\{magit-status-mode-map}"
470 :interactive nil
471 :group 'magit-status
472 (magit-hack-dir-local-variables)
473 (setq magit--imenu-group-types '(not branch commit)))
474
475 (put 'magit-status-mode 'magit-diff-default-arguments
476 '("--no-ext-diff" "--ignore-submodules=none"))
477 (put 'magit-status-mode 'magit-log-default-arguments
478 '("-n256" "--decorate"))
479
480 ;;;###autoload
481 (defun magit-status-setup-buffer (&optional directory)
482 (let ((default-directory (or directory default-directory)))
483 (when (file-remote-p default-directory)
484 (magit-git-version-assert))
485 (pcase-let
486 ((`(,dargs ,dfiles) (magit-diff--get-value 'magit-status-mode 'status))
487 (`(,largs ,lfiles) (magit-log--get-value 'magit-status-mode 'status)))
488 (magit-setup-buffer #'magit-status-mode nil
489 :initial-section #'magit-status-goto-initial-section
490 :select-section (and$ (magit-status--get-file-position)
491 (lambda () (apply #'magit-status--goto-file-position $)))
492 (magit-buffer-diff-args dargs)
493 (magit-buffer-diff-files dfiles)
494 (magit-buffer-log-args largs)
495 (magit-buffer-log-files lfiles)))))
496
497 (defun magit-status-refresh-buffer ()
498 (magit-git-exit-code "update-index" "--refresh")
499 (magit-insert-section (status)
500 (magit-run-section-hook 'magit-status-sections-hook)))
501
502 (defun magit-status--get-file-position ()
503 (and-let* ((_ magit-status-goto-file-position)
504 (file (magit-file-relative-name)))
505 (save-excursion
506 (widen)
507 (list file (line-number-at-pos) (current-column)))))
508
509 (defun magit-status--goto-file-position (file line column)
510 (pcase-let ((`(,upos ,uloc)
511 (magit-diff--locate-file-position file line column 'unstaged))
512 (`(,spos ,sloc)
513 (magit-diff--locate-file-position file line column 'staged)))
514 (cond ((eq uloc 'line) (goto-char upos))
515 ((eq sloc 'line) (goto-char spos))
516 ((eq uloc 'hunk) (goto-char upos))
517 ((eq sloc 'hunk) (goto-char spos))
518 (upos (goto-char upos))
519 (spos (goto-char spos)))
520 (and (or upos spos)
521 (progn (magit-section-reveal (magit-current-section))
522 t))))
523
524 (defun magit-status-goto-initial-section ()
525 "Jump to the section specified by `magit-status-initial-section'."
526 (when-let ((section
527 (seq-some (lambda (initial)
528 (if (integerp initial)
529 (nth (1- initial)
530 (magit-section-siblings
531 (magit-current-section) 'next))
532 (magit-get-section initial)))
533 magit-status-initial-section)))
534 (goto-char (oref section start))
535 (when-let ((vis (cdr (assq 'magit-status-initial-section
536 magit-section-initial-visibility-alist))))
537 (if (eq vis 'hide)
538 (magit-section-hide section)
539 (magit-section-show section)))))
540
541 (defun magit-status-maybe-update-revision-buffer (&optional _)
542 "When moving in the status buffer, update the revision buffer.
543 If there is no revision buffer in the same frame, then do nothing.
544 See also info node `(magit)Section Movement'."
545 (when (derived-mode-p 'magit-status-mode)
546 (magit--maybe-update-revision-buffer)))
547
548 (defun magit-status-maybe-update-stash-buffer (&optional _)
549 "When moving in the status buffer, update the stash buffer.
550 If there is no stash buffer in the same frame, then do nothing.
551 See also info node `(magit)Section Movement'."
552 (when (derived-mode-p 'magit-status-mode)
553 (magit--maybe-update-stash-buffer)))
554
555 (defun magit-status-maybe-update-blob-buffer (&optional _)
556 "When moving in the status buffer, update the blob buffer.
557 If there is no blob buffer in the same frame, then do nothing.
558 See also info node `(magit)Section Movement'."
559 (when (derived-mode-p 'magit-status-mode)
560 (magit--maybe-update-blob-buffer)))
561
562 ;;; Sections
563 ;;;; Special Headers
564
565 (defun magit-insert-status-headers ()
566 "Insert header sections appropriate for `magit-status-mode' buffers.
567 The sections are inserted by running the functions on the hook
568 `magit-status-headers-hook'."
569 (if (magit-rev-verify "HEAD")
570 (magit-insert-headers 'magit-status-headers-hook)
571 (insert "In the beginning there was darkness\n\n")))
572
573 (defvar-keymap magit-error-section-map
574 :doc "Keymap for `error' sections."
575 "<remap> <magit-visit-thing>" #'magit-process-buffer
576 "<1>" (magit-menu-item "Visit process output" #'magit-process-buffer))
577
578 (defun magit-insert-error-header ()
579 "Insert the message about the Git error that just occurred.
580
581 This function is only aware of the last error that occur when Git
582 was run for side-effects. If, for example, an error occurs while
583 generating a diff, then that error won't be inserted. Refreshing
584 the status buffer causes this section to disappear again."
585 (when magit-this-error
586 (magit-insert-section (error 'git)
587 (insert (propertize (format "%-10s" "GitError! ")
588 'font-lock-face 'magit-section-heading))
589 (insert (propertize magit-this-error 'font-lock-face 'error))
590 (when magit-show-process-buffer-hint
591 (insert (substitute-command-keys
592 " [Type \\[magit-process-buffer] for details]")))
593 (insert ?\n))
594 (setq magit-this-error nil)))
595
596 (defun magit-insert-diff-filter-header ()
597 "Insert a header line showing the effective diff filters.
598
599 If any diff filters are in effect in the status buffer, they are
600 shown on a line beginning with \"Filter! ...\" as a reminder, so you
601 won't end up wondering why certain changes, which you expect to be
602 displayed, are not actually displayed.
603
604 Filters either come from arguments, which can be changed using \
605 \\<magit-status-mode-map>\\[magit-diff-refresh],
606 or from Git variables, which are configured outside of Magit.
607
608 If you do not want this reminder to be shown, remove it like so:
609
610 (remove-hook \\='magit-status-headers-hook
611 \\='magit-insert-diff-filter-header)"
612 (let ((ignore-modules (magit-ignore-submodules-p)))
613 (when (or ignore-modules
614 magit-buffer-diff-files)
615 (magit-insert-section (diff-filter)
616 (insert (propertize (format "%-10s" "Filter! ")
617 'font-lock-face 'magit-section-heading))
618 (when ignore-modules
619 (insert ignore-modules)
620 (when magit-buffer-diff-files
621 (insert " -- ")))
622 (when magit-buffer-diff-files
623 (insert (string-join magit-buffer-diff-files " ")))
624 (insert (substitute-command-keys " \
625 [\\[magit-diff-refresh] to change, \\[magit-describe-section] for help]\n"))))))
626
627 ;;;; Reference Headers
628
629 (defun magit-insert-head-branch-header (&optional branch)
630 "Insert a header line about the current branch.
631 If `HEAD' is detached, then insert information about that commit
632 instead. The optional BRANCH argument is for internal use only."
633 (let ((output (magit-rev-format "%h %s" (or branch "HEAD"))))
634 (string-match "^\\([^ ]+\\) \\(.*\\)" output)
635 (magit-bind-match-strings (commit summary) output
636 (when (equal summary "")
637 (setq summary "(no commit message)"))
638 (if-let ((branch (or branch (magit-get-current-branch))))
639 (magit-insert-section (branch branch)
640 (insert (format "%-10s" "Head: "))
641 (when magit-status-show-hashes-in-headers
642 (insert (propertize commit 'font-lock-face 'magit-hash) ?\s))
643 (insert (propertize branch 'font-lock-face 'magit-branch-local))
644 (insert ?\s)
645 (insert (magit-log--wash-summary summary))
646 (insert ?\n))
647 (magit-insert-section (commit commit)
648 (insert (format "%-10s" "Head: "))
649 (insert (propertize commit 'font-lock-face 'magit-hash))
650 (insert ?\s)
651 (insert (magit-log--wash-summary summary))
652 (insert ?\n))))))
653
654 (defun magit-insert-upstream-branch-header (&optional branch upstream keyword)
655 "Insert a header line about the upstream of the current branch.
656 If no branch is checked out, then insert nothing. The optional
657 arguments are for internal use only."
658 (when-let ((branch (or branch (magit-get-current-branch))))
659 (let ((remote (magit-get "branch" branch "remote"))
660 (merge (magit-get "branch" branch "merge"))
661 (rebase (magit-get "branch" branch "rebase")))
662 (when (or remote merge)
663 (unless upstream
664 (setq upstream (magit-get-upstream-branch branch)))
665 (magit-insert-section (branch upstream)
666 (pcase rebase
667 ("true")
668 ("false" (setq rebase nil))
669 (_ (setq rebase (magit-get-boolean "pull.rebase"))))
670 (insert (format "%-10s" (or keyword (if rebase "Rebase: " "Merge: "))))
671 (insert
672 (cond
673 (upstream
674 (concat (and magit-status-show-hashes-in-headers
675 (concat (propertize (magit-rev-format "%h" upstream)
676 'font-lock-face 'magit-hash)
677 " "))
678 upstream " "
679 (magit-log--wash-summary
680 (or (magit-rev-format "%s" upstream)
681 "(no commit message)"))))
682 ((magit--unnamed-upstream-p remote merge)
683 (concat (propertize merge 'font-lock-face 'magit-branch-remote)
684 " from "
685 (propertize remote 'font-lock-face 'bold)))
686 ((magit--valid-upstream-p remote merge)
687 (if (equal remote ".")
688 (concat
689 (propertize merge 'font-lock-face 'magit-branch-local) " "
690 (propertize "does not exist"
691 'font-lock-face 'magit-branch-warning))
692 (format
693 "%s %s %s"
694 (propertize merge 'font-lock-face 'magit-branch-remote)
695 (propertize "does not exist on"
696 'font-lock-face 'magit-branch-warning)
697 (propertize remote 'font-lock-face 'magit-branch-remote))))
698 ((propertize "invalid upstream configuration"
699 'font-lock-face 'magit-branch-warning))))
700 (insert ?\n))))))
701
702 (defun magit-insert-push-branch-header ()
703 "Insert a header line about the branch the current branch is pushed to."
704 (when-let* ((branch (magit-get-current-branch))
705 (target (magit-get-push-branch branch)))
706 (magit-insert-section (branch target)
707 (insert (format "%-10s" "Push: "))
708 (insert
709 (cond-let
710 ((magit-rev-verify target)
711 (concat (and magit-status-show-hashes-in-headers
712 (concat (propertize (magit-rev-format "%h" target)
713 'font-lock-face 'magit-hash)
714 " "))
715 target " "
716 (magit-log--wash-summary
717 (or (magit-rev-format "%s" target)
718 "(no commit message)"))))
719 [[remote (magit-get-push-remote branch)]]
720 ((magit-remote-p remote)
721 (concat target " "
722 (propertize "does not exist"
723 'font-lock-face 'magit-branch-warning)))
724 ((concat remote " "
725 (propertize "remote does not exist"
726 'font-lock-face 'magit-branch-warning)))))
727 (insert ?\n))))
728
729 (defun magit-insert-tags-header ()
730 "Insert a header line about the current and/or next tag."
731 (let* ((this-tag (magit-get-current-tag nil t))
732 (next-tag (magit-get-next-tag nil t))
733 (this-cnt (cadr this-tag))
734 (next-cnt (cadr next-tag))
735 (this-tag (car this-tag))
736 (next-tag (car next-tag))
737 (both-tags (and this-tag next-tag t)))
738 (when (or this-tag next-tag)
739 (magit-insert-section (tag (or this-tag next-tag))
740 (insert (format "%-10s" (if both-tags "Tags: " "Tag: ")))
741 (cl-flet ((insert-count (tag count face)
742 (insert (concat (propertize tag 'font-lock-face 'magit-tag)
743 (and (> count 0)
744 (format " (%s)"
745 (propertize
746 (format "%s" count)
747 'font-lock-face face)))))))
748 (when this-tag (insert-count this-tag this-cnt 'magit-branch-local))
749 (when both-tags (insert ", "))
750 (when next-tag (insert-count next-tag next-cnt 'magit-tag)))
751 (insert ?\n)))))
752
753 ;;;; Auxiliary Headers
754
755 (defun magit-insert-user-header ()
756 "Insert a header line about the current user."
757 (let ((name (magit-get "user.name"))
758 (email (magit-get "user.email")))
759 (when (and name email)
760 (magit-insert-section (user name)
761 (insert (format "%-10s" "User: "))
762 (insert (propertize name 'font-lock-face 'magit-log-author))
763 (insert " <" email ">\n")))))
764
765 (defun magit-insert-repo-header ()
766 "Insert a header line showing the path to the repository top-level."
767 (let ((topdir (magit-toplevel)))
768 (magit-insert-section (repo topdir)
769 (insert (format "%-10s%s\n" "Repo: " (abbreviate-file-name topdir))))))
770
771 (defun magit-insert-remote-header ()
772 "Insert a header line about the remote of the current branch.
773
774 If no remote is configured for the current branch, then fall back
775 showing the \"origin\" remote, or if that does not exist the first
776 remote in alphabetic order."
777 (when-let* ((name (magit-get-some-remote))
778 ;; Under certain configurations it's possible for
779 ;; url to be nil, when name is not, see #2858.
780 (url (magit-get "remote" name "url")))
781 (magit-insert-section (remote name)
782 (insert (format "%-10s" "Remote: "))
783 (insert (propertize name 'font-lock-face 'magit-branch-remote) ?\s)
784 (insert url ?\n))))
785
786 ;;;; File Sections
787
788 (defvar-keymap magit-untracked-section-map
789 :doc "Keymap for the `untracked' section."
790 "<remap> <magit-delete-thing>" #'magit-discard
791 "<remap> <magit-stage-files>" #'magit-stage
792 "<2>" (magit-menu-item "Discard files" #'magit-discard)
793 "<1>" (magit-menu-item "Stage files" #'magit-stage))
794
795 (magit-define-section-jumper magit-jump-to-untracked
796 "Untracked files" untracked nil magit-insert-untracked-files)
797
798 (magit-define-section-jumper magit-jump-to-tracked
799 "Tracked files" tracked nil magit-insert-tracked-files)
800
801 (magit-define-section-jumper magit-jump-to-ignored
802 "Ignored files" ignored nil magit-insert-ignored-files)
803
804 (magit-define-section-jumper magit-jump-to-skip-worktree
805 "Skip-worktree files" skip-worktree nil magit-insert-skip-worktree-files)
806
807 (magit-define-section-jumper magit-jump-to-assume-unchanged
808 "Assume-unchanged files" assume-unchanged nil
809 magit-insert-assume-unchanged-files)
810
811 (defun magit-insert-untracked-files ()
812 "Maybe insert a list of untracked files.
813
814 List files if `magit-status-show-untracked-files' is non-nil, but also
815 take the local value of Git variable `status.showUntrackedFiles' into
816 account. The local value of the Lisp variable takes precedence over the
817 local value of the Git variable. The global value of the Git variable
818 is always ignored."
819 (magit-insert-files 'untracked #'magit-list-untracked-files))
820
821 (defun magit-insert-tracked-files ()
822 "Insert a list of tracked files.
823 Honor the buffer's file filter, which can be set using \"D - -\"."
824 (magit-insert-files 'tracked #'magit-list-files))
825
826 (defun magit-insert-ignored-files ()
827 "Insert a list of ignored files.
828 Honor the buffer's file filter, which can be set using \"D - -\"."
829 (magit-insert-files 'ignored (##magit-ignored-files "--directory" %)))
830
831 (defun magit-insert-skip-worktree-files ()
832 "Insert a list of skip-worktree files.
833 Honor the buffer's file filter, which can be set using \"D - -\"."
834 (magit-insert-files 'skip-worktree #'magit-skip-worktree-files))
835
836 (defun magit-insert-assume-unchanged-files ()
837 "Insert a list of files that are assumed to be unchanged.
838 Honor the buffer's file filter, which can be set using \"D - -\"."
839 (magit-insert-files 'assume-unchanged #'magit-assume-unchanged-files))
840
841 (defun magit-insert-files (type fn)
842 (when-let ((files (funcall fn
843 (and magit-buffer-diff-files
844 (cons "--" magit-buffer-diff-files)))))
845 (magit-insert-section section ((eval type) nil t)
846 (magit-insert-heading (length files)
847 (let ((title (symbol-name type)))
848 (format "%c%s files"
849 (capitalize (aref title 0))
850 (substring title 1))))
851 (magit-insert-section-body
852 (let ((magit-section-insert-in-reverse t)
853 (limit magit-status-file-list-limit))
854 (while (and files (> limit 0))
855 (decf limit)
856 (let ((file (pop files)))
857 (magit-insert-section (file file)
858 (insert (funcall magit-format-file-function
859 'list file 'magit-filename))
860 (insert ?\n))))
861 (when files
862 (magit-insert-section (info)
863 (insert (propertize
864 (format "%s files not listed\n" (length files))
865 'face 'warning)))))
866 (insert ?\n)
867 (oset section children (nreverse (oref section children)))))))
868
869 ;;; _
870 (provide 'magit-status)
871 ;; Local Variables:
872 ;; read-symbol-shorthands: (
873 ;; ("and$" . "cond-let--and$")
874 ;; ("thread$" . "cond-let--thread$")
875 ;; ("when$" . "cond-let--when$")
876 ;; ("and-let*" . "cond-let--and-let*")
877 ;; ("and-let" . "cond-let--and-let")
878 ;; ("if-let*" . "cond-let--if-let*")
879 ;; ("if-let" . "cond-let--if-let")
880 ;; ("when-let*" . "cond-let--when-let*")
881 ;; ("when-let" . "cond-let--when-let")
882 ;; ("while-let*" . "cond-let--while-let*")
883 ;; ("while-let" . "cond-let--while-let")
884 ;; ("match-string" . "match-string")
885 ;; ("match-str" . "match-string-no-properties"))
886 ;; End:
887 ;;; magit-status.el ends here