magit-extras.el (34629B)
1 ;;; magit-extras.el --- additional functionality for Magit -*- lexical-binding: t -*- 2 3 ;; Copyright (C) 2008-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 ;; Additional functionality for Magit. 29 30 ;;; Code: 31 32 (require 'magit) 33 34 (declare-function change-log-insert-entries "add-log" (changelogs)) 35 (declare-function diff-add-log-current-defuns "diff-mode" ()) 36 (declare-function dired-read-shell-command "dired-aux" (prompt arg files)) 37 ;; For `magit-project-status'. 38 (declare-function project-root "project" (project)) 39 (declare-function vc-git-command "vc-git" (buffer okstatus file-or-list &rest flags)) 40 41 (defvar ido-exit) 42 (defvar ido-fallback) 43 (defvar project-prefix-map) 44 (defvar project-switch-commands) 45 46 (defgroup magit-extras nil 47 "Additional functionality for Magit." 48 :group 'magit-extensions) 49 50 ;;; External Tools 51 52 (defcustom magit-gitk-executable 53 (or (and (eq system-type 'windows-nt) 54 (let ((exe (magit-git-string 55 "-c" "alias.X=!x() { which \"$1\" | cygpath -mf -; }; x" 56 "X" "gitk.exe"))) 57 (and exe (file-executable-p exe) exe))) 58 (executable-find "gitk") "gitk") 59 "The Gitk executable." 60 :group 'magit-extras 61 :set-after '(magit-git-executable) 62 :type 'string) 63 64 ;;;###autoload 65 (defun magit-run-git-gui () 66 "Run `git gui' for the current git repository." 67 (interactive) 68 (magit-with-toplevel (magit-process-git 0 "gui"))) 69 70 ;;;###autoload 71 (defun magit-run-git-gui-blame (commit filename &optional linenum) 72 "Run `git gui blame' on the given FILENAME and COMMIT. 73 Interactively run it for the current file and the `HEAD', with a 74 prefix or when the current file cannot be determined let the user 75 choose. When the current buffer is visiting FILENAME instruct 76 blame to center around the line point is on." 77 (interactive 78 (let (revision filename) 79 (when (or current-prefix-arg 80 (not (setq revision "HEAD" 81 filename (magit-file-relative-name nil 'tracked)))) 82 (setq revision (magit-read-branch-or-commit "Blame from revision")) 83 (setq filename (magit-read-file-from-rev revision "Blame file"))) 84 (list revision filename 85 (and (equal filename 86 (ignore-errors 87 (magit-file-relative-name buffer-file-name))) 88 (line-number-at-pos))))) 89 (magit-with-toplevel 90 (magit-process-git 0 "gui" "blame" 91 (and linenum (list (format "--line=%d" linenum))) 92 commit 93 filename))) 94 95 ;;;###autoload 96 (defun magit-run-gitk () 97 "Run `gitk' in the current repository." 98 (interactive) 99 (magit-process-file magit-gitk-executable nil 0)) 100 101 ;;;###autoload 102 (defun magit-run-gitk-branches () 103 "Run `gitk --branches' in the current repository." 104 (interactive) 105 (magit-process-file magit-gitk-executable nil 0 nil "--branches")) 106 107 ;;;###autoload 108 (defun magit-run-gitk-all () 109 "Run `gitk --all' in the current repository." 110 (interactive) 111 (magit-process-file magit-gitk-executable nil 0 nil "--all")) 112 113 ;;; Emacs Tools 114 115 ;;;###autoload 116 (defun ido-enter-magit-status () 117 "Drop into `magit-status' from file switching. 118 119 This command does not work in Emacs 26.1. 120 See https://github.com/magit/magit/issues/3634 121 and https://debbugs.gnu.org/cgi/bugreport.cgi?bug=31707. 122 123 To make this command available use something like: 124 125 (add-hook \\='ido-setup-hook 126 (lambda () 127 (define-key ido-completion-map 128 (kbd \"C-x g\") \\='ido-enter-magit-status))) 129 130 Starting with Emacs 25.1 the Ido keymaps are defined just once 131 instead of every time Ido is invoked, so now you can modify it 132 like pretty much every other keymap: 133 134 (define-key ido-common-completion-map 135 (kbd \"C-x g\") \\='ido-enter-magit-status)" 136 (interactive) 137 (setq ido-exit 'fallback) 138 (setq ido-fallback 'magit-status) ; for Emacs >= 26.2 139 (with-no-warnings (setq fallback 'magit-status)) ; for Emacs 25 140 (exit-minibuffer)) 141 142 ;;;###autoload 143 (defun magit-project-status () 144 "Run `magit-status' in the current project's root." 145 (interactive) 146 (magit-status-setup-buffer (project-root (project-current t)))) 147 148 (defvar magit-bind-magit-project-status t 149 "Whether to bind \"m\" to `magit-project-status' in `project-prefix-map'. 150 If so, then an entry is added to `project-switch-commands' as 151 well. If you want to use another key, then you must set this 152 to nil before loading Magit to prevent \"m\" from being bound.") 153 154 (with-eval-after-load 'project 155 ;; Only more recent versions of project.el have `project-prefix-map' and 156 ;; `project-switch-commands', though project.el is available in Emacs 25. 157 (when (and magit-bind-magit-project-status 158 (boundp 'project-prefix-map) 159 ;; Only modify if it hasn't already been modified. 160 (equal project-switch-commands 161 (eval (car (get 'project-switch-commands 'standard-value)) 162 t))) 163 (define-key project-prefix-map "m" #'magit-project-status) 164 (add-to-list 'project-switch-commands '(magit-project-status "Magit") t))) 165 166 ;;;###autoload 167 (defun magit-dired-jump (&optional other-window) 168 "Visit file at point using Dired. 169 With a prefix argument, visit in another window. If there 170 is no file at point, then instead visit `default-directory'." 171 (interactive "P") 172 (dired-jump other-window 173 (when-let ((file (magit-file-at-point))) 174 (expand-file-name (if (file-directory-p file) 175 (file-name-as-directory file) 176 file))))) 177 178 ;;;###autoload 179 (defun magit-dired-log (&optional follow) 180 "Show log for all marked files, or the current file." 181 (interactive "P") 182 (if-let ((topdir (magit-toplevel default-directory))) 183 (let ((args (car (magit-log-arguments))) 184 (files (dired-get-marked-files nil nil #'magit-file-tracked-p))) 185 (unless files 186 (user-error "No marked file is being tracked by Git")) 187 (when (and follow 188 (not (member "--follow" args)) 189 (not (cdr files))) 190 (push "--follow" args)) 191 (magit-log-setup-buffer 192 (list (or (magit-get-current-branch) "HEAD")) 193 args 194 (let ((default-directory topdir)) 195 (mapcar #'file-relative-name files)) 196 magit-log-buffer-file-locked)) 197 (magit--not-inside-repository-error))) 198 199 ;;;###autoload 200 (defun magit-dired-am-apply-patches (repo &optional arg) 201 "In Dired, apply the marked (or next ARG) files as patches. 202 If inside a repository, then apply in that. Otherwise prompt 203 for a repository." 204 (interactive (list (or (magit-toplevel) 205 (magit-read-repository t)) 206 current-prefix-arg)) 207 ;; Note: The ERROR argument of `dired-get-marked-files' isn't 208 ;; available until Emacs 27. 209 (let ((files (or (dired-get-marked-files nil arg) 210 (user-error "No files specified")))) 211 (magit-status-setup-buffer repo) 212 (magit-am-apply-patches files))) 213 214 ;;;###autoload 215 (defun magit-do-async-shell-command (file) 216 "Open FILE with `dired-do-async-shell-command'. 217 Interactively, open the file at point." 218 (interactive (list (or (magit-file-at-point) 219 (completing-read "Act on file: " 220 (magit-list-files))))) 221 (require 'dired-aux) 222 (dired-do-async-shell-command 223 (dired-read-shell-command "& on %s: " current-prefix-arg (list file)) 224 nil (list file))) 225 226 ;;; Shift Selection 227 228 (defun magit--turn-on-shift-select-mode-p () 229 (and shift-select-mode 230 this-command-keys-shift-translated 231 (not mark-active) 232 (not (eq (car-safe transient-mark-mode) 'only)))) 233 234 ;;;###autoload 235 (defun magit-previous-line (&optional arg try-vscroll) 236 "Like `previous-line' but with Magit-specific shift-selection. 237 238 Magit's selection mechanism is based on the region but selects an 239 area that is larger than the region. This causes `previous-line' 240 when invoked while holding the shift key to move up one line and 241 thereby select two lines. When invoked inside a hunk body this 242 command does not move point on the first invocation and thereby 243 it only selects a single line. Which inconsistency you prefer 244 is a matter of preference." 245 (declare (interactive-only 246 "use `forward-line' with negative argument instead.")) 247 (interactive "p\np") 248 (unless arg (setq arg 1)) 249 (let ((stay (or (magit-diff-inside-hunk-body-p) 250 (magit-section-position-in-heading-p)))) 251 (if (and stay (= arg 1) (magit--turn-on-shift-select-mode-p)) 252 (push-mark nil nil t) 253 (with-no-warnings 254 (handle-shift-selection) 255 (previous-line (if stay (max (1- arg) 1) arg) try-vscroll))))) 256 257 ;;;###autoload 258 (defun magit-next-line (&optional arg try-vscroll) 259 "Like `next-line' but with Magit-specific shift-selection. 260 261 Magit's selection mechanism is based on the region but selects 262 an area that is larger than the region. This causes `next-line' 263 when invoked while holding the shift key to move down one line 264 and thereby select two lines. When invoked inside a hunk body 265 this command does not move point on the first invocation and 266 thereby it only selects a single line. Which inconsistency you 267 prefer is a matter of preference." 268 (declare (interactive-only forward-line)) 269 (interactive "p\np") 270 (unless arg (setq arg 1)) 271 (let ((stay (or (magit-diff-inside-hunk-body-p) 272 (magit-section-position-in-heading-p)))) 273 (if (and stay (= arg 1) (magit--turn-on-shift-select-mode-p)) 274 (push-mark nil nil t) 275 (with-no-warnings 276 (handle-shift-selection) 277 (next-line (if stay (max (1- arg) 1) arg) try-vscroll))))) 278 279 ;;; Clean 280 281 ;;;###autoload 282 (defun magit-clean (&optional arg) 283 "Remove untracked files from the working tree. 284 With a prefix argument also remove ignored files, 285 with two prefix arguments remove ignored files only. 286 \n(git clean -f -d [-x|-X])" 287 (interactive "p") 288 (when (yes-or-no-p (format "Remove %s files? " 289 (pcase arg 290 (1 "untracked") 291 (4 "untracked and ignored") 292 (_ "ignored")))) 293 (magit-wip-commit-before-change) 294 (magit-run-git "clean" "-f" "-d" (pcase arg (4 "-x") (16 "-X"))))) 295 296 (put 'magit-clean 'disabled t) 297 298 ;;; ChangeLog 299 300 (defun magit-generate-changelog (&optional amending) 301 "Insert ChangeLog entries into the current buffer. 302 303 The entries are generated from the diff being committed. 304 If prefix argument, AMENDING, is non-nil, include changes 305 in HEAD as well as staged changes in the diff to check." 306 (interactive "P") 307 (unless (magit-commit-message-buffer) 308 (user-error "No commit in progress")) 309 (require 'diff-mode) ; `diff-add-log-current-defuns'. 310 (require 'vc-git) ; `vc-git-diff'. 311 (require 'add-log) ; `change-log-insert-entries'. 312 (unless (and (fboundp 'change-log-insert-entries) 313 (fboundp 'diff-add-log-current-defuns)) 314 (user-error "`magit-generate-changelog' requires Emacs 27 or better")) 315 (setq default-directory 316 (if (and (file-regular-p "gitdir") 317 (not (magit-git-true "rev-parse" "--is-inside-work-tree")) 318 (magit-git-true "rev-parse" "--is-inside-git-dir")) 319 (file-name-directory (magit-file-line "gitdir")) 320 (magit-toplevel))) 321 (let ((rev1 (if amending "HEAD^1" "HEAD")) 322 (rev2 nil)) 323 ;; Magit may have updated the files without notifying vc, but 324 ;; `diff-add-log-current-defuns' relies on vc being up-to-date. 325 (mapc #'vc-file-clearprops (magit-staged-files)) 326 (change-log-insert-entries 327 (with-temp-buffer 328 (vc-git-command (current-buffer) 1 nil 329 "diff-index" "--exit-code" "--patch" 330 (and (magit-anything-staged-p) "--cached") 331 rev1 "--") 332 ;; `diff-find-source-location' consults these vars. 333 (defvar diff-vc-revisions) 334 (setq-local diff-vc-revisions (list rev1 rev2)) 335 (setq-local diff-vc-backend 'Git) 336 (diff-add-log-current-defuns))))) 337 338 ;;;###autoload 339 (defun magit-add-change-log-entry (&optional whoami file-name other-window) 340 "Find change log file and add date entry and item for current change. 341 This differs from `add-change-log-entry' (which see) in that 342 it acts on the current hunk in a Magit buffer instead of on 343 a position in a file-visiting buffer." 344 (interactive (list current-prefix-arg 345 (prompt-for-change-log-name))) 346 (pcase-let ((`(,buf ,pos) (magit-diff-visit-file--noselect))) 347 (magit--with-temp-position buf pos 348 (let ((add-log-buffer-file-name-function 349 (lambda () 350 (or magit-buffer-file-name 351 (buffer-file-name))))) 352 (add-change-log-entry whoami file-name other-window))))) 353 354 ;;;###autoload 355 (defun magit-add-change-log-entry-other-window (&optional whoami file-name) 356 "Find change log file in other window and add entry and item. 357 This differs from `add-change-log-entry-other-window' (which see) 358 in that it acts on the current hunk in a Magit buffer instead of 359 on a position in a file-visiting buffer." 360 (interactive (and current-prefix-arg 361 (list current-prefix-arg 362 (prompt-for-change-log-name)))) 363 (magit-add-change-log-entry whoami file-name t)) 364 365 ;;; Edit Line Commit 366 367 ;;;###autoload 368 (defun magit-edit-line-commit (&optional type) 369 "Edit the commit that added the current line. 370 371 With a prefix argument edit the commit that removes the line, 372 if any. The commit is determined using `git blame' and made 373 editable using `git rebase --interactive' if it is reachable 374 from `HEAD', or by checking out the commit (or a branch that 375 points at it) otherwise." 376 (interactive (list (and current-prefix-arg 'removal))) 377 (let* ((chunk (magit-current-blame-chunk (or type 'addition))) 378 (rev (oref chunk orig-rev))) 379 (if (equal rev "0000000000000000000000000000000000000000") 380 (message "This line has not been committed yet") 381 (let ((rebase (magit-rev-ancestor-p rev "HEAD")) 382 (file (expand-file-name (oref chunk orig-file) 383 (magit-toplevel)))) 384 (if rebase 385 (let ((magit--rebase-published-symbol 'edit-published)) 386 (magit-rebase-edit-commit rev (magit-rebase-arguments))) 387 (magit-checkout (or (magit-rev-branch rev) rev))) 388 (unless (and buffer-file-name 389 (file-equal-p file buffer-file-name)) 390 (let ((blame-type (and magit-blame-mode magit-blame-type))) 391 (if rebase 392 (set-process-sentinel 393 magit-this-process 394 (lambda (process event) 395 (magit-sequencer-process-sentinel process event) 396 (when (eq (process-status process) 'exit) 397 (find-file file) 398 (when blame-type 399 (magit-blame--pre-blame-setup blame-type) 400 (magit-blame--run (magit-blame-arguments)))))) 401 (find-file file) 402 (when blame-type 403 (magit-blame--pre-blame-setup blame-type) 404 (magit-blame--run (magit-blame-arguments)))))))))) 405 406 (put 'magit-edit-line-commit 'disabled t) 407 408 ;;;###autoload 409 (defun magit-diff-edit-hunk-commit (file) 410 "From a hunk, edit the respective commit and visit the file. 411 412 First visit the file being modified by the hunk at the correct 413 location using `magit-diff-visit-file'. This actually visits a 414 blob. When point is on a diff header, not within an individual 415 hunk, then this visits the blob the first hunk is about. 416 417 Then invoke `magit-edit-line-commit', which uses an interactive 418 rebase to make the commit editable, or if that is not possible 419 because the commit is not reachable from `HEAD' by checking out 420 that commit directly. This also causes the actual worktree file 421 to be visited. 422 423 Neither the blob nor the file buffer are killed when finishing 424 the rebase. If that is undesirable, then it might be better to 425 use `magit-rebase-edit-command' instead of this command." 426 (interactive (list (magit-file-at-point t t))) 427 (let ((magit-diff-visit-previous-blob nil)) 428 (with-current-buffer 429 (magit-diff-visit-file--internal file nil #'pop-to-buffer-same-window) 430 (magit-edit-line-commit)))) 431 432 (put 'magit-diff-edit-hunk-commit 'disabled t) 433 434 ;;; Reshelve 435 436 (defcustom magit-reshelve-since-committer-only nil 437 "Whether `magit-reshelve-since' changes only the committer dates. 438 Otherwise the author dates are also changed." 439 :package-version '(magit . "3.0.0") 440 :group 'magit-commands 441 :type 'boolean) 442 443 ;;;###autoload 444 (defun magit-reshelve-since (rev keyid) 445 "Change the author and committer dates of the commits since REV. 446 447 Ask the user for the first reachable commit whose dates should 448 be changed. Then read the new date for that commit. The initial 449 minibuffer input and the previous history element offer good 450 values. The next commit will be created one minute later and so 451 on. 452 453 This command is only intended for interactive use and should only 454 be used on highly rearranged and unpublished history. 455 456 If KEYID is non-nil, then use that to sign all reshelved commits. 457 Interactively use the value of the \"--gpg-sign\" option in the 458 list returned by `magit-rebase-arguments'." 459 (interactive (list nil 460 (transient-arg-value "--gpg-sign=" 461 (magit-rebase-arguments)))) 462 (let* ((current (or (magit-get-current-branch) 463 (user-error "Refusing to reshelve detached head"))) 464 (backup (concat "refs/original/refs/heads/" current))) 465 (cond 466 ((not rev) 467 (when (and (magit-ref-p backup) 468 (not (magit-y-or-n-p 469 (format "Backup ref %s already exists. Override? " backup)))) 470 (user-error "Abort")) 471 (magit-log-select 472 (lambda (rev) 473 (magit-reshelve-since rev keyid)) 474 "Type %p on a commit to reshelve it and the commits above it,")) 475 (t 476 (cl-flet ((adjust (time offset) 477 (format-time-string 478 "%F %T %z" 479 (+ (floor time) 480 (* offset 60) 481 (- (car (decode-time time))))))) 482 (let* ((start (concat rev "^")) 483 (range (concat start ".." current)) 484 (time-rev (adjust (float-time (string-to-number 485 (magit-rev-format "%at" start))) 486 1)) 487 (time-now (adjust (float-time) 488 (- (string-to-number 489 (magit-git-string "rev-list" "--count" 490 range)))))) 491 (push time-rev magit--reshelve-history) 492 (let ((date (floor 493 (float-time 494 (date-to-time 495 (read-string "Date for first commit: " 496 time-now 'magit--reshelve-history))))) 497 (process-environment process-environment)) 498 (push "FILTER_BRANCH_SQUELCH_WARNING=1" process-environment) 499 (magit-with-toplevel 500 (magit-run-git-async 501 "filter-branch" "--force" "--env-filter" 502 (format 503 "case $GIT_COMMIT in %s\nesac" 504 (mapconcat 505 (lambda (rev) 506 (prog1 (concat 507 (format "%s) " rev) 508 (and (not magit-reshelve-since-committer-only) 509 (format "export GIT_AUTHOR_DATE=\"%s\"; " date)) 510 (format "export GIT_COMMITTER_DATE=\"%s\";;" date)) 511 (cl-incf date 60))) 512 (magit-git-lines "rev-list" "--reverse" range) 513 " ")) 514 (and keyid 515 (list "--commit-filter" 516 (format "git commit-tree --gpg-sign=%s \"$@\";" 517 keyid))) 518 range "--")) 519 (set-process-sentinel 520 magit-this-process 521 (lambda (process event) 522 (when (memq (process-status process) '(exit signal)) 523 (if (> (process-exit-status process) 0) 524 (magit-process-sentinel process event) 525 (process-put process 'inhibit-refresh t) 526 (magit-process-sentinel process event) 527 (magit-run-git "update-ref" "-d" backup)))))))))))) 528 529 ;;; Revision Stack 530 531 (defvar magit-revision-stack nil) 532 533 (defcustom magit-pop-revision-stack-format 534 '("[%N: %h] " 535 "%N: %cs %H\n %s\n" 536 "\\[\\([0-9]+\\)[]:]") 537 "Control how `magit-pop-revision-stack' inserts a revision. 538 539 The command `magit-pop-revision-stack' inserts a representation 540 of the revision last pushed to the `magit-revision-stack' into 541 the current buffer. It inserts text at point and/or near the end 542 of the buffer, and removes the consumed revision from the stack. 543 544 The entries on the stack have the format (HASH TOPLEVEL) and this 545 option has the format (POINT-FORMAT EOB-FORMAT INDEX-REGEXP), all 546 of which may be nil or a string (though either one of EOB-FORMAT 547 or POINT-FORMAT should be a string, and if INDEX-REGEXP is 548 non-nil, then the two formats should be too). 549 550 First INDEX-REGEXP is used to find the previously inserted entry, 551 by searching backward from point. The first submatch must match 552 the index number. That number is incremented by one, and becomes 553 the index number of the entry to be inserted. If you don't want 554 to number the inserted revisions, then use nil for INDEX-REGEXP. 555 556 If INDEX-REGEXP is non-nil, then both POINT-FORMAT and EOB-FORMAT 557 should contain \"%N\", which is replaced with the number that was 558 determined in the previous step. 559 560 Both formats, if non-nil and after removing %N, are then expanded 561 using `git show --format=FORMAT ...' inside TOPLEVEL. 562 563 The expansion of POINT-FORMAT is inserted at point, and the 564 expansion of EOB-FORMAT is inserted at the end of the buffer (if 565 the buffer ends with a comment, then it is inserted right before 566 that)." 567 :package-version '(magit . "3.2.0") 568 :group 'magit-commands 569 :type '(list (choice (string :tag "Insert at point format") 570 (cons (string :tag "Insert at point format") 571 (repeat (string :tag "Argument to git show"))) 572 (const :tag "Don't insert at point" nil)) 573 (choice (string :tag "Insert at eob format") 574 (cons (string :tag "Insert at eob format") 575 (repeat (string :tag "Argument to git show"))) 576 (const :tag "Don't insert at eob" nil)) 577 (choice (regexp :tag "Find index regexp") 578 (const :tag "Don't number entries" nil)))) 579 580 (defcustom magit-copy-revision-abbreviated nil 581 "Whether to save abbreviated revision to `kill-ring' and `magit-revision-stack'." 582 :package-version '(magit . "3.0.0") 583 :group 'magit-miscellaneous 584 :type 'boolean) 585 586 ;;;###autoload 587 (defun magit-pop-revision-stack (rev toplevel) 588 "Insert a representation of a revision into the current buffer. 589 590 Pop a revision from the `magit-revision-stack' and insert it into 591 the current buffer according to `magit-pop-revision-stack-format'. 592 Revisions can be put on the stack using `magit-copy-section-value' 593 and `magit-copy-buffer-revision'. 594 595 If the stack is empty or with a prefix argument, instead read a 596 revision in the minibuffer. By using the minibuffer history this 597 allows selecting an item which was popped earlier or to insert an 598 arbitrary reference or revision without first pushing it onto the 599 stack. 600 601 When reading the revision from the minibuffer, then it might not 602 be possible to guess the correct repository. When this command 603 is called inside a repository (e.g. while composing a commit 604 message), then that repository is used. Otherwise (e.g. while 605 composing an email) then the repository recorded for the top 606 element of the stack is used (even though we insert another 607 revision). If not called inside a repository and with an empty 608 stack, or with two prefix arguments, then read the repository in 609 the minibuffer too." 610 (interactive 611 (if (or current-prefix-arg (not magit-revision-stack)) 612 (let ((default-directory 613 (or (and (not (= (prefix-numeric-value current-prefix-arg) 16)) 614 (or (magit-toplevel) 615 (cadr (car magit-revision-stack)))) 616 (magit-read-repository)))) 617 (list (magit-read-branch-or-commit "Insert revision") 618 default-directory)) 619 (push (caar magit-revision-stack) magit-revision-history) 620 (pop magit-revision-stack))) 621 (if rev 622 (pcase-let ((`(,pnt-format ,eob-format ,idx-format) 623 magit-pop-revision-stack-format)) 624 (let ((default-directory toplevel) 625 (idx (and idx-format 626 (save-excursion 627 (if (re-search-backward idx-format nil t) 628 (number-to-string 629 (1+ (string-to-number (match-string 1)))) 630 "1")))) 631 pnt-args eob-args) 632 (when (listp pnt-format) 633 (setq pnt-args (cdr pnt-format)) 634 (setq pnt-format (car pnt-format))) 635 (when (listp eob-format) 636 (setq eob-args (cdr eob-format)) 637 (setq eob-format (car eob-format))) 638 (when pnt-format 639 (when idx-format 640 (setq pnt-format 641 (replace-regexp-in-string "%N" idx pnt-format t t))) 642 (magit-rev-insert-format pnt-format rev pnt-args) 643 (backward-delete-char 1)) 644 (when eob-format 645 (when idx-format 646 (setq eob-format 647 (replace-regexp-in-string "%N" idx eob-format t t))) 648 (save-excursion 649 (goto-char (point-max)) 650 (skip-syntax-backward ">s-") 651 (beginning-of-line) 652 (if (and comment-start (looking-at comment-start)) 653 (while (looking-at comment-start) 654 (forward-line -1)) 655 (forward-line) 656 (unless (= (current-column) 0) 657 (insert ?\n))) 658 (insert ?\n) 659 (magit-rev-insert-format eob-format rev eob-args) 660 (backward-delete-char 1))))) 661 (user-error "Revision stack is empty"))) 662 663 (define-key git-commit-mode-map 664 (kbd "C-c C-w") 'magit-pop-revision-stack) 665 666 ;;;###autoload 667 (defun magit-copy-section-value (arg) 668 "Save the value of the current section for later use. 669 670 Save the section value to the `kill-ring', and, provided that 671 the current section is a commit, branch, or tag section, push 672 the (referenced) revision to the `magit-revision-stack' for use 673 with `magit-pop-revision-stack'. 674 675 When `magit-copy-revision-abbreviated' is non-nil, save the 676 abbreviated revision to the `kill-ring' and the 677 `magit-revision-stack'. 678 679 When the current section is a branch or a tag, and a prefix 680 argument is used, then save the revision at its tip to the 681 `kill-ring' instead of the reference name. 682 683 When the region is active, then save that to the `kill-ring', 684 like `kill-ring-save' would, instead of behaving as described 685 above. If a prefix argument is used and the region is within 686 a hunk, then strip the diff marker column and keep only either 687 the added or removed lines, depending on the sign of the prefix 688 argument." 689 (interactive "P") 690 (cond 691 ((and arg 692 (magit-section-internal-region-p) 693 (magit-section-match 'hunk)) 694 (kill-new 695 (thread-last (buffer-substring-no-properties 696 (region-beginning) 697 (region-end)) 698 (replace-regexp-in-string 699 (format "^\\%c.*\n?" (if (< (prefix-numeric-value arg) 0) ?+ ?-)) 700 "") 701 (replace-regexp-in-string "^[ \\+\\-]" ""))) 702 (deactivate-mark)) 703 ((use-region-p) 704 (call-interactively #'copy-region-as-kill)) 705 (t 706 (when-let ((section (magit-current-section)) 707 (value (oref section value))) 708 (magit-section-case 709 ((branch commit module-commit tag) 710 (let ((default-directory default-directory) ref) 711 (magit-section-case 712 ((branch tag) 713 (setq ref value)) 714 (module-commit 715 (setq default-directory 716 (file-name-as-directory 717 (expand-file-name (magit-section-parent-value section) 718 (magit-toplevel)))))) 719 (setq value (magit-rev-parse 720 (and magit-copy-revision-abbreviated "--short") 721 value)) 722 (push (list value default-directory) magit-revision-stack) 723 (kill-new (message "%s" (or (and current-prefix-arg ref) 724 value))))) 725 (t (kill-new (message "%s" value)))))))) 726 727 ;;;###autoload 728 (defun magit-copy-buffer-revision () 729 "Save the revision of the current buffer for later use. 730 731 Save the revision shown in the current buffer to the `kill-ring' 732 and push it to the `magit-revision-stack'. 733 734 This command is mainly intended for use in `magit-revision-mode' 735 buffers, the only buffers where it is always unambiguous exactly 736 which revision should be saved. 737 738 Most other Magit buffers usually show more than one revision, in 739 some way or another, so this command has to select one of them, 740 and that choice might not always be the one you think would have 741 been the best pick. 742 743 In such buffers it is often more useful to save the value of 744 the current section instead, using `magit-copy-section-value'. 745 746 When the region is active, then save that to the `kill-ring', 747 like `kill-ring-save' would, instead of behaving as described 748 above. 749 750 When `magit-copy-revision-abbreviated' is non-nil, save the 751 abbreviated revision to the `kill-ring' and the 752 `magit-revision-stack'." 753 (interactive) 754 (if (use-region-p) 755 (call-interactively #'copy-region-as-kill) 756 (when-let ((rev (or magit-buffer-revision 757 (cl-case major-mode 758 (magit-diff-mode 759 (if (string-match "\\.\\.\\.?\\(.+\\)" 760 magit-buffer-range) 761 (match-string 1 magit-buffer-range) 762 magit-buffer-range)) 763 (magit-status-mode "HEAD"))))) 764 (when (magit-commit-p rev) 765 (setq rev (magit-rev-parse 766 (and magit-copy-revision-abbreviated "--short") 767 rev)) 768 (push (list rev default-directory) magit-revision-stack) 769 (kill-new (message "%s" rev)))))) 770 771 ;;; Buffer Switching 772 773 ;;;###autoload 774 (defun magit-display-repository-buffer (buffer) 775 "Display a Magit buffer belonging to the current Git repository. 776 The buffer is displayed using `magit-display-buffer', which see." 777 (interactive (list (magit--read-repository-buffer 778 "Display magit buffer: "))) 779 (magit-display-buffer buffer)) 780 781 ;;;###autoload 782 (defun magit-switch-to-repository-buffer (buffer) 783 "Switch to a Magit buffer belonging to the current Git repository." 784 (interactive (list (magit--read-repository-buffer 785 "Switch to magit buffer: "))) 786 (switch-to-buffer buffer)) 787 788 ;;;###autoload 789 (defun magit-switch-to-repository-buffer-other-window (buffer) 790 "Switch to a Magit buffer belonging to the current Git repository." 791 (interactive (list (magit--read-repository-buffer 792 "Switch to magit buffer in another window: "))) 793 (switch-to-buffer-other-window buffer)) 794 795 ;;;###autoload 796 (defun magit-switch-to-repository-buffer-other-frame (buffer) 797 "Switch to a Magit buffer belonging to the current Git repository." 798 (interactive (list (magit--read-repository-buffer 799 "Switch to magit buffer in another frame: "))) 800 (switch-to-buffer-other-frame buffer)) 801 802 (defun magit--read-repository-buffer (prompt) 803 (if-let ((topdir (magit-rev-parse-safe "--show-toplevel"))) 804 (read-buffer 805 prompt (magit-get-mode-buffer 'magit-status-mode) t 806 (pcase-lambda (`(,_ . ,buf)) 807 (and buf 808 (with-current-buffer buf 809 (and (or (derived-mode-p 'magit-mode 810 'magit-repolist-mode 811 'magit-submodule-list-mode 812 'git-rebase-mode) 813 (and buffer-file-name 814 (string-match-p git-commit-filename-regexp 815 buffer-file-name))) 816 (equal (magit-rev-parse-safe "--show-toplevel") 817 topdir)))))) 818 (user-error "Not inside a Git repository"))) 819 820 ;;; Miscellaneous 821 822 ;;;###autoload 823 (defun magit-abort-dwim () 824 "Abort current operation. 825 Depending on the context, this will abort a merge, a rebase, a 826 patch application, a cherry-pick, a revert, or a bisect." 827 (interactive) 828 (cond ((magit-merge-in-progress-p) (magit-merge-abort)) 829 ((magit-rebase-in-progress-p) (magit-rebase-abort)) 830 ((magit-am-in-progress-p) (magit-am-abort)) 831 ((magit-sequencer-in-progress-p) (magit-sequencer-abort)) 832 ((magit-bisect-in-progress-p) (magit-bisect-reset)))) 833 834 ;;; _ 835 (provide 'magit-extras) 836 ;;; magit-extras.el ends here