config

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

magit-status.el (32453B)


      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 " magit-jump-to-stashes)
    347     ("t " magit-jump-to-tracked)
    348     ("n " magit-jump-to-untracked)
    349     ("i " magit-jump-to-ignored)
    350     ("u " magit-jump-to-unstaged)
    351     ("s " magit-jump-to-staged)]
    352    [""
    353     ("fu" magit-jump-to-unpulled-from-upstream)
    354     ("fp" magit-jump-to-unpulled-from-pushremote)
    355     ("pu" magit-jump-to-unpushed-to-upstream)
    356     ("pp" magit-jump-to-unpushed-to-pushremote)
    357     ("a " magit-jump-to-assume-unchanged)
    358     ("w " magit-jump-to-skip-worktree)]
    359    ["Jump using"
    360     ("j"  "Imenu" imenu)]])
    361 
    362 (define-derived-mode magit-status-mode magit-mode "Magit"
    363   "Mode for looking at Git status.
    364 
    365 This mode is documented in info node `(magit)Status Buffer'.
    366 
    367 \\<magit-mode-map>\
    368 Type \\[magit-refresh] to refresh the current buffer.
    369 Type \\[magit-section-toggle] to expand or hide the section at point.
    370 Type \\[magit-visit-thing] to visit the change or commit at point.
    371 
    372 Type \\[magit-dispatch] to invoke major commands.
    373 
    374 Staging and applying changes is documented in info node
    375 `(magit)Staging and Unstaging' and info node `(magit)Applying'.
    376 
    377 \\<magit-hunk-section-map>Type \
    378 \\[magit-apply] to apply the change at point, \
    379 \\[magit-stage] to stage,
    380 \\[magit-unstage] to unstage, \
    381 \\[magit-discard] to discard, or \
    382 \\[magit-reverse] to reverse it.
    383 
    384 \\<magit-status-mode-map>\
    385 Type \\[magit-commit] to create a commit.
    386 
    387 \\{magit-status-mode-map}"
    388   :group 'magit-status
    389   (hack-dir-local-variables-non-file-buffer)
    390   (when magit-status-initial-section
    391     (add-hook 'magit-post-create-buffer-hook
    392               #'magit-status-goto-initial-section nil t))
    393   (setq magit--imenu-group-types '(not branch commit)))
    394 
    395 (put 'magit-status-mode 'magit-diff-default-arguments
    396      '("--no-ext-diff"))
    397 (put 'magit-status-mode 'magit-log-default-arguments
    398      '("-n256" "--decorate"))
    399 
    400 ;;;###autoload
    401 (defun magit-status-setup-buffer (&optional directory)
    402   (unless directory
    403     (setq directory default-directory))
    404   (when (file-remote-p directory)
    405     (magit-git-version-assert))
    406   (let* ((default-directory directory)
    407          (d (magit-diff--get-value 'magit-status-mode
    408                                    magit-status-use-buffer-arguments))
    409          (l (magit-log--get-value 'magit-status-mode
    410                                   magit-status-use-buffer-arguments))
    411          (file (and magit-status-goto-file-position
    412                     (magit-file-relative-name)))
    413          (line (and file (save-restriction (widen) (line-number-at-pos))))
    414          (col  (and file (save-restriction (widen) (current-column))))
    415          (buf  (magit-setup-buffer #'magit-status-mode nil
    416                  (magit-buffer-diff-args  (nth 0 d))
    417                  (magit-buffer-diff-files (nth 1 d))
    418                  (magit-buffer-log-args   (nth 0 l))
    419                  (magit-buffer-log-files  (nth 1 l)))))
    420     (when file
    421       (with-current-buffer buf
    422         (let ((staged (magit-get-section '((staged) (status)))))
    423           (if (and staged
    424                    (cadr (magit-diff--locate-hunk file line staged)))
    425               (magit-diff--goto-position file line col staged)
    426             (let ((unstaged (magit-get-section '((unstaged) (status)))))
    427               (unless (and unstaged
    428                            (magit-diff--goto-position file line col unstaged))
    429                 (when staged
    430                   (magit-diff--goto-position file line col staged))))))))
    431     buf))
    432 
    433 (defun magit-status-refresh-buffer ()
    434   (magit-git-exit-code "update-index" "--refresh")
    435   (magit-insert-section (status)
    436     (magit-run-section-hook 'magit-status-sections-hook)))
    437 
    438 (defun magit-status-goto-initial-section ()
    439   "Jump to the section specified by `magit-status-initial-section'."
    440   (when-let ((section
    441               (--some (if (integerp it)
    442                           (nth (1- it)
    443                                (magit-section-siblings (magit-current-section)
    444                                                        'next))
    445                         (magit-get-section it))
    446                       magit-status-initial-section)))
    447     (goto-char (oref section start))
    448     (when-let ((vis (cdr (assq 'magit-status-initial-section
    449                                magit-section-initial-visibility-alist))))
    450       (if (eq vis 'hide)
    451           (magit-section-hide section)
    452         (magit-section-show section)))))
    453 
    454 (defun magit-status-maybe-update-revision-buffer (&optional _)
    455   "When moving in the status buffer, update the revision buffer.
    456 If there is no revision buffer in the same frame, then do nothing."
    457   (when (derived-mode-p 'magit-status-mode)
    458     (magit--maybe-update-revision-buffer)))
    459 
    460 (defun magit-status-maybe-update-stash-buffer (&optional _)
    461   "When moving in the status buffer, update the stash buffer.
    462 If there is no stash buffer in the same frame, then do nothing."
    463   (when (derived-mode-p 'magit-status-mode)
    464     (magit--maybe-update-stash-buffer)))
    465 
    466 (defun magit-status-maybe-update-blob-buffer (&optional _)
    467   "When moving in the status buffer, update the blob buffer.
    468 If there is no blob buffer in the same frame, then do nothing."
    469   (when (derived-mode-p 'magit-status-mode)
    470     (magit--maybe-update-blob-buffer)))
    471 
    472 ;;; Sections
    473 ;;;; Special Headers
    474 
    475 (defun magit-insert-status-headers ()
    476   "Insert header sections appropriate for `magit-status-mode' buffers.
    477 The sections are inserted by running the functions on the hook
    478 `magit-status-headers-hook'."
    479   (if (magit-rev-verify "HEAD")
    480       (magit-insert-headers 'magit-status-headers-hook)
    481     (insert "In the beginning there was darkness\n\n")))
    482 
    483 (defvar-keymap magit-error-section-map
    484   :doc "Keymap for `error' sections."
    485   "<remap> <magit-visit-thing>" #'magit-process-buffer
    486   "<1>" (magit-menu-item "Visit process output" #'magit-process-buffer))
    487 
    488 (defun magit-insert-error-header ()
    489   "Insert the message about the Git error that just occurred.
    490 
    491 This function is only aware of the last error that occur when Git
    492 was run for side-effects.  If, for example, an error occurs while
    493 generating a diff, then that error won't be inserted.  Refreshing
    494 the status buffer causes this section to disappear again."
    495   (when magit-this-error
    496     (magit-insert-section (error 'git)
    497       (insert (propertize (format "%-10s" "GitError! ")
    498                           'font-lock-face 'magit-section-heading))
    499       (insert (propertize magit-this-error 'font-lock-face 'error))
    500       (when-let ((key (car (where-is-internal 'magit-process-buffer))))
    501         (insert (format "  [Type `%s' for details]" (key-description key))))
    502       (insert ?\n))
    503     (setq magit-this-error nil)))
    504 
    505 (defun magit-insert-diff-filter-header ()
    506   "Insert a header line showing the effective diff filters."
    507   (let ((ignore-modules (magit-ignore-submodules-p)))
    508     (when (or ignore-modules
    509               magit-buffer-diff-files)
    510       (insert (propertize (format "%-10s" "Filter! ")
    511                           'font-lock-face 'magit-section-heading))
    512       (when ignore-modules
    513         (insert ignore-modules)
    514         (when magit-buffer-diff-files
    515           (insert " -- ")))
    516       (when magit-buffer-diff-files
    517         (insert (string-join magit-buffer-diff-files " ")))
    518       (insert ?\n))))
    519 
    520 ;;;; Reference Headers
    521 
    522 (defun magit-insert-head-branch-header (&optional branch)
    523   "Insert a header line about the current branch.
    524 If `HEAD' is detached, then insert information about that commit
    525 instead.  The optional BRANCH argument is for internal use only."
    526   (let ((branch (or branch (magit-get-current-branch)))
    527         (output (magit-rev-format "%h %s" (or branch "HEAD"))))
    528     (string-match "^\\([^ ]+\\) \\(.*\\)" output)
    529     (magit-bind-match-strings (commit summary) output
    530       (when (equal summary "")
    531         (setq summary "(no commit message)"))
    532       (if branch
    533           (magit-insert-section (branch branch)
    534             (insert (format "%-10s" "Head: "))
    535             (when magit-status-show-hashes-in-headers
    536               (insert (propertize commit 'font-lock-face 'magit-hash) ?\s))
    537             (insert (propertize branch 'font-lock-face 'magit-branch-local))
    538             (insert ?\s)
    539             (insert (funcall magit-log-format-message-function branch summary))
    540             (insert ?\n))
    541         (magit-insert-section (commit commit)
    542           (insert (format "%-10s" "Head: "))
    543           (insert (propertize commit 'font-lock-face 'magit-hash))
    544           (insert ?\s)
    545           (insert (funcall magit-log-format-message-function nil summary))
    546           (insert ?\n))))))
    547 
    548 (defun magit-insert-upstream-branch-header (&optional branch upstream keyword)
    549   "Insert a header line about the upstream of the current branch.
    550 If no branch is checked out, then insert nothing.  The optional
    551 arguments are for internal use only."
    552   (when-let ((branch (or branch (magit-get-current-branch))))
    553     (let ((remote (magit-get "branch" branch "remote"))
    554           (merge  (magit-get "branch" branch "merge"))
    555           (rebase (magit-get "branch" branch "rebase")))
    556       (when (or remote merge)
    557         (unless upstream
    558           (setq upstream (magit-get-upstream-branch branch)))
    559         (magit-insert-section (branch upstream)
    560           (pcase rebase
    561             ("true")
    562             ("false" (setq rebase nil))
    563             (_       (setq rebase (magit-get-boolean "pull.rebase"))))
    564           (insert (format "%-10s" (or keyword (if rebase "Rebase: " "Merge: "))))
    565           (insert
    566            (if upstream
    567                (concat (and magit-status-show-hashes-in-headers
    568                             (concat (propertize (magit-rev-format "%h" upstream)
    569                                                 'font-lock-face 'magit-hash)
    570                                     " "))
    571                        upstream " "
    572                        (funcall magit-log-format-message-function upstream
    573                                 (funcall magit-log-format-message-function nil
    574                                          (or (magit-rev-format "%s" upstream)
    575                                              "(no commit message)"))))
    576              (cond
    577               ((magit--unnamed-upstream-p remote merge)
    578                (concat (propertize merge  'font-lock-face 'magit-branch-remote)
    579                        " from "
    580                        (propertize remote 'font-lock-face 'bold)))
    581               ((magit--valid-upstream-p remote merge)
    582                (if (equal remote ".")
    583                    (concat
    584                     (propertize merge 'font-lock-face 'magit-branch-local) " "
    585                     (propertize "does not exist"
    586                                 'font-lock-face 'magit-branch-warning))
    587                  (format
    588                   "%s %s %s"
    589                   (propertize merge 'font-lock-face 'magit-branch-remote)
    590                   (propertize "does not exist on"
    591                               'font-lock-face 'magit-branch-warning)
    592                   (propertize remote 'font-lock-face 'magit-branch-remote))))
    593               (t
    594                (propertize "invalid upstream configuration"
    595                            'font-lock-face 'magit-branch-warning)))))
    596           (insert ?\n))))))
    597 
    598 (defun magit-insert-push-branch-header ()
    599   "Insert a header line about the branch the current branch is pushed to."
    600   (when-let* ((branch (magit-get-current-branch))
    601               (target (magit-get-push-branch branch)))
    602     (magit-insert-section (branch target)
    603       (insert (format "%-10s" "Push: "))
    604       (insert
    605        (if (magit-rev-verify target)
    606            (concat (and magit-status-show-hashes-in-headers
    607                         (concat (propertize (magit-rev-format "%h" target)
    608                                             'font-lock-face 'magit-hash)
    609                                 " "))
    610                    target " "
    611                    (funcall magit-log-format-message-function target
    612                             (funcall magit-log-format-message-function nil
    613                                      (or (magit-rev-format "%s" target)
    614                                          "(no commit message)"))))
    615          (let ((remote (magit-get-push-remote branch)))
    616            (if (magit-remote-p remote)
    617                (concat target " "
    618                        (propertize "does not exist"
    619                                    'font-lock-face 'magit-branch-warning))
    620              (concat remote " "
    621                      (propertize "remote does not exist"
    622                                  'font-lock-face 'magit-branch-warning))))))
    623       (insert ?\n))))
    624 
    625 (defun magit-insert-tags-header ()
    626   "Insert a header line about the current and/or next tag."
    627   (let* ((this-tag (magit-get-current-tag nil t))
    628          (next-tag (magit-get-next-tag nil t))
    629          (this-cnt (cadr this-tag))
    630          (next-cnt (cadr next-tag))
    631          (this-tag (car this-tag))
    632          (next-tag (car next-tag))
    633          (both-tags (and this-tag next-tag t)))
    634     (when (or this-tag next-tag)
    635       (magit-insert-section (tag (or this-tag next-tag))
    636         (insert (format "%-10s" (if both-tags "Tags: " "Tag: ")))
    637         (cl-flet ((insert-count (tag count face)
    638                     (insert (concat (propertize tag 'font-lock-face 'magit-tag)
    639                                     (and (> count 0)
    640                                          (format " (%s)"
    641                                                  (propertize
    642                                                   (format "%s" count)
    643                                                   'font-lock-face face)))))))
    644           (when this-tag  (insert-count this-tag this-cnt 'magit-branch-local))
    645           (when both-tags (insert ", "))
    646           (when next-tag  (insert-count next-tag next-cnt 'magit-tag)))
    647         (insert ?\n)))))
    648 
    649 ;;;; Auxiliary Headers
    650 
    651 (defun magit-insert-user-header ()
    652   "Insert a header line about the current user."
    653   (let ((name  (magit-get "user.name"))
    654         (email (magit-get "user.email")))
    655     (when (and name email)
    656       (magit-insert-section (user name)
    657         (insert (format "%-10s" "User: "))
    658         (insert (propertize name 'font-lock-face 'magit-log-author))
    659         (insert " <" email ">\n")))))
    660 
    661 (defun magit-insert-repo-header ()
    662   "Insert a header line showing the path to the repository top-level."
    663   (let ((topdir (magit-toplevel)))
    664     (magit-insert-section (repo topdir)
    665       (insert (format "%-10s%s\n" "Repo: " (abbreviate-file-name topdir))))))
    666 
    667 (defun magit-insert-remote-header ()
    668   "Insert a header line about the remote of the current branch.
    669 
    670 If no remote is configured for the current branch, then fall back
    671 showing the \"origin\" remote, or if that does not exist the first
    672 remote in alphabetic order."
    673   (when-let* ((name (magit-get-some-remote))
    674               ;; Under certain configurations it's possible for
    675               ;; url to be nil, when name is not, see #2858.
    676               (url (magit-get "remote" name "url")))
    677     (magit-insert-section (remote name)
    678       (insert (format "%-10s" "Remote: "))
    679       (insert (propertize name 'font-lock-face 'magit-branch-remote) ?\s)
    680       (insert url ?\n))))
    681 
    682 ;;;; File Sections
    683 
    684 (defvar-keymap magit-untracked-section-map
    685   :doc "Keymap for the `untracked' section."
    686   "<remap> <magit-delete-thing>" #'magit-discard
    687   "<remap> <magit-stage-file>"   #'magit-stage
    688   "<2>" (magit-menu-item "Discard files" #'magit-discard)
    689   "<1>" (magit-menu-item "Stage files"   #'magit-stage))
    690 
    691 (magit-define-section-jumper magit-jump-to-untracked
    692   "Untracked files" untracked nil magit-insert-untracked-files)
    693 
    694 (magit-define-section-jumper magit-jump-to-tracked
    695   "Tracked files" tracked nil magit-insert-tracked-files)
    696 
    697 (magit-define-section-jumper magit-jump-to-ignored
    698   "Ignored files" ignored nil magit-insert-ignored-files)
    699 
    700 (magit-define-section-jumper magit-jump-to-skip-worktree
    701   "Skip-worktree files" skip-worktree nil magit-insert-skip-worktree-files)
    702 
    703 (magit-define-section-jumper magit-jump-to-assume-unchanged
    704   "Assume-unchanged files" assume-unchanged nil
    705   magit-insert-assume-unchanged-files)
    706 
    707 (defun magit-insert-untracked-files ()
    708   "Maybe insert a list or tree of untracked files.
    709 
    710 Do so depending on the value of `status.showUntrackedFiles'.
    711 Note that even if the value is `all', Magit still initially
    712 only shows directories.  But the directory sections can then
    713 be expanded using \"TAB\".
    714 
    715 If the first element of `magit-buffer-diff-files' is a
    716 directory, then limit the list to files below that.  The value
    717 value of that variable can be set using \"D -- DIRECTORY RET g\"."
    718   (let ((show (or (magit-get "status.showUntrackedFiles") "normal")))
    719     (unless (equal show "no")
    720       (let* ((all (equal show "all"))
    721              (base (car magit-buffer-diff-files))
    722              (base (and base (file-directory-p base) base)))
    723         (magit-insert-files 'untracked
    724                             (lambda () (magit-untracked-files nil base (not all)))
    725                             (not all))))))
    726 
    727 (defun magit-insert-tracked-files ()
    728   "Insert a tree of tracked files.
    729 
    730 If the first element of `magit-buffer-diff-files' is a
    731 directory, then limit the list to files below that.  The value
    732 value of that variable can be set using \"D -- DIRECTORY RET g\"."
    733   (magit-insert-files 'tracked #'magit-list-files))
    734 
    735 (defun magit-insert-ignored-files ()
    736   "Insert a tree of ignored files.
    737 
    738 If the first element of `magit-buffer-diff-files' is a
    739 directory, then limit the list to files below that.  The value
    740 of that variable can be set using \"D -- DIRECTORY RET g\"."
    741   (magit-insert-files 'ignored #'magit-ignored-files))
    742 
    743 (defun magit-insert-skip-worktree-files ()
    744   "Insert a tree of skip-worktree files.
    745 
    746 If the first element of `magit-buffer-diff-files' is a
    747 directory, then limit the list to files below that.  The value
    748 of that variable can be set using \"D -- DIRECTORY RET g\"."
    749   (magit-insert-files 'skip-worktree #'magit-skip-worktree-files))
    750 
    751 (defun magit-insert-assume-unchanged-files ()
    752   "Insert a tree of files that are assumed to be unchanged.
    753 
    754 If the first element of `magit-buffer-diff-files' is a
    755 directory, then limit the list to files below that.  The value
    756 of that variable can be set using \"D -- DIRECTORY RET g\"."
    757   (magit-insert-files 'assume-unchanged #'magit-assume-unchanged-files))
    758 
    759 (defun magit-insert-files (type fn &optional nogroup)
    760   (when-let ((files (funcall fn)))
    761     (let* ((base (car magit-buffer-diff-files))
    762            (base (and base (file-directory-p base) base))
    763            (title (symbol-name type)))
    764       (magit-insert-section ((eval type) nil t)
    765         (magit-insert-heading (length files)
    766           (format "%c%s files"
    767                   (capitalize (aref title 0))
    768                   (substring title 1)))
    769         (magit-insert-files-1 files base nogroup)
    770         (insert ?\n)))))
    771 
    772 (defun magit-insert-files-1 (files directory &optional nogroup)
    773   (while (and files (or nogroup
    774                         (not directory)
    775                         (string-prefix-p directory (car files))))
    776     (let ((dir (file-name-directory (car files))))
    777       (if (or nogroup (equal dir directory))
    778           (let ((file (pop files)))
    779             (magit-insert-section (file file)
    780               (insert (propertize file 'font-lock-face 'magit-filename) ?\n)))
    781         (magit-insert-section (file dir t)
    782           (insert (propertize dir 'file 'magit-filename) ?\n)
    783           (magit-insert-heading)
    784           (setq files (magit-insert-files-1 files dir))))))
    785   files)
    786 
    787 ;;; _
    788 (provide 'magit-status)
    789 ;;; magit-status.el ends here