Klimi's new dotfiles with stow.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

783 line
32 KiB

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