commit | author | age
|
5cb5f7
|
1 |
;;; magit-log.el --- inspect Git history -*- 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 support for looking at Git logs, including |
|
27 |
;; special logs like reflogs and cherry-logs, as well as for selecting |
|
28 |
;; a commit from a log. |
|
29 |
|
|
30 |
;;; Code: |
|
31 |
|
|
32 |
(require 'magit-core) |
|
33 |
(require 'magit-diff) |
|
34 |
|
|
35 |
(declare-function magit-blob-visit "magit-files" (blob-or-file line)) |
|
36 |
(declare-function magit-insert-head-branch-header "magit-status" |
|
37 |
(&optional branch)) |
|
38 |
(declare-function magit-insert-upstream-branch-header "magit-status" |
|
39 |
(&optional branch pull keyword)) |
|
40 |
(declare-function magit-read-file-from-rev "magit-files" |
|
41 |
(rev prompt &optional default)) |
|
42 |
(declare-function magit-show-commit "magit-diff" |
|
43 |
(arg1 &optional arg2 arg3 arg4)) |
|
44 |
(defvar magit-refs-focus-column-width) |
|
45 |
(defvar magit-refs-margin) |
|
46 |
(defvar magit-refs-show-commit-count) |
|
47 |
(defvar magit-buffer-margin) |
|
48 |
(defvar magit-status-margin) |
|
49 |
(defvar magit-status-sections-hook) |
|
50 |
|
|
51 |
(require 'ansi-color) |
|
52 |
(require 'crm) |
|
53 |
(require 'which-func) |
|
54 |
|
|
55 |
(eval-when-compile |
|
56 |
(require 'subr-x)) |
|
57 |
|
|
58 |
(defvar bookmark-make-record-function) |
|
59 |
|
|
60 |
;;; Options |
|
61 |
;;;; Log Mode |
|
62 |
|
|
63 |
(defgroup magit-log nil |
|
64 |
"Inspect and manipulate Git history." |
|
65 |
:link '(info-link "(magit)Logging") |
|
66 |
:group 'magit-modes) |
|
67 |
|
|
68 |
(defcustom magit-log-mode-hook nil |
|
69 |
"Hook run after entering Magit-Log mode." |
|
70 |
:group 'magit-log |
|
71 |
:type 'hook) |
|
72 |
|
|
73 |
(defcustom magit-log-arguments '("-n256" "--graph" "--decorate") |
|
74 |
"The log arguments used in `magit-log-mode' buffers." |
|
75 |
:package-version '(magit . "2.3.0") |
|
76 |
:group 'magit-git-arguments |
|
77 |
:group 'magit-log |
|
78 |
:type '(repeat (string :tag "Argument"))) |
|
79 |
|
|
80 |
(defcustom magit-log-remove-graph-args '("--follow" "--grep" "-G" "-S" "-L") |
|
81 |
"The log arguments that cause the `--graph' argument to be dropped." |
|
82 |
:package-version '(magit . "2.3.0") |
|
83 |
:group 'magit-log |
|
84 |
:type '(repeat (string :tag "Argument")) |
|
85 |
:options '("--follow" "--grep" "-G" "-S" "-L")) |
|
86 |
|
|
87 |
(defcustom magit-log-revision-headers-format "\ |
|
88 |
%+b |
|
89 |
Author: %aN <%aE> |
|
90 |
Committer: %cN <%cE>" |
|
91 |
"Additional format string used with the `++header' argument." |
|
92 |
:package-version '(magit . "2.3.0") |
|
93 |
:group 'magit-log |
|
94 |
:type 'string) |
|
95 |
|
|
96 |
(defcustom magit-log-auto-more nil |
|
97 |
"Insert more log entries automatically when moving past the last entry. |
|
98 |
Only considered when moving past the last entry with |
|
99 |
`magit-goto-*-section' commands." |
|
100 |
:group 'magit-log |
|
101 |
:type 'boolean) |
|
102 |
|
|
103 |
(defcustom magit-log-margin '(t age magit-log-margin-width t 18) |
|
104 |
"Format of the margin in `magit-log-mode' buffers. |
|
105 |
|
|
106 |
The value has the form (INIT STYLE WIDTH AUTHOR AUTHOR-WIDTH). |
|
107 |
|
|
108 |
If INIT is non-nil, then the margin is shown initially. |
|
109 |
STYLE controls how to format the committer date. It can be one |
|
110 |
of `age' (to show the age of the commit), `age-abbreviated' (to |
|
111 |
abbreviate the time unit to a character), or a string (suitable |
|
112 |
for `format-time-string') to show the actual date. |
|
113 |
WIDTH controls the width of the margin. This exists for forward |
|
114 |
compatibility and currently the value should not be changed. |
|
115 |
AUTHOR controls whether the name of the author is also shown by |
|
116 |
default. |
|
117 |
AUTHOR-WIDTH has to be an integer. When the name of the author |
|
118 |
is shown, then this specifies how much space is used to do so." |
|
119 |
:package-version '(magit . "2.9.0") |
|
120 |
:group 'magit-log |
|
121 |
:group 'magit-margin |
|
122 |
:type magit-log-margin--custom-type |
|
123 |
:initialize 'magit-custom-initialize-reset |
|
124 |
:set (apply-partially #'magit-margin-set-variable 'magit-log-mode)) |
|
125 |
|
|
126 |
(defcustom magit-log-show-refname-after-summary nil |
|
127 |
"Whether to show refnames after commit summaries. |
|
128 |
This is useful if you use really long branch names." |
|
129 |
:package-version '(magit . "2.2.0") |
|
130 |
:group 'magit-log |
|
131 |
:type 'boolean) |
|
132 |
|
|
133 |
(defcustom magit-log-highlight-keywords t |
|
134 |
"Whether to highlight bracketed keywords in commit summaries." |
|
135 |
:package-version '(magit . "2.12.0") |
|
136 |
:group 'magit-log |
|
137 |
:type 'boolean) |
|
138 |
|
|
139 |
(defcustom magit-log-header-line-function 'magit-log-header-line-sentence |
|
140 |
"Function used to generate text shown in header line of log buffers." |
|
141 |
:package-version '(magit . "2.12.0") |
|
142 |
:group 'magit-log |
|
143 |
:type '(choice (function-item magit-log-header-line-arguments) |
|
144 |
(function-item magit-log-header-line-sentence) |
|
145 |
function)) |
|
146 |
|
|
147 |
(defcustom magit-log-trace-definition-function 'which-function |
|
148 |
"Function used to determine the function at point. |
|
149 |
This is used by the command `magit-log-trace-definition'." |
|
150 |
:package-version '(magit . "2.90.0") |
|
151 |
:group 'magit-log |
|
152 |
:type '(choice (function-item which-function) function)) |
|
153 |
|
|
154 |
(defface magit-log-graph |
|
155 |
'((((class color) (background light)) :foreground "grey30") |
|
156 |
(((class color) (background dark)) :foreground "grey80")) |
|
157 |
"Face for the graph part of the log output." |
|
158 |
:group 'magit-faces) |
|
159 |
|
|
160 |
(defface magit-log-author |
|
161 |
'((((class color) (background light)) :foreground "firebrick") |
|
162 |
(((class color) (background dark)) :foreground "tomato")) |
|
163 |
"Face for the author part of the log output." |
|
164 |
:group 'magit-faces) |
|
165 |
|
|
166 |
(defface magit-log-date |
|
167 |
'((((class color) (background light)) :foreground "grey30") |
|
168 |
(((class color) (background dark)) :foreground "grey80")) |
|
169 |
"Face for the date part of the log output." |
|
170 |
:group 'magit-faces) |
|
171 |
|
|
172 |
(defface magit-header-line-log-select |
|
173 |
'((t :inherit bold)) |
|
174 |
"Face for the `header-line' in `magit-log-select-mode'." |
|
175 |
:group 'magit-faces) |
|
176 |
|
|
177 |
;;;; File Log |
|
178 |
|
|
179 |
(defcustom magit-log-buffer-file-locked t |
|
180 |
"Whether `magit-log-buffer-file' uses a dedicated buffer." |
|
181 |
:package-version '(magit . "2.7.0") |
|
182 |
:group 'magit-commands |
|
183 |
:group 'magit-log |
|
184 |
:type 'boolean) |
|
185 |
|
|
186 |
;;;; Select Mode |
|
187 |
|
|
188 |
(defcustom magit-log-select-arguments '("-n256" "--graph" "--decorate") |
|
189 |
"The log arguments used in `magit-log-select-mode' buffers." |
|
190 |
:package-version '(magit . "2.3.0") |
|
191 |
:group 'magit-log |
|
192 |
:type '(repeat (string :tag "Argument"))) |
|
193 |
|
|
194 |
(defcustom magit-log-select-show-usage 'both |
|
195 |
"Whether to show usage information when selecting a commit from a log. |
|
196 |
The message can be shown in the `echo-area' or the `header-line', or in |
|
197 |
`both' places. If the value isn't one of these symbols, then it should |
|
198 |
be nil, in which case no usage information is shown." |
|
199 |
:package-version '(magit . "2.1.0") |
|
200 |
:group 'magit-log |
|
201 |
:type '(choice (const :tag "in echo-area" echo-area) |
|
202 |
(const :tag "in header-line" header-line) |
|
203 |
(const :tag "in both places" both) |
|
204 |
(const :tag "nowhere"))) |
|
205 |
|
|
206 |
(defcustom magit-log-select-margin |
|
207 |
(list (nth 0 magit-log-margin) |
|
208 |
(nth 1 magit-log-margin) |
|
209 |
'magit-log-margin-width t |
|
210 |
(nth 4 magit-log-margin)) |
|
211 |
"Format of the margin in `magit-log-select-mode' buffers. |
|
212 |
|
|
213 |
The value has the form (INIT STYLE WIDTH AUTHOR AUTHOR-WIDTH). |
|
214 |
|
|
215 |
If INIT is non-nil, then the margin is shown initially. |
|
216 |
STYLE controls how to format the committer date. It can be one |
|
217 |
of `age' (to show the age of the commit), `age-abbreviated' (to |
|
218 |
abbreviate the time unit to a character), or a string (suitable |
|
219 |
for `format-time-string') to show the actual date. |
|
220 |
WIDTH controls the width of the margin. This exists for forward |
|
221 |
compatibility and currently the value should not be changed. |
|
222 |
AUTHOR controls whether the name of the author is also shown by |
|
223 |
default. |
|
224 |
AUTHOR-WIDTH has to be an integer. When the name of the author |
|
225 |
is shown, then this specifies how much space is used to do so." |
|
226 |
:package-version '(magit . "2.9.0") |
|
227 |
:group 'magit-log |
|
228 |
:group 'magit-margin |
|
229 |
:type magit-log-margin--custom-type |
|
230 |
:initialize 'magit-custom-initialize-reset |
|
231 |
:set-after '(magit-log-margin) |
|
232 |
:set (apply-partially #'magit-margin-set-variable 'magit-log-select-mode)) |
|
233 |
|
|
234 |
;;;; Cherry Mode |
|
235 |
|
|
236 |
(defcustom magit-cherry-sections-hook |
|
237 |
'(magit-insert-cherry-headers |
|
238 |
magit-insert-cherry-commits) |
|
239 |
"Hook run to insert sections into the cherry buffer." |
|
240 |
:package-version '(magit . "2.1.0") |
|
241 |
:group 'magit-log |
|
242 |
:type 'hook) |
|
243 |
|
|
244 |
(defcustom magit-cherry-margin |
|
245 |
(list (nth 0 magit-log-margin) |
|
246 |
(nth 1 magit-log-margin) |
|
247 |
'magit-log-margin-width t |
|
248 |
(nth 4 magit-log-margin)) |
|
249 |
"Format of the margin in `magit-cherry-mode' buffers. |
|
250 |
|
|
251 |
The value has the form (INIT STYLE WIDTH AUTHOR AUTHOR-WIDTH). |
|
252 |
|
|
253 |
If INIT is non-nil, then the margin is shown initially. |
|
254 |
STYLE controls how to format the committer date. It can be one |
|
255 |
of `age' (to show the age of the commit), `age-abbreviated' (to |
|
256 |
abbreviate the time unit to a character), or a string (suitable |
|
257 |
for `format-time-string') to show the actual date. |
|
258 |
WIDTH controls the width of the margin. This exists for forward |
|
259 |
compatibility and currently the value should not be changed. |
|
260 |
AUTHOR controls whether the name of the author is also shown by |
|
261 |
default. |
|
262 |
AUTHOR-WIDTH has to be an integer. When the name of the author |
|
263 |
is shown, then this specifies how much space is used to do so." |
|
264 |
:package-version '(magit . "2.9.0") |
|
265 |
:group 'magit-log |
|
266 |
:group 'magit-margin |
|
267 |
:type magit-log-margin--custom-type |
|
268 |
:initialize 'magit-custom-initialize-reset |
|
269 |
:set-after '(magit-log-margin) |
|
270 |
:set (apply-partially #'magit-margin-set-variable 'magit-cherry-mode)) |
|
271 |
|
|
272 |
;;;; Reflog Mode |
|
273 |
|
|
274 |
(defcustom magit-reflog-arguments '("-n256") |
|
275 |
"The log arguments used in `magit-reflog-mode' buffers." |
|
276 |
:package-version '(magit . "2.3.0") |
|
277 |
:group 'magit-git-arguments |
|
278 |
:type '(repeat (string :tag "Argument"))) |
|
279 |
|
|
280 |
(defcustom magit-reflog-margin |
|
281 |
(list (nth 0 magit-log-margin) |
|
282 |
(nth 1 magit-log-margin) |
|
283 |
'magit-log-margin-width nil |
|
284 |
(nth 4 magit-log-margin)) |
|
285 |
"Format of the margin in `magit-reflog-mode' buffers. |
|
286 |
|
|
287 |
The value has the form (INIT STYLE WIDTH AUTHOR AUTHOR-WIDTH). |
|
288 |
|
|
289 |
If INIT is non-nil, then the margin is shown initially. |
|
290 |
STYLE controls how to format the committer date. It can be one |
|
291 |
of `age' (to show the age of the commit), `age-abbreviated' (to |
|
292 |
abbreviate the time unit to a character), or a string (suitable |
|
293 |
for `format-time-string') to show the actual date. |
|
294 |
WIDTH controls the width of the margin. This exists for forward |
|
295 |
compatibility and currently the value should not be changed. |
|
296 |
AUTHOR controls whether the name of the author is also shown by |
|
297 |
default. |
|
298 |
AUTHOR-WIDTH has to be an integer. When the name of the author |
|
299 |
is shown, then this specifies how much space is used to do so." |
|
300 |
:package-version '(magit . "2.9.0") |
|
301 |
:group 'magit-log |
|
302 |
:group 'magit-margin |
|
303 |
:type magit-log-margin--custom-type |
|
304 |
:initialize 'magit-custom-initialize-reset |
|
305 |
:set-after '(magit-log-margin) |
|
306 |
:set (apply-partially #'magit-margin-set-variable 'magit-reflog-mode)) |
|
307 |
|
|
308 |
(defface magit-reflog-commit '((t :foreground "green")) |
|
309 |
"Face for commit commands in reflogs." |
|
310 |
:group 'magit-faces) |
|
311 |
|
|
312 |
(defface magit-reflog-amend '((t :foreground "magenta")) |
|
313 |
"Face for amend commands in reflogs." |
|
314 |
:group 'magit-faces) |
|
315 |
|
|
316 |
(defface magit-reflog-merge '((t :foreground "green")) |
|
317 |
"Face for merge, checkout and branch commands in reflogs." |
|
318 |
:group 'magit-faces) |
|
319 |
|
|
320 |
(defface magit-reflog-checkout '((t :foreground "blue")) |
|
321 |
"Face for checkout commands in reflogs." |
|
322 |
:group 'magit-faces) |
|
323 |
|
|
324 |
(defface magit-reflog-reset '((t :foreground "red")) |
|
325 |
"Face for reset commands in reflogs." |
|
326 |
:group 'magit-faces) |
|
327 |
|
|
328 |
(defface magit-reflog-rebase '((t :foreground "magenta")) |
|
329 |
"Face for rebase commands in reflogs." |
|
330 |
:group 'magit-faces) |
|
331 |
|
|
332 |
(defface magit-reflog-cherry-pick '((t :foreground "green")) |
|
333 |
"Face for cherry-pick commands in reflogs." |
|
334 |
:group 'magit-faces) |
|
335 |
|
|
336 |
(defface magit-reflog-remote '((t :foreground "cyan")) |
|
337 |
"Face for pull and clone commands in reflogs." |
|
338 |
:group 'magit-faces) |
|
339 |
|
|
340 |
(defface magit-reflog-other '((t :foreground "cyan")) |
|
341 |
"Face for other commands in reflogs." |
|
342 |
:group 'magit-faces) |
|
343 |
|
|
344 |
;;;; Log Sections |
|
345 |
|
|
346 |
(defcustom magit-log-section-commit-count 10 |
|
347 |
"How many recent commits to show in certain log sections. |
|
348 |
How many recent commits `magit-insert-recent-commits' and |
|
349 |
`magit-insert-unpulled-from-upstream-or-recent' (provided |
|
350 |
the upstream isn't ahead of the current branch) show." |
|
351 |
:package-version '(magit . "2.1.0") |
|
352 |
:group 'magit-status |
|
353 |
:type 'number) |
|
354 |
|
|
355 |
(defcustom magit-log-section-arguments '("-n256" "--decorate") |
|
356 |
"The log arguments used in buffers that show other things besides logs." |
|
357 |
:package-version '(magit . "2.4.0") |
|
358 |
:group 'magit-git-arguments |
|
359 |
:group 'magit-log |
|
360 |
:group 'magit-status |
|
361 |
:type '(repeat (string :tag "Argument"))) |
|
362 |
|
|
363 |
;;; Commands |
|
364 |
;;;; Popups |
|
365 |
|
|
366 |
(defvar magit-log-popup |
|
367 |
'(:variable magit-log-arguments |
|
368 |
:man-page "git-log" |
|
369 |
:switches ((?g "Show graph" "--graph") |
|
370 |
(?c "Show graph in color" "--color") |
|
371 |
(?d "Show refnames" "--decorate") |
|
372 |
(?S "Show signatures" "--show-signature") |
|
373 |
(?u "Show diffs" "--patch") |
|
374 |
(?s "Show diffstats" "--stat") |
|
375 |
(?h "Show header" "++header" magit-log++header) |
|
376 |
(?r "Show in reverse order" "--reverse") |
|
377 |
(?D "Simplify by decoration" "--simplify-by-decoration") |
|
378 |
(?f "Follow renames when showing single-file log" "--follow")) |
|
379 |
:options ((?n "Limit number of commits" "-n") |
|
380 |
(?f "Limit to files" "-- " magit-read-files) |
|
381 |
(?a "Limit to author" "--author=") |
|
382 |
(?o "Order commits by" "++order=" magit-log-select-order) |
|
383 |
(?g "Search messages" "--grep=") |
|
384 |
(?G "Search changes" "-G") |
|
385 |
(?S "Search occurrences" "-S") |
|
386 |
(?L "Trace line evolution" "-L" magit-read-file-trace)) |
|
387 |
:actions ((?l "Log current" magit-log-current) |
|
388 |
(?L "Log local branches" magit-log-branches) |
|
389 |
(?r "Reflog current" magit-reflog-current) |
|
390 |
(?o "Log other" magit-log-other) |
|
391 |
(?b "Log all branches" magit-log-all-branches) |
|
392 |
(?O "Reflog other" magit-reflog-other) |
|
393 |
(?h "Log HEAD" magit-log-head) |
|
394 |
(?a "Log all references" magit-log-all) |
|
395 |
(?H "Reflog HEAD" magit-reflog-head)) |
|
396 |
:default-action magit-log-current |
|
397 |
:max-action-columns 3)) |
|
398 |
|
|
399 |
(defvar magit-log-mode-refresh-popup |
|
400 |
'(:variable magit-log-arguments |
|
401 |
:man-page "git-log" |
|
402 |
:switches ((?g "Show graph" "--graph") |
|
403 |
(?c "Show graph in color" "--color") |
|
404 |
(?d "Show refnames" "--decorate") |
|
405 |
(?S "Show signatures" "--show-signature") |
|
406 |
(?u "Show diffs" "--patch") |
|
407 |
(?s "Show diffstats" "--stat") |
|
408 |
(?r "Show in reverse order" "--reverse") |
|
409 |
(?D "Simplify by decoration" "--simplify-by-decoration") |
|
410 |
(?f "Follow renames when showing single-file log" "--follow")) |
|
411 |
:options ((?n "Limit number of commits" "-n") |
|
412 |
(?f "Limit to files" "-- " magit-read-files) |
|
413 |
(?a "Limit to author" "--author=") |
|
414 |
(?o "Order commits by" "++order=" magit-log-select-order) |
|
415 |
(?g "Search messages" "--grep=") |
|
416 |
(?G "Search changes" "-G") |
|
417 |
(?S "Search occurrences" "-S") |
|
418 |
(?L "Trace line evolution" "-L" magit-read-file-trace)) |
|
419 |
:actions ((?g "Refresh" magit-log-refresh) |
|
420 |
(?L "Toggle margin" magit-toggle-margin) |
|
421 |
(?s "Set defaults" magit-log-set-default-arguments) nil |
|
422 |
(?w "Save defaults" magit-log-save-default-arguments)) |
|
423 |
:max-action-columns 2)) |
|
424 |
|
|
425 |
(defvar magit-reflog-mode-refresh-popup |
|
426 |
'(:variable magit-reflog-arguments |
|
427 |
:man-page "git-reflog" |
|
428 |
:options ((?n "Limit number of commits" "-n")))) |
|
429 |
|
|
430 |
(defvar magit-log-refresh-popup |
|
431 |
'(:variable magit-log-arguments |
|
432 |
:man-page "git-log" |
|
433 |
:switches ((?g "Show graph" "--graph") |
|
434 |
(?c "Show graph in color" "--color") |
|
435 |
(?d "Show refnames" "--decorate")) |
|
436 |
:options ((?n "Limit number of commits" "-n") |
|
437 |
(?o "Order commits by" "++order=" magit-log-select-order)) |
|
438 |
:actions ("Refresh" |
|
439 |
(?g "buffer" magit-log-refresh) |
|
440 |
(?s "buffer and set defaults" magit-log-set-default-arguments) |
|
441 |
(?w "buffer and save defaults" magit-log-save-default-arguments) |
|
442 |
"Margin" |
|
443 |
(?L "toggle visibility" magit-toggle-margin) |
|
444 |
(?l "cycle style" magit-cycle-margin-style) |
|
445 |
(?d "toggle details" magit-toggle-margin-details)) |
|
446 |
:max-action-columns 1)) |
|
447 |
|
|
448 |
(magit-define-popup-keys-deferred 'magit-log-popup) |
|
449 |
(magit-define-popup-keys-deferred 'magit-log-mode-refresh-popup) |
|
450 |
(magit-define-popup-keys-deferred 'magit-log-refresh-popup) |
|
451 |
|
|
452 |
(defun magit-log-select-order (&rest _ignored) |
|
453 |
"Set one `--<value>-order' option in Git log. |
|
454 |
This encompasses the options `--author-date-order', |
|
455 |
`--date-order', and `--topo-order'." |
|
456 |
(magit-read-char-case "Order commits by " t |
|
457 |
(?t "[t]opography" "topo") |
|
458 |
(?a "[a]uthor date" "author-date") |
|
459 |
(?c "[c]ommitter date" "date"))) |
|
460 |
|
|
461 |
;; This is a dummy procedure used to show help in `magit-log-popup'. |
|
462 |
(defun magit-log++header () |
|
463 |
"Insert a header after each revision summary in Git log. |
|
464 |
Customize `magit-log-revision-headers-format' to change this |
|
465 |
header." |
|
466 |
nil) |
|
467 |
|
|
468 |
(defun magit-log-get-buffer-args () |
|
469 |
(cond ((and magit-use-sticky-arguments |
|
470 |
(derived-mode-p 'magit-log-mode)) |
|
471 |
(list (nth 1 magit-refresh-args) |
|
472 |
(nth 2 magit-refresh-args))) |
|
473 |
((and (eq magit-use-sticky-arguments t) |
|
474 |
(--when-let (magit-mode-get-buffer 'magit-log-mode) |
|
475 |
(with-current-buffer it |
|
476 |
(list (nth 1 magit-refresh-args) |
|
477 |
(nth 2 magit-refresh-args)))))) |
|
478 |
(t |
|
479 |
(list (default-value 'magit-log-arguments) nil)))) |
|
480 |
|
|
481 |
(defun magit-log-arguments (&optional refresh) |
|
482 |
(cond ((memq magit-current-popup |
|
483 |
'(magit-log-popup magit-log-refresh-popup)) |
|
484 |
(magit-popup-export-file-args magit-current-popup-args)) |
|
485 |
((and refresh (not (derived-mode-p 'magit-log-mode))) |
|
486 |
(list magit-log-section-arguments nil)) |
|
487 |
(t |
|
488 |
(magit-log-get-buffer-args)))) |
|
489 |
|
|
490 |
(defun magit-log-popup (arg) |
|
491 |
"Popup console for log commands." |
|
492 |
(interactive "P") |
|
493 |
(let ((magit-log-refresh-popup |
|
494 |
(pcase major-mode |
|
495 |
(`magit-log-mode magit-log-mode-refresh-popup) |
|
496 |
(_ magit-log-refresh-popup))) |
|
497 |
(magit-log-arguments |
|
498 |
(apply #'magit-popup-import-file-args (magit-log-get-buffer-args)))) |
|
499 |
(magit-invoke-popup 'magit-log-popup nil arg))) |
|
500 |
|
|
501 |
;;;###autoload |
|
502 |
(defun magit-log-buffer-file-popup () |
|
503 |
"Popup console for log commands. |
|
504 |
|
|
505 |
This is a variant of `magit-log-popup' which shows the same popup |
|
506 |
but which limits the log to the file being visited in the current |
|
507 |
buffer." |
|
508 |
(interactive) |
|
509 |
(if-let ((file (magit-file-relative-name))) |
|
510 |
(let ((magit-log-arguments |
|
511 |
(magit-popup-import-file-args |
|
512 |
(if-let ((buffer (magit-mode-get-buffer 'magit-log-mode))) |
|
513 |
(with-current-buffer buffer |
|
514 |
(nth 2 magit-refresh-args)) |
|
515 |
(default-value 'magit-log-arguments)) |
|
516 |
(list file)))) |
|
517 |
(magit-invoke-popup 'magit-log-popup nil nil)) |
|
518 |
(user-error "Buffer isn't visiting a file"))) |
|
519 |
|
|
520 |
(defun magit-log-refresh-popup (arg) |
|
521 |
"Popup console for changing log arguments in the current buffer." |
|
522 |
(interactive "P") |
|
523 |
(magit-log-refresh-assert) |
|
524 |
(let ((magit-log-refresh-popup |
|
525 |
(cond ((derived-mode-p 'magit-log-select-mode) |
|
526 |
magit-log-refresh-popup) |
|
527 |
((derived-mode-p 'magit-log-mode) |
|
528 |
(let ((def (copy-sequence magit-log-refresh-popup))) |
|
529 |
(plist-put def :switches (plist-get magit-log-popup :switches)) |
|
530 |
(plist-put def :options (plist-get magit-log-popup :options)) |
|
531 |
def)) |
|
532 |
(t |
|
533 |
magit-log-refresh-popup))) |
|
534 |
(magit-log-arguments |
|
535 |
(cond ((derived-mode-p 'magit-log-select-mode) |
|
536 |
(cadr magit-refresh-args)) |
|
537 |
((derived-mode-p 'magit-log-mode) |
|
538 |
(magit-popup-import-file-args (nth 1 magit-refresh-args) |
|
539 |
(nth 2 magit-refresh-args))) |
|
540 |
(t |
|
541 |
magit-log-section-arguments)))) |
|
542 |
(magit-invoke-popup 'magit-log-refresh-popup nil arg))) |
|
543 |
|
|
544 |
(defun magit-read-file-trace (&rest _ignored) |
|
545 |
(let ((file (magit-read-file-from-rev "HEAD" "File")) |
|
546 |
(trace (magit-read-string "Trace"))) |
|
547 |
(concat trace (or (match-string 2 trace) ":") file))) |
|
548 |
|
|
549 |
;;;; Refresh Commands |
|
550 |
|
|
551 |
(defun magit-log-refresh (args files) |
|
552 |
"Set the local log arguments for the current buffer." |
|
553 |
(interactive (magit-log-arguments t)) |
|
554 |
(magit-log-refresh-assert) |
|
555 |
(cond ((derived-mode-p 'magit-log-select-mode) |
|
556 |
(setcar (cdr magit-refresh-args) args)) |
|
557 |
((derived-mode-p 'magit-log-mode) |
|
558 |
(setcdr magit-refresh-args (list args files))) |
|
559 |
(t |
|
560 |
(setq-local magit-log-section-arguments args))) |
|
561 |
(magit-refresh)) |
|
562 |
|
|
563 |
(defun magit-log-set-default-arguments (args files) |
|
564 |
"Set the global log arguments for the current buffer." |
|
565 |
(interactive (magit-log-arguments t)) |
|
566 |
(magit-log-refresh-assert) |
|
567 |
(cond ((derived-mode-p 'magit-log-select-mode) |
|
568 |
(customize-set-variable 'magit-log-select-arguments args) |
|
569 |
(setcar (cdr magit-refresh-args) args)) |
|
570 |
((derived-mode-p 'magit-log-mode) |
|
571 |
(customize-set-variable 'magit-log-arguments args) |
|
572 |
(setcdr magit-refresh-args (list args files))) |
|
573 |
(t |
|
574 |
(customize-set-variable 'magit-log-section-arguments args) |
|
575 |
(kill-local-variable 'magit-log-section-arguments))) |
|
576 |
(magit-refresh)) |
|
577 |
|
|
578 |
(defun magit-log-save-default-arguments (args files) |
|
579 |
"Set and save the global log arguments for the current buffer." |
|
580 |
(interactive (magit-log-arguments t)) |
|
581 |
(magit-log-refresh-assert) |
|
582 |
(cond ((derived-mode-p 'magit-log-select-mode) |
|
583 |
(customize-save-variable 'magit-log-select-arguments args) |
|
584 |
(setcar (cdr magit-refresh-args) args)) |
|
585 |
((derived-mode-p 'magit-log-mode) |
|
586 |
(customize-save-variable 'magit-log-arguments args) |
|
587 |
(setcdr magit-refresh-args (list args files))) |
|
588 |
(t |
|
589 |
(customize-save-variable 'magit-log-section-arguments args) |
|
590 |
(kill-local-variable 'magit-log-section-arguments))) |
|
591 |
(magit-refresh)) |
|
592 |
|
|
593 |
(defun magit-log-refresh-assert () |
|
594 |
(cond ((derived-mode-p 'magit-reflog-mode) |
|
595 |
(user-error "Cannot change log arguments in reflog buffers")) |
|
596 |
((derived-mode-p 'magit-cherry-mode) |
|
597 |
(user-error "Cannot change log arguments in cherry buffers")))) |
|
598 |
|
|
599 |
;;;; Log Commands |
|
600 |
|
|
601 |
(defvar magit-log-read-revs-map |
|
602 |
(let ((map (make-sparse-keymap))) |
|
603 |
(set-keymap-parent map crm-local-completion-map) |
|
604 |
(define-key map "\s" 'self-insert-command) |
|
605 |
map)) |
|
606 |
|
|
607 |
(defun magit-log-read-revs (&optional use-current) |
|
608 |
(or (and use-current (--when-let (magit-get-current-branch) (list it))) |
|
609 |
(let ((collection `(,@(and (file-exists-p (magit-git-dir "FETCH_HEAD")) |
|
610 |
(list "FETCH_HEAD")) |
|
611 |
,@(magit-list-refnames)))) |
|
612 |
(split-string |
|
613 |
(magit-completing-read-multiple "Log rev,s" collection |
|
614 |
"\\(\\.\\.\\.?\\|[, ]\\)" |
|
615 |
(or (magit-branch-or-commit-at-point) |
|
616 |
(unless use-current |
|
617 |
(magit-get-previous-branch))) |
|
618 |
'magit-revision-history |
|
619 |
magit-log-read-revs-map) |
|
620 |
"[, ]" t)))) |
|
621 |
|
|
622 |
(defun magit-git-log (revs args files) |
|
623 |
(require 'magit) |
|
624 |
(magit-mode-setup #'magit-log-mode revs args files) |
|
625 |
(magit-log-goto-same-commit)) |
|
626 |
|
|
627 |
;;;###autoload |
|
628 |
(defun magit-log-current (revs &optional args files) |
|
629 |
"Show log for the current branch. |
|
630 |
When `HEAD' is detached or with a prefix argument show log for |
|
631 |
one or more revs read from the minibuffer." |
|
632 |
(interactive (cons (magit-log-read-revs t) |
|
633 |
(magit-log-arguments))) |
|
634 |
(magit-git-log revs args files)) |
|
635 |
|
|
636 |
;;;###autoload |
|
637 |
(defun magit-log-other (revs &optional args files) |
|
638 |
"Show log for one or more revs read from the minibuffer. |
|
639 |
The user can input any revision or revisions separated by a |
|
640 |
space, or even ranges, but only branches and tags, and a |
|
641 |
representation of the commit at point, are available as |
|
642 |
completion candidates." |
|
643 |
(interactive (cons (magit-log-read-revs) |
|
644 |
(magit-log-arguments))) |
|
645 |
(magit-git-log revs args files)) |
|
646 |
|
|
647 |
;;;###autoload |
|
648 |
(defun magit-log-head (&optional args files) |
|
649 |
"Show log for `HEAD'." |
|
650 |
(interactive (magit-log-arguments)) |
|
651 |
(magit-git-log (list "HEAD") args files)) |
|
652 |
|
|
653 |
;;;###autoload |
|
654 |
(defun magit-log-branches (&optional args files) |
|
655 |
"Show log for all local branches and `HEAD'." |
|
656 |
(interactive (magit-log-arguments)) |
|
657 |
(magit-git-log (if (magit-get-current-branch) |
|
658 |
(list "--branches") |
|
659 |
(list "HEAD" "--branches")) |
|
660 |
args files)) |
|
661 |
|
|
662 |
;;;###autoload |
|
663 |
(defun magit-log-all-branches (&optional args files) |
|
664 |
"Show log for all local and remote branches and `HEAD'." |
|
665 |
(interactive (magit-log-arguments)) |
|
666 |
(magit-git-log (if (magit-get-current-branch) |
|
667 |
(list "--branches" "--remotes") |
|
668 |
(list "HEAD" "--branches" "--remotes")) |
|
669 |
args files)) |
|
670 |
|
|
671 |
;;;###autoload |
|
672 |
(defun magit-log-all (&optional args files) |
|
673 |
"Show log for all references and `HEAD'." |
|
674 |
(interactive (magit-log-arguments)) |
|
675 |
(magit-git-log (if (magit-get-current-branch) |
|
676 |
(list "--all") |
|
677 |
(list "HEAD" "--all")) |
|
678 |
args files)) |
|
679 |
|
|
680 |
;;;###autoload |
|
681 |
(defun magit-log-buffer-file (&optional follow beg end) |
|
682 |
"Show log for the blob or file visited in the current buffer. |
|
683 |
With a prefix argument or when `--follow' is part of |
|
684 |
`magit-log-arguments', then follow renames. When the region is |
|
685 |
active, restrict the log to the lines that the region touches." |
|
686 |
(interactive |
|
687 |
(cons current-prefix-arg |
|
688 |
(and (region-active-p) |
|
689 |
(magit-file-relative-name) |
|
690 |
(save-restriction |
|
691 |
(widen) |
|
692 |
(list (line-number-at-pos (region-beginning)) |
|
693 |
(line-number-at-pos |
|
694 |
(let ((end (region-end))) |
|
695 |
(if (char-after end) |
|
696 |
end |
|
697 |
;; Ensure that we don't get the line number |
|
698 |
;; of a trailing newline. |
|
699 |
(1- end))))))))) |
|
700 |
(require 'magit) |
|
701 |
(if-let ((file (magit-file-relative-name))) |
|
702 |
(magit-mode-setup-internal |
|
703 |
#'magit-log-mode |
|
704 |
(list (list (or magit-buffer-refname |
|
705 |
(magit-get-current-branch) |
|
706 |
"HEAD")) |
|
707 |
(let ((args (car (magit-log-arguments)))) |
|
708 |
(when (and follow (not (member "--follow" args))) |
|
709 |
(push "--follow" args)) |
|
710 |
(when (and (file-regular-p |
|
711 |
(expand-file-name file (magit-toplevel))) |
|
712 |
beg end) |
|
713 |
(setq args (cons (format "-L%s,%s:%s" beg end file) |
|
714 |
(cl-delete "-L" args :test |
|
715 |
'string-prefix-p))) |
|
716 |
(setq file nil)) |
|
717 |
args) |
|
718 |
(and file (list file))) |
|
719 |
magit-log-buffer-file-locked) |
|
720 |
(user-error "Buffer isn't visiting a file")) |
|
721 |
(magit-log-goto-same-commit)) |
|
722 |
|
|
723 |
;;;###autoload |
|
724 |
(defun magit-log-trace-definition (file fn rev) |
|
725 |
"Show log for the definition at point." |
|
726 |
(interactive (list (or (magit-file-relative-name) |
|
727 |
(user-error "Buffer isn't visiting a file")) |
|
728 |
(funcall magit-log-trace-definition-function) |
|
729 |
(or magit-buffer-refname |
|
730 |
(magit-get-current-branch) |
|
731 |
"HEAD"))) |
|
732 |
(require 'magit) |
|
733 |
(magit-mode-setup-internal |
|
734 |
#'magit-log-mode |
|
735 |
(list (list rev) |
|
736 |
(cons (format "-L:%s:%s" (regexp-quote fn) file) |
|
737 |
(cl-delete "-L" (car (magit-log-arguments)) |
|
738 |
:test 'string-prefix-p)) |
|
739 |
nil) |
|
740 |
magit-log-buffer-file-locked) |
|
741 |
(magit-log-goto-same-commit)) |
|
742 |
|
|
743 |
(defun magit-diff-trace-definition () |
|
744 |
"Show log for the definition at point in a diff." |
|
745 |
(interactive) |
|
746 |
(let (buf pos) |
|
747 |
(save-window-excursion |
|
748 |
(call-interactively #'magit-diff-visit-file) |
|
749 |
(setq buf (current-buffer)) |
|
750 |
(setq pos (point))) |
|
751 |
(save-excursion |
|
752 |
(with-current-buffer buf |
|
753 |
(goto-char pos) |
|
754 |
(call-interactively #'magit-log-trace-definition))))) |
|
755 |
|
|
756 |
;;;###autoload |
|
757 |
(defun magit-log-merged (commit branch &optional args files) |
|
758 |
"Show log for the merge of COMMIT into BRANCH. |
|
759 |
More precisely, find merge commit M that brought COMMIT into |
|
760 |
BRANCH, and show the log of the range \"M^..M\". This command |
|
761 |
requires git-when-merged, which is available from |
|
762 |
https://github.com/mhagger/git-when-merged." |
|
763 |
(interactive |
|
764 |
(append (let ((commit (magit-read-branch-or-commit "Commit"))) |
|
765 |
(list commit |
|
766 |
(magit-read-other-branch "Merged into" commit))) |
|
767 |
(magit-log-arguments))) |
|
768 |
(unless (executable-find "git-when-merged") |
|
769 |
(user-error "This command requires git-when-merged (%s)" |
|
770 |
"https://github.com/mhagger/git-when-merged")) |
|
771 |
(magit-git-log |
|
772 |
(list (or (magit-git-string "when-merged" "--show-branch" commit branch) |
|
773 |
(user-error "Could not find when %s was merged into %s" |
|
774 |
commit branch))) |
|
775 |
args files)) |
|
776 |
|
|
777 |
(defun magit-git-reflog (ref args) |
|
778 |
(require 'magit) |
|
779 |
(magit-mode-setup #'magit-reflog-mode ref args)) |
|
780 |
|
|
781 |
;;;###autoload |
|
782 |
(defun magit-reflog-current (args) |
|
783 |
"Display the reflog of the current branch." |
|
784 |
(interactive (list magit-reflog-arguments)) |
|
785 |
(magit-git-reflog (magit-get-current-branch) args)) |
|
786 |
|
|
787 |
;;;###autoload |
|
788 |
(defun magit-reflog-other (ref args) |
|
789 |
"Display the reflog of a branch or another ref." |
|
790 |
(interactive (list (magit-read-local-branch-or-ref "Show reflog for") |
|
791 |
magit-reflog-arguments)) |
|
792 |
(magit-git-reflog ref args)) |
|
793 |
|
|
794 |
;;;###autoload |
|
795 |
(defun magit-reflog-head (args) |
|
796 |
"Display the `HEAD' reflog." |
|
797 |
(interactive (list magit-reflog-arguments)) |
|
798 |
(magit-git-reflog "HEAD" args)) |
|
799 |
|
|
800 |
;;;; Limit Commands |
|
801 |
|
|
802 |
(defun magit-log-toggle-commit-limit () |
|
803 |
"Toggle the number of commits the current log buffer is limited to. |
|
804 |
If the number of commits is currently limited, then remove that |
|
805 |
limit. Otherwise set it to 256." |
|
806 |
(interactive) |
|
807 |
(magit-log-set-commit-limit (lambda (&rest _) nil))) |
|
808 |
|
|
809 |
(defun magit-log-double-commit-limit () |
|
810 |
"Double the number of commits the current log buffer is limited to." |
|
811 |
(interactive) |
|
812 |
(magit-log-set-commit-limit '*)) |
|
813 |
|
|
814 |
(defun magit-log-half-commit-limit () |
|
815 |
"Half the number of commits the current log buffer is limited to." |
|
816 |
(interactive) |
|
817 |
(magit-log-set-commit-limit '/)) |
|
818 |
|
|
819 |
(defun magit-log-set-commit-limit (fn) |
|
820 |
(let* ((val (car (magit-log-arguments t))) |
|
821 |
(arg (--first (string-match "^-n\\([0-9]+\\)?$" it) val)) |
|
822 |
(num (and arg (string-to-number (match-string 1 arg)))) |
|
823 |
(num (if num (funcall fn num 2) 256))) |
|
824 |
(setq val (delete arg val)) |
|
825 |
(setcar (cdr magit-refresh-args) |
|
826 |
(if (and num (> num 0)) |
|
827 |
(cons (format "-n%i" num) val) |
|
828 |
val))) |
|
829 |
(magit-refresh)) |
|
830 |
|
|
831 |
(defun magit-log-get-commit-limit () |
|
832 |
(--when-let (--first (string-match "^-n\\([0-9]+\\)?$" it) |
|
833 |
(car (magit-log-arguments t))) |
|
834 |
(string-to-number (match-string 1 it)))) |
|
835 |
|
|
836 |
;;;; Other Commands |
|
837 |
|
|
838 |
(defun magit-log-bury-buffer (&optional arg) |
|
839 |
"Bury the current buffer or the revision buffer in the same frame. |
|
840 |
Like `magit-mode-bury-buffer' (which see) but with a negative |
|
841 |
prefix argument instead bury the revision buffer, provided it |
|
842 |
is displayed in the current frame." |
|
843 |
(interactive "p") |
|
844 |
(if (< arg 0) |
|
845 |
(let* ((buf (magit-mode-get-buffer 'magit-revision-mode)) |
|
846 |
(win (and buf (get-buffer-window buf (selected-frame))))) |
|
847 |
(if win |
|
848 |
(with-selected-window win |
|
849 |
(with-current-buffer buf |
|
850 |
(magit-mode-bury-buffer (> (abs arg) 1)))) |
|
851 |
(user-error "No revision buffer in this frame"))) |
|
852 |
(magit-mode-bury-buffer (> arg 1)))) |
|
853 |
|
|
854 |
;;;###autoload |
|
855 |
(defun magit-log-move-to-parent (&optional n) |
|
856 |
"Move to the Nth parent of the current commit." |
|
857 |
(interactive "p") |
|
858 |
(when (derived-mode-p 'magit-log-mode) |
|
859 |
(when (magit-section-match 'commit) |
|
860 |
(let* ((section (magit-current-section)) |
|
861 |
(parent-rev (format "%s^%s" (oref section value) (or n 1)))) |
|
862 |
(if-let ((parent-hash (magit-rev-parse "--short" parent-rev))) |
|
863 |
(if-let ((parent (--first (equal (oref section value) |
|
864 |
parent-hash) |
|
865 |
(magit-section-siblings section 'next)))) |
|
866 |
(magit-section-goto parent) |
|
867 |
(user-error |
|
868 |
(substitute-command-keys |
|
869 |
(concat "Parent " parent-hash " not found. Try typing " |
|
870 |
"\\[magit-log-double-commit-limit] first")))) |
|
871 |
(user-error "Parent %s does not exist" parent-rev)))))) |
|
872 |
|
|
873 |
;;; Log Mode |
|
874 |
|
|
875 |
(defvar magit-log-mode-map |
|
876 |
(let ((map (make-sparse-keymap))) |
|
877 |
(set-keymap-parent map magit-mode-map) |
|
878 |
(define-key map "\C-c\C-b" 'magit-go-backward) |
|
879 |
(define-key map "\C-c\C-f" 'magit-go-forward) |
|
880 |
(define-key map "\C-c\C-n" 'magit-log-move-to-parent) |
|
881 |
(define-key map "=" 'magit-log-toggle-commit-limit) |
|
882 |
(define-key map "+" 'magit-log-double-commit-limit) |
|
883 |
(define-key map "-" 'magit-log-half-commit-limit) |
|
884 |
(define-key map "q" 'magit-log-bury-buffer) |
|
885 |
map) |
|
886 |
"Keymap for `magit-log-mode'.") |
|
887 |
|
|
888 |
(define-derived-mode magit-log-mode magit-mode "Magit Log" |
|
889 |
"Mode for looking at Git log. |
|
890 |
|
|
891 |
This mode is documented in info node `(magit)Log Buffer'. |
|
892 |
|
|
893 |
\\<magit-mode-map>\ |
|
894 |
Type \\[magit-refresh] to refresh the current buffer. |
|
895 |
Type \\[magit-visit-thing] or \\[magit-diff-show-or-scroll-up] \ |
|
896 |
to visit the commit at point. |
|
897 |
|
|
898 |
Type \\[magit-branch-popup] to see available branch commands. |
|
899 |
Type \\[magit-merge-popup] to merge the branch or commit at point. |
|
900 |
Type \\[magit-cherry-pick-popup] to apply the commit at point. |
|
901 |
Type \\[magit-reset] to reset `HEAD' to the commit at point. |
|
902 |
|
|
903 |
\\{magit-log-mode-map}" |
|
904 |
:group 'magit-log |
|
905 |
(hack-dir-local-variables-non-file-buffer) |
|
906 |
(setq imenu-prev-index-position-function |
|
907 |
'magit-imenu--log-prev-index-position-function) |
|
908 |
(setq imenu-extract-index-name-function |
|
909 |
'magit-imenu--log-extract-index-name-function) |
|
910 |
(setq-local bookmark-make-record-function |
|
911 |
'magit-bookmark--log-make-record)) |
|
912 |
|
|
913 |
(defvar magit-log-disable-graph-hack-args |
|
914 |
'("-G" "--grep" "--author") |
|
915 |
"Arguments which disable the graph speedup hack.") |
|
916 |
|
|
917 |
(defun magit-log-refresh-buffer (revs args files) |
|
918 |
(magit-set-header-line-format |
|
919 |
(funcall magit-log-header-line-function revs args files)) |
|
920 |
(if (= (length files) 1) |
|
921 |
(unless (magit-file-tracked-p (car files)) |
|
922 |
(setq args (cons "--full-history" args))) |
|
923 |
(setq args (remove "--follow" args))) |
|
924 |
(when (--any-p (string-match-p |
|
925 |
(concat "^" (regexp-opt magit-log-remove-graph-args)) it) |
|
926 |
args) |
|
927 |
(setq args (remove "--graph" args))) |
|
928 |
(unless (member "--graph" args) |
|
929 |
(setq args (remove "--color" args))) |
|
930 |
(when-let ((limit (magit-log-get-commit-limit)) |
|
931 |
(limit (* 2 limit)) ; increase odds for complete graph |
|
932 |
(count (and (= (length revs) 1) |
|
933 |
(> limit 1024) ; otherwise it's fast enough |
|
934 |
(setq revs (car revs)) |
|
935 |
(not (string-match-p "\\.\\." revs)) |
|
936 |
(not (member revs '("--all" "--branches"))) |
|
937 |
(-none-p (lambda (arg) |
|
938 |
(--any-p (string-prefix-p it arg) |
|
939 |
magit-log-disable-graph-hack-args)) |
|
940 |
args) |
|
941 |
(magit-git-string "rev-list" "--count" |
|
942 |
"--first-parent" args revs)))) |
|
943 |
(setq revs (if (< (string-to-number count) limit) |
|
944 |
revs |
|
945 |
(format "%s~%s..%s" revs limit revs)))) |
|
946 |
(magit-insert-section (logbuf) |
|
947 |
(magit-insert-log revs args files))) |
|
948 |
|
|
949 |
(defun magit-log-header-line-arguments (revs args files) |
|
950 |
"Return string describing some of the used arguments." |
|
951 |
(mapconcat (lambda (arg) |
|
952 |
(if (string-match-p " " arg) |
|
953 |
(prin1 arg) |
|
954 |
arg)) |
|
955 |
`("git" "log" ,@args ,@revs "--" ,@files) |
|
956 |
" ")) |
|
957 |
|
|
958 |
(defun magit-log-header-line-sentence (revs args files) |
|
959 |
"Return string containing all arguments." |
|
960 |
(concat "Commits in " |
|
961 |
(mapconcat #'identity revs " ") |
|
962 |
(and (member "--reverse" args) |
|
963 |
" in reverse") |
|
964 |
(and files (concat " touching " |
|
965 |
(mapconcat 'identity files " "))) |
|
966 |
(--some (and (string-prefix-p "-L" it) |
|
967 |
(concat " " it)) |
|
968 |
args))) |
|
969 |
|
|
970 |
(defun magit-insert-log (revs &optional args files) |
|
971 |
"Insert a log section. |
|
972 |
Do not add this to a hook variable." |
|
973 |
(let ((magit-git-global-arguments |
|
974 |
(remove "--literal-pathspecs" magit-git-global-arguments))) |
|
975 |
(magit-git-wash (apply-partially #'magit-log-wash-log 'log) |
|
976 |
"log" |
|
977 |
(format "--format=%s%%h%s%%x00%s%%x00%%aN%%x00%%at%%x00%%s%s" |
|
978 |
(if (and (member "--left-right" args) |
|
979 |
(not (member "--graph" args))) |
|
980 |
"%m " |
|
981 |
"") |
|
982 |
(if (member "--decorate" args) "%d" "") |
|
983 |
(if (member "--show-signature" args) |
|
984 |
(progn (setq args (remove "--show-signature" args)) "%G?") |
|
985 |
"") |
|
986 |
(if (member "++header" args) |
|
987 |
(if (member "--graph" (setq args (remove "++header" args))) |
|
988 |
(concat "\n" magit-log-revision-headers-format "\n") |
|
989 |
(concat "\n" magit-log-revision-headers-format "\n")) |
|
990 |
"")) |
|
991 |
(progn |
|
992 |
(--when-let (--first (string-match "^\\+\\+order=\\(.+\\)$" it) args) |
|
993 |
(setq args (cons (format "--%s-order" (match-string 1 it)) |
|
994 |
(remove it args)))) |
|
995 |
(when (member "--decorate" args) |
|
996 |
(setq args (cons "--decorate=full" (remove "--decorate" args)))) |
|
997 |
(when (member "--reverse" args) |
|
998 |
(setq args (remove "--graph" args))) |
|
999 |
args) |
|
1000 |
"--use-mailmap" "--no-prefix" revs "--" files))) |
|
1001 |
|
|
1002 |
(defvar magit-commit-section-map |
|
1003 |
(let ((map (make-sparse-keymap))) |
|
1004 |
(define-key map [remap magit-visit-thing] 'magit-show-commit) |
|
1005 |
(define-key map "a" 'magit-cherry-apply) |
|
1006 |
map) |
|
1007 |
"Keymap for `commit' sections.") |
|
1008 |
|
|
1009 |
(defvar magit-module-commit-section-map |
|
1010 |
(let ((map (make-sparse-keymap))) |
|
1011 |
(define-key map [remap magit-visit-thing] 'magit-show-commit) |
|
1012 |
map) |
|
1013 |
"Keymap for `module-commit' sections.") |
|
1014 |
|
|
1015 |
(defconst magit-log-heading-re |
|
1016 |
(concat "^" |
|
1017 |
"\\(?4:[-_/|\\*o<>. ]*\\)" ; graph |
|
1018 |
"\\(?1:[0-9a-fA-F]+\\)" ; sha1 |
|
1019 |
"\\(?3:[^\0\n]+)\\)?\0" ; refs |
|
1020 |
"\\(?7:[BGUXYREN]\\)?\0" ; gpg |
|
1021 |
"\\(?5:[^\0\n]*\\)\0" ; author |
|
1022 |
;; Note: Date is optional because, prior to Git v2.19.0, |
|
1023 |
;; `git rebase -i --root` corrupts the root's author date. |
|
1024 |
"\\(?6:[^\0\n]*\\)\0" ; date |
|
1025 |
"\\(?2:.*\\)$")) ; msg |
|
1026 |
|
|
1027 |
(defconst magit-log-cherry-re |
|
1028 |
(concat "^" |
|
1029 |
"\\(?8:[-+]\\) " ; cherry |
|
1030 |
"\\(?1:[0-9a-fA-F]+\\) " ; sha1 |
|
1031 |
"\\(?2:.*\\)$")) ; msg |
|
1032 |
|
|
1033 |
(defconst magit-log-module-re |
|
1034 |
(concat "^" |
|
1035 |
"\\(?:\\(?11:[<>]\\) \\)?" ; side |
|
1036 |
"\\(?1:[0-9a-fA-F]+\\) " ; sha1 |
|
1037 |
"\\(?2:.*\\)$")) ; msg |
|
1038 |
|
|
1039 |
(defconst magit-log-bisect-vis-re |
|
1040 |
(concat "^" |
|
1041 |
"\\(?4:[-_/|\\*o<>. ]*\\)" ; graph |
|
1042 |
"\\(?1:[0-9a-fA-F]+\\)" ; sha1 |
|
1043 |
"\\(?3:[^\0\n]+)\\)?\0" ; refs |
|
1044 |
"\\(?2:.*\\)$")) ; msg |
|
1045 |
|
|
1046 |
(defconst magit-log-bisect-log-re |
|
1047 |
(concat "^# " |
|
1048 |
"\\(?3:bad:\\|skip:\\|good:\\) " ; "refs" |
|
1049 |
"\\[\\(?1:[^]\n]+\\)\\] " ; sha1 |
|
1050 |
"\\(?2:.*\\)$")) ; msg |
|
1051 |
|
|
1052 |
(defconst magit-log-reflog-re |
|
1053 |
(concat "^" |
|
1054 |
"\\(?1:[^\0\n]+\\)\0" ; sha1 |
|
1055 |
"\\(?5:[^\0\n]*\\)\0" ; author |
|
1056 |
"\\(?:\\(?:[^@\n]+@{\\(?6:[^}\n]+\\)}\0" ; date |
|
1057 |
"\\(?10:merge \\|autosave \\|restart \\|[^:\n]+: \\)?" ; refsub |
|
1058 |
"\\(?2:.*\\)?\\)\\|\0\\)$")) ; msg |
|
1059 |
|
|
1060 |
(defconst magit-reflog-subject-re |
|
1061 |
(concat "\\(?1:[^ ]+\\) ?" ; command |
|
1062 |
"\\(?2:\\(?: ?-[^ ]+\\)+\\)?" ; option |
|
1063 |
"\\(?: ?(\\(?3:[^)]+\\))\\)?")) ; type |
|
1064 |
|
|
1065 |
(defconst magit-log-stash-re |
|
1066 |
(concat "^" |
|
1067 |
"\\(?1:[^\0\n]+\\)\0" ; "sha1" |
|
1068 |
"\\(?5:[^\0\n]*\\)\0" ; author |
|
1069 |
"\\(?6:[^\0\n]+\\)\0" ; date |
|
1070 |
"\\(?2:.*\\)$")) ; msg |
|
1071 |
|
|
1072 |
(defvar magit-log-count nil) |
|
1073 |
|
|
1074 |
(defvar magit-log-format-message-function 'magit-log-propertize-keywords) |
|
1075 |
|
|
1076 |
(defun magit-log-wash-log (style args) |
|
1077 |
(setq args (-flatten args)) |
|
1078 |
(when (and (member "--graph" args) |
|
1079 |
(member "--color" args)) |
|
1080 |
(let ((ansi-color-apply-face-function |
|
1081 |
(lambda (beg end face) |
|
1082 |
(put-text-property beg end 'font-lock-face |
|
1083 |
(or face 'magit-log-graph))))) |
|
1084 |
(ansi-color-apply-on-region (point-min) (point-max)))) |
|
1085 |
(when (eq style 'cherry) |
|
1086 |
(reverse-region (point-min) (point-max))) |
|
1087 |
(let ((magit-log-count 0)) |
|
1088 |
(magit-wash-sequence (apply-partially 'magit-log-wash-rev style |
|
1089 |
(magit-abbrev-length))) |
|
1090 |
(if (derived-mode-p 'magit-log-mode) |
|
1091 |
(when (eq magit-log-count (magit-log-get-commit-limit)) |
|
1092 |
(magit-insert-section (longer) |
|
1093 |
(insert-text-button |
|
1094 |
(substitute-command-keys |
|
1095 |
(format "Type \\<%s>\\[%s] to show more history" |
|
1096 |
'magit-log-mode-map |
|
1097 |
'magit-log-double-commit-limit)) |
|
1098 |
'action (lambda (_button) |
|
1099 |
(magit-log-double-commit-limit)) |
|
1100 |
'follow-link t |
|
1101 |
'mouse-face 'magit-section-highlight))) |
|
1102 |
(insert ?\n)))) |
|
1103 |
|
|
1104 |
(cl-defun magit-log-wash-rev (style abbrev) |
|
1105 |
(when (derived-mode-p 'magit-log-mode) |
|
1106 |
(cl-incf magit-log-count)) |
|
1107 |
(looking-at (pcase style |
|
1108 |
(`log magit-log-heading-re) |
|
1109 |
(`cherry magit-log-cherry-re) |
|
1110 |
(`module magit-log-module-re) |
|
1111 |
(`reflog magit-log-reflog-re) |
|
1112 |
(`stash magit-log-stash-re) |
|
1113 |
(`bisect-vis magit-log-bisect-vis-re) |
|
1114 |
(`bisect-log magit-log-bisect-log-re))) |
|
1115 |
(magit-bind-match-strings |
|
1116 |
(hash msg refs graph author date gpg cherry _ refsub side) nil |
|
1117 |
(setq msg (substring-no-properties msg)) |
|
1118 |
(when refs |
|
1119 |
(setq refs (substring-no-properties refs))) |
|
1120 |
(let ((align (or (eq style 'cherry) |
|
1121 |
(not (member "--stat" (cadr magit-refresh-args))))) |
|
1122 |
(non-graph-re (if (eq style 'bisect-vis) |
|
1123 |
magit-log-bisect-vis-re |
|
1124 |
magit-log-heading-re))) |
|
1125 |
(magit-delete-line) |
|
1126 |
;; If the reflog entries have been pruned, the output of `git |
|
1127 |
;; reflog show' includes a partial line that refers to the hash |
|
1128 |
;; of the youngest expired reflog entry. |
|
1129 |
(when (and (eq style 'reflog) (not date)) |
|
1130 |
(cl-return-from magit-log-wash-rev t)) |
|
1131 |
(magit-insert-section section (commit hash) |
|
1132 |
(pcase style |
|
1133 |
(`stash (oset section type 'stash)) |
|
1134 |
(`module (oset section type 'module-commit)) |
|
1135 |
(`bisect-log (setq hash (magit-rev-parse "--short" hash)))) |
|
1136 |
(when cherry |
|
1137 |
(when (and (derived-mode-p 'magit-refs-mode) |
|
1138 |
magit-refs-show-commit-count) |
|
1139 |
(insert (make-string (1- magit-refs-focus-column-width) ?\s))) |
|
1140 |
(insert (propertize cherry 'face (if (string= cherry "-") |
|
1141 |
'magit-cherry-equivalent |
|
1142 |
'magit-cherry-unmatched))) |
|
1143 |
(insert ?\s)) |
|
1144 |
(when side |
|
1145 |
(insert (propertize side 'face (if (string= side "<") |
|
1146 |
'magit-cherry-equivalent |
|
1147 |
'magit-cherry-unmatched))) |
|
1148 |
(insert ?\s)) |
|
1149 |
(when align |
|
1150 |
(insert (propertize hash 'face 'magit-hash) ?\s)) |
|
1151 |
(when graph |
|
1152 |
(insert graph)) |
|
1153 |
(unless align |
|
1154 |
(insert (propertize hash 'face 'magit-hash) ?\s)) |
|
1155 |
(when (and refs (not magit-log-show-refname-after-summary)) |
|
1156 |
(insert (magit-format-ref-labels refs) ?\s)) |
|
1157 |
(when (eq style 'reflog) |
|
1158 |
(insert (format "%-2s " (1- magit-log-count))) |
|
1159 |
(when refsub |
|
1160 |
(insert (magit-reflog-format-subject |
|
1161 |
(substring refsub 0 (if (string-match-p ":" refsub) -2 -1)))))) |
|
1162 |
(when msg |
|
1163 |
(when gpg |
|
1164 |
(setq msg (propertize msg 'face |
|
1165 |
(pcase (aref gpg 0) |
|
1166 |
(?G 'magit-signature-good) |
|
1167 |
(?B 'magit-signature-bad) |
|
1168 |
(?U 'magit-signature-untrusted) |
|
1169 |
(?X 'magit-signature-expired) |
|
1170 |
(?Y 'magit-signature-expired-key) |
|
1171 |
(?R 'magit-signature-revoked) |
|
1172 |
(?E 'magit-signature-error))))) |
|
1173 |
(insert (funcall magit-log-format-message-function hash msg))) |
|
1174 |
(when (and refs magit-log-show-refname-after-summary) |
|
1175 |
(insert ?\s) |
|
1176 |
(insert (magit-format-ref-labels refs))) |
|
1177 |
(insert ?\n) |
|
1178 |
(when (memq style '(log reflog stash)) |
|
1179 |
(goto-char (line-beginning-position)) |
|
1180 |
(when (and refsub |
|
1181 |
(string-match "\\`\\([^ ]\\) \\+\\(..\\)\\(..\\)" date)) |
|
1182 |
(setq date (+ (string-to-number (match-string 1 date)) |
|
1183 |
(* (string-to-number (match-string 2 date)) 60 60) |
|
1184 |
(* (string-to-number (match-string 3 date)) 60)))) |
|
1185 |
(save-excursion |
|
1186 |
(backward-char) |
|
1187 |
(magit-log-format-margin hash author date))) |
|
1188 |
(when (and (eq style 'cherry) |
|
1189 |
(magit-buffer-margin-p)) |
|
1190 |
(save-excursion |
|
1191 |
(backward-char) |
|
1192 |
(apply #'magit-log-format-margin hash |
|
1193 |
(split-string (magit-rev-format "%aN%x00%ct" hash) "\0")))) |
|
1194 |
(when (and graph |
|
1195 |
(not (eobp)) |
|
1196 |
(not (looking-at non-graph-re))) |
|
1197 |
(when (looking-at "") |
|
1198 |
(magit-insert-heading) |
|
1199 |
(delete-char 1) |
|
1200 |
(magit-insert-section (commit-header) |
|
1201 |
(forward-line) |
|
1202 |
(magit-insert-heading) |
|
1203 |
(re-search-forward "") |
|
1204 |
(backward-delete-char 1) |
|
1205 |
(forward-char) |
|
1206 |
(insert ?\n)) |
|
1207 |
(delete-char 1)) |
|
1208 |
(if (looking-at "^\\(---\\|\n\s\\|\ndiff\\)") |
|
1209 |
(let ((limit (save-excursion |
|
1210 |
(and (re-search-forward non-graph-re nil t) |
|
1211 |
(match-beginning 0))))) |
|
1212 |
(unless (oref magit-insert-section--current content) |
|
1213 |
(magit-insert-heading)) |
|
1214 |
(delete-char (if (looking-at "\n") 1 4)) |
|
1215 |
(magit-diff-wash-diffs (list "--stat") limit)) |
|
1216 |
(when align |
|
1217 |
(setq align (make-string (1+ abbrev) ? ))) |
|
1218 |
(when (and (not (eobp)) (not (looking-at non-graph-re))) |
|
1219 |
(when align |
|
1220 |
(setq align (make-string (1+ abbrev) ? ))) |
|
1221 |
(while (and (not (eobp)) (not (looking-at non-graph-re))) |
|
1222 |
(when align |
|
1223 |
(save-excursion (insert align))) |
|
1224 |
(magit-make-margin-overlay) |
|
1225 |
(forward-line)) |
|
1226 |
;; When `--format' is used and its value isn't one of the |
|
1227 |
;; predefined formats, then `git-log' does not insert a |
|
1228 |
;; separator line. |
|
1229 |
(save-excursion |
|
1230 |
(forward-line -1) |
|
1231 |
(looking-at "[-_/|\\*o<>. ]*")) |
|
1232 |
(setq graph (match-string 0)) |
|
1233 |
(unless (string-match-p "[/\\]" graph) |
|
1234 |
(insert graph ?\n)))))))) |
|
1235 |
t) |
|
1236 |
|
|
1237 |
(defun magit-log-propertize-keywords (_rev msg) |
|
1238 |
(let ((start 0)) |
|
1239 |
(when (string-match "^\\(squash\\|fixup\\)! " msg start) |
|
1240 |
(setq start (match-end 0)) |
|
1241 |
(put-text-property (match-beginning 0) |
|
1242 |
(match-end 0) |
|
1243 |
'face 'magit-keyword-squash msg)) |
|
1244 |
(while (string-match "\\[[^[]*\\]" msg start) |
|
1245 |
(setq start (match-end 0)) |
|
1246 |
(when magit-log-highlight-keywords |
|
1247 |
(put-text-property (match-beginning 0) |
|
1248 |
(match-end 0) |
|
1249 |
'face 'magit-keyword msg)))) |
|
1250 |
msg) |
|
1251 |
|
|
1252 |
(defun magit-log-maybe-show-more-commits (section) |
|
1253 |
"When point is at the end of a log buffer, insert more commits. |
|
1254 |
|
|
1255 |
Log buffers end with a button \"Type + to show more history\". |
|
1256 |
When the use of a section movement command puts point on that |
|
1257 |
button, then automatically show more commits, without the user |
|
1258 |
having to press \"+\". |
|
1259 |
|
|
1260 |
This function is called by `magit-section-movement-hook' and |
|
1261 |
exists mostly for backward compatibility reasons." |
|
1262 |
(when (and (eq (oref section type) 'longer) |
|
1263 |
magit-log-auto-more) |
|
1264 |
(magit-log-double-commit-limit) |
|
1265 |
(forward-line -1) |
|
1266 |
(magit-section-forward))) |
|
1267 |
|
|
1268 |
(defvar magit--update-revision-buffer nil) |
|
1269 |
|
|
1270 |
(defun magit-log-maybe-update-revision-buffer (&optional _) |
|
1271 |
"When moving in the log buffer, update the revision buffer. |
|
1272 |
If there is no revision buffer in the same frame, then do nothing." |
|
1273 |
(when (derived-mode-p 'magit-log-mode) |
|
1274 |
(magit-log-maybe-update-revision-buffer-1))) |
|
1275 |
|
|
1276 |
(defun magit-log-maybe-update-revision-buffer-1 () |
|
1277 |
(unless magit--update-revision-buffer |
|
1278 |
(when-let ((commit (magit-section-value-if 'commit)) |
|
1279 |
(buffer (magit-mode-get-buffer 'magit-revision-mode nil t))) |
|
1280 |
(setq magit--update-revision-buffer (list commit buffer)) |
|
1281 |
(run-with-idle-timer |
|
1282 |
magit-update-other-window-delay nil |
|
1283 |
(let ((args (magit-show-commit--arguments))) |
|
1284 |
(lambda () |
|
1285 |
(pcase-let ((`(,rev ,buf) magit--update-revision-buffer)) |
|
1286 |
(setq magit--update-revision-buffer nil) |
|
1287 |
(when (buffer-live-p buf) |
|
1288 |
(let ((magit-display-buffer-noselect t)) |
|
1289 |
(apply #'magit-show-commit rev args)))) |
|
1290 |
(setq magit--update-revision-buffer nil))))))) |
|
1291 |
|
|
1292 |
(defvar magit--update-blob-buffer nil) |
|
1293 |
|
|
1294 |
(defun magit-log-maybe-update-blob-buffer (&optional _) |
|
1295 |
"When moving in the log buffer, update the blob buffer. |
|
1296 |
If there is no blob buffer in the same frame, then do nothing." |
|
1297 |
(when (derived-mode-p 'magit-log-mode) |
|
1298 |
(magit-log-maybe-update-blob-buffer-1))) |
|
1299 |
|
|
1300 |
(defun magit-log-maybe-update-blob-buffer-1 () |
|
1301 |
(unless magit--update-revision-buffer |
|
1302 |
(when-let ((commit (magit-section-value-if 'commit)) |
|
1303 |
(buffer (--first (with-current-buffer it magit-buffer-revision) |
|
1304 |
(mapcar #'window-buffer (window-list))))) |
|
1305 |
(setq magit--update-blob-buffer (list commit buffer)) |
|
1306 |
(run-with-idle-timer |
|
1307 |
magit-update-other-window-delay nil |
|
1308 |
(lambda () |
|
1309 |
(pcase-let ((`(,rev ,buf) magit--update-blob-buffer)) |
|
1310 |
(setq magit--update-blob-buffer nil) |
|
1311 |
(when (buffer-live-p buf) |
|
1312 |
(save-excursion |
|
1313 |
(with-selected-window (get-buffer-window buf) |
|
1314 |
(with-current-buffer buf |
|
1315 |
(magit-blob-visit (list (magit-rev-parse rev) |
|
1316 |
(magit-file-relative-name |
|
1317 |
magit-buffer-file-name)) |
|
1318 |
(line-number-at-pos)))))))))))) |
|
1319 |
|
|
1320 |
(defun magit-log-goto-same-commit (&optional default) |
|
1321 |
(let ((prev magit-previous-section)) |
|
1322 |
(when-let ((rev (cond ((and prev (magit-section-match 'commit prev)) |
|
1323 |
(oref prev value)) |
|
1324 |
((and prev (magit-section-match 'branch prev)) |
|
1325 |
(magit-rev-format "%h" (oref prev value))) |
|
1326 |
(default (magit-rev-format "%h" default)))) |
|
1327 |
(same (--first (equal (oref it value) rev) |
|
1328 |
(oref magit-root-section children)))) |
|
1329 |
(goto-char (oref same start))))) |
|
1330 |
|
|
1331 |
;;; Log Margin |
|
1332 |
|
|
1333 |
(defvar-local magit-log-margin-show-shortstat nil) |
|
1334 |
|
|
1335 |
(defun magit-toggle-log-margin-style () |
|
1336 |
"Toggle between the regular and the shortstat margin style. |
|
1337 |
The shortstat style is experimental and rather slow." |
|
1338 |
(interactive) |
|
1339 |
(setq magit-log-margin-show-shortstat |
|
1340 |
(not magit-log-margin-show-shortstat)) |
|
1341 |
(magit-set-buffer-margin nil t)) |
|
1342 |
|
|
1343 |
(defun magit-log-format-margin (rev author date) |
|
1344 |
(when-let ((option (magit-margin-option))) |
|
1345 |
(if magit-log-margin-show-shortstat |
|
1346 |
(magit-log-format-shortstat-margin rev) |
|
1347 |
(pcase-let ((`(,_ ,style ,width ,details ,details-width) |
|
1348 |
(or magit-buffer-margin |
|
1349 |
(symbol-value option)))) |
|
1350 |
(magit-make-margin-overlay |
|
1351 |
(concat (and details |
|
1352 |
(concat (propertize (truncate-string-to-width |
|
1353 |
(or author "") |
|
1354 |
details-width |
|
1355 |
nil ?\s (make-string 1 magit-ellipsis)) |
|
1356 |
'face 'magit-log-author) |
|
1357 |
" ")) |
|
1358 |
(propertize |
|
1359 |
(if (stringp style) |
|
1360 |
(format-time-string |
|
1361 |
style |
|
1362 |
(seconds-to-time (string-to-number date))) |
|
1363 |
(pcase-let* ((abbr (eq style 'age-abbreviated)) |
|
1364 |
(`(,cnt ,unit) (magit--age date abbr))) |
|
1365 |
(format (format (if abbr "%%2i%%-%ic" "%%2i %%-%is") |
|
1366 |
(- width (if details (1+ details-width) 0))) |
|
1367 |
cnt unit))) |
|
1368 |
'face 'magit-log-date))))))) |
|
1369 |
|
|
1370 |
(defun magit-log-format-shortstat-margin (rev) |
|
1371 |
(magit-make-margin-overlay |
|
1372 |
(if-let ((line (and rev (magit-git-string |
|
1373 |
"show" "--format=" "--shortstat" rev)))) |
|
1374 |
(if (string-match "\ |
|
1375 |
\\([0-9]+\\) files? changed, \ |
|
1376 |
\\(?:\\([0-9]+\\) insertions?(\\+)\\)?\ |
|
1377 |
\\(?:\\(?:, \\)?\\([0-9]+\\) deletions?(-)\\)?\\'" line) |
|
1378 |
(magit-bind-match-strings (files add del) line |
|
1379 |
(format |
|
1380 |
"%5s %5s%4s" |
|
1381 |
(if add |
|
1382 |
(propertize (format "%s+" add) 'face 'magit-diffstat-added) |
|
1383 |
"") |
|
1384 |
(if del |
|
1385 |
(propertize (format "%s-" del) 'face 'magit-diffstat-removed) |
|
1386 |
"") |
|
1387 |
files)) |
|
1388 |
"") |
|
1389 |
""))) |
|
1390 |
|
|
1391 |
(defun magit-log-margin-width (style details details-width) |
|
1392 |
(if magit-log-margin-show-shortstat |
|
1393 |
16 |
|
1394 |
(+ (if details (1+ details-width) 0) |
|
1395 |
(if (stringp style) |
|
1396 |
(length (format-time-string style)) |
|
1397 |
(+ 2 ; two digits |
|
1398 |
1 ; trailing space |
|
1399 |
(if (eq style 'age-abbreviated) |
|
1400 |
1 ; single character |
|
1401 |
(+ 1 ; gap after digits |
|
1402 |
(apply #'max (--map (max (length (nth 1 it)) |
|
1403 |
(length (nth 2 it))) |
|
1404 |
magit--age-spec))))))))) |
|
1405 |
|
|
1406 |
;;; Select Mode |
|
1407 |
|
|
1408 |
(defvar magit-log-select-mode-map |
|
1409 |
(let ((map (make-sparse-keymap))) |
|
1410 |
(set-keymap-parent map magit-log-mode-map) |
|
1411 |
(define-key map "\C-c\C-b" 'undefined) |
|
1412 |
(define-key map "\C-c\C-f" 'undefined) |
|
1413 |
(define-key map "." 'magit-log-select-pick) |
|
1414 |
(define-key map "e" 'magit-log-select-pick) |
|
1415 |
(define-key map "\C-c\C-c" 'magit-log-select-pick) |
|
1416 |
(define-key map "q" 'magit-log-select-quit) |
|
1417 |
(define-key map "\C-c\C-k" 'magit-log-select-quit) |
|
1418 |
map) |
|
1419 |
"Keymap for `magit-log-select-mode'.") |
|
1420 |
|
|
1421 |
(put 'magit-log-select-pick :advertised-binding [?\C-c ?\C-c]) |
|
1422 |
(put 'magit-log-select-quit :advertised-binding [?\C-c ?\C-k]) |
|
1423 |
|
|
1424 |
(define-derived-mode magit-log-select-mode magit-log-mode "Magit Select" |
|
1425 |
"Mode for selecting a commit from history. |
|
1426 |
|
|
1427 |
This mode is documented in info node `(magit)Select from Log'. |
|
1428 |
|
|
1429 |
\\<magit-mode-map>\ |
|
1430 |
Type \\[magit-refresh] to refresh the current buffer. |
|
1431 |
Type \\[magit-visit-thing] or \\[magit-diff-show-or-scroll-up] \ |
|
1432 |
to visit the commit at point. |
|
1433 |
|
|
1434 |
\\<magit-log-select-mode-map>\ |
|
1435 |
Type \\[magit-log-select-pick] to select the commit at point. |
|
1436 |
Type \\[magit-log-select-quit] to abort without selecting a commit." |
|
1437 |
:group 'magit-log |
|
1438 |
(hack-dir-local-variables-non-file-buffer)) |
|
1439 |
|
|
1440 |
(defun magit-log-select-refresh-buffer (rev args) |
|
1441 |
(magit-insert-section (logbuf) |
|
1442 |
(magit-insert-log rev args))) |
|
1443 |
|
|
1444 |
(defvar-local magit-log-select-pick-function nil) |
|
1445 |
(defvar-local magit-log-select-quit-function nil) |
|
1446 |
|
|
1447 |
(defun magit-log-select (pick &optional msg quit branch args initial) |
|
1448 |
(declare (indent defun)) |
|
1449 |
(magit-mode-setup #'magit-log-select-mode |
|
1450 |
(or branch (magit-get-current-branch) "HEAD") |
|
1451 |
(append args magit-log-select-arguments)) |
|
1452 |
(magit-log-goto-same-commit initial) |
|
1453 |
(setq magit-log-select-pick-function pick) |
|
1454 |
(setq magit-log-select-quit-function quit) |
|
1455 |
(when magit-log-select-show-usage |
|
1456 |
(let ((pick (propertize (substitute-command-keys |
|
1457 |
"\\[magit-log-select-pick]") |
|
1458 |
'face |
|
1459 |
'magit-header-line-key)) |
|
1460 |
(quit (propertize (substitute-command-keys |
|
1461 |
"\\[magit-log-select-quit]") |
|
1462 |
'face |
|
1463 |
'magit-header-line-key))) |
|
1464 |
(setq msg (format-spec |
|
1465 |
(if msg |
|
1466 |
(if (string-suffix-p "," msg) |
|
1467 |
(concat msg " or %q to abort") |
|
1468 |
msg) |
|
1469 |
"Type %p to select commit at point, or %q to abort") |
|
1470 |
`((?p . ,pick) |
|
1471 |
(?q . ,quit))))) |
|
1472 |
(add-face-text-property 0 (length msg) 'magit-header-line-log-select t msg) |
|
1473 |
(when (memq magit-log-select-show-usage '(both header-line)) |
|
1474 |
(magit-set-header-line-format msg)) |
|
1475 |
(when (memq magit-log-select-show-usage '(both echo-area)) |
|
1476 |
(message "%s" (substring-no-properties msg))))) |
|
1477 |
|
|
1478 |
(defun magit-log-select-pick () |
|
1479 |
"Select the commit at point and act on it. |
|
1480 |
Call `magit-log-select-pick-function' with the selected |
|
1481 |
commit as argument." |
|
1482 |
(interactive) |
|
1483 |
(let ((fun magit-log-select-pick-function) |
|
1484 |
(rev (magit-commit-at-point))) |
|
1485 |
(magit-mode-bury-buffer 'kill) |
|
1486 |
(funcall fun rev))) |
|
1487 |
|
|
1488 |
(defun magit-log-select-quit () |
|
1489 |
"Abort selecting a commit, don't act on any commit." |
|
1490 |
(interactive) |
|
1491 |
(magit-mode-bury-buffer 'kill) |
|
1492 |
(when magit-log-select-quit-function |
|
1493 |
(funcall magit-log-select-quit-function))) |
|
1494 |
|
|
1495 |
;;; Cherry Mode |
|
1496 |
|
|
1497 |
(defvar magit-cherry-mode-map |
|
1498 |
(let ((map (make-sparse-keymap))) |
|
1499 |
(set-keymap-parent map magit-mode-map) |
|
1500 |
(define-key map "q" 'magit-log-bury-buffer) |
|
1501 |
(define-key map "L" 'magit-margin-popup) |
|
1502 |
map) |
|
1503 |
"Keymap for `magit-cherry-mode'.") |
|
1504 |
|
|
1505 |
(define-derived-mode magit-cherry-mode magit-mode "Magit Cherry" |
|
1506 |
"Mode for looking at commits not merged upstream. |
|
1507 |
|
|
1508 |
\\<magit-mode-map>\ |
|
1509 |
Type \\[magit-refresh] to refresh the current buffer. |
|
1510 |
Type \\[magit-visit-thing] or \\[magit-diff-show-or-scroll-up] \ |
|
1511 |
to visit the commit at point. |
|
1512 |
|
|
1513 |
Type \\[magit-cherry-pick-popup] to apply the commit at point. |
|
1514 |
|
|
1515 |
\\{magit-cherry-mode-map}" |
|
1516 |
:group 'magit-log |
|
1517 |
(hack-dir-local-variables-non-file-buffer) |
|
1518 |
(setq imenu-create-index-function |
|
1519 |
'magit-imenu--cherry-create-index-function) |
|
1520 |
(setq-local bookmark-make-record-function |
|
1521 |
'magit-bookmark--cherry-make-record)) |
|
1522 |
|
|
1523 |
;;;###autoload |
|
1524 |
(defun magit-cherry (head upstream) |
|
1525 |
"Show commits in a branch that are not merged in the upstream branch." |
|
1526 |
(interactive |
|
1527 |
(let ((head (magit-read-branch "Cherry head"))) |
|
1528 |
(list head (magit-read-other-branch "Cherry upstream" head |
|
1529 |
(magit-get-upstream-branch head))))) |
|
1530 |
(require 'magit) |
|
1531 |
(magit-mode-setup #'magit-cherry-mode upstream head)) |
|
1532 |
|
|
1533 |
(defun magit-cherry-refresh-buffer (_upstream _head) |
|
1534 |
(magit-insert-section (cherry) |
|
1535 |
(magit-run-section-hook 'magit-cherry-sections-hook))) |
|
1536 |
|
|
1537 |
(defun magit-insert-cherry-headers () |
|
1538 |
"Insert headers appropriate for `magit-cherry-mode' buffers." |
|
1539 |
(magit-insert-head-branch-header (nth 1 magit-refresh-args)) |
|
1540 |
(magit-insert-upstream-branch-header (nth 1 magit-refresh-args) |
|
1541 |
(nth 0 magit-refresh-args) |
|
1542 |
"Upstream: ") |
|
1543 |
(insert ?\n)) |
|
1544 |
|
|
1545 |
(defun magit-insert-cherry-commits () |
|
1546 |
"Insert commit sections into a `magit-cherry-mode' buffer." |
|
1547 |
(magit-insert-section (cherries) |
|
1548 |
(magit-insert-heading "Cherry commits:") |
|
1549 |
(magit-git-wash (apply-partially 'magit-log-wash-log 'cherry) |
|
1550 |
"cherry" "-v" "--abbrev" magit-refresh-args))) |
|
1551 |
|
|
1552 |
;;; Reflog Mode |
|
1553 |
|
|
1554 |
(defvar magit-reflog-mode-map |
|
1555 |
(let ((map (make-sparse-keymap))) |
|
1556 |
(set-keymap-parent map magit-log-mode-map) |
|
1557 |
(define-key map "L" 'magit-margin-popup) |
|
1558 |
map) |
|
1559 |
"Keymap for `magit-reflog-mode'.") |
|
1560 |
|
|
1561 |
(define-derived-mode magit-reflog-mode magit-log-mode "Magit Reflog" |
|
1562 |
"Mode for looking at Git reflog. |
|
1563 |
|
|
1564 |
This mode is documented in info node `(magit)Reflog'. |
|
1565 |
|
|
1566 |
\\<magit-mode-map>\ |
|
1567 |
Type \\[magit-refresh] to refresh the current buffer. |
|
1568 |
Type \\[magit-visit-thing] or \\[magit-diff-show-or-scroll-up] \ |
|
1569 |
to visit the commit at point. |
|
1570 |
|
|
1571 |
Type \\[magit-cherry-pick-popup] to apply the commit at point. |
|
1572 |
Type \\[magit-reset] to reset `HEAD' to the commit at point. |
|
1573 |
|
|
1574 |
\\{magit-reflog-mode-map}" |
|
1575 |
:group 'magit-log |
|
1576 |
(hack-dir-local-variables-non-file-buffer) |
|
1577 |
(setq-local bookmark-make-record-function |
|
1578 |
'magit-bookmark--reflog-make-record)) |
|
1579 |
|
|
1580 |
(defun magit-reflog-refresh-buffer (ref args) |
|
1581 |
(magit-set-header-line-format (concat "Reflog for " ref)) |
|
1582 |
(magit-insert-section (reflogbuf) |
|
1583 |
(magit-git-wash (apply-partially 'magit-log-wash-log 'reflog) |
|
1584 |
"reflog" "show" "--format=%h%x00%aN%x00%gd%x00%gs" "--date=raw" |
|
1585 |
args ref "--"))) |
|
1586 |
|
|
1587 |
(defvar magit-reflog-labels |
|
1588 |
'(("commit" . magit-reflog-commit) |
|
1589 |
("amend" . magit-reflog-amend) |
|
1590 |
("merge" . magit-reflog-merge) |
|
1591 |
("checkout" . magit-reflog-checkout) |
|
1592 |
("branch" . magit-reflog-checkout) |
|
1593 |
("reset" . magit-reflog-reset) |
|
1594 |
("rebase" . magit-reflog-rebase) |
|
1595 |
("cherry-pick" . magit-reflog-cherry-pick) |
|
1596 |
("initial" . magit-reflog-commit) |
|
1597 |
("pull" . magit-reflog-remote) |
|
1598 |
("clone" . magit-reflog-remote) |
|
1599 |
("autosave" . magit-reflog-commit) |
|
1600 |
("restart" . magit-reflog-reset))) |
|
1601 |
|
|
1602 |
(defun magit-reflog-format-subject (subject) |
|
1603 |
(let* ((match (string-match magit-reflog-subject-re subject)) |
|
1604 |
(command (and match (match-string 1 subject))) |
|
1605 |
(option (and match (match-string 2 subject))) |
|
1606 |
(type (and match (match-string 3 subject))) |
|
1607 |
(label (if (string= command "commit") |
|
1608 |
(or type command) |
|
1609 |
command)) |
|
1610 |
(text (if (string= command "commit") |
|
1611 |
label |
|
1612 |
(mapconcat #'identity |
|
1613 |
(delq nil (list command option type)) |
|
1614 |
" ")))) |
|
1615 |
(format "%-16s " |
|
1616 |
(propertize text 'face |
|
1617 |
(or (cdr (assoc label magit-reflog-labels)) |
|
1618 |
'magit-reflog-other))))) |
|
1619 |
|
|
1620 |
;;; Log Sections |
|
1621 |
;;;; Standard Log Sections |
|
1622 |
|
|
1623 |
(defvar magit-unpulled-section-map |
|
1624 |
(let ((map (make-sparse-keymap))) |
|
1625 |
(define-key map [remap magit-visit-thing] 'magit-diff-dwim) |
|
1626 |
map) |
|
1627 |
"Keymap for `unpulled' sections.") |
|
1628 |
|
|
1629 |
(magit-define-section-jumper magit-jump-to-unpulled-from-upstream |
|
1630 |
"Unpulled from @{upstream}" unpulled "..@{upstream}") |
|
1631 |
|
|
1632 |
(defun magit-insert-unpulled-from-upstream () |
|
1633 |
"Insert commits that haven't been pulled from the upstream yet." |
|
1634 |
(when (magit-git-success "rev-parse" "@{upstream}") |
|
1635 |
(magit-insert-section (unpulled "..@{upstream}" t) |
|
1636 |
(magit-insert-heading |
|
1637 |
(format (propertize "Unpulled from %s:" 'face 'magit-section-heading) |
|
1638 |
(magit-get-upstream-branch))) |
|
1639 |
(magit-insert-log "..@{upstream}" magit-log-section-arguments)))) |
|
1640 |
|
|
1641 |
(magit-define-section-jumper magit-jump-to-unpulled-from-pushremote |
|
1642 |
"Unpulled from <push-remote>" unpulled |
|
1643 |
(concat ".." (magit-get-push-branch))) |
|
1644 |
|
|
1645 |
(defun magit-insert-unpulled-from-pushremote () |
|
1646 |
"Insert commits that haven't been pulled from the push-remote yet." |
|
1647 |
(--when-let (magit-get-push-branch) |
|
1648 |
(unless (and (equal (magit-rev-name it) |
|
1649 |
(magit-rev-name "@{upstream}")) |
|
1650 |
(or (memq 'magit-insert-unpulled-from-upstream |
|
1651 |
magit-status-sections-hook) |
|
1652 |
(memq 'magit-insert-unpulled-from-upstream-or-recent |
|
1653 |
magit-status-sections-hook))) |
|
1654 |
(magit-insert-section (unpulled (concat ".." it) t) |
|
1655 |
(magit-insert-heading |
|
1656 |
(format (propertize "Unpulled from %s:" 'face 'magit-section-heading) |
|
1657 |
(propertize it 'face 'magit-branch-remote))) |
|
1658 |
(magit-insert-log (concat ".." it) magit-log-section-arguments))))) |
|
1659 |
|
|
1660 |
(defvar magit-unpushed-section-map |
|
1661 |
(let ((map (make-sparse-keymap))) |
|
1662 |
(define-key map [remap magit-visit-thing] 'magit-diff-dwim) |
|
1663 |
map) |
|
1664 |
"Keymap for `unpushed' sections.") |
|
1665 |
|
|
1666 |
(magit-define-section-jumper magit-jump-to-unpushed-to-upstream |
|
1667 |
"Unpushed to @{upstream}" unpushed "@{upstream}..") |
|
1668 |
|
|
1669 |
(defun magit-insert-unpushed-to-upstream-or-recent () |
|
1670 |
"Insert section showing unpushed or other recent commits. |
|
1671 |
If an upstream is configured for the current branch and it is |
|
1672 |
behind of the current branch, then show the commits that have |
|
1673 |
not yet been pushed into the upstream branch. If no upstream is |
|
1674 |
configured or if the upstream is not behind of the current branch, |
|
1675 |
then show the last `magit-log-section-commit-count' commits." |
|
1676 |
(let ((upstream (magit-rev-parse "@{upstream}"))) |
|
1677 |
(if (or (not upstream) |
|
1678 |
(magit-rev-ancestor-p "HEAD" upstream)) |
|
1679 |
(magit-insert-recent-commits 'unpushed "@{upstream}..") |
|
1680 |
(magit-insert-unpushed-to-upstream)))) |
|
1681 |
|
|
1682 |
(defun magit-insert-unpushed-to-upstream () |
|
1683 |
"Insert commits that haven't been pushed to the upstream yet." |
|
1684 |
(when (magit-git-success "rev-parse" "@{upstream}") |
|
1685 |
(magit-insert-section (unpushed "@{upstream}..") |
|
1686 |
(magit-insert-heading |
|
1687 |
(format (propertize "Unmerged into %s:" 'face 'magit-section-heading) |
|
1688 |
(magit-get-upstream-branch))) |
|
1689 |
(magit-insert-log "@{upstream}.." magit-log-section-arguments)))) |
|
1690 |
|
|
1691 |
(defun magit-insert-recent-commits (&optional type value) |
|
1692 |
"Insert section showing recent commits. |
|
1693 |
Show the last `magit-log-section-commit-count' commits." |
|
1694 |
(let* ((start (format "HEAD~%s" magit-log-section-commit-count)) |
|
1695 |
(range (and (magit-rev-verify start) |
|
1696 |
(concat start "..HEAD")))) |
|
1697 |
(magit-insert-section ((eval (or type 'recent)) |
|
1698 |
(or value range) |
|
1699 |
t) |
|
1700 |
(magit-insert-heading "Recent commits") |
|
1701 |
(magit-insert-log range |
|
1702 |
(cons (format "-n%d" magit-log-section-commit-count) |
|
1703 |
(--remove (string-prefix-p "-n" it) |
|
1704 |
magit-log-section-arguments)))))) |
|
1705 |
|
|
1706 |
(magit-define-section-jumper magit-jump-to-unpushed-to-pushremote |
|
1707 |
"Unpushed to <push-remote>" unpushed |
|
1708 |
(concat (magit-get-push-branch) "..")) |
|
1709 |
|
|
1710 |
(defun magit-insert-unpushed-to-pushremote () |
|
1711 |
"Insert commits that haven't been pushed to the push-remote yet." |
|
1712 |
(--when-let (magit-get-push-branch) |
|
1713 |
(unless (and (equal (magit-rev-name it) |
|
1714 |
(magit-rev-name "@{upstream}")) |
|
1715 |
(or (memq 'magit-insert-unpushed-to-upstream |
|
1716 |
magit-status-sections-hook) |
|
1717 |
(memq 'magit-insert-unpushed-to-upstream-or-recent |
|
1718 |
magit-status-sections-hook))) |
|
1719 |
(magit-insert-section (unpushed (concat it "..") t) |
|
1720 |
(magit-insert-heading |
|
1721 |
(format (propertize "Unpushed to %s:" 'face 'magit-section-heading) |
|
1722 |
(propertize it 'face 'magit-branch-remote))) |
|
1723 |
(magit-insert-log (concat it "..") magit-log-section-arguments))))) |
|
1724 |
|
|
1725 |
;;;; Auxiliary Log Sections |
|
1726 |
|
|
1727 |
(defun magit-insert-unpulled-cherries () |
|
1728 |
"Insert section showing unpulled commits. |
|
1729 |
Like `magit-insert-unpulled-from-upstream' but prefix each commit |
|
1730 |
which has not been applied yet (i.e. a commit with a patch-id |
|
1731 |
not shared with any local commit) with \"+\", and all others with |
|
1732 |
\"-\"." |
|
1733 |
(when (magit-git-success "rev-parse" "@{upstream}") |
|
1734 |
(magit-insert-section (unpulled "..@{upstream}") |
|
1735 |
(magit-insert-heading "Unpulled commits:") |
|
1736 |
(magit-git-wash (apply-partially 'magit-log-wash-log 'cherry) |
|
1737 |
"cherry" "-v" (magit-abbrev-arg) |
|
1738 |
(magit-get-current-branch) "@{upstream}")))) |
|
1739 |
|
|
1740 |
(defun magit-insert-unpushed-cherries () |
|
1741 |
"Insert section showing unpushed commits. |
|
1742 |
Like `magit-insert-unpushed-to-upstream' but prefix each commit |
|
1743 |
which has not been applied to upstream yet (i.e. a commit with |
|
1744 |
a patch-id not shared with any upstream commit) with \"+\", and |
|
1745 |
all others with \"-\"." |
|
1746 |
(when (magit-git-success "rev-parse" "@{upstream}") |
|
1747 |
(magit-insert-section (unpushed "@{upstream}..") |
|
1748 |
(magit-insert-heading "Unpushed commits:") |
|
1749 |
(magit-git-wash (apply-partially 'magit-log-wash-log 'cherry) |
|
1750 |
"cherry" "-v" (magit-abbrev-arg) "@{upstream}")))) |
|
1751 |
|
|
1752 |
;;; _ |
|
1753 |
(provide 'magit-log) |
|
1754 |
;;; magit-log.el ends here |