config

Personal configuration.
git clone git://code.dwrz.net/config
Log | Files | Refs

magit-push.el (16548B)


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