dotemacs

My Emacs configuration
git clone git://git.entf.net/dotemacs
Log | Files | Refs | LICENSE

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