dotemacs

My Emacs configuration
git clone git://git.entf.net/dotemacs
Log | Files | Refs | LICENSE

magit-status.el (36368B)


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