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