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