| 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 |