fordon_greeman / voidfiles

Branch: main
Switch branches/tags
main
theidioticdev add emacs 2079ec5 1d ago
338 lines · 12.5 KB Raw Blame History
1 ;;; magit-merge.el --- Merge functionality -*- 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 merge commands.
26
27 ;;; Code:
28
29 (require 'magit)
30 (require 'magit-diff)
31
32 (declare-function magit-git-push "magit-push" (branch target args))
33
34 ;;; Commands
35
36 ;;;###autoload(autoload 'magit-merge "magit" nil t)
37 (transient-define-prefix magit-merge ()
38 "Merge branches."
39 :man-page "git-merge"
40 :incompatible '(("--ff-only" "--no-ff"))
41 ["Arguments"
42 :if-not magit-merge-in-progress-p
43 ("-f" "Fast-forward only" "--ff-only")
44 ("-n" "No fast-forward" "--no-ff")
45 (magit-merge:--strategy)
46 (5 magit-merge:--strategy-option)
47 (5 "-b" "Ignore changes in amount of whitespace" "-Xignore-space-change")
48 (5 "-w" "Ignore whitespace when comparing lines" "-Xignore-all-space")
49 (5 magit-diff:--diff-algorithm :argument "-Xdiff-algorithm=")
50 (magit:--gpg-sign)
51 (magit:--signoff)]
52 ["Actions"
53 :if-not magit-merge-in-progress-p
54 [("m" "Merge" magit-merge-plain)
55 ("e" "Merge and edit message" magit-merge-editmsg)
56 ("n" "Merge but don't commit" magit-merge-nocommit)
57 ("a" "Absorb" magit-merge-absorb)]
58 [("p" "Preview merge" magit-merge-preview)
59 ""
60 ("s" "Squash merge" magit-merge-squash)
61 ("d" "Dissolve" magit-merge-dissolve)]]
62 ["Actions"
63 :if magit-merge-in-progress-p
64 ("m" "Commit merge" magit-commit-create)
65 ("a" "Abort merge" magit-merge-abort)])
66
67 (defun magit-merge-arguments ()
68 (transient-args 'magit-merge))
69
70 (transient-define-argument magit-merge:--strategy ()
71 :description "Strategy"
72 :class 'transient-option
73 ;; key for merge and rebase: "-s"
74 ;; key for cherry-pick and revert: "=s"
75 ;; shortarg for merge and rebase: "-s"
76 ;; shortarg for cherry-pick and revert: none
77 :key "-s"
78 :argument "--strategy="
79 :choices '("resolve" "recursive" "octopus" "ours" "subtree"))
80
81 (transient-define-argument magit-merge:--strategy-option ()
82 :description "Strategy Option"
83 :class 'transient-option
84 :key "-X"
85 :argument "--strategy-option="
86 :choices '("ours" "theirs" "patience"))
87
88 ;;;###autoload
89 (defun magit-merge-plain (rev &optional args nocommit)
90 "Merge commit REV into the current branch; using default message.
91
92 Unless there are conflicts or a prefix argument is used create a
93 merge commit using a generic commit message and without letting
94 the user inspect the result. With a prefix argument pretend the
95 merge failed to give the user the opportunity to inspect the
96 merge.
97
98 To create an octopus-merge, separate branches with commas.
99
100 \(git merge --no-edit|--no-commit [ARGS] REV)"
101 (interactive (list (magit-read-other-branches-or-commits "Merge")
102 (magit-merge-arguments)
103 current-prefix-arg))
104 (magit-merge-assert)
105 (magit-run-git-async "merge" (if nocommit "--no-commit" "--no-edit") args rev))
106
107 ;;;###autoload
108 (defun magit-merge-editmsg (rev &optional args)
109 "Merge commit REV into the current branch; and edit message.
110
111 Perform the merge and prepare a commit message but let the user
112 edit it.
113
114 To create an octopus-merge, separate branches with commas.
115
116 \(git merge --edit --no-ff [ARGS] REV)"
117 (interactive (list (magit-read-other-branches-or-commits "Merge")
118 (magit-merge-arguments)))
119 (magit-merge-assert)
120 (cl-pushnew "--no-ff" args :test #'equal)
121 (magit-run-git-with-editor "merge" "--edit" (delete "--ff-only" args) rev))
122
123 ;;;###autoload
124 (defun magit-merge-nocommit (rev &optional args)
125 "Merge commit REV into the current branch; pretending it failed.
126
127 Pretend the merge failed to give the user the opportunity to
128 inspect the merge and change the commit message.
129
130 To create an octopus-merge, separate branches with commas.
131
132 \(git merge --no-commit --no-ff [ARGS] REV)"
133 (interactive (list (magit-read-other-branches-or-commits "Merge")
134 (magit-merge-arguments)))
135 (magit-merge-assert)
136 (cl-pushnew "--no-ff" args :test #'equal)
137 (magit-run-git-async "merge" "--no-commit" args rev))
138
139 ;;;###autoload
140 (defun magit-merge-dissolve (branch &optional args)
141 "Merge the current branch into BRANCH and remove the former.
142
143 Before merging, force push the source branch to its push-remote,
144 provided the respective remote branch already exists, ensuring
145 that the respective pull-request (if any) won't get stuck on some
146 obsolete version of the commits that are being merged. Finally
147 if `forge-branch-pullreq' was used to create the merged branch,
148 then also remove the respective remote branch."
149 (interactive
150 (list (let ((branch (magit-get-current-branch)))
151 (magit-read-other-local-branch
152 (format "Merge `%s' into" (or branch (magit-rev-parse "HEAD")))
153 nil
154 (and branch (magit-get-local-upstream-branch branch))))
155 (magit-merge-arguments)))
156 (let ((current (magit-get-current-branch))
157 (head (magit-rev-parse "HEAD")))
158 (when (zerop (magit-call-git "checkout" branch))
159 (if current
160 (magit--merge-absorb current args)
161 (magit-run-git-with-editor "merge" args head)))))
162
163 ;;;###autoload
164 (defun magit-merge-absorb (branch &optional args)
165 "Merge BRANCH into the current branch and remove the former.
166
167 Before merging, force push the source branch to its push-remote,
168 provided the respective remote branch already exists, ensuring
169 that the respective pull-request (if any) won't get stuck on some
170 obsolete version of the commits that are being merged. Finally
171 if `forge-branch-pullreq' was used to create the merged branch,
172 then also remove the respective remote branch."
173 (interactive (list (magit-read-other-local-branch "Absorb branch")
174 (magit-merge-arguments)))
175 (magit--merge-absorb branch args))
176
177 (defun magit--merge-absorb (branch args &optional message)
178 (when (equal branch (magit-main-branch))
179 (unless (yes-or-no-p
180 (format "Do you really want to merge `%s' into another branch? "
181 branch))
182 (user-error "Abort")))
183 (cond-let
184 ([target (magit-get-push-branch branch t)]
185 (magit-git-push branch target (list "--force-with-lease"))
186 (set-process-sentinel
187 magit-this-process
188 (lambda (process event)
189 (when (memq (process-status process) '(exit signal))
190 (if (not (zerop (process-exit-status process)))
191 (magit-process-sentinel process event)
192 (process-put process 'inhibit-refresh t)
193 (magit-process-sentinel process event)
194 (magit--merge-absorb-1 branch args))
195 (when message
196 (message message))))))
197 ((magit--merge-absorb-1 branch args))))
198
199 (defun magit--merge-absorb-1 (branch args)
200 (if-let ((pr (magit-get "branch" branch "pullRequest")))
201 (magit-run-git-async
202 "merge" args "-m"
203 (format "Merge branch '%s'%s [#%s]"
204 branch
205 (let ((current (magit-get-current-branch)))
206 (if (equal current (magit-main-branch))
207 ""
208 (format " into %s" current)))
209 pr)
210 branch)
211 (magit-run-git-async "merge" args "--no-edit" branch))
212 (set-process-sentinel
213 magit-this-process
214 (lambda (process event)
215 (when (memq (process-status process) '(exit signal))
216 (if (> (process-exit-status process) 0)
217 (magit-process-sentinel process event)
218 (process-put process 'inhibit-refresh t)
219 (magit-process-sentinel process event)
220 (magit-branch-maybe-delete-pr-remote branch)
221 (magit-branch-unset-pushRemote branch)
222 (magit-run-git "branch" "-D" branch))))))
223
224 ;;;###autoload
225 (defun magit-merge-squash (rev)
226 "Squash commit REV into the current branch; don't create a commit.
227 \n(git merge --squash REV)"
228 (interactive (list (magit-read-other-branch-or-commit "Squash")))
229 (magit-merge-assert)
230 (magit-run-git-async "merge" "--squash" rev))
231
232 ;;;###autoload
233 (defun magit-merge-preview (rev)
234 "Preview result of merging REV into the current branch."
235 (interactive (list (magit-read-other-branch-or-commit "Preview merge")))
236 (magit-merge-preview-setup-buffer rev))
237
238 ;;;###autoload
239 (defun magit-merge-abort ()
240 "Abort the current merge operation.
241 \n(git merge --abort)"
242 (interactive)
243 (unless (file-exists-p (expand-file-name "MERGE_HEAD" (magit-gitdir)))
244 (user-error "No merge in progress"))
245 (magit-confirm 'abort-merge)
246 (magit-run-git-async "merge" "--abort"))
247
248 (defun magit-checkout-stage (file arg)
249 "During a conflict checkout and stage side, or restore conflict."
250 (interactive
251 (let ((file (magit-completing-read "Checkout file"
252 (magit-tracked-files) nil 'any nil
253 'magit-read-file-hist
254 (magit-current-file))))
255 (cond ((member file (magit-unmerged-files))
256 (list file (magit-checkout-read-stage file)))
257 ((yes-or-no-p (format "Restore conflicts in %s? " file))
258 (list file "--merge"))
259 ((user-error "Quit")))))
260 (pcase (cons arg (cddr (car (magit-file-status file))))
261 ((or `("--ours" ?D ,_)
262 '("--ours" ?U ?A)
263 `("--theirs" ,_ ?D)
264 '("--theirs" ?A ?U))
265 (magit-run-git "rm" "--" file))
266 (_ (if (equal arg "--merge")
267 ;; This fails if the file was deleted on one
268 ;; side. And we cannot do anything about it.
269 (magit-run-git "checkout" "--merge" "--" file)
270 (magit-call-git "checkout" arg "--" file)
271 (magit-run-git "add" "-u" "--" file)))))
272
273 ;;; Utilities
274
275 (defun magit-merge-in-progress-p ()
276 (file-exists-p (expand-file-name "MERGE_HEAD" (magit-gitdir))))
277
278 (defun magit--merge-range (&optional head)
279 (unless head
280 (setq head (magit-get-shortname
281 (car (magit-file-lines
282 (expand-file-name "MERGE_HEAD" (magit-gitdir)))))))
283 (and head
284 (concat (magit-merge-base "HEAD" head "--octopus")
285 ".." head)))
286
287 (defun magit-merge-assert ()
288 (or (not (magit-anything-modified-p t))
289 (magit-confirm 'merge-dirty
290 "Merging with dirty worktree is risky. Continue")))
291
292 (defun magit-checkout-read-stage (file)
293 (magit-read-char-case (format "For %s checkout: " file) t
294 (?o "[o]ur stage" "--ours")
295 (?t "[t]heir stage" "--theirs")
296 (?c (if magit-verbose-messages "restore [c]onflict" "[c]onflict")
297 "--merge")))
298
299 ;;; Sections
300
301 (defun magit-insert-merge-log ()
302 "Insert section for the on-going merge.
303 Display the heads that are being merged.
304 If no merge is in progress, do nothing."
305 (when (magit-merge-in-progress-p)
306 (let* ((heads (mapcar #'magit-get-shortname
307 (magit-file-lines
308 (expand-file-name "MERGE_HEAD" (magit-gitdir)))))
309 (range (magit--merge-range (car heads))))
310 (magit-insert-section (unmerged range)
311 (magit-insert-heading
312 (format "Merging %s:" (string-join heads ", ")))
313 (magit--insert-log nil
314 range
315 (let ((args magit-buffer-log-args))
316 (unless (member "--decorate=full" magit-buffer-log-args)
317 (push "--decorate=full" args))
318 args))))))
319
320 ;;; _
321 (provide 'magit-merge)
322 ;; Local Variables:
323 ;; read-symbol-shorthands: (
324 ;; ("and$" . "cond-let--and$")
325 ;; ("thread$" . "cond-let--thread$")
326 ;; ("when$" . "cond-let--when$")
327 ;; ("and-let*" . "cond-let--and-let*")
328 ;; ("and-let" . "cond-let--and-let")
329 ;; ("if-let*" . "cond-let--if-let*")
330 ;; ("if-let" . "cond-let--if-let")
331 ;; ("when-let*" . "cond-let--when-let*")
332 ;; ("when-let" . "cond-let--when-let")
333 ;; ("while-let*" . "cond-let--while-let*")
334 ;; ("while-let" . "cond-let--while-let")
335 ;; ("match-string" . "match-string")
336 ;; ("match-str" . "match-string-no-properties"))
337 ;; End:
338 ;;; magit-merge.el ends here