dotemacs

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

magit-patch.el (11933B)


      1 ;;; magit-patch.el --- creating and applying patches  -*- 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 ;; This library implements patch commands.
     29 
     30 ;;; Code:
     31 
     32 (require 'magit)
     33 
     34 ;;; Options
     35 
     36 (defcustom magit-patch-save-arguments '(exclude "--stat")
     37   "Control arguments used by the command `magit-patch-save'.
     38 
     39 `magit-patch-save' (which see) saves a diff for the changes
     40 shown in the current buffer in a patch file.  It may use the
     41 same arguments as used in the buffer or a subset thereof, or
     42 a constant list of arguments, depending on this option and
     43 the prefix argument."
     44   :package-version '(magit . "2.12.0")
     45   :group 'magit-diff
     46   :type '(choice (const :tag "use buffer arguments" buffer)
     47                  (cons :tag "use buffer arguments except"
     48                        (const :format "" exclude)
     49                        (repeat :format "%v%i\n"
     50                                (string :tag "Argument")))
     51                  (repeat :tag "use constant arguments"
     52                          (string :tag "Argument"))))
     53 
     54 ;;; Commands
     55 
     56 ;;;###autoload (autoload 'magit-patch "magit-patch" nil t)
     57 (transient-define-prefix magit-patch ()
     58   "Create or apply patches."
     59   ["Actions"
     60    [("c"  "Create patches"     magit-patch-create)
     61     ("w"  "Apply patches"      magit-am)]
     62    [("a"  "Apply plain patch"  magit-patch-apply)
     63     ("s"  "Save diff as patch" magit-patch-save)]
     64    [("r"  "Request pull"       magit-request-pull)]])
     65 
     66 ;;;###autoload (autoload 'magit-patch-create "magit-patch" nil t)
     67 (transient-define-prefix magit-patch-create (range args files)
     68   "Create patches for the commits in RANGE.
     69 When a single commit is given for RANGE, create a patch for the
     70 changes introduced by that commit (unlike 'git format-patch'
     71 which creates patches for all commits that are reachable from
     72 `HEAD' but not from the specified commit)."
     73   :man-page "git-format-patch"
     74   :incompatible '(("--subject-prefix=" "--rfc"))
     75   ["Mail arguments"
     76    (6 magit-format-patch:--in-reply-to)
     77    (6 magit-format-patch:--thread)
     78    (6 magit-format-patch:--from)
     79    (6 magit-format-patch:--to)
     80    (6 magit-format-patch:--cc)]
     81   ["Patch arguments"
     82    (magit-format-patch:--base)
     83    (magit-format-patch:--reroll-count)
     84    (5 magit-format-patch:--interdiff)
     85    (magit-format-patch:--range-diff)
     86    (magit-format-patch:--subject-prefix)
     87    ("C-m r  " "RFC subject prefix" "--rfc")
     88    ("C-m l  " "Add cover letter" "--cover-letter")
     89    (5 magit-format-patch:--cover-from-description)
     90    (5 magit-format-patch:--notes)
     91    (magit-format-patch:--output-directory)]
     92   ["Diff arguments"
     93    (magit-diff:-U)
     94    (magit-diff:-M)
     95    (magit-diff:-C)
     96    (magit-diff:--diff-algorithm)
     97    (magit:--)
     98    (7 "-b" "Ignore whitespace changes" ("-b" "--ignore-space-change"))
     99    (7 "-w" "Ignore all whitespace"     ("-w" "--ignore-all-space"))]
    100   ["Actions"
    101    ("c" "Create patches" magit-patch-create)]
    102   (interactive
    103    (if (not (eq transient-current-command 'magit-patch-create))
    104        (list nil nil nil)
    105      (cons (if-let ((revs (magit-region-values 'commit t)))
    106                (concat (car (last revs)) "^.." (car revs))
    107              (let ((range (magit-read-range-or-commit
    108                            "Format range or commit")))
    109                (if (string-match-p "\\.\\." range)
    110                    range
    111                  (format "%s~..%s" range range))))
    112            (let ((args (transient-args 'magit-patch-create)))
    113              (list (-filter #'stringp args)
    114                    (cdr (assoc "--" args)))))))
    115   (if (not range)
    116       (transient-setup 'magit-patch-create)
    117     (magit-run-git "format-patch" range args "--" files)
    118     (when (member "--cover-letter" args)
    119       (save-match-data
    120         (find-file
    121          (expand-file-name
    122           (concat (when-let ((v (transient-arg-value "--reroll-count=" args)))
    123                     (format "v%s-" v))
    124                   "0000-cover-letter.patch")
    125           (let ((topdir (magit-toplevel)))
    126             (if-let ((dir (transient-arg-value "--output-directory=" args)))
    127                 (expand-file-name dir topdir)
    128               topdir))))))))
    129 
    130 (transient-define-argument magit-format-patch:--in-reply-to ()
    131   :description "In reply to"
    132   :class 'transient-option
    133   :key "C-m C-r"
    134   :argument "--in-reply-to=")
    135 
    136 (transient-define-argument magit-format-patch:--thread ()
    137   :description "Thread style"
    138   :class 'transient-option
    139   :key "C-m s  "
    140   :argument "--thread="
    141   :reader #'magit-format-patch-select-thread-style)
    142 
    143 (defun magit-format-patch-select-thread-style (&rest _ignore)
    144   (magit-read-char-case "Thread style " t
    145     (?d "[d]eep" "deep")
    146     (?s "[s]hallow" "shallow")))
    147 
    148 (transient-define-argument magit-format-patch:--base ()
    149   :description "Insert base commit"
    150   :class 'transient-option
    151   :key "C-m b  "
    152   :argument "--base="
    153   :reader #'magit-format-patch-select-base)
    154 
    155 (defun magit-format-patch-select-base (prompt initial-input history)
    156   (or (magit-completing-read prompt (cons "auto" (magit-list-refnames))
    157                              nil nil initial-input history "auto")
    158       (user-error "Nothing selected")))
    159 
    160 (transient-define-argument magit-format-patch:--reroll-count ()
    161   :description "Reroll count"
    162   :class 'transient-option
    163   :key "C-m v  "
    164   :shortarg "-v"
    165   :argument "--reroll-count="
    166   :reader 'transient-read-number-N+)
    167 
    168 (transient-define-argument magit-format-patch:--interdiff ()
    169   :description "Insert interdiff"
    170   :class 'transient-option
    171   :key "C-m d i"
    172   :argument "--interdiff="
    173   :reader #'magit-transient-read-revision)
    174 
    175 (transient-define-argument magit-format-patch:--range-diff ()
    176   :description "Insert range-diff"
    177   :class 'transient-option
    178   :key "C-m d r"
    179   :argument "--range-diff="
    180   :reader #'magit-format-patch-select-range-diff)
    181 
    182 (defun magit-format-patch-select-range-diff (prompt _initial-input _history)
    183   (magit-read-range-or-commit prompt))
    184 
    185 (transient-define-argument magit-format-patch:--subject-prefix ()
    186   :description "Subject Prefix"
    187   :class 'transient-option
    188   :key "C-m p  "
    189   :argument "--subject-prefix=")
    190 
    191 (transient-define-argument magit-format-patch:--cover-from-description ()
    192   :description "Use branch description"
    193   :class 'transient-option
    194   :key "C-m D  "
    195   :argument "--cover-from-description="
    196   :reader #'magit-format-patch-select-description-mode)
    197 
    198 (defun magit-format-patch-select-description-mode (&rest _ignore)
    199   (magit-read-char-case "Use description as " t
    200     (?m "[m]essage" "message")
    201     (?s "[s]ubject" "subject")
    202     (?a "[a]uto"    "auto")
    203     (?n "[n]othing" "none")))
    204 
    205 (transient-define-argument magit-format-patch:--notes ()
    206   :description "Insert commentary from notes"
    207   :class 'transient-option
    208   :key "C-m n  "
    209   :argument "--notes="
    210   :reader #'magit-notes-read-ref)
    211 
    212 (transient-define-argument magit-format-patch:--from ()
    213   :description "From"
    214   :class 'transient-option
    215   :key "C-m C-f"
    216   :argument "--from="
    217   :reader 'magit-transient-read-person)
    218 
    219 (transient-define-argument magit-format-patch:--to ()
    220   :description "To"
    221   :class 'transient-option
    222   :key "C-m C-t"
    223   :argument "--to="
    224   :reader 'magit-transient-read-person)
    225 
    226 (transient-define-argument magit-format-patch:--cc ()
    227   :description "CC"
    228   :class 'transient-option
    229   :key "C-m C-c"
    230   :argument "--cc="
    231   :reader 'magit-transient-read-person)
    232 
    233 (transient-define-argument magit-format-patch:--output-directory ()
    234   :description "Output directory"
    235   :class 'transient-option
    236   :key "C-m o  "
    237   :shortarg "-o"
    238   :argument "--output-directory="
    239   :reader 'transient-read-existing-directory)
    240 
    241 ;;;###autoload (autoload 'magit-patch-apply "magit-patch" nil t)
    242 (transient-define-prefix magit-patch-apply (file &rest args)
    243   "Apply the patch file FILE."
    244   :man-page "git-apply"
    245   ["Arguments"
    246    ("-i" "Also apply to index" "--index")
    247    ("-c" "Only apply to index" "--cached")
    248    ("-3" "Fall back on 3way merge" ("-3" "--3way"))]
    249   ["Actions"
    250    ("a"  "Apply patch" magit-patch-apply)]
    251   (interactive
    252    (if (not (eq transient-current-command 'magit-patch-apply))
    253        (list nil)
    254      (list (expand-file-name
    255             (read-file-name "Apply patch: "
    256                             default-directory nil nil
    257                             (when-let ((file (magit-file-at-point)))
    258                               (file-relative-name file))))
    259            (transient-args 'magit-patch-apply))))
    260   (if (not file)
    261       (transient-setup 'magit-patch-apply)
    262     (magit-run-git "apply" args "--" (magit-convert-filename-for-git file))))
    263 
    264 ;;;###autoload
    265 (defun magit-patch-save (file &optional arg)
    266   "Write current diff into patch FILE.
    267 
    268 What arguments are used to create the patch depends on the value
    269 of `magit-patch-save-arguments' and whether a prefix argument is
    270 used.
    271 
    272 If the value is the symbol `buffer', then use the same arguments
    273 as the buffer.  With a prefix argument use no arguments.
    274 
    275 If the value is a list beginning with the symbol `exclude', then
    276 use the same arguments as the buffer except for those matched by
    277 entries in the cdr of the list.  The comparison is done using
    278 `string-prefix-p'.  With a prefix argument use the same arguments
    279 as the buffer.
    280 
    281 If the value is a list of strings (including the empty list),
    282 then use those arguments.  With a prefix argument use the same
    283 arguments as the buffer.
    284 
    285 Of course the arguments that are required to actually show the
    286 same differences as those shown in the buffer are always used."
    287   (interactive (list (read-file-name "Write patch file: " default-directory)
    288                      current-prefix-arg))
    289   (unless (derived-mode-p 'magit-diff-mode)
    290     (user-error "Only diff buffers can be saved as patches"))
    291   (let ((rev     magit-buffer-range)
    292         (typearg magit-buffer-typearg)
    293         (args    magit-buffer-diff-args)
    294         (files   magit-buffer-diff-files))
    295     (cond ((eq magit-patch-save-arguments 'buffer)
    296            (when arg
    297              (setq args nil)))
    298           ((eq (car-safe magit-patch-save-arguments) 'exclude)
    299            (unless arg
    300              (setq args (-difference args (cdr magit-patch-save-arguments)))))
    301           ((not arg)
    302            (setq args magit-patch-save-arguments)))
    303     (with-temp-file file
    304       (magit-git-insert "diff" rev "-p" typearg args "--" files)))
    305   (magit-refresh))
    306 
    307 ;;;###autoload
    308 (defun magit-request-pull (url start end)
    309   "Request upstream to pull from your public repository.
    310 
    311 URL is the url of your publicly accessible repository.
    312 START is a commit that already is in the upstream repository.
    313 END is the last commit, usually a branch name, which upstream
    314 is asked to pull.  START has to be reachable from that commit."
    315   (interactive
    316    (list (magit-get "remote" (magit-read-remote "Remote") "url")
    317          (magit-read-branch-or-commit "Start" (magit-get-upstream-branch))
    318          (magit-read-branch-or-commit "End")))
    319   (let ((dir default-directory))
    320     ;; mu4e changes default-directory
    321     (compose-mail)
    322     (setq default-directory dir))
    323   (message-goto-body)
    324   (magit-git-insert "request-pull" start url end)
    325   (set-buffer-modified-p nil))
    326 
    327 ;;; _
    328 (provide 'magit-patch)
    329 ;;; magit-patch.el ends here