fordon_greeman / voidfiles

Branch: 2079ec5
Switch branches/tags
main
theidioticdev add emacs 2079ec5 22h ago
2156 lines · 85.4 KB Raw Blame History
1 ;;; magit-log.el --- Inspect Git history -*- lexical-binding:t; coding:utf-8 -*-
2
3 ;; Copyright (C) 2008-2026 The Magit Project Contributors
4
5 ;; Author: Jonas Bernoulli <emacs.magit@jonas.bernoulli.dev>
6 ;; Maintainer: Jonas Bernoulli <emacs.magit@jonas.bernoulli.dev>
7
8 ;; SPDX-License-Identifier: GPL-3.0-or-later
9
10 ;; Magit is free software: you can redistribute it and/or modify it
11 ;; under the terms of the GNU General Public License as published by
12 ;; the Free Software Foundation, either version 3 of the License, or
13 ;; (at your option) any later version.
14 ;;
15 ;; Magit is distributed in the hope that it will be useful, but WITHOUT
16 ;; ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
17 ;; or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
18 ;; License for more details.
19 ;;
20 ;; You should have received a copy of the GNU General Public License
21 ;; along with Magit. If not, see <https://www.gnu.org/licenses/>.
22
23 ;;; Commentary:
24
25 ;; This library implements support for looking at Git logs, including
26 ;; special logs like cherry-logs, as well as for selecting a commit
27 ;; from a log.
28
29 ;;; Code:
30
31 (require 'magit-core)
32 (require 'magit-diff)
33
34 (declare-function magit-blob-visit "magit-files" (rev file))
35 (declare-function magit-cherry-apply "magit-sequence" (commit &optional args))
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 include-dirs))
42 (declare-function magit-rebase--get-state-lines "magit-sequence"
43 (file))
44 (declare-function magit-show-commit "magit-diff"
45 (arg1 &optional arg2 arg3 arg4))
46 (declare-function magit-reflog-format-subject "magit-reflog" (subject))
47 (defvar magit-refs-focus-column-width)
48 (defvar magit-refs-margin)
49 (defvar magit-refs-show-commit-count)
50 (defvar magit--right-margin-config)
51 (defvar magit-status-margin)
52 (defvar magit-status-sections-hook)
53 (defvar magit-status-use-buffer-arguments)
54
55 (require 'ansi-color)
56 (require 'crm)
57 (require 'which-func)
58
59 (make-obsolete-variable 'magit-log-highlight-keywords
60 'magit-log-wash-summary-hook
61 "Magit 4.3.0")
62
63 (make-obsolete-variable 'magit-log-format-message-function
64 'magit-log-wash-summary-hook
65 "Magit 4.3.0")
66
67 ;;; Options
68 ;;;; Log Mode
69
70 (defgroup magit-log nil
71 "Inspect and manipulate Git history."
72 :link '(info-link "(magit)Logging")
73 :group 'magit-commands
74 :group 'magit-modes)
75
76 (defcustom magit-log-mode-hook nil
77 "Hook run after entering Magit-Log mode."
78 :group 'magit-log
79 :type 'hook)
80
81 (defcustom magit-log-remove-graph-args '("--follow" "-G" "-S" "-L")
82 "The log arguments that cause the \"--graph\" argument to be dropped.
83
84 The default value lists the arguments that are incompatible with
85 \"--graph\" and therefore must be dropped when that is used. You
86 can add additional arguments that are available in `magit-log',
87 but I recommend that you don't do that. Nowadays I would define
88 this as a constant, but I am preserving it as an option, in case
89 someone actually customized it."
90 :package-version '(magit . "4.3.7")
91 :group 'magit-log
92 :type '(repeat (string :tag "Argument"))
93 :options '("--follow" "-G" "-S" "-L"))
94
95 (defcustom magit-log-revision-headers-format "\
96 %+b%+N
97 Author: %aN <%aE>
98 Committer: %cN <%cE>"
99 "Additional format string used with the `++header' argument."
100 :package-version '(magit . "3.2.0")
101 :group 'magit-log
102 :type 'string)
103
104 (defcustom magit-log-auto-more nil
105 "Insert more log entries automatically when moving past the last entry.
106 Only considered when moving past the last entry with
107 `magit-goto-*-section' commands."
108 :group 'magit-log
109 :type 'boolean)
110
111 (defcustom magit-log-margin '(t age magit-log-margin-width t 18)
112 "Format of the margin in `magit-log-mode' buffers.
113
114 The value has the form (INIT STYLE WIDTH AUTHOR AUTHOR-WIDTH).
115
116 If INIT is non-nil, then the margin is shown initially.
117 STYLE controls how to format the author or committer date.
118 It can be one of `age' (to show the age of the commit),
119 `age-abbreviated' (to abbreviate the time unit to a character),
120 or a string (suitable for `format-time-string') to show the
121 actual date. Option `magit-log-margin-show-committer-date'
122 controls which date is being displayed.
123 WIDTH controls the width of the margin. This exists for forward
124 compatibility and currently the value should not be changed.
125 AUTHOR controls whether the name of the author is also shown by
126 default.
127 AUTHOR-WIDTH has to be an integer. When the name of the author
128 is shown, then this specifies how much space is used to do so."
129 :package-version '(magit . "2.9.0")
130 :group 'magit-log
131 :group 'magit-margin
132 :type magit-log-margin--custom-type
133 :initialize #'magit-custom-initialize-reset
134 :set (apply-partially #'magit-margin-set-variable 'magit-log-mode))
135
136 (defcustom magit-log-margin-show-committer-date nil
137 "Whether to show the committer date in the margin.
138
139 This option only controls whether the committer date is displayed
140 instead of the author date. Whether some date is displayed in
141 the margin and whether the margin is displayed at all is
142 controlled by other options."
143 :package-version '(magit . "3.0.0")
144 :group 'magit-log
145 :group 'magit-margin
146 :type 'boolean)
147
148 (defcustom magit-log-show-refname-after-summary nil
149 "Whether to show refnames after commit summaries.
150 This is useful if you use really long branch names."
151 :package-version '(magit . "2.2.0")
152 :group 'magit-log
153 :type 'boolean)
154
155 (defcustom magit-log-wash-summary-hook
156 (list #'magit-highlight-squash-markers
157 #'magit-highlight-bracket-keywords)
158 "Functions used to highlight parts of each individual commit summary.
159
160 These functions are called in order, in a buffer containing the first
161 line of the commit message. They should set text properties as they see
162 fit, usually just `font-lock-face'. Before each function is called,
163 point is at the beginning of the buffer.
164
165 See also the related `magit-revision-wash-message-hook'. You likely
166 want to use the same functions for both hooks."
167 :package-version '(magit . "4.3.0")
168 :group 'magit-log
169 :type 'hook
170 :options (list #'magit-highlight-squash-markers
171 #'magit-highlight-bracket-keywords))
172
173 (defcustom magit-log-trailer-labels nil
174 "Whether and how to insert labels, derived from commit message trailers.
175
176 If non-nil, the value has the form (FUNCTION . OPTIONS). FUNCTION is
177 called with one argument, the trailers as an alist, and should return a
178 string, which is then inserted in between the refnames and the message,
179 or nil. OPTIONS specifies additional options for \"%(trailers)\". You
180 should probably use something like \"key=KEY1,key=KEY2,only=true\".
181 \"unfold=true,separator=^^,key_value_separator=^_\" is always appended
182 to the options specified here. See the git-log(1) manpage."
183 :package-version '(magit . "4.4.1")
184 :group 'magit-log
185 :type '(choice (const :tag "Ignore trailers" nil)
186 (cons :tag "Process trailers"
187 (function :tag "Formatting function")
188 (string :tag "Options for %%(trailers)"))))
189
190 (defcustom magit-log-header-line-function #'magit-log-header-line-sentence
191 "Function used to generate text shown in header line of log buffers."
192 :package-version '(magit . "2.12.0")
193 :group 'magit-log
194 :type `(radio (function-item ,#'magit-log-header-line-arguments)
195 (function-item ,#'magit-log-header-line-sentence)
196 function))
197
198 (defcustom magit-log-trace-definition-function #'magit-which-function
199 "Function used to determine the function at point.
200 This is used by the command `magit-log-trace-definition'.
201 You should prefer `magit-which-function' over `which-function'
202 because the latter may make use of Imenu's outdated cache."
203 :package-version '(magit . "3.0.0")
204 :group 'magit-log
205 :type `(radio (function-item ,#'magit-which-function)
206 (function-item ,#'which-function)
207 (function-item ,#'add-log-current-defun)
208 function))
209
210 (defcustom magit-log-color-graph-limit 256
211 "Number of commits over which log graphs are not colored.
212 When showing more commits than specified, then the \"--color\"
213 argument is silently dropped. This is necessary because the
214 `ansi-color' library, which is used to turn control sequences
215 into faces, is just too slow."
216 :package-version '(magit . "4.0.0")
217 :group 'magit-log
218 :type 'number)
219
220 (defcustom magit-log-show-signatures-limit 256
221 "Number of commits over which signatures are not verified.
222 When showing more commits than specified by this option, then the
223 \"--show-signature\" argument, if specified, is silently dropped.
224 This is necessary because checking the signature of a large
225 number of commits is just too slow."
226 :package-version '(magit . "4.0.0")
227 :group 'magit-log
228 :type 'number)
229
230 (defface magit-log-graph
231 '((((class color) (background light)) :foreground "grey30")
232 (((class color) (background dark)) :foreground "grey80"))
233 "Face for the graph part of the log output."
234 :group 'magit-faces)
235
236 (defface magit-log-author
237 '((((class color) (background light))
238 :foreground "firebrick"
239 :slant normal
240 :weight normal)
241 (((class color) (background dark))
242 :foreground "tomato"
243 :slant normal
244 :weight normal))
245 "Face for the author part of the log output."
246 :group 'magit-faces)
247
248 (defface magit-log-date
249 '((((class color) (background light))
250 :foreground "grey30"
251 :slant normal
252 :weight normal)
253 (((class color) (background dark))
254 :foreground "grey80"
255 :slant normal
256 :weight normal))
257 "Face for the date part of the log output."
258 :group 'magit-faces)
259
260 (defface magit-header-line-log-select
261 '((t :inherit bold))
262 "Face for the `header-line' in `magit-log-select-mode'."
263 :group 'magit-faces)
264
265 ;;;; File Log
266
267 (defcustom magit-log-buffer-file-locked t
268 "Whether `magit-log-buffer-file-quick' uses a dedicated buffer."
269 :package-version '(magit . "2.7.0")
270 :group 'magit-commands
271 :group 'magit-log
272 :type 'boolean)
273
274 ;;;; Select Mode
275
276 (defcustom magit-log-select-show-usage 'both
277 "Whether to show usage information when selecting a commit from a log.
278 The message can be shown in the `echo-area' or the `header-line', or in
279 `both' places. If the value isn't one of these symbols, then it should
280 be nil, in which case no usage information is shown."
281 :package-version '(magit . "2.1.0")
282 :group 'magit-log
283 :type '(choice (const :tag "In echo-area" echo-area)
284 (const :tag "In header-line" header-line)
285 (const :tag "In both places" both)
286 (const :tag "Nowhere")))
287
288 (defcustom magit-log-select-margin
289 (list (nth 0 magit-log-margin)
290 (nth 1 magit-log-margin)
291 'magit-log-margin-width t
292 (nth 4 magit-log-margin))
293 "Format of the margin in `magit-log-select-mode' buffers.
294
295 The value has the form (INIT STYLE WIDTH AUTHOR AUTHOR-WIDTH).
296
297 If INIT is non-nil, then the margin is shown initially.
298 STYLE controls how to format the author or committer date.
299 It can be one of `age' (to show the age of the commit),
300 `age-abbreviated' (to abbreviate the time unit to a character),
301 or a string (suitable for `format-time-string') to show the
302 actual date. Option `magit-log-margin-show-committer-date'
303 controls which date is being displayed.
304 WIDTH controls the width of the margin. This exists for forward
305 compatibility and currently the value should not be changed.
306 AUTHOR controls whether the name of the author is also shown by
307 default.
308 AUTHOR-WIDTH has to be an integer. When the name of the author
309 is shown, then this specifies how much space is used to do so."
310 :package-version '(magit . "2.9.0")
311 :group 'magit-log
312 :group 'magit-margin
313 :type magit-log-margin--custom-type
314 :initialize #'magit-custom-initialize-reset
315 :set-after '(magit-log-margin)
316 :set (apply-partially #'magit-margin-set-variable 'magit-log-select-mode))
317
318 ;;;; Cherry Mode
319
320 (defcustom magit-cherry-sections-hook
321 (list #'magit-insert-cherry-headers
322 #'magit-insert-cherry-commits)
323 "Hook run to insert sections into the cherry buffer."
324 :package-version '(magit . "2.1.0")
325 :group 'magit-log
326 :type 'hook)
327
328 (defcustom magit-cherry-margin
329 (list (nth 0 magit-log-margin)
330 (nth 1 magit-log-margin)
331 'magit-log-margin-width t
332 (nth 4 magit-log-margin))
333 "Format of the margin in `magit-cherry-mode' buffers.
334
335 The value has the form (INIT STYLE WIDTH AUTHOR AUTHOR-WIDTH).
336
337 If INIT is non-nil, then the margin is shown initially.
338 STYLE controls how to format the author or committer date.
339 It can be one of `age' (to show the age of the commit),
340 `age-abbreviated' (to abbreviate the time unit to a character),
341 or a string (suitable for `format-time-string') to show the
342 actual date. Option `magit-log-margin-show-committer-date'
343 controls which date is being displayed.
344 WIDTH controls the width of the margin. This exists for forward
345 compatibility and currently the value should not be changed.
346 AUTHOR controls whether the name of the author is also shown by
347 default.
348 AUTHOR-WIDTH has to be an integer. When the name of the author
349 is shown, then this specifies how much space is used to do so."
350 :package-version '(magit . "2.9.0")
351 :group 'magit-log
352 :group 'magit-margin
353 :type magit-log-margin--custom-type
354 :initialize #'magit-custom-initialize-reset
355 :set-after '(magit-log-margin)
356 :set (apply-partially #'magit-margin-set-variable 'magit-cherry-mode))
357
358 ;;;; Log Sections
359
360 (defcustom magit-log-section-commit-count 10
361 "How many recent commits to show in certain log sections.
362 How many recent commits `magit-insert-recent-commits' and
363 `magit-insert-unpushed-to-upstream-or-recent' (provided
364 the upstream isn't ahead of the current branch) show."
365 :package-version '(magit . "2.1.0")
366 :group 'magit-status
367 :type 'number)
368
369 (defcustom magit-log-merged-commit-count 20
370 "How many surrounding commits to show for `magit-log-merged'.
371 `magit-log-merged' will shows approximately half of this number
372 commits before and half after."
373 :package-version '(magit . "3.3.0")
374 :group 'magit-log
375 :type 'integer)
376
377 ;;; Variables
378
379 (defvar-local magit-buffer-log-revisions nil)
380 (defvar-local magit-buffer-log-args nil)
381 (defvar-local magit-buffer-log-files nil)
382
383 ;;; Arguments
384 ;;;; Prefix Classes
385
386 (defclass magit-log-prefix (transient-prefix)
387 ((history-key :initform 'magit-log)
388 (major-mode :initform 'magit-log-mode)))
389
390 (defclass magit-log-refresh-prefix (magit-log-prefix)
391 ((history-key :initform 'magit-log)
392 (major-mode :initform nil)))
393
394 ;;;; Prefix Methods
395
396 (cl-defmethod transient-prefix-value ((obj magit-log-prefix))
397 (let ((args (cl-call-next-method obj)))
398 (list (seq-filter #'atom args)
399 (cdr (assoc "--" args)))))
400
401 (cl-defmethod transient-init-value ((obj magit-log-prefix))
402 (pcase-let ((`(,args ,files)
403 (magit-log--get-value 'magit-log-mode 'prefix)))
404 (when-let ((_(not (eq transient-current-command 'magit-dispatch)))
405 (file (magit-file-relative-name)))
406 (setq files (list file)))
407 (oset obj value (if files `(("--" ,@files) ,@args) args))))
408
409 (cl-defmethod transient-init-value ((obj magit-log-refresh-prefix))
410 (oset obj value (if magit-buffer-log-files
411 `(("--" ,@magit-buffer-log-files)
412 ,@magit-buffer-log-args)
413 magit-buffer-log-args)))
414
415 (cl-defmethod transient-set-value ((obj magit-log-prefix))
416 (magit-log--set-value obj))
417
418 (cl-defmethod transient-save-value ((obj magit-log-prefix))
419 (magit-log--set-value obj 'save))
420
421 ;;;; Argument Access
422
423 (defun magit-log-arguments (&optional mode)
424 "Return the current log arguments."
425 (if (memq transient-current-command '(magit-log magit-log-refresh))
426 (transient-args transient-current-command)
427 (magit-log--get-value (or mode 'magit-log-mode) 'direct)))
428
429 (defun magit-log--get-value (mode &optional use-buffer-args)
430 (setq use-buffer-args
431 (pcase-exhaustive use-buffer-args
432 ('prefix magit-prefix-use-buffer-arguments)
433 ('status magit-status-use-buffer-arguments)
434 ('direct magit-direct-use-buffer-arguments)
435 ('nil magit-direct-use-buffer-arguments)
436 ((or 'always 'selected 'current 'never)
437 use-buffer-args)))
438 (cond-let
439 ((and (memq use-buffer-args '(always selected current))
440 (eq major-mode mode))
441 (list magit-buffer-log-args
442 magit-buffer-log-files))
443 ([_(memq use-buffer-args '(always selected))]
444 [buffer (magit-get-mode-buffer mode nil (eq use-buffer-args 'selected))]
445 (list (buffer-local-value 'magit-buffer-log-args buffer)
446 (buffer-local-value 'magit-buffer-log-files buffer)))
447 ((plist-member (symbol-plist mode) 'magit-log-current-arguments)
448 (list (get mode 'magit-log-current-arguments) nil))
449 ([elt (assq (intern (format "magit-log:%s" mode)) transient-values)]
450 (list (cdr elt) nil))
451 ((list (get mode 'magit-log-default-arguments) nil))))
452
453 (defun magit-log--set-value (obj &optional save)
454 (pcase-let* ((obj (oref obj prototype))
455 (mode (or (oref obj major-mode) major-mode))
456 (key (intern (format "magit-log:%s" mode)))
457 (`(,args ,files) (transient-args (oref obj command))))
458 (put mode 'magit-log-current-arguments args)
459 (when save
460 (setf (alist-get key transient-values) args)
461 (transient-save-values))
462 (transient--history-push obj)
463 (setq magit-buffer-log-args args)
464 (unless (derived-mode-p 'magit-log-select-mode)
465 (setq magit-buffer-log-files files))
466 (magit-refresh)))
467
468 ;;; Commands
469 ;;;; Prefix Commands
470
471 (transient-define-group magit-log-infix-arguments
472 ;; The grouping in git-log(1) appears to be guided by implementation
473 ;; details, so our logical grouping only follows it to an extent.
474 ;; Arguments that are "misplaced" here:
475 ;; 1. From "Commit Formatting".
476 ;; 2. From "Common Diff Options". ;FIXME
477 ;; 3. From unnamed first group.
478 ;; 4. Implemented by Magit.
479 ["Commit limiting"
480 :if magit-log-infix-arguments--show-p
481 (magit-log:-n)
482 (magit:--author)
483 (7 magit-log:--since)
484 (7 magit-log:--until)
485 (magit-log:--grep)
486 (7 "-i" "Search case-insensitive" ("-i" "--regexp-ignore-case"))
487 (7 "-I" "Invert search pattern" "--invert-grep")
488 (magit-log:-G) ;2
489 (magit-log:-S) ;2
490 (magit-log:-L) ;2
491 (7 "=M" "Only merges" "--merges")
492 (7 "=m" "Omit merges" "--no-merges")
493 (7 "=p" "First parent" "--first-parent")]
494 ["History simplification"
495 :if magit-log-infix-arguments--show-p
496 ( "-D" "Simplify by decoration" "--simplify-by-decoration")
497 (magit:--)
498 ( "-f" "Follow renames when showing single-file log" "--follow") ;3
499 (6 "/s" "Only commits changing given paths" "--sparse")
500 (7 "/d" "Only selected commits plus meaningful history" "--dense")
501 (7 "/a" "Only commits existing directly on ancestry path" "--ancestry-path")
502 (6 "/f" "Do not prune history" "--full-history")
503 (7 "/m" "Prune some history" "--simplify-merges")]
504 ["Commit ordering"
505 :if magit-log-infix-arguments--show-p
506 (magit-log:--*-order)
507 ("-r" "Reverse order" "--reverse")]
508 ["Formatting"
509 :if magit-log-infix-arguments--show-p
510 ("-g" "Show graph" "--graph") ;1
511 ("-c" "Show graph in color" "--color") ;2
512 (magit-log:--graph-lane-limit :level 5) ;TODO
513 ("-d" "Show refnames" "--decorate") ;3
514 ("=S" "Show signatures" "--show-signature") ;1
515 ("-h" "Show header" "++header") ;4
516 ("-p" "Show diffs" ("-p" "--patch")) ;2
517 ("-s" "Show diffstats" "--stat")]) ;2
518
519 (defun magit-log-infix-arguments--show-p ()
520 (if (eq (oref (transient-prefix-object) command) 'magit-log-refresh)
521 (eq major-mode 'magit-log-mode)
522 t))
523
524 ;;;###autoload(autoload 'magit-log "magit-log" nil t)
525 (transient-define-prefix magit-log ()
526 "Show a commit or reference log."
527 :man-page "git-log"
528 :class 'magit-log-prefix
529 :incompatible '(("--merges" "--no-merges"))
530 'magit-log-infix-arguments
531 [["Log"
532 ("l" magit-log-current)
533 ("o" "other" magit-log-other)
534 ("h" "HEAD" magit-log-head :level 0)
535 ("u" "related" magit-log-related)]
536 [""
537 ("L" "local branches" magit-log-branches)
538 ("b" "all branches" magit-log-all-branches)
539 ("a" "all references" magit-log-all)
540 ("R" "reflog objects" magit-log-reflog :level 0)
541 ("B" "matching branches" magit-log-matching-branches :level 7)
542 ("T" "matching tags" magit-log-matching-tags :level 7)
543 ("m" "merged" magit-log-merged :level 7)]
544 ["Reflog"
545 ("r" "current" magit-reflog-current)
546 ("O" "other" magit-reflog-other)
547 ("H" "HEAD" magit-reflog-head)]
548 [:if-non-nil magit-wip-mode
549 :description "Wiplog"
550 ("i" "index" magit-wip-log-index)
551 ("w" "worktree" magit-wip-log-worktree)]
552 ["Other"
553 ("s" "shortlog" magit-shortlog)]])
554
555 ;;;###autoload(autoload 'magit-log-refresh "magit-log" nil t)
556 (transient-define-prefix magit-log-refresh ()
557 "Change the arguments used for the log(s) in the current buffer."
558 :man-page "git-log"
559 :class 'magit-log-refresh-prefix
560 :incompatible '(("--merges" "--no-merges"))
561 'magit-log-infix-arguments
562 [:if-not-mode magit-log-mode
563 :description "Arguments"
564 (magit-log:-n)
565 (magit-log:--*-order)
566 ("-g" "Show graph" "--graph")
567 ("-c" "Show graph in color" "--color")
568 ("-d" "Show refnames" "--decorate")]
569 [["Refresh"
570 ("g" "buffer" magit-log-refresh)
571 ("s" "buffer and set defaults" transient-set-and-exit)
572 ("w" "buffer and save defaults" transient-save-and-exit)]
573 ["Margin"
574 (magit-toggle-margin)
575 (magit-cycle-margin-style)
576 (magit-toggle-margin-details)
577 (magit-toggle-log-margin-style)]
578 [:if-mode magit-log-mode
579 :description "Toggle"
580 ("b" "buffer lock" magit-toggle-buffer-lock)]]
581 (interactive)
582 (cond
583 ((not (eq transient-current-command 'magit-log-refresh))
584 (pcase major-mode
585 ('magit-reflog-mode
586 (user-error "Cannot change log arguments in reflog buffers"))
587 ('magit-cherry-mode
588 (user-error "Cannot change log arguments in cherry buffers")))
589 (transient-setup 'magit-log-refresh))
590 (t
591 (pcase-let ((`(,args ,files) (magit-log-arguments)))
592 (setq magit-buffer-log-args args)
593 (unless (derived-mode-p 'magit-log-select-mode)
594 (setq magit-buffer-log-files files)))
595 (magit-refresh))))
596
597 ;;;; Infix Commands
598
599 (transient-define-argument magit-log:-n ()
600 :description "Limit number of commits"
601 :class 'transient-option
602 ;; For historic reasons (and because it easy to guess what "-n"
603 ;; stands for) this is the only argument where we do not use the
604 ;; long argument ("--max-count").
605 :shortarg "-n"
606 :argument "-n"
607 :reader #'transient-read-number-N+)
608
609 (transient-define-argument magit-log:--graph-lane-limit ()
610 :description "Show graph lanes"
611 :class 'transient-option
612 :key "=g"
613 :argument "--graph-lane-limit="
614 :reader #'transient-read-number-N+
615 :if (##magit-git-version>= "2.55"))
616
617 (transient-define-argument magit:--author ()
618 :description "Limit to author"
619 :class 'transient-option
620 :key "-A"
621 :argument "--author="
622 :multi-value 'repeat
623 :reader #'magit-transient-read-persons)
624
625 (transient-define-argument magit-log:--since ()
626 :description "Limit to commits since"
627 :class 'transient-option
628 :key "=s"
629 :argument "--since="
630 :reader #'transient-read-date)
631
632 (transient-define-argument magit-log:--until ()
633 :description "Limit to commits until"
634 :class 'transient-option
635 :key "=u"
636 :argument "--until="
637 :reader #'transient-read-date)
638
639 (transient-define-argument magit-log:--*-order ()
640 :description "Order commits by"
641 :class 'transient-switches
642 :key "-o"
643 :argument-format "--%s-order"
644 :argument-regexp "\\(--\\(topo\\|author-date\\|date\\)-order\\)"
645 :choices '("topo" "author-date" "date"))
646
647 (transient-define-argument magit-log:--grep ()
648 :description "Search messages"
649 :class 'transient-option
650 :key "-F"
651 :argument "--grep=")
652
653 (transient-define-argument magit-log:-G ()
654 :description "Search changes"
655 :class 'transient-option
656 :argument "-G")
657
658 (transient-define-argument magit-log:-S ()
659 :description "Search occurrences"
660 :class 'transient-option
661 :argument "-S")
662
663 (transient-define-argument magit-log:-L ()
664 :description "Trace line evolution"
665 :class 'transient-option
666 :argument "-L"
667 :reader #'magit-read-file-trace)
668
669 (defun magit-read-file-trace (&rest _ignored)
670 (let ((file (magit-read-file-from-rev "HEAD" "File"))
671 (trace (magit-read-string "Trace")))
672 (concat trace ":" file)))
673
674 ;;;; Setup Commands
675
676 (defvar-keymap magit-log-read-revs-map
677 :parent crm-local-completion-map
678 "SPC" #'self-insert-command)
679
680 (defun magit-log-read-revs (&optional use-current)
681 (or (and use-current (and$ (magit-get-current-branch) (list $)))
682 (let ((crm-separator "\\(\\.\\.\\.?\\|[, ]\\)")
683 (crm-local-completion-map magit-log-read-revs-map))
684 (split-string (magit-completing-read-multiple
685 "Log rev,s: "
686 (magit-list-refnames nil t)
687 nil 'any nil 'magit-revision-history
688 (or (magit-branch-or-commit-at-point)
689 (and (not use-current)
690 (magit-get-previous-branch)))
691 nil t)
692 "[, ]" t))))
693
694 (defun magit-log-read-pattern (option)
695 "Read a string from the user to pass as parameter to OPTION."
696 (magit-read-string (format "Type a pattern to pass to %s" option)))
697
698 ;;;###autoload(autoload 'magit-log-current "magit-log" nil t)
699 (transient-define-suffix magit-log-current (&optional args files)
700 "Show log for the current branch, or `HEAD' if no branch is checked out."
701 :description (##if (magit-get-current-branch) "current" "HEAD")
702 (interactive (magit-log-arguments))
703 (magit-log-setup-buffer (list (or (magit-get-current-branch) "HEAD"))
704 args files))
705
706 ;;;###autoload
707 (defun magit-log-head (&optional args files)
708 "Show log for `HEAD'."
709 (interactive (magit-log-arguments))
710 (magit-log-setup-buffer (list "HEAD") args files))
711
712 ;;;###autoload
713 (defun magit-log-related (revs &optional args files)
714 "Show log for the current branch, its upstream and its push target.
715 When the upstream is a local branch, then also show its own
716 upstream. When `HEAD' is detached, then show log for that, the
717 previously checked out branch and its upstream and push-target."
718 (interactive
719 (cons (let ((current (magit-get-current-branch))
720 head rebase target upstream upup)
721 (unless current
722 (setq rebase (magit-rebase--get-state-lines "head-name"))
723 (cond (rebase
724 (setq rebase (magit-ref-abbrev rebase))
725 (setq current rebase)
726 (setq head "HEAD"))
727 ((setq current (magit-get-previous-branch)))))
728 (cond (current
729 (setq current
730 (magit--propertize-face current 'magit-branch-local))
731 (setq target (magit-get-push-branch current t))
732 (setq upstream (magit-get-upstream-branch current))
733 (when upstream
734 (setq upup (and (magit-local-branch-p upstream)
735 (magit-get-upstream-branch upstream)))))
736 ((setq head "HEAD")))
737 (delq nil (list current head target upstream upup)))
738 (magit-log-arguments)))
739 (magit-log-setup-buffer revs args files))
740
741 ;;;###autoload
742 (defun magit-log-other (revs &optional args files)
743 "Show log for one or more revs read from the minibuffer.
744 The user can input any revision or revisions separated by a
745 space, or even ranges, but only branches and tags, and a
746 representation of the commit at point, are available as
747 completion candidates."
748 (interactive (cons (magit-log-read-revs)
749 (magit-log-arguments)))
750 (magit-log-setup-buffer revs args files))
751
752 ;;;###autoload
753 (defun magit-log-branches (&optional args files)
754 "Show log for all local branches and `HEAD'."
755 (interactive (magit-log-arguments))
756 (magit-log-setup-buffer (if (magit-get-current-branch)
757 (list "--branches")
758 (list "HEAD" "--branches"))
759 args files))
760
761 ;;;###autoload
762 (defun magit-log-matching-branches (pattern &optional args files)
763 "Show log for all branches matching PATTERN and `HEAD'."
764 (interactive (cons (magit-log-read-pattern "--branches")
765 (magit-log-arguments)))
766 (magit-log-setup-buffer (list "HEAD" (format "--branches=%s" pattern))
767 args files))
768
769 ;;;###autoload
770 (defun magit-log-matching-tags (pattern &optional args files)
771 "Show log for all tags matching PATTERN and `HEAD'."
772 (interactive (cons (magit-log-read-pattern "--tags")
773 (magit-log-arguments)))
774 (magit-log-setup-buffer (list "HEAD" (format "--tags=%s" pattern))
775 args files))
776
777 ;;;###autoload
778 (defun magit-log-all-branches (&optional args files)
779 "Show log for all local and remote branches and `HEAD'."
780 (interactive (magit-log-arguments))
781 (magit-log-setup-buffer (if (magit-get-current-branch)
782 (list "--branches" "--remotes")
783 (list "HEAD" "--branches" "--remotes"))
784 args files))
785
786 ;;;###autoload
787 (defun magit-log-all (&optional args files)
788 "Show log for all references and `HEAD'."
789 (interactive (magit-log-arguments))
790 (magit-log-setup-buffer (list "--all") args files))
791
792 ;;;###autoload
793 (defun magit-log-reflog (&optional args files)
794 "Show log for all objects mentioned in all reflogs."
795 (interactive (magit-log-arguments))
796 (magit-log-setup-buffer (list "--reflog") args files))
797
798 ;;;###autoload
799 (defun magit-log-buffer-file (&optional follow beg end)
800 "Show log for the blob or file visited in the current buffer.
801 With a prefix argument or when \"--follow\" is an active log
802 argument, then follow renames. When the region is active,
803 restrict the log to the lines that the region touches."
804 (interactive (cons current-prefix-arg (magit-file-region-line-numbers)))
805 (require 'magit)
806 (if-let ((file (magit-file-relative-name)))
807 (magit-log-setup-buffer
808 (list (or magit-buffer-revision
809 (magit-get-current-branch)
810 "HEAD"))
811 (let ((args (car (magit-log-arguments))))
812 (when (and follow (not (member "--follow" args)))
813 (push "--follow" args))
814 (when (and beg end)
815 (setq args (cons (format "-L%s,%s:%s" beg end file)
816 (cl-delete "-L" args :test
817 #'string-prefix-p)))
818 (setq file nil))
819 args)
820 (and file (list file))
821 magit-log-buffer-file-locked)
822 (user-error "Buffer isn't visiting a file")))
823
824 ;;;###autoload
825 (defun magit-log-trace-definition (file fn commit)
826 "Show log for the definition at point."
827 (interactive (list (or (magit-file-relative-name)
828 (user-error "Buffer isn't visiting a file"))
829 (or (funcall magit-log-trace-definition-function)
830 (user-error "No function at point found"))
831 (or magit-buffer-revision
832 (magit-get-current-branch)
833 "HEAD")))
834 (require 'magit)
835 (magit-log-setup-buffer
836 (list commit)
837 (cons (format "-L:%s%s:%s"
838 (string-replace ":" "\\:" (regexp-quote fn))
839 (if (derived-mode-p 'lisp-mode 'emacs-lisp-mode)
840 ;; Git doesn't treat "-" the same way as
841 ;; "_", leading to false-positives such as
842 ;; "foo-suffix" being considered a match
843 ;; for "foo". Wing it.
844 "\\( \\|$\\)"
845 ;; We could use "\\b" here, but since Git
846 ;; already does something equivalent, that
847 ;; isn't necessary.
848 "")
849 file)
850 (cl-delete "-L" (car (magit-log-arguments))
851 :test #'string-prefix-p))
852 nil magit-log-buffer-file-locked))
853
854 (defun magit-diff-trace-definition ()
855 "Show log for the definition at point in a diff."
856 (interactive)
857 (pcase-let ((`(,buf ,pos) (magit-diff-visit-file--noselect)))
858 (magit--with-temp-position buf pos
859 (call-interactively #'magit-log-trace-definition))))
860
861 ;;;###autoload
862 (defun magit-log-merged (commit branch &optional args files)
863 "Show log for the merge of COMMIT into BRANCH.
864
865 More precisely, find merge commit M that brought COMMIT into
866 BRANCH, and show the log of the range \"M^1..M\". If COMMIT is
867 directly on BRANCH, then show approximately
868 `magit-log-merged-commit-count' surrounding commits instead.
869
870 This command requires git-when-merged, which is available from
871 https://github.com/mhagger/git-when-merged."
872 (interactive
873 (append (let ((commit (magit-read-branch-or-commit "Log merge of commit")))
874 (list commit
875 (magit-read-other-branch "Merged into" commit)))
876 (magit-log-arguments)))
877 (unless (magit-git-executable-find "git-when-merged")
878 (user-error "This command requires git-when-merged (%s)"
879 "https://github.com/mhagger/git-when-merged"))
880 (let (exit m)
881 (with-temp-buffer
882 (save-excursion
883 (setq exit (magit-process-git t "when-merged" "-c"
884 (magit-abbrev-arg)
885 commit branch)))
886 (setq m (buffer-substring-no-properties (point) (line-end-position))))
887 (if (zerop exit)
888 (magit-log-setup-buffer (list (format "%s^1..%s" m m))
889 args files nil commit)
890 ;; Output: "<ref><lots of spaces><message>".
891 ;; This is not the same as `string-trim'.
892 (setq m (string-trim-left (substring m (string-match " " m))))
893 (if (equal m "Commit is directly on this branch.")
894 (let* ((from (format "%s~%d" commit
895 (/ magit-log-merged-commit-count 2)))
896 (to (- (car (magit-rev-diff-count branch commit t))
897 (/ magit-log-merged-commit-count 2)))
898 (to (if (<= to 0)
899 branch
900 (format "%s~%s" branch to))))
901 (unless (magit-commit-p from)
902 (setq from (magit-git-string "rev-list" "--max-parents=0"
903 commit)))
904 (magit-log-setup-buffer (list (concat from ".." to))
905 (cons "--first-parent" args)
906 files nil commit))
907 (user-error "Could not find when %s was merged into %s: %s"
908 commit branch m)))))
909
910 ;;;###autoload
911 (defun magit-delete-shelved-branch (branch)
912 "Delete the shelved BRANCH.
913 Delete a ref created by `magit-branch-shelve'."
914 (interactive (list (magit-read-shelved-branch "Delete shelved branch")))
915 (magit-run-git "update-ref" "-d" (concat "refs/shelved/" branch)))
916
917 ;;;; Limit Commands
918
919 (defun magit-log-toggle-commit-limit ()
920 "Toggle the number of commits the current log buffer is limited to.
921 If the number of commits is currently limited, then remove that
922 limit. Otherwise set it to 256."
923 (interactive)
924 (magit-log-set-commit-limit (lambda (&rest _) nil)))
925
926 (defun magit-log-double-commit-limit ()
927 "Double the number of commits the current log buffer is limited to."
928 (interactive)
929 (magit-log-set-commit-limit '*))
930
931 (defun magit-log-half-commit-limit ()
932 "Half the number of commits the current log buffer is limited to."
933 (interactive)
934 (magit-log-set-commit-limit '/))
935
936 (defun magit-log-set-commit-limit (fn)
937 (let* ((val magit-buffer-log-args)
938 (arg (seq-find (##string-match "^-n\\([0-9]+\\)?$" %) val))
939 (num (and arg (string-to-number (match-str 1 arg))))
940 (num (if num (funcall fn num 2) 256)))
941 (setq val (remove arg val))
942 (setq magit-buffer-log-args
943 (if (and num (> num 0))
944 (cons (format "-n%d" num) val)
945 val)))
946 (magit-refresh))
947
948 (defun magit-log-get-commit-limit (&optional args)
949 (and$ (seq-find (##string-match "^-n\\([0-9]+\\)?$" %)
950 (or args magit-buffer-log-args))
951 (string-to-number (match-str 1 $))))
952
953 ;;;; Mode Commands
954
955 (defun magit-log-bury-buffer (&optional arg)
956 "Bury the current buffer or the revision buffer in the same frame.
957 Like `magit-mode-bury-buffer' (which see) but with a negative
958 prefix argument instead bury the revision buffer, provided it
959 is displayed in the current frame."
960 (interactive "p")
961 (cond-let*
962 ((>= arg 0)
963 (magit-mode-bury-buffer (> arg 1)))
964 ([buf (magit-get-mode-buffer 'magit-revision-mode)]
965 [win (get-buffer-window buf (selected-frame))]
966 (with-selected-window win
967 (with-current-buffer buf
968 (magit-mode-bury-buffer (> (abs arg) 1)))))
969 ((user-error "No revision buffer in this frame"))))
970
971 ;;;###autoload
972 (defun magit-log-move-to-parent (&optional n)
973 "Move to the Nth parent of the current commit."
974 (interactive "p")
975 (when (and (derived-mode-p 'magit-log-mode)
976 (magit-section-match 'commit))
977 (let* ((section (magit-current-section))
978 (parent-rev (format "%s^%s" (oref section value) (or n 1))))
979 (if-let ((parent-hash (magit-rev-parse "--short" parent-rev)))
980 (if-let ((parent (seq-find (##equal (oref % value) parent-hash)
981 (magit-section-siblings section 'next))))
982 (magit-section-goto parent)
983 (user-error
984 (substitute-command-keys
985 (concat "Parent " parent-hash " not found. Try typing "
986 "\\[magit-log-double-commit-limit] first"))))
987 (user-error "Parent %s does not exist" parent-rev)))))
988
989 (defun magit-log-move-to-revision (commit)
990 "Read a commit and move to it in current log buffer.
991
992 If the chosen reference or commit isn't being displayed in
993 the current log buffer, then inform the user about that and do
994 nothing else.
995
996 If invoked outside any log buffer, then display the log buffer
997 of the current repository first; creating it if necessary."
998 (interactive
999 (list (magit-completing-read
1000 "In log, jump to"
1001 (magit-list-refnames nil t)
1002 nil 'any nil 'magit-revision-history
1003 (or (and$ (magit-commit-at-point)
1004 (magit-rev-fixup-target $))
1005 (magit-get-current-branch)))))
1006 (with-current-buffer
1007 (cond ((derived-mode-p 'magit-log-mode)
1008 (current-buffer))
1009 ((and$ (magit-get-mode-buffer 'magit-log-mode)
1010 (pop-to-buffer-same-window $)))
1011 ((apply #'magit-log-all-branches (magit-log-arguments))))
1012 (unless (magit-log-goto-commit-section (magit-rev-abbrev commit))
1013 (user-error "%s isn't visible in the current log buffer" commit))))
1014
1015 ;;;; Shortlog Commands
1016
1017 ;;;###autoload(autoload 'magit-shortlog "magit-log" nil t)
1018 (transient-define-prefix magit-shortlog ()
1019 "Show a history summary."
1020 :man-page "git-shortlog"
1021 :value '("--numbered" "--summary")
1022 ["Arguments"
1023 ("-n" "Sort by number of commits" ("-n" "--numbered"))
1024 ("-s" "Show commit count summary only" ("-s" "--summary"))
1025 ("-e" "Show email addresses" ("-e" "--email"))
1026 ("-g" "Group commits by" "--group="
1027 :choices ("author" "committer" "trailer:"))
1028 (7 "-f" "Format string" "--format=")
1029 (7 "-w" "Linewrap" "-w" :class transient-option)]
1030 ["Shortlog"
1031 ("s" "since" magit-shortlog-since)
1032 ("r" "range" magit-shortlog-range)])
1033
1034 (defun magit-git-shortlog (rev args)
1035 (let ((dir default-directory))
1036 (with-current-buffer (get-buffer-create "*magit-shortlog*")
1037 (setq default-directory dir)
1038 (setq buffer-read-only t)
1039 (let ((inhibit-read-only t))
1040 (erase-buffer)
1041 (save-excursion
1042 (magit-git-insert "shortlog" args rev))
1043 (switch-to-buffer-other-window (current-buffer))))))
1044
1045 ;;;###autoload
1046 (defun magit-shortlog-since (commit args)
1047 "Show a history summary for commits since REV."
1048 (interactive
1049 (list (magit-read-branch-or-commit "Shortlog since" (magit-get-current-tag))
1050 (transient-args 'magit-shortlog)))
1051 (magit-git-shortlog (concat commit "..") args))
1052
1053 ;;;###autoload
1054 (defun magit-shortlog-range (rev-or-range args)
1055 "Show a history summary for commit or range REV-OR-RANGE."
1056 (interactive
1057 (list (magit-read-range-or-commit "Shortlog for revision or range")
1058 (transient-args 'magit-shortlog)))
1059 (magit-git-shortlog rev-or-range args))
1060
1061 ;;;; Movement Commands
1062
1063 (defvar magit-reference-movement-faces
1064 '(magit-tag
1065 magit-branch-remote
1066 magit-branch-remote-head
1067 magit-branch-local
1068 magit-branch-current
1069 magit-branch-upstream
1070 magit-branch-warning
1071 magit-head
1072 magit-refname
1073 magit-refname-stash
1074 magit-refname-wip
1075 magit-refname-pullreq))
1076
1077 (defvar-keymap magit-reference-navigation-repeat-map
1078 :repeat t
1079 "p" #'magit-previous-reference
1080 "n" #'magit-next-reference
1081 "r" #'magit-next-reference)
1082
1083 (defun magit-previous-reference ()
1084 "Move to the previous Git reference appearing in the current buffer.
1085
1086 Move to the previous location that uses a face appearing in
1087 `magit-reference-movement-faces'. If `repeat-mode' is enabled,
1088 this command and its counterpart can be repeated using \
1089 \\<magit-reference-navigation-repeat-map>\
1090 \\[magit-previous-reference] and \\[magit-next-reference]."
1091 (interactive)
1092 (magit-next-reference t))
1093
1094 (defun magit-next-reference (&optional previous)
1095 "Move to the next Git reference appearing in the current buffer.
1096
1097 Move to the next location that uses a face appearing in
1098 `magit-reference-movement-faces'. If `repeat-mode' is enabled,
1099 this command and its counterpart can be repeated using \
1100 \\<magit-reference-navigation-repeat-map>\
1101 \\[magit-previous-reference] and \\[magit-next-reference]."
1102 (interactive)
1103 (catch 'found
1104 (let ((pos (point)))
1105 (while (and (not (eobp))
1106 (setq pos (if previous
1107 (previous-single-property-change pos 'face)
1108 (next-single-property-change pos 'face))))
1109 (when (cl-intersection (ensure-list (get-text-property pos 'face))
1110 magit-reference-movement-faces)
1111 (throw 'found (goto-char pos))))
1112 (message "No more references"))))
1113
1114 ;;; Log Mode
1115
1116 (defvar magit-log-disable-graph-hack-args
1117 '("-G" "--grep" "--author")
1118 "Arguments which disable the graph speedup hack.")
1119
1120 (defvar-keymap magit-log-mode-map
1121 :doc "Keymap for `magit-log-mode'."
1122 :parent magit-mode-map
1123 "C-c C-b" #'magit-go-backward
1124 "C-c C-f" #'magit-go-forward
1125 "C-c C-n" #'magit-log-move-to-parent
1126 "j" #'magit-log-move-to-revision
1127 "=" #'magit-log-toggle-commit-limit
1128 "+" #'magit-log-double-commit-limit
1129 "-" #'magit-log-half-commit-limit
1130 "q" #'magit-log-bury-buffer)
1131
1132 (define-derived-mode magit-log-mode magit-mode "Magit Log"
1133 "Mode for looking at Git log.
1134
1135 This mode is documented in info node `(magit)Log Buffer'.
1136
1137 \\<magit-mode-map>\
1138 Type \\[magit-refresh] to refresh the current buffer.
1139 Type \\[magit-visit-thing] or \\[magit-diff-show-or-scroll-up] \
1140 to visit the commit at point.
1141
1142 Type \\[magit-branch] to see available branch commands.
1143 Type \\[magit-merge] to merge the branch or commit at point.
1144 Type \\[magit-cherry-pick] to apply the commit at point.
1145 Type \\[magit-reset] to reset `HEAD' to the commit at point.
1146
1147 \\{magit-log-mode-map}"
1148 :interactive nil
1149 :group 'magit-log
1150 (magit-hack-dir-local-variables)
1151 (setq magit--imenu-item-types 'commit))
1152
1153 (put 'magit-log-mode 'magit-log-default-arguments
1154 '("--graph" "-n256" "--decorate"))
1155
1156 (defun magit-log-setup-buffer (revs args files &optional locked focus)
1157 (require 'magit)
1158 (with-current-buffer
1159 (magit-setup-buffer #'magit-log-mode locked
1160 (magit-buffer-log-revisions revs)
1161 (magit-buffer-log-args args)
1162 (magit-buffer-log-files files))
1163 (when (if focus
1164 (magit-log-goto-commit-section focus)
1165 (magit-log-goto-same-commit))
1166 (magit-section-update-highlight))
1167 (current-buffer)))
1168
1169 (defun magit-log-refresh-buffer ()
1170 (let ((revs magit-buffer-log-revisions)
1171 (args magit-buffer-log-args)
1172 (files magit-buffer-log-files)
1173 (limit (magit-log-get-commit-limit)))
1174 (magit-set-header-line-format
1175 (funcall magit-log-header-line-function revs args files))
1176 (unless (length= files 1)
1177 (setq args (remove "--follow" args)))
1178 (when (and (car magit-log-remove-graph-args)
1179 (let ((re (concat "^" (regexp-opt magit-log-remove-graph-args))))
1180 (seq-some (##string-match-p re %) args)))
1181 (setq args (remove "--graph" args)))
1182 (setq args (magit-log--maybe-drop-color-graph args limit))
1183 (when-let* ((limit limit)
1184 (limit (* 2 limit)) ; increase odds for complete graph
1185 (count (and (length= revs 1)
1186 (> limit 1024) ; otherwise it's fast enough
1187 (setq revs (car revs))
1188 (not (string-search ".." revs))
1189 (not (member revs '("--all" "--branches")))
1190 (not (seq-some
1191 (lambda (arg)
1192 (seq-some (##string-prefix-p % arg)
1193 magit-log-disable-graph-hack-args))
1194 args))
1195 (magit-git-string "rev-list" "--count"
1196 "--first-parent" args revs))))
1197 (setq revs (if (< (string-to-number count) limit)
1198 revs
1199 (format "%s~%s..%s" revs limit revs))))
1200 (let ((delay (cl-find-if (##member % '("++header" "--patch" "--stat"))
1201 args)))
1202 (setq magit-section-inhibit-markers (if delay 'delay t))
1203 (setq magit-section-insert-in-reverse (not delay)))
1204 (magit-insert-section (logbuf)
1205 (magit--insert-log t revs args files))))
1206
1207 (defvar-local magit-log--color-graph nil)
1208
1209 (defun magit-log--maybe-drop-color-graph (args limit)
1210 (cond ((not (member "--color" args))
1211 (setq magit-log--color-graph nil))
1212 ((cond ((not (member "--graph" args)) t)
1213 ((not magit-log-color-graph-limit) nil)
1214 ((not limit)
1215 (message "Dropping --color because -n isn't set (see %s)"
1216 'magit-log-color-graph-limit))
1217 ((> limit magit-log-color-graph-limit)
1218 (message "Dropping --color because -n is larger than %s"
1219 'magit-log-color-graph-limit)))
1220 (setq args (remove "--color" args))
1221 (setq magit-log--color-graph nil))
1222 ((setq magit-log--color-graph t)))
1223 args)
1224
1225 (cl-defmethod magit-buffer-value (&context (major-mode magit-log-mode))
1226 (append magit-buffer-log-revisions
1227 (if (and magit-buffer-log-revisions magit-buffer-log-files)
1228 (cons "--" magit-buffer-log-files)
1229 magit-buffer-log-files)))
1230
1231 (defun magit-log-header-line-arguments (revs args files)
1232 "Return string describing some of the used arguments."
1233 (mapconcat (##if (string-search " " %) (prin1 %) %)
1234 `("git" "log" ,@args ,@revs "--" ,@files)
1235 " "))
1236
1237 (defun magit-log-header-line-sentence (revs args files)
1238 "Return string containing all arguments."
1239 (concat "Commits in "
1240 (string-join revs " ")
1241 (and (member "--reverse" args)
1242 " in reverse")
1243 (and files (concat " touching "
1244 (string-join files " ")))
1245 (seq-some (##and (string-prefix-p "-L" %)
1246 (concat " " %))
1247 args)))
1248
1249 (defun magit-insert-log (revs &optional args files)
1250 (declare (obsolete magit--insert-log "Magit 4.0.0"))
1251 (magit--insert-log nil revs args files))
1252
1253 (defconst magit-log-heading-format
1254 ;; See `magit-log-heading-re'.
1255 (concat "--format="
1256 "%s" ; 4 graph --graph
1257 "%%h%%x0c" ; 1 %h hash
1258 "%s%%x0c" ; 3 %D refs --decorate
1259 "%s%%x0c" ; 7 %G? gpg --show-signature
1260 "%%aN%%x0c" ; 5 %aN author
1261 "%s%%x0c" ; 6 %at date magit-log-margin-show-committer-date
1262 "%s%%x0c" ; 12 %() trailers magit-log-trailer-labels
1263 "%%s" ; 2 %s msg
1264 "%s")) ; \n .. headers magit-log-revision-headers-format
1265
1266 (defun magit--insert-log (keep-error revs &optional args files)
1267 "Insert a log section.
1268 Do not add this to a hook variable."
1269 (declare (indent defun))
1270 (setq magit-section-preserve-visibility t) ; TODO do it here?
1271 (let ((magit-git-global-arguments
1272 (remove "--literal-pathspecs" magit-git-global-arguments)))
1273 (magit--git-wash (apply-partially #'magit-log-wash-log 'log) keep-error
1274 "log"
1275 (format magit-log-heading-format
1276 (if (and (member "--left-right" args)
1277 (not (member "--graph" args)))
1278 "%m "
1279 "")
1280 (if (member "--decorate" args) "%D" "")
1281 (if (not (member "--show-signature" args))
1282 ""
1283 (setq args (remove "--show-signature" args))
1284 (let ((limit (magit-log-get-commit-limit args)))
1285 (cond
1286 ((not limit)
1287 (message
1288 "Dropping --show-signature because -n isn't set (see %s)"
1289 'magit-log-show-signatures-limit)
1290 "")
1291 ((> limit magit-log-show-signatures-limit)
1292 (message
1293 "Dropping --show-signature because -n is larger than %s"
1294 'magit-log-show-signatures-limit)
1295 "")
1296 ("%G?"))))
1297 (if magit-log-margin-show-committer-date "%ct" "%at")
1298 (if magit-log-trailer-labels
1299 (format "%%(trailers:%s%s)"
1300 (if (not (equal (cdr magit-log-trailer-labels) ""))
1301 (concat (cdr magit-log-trailer-labels) ",")
1302 "")
1303 "unfold=true,separator=,key_value_separator=")
1304 "")
1305 (if (member "++header" args)
1306 (if (member "--graph" (setq args (remove "++header" args)))
1307 (concat "\n" magit-log-revision-headers-format "\n")
1308 (concat "\n" magit-log-revision-headers-format "\n"))
1309 ""))
1310 (progn
1311 (when-let ((order (seq-find (##string-match "^\\+\\+order=\\(.+\\)$" %)
1312 args)))
1313 (setq args (cons (format "--%s-order" (match-str 1 order))
1314 (remove order args))))
1315 (when (member "--decorate" args)
1316 (setq args (cons "--decorate=full" (remove "--decorate" args))))
1317 (when (member "--reverse" args)
1318 (setq args (remove "--graph" args)))
1319 (setq args (magit-diff--maybe-add-stat-arguments args))
1320 args)
1321 "--use-mailmap" "--no-prefix" revs "--" files)))
1322
1323 (cl-defmethod magit-menu-common-value ((_section magit-commit-section))
1324 (or (magit-diff--region-range)
1325 (oref (magit-current-section) value)))
1326
1327 (defvar-keymap magit-commit-section-map
1328 :doc "Keymap for `commit' sections."
1329 "<remap> <magit-visit-thing>" #'magit-show-commit
1330 "<3>" (magit-menu-item "Apply %x" #'magit-cherry-apply)
1331 "<2>" (magit-menu-item "Show commit %x" #'magit-show-commit
1332 '(:visible (not (region-active-p))))
1333 "<1>" (magit-menu-item "Diff %x" #'magit-diff-range
1334 '(:visible (region-active-p))))
1335
1336 (defvar-keymap magit-module-commit-section-map
1337 :doc "Keymap for `module-commit' sections."
1338 :parent magit-commit-section-map)
1339
1340 (defconst magit-log-heading-re
1341 ;; Use a form feed instead of a null byte as the delimiter because using
1342 ;; the latter interferes with the graph prefix when ++header is used.
1343 (concat "^"
1344 "\\(?4:[-_/|\\*o<>. ]*\\)" ; graph
1345 "\\(?1:[0-9a-fA-F]+\\)? " ; hash
1346 "\\(?3:[^ \n]+\\)? " ; refs
1347 "\\(?7:[BGUXYREN]\\)? " ; gpg
1348 "\\(?5:[^ \n]*\\) " ; author
1349 ;; Prior to Git v2.19.0, "git rebase -i --root" corrupted the
1350 ;; root's author date. Keep date optional because even though
1351 ;; we no longer support such old releases, the roots they create
1352 ;; may live on.
1353 "\\(?6:[^ \n]*\\) " ; date
1354 "\\(?12:[^ \n]+\\)? " ; trailers
1355 "\\(?2:.*\\)$")) ; msg
1356
1357 (defconst magit-log-cherry-re
1358 (concat "^"
1359 "\\(?8:[-+]\\) " ; cherry
1360 "\\(?1:[0-9a-fA-F]+\\) " ; hash
1361 "\\(?2:.*\\)$")) ; msg
1362
1363 (defconst magit-log-module-re
1364 (concat "^"
1365 "\\(?:\\(?11:[<>]\\) \\)?" ; side
1366 "\\(?1:[0-9a-fA-F]+\\) " ; hash
1367 "\\(?2:.*\\)$")) ; msg
1368
1369 (defconst magit-log-bisect-vis-re
1370 (concat "^"
1371 "\\(?4:[-_/|\\*o<>. ]*\\)" ; graph
1372 "\\(?1:[0-9a-fA-F]+\\)?\0" ; hash
1373 "\\(?3:[^\0\n]+\\)?\0" ; refs
1374 "\\(?2:.*\\)$")) ; msg
1375
1376 (defconst magit-log-bisect-log-re
1377 (concat "^# "
1378 "\\(?3:[^: \n]+:\\) " ; "refs"
1379 "\\[\\(?1:[^]\n]+\\)\\] " ; hash
1380 "\\(?2:.*\\)$")) ; msg
1381
1382 (defconst magit-log-reflog-re
1383 (concat "^"
1384 "\\(?1:[^\0\n]+\\)\0" ; hash
1385 "\\(?5:[^\0\n]*\\)\0" ; author
1386 "\\(?:\\(?:[^@\n]+@{\\(?6:[^}\n]+\\)}\0" ; date
1387 ;;; refsub
1388 "\\(?10:merge \\|autosave \\|restart \\|rewritten \\|[^:\n]+: \\)?"
1389 "\\(?2:.*\\)\\)\\|\0\\)$")) ; msg
1390
1391 (defconst magit-reflog-subject-re
1392 (concat "\\(?1:[^ ]+\\) ?" ; command
1393 "\\(?2:\\(?: ?-[^ ]+\\)+\\)?" ; option
1394 "\\(?: ?(\\(?3:[^)]+\\))\\)?")) ; type
1395
1396 (defconst magit-log-stash-re
1397 (concat "^"
1398 "\\(?1:[^\0\n]+\\)\0" ; "hash"
1399 "\\(?5:[^\0\n]*\\)\0" ; author
1400 "\\(?6:[^\0\n]+\\)\0" ; date
1401 "\\(?2:.*\\)$")) ; msg
1402
1403 (defvar magit-log-count nil)
1404
1405 (defun magit-log-wash-log (style args)
1406 (setq args (flatten-tree args))
1407 (when (if (derived-mode-p 'magit-log-mode)
1408 magit-log--color-graph
1409 (and (member "--graph" args)
1410 (member "--color" args)))
1411 (let ((ansi-color-apply-face-function
1412 (lambda (beg end face)
1413 (put-text-property beg end 'font-lock-face
1414 (or face 'magit-log-graph)))))
1415 (ansi-color-apply-on-region (point-min) (point-max))))
1416 (when (eq style 'cherry)
1417 (reverse-region (point-min) (point-max)))
1418 (let ((magit-log-count 0))
1419 (when (looking-at "^\\.\\.\\.")
1420 (magit-delete-line))
1421 (magit-wash-sequence (apply-partially #'magit-log-wash-rev style
1422 (magit-abbrev-length)))
1423 (if (derived-mode-p 'magit-log-mode 'magit-reflog-mode)
1424 (when (eq magit-log-count (magit-log-get-commit-limit))
1425 (magit-insert-section (longer)
1426 (insert-text-button
1427 (substitute-command-keys
1428 (format "Type \\<%s>\\[%s] to show more history"
1429 'magit-log-mode-map
1430 'magit-log-double-commit-limit))
1431 'action (lambda (_button)
1432 (magit-log-double-commit-limit))
1433 'follow-link t
1434 'mouse-face 'magit-section-highlight)))
1435 (insert ?\n))))
1436
1437 (cl-defun magit-log-wash-rev (style abbrev)
1438 (when (derived-mode-p 'magit-log-mode 'magit-reflog-mode)
1439 (incf magit-log-count))
1440 (looking-at (pcase style
1441 ('log magit-log-heading-re)
1442 ('cherry magit-log-cherry-re)
1443 ('module magit-log-module-re)
1444 ('reflog magit-log-reflog-re)
1445 ('stash magit-log-stash-re)
1446 ('bisect-vis magit-log-bisect-vis-re)
1447 ('bisect-log magit-log-bisect-log-re)))
1448 (let* ((hash (match-str 1))
1449 (msg (match-str 2))
1450 (refs (match-str 3))
1451 (refs (and refs (magit-format-ref-labels refs)))
1452 (graph (match-string 4))
1453 (author (match-str 5))
1454 (date (match-str 6))
1455 (gpg (match-str 7))
1456 (cherry (match-str 8))
1457 (refsub (match-str 10))
1458 (side (match-str 11))
1459 (trailers (match-str 12))
1460 (trailers (and trailers
1461 (funcall (car magit-log-trailer-labels)
1462 (mapcar (##split-string % "")
1463 (split-string trailers "")))))
1464 (align (or (eq style 'cherry)
1465 (not (member "--stat" magit-buffer-log-args))))
1466 (non-graph-re (if (eq style 'bisect-vis)
1467 magit-log-bisect-vis-re
1468 magit-log-heading-re)))
1469 (magit-delete-line)
1470 ;; If the reflog entries have been pruned, the output of `git
1471 ;; reflog show' includes a partial line that refers to the hash
1472 ;; of the youngest expired reflog entry.
1473 (when (and (eq style 'reflog) (not date))
1474 (cl-return-from magit-log-wash-rev t))
1475 (magit-insert-section
1476 ((eval (pcase style
1477 ('stash 'stash)
1478 ('module 'module-commit)
1479 (_ 'commit)))
1480 hash)
1481 (setq hash (propertize (if (eq style 'bisect-log)
1482 (magit-rev-parse "--short" hash)
1483 hash)
1484 'font-lock-face
1485 (pcase (and gpg (aref gpg 0))
1486 (?G 'magit-signature-good)
1487 (?B 'magit-signature-bad)
1488 (?U 'magit-signature-untrusted)
1489 (?X 'magit-signature-expired)
1490 (?Y 'magit-signature-expired-key)
1491 (?R 'magit-signature-revoked)
1492 (?E 'magit-signature-error)
1493 (?N 'magit-hash)
1494 (_ 'magit-hash))))
1495 (when cherry
1496 (when (and (derived-mode-p 'magit-refs-mode)
1497 magit-refs-show-commit-count)
1498 (insert (make-string (1- magit-refs-focus-column-width) ?\s)))
1499 (insert (propertize cherry 'font-lock-face
1500 (if (string= cherry "-")
1501 'magit-cherry-equivalent
1502 'magit-cherry-unmatched)))
1503 (insert ?\s))
1504 (when side
1505 (insert (propertize side 'font-lock-face
1506 (if (string= side "<")
1507 'magit-cherry-equivalent
1508 'magit-cherry-unmatched)))
1509 (insert ?\s))
1510 (when align
1511 (insert hash ?\s))
1512 (when graph
1513 (insert graph))
1514 (unless align
1515 (insert hash ?\s))
1516 (unless magit-log-show-refname-after-summary
1517 (when refs
1518 (insert refs ?\s))
1519 (when trailers
1520 (insert trailers ?\s)))
1521 (when (eq style 'reflog)
1522 (insert (format "%-2s " (1- magit-log-count)))
1523 (when refsub
1524 (insert (magit-reflog-format-subject
1525 (substring refsub 0
1526 (if (string-search ":" refsub) -2 -1))))))
1527 (insert (magit-log--wash-summary msg))
1528 (when magit-log-show-refname-after-summary
1529 (when refs
1530 (insert ?\s refs))
1531 (when trailers
1532 (insert ?\s trailers)))
1533 (insert ?\n)
1534 (when (memq style '(log reflog stash))
1535 (goto-char (line-beginning-position))
1536 (when (and refsub
1537 (string-match "\\`\\([^ ]\\) \\+\\(..\\)\\(..\\)" date))
1538 (setq date (+ (string-to-number (match-str 1 date))
1539 (* (string-to-number (match-str 2 date)) 60 60)
1540 (* (string-to-number (match-str 3 date)) 60))))
1541 (magit-log-format-margin hash author date))
1542 (when (and (eq style 'cherry)
1543 (magit--right-margin-active))
1544 (apply #'magit-log-format-margin hash
1545 (split-string (magit-rev-format "%aN%x00%ct" hash) "\0")))
1546 (when (and graph
1547 (not (eobp))
1548 (not (looking-at non-graph-re)))
1549 (when (looking-at "")
1550 (magit-insert-heading)
1551 (delete-char 1)
1552 (magit-insert-section (commit-header)
1553 (forward-line)
1554 (magit-insert-heading)
1555 (re-search-forward "")
1556 (delete-char -1)
1557 (forward-char)
1558 (insert ?\n))
1559 (delete-char 1))
1560 (if (looking-at "^\\(---\\|\n\s\\|\ndiff\\)")
1561 (let ((limit (save-excursion
1562 (and (re-search-forward non-graph-re nil t)
1563 (match-beginning 0)))))
1564 (unless (oref magit-insert-section--current content)
1565 (magit-insert-heading))
1566 (delete-char (if (looking-at "\n") 1 4))
1567 (magit-diff-wash-diffs (list "--stat") limit))
1568 (when align
1569 (setq align (make-string (1+ abbrev) ?\s)))
1570 (when (and (not (eobp)) (not (looking-at non-graph-re)))
1571 (when align
1572 (setq align (make-string (1+ abbrev) ?\s)))
1573 (while (and (not (eobp)) (not (looking-at non-graph-re)))
1574 (when align
1575 (save-excursion (insert align)))
1576 (forward-line)
1577 (magit-make-margin-overlay))
1578 ;; When "--format" is used and its value isn't one of the
1579 ;; predefined formats, then "git log" does not insert a
1580 ;; separator line.
1581 (save-excursion
1582 (forward-line -1)
1583 (looking-at "[-_/|\\*o<>. ]*"))
1584 (setq graph (match-string 0))
1585 (unless (string-match-p "[/\\.]" graph)
1586 (insert graph ?\n)))))))
1587 t)
1588
1589 (defun magit-log--wash-summary (summary)
1590 (with-temp-buffer
1591 (save-excursion (insert summary))
1592 (run-hook-wrapped 'magit-log-wash-summary-hook
1593 (lambda (fn) (prog1 nil (save-excursion (funcall fn)))))
1594 (buffer-string)))
1595
1596 (defun magit-log-maybe-show-more-commits (section)
1597 "When point is at the end of a log buffer, insert more commits.
1598
1599 Log buffers end with a button \"Type + to show more history\".
1600 When the use of a section movement command puts point on that
1601 button, then automatically show more commits, without the user
1602 having to press \"+\".
1603
1604 This function is called by `magit-section-movement-hook' and
1605 exists mostly for backward compatibility reasons."
1606 (when (and (eq (oref section type) 'longer)
1607 magit-log-auto-more)
1608 (magit-log-double-commit-limit)
1609 (forward-line -1)
1610 (magit-section-forward)))
1611
1612 (add-hook 'magit-section-movement-hook #'magit-log-maybe-show-more-commits)
1613 (add-hook 'magit-mouse-set-point-hook #'magit-log-maybe-show-more-commits)
1614
1615 (defvar magit--update-revision-buffer nil)
1616
1617 (defun magit-log-maybe-update-revision-buffer (&optional _)
1618 "When moving in a log or cherry buffer, update the revision buffer.
1619 If there is no revision buffer in the same frame, then do nothing.
1620 See also info node `(magit)Section Movement'."
1621 (when (derived-mode-p 'magit-log-mode 'magit-cherry-mode 'magit-reflog-mode)
1622 (magit--maybe-update-revision-buffer)))
1623
1624 (add-hook 'magit-section-movement-hook #'magit-log-maybe-update-revision-buffer)
1625 (add-hook 'magit-mouse-set-point-hook #'magit-log-maybe-update-revision-buffer)
1626
1627 (defun magit--maybe-update-revision-buffer ()
1628 (when-let* ((commit (magit-section-value-if 'commit))
1629 (buffer (magit-get-mode-buffer 'magit-revision-mode nil t)))
1630 (if magit--update-revision-buffer
1631 (setq magit--update-revision-buffer (list commit buffer))
1632 (setq magit--update-revision-buffer (list commit buffer))
1633 (run-with-idle-timer
1634 magit-update-other-window-delay nil
1635 (let ((args (let ((magit-direct-use-buffer-arguments 'selected))
1636 (magit-show-commit--arguments))))
1637 (lambda ()
1638 (pcase-let ((`(,rev ,buf) magit--update-revision-buffer))
1639 (setq magit--update-revision-buffer nil)
1640 (when (buffer-live-p buf)
1641 (let ((magit-display-buffer-noselect t))
1642 (apply #'magit-show-commit rev args))))
1643 (setq magit--update-revision-buffer nil)))))))
1644
1645 (defvar magit--update-blob-buffer nil)
1646
1647 (defun magit-log-maybe-update-blob-buffer (&optional _)
1648 "When moving in a log or cherry buffer, update the blob buffer.
1649 If there is no blob buffer in the same frame, then do nothing.
1650 See also info node `(magit)Section Movement'."
1651 (when (derived-mode-p 'magit-log-mode 'magit-cherry-mode 'magit-reflog-mode)
1652 (magit--maybe-update-blob-buffer)))
1653
1654 (defun magit--maybe-update-blob-buffer ()
1655 (when-let* ((commit (magit-section-value-if 'commit))
1656 (buffer (seq-find (##with-current-buffer %
1657 (eq revert-buffer-function
1658 'magit-revert-rev-file-buffer))
1659 (mapcar #'window-buffer (window-list)))))
1660 (if magit--update-blob-buffer
1661 (setq magit--update-blob-buffer (list commit buffer))
1662 (setq magit--update-blob-buffer (list commit buffer))
1663 (run-with-idle-timer
1664 magit-update-other-window-delay nil
1665 (lambda ()
1666 (pcase-let ((`(,rev ,buf) magit--update-blob-buffer))
1667 (setq magit--update-blob-buffer nil)
1668 (when (buffer-live-p buf)
1669 (with-selected-window (get-buffer-window buf)
1670 (with-current-buffer buf
1671 (save-excursion
1672 (magit-blob-visit (magit-rev-parse rev)
1673 (magit-file-relative-name
1674 magit-buffer-file-name))))))))))))
1675
1676 (defun magit-log-goto-commit-section (rev)
1677 (let ((abbrev (magit-rev-format "%h" rev)))
1678 (when-let ((section (seq-find (##equal (oref % value) abbrev)
1679 (oref magit-root-section children))))
1680 (goto-char (oref section start)))))
1681
1682 (defun magit-log-goto-same-commit ()
1683 (when (and magit-previous-section
1684 (magit-section-match '(commit branch)
1685 magit-previous-section))
1686 (magit-log-goto-commit-section (oref magit-previous-section value))))
1687
1688 ;;; Log Margin
1689
1690 (defvar-local magit-log-margin-show-shortstat nil)
1691
1692 (transient-define-suffix magit-toggle-log-margin-style ()
1693 "Toggle between the regular and the shortstat margin style.
1694 The shortstat style is experimental and rather slow."
1695 :description "Toggle shortstat"
1696 :key "x"
1697 :transient t
1698 (interactive)
1699 (setq magit-log-margin-show-shortstat
1700 (not magit-log-margin-show-shortstat))
1701 (magit-set-buffer-margins nil t))
1702
1703 (defun magit-log-format-margin (rev author date)
1704 (when (magit--right-margin-option)
1705 (if magit-log-margin-show-shortstat
1706 (magit-log-format-shortstat-margin rev)
1707 (magit-log-format-author-margin author date))))
1708
1709 (defun magit-log-format-author-margin (author date)
1710 (pcase-let ((`(,_ ,style ,width ,details ,details-width)
1711 (or magit--right-margin-config
1712 (symbol-value (magit--right-margin-option))
1713 (error "No margin format specified for %s" major-mode))))
1714 (magit-make-margin-overlay
1715 (concat (and details
1716 (concat (magit--propertize-face
1717 (truncate-string-to-width
1718 (or author "")
1719 details-width
1720 nil ?\s
1721 (magit--ellipsis 'margin))
1722 'magit-log-author)
1723 " "))
1724 (magit--propertize-face
1725 (if (stringp style)
1726 (format-time-string
1727 style
1728 (seconds-to-time (string-to-number date)))
1729 (pcase-let* ((abbr (eq style 'age-abbreviated))
1730 (`(,cnt ,unit) (magit--age date abbr)))
1731 (format (format (if abbr "%%2d%%-%dc" "%%2d %%-%ds")
1732 (- width (if details (1+ details-width) 0)))
1733 cnt unit)))
1734 'magit-log-date)))))
1735
1736 (defun magit-log-format-shortstat-margin (rev)
1737 (magit-make-margin-overlay
1738 (if-let ((line (and rev (magit-git-string
1739 "show" "--format=" "--shortstat" rev))))
1740 (if (string-match "\
1741 \\([0-9]+\\) files? changed, \
1742 \\(?:\\([0-9]+\\) insertions?(\\+)\\)?\
1743 \\(?:\\(?:, \\)?\\([0-9]+\\) deletions?(-)\\)?\\'" line)
1744 (magit-bind-match-strings (files add del) line
1745 (format
1746 "%5s %5s%4s"
1747 (if add
1748 (magit--propertize-face (format "%s+" add)
1749 'magit-diffstat-added)
1750 "")
1751 (if del
1752 (magit--propertize-face (format "%s-" del)
1753 'magit-diffstat-removed)
1754 "")
1755 files))
1756 "")
1757 "")))
1758
1759 (defun magit-log-margin-width (style details details-width)
1760 (if magit-log-margin-show-shortstat
1761 16
1762 (+ (if details (1+ details-width) 0)
1763 (if (stringp style)
1764 (length (format-time-string style))
1765 (+ 2 ; two digits
1766 1 ; trailing space
1767 (if (eq style 'age-abbreviated)
1768 1 ; single character
1769 (+ 1 ; gap after digits
1770 (apply #'max (mapcar (##max (length (nth 1 %))
1771 (length (nth 2 %)))
1772 magit--age-spec)))))))))
1773
1774 ;;; Select Mode
1775
1776 (defvar-keymap magit-log-select-mode-map
1777 :doc "Keymap for `magit-log-select-mode'."
1778 :parent magit-log-mode-map
1779 "C-c C-b" #'undefined
1780 "C-c C-f" #'undefined
1781 "." #'magit-log-select-pick
1782 "e" #'magit-log-select-pick
1783 "C-c C-c" #'magit-log-select-pick
1784 "q" #'magit-log-select-quit
1785 "C-c C-k" #'magit-log-select-quit)
1786 (put 'magit-log-select-pick :advertised-binding [?\C-c ?\C-c])
1787 (put 'magit-log-select-quit :advertised-binding [?\C-c ?\C-k])
1788
1789 (define-derived-mode magit-log-select-mode magit-log-mode "Magit Select"
1790 "Mode for selecting a commit from history.
1791
1792 This mode is documented in info node `(magit)Select from Log'.
1793
1794 \\<magit-mode-map>\
1795 Type \\[magit-refresh] to refresh the current buffer.
1796 Type \\[magit-visit-thing] or \\[magit-diff-show-or-scroll-up] \
1797 to visit the commit at point.
1798
1799 \\<magit-log-select-mode-map>\
1800 Type \\[magit-log-select-pick] to select the commit at point.
1801 Type \\[magit-log-select-quit] to abort without selecting a commit."
1802 :group 'magit-log
1803 (magit-hack-dir-local-variables))
1804
1805 (put 'magit-log-select-mode 'magit-log-default-arguments
1806 '("--graph" "-n256" "--decorate"))
1807
1808 (defun magit-log-select-setup-buffer (revs args select-section)
1809 (magit-setup-buffer #'magit-log-select-mode nil
1810 :select-section select-section
1811 (magit-buffer-log-revisions revs)
1812 (magit-buffer-log-args args)))
1813
1814 (defun magit-log-select-refresh-buffer ()
1815 (setq magit-section-inhibit-markers t)
1816 (setq magit-section-insert-in-reverse t)
1817 (magit-insert-section (logbuf)
1818 (magit--insert-log t magit-buffer-log-revisions
1819 (magit-log--maybe-drop-color-graph
1820 magit-buffer-log-args
1821 (magit-log-get-commit-limit)))))
1822
1823 (cl-defmethod magit-buffer-value (&context (major-mode magit-log-select-mode))
1824 magit-buffer-log-revisions)
1825
1826 (defvar-local magit-log-select-pick-function nil)
1827 (defvar-local magit-log-select-quit-function nil)
1828
1829 (defun magit-log-select (pick &optional msg quit branch args initial)
1830 (declare (indent defun))
1831 (unless initial
1832 (setq initial (magit-commit-at-point)))
1833 (magit-log-select-setup-buffer
1834 (or branch (magit-get-current-branch) "HEAD")
1835 (append args
1836 (car (magit-log--get-value 'magit-log-select-mode 'direct)))
1837 (lambda ()
1838 (if initial
1839 (magit-log-goto-commit-section initial)
1840 (while-let* ((rev (magit-section-value-if 'commit))
1841 (_(string-match-p "\\`\\(squash!\\|fixup!\\|amend!\\)"
1842 (magit-rev-format "%s" rev)))
1843 (section (magit-current-section))
1844 (next (car (magit-section-siblings section 'next))))
1845 (magit-section-goto next)))))
1846 (setq magit-log-select-pick-function pick)
1847 (setq magit-log-select-quit-function quit)
1848 (when magit-log-select-show-usage
1849 (let ((pick (propertize (substitute-command-keys
1850 "\\[magit-log-select-pick]")
1851 'font-lock-face
1852 'magit-header-line-key))
1853 (quit (propertize (substitute-command-keys
1854 "\\[magit-log-select-quit]")
1855 'font-lock-face
1856 'magit-header-line-key)))
1857 (setq msg (format-spec
1858 (if msg
1859 (if (string-suffix-p "," msg)
1860 (concat msg " or %q to abort")
1861 msg)
1862 "Type %p to select commit at point, or %q to abort")
1863 `((?p . ,pick)
1864 (?q . ,quit)))))
1865 (magit--add-face-text-property
1866 0 (length msg) 'magit-header-line-log-select t msg)
1867 (when (memq magit-log-select-show-usage '(both header-line))
1868 (magit-set-header-line-format msg))
1869 (when (memq magit-log-select-show-usage '(both echo-area))
1870 (message "%s" (substring-no-properties msg)))))
1871
1872 (defun magit-log-select-pick ()
1873 "Select the commit at point and act on it.
1874 Call `magit-log-select-pick-function' with the selected
1875 commit as argument."
1876 (interactive)
1877 (let ((fun magit-log-select-pick-function)
1878 (rev (magit-commit-at-point)))
1879 (magit-mode-bury-buffer 'kill)
1880 (funcall fun rev)))
1881
1882 (defun magit-log-select-quit ()
1883 "Abort selecting a commit, don't act on any commit.
1884 Call `magit-log-select-quit-function' if set."
1885 (interactive)
1886 (let ((fun magit-log-select-quit-function))
1887 (magit-mode-bury-buffer 'kill)
1888 (when fun (funcall fun))))
1889
1890 ;;; Cherry Mode
1891
1892 (defvar-keymap magit-cherry-mode-map
1893 :doc "Keymap for `magit-cherry-mode'."
1894 :parent magit-mode-map
1895 "q" #'magit-log-bury-buffer
1896 "L" #'magit-margin-settings)
1897
1898 (define-derived-mode magit-cherry-mode magit-mode "Magit Cherry"
1899 "Mode for looking at commits not merged upstream.
1900
1901 \\<magit-mode-map>\
1902 Type \\[magit-refresh] to refresh the current buffer.
1903 Type \\[magit-visit-thing] or \\[magit-diff-show-or-scroll-up] \
1904 to visit the commit at point.
1905
1906 Type \\[magit-cherry-pick] to apply the commit at point.
1907
1908 \\{magit-cherry-mode-map}"
1909 :interactive nil
1910 :group 'magit-log
1911 (magit-hack-dir-local-variables)
1912 (setq magit--imenu-group-types 'cherries))
1913
1914 (defvar-local magit-buffer-cherry-upstream nil)
1915 (defvar-local magit-buffer-cherry-range nil)
1916
1917 (defun magit-cherry-setup-buffer (head upstream)
1918 (magit-setup-buffer #'magit-cherry-mode nil
1919 (magit-buffer-refname head)
1920 (magit-buffer-cherry-upstream upstream)
1921 (magit-buffer-cherry-range (concat upstream ".." head))))
1922
1923 (defun magit-cherry-refresh-buffer ()
1924 (setq magit-section-insert-in-reverse t)
1925 (magit-insert-section (cherry)
1926 (magit-run-section-hook 'magit-cherry-sections-hook)))
1927
1928 (cl-defmethod magit-buffer-value (&context (major-mode magit-cherry-mode))
1929 magit-buffer-cherry-range)
1930
1931 ;;;###autoload
1932 (defun magit-cherry (head upstream)
1933 "Show commits in a branch that are not merged in the upstream branch."
1934 (interactive
1935 (let ((head (magit-read-branch "Cherry head")))
1936 (list head (magit-read-other-branch "Cherry upstream" head
1937 (magit-get-upstream-branch head)))))
1938 (require 'magit)
1939 (magit-cherry-setup-buffer head upstream))
1940
1941 (defun magit-insert-cherry-headers ()
1942 "Insert headers appropriate for `magit-cherry-mode' buffers."
1943 (let ((branch (magit-branch-set-face magit-buffer-refname))
1944 (upstream (magit-branch-set-face magit-buffer-cherry-upstream)))
1945 (magit-insert-head-branch-header branch)
1946 (magit-insert-upstream-branch-header branch upstream "Upstream: ")
1947 (insert ?\n)))
1948
1949 (defun magit-insert-cherry-commits ()
1950 "Insert commit sections into a `magit-cherry-mode' buffer."
1951 (magit-insert-section (cherries)
1952 (magit-insert-heading t "Cherry commits")
1953 (magit-git-wash (apply-partially #'magit-log-wash-log 'cherry)
1954 "cherry" "-v" "--abbrev"
1955 magit-buffer-cherry-upstream
1956 magit-buffer-refname)))
1957
1958 ;;; Log Sections
1959 ;;;; Standard Log Sections
1960
1961 (defvar-keymap magit-log-section-map
1962 :doc "Keymap for log sections.
1963 The classes `magit-{unpulled,unpushed,unmerged}-section' derive
1964 from the abstract `magit-log-section' class. Accordingly this
1965 keymap is the parent of their keymaps."
1966 "<remap> <magit-visit-thing>" #'magit-diff-dwim
1967 "<1>" (magit-menu-item "Visit diff" #'magit-diff-dwim))
1968
1969 (cl-defmethod magit-section-ident-value ((section magit-unpulled-section))
1970 "Return \"..@{push}\".
1971 \"..@{push}\" cannot be used as the value because that is ambiguous
1972 if `push.default' does not allow a 1:1 mapping, and many commands
1973 would fail because of that. But here that does not matter and we
1974 need an unique value, so we use that string in the pushremote case."
1975 (let ((value (oref section value)))
1976 (if (equal value "..@{upstream}") value "..@{push}")))
1977
1978 (magit-define-section-jumper magit-jump-to-unpulled-from-upstream
1979 "Unpulled from @{upstream}" unpulled "..@{upstream}"
1980 magit-insert-unpulled-from-upstream)
1981
1982 (defun magit-insert-unpulled-from-upstream ()
1983 "Insert commits that haven't been pulled from the upstream yet."
1984 (when-let ((upstream (magit-get-upstream-branch)))
1985 (magit-insert-section (unpulled "..@{upstream}" t)
1986 (magit-insert-heading
1987 (format (propertize "Unpulled from %s."
1988 'font-lock-face 'magit-section-heading)
1989 upstream))
1990 (magit--insert-log nil "..@{upstream}" magit-buffer-log-args)
1991 (magit-log-insert-child-count))))
1992
1993 (magit-define-section-jumper magit-jump-to-unpulled-from-pushremote
1994 "Unpulled from <push-remote>" unpulled "..@{push}"
1995 magit-insert-unpulled-from-pushremote)
1996
1997 (defun magit-insert-unpulled-from-pushremote ()
1998 "Insert commits that haven't been pulled from the push-remote yet."
1999 (when-let* ((target (magit-get-push-branch))
2000 (range (concat ".." target))
2001 (_(magit--insert-pushremote-log-p)))
2002 (magit-insert-section (unpulled range t)
2003 (magit-insert-heading
2004 (format (propertize "Unpulled from %s."
2005 'font-lock-face 'magit-section-heading)
2006 (propertize target 'font-lock-face 'magit-branch-remote)))
2007 (magit--insert-log nil range magit-buffer-log-args)
2008 (magit-log-insert-child-count))))
2009
2010 (cl-defmethod magit-section-ident-value ((section magit-unpushed-section))
2011 "Return \"..@{push}\".
2012 \"..@{push}\" cannot be used as the value because that is ambiguous
2013 if `push.default' does not allow a 1:1 mapping, and many commands
2014 would fail because of that. But here that does not matter and we
2015 need an unique value, so we use that string in the pushremote case."
2016 (let ((value (oref section value)))
2017 (if (equal value "@{upstream}..") value "@{push}..")))
2018
2019 (magit-define-section-jumper magit-jump-to-unpushed-to-upstream
2020 "Unpushed to @{upstream}" unpushed "@{upstream}.." nil
2021 :if (lambda ()
2022 (or (memq 'magit-insert-unpushed-to-upstream-or-recent
2023 magit-status-sections-hook)
2024 (memq 'magit-insert-unpushed-to-upstream
2025 magit-status-sections-hook)))
2026 :description (lambda ()
2027 (let ((upstream (magit-get-upstream-branch)))
2028 (if (or (not upstream)
2029 (magit-rev-ancestor-p "HEAD" upstream))
2030 "Recent commits"
2031 "Unmerged into upstream"))))
2032
2033 (defun magit-insert-unpushed-to-upstream-or-recent ()
2034 "Insert section showing unpushed or other recent commits.
2035 If an upstream is configured for the current branch and it is
2036 behind of the current branch, then show the commits that have
2037 not yet been pushed into the upstream branch. If no upstream is
2038 configured or if the upstream is not behind of the current branch,
2039 then show the last `magit-log-section-commit-count' commits."
2040 (let ((upstream (magit-get-upstream-branch)))
2041 (if (or (not upstream)
2042 (magit-rev-ancestor-p "HEAD" upstream))
2043 (magit-insert-recent-commits 'unpushed "@{upstream}..")
2044 (magit-insert-unpushed-to-upstream))))
2045
2046 (defun magit-insert-unpushed-to-upstream ()
2047 "Insert commits that haven't been pushed to the upstream yet."
2048 (when (magit-git-success "rev-parse" "@{upstream}")
2049 (magit-insert-section (unpushed "@{upstream}..")
2050 (magit-insert-heading
2051 (format (propertize "Unmerged into %s."
2052 'font-lock-face 'magit-section-heading)
2053 (magit-get-upstream-branch)))
2054 (magit--insert-log nil "@{upstream}.." magit-buffer-log-args)
2055 (magit-log-insert-child-count))))
2056
2057 (defun magit-insert-recent-commits (&optional type value)
2058 "Insert section showing recent commits.
2059 Show the last `magit-log-section-commit-count' commits."
2060 (let* ((start (format "HEAD~%s" magit-log-section-commit-count))
2061 (range (and (magit-rev-verify start)
2062 (concat start "..HEAD"))))
2063 (magit-insert-section ((eval (or type 'recent))
2064 (or value range)
2065 t)
2066 (magit-insert-heading "Recent commits")
2067 (magit--insert-log nil
2068 (and (member "--graph" magit-buffer-log-args) range)
2069 (cons (format "-n%d" magit-log-section-commit-count)
2070 (seq-remove (##string-prefix-p "-n" %)
2071 magit-buffer-log-args))))))
2072
2073 (magit-define-section-jumper magit-jump-to-unpushed-to-pushremote
2074 "Unpushed to <push-remote>" unpushed "@{push}.."
2075 magit-insert-unpushed-to-pushremote)
2076
2077 (defun magit-insert-unpushed-to-pushremote ()
2078 "Insert commits that haven't been pushed to the push-remote yet."
2079 (when-let* ((target (magit-get-push-branch))
2080 (range (concat target ".."))
2081 (_(magit--insert-pushremote-log-p)))
2082 (magit-insert-section (unpushed range t)
2083 (magit-insert-heading
2084 (format (propertize "Unpushed to %s."
2085 'font-lock-face 'magit-section-heading)
2086 (propertize target 'font-lock-face 'magit-branch-remote)))
2087 (magit--insert-log nil range magit-buffer-log-args)
2088 (magit-log-insert-child-count))))
2089
2090 (defun magit--insert-pushremote-log-p ()
2091 (magit--with-refresh-cache
2092 (cons default-directory 'magit--insert-pushremote-log-p)
2093 (not (and (equal (magit-get-push-branch)
2094 (magit-get-upstream-branch))
2095 (or (memq 'magit-insert-unpulled-from-upstream
2096 magit-status-sections-hook)
2097 (memq 'magit-insert-unpulled-from-upstream-or-recent
2098 magit-status-sections-hook))))))
2099
2100 (defun magit-log-insert-child-count ()
2101 (when magit-section-show-child-count
2102 (let ((count (length (oref magit-insert-section--current children))))
2103 (when (> count 0)
2104 (when (eq count (magit-log-get-commit-limit))
2105 (setq count (format "%s+" count)))
2106 (save-excursion
2107 (goto-char (- (oref magit-insert-section--current content) 2))
2108 (insert (format " (%s)" count))
2109 (delete-char 1))))))
2110
2111 ;;;; Auxiliary Log Sections
2112
2113 (defun magit-insert-unpulled-cherries ()
2114 "Insert section showing unpulled commits.
2115 Like `magit-insert-unpulled-from-upstream' but prefix each commit
2116 which has not been applied yet (i.e., a commit with a patch-id
2117 not shared with any local commit) with \"+\", and all others with
2118 \"-\"."
2119 (when (magit-git-success "rev-parse" "@{upstream}")
2120 (magit-insert-section (unpulled "..@{upstream}")
2121 (magit-insert-heading t "Unpulled commits")
2122 (magit-git-wash (apply-partially #'magit-log-wash-log 'cherry)
2123 "cherry" "-v" (magit-abbrev-arg)
2124 (magit-get-current-branch) "@{upstream}"))))
2125
2126 (defun magit-insert-unpushed-cherries ()
2127 "Insert section showing unpushed commits.
2128 Like `magit-insert-unpushed-to-upstream' but prefix each commit
2129 which has not been applied to upstream yet (i.e., a commit with
2130 a patch-id not shared with any upstream commit) with \"+\", and
2131 all others with \"-\"."
2132 (when (magit-git-success "rev-parse" "@{upstream}")
2133 (magit-insert-section (unpushed "@{upstream}..")
2134 (magit-insert-heading t "Unpushed commits")
2135 (magit-git-wash (apply-partially #'magit-log-wash-log 'cherry)
2136 "cherry" "-v" (magit-abbrev-arg) "@{upstream}"))))
2137
2138 ;;; _
2139 (provide 'magit-log)
2140 ;; Local Variables:
2141 ;; read-symbol-shorthands: (
2142 ;; ("and$" . "cond-let--and$")
2143 ;; ("thread$" . "cond-let--thread$")
2144 ;; ("when$" . "cond-let--when$")
2145 ;; ("and-let*" . "cond-let--and-let*")
2146 ;; ("and-let" . "cond-let--and-let")
2147 ;; ("if-let*" . "cond-let--if-let*")
2148 ;; ("if-let" . "cond-let--if-let")
2149 ;; ("when-let*" . "cond-let--when-let*")
2150 ;; ("when-let" . "cond-let--when-let")
2151 ;; ("while-let*" . "cond-let--while-let*")
2152 ;; ("while-let" . "cond-let--while-let")
2153 ;; ("match-string" . "match-string")
2154 ;; ("match-str" . "match-string-no-properties"))
2155 ;; End:
2156 ;;; magit-log.el ends here