config

Personal configuration.
git clone git://code.dwrz.net/config
Log | Files | Refs

magit-status.el (34889B)


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