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