fordon_greeman / voidfiles

Branch: 2079ec5
Switch branches/tags
main
theidioticdev add emacs 2079ec5 22h ago
405 lines · 17.1 KB Raw Blame History
1 ;;; magit-push.el --- Update remote objects and refs -*- 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 push commands.
26
27 ;;; Code:
28
29 (require 'magit)
30
31 ;;; Options
32
33 (defcustom magit-push-options '("skip-ci" "ci.skip")
34 "Choices offered when setting \"--push-options\" in the push menu."
35 :package-version '(magit . "4.7.0")
36 :group 'magit-commands
37 :type '(repeat string))
38
39 ;;; Menu
40
41 ;;;###autoload(autoload 'magit-push "magit-push" nil t)
42 (transient-define-prefix magit-push ()
43 "Push to another repository."
44 :man-page "git-push"
45 ["Arguments"
46 ("-f" "Force with lease" (nil "--force-with-lease"))
47 ("-F" "Force" ("-f" "--force"))
48 ("-h" "Disable hooks" "--no-verify")
49 ("-n" "Dry run" ("-n" "--dry-run"))
50 ("-u" "Set upstream" "--set-upstream" :level 5)
51 ("-T" "Include all tags" "--tags")
52 ("-t" "Include related annotated tags" "--follow-tags")
53 ("-o" magit-push:--push-option :level 5)]
54 [:if magit-get-current-branch
55 :description (##format (propertize "Push %s to" 'face 'transient-heading)
56 (propertize (magit-get-current-branch)
57 'face 'magit-branch-local))
58 ("p" magit-push-current-to-pushremote)
59 ("u" magit-push-current-to-upstream)
60 ("e" "elsewhere" magit-push-current)]
61 ["Push"
62 [("o" "another branch" magit-push-other)
63 ("r" "explicit refspecs" magit-push-refspecs)
64 ("m" "matching branches" magit-push-matching)]
65 [("T" "a tag" magit-push-tag)
66 ("t" "all tags" magit-push-tags)
67 (6 "n" "a note ref" magit-push-notes-ref)]]
68 ["Configure"
69 ("C" "Set variables..." magit-branch-configure)])
70
71 (defun magit-push-arguments ()
72 (transient-args 'magit-push))
73
74 (transient-define-infix magit-push:--push-option ()
75 :description "Set push-options"
76 :class 'transient-option
77 :shortarg "-o"
78 :argument "--push-option="
79 :multi-value 'repeat
80 :choices (lambda () magit-push-options))
81
82 ;;; Commands
83
84 (defun magit-git-push (branch target args)
85 (run-hooks 'magit-credential-hook)
86 ;; If the remote branch already exists, then we do not have to
87 ;; qualify the target, which we prefer to avoid doing because
88 ;; using the default namespace is wrong in obscure cases.
89 (pcase-let ((namespace (if (magit-get-tracked target) "" "refs/heads/"))
90 (`(,remote . ,target)
91 (magit-split-branch-name target)))
92 (magit-run-git-async "push" "-v" args remote
93 (format "%s:%s%s" branch namespace target))))
94
95 ;;;###autoload(autoload 'magit-push-current-to-pushremote "magit-push" nil t)
96 (transient-define-suffix magit-push-current-to-pushremote (args)
97 "Push the current branch to its push-remote.
98
99 When the push-remote is not configured, then read the push-remote
100 from the user, set it, and then push to it. With a prefix
101 argument the push-remote can be changed before pushed to it."
102 :if #'magit-get-current-branch
103 :description #'magit-push--pushbranch-description
104 (interactive (list (magit-push-arguments)))
105 (pcase-let ((`(,branch ,remote ,changed)
106 (magit--select-push-remote "push there")))
107 (when changed
108 (magit-confirm 'set-and-push
109 (list "Really use \"%s\" as push-remote and push \"%s\" there"
110 remote branch)))
111 (run-hooks 'magit-credential-hook)
112 (magit-run-git-async "push" "-v" args remote
113 (format "refs/heads/%s:refs/heads/%s"
114 branch branch)))) ; see #3847 and #3872
115
116 (defun magit-push--pushbranch-description ()
117 (let* ((branch (magit-get-current-branch))
118 (target (magit-get-push-branch branch t))
119 (remote (magit-get-push-remote branch))
120 (v (magit--push-remote-variable branch t)))
121 (cond
122 (target)
123 ((member remote (magit-list-remotes))
124 (format "%s, creating it"
125 (magit--propertize-face (concat remote "/" branch)
126 'magit-branch-remote)))
127 (remote
128 (format "%s, replacing invalid" v))
129 ((format "%s, setting that" v)))))
130
131 ;;;###autoload(autoload 'magit-push-current-to-upstream "magit-push" nil t)
132 (transient-define-suffix magit-push-current-to-upstream (args)
133 "Push the current branch to its upstream branch.
134
135 With a prefix argument or when the upstream is either not
136 configured or unusable, then let the user first configure
137 the upstream."
138 :if #'magit-get-current-branch
139 :description #'magit-push--upstream-description
140 (interactive (list (magit-push-arguments)))
141 (let* ((branch (or (magit-get-current-branch)
142 (user-error "No branch is checked out")))
143 (remote (magit-get "branch" branch "remote"))
144 (merge (magit-get "branch" branch "merge")))
145 (when (or current-prefix-arg
146 (not (or (magit-get-upstream-branch branch)
147 (magit--unnamed-upstream-p remote merge)
148 (magit--valid-upstream-p remote merge))))
149 (let ((upstream (magit-completing-read
150 (format "Set upstream of %s and push there" branch)
151 (seq-union (mapcar (##concat % "/" branch)
152 (magit-list-remotes))
153 (magit-list-remote-branch-names))
154 nil 'any nil 'magit-revision-history
155 (or (magit-remote-branch-at-point)
156 (and$ (magit-primary-remote)
157 (magit-main-branch $))))))
158 (pcase-setq `(,remote . ,merge)
159 (or (magit-get-tracked upstream)
160 (magit-split-branch-name upstream)))
161 (unless (string-prefix-p "refs/" merge)
162 ;; User selected a non-existent remote-tracking branch.
163 ;; It is very likely, but not certain, that this is the
164 ;; correct thing to do. It is even more likely that it
165 ;; is what the user wants to happen.
166 (setq merge (concat "refs/heads/" merge)))
167 (magit-confirm 'set-and-push
168 (list "Really use \"%s\" as upstream and push \"%s\" there"
169 upstream branch)))
170 (cl-pushnew "--set-upstream" args :test #'equal))
171 (run-hooks 'magit-credential-hook)
172 (magit-run-git-async "push" "-v" args remote (concat branch ":" merge))))
173
174 (defun magit-push--upstream-description ()
175 (and-let ((branch (magit-get-current-branch)))
176 (or (magit-get-upstream-branch branch)
177 (let ((remote (magit-get "branch" branch "remote"))
178 (merge (magit-get "branch" branch "merge"))
179 (u (magit--propertize-face "@{upstream}" 'bold)))
180 (cond
181 ((magit--unnamed-upstream-p remote merge)
182 (format "%s as %s"
183 (magit--propertize-face remote 'bold)
184 (magit--propertize-face merge 'magit-branch-remote)))
185 ((magit--valid-upstream-p remote merge)
186 (format "%s creating %s"
187 (magit--propertize-face remote 'magit-branch-remote)
188 (magit--propertize-face merge 'magit-branch-remote)))
189 ((or remote merge)
190 (concat u ", creating it and replacing invalid"))
191 ((concat u ", creating it")))))))
192
193 ;;;###autoload
194 (defun magit-push-current (target args)
195 "Push the current branch to a branch read in the minibuffer."
196 (interactive
197 (if-let ((current (magit-get-current-branch)))
198 (list (magit-read-remote-branch (format "Push %s to" current)
199 nil nil current 'confirm)
200 (magit-push-arguments))
201 (user-error "No branch is checked out")))
202 (magit-git-push (magit-get-current-branch) target args))
203
204 ;;;###autoload
205 (defun magit-push-other (source target args)
206 "Push an arbitrary branch or commit somewhere.
207 Both the source and the target are read in the minibuffer."
208 (interactive
209 (let ((source (magit-read-local-branch-or-commit "Push")))
210 (list source
211 (magit-read-remote-branch
212 (format "Push %s to" source) nil
213 (cond ((magit-local-branch-p source)
214 (or (magit-get-push-branch source)
215 (magit-get-upstream-branch source)))
216 ((magit-rev-ancestor-p source "HEAD")
217 (or (magit-get-push-branch)
218 (magit-get-upstream-branch))))
219 source 'confirm)
220 (magit-push-arguments))))
221 (magit-git-push source target args))
222
223 (defvar magit-push-refspecs-history nil)
224
225 ;;;###autoload
226 (defun magit-push-refspecs (remote refspecs args)
227 "Push one or multiple REFSPECS to a REMOTE.
228 Both the REMOTE and the REFSPECS are read in the minibuffer. To
229 use multiple REFSPECS, separate them with commas. Completion is
230 only available for the part before the colon, or when no colon
231 is used."
232 (interactive
233 (list (magit-read-remote "Push to remote")
234 (magit-completing-read-multiple
235 "Push refspec,s: "
236 (cons "HEAD" (magit-list-local-branch-names))
237 nil 'any nil 'magit-push-refspecs-history)
238 (magit-push-arguments)))
239 (run-hooks 'magit-credential-hook)
240 (magit-run-git-async "push" "-v" args remote refspecs))
241
242 ;;;###autoload
243 (defun magit-push-matching (remote &optional args)
244 "Push all matching branches to another repository.
245 If multiple remotes exist, then read one from the user.
246 If just one exists, use that without requiring confirmation."
247 (interactive (list (magit-read-remote "Push matching branches to" nil t)
248 (magit-push-arguments)))
249 (run-hooks 'magit-credential-hook)
250 (magit-run-git-async "push" "-v" args remote ":"))
251
252 ;;;###autoload
253 (defun magit-push-tags (remote &optional args)
254 "Push all tags to another repository.
255 If only one remote exists, then push to that. Otherwise prompt
256 for a remote, offering the remote configured for the current
257 branch as default."
258 (interactive (list (magit-read-remote "Push tags to remote" nil t)
259 (magit-push-arguments)))
260 (run-hooks 'magit-credential-hook)
261 (magit-run-git-async "push" remote "--tags" args))
262
263 ;;;###autoload
264 (defun magit-push-tag (tag remote &optional args)
265 "Push a tag to another repository."
266 (interactive
267 (let ((tag (magit-read-tag "Push tag")))
268 (list tag (magit-read-remote (format "Push %s to remote" tag) nil t)
269 (magit-push-arguments))))
270 (run-hooks 'magit-credential-hook)
271 (magit-run-git-async "push" remote tag args))
272
273 ;;;###autoload
274 (defun magit-push-notes-ref (ref remote &optional args)
275 "Push a notes ref to another repository."
276 (interactive
277 (let ((note (magit-notes-read-ref "Push notes")))
278 (list note
279 (magit-read-remote (format "Push %s to remote" note) nil t)
280 (magit-push-arguments))))
281 (run-hooks 'magit-credential-hook)
282 (magit-run-git-async "push" remote ref args))
283
284 ;;;###autoload(autoload 'magit-push-implicitly "magit-push" nil t)
285 (transient-define-suffix magit-push-implicitly (args)
286 "Push somewhere without using an explicit refspec.
287
288 This command simply runs \"git push -v [ARGS]\". ARGS are the
289 arguments specified in the popup buffer. No explicit refspec
290 arguments are used. Instead the behavior depends on at least
291 these Git variables: `push.default', `remote.pushDefault',
292 `branch.<branch>.pushRemote', `branch.<branch>.remote',
293 `branch.<branch>.merge', and `remote.<remote>.push'.
294
295 If you add this suffix to a transient prefix without explicitly
296 specifying the description, then an attempt is made to predict
297 what this command will do. To add it use something like:
298
299 (transient-insert-suffix \\='magit-push \"o\"
300 \\='(\"i\" magit-push-implicitly))"
301 :description #'magit-push-implicitly--desc
302 (interactive (list (magit-push-arguments)))
303 (run-hooks 'magit-credential-hook)
304 (magit-run-git-async "push" "-v" args))
305
306 (defun magit-push-implicitly--desc ()
307 ;; This implements the logic for git push as documented.
308 ;; First, we resolve a remote to use based on various remote and
309 ;; pushRemote options.
310 ;; Then, we resolve the refspec to use for the remote based on push
311 ;; and pushDefault options.
312 ;; Note that the remote and refspec to push are handled separately,
313 ;; so it doesn't make sense to talk about "pushing to upstream".
314 ;; Depending on the options, you could end up pushing to the
315 ;; "upstream" remote but not the "upstream" branch, and vice versa.
316 (let* ((branch (magit-get-current-branch))
317 (remote (or (magit-get-push-remote branch)
318 ;; Note: Avoid `magit-get-remote' because it
319 ;; filters out the local repo case (".").
320 (magit-get "branch" branch "remote")
321 (cond-let
322 [[remotes (magit-list-remotes)]]
323 ((and (magit-git-version>= "2.27")
324 (length= remotes 1))
325 (car remotes))
326 ((car (member "origin" remotes)))))))
327 (if (null remote)
328 "nothing (no remote)"
329 (let ((refspec (magit-get "remote" remote "push")))
330 (if refspec
331 (format "to %s with refspecs %s"
332 (magit--propertize-face remote 'bold)
333 (magit--propertize-face refspec 'bold))
334 (pcase (or (magit-get "push.default") "simple")
335 ("nothing" "nothing (due to push.default)")
336 ((or "current" "simple")
337 (format "%s to %s"
338 (magit--propertize-face branch 'magit-branch-current)
339 (magit--propertize-face (format "%s/%s" remote branch)
340 'magit-branch-remote)))
341 ((or "upstream" "tracking")
342 (let ((ref (magit-get "branch" branch "merge")))
343 (if ref
344 (format "%s to %s"
345 (magit--propertize-face branch 'magit-branch-current)
346 (cond
347 ((string-prefix-p "refs/heads/" ref)
348 (magit--propertize-face
349 (format "%s/%s" remote
350 (substring ref (length "refs/heads/")))
351 'magit-branch-remote))
352 ((not (string-match "/" ref))
353 (magit--propertize-face (format "%s/%s" remote ref)
354 'magit-branch-remote))
355 ((format "%s as %s"
356 (magit--propertize-face remote 'bold)
357 (magit--propertize-face ref 'bold)))))
358 "nothing (no upstream)")))
359 ("matching" (format "all matching to %s"
360 (magit--propertize-face remote 'bold)))))))))
361
362 ;;;###autoload(autoload 'magit-push-to-remote "magit-push" nil t)
363 (transient-define-suffix magit-push-to-remote (remote args)
364 "Push to REMOTE without using an explicit refspec.
365 The REMOTE is read in the minibuffer.
366
367 This command simply runs \"git push -v [ARGS] REMOTE\". ARGS
368 are the arguments specified in the popup buffer. No refspec
369 arguments are used. Instead the behavior depends on at least
370 these Git variables: `push.default', `remote.pushDefault',
371 `branch.<branch>.pushRemote', `branch.<branch>.remote',
372 `branch.<branch>.merge', and `remote.<remote>.push'.
373
374 You can add this command as a suffix using something like:
375
376 (transient-insert-suffix \\='magit-push \"o\"
377 \\='(\"x\" magit-push-to-remote))"
378 :description #'magit-push-to-remote--desc
379 (interactive (list (magit-read-remote "Push to remote")
380 (magit-push-arguments)))
381 (run-hooks 'magit-credential-hook)
382 (magit-run-git-async "push" "-v" args remote))
383
384 (defun magit-push-to-remote--desc ()
385 (format "using %s" (magit--propertize-face "git push <remote>" 'bold)))
386
387 ;;; _
388 (provide 'magit-push)
389 ;; Local Variables:
390 ;; read-symbol-shorthands: (
391 ;; ("and$" . "cond-let--and$")
392 ;; ("thread$" . "cond-let--thread$")
393 ;; ("when$" . "cond-let--when$")
394 ;; ("and-let*" . "cond-let--and-let*")
395 ;; ("and-let" . "cond-let--and-let")
396 ;; ("if-let*" . "cond-let--if-let*")
397 ;; ("if-let" . "cond-let--if-let")
398 ;; ("when-let*" . "cond-let--when-let*")
399 ;; ("when-let" . "cond-let--when-let")
400 ;; ("while-let*" . "cond-let--while-let*")
401 ;; ("while-let" . "cond-let--while-let")
402 ;; ("match-string" . "match-string")
403 ;; ("match-str" . "match-string-no-properties"))
404 ;; End:
405 ;;; magit-push.el ends here