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