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