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