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