fordon_greeman / voidfiles

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