config

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

magit-remote.el (16094B)


      1 ;;; magit-remote.el --- Transfer Git commits  -*- 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 remote commands.
     26 
     27 ;;; Code:
     28 
     29 (require 'magit)
     30 
     31 ;;; Options
     32 
     33 (defcustom magit-remote-add-set-remote.pushDefault 'ask-if-unset
     34   "Whether to set the value of `remote.pushDefault' after adding a remote.
     35 
     36 If `ask', then always ask.  If `ask-if-unset', then ask, but only
     37 if the variable isn't set already.  If nil, then don't ever set.
     38 If the value is a string, then set without asking, provided that
     39 the name of the added remote is equal to that string and the
     40 variable isn't already set."
     41   :package-version '(magit . "2.4.0")
     42   :group 'magit-commands
     43   :type '(choice (const  :tag "ask if unset" ask-if-unset)
     44                  (const  :tag "always ask" ask)
     45                  (string :tag "set if named")
     46                  (const  :tag "don't set")))
     47 
     48 (defcustom magit-remote-direct-configure t
     49   "Whether the command `magit-remote' shows Git variables.
     50 When set to nil, no variables are displayed by this transient
     51 command, instead the sub-transient `magit-remote-configure'
     52 has to be used to view and change remote related variables."
     53   :package-version '(magit . "2.12.0")
     54   :group 'magit-commands
     55   :type 'boolean)
     56 
     57 (defcustom magit-prefer-push-default nil
     58   "Whether to prefer `remote.pushDefault' over per-branch variables."
     59   :package-version '(magit . "3.0.0")
     60   :group 'magit-commands
     61   :type 'boolean)
     62 
     63 ;;; Commands
     64 
     65 ;;;###autoload (autoload 'magit-remote "magit-remote" nil t)
     66 (transient-define-prefix magit-remote (remote)
     67   "Add, configure or remove a remote."
     68   :man-page "git-remote"
     69   :value '("-f")
     70   ["Variables"
     71    :if (lambda ()
     72          (and magit-remote-direct-configure
     73               (oref (transient-prefix-object) scope)))
     74    ("u" magit-remote.<remote>.url)
     75    ("U" magit-remote.<remote>.fetch)
     76    ("s" magit-remote.<remote>.pushurl)
     77    ("S" magit-remote.<remote>.push)
     78    ("O" magit-remote.<remote>.tagopt)]
     79   ["Arguments for add"
     80    ("-f" "Fetch after add" "-f")]
     81   ["Actions"
     82    [("a" "Add"                  magit-remote-add)
     83     ("r" "Rename"               magit-remote-rename)
     84     ("k" "Remove"               magit-remote-remove)]
     85    [("C" "Configure..."         magit-remote-configure)
     86     ("p" "Prune stale branches" magit-remote-prune)
     87     ("P" "Prune stale refspecs" magit-remote-prune-refspecs)
     88     ("b" magit-update-default-branch)
     89     (7 "z" "Unshallow remote"   magit-remote-unshallow)]]
     90   (interactive (list (magit-get-current-remote)))
     91   (transient-setup 'magit-remote nil nil :scope remote))
     92 
     93 (defun magit-read-url (prompt &optional initial-input)
     94   (let ((url (magit-read-string-ns prompt initial-input)))
     95     (if (string-prefix-p "~" url)
     96         (expand-file-name url)
     97       url)))
     98 
     99 ;;;###autoload
    100 (defun magit-remote-add (remote url &optional args)
    101   "Add a remote named REMOTE and fetch it."
    102   (interactive
    103    (let ((origin (magit-get "remote.origin.url"))
    104          (remote (magit-read-string-ns "Remote name")))
    105      (list remote
    106            (magit-read-url
    107             "Remote url"
    108             (and origin
    109                  (string-match "\\([^:/]+\\)/[^/]+\\(\\.git\\)?\\'" origin)
    110                  (replace-match remote t t origin 1)))
    111            (transient-args 'magit-remote))))
    112   (if (pcase (list magit-remote-add-set-remote.pushDefault
    113                    (magit-get "remote.pushDefault"))
    114         (`(,(pred stringp) ,_) t)
    115         ((or `(ask ,_) '(ask-if-unset nil))
    116          (y-or-n-p (format "Set `remote.pushDefault' to \"%s\"? " remote))))
    117       (progn (magit-call-git "remote" "add" args remote url)
    118              (setf (magit-get "remote.pushDefault") remote)
    119              (magit-refresh))
    120     (magit-run-git-async "remote" "add" args remote url)))
    121 
    122 ;;;###autoload
    123 (defun magit-remote-rename (old new)
    124   "Rename the remote named OLD to NEW."
    125   (interactive
    126    (let  ((remote (magit-read-remote "Rename remote")))
    127      (list remote (magit-read-string-ns (format "Rename %s to" remote)))))
    128   (unless (string= old new)
    129     (magit-call-git "remote" "rename" old new)
    130     (magit-remote--cleanup-push-variables old new)
    131     (magit-refresh)))
    132 
    133 ;;;###autoload
    134 (defun magit-remote-remove (remote)
    135   "Delete the remote named REMOTE."
    136   (interactive (list (magit-read-remote "Delete remote")))
    137   (magit-call-git "remote" "rm" remote)
    138   (magit-remote--cleanup-push-variables remote)
    139   (magit-refresh))
    140 
    141 (defun magit-remote--cleanup-push-variables (remote &optional new-name)
    142   (magit-with-toplevel
    143     (when (equal (magit-get "remote.pushDefault") remote)
    144       (magit-set new-name "remote.pushDefault"))
    145     (dolist (var (magit-git-lines "config" "--name-only"
    146                                   "--get-regexp" "^branch\\.[^.]*\\.pushRemote"
    147                                   (format "^%s$" remote)))
    148       (magit-call-git "config" (and (not new-name) "--unset") var new-name))))
    149 
    150 (defconst magit--refspec-re "\\`\\(\\+\\)?\\([^:]+\\):\\(.*\\)\\'")
    151 
    152 ;;;###autoload
    153 (defun magit-remote-prune (remote)
    154   "Remove stale remote-tracking branches for REMOTE."
    155   (interactive (list (magit-read-remote "Prune stale branches of remote")))
    156   (magit-run-git-async "remote" "prune" remote))
    157 
    158 ;;;###autoload
    159 (defun magit-remote-prune-refspecs (remote)
    160   "Remove stale refspecs for REMOTE.
    161 
    162 A refspec is stale if there no longer exists at least one branch
    163 on the remote that would be fetched due to that refspec.  A stale
    164 refspec is problematic because its existence causes Git to refuse
    165 to fetch according to the remaining non-stale refspecs.
    166 
    167 If only stale refspecs remain, then offer to either delete the
    168 remote or to replace the stale refspecs with the default refspec.
    169 
    170 Also remove the remote-tracking branches that were created due to
    171 the now stale refspecs.  Other stale branches are not removed."
    172   (interactive (list (magit-read-remote "Prune refspecs of remote")))
    173   (let* ((tracking-refs (magit-list-remote-branches remote))
    174          (remote-refs (magit-remote-list-refs remote))
    175          (variable (format "remote.%s.fetch" remote))
    176          (refspecs (magit-get-all variable))
    177          stale)
    178     (dolist (refspec refspecs)
    179       (when (string-match magit--refspec-re refspec)
    180         (let ((theirs (match-string 2 refspec))
    181               (ours   (match-string 3 refspec)))
    182           (unless (if (string-match "\\*" theirs)
    183                       (let ((re (replace-match ".*" t t theirs)))
    184                         (--some (string-match-p re it) remote-refs))
    185                     (member theirs remote-refs))
    186             (push (cons refspec
    187                         (if (string-match "\\*" ours)
    188                             (let ((re (replace-match ".*" t t ours)))
    189                               (--filter (string-match-p re it) tracking-refs))
    190                           (list (car (member ours tracking-refs)))))
    191                   stale)))))
    192     (if (not stale)
    193         (message "No stale refspecs for remote %S" remote)
    194       (if (= (length stale)
    195              (length refspecs))
    196           (magit-read-char-case
    197               (format "All of %s's refspecs are stale.  " remote) nil
    198             (?s "replace with [d]efault refspec"
    199                 (magit-set-all
    200                  (list (format "+refs/heads/*:refs/remotes/%s/*" remote))
    201                  variable))
    202             (?r "[r]emove remote"
    203                 (magit-call-git "remote" "rm" remote))
    204             (?a "or [a]abort"
    205                 (user-error "Abort")))
    206         (if (if (length= stale 1)
    207                 (pcase-let ((`(,refspec . ,refs) (car stale)))
    208                   (magit-confirm 'prune-stale-refspecs
    209                     (format "Prune stale refspec %s and branch %%s" refspec)
    210                     (format "Prune stale refspec %s and %%d branches" refspec)
    211                     nil refs))
    212               (magit-confirm 'prune-stale-refspecs nil
    213                 (format "Prune %%d stale refspecs and %d branches"
    214                         (length (cl-mapcan (lambda (s) (copy-sequence (cdr s)))
    215                                            stale)))
    216                 nil
    217                 (mapcar (pcase-lambda (`(,refspec . ,refs))
    218                           (concat refspec "\n"
    219                                   (mapconcat (lambda (b) (concat "  " b))
    220                                              refs "\n")))
    221                         stale)))
    222             (pcase-dolist (`(,refspec . ,refs) stale)
    223               (magit-call-git "config" "--unset" variable
    224                               (regexp-quote refspec))
    225               (magit--log-action
    226                (lambda (refs)
    227                  (format "Deleting %d branches" (length refs)))
    228                (lambda (ref)
    229                  (format "Deleting branch %s (was %s)" ref
    230                          (magit-rev-parse "--short" ref)))
    231                refs)
    232               (dolist (ref refs)
    233                 (magit-call-git "update-ref" "-d" ref)))
    234           (user-error "Abort")))
    235       (magit-refresh))))
    236 
    237 ;;;###autoload
    238 (defun magit-remote-set-head (remote &optional branch)
    239   "Set the local representation of REMOTE's default branch.
    240 Query REMOTE and set the symbolic-ref refs/remotes/<remote>/HEAD
    241 accordingly.  With a prefix argument query for the branch to be
    242 used, which allows you to select an incorrect value if you fancy
    243 doing that."
    244   (interactive
    245    (let  ((remote (magit-read-remote "Set HEAD for remote")))
    246      (list remote
    247            (and current-prefix-arg
    248                 (magit-read-remote-branch (format "Set %s/HEAD to" remote)
    249                                           remote nil nil t)))))
    250   (magit-run-git "remote" "set-head" remote (or branch "--auto")))
    251 
    252 ;;;###autoload
    253 (defun magit-remote-unset-head (remote)
    254   "Unset the local representation of REMOTE's default branch.
    255 Delete the symbolic-ref \"refs/remotes/<remote>/HEAD\"."
    256   (interactive (list (magit-read-remote "Unset HEAD for remote")))
    257   (magit-run-git "remote" "set-head" remote "--delete"))
    258 
    259 ;;;###autoload (autoload 'magit-update-default-branch "magit-remote" nil t)
    260 (transient-define-suffix magit-update-default-branch ()
    261   "Update name of the default branch after upstream changed it."
    262   :description "Update default branch"
    263   :inapt-if-not #'magit-get-some-remote
    264   (interactive)
    265   (pcase-let ((`(,_remote ,oldname) (magit--get-default-branch))
    266               (`( ,remote ,newname) (magit--get-default-branch t)))
    267     (cond
    268      ((equal oldname newname)
    269       (setq oldname
    270             (read-string
    271              (format
    272               "Name of default branch is still `%s', %s\n%s `%s': " oldname
    273               "but the upstreams of some local branches might need updating."
    274               "Name of upstream branches to replace with" newname)))
    275       (magit--set-default-branch newname oldname)
    276       (magit-refresh))
    277      (t
    278       (unless oldname
    279         (setq oldname
    280               (magit-read-other-local-branch
    281                (format "Name of old default branch to be renamed to `%s'"
    282                        newname)
    283                newname "master")))
    284       (cond
    285        ((y-or-n-p (format "Default branch changed from `%s' to `%s' on %s.%s"
    286                           oldname newname remote "  Do the same locally? "))
    287         (magit--set-default-branch newname oldname)
    288         (magit-refresh))
    289        ((user-error "Abort")))))))
    290 
    291 ;;;###autoload
    292 (defun magit-remote-unshallow (remote)
    293   "Convert a shallow remote into a full one.
    294 If only a single refspec is set and it does not contain a
    295 wildcard, then also offer to replace it with the standard
    296 refspec."
    297   (interactive (list (or (magit-get-current-remote)
    298                          (magit-read-remote "Delete remote"))))
    299   (let ((refspecs (magit-get-all "remote" remote "fetch"))
    300         (standard (format "+refs/heads/*:refs/remotes/%s/*" remote)))
    301     (when (and (length= refspecs 1)
    302                (not (string-search "*" (car refspecs)))
    303                (yes-or-no-p (format "Also replace refspec %s with %s? "
    304                                     (car refspecs)
    305                                     standard)))
    306       (magit-set standard "remote" remote "fetch"))
    307     (magit-git-fetch "--unshallow" remote)))
    308 
    309 ;;; Configure
    310 
    311 ;;;###autoload (autoload 'magit-remote-configure "magit-remote" nil t)
    312 (transient-define-prefix magit-remote-configure (remote)
    313   "Configure a remote."
    314   :man-page "git-remote"
    315   [:description
    316    (lambda ()
    317      (concat (propertize "Configure " 'face 'transient-heading)
    318              (propertize (oref (transient-prefix-object) scope)
    319                          'face 'magit-branch-remote)))
    320    ("u" magit-remote.<remote>.url)
    321    ("U" magit-remote.<remote>.fetch)
    322    ("s" magit-remote.<remote>.pushurl)
    323    ("S" magit-remote.<remote>.push)
    324    ("O" magit-remote.<remote>.tagopt)]
    325   (interactive
    326    (list (or (and (not current-prefix-arg)
    327                   (not (and magit-remote-direct-configure
    328                             (eq transient-current-command 'magit-remote)))
    329                   (magit-get-current-remote))
    330              (magit--read-remote-scope))))
    331   (transient-setup 'magit-remote-configure nil nil :scope remote))
    332 
    333 (defun magit--read-remote-scope (&optional obj)
    334   (magit-read-remote
    335    (if obj
    336        (format "Set %s for remote"
    337                (format (oref obj variable) "<name>"))
    338      "Configure remote")))
    339 
    340 (transient-define-infix magit-remote.<remote>.url ()
    341   :class 'magit--git-variable:urls
    342   :scope #'magit--read-remote-scope
    343   :variable "remote.%s.url"
    344   :multi-value t
    345   :history-key 'magit-remote.<remote>.*url)
    346 
    347 (transient-define-infix magit-remote.<remote>.fetch ()
    348   :class 'magit--git-variable
    349   :scope #'magit--read-remote-scope
    350   :variable "remote.%s.fetch"
    351   :multi-value t)
    352 
    353 (transient-define-infix magit-remote.<remote>.pushurl ()
    354   :class 'magit--git-variable:urls
    355   :scope #'magit--read-remote-scope
    356   :variable "remote.%s.pushurl"
    357   :multi-value t
    358   :history-key 'magit-remote.<remote>.*url
    359   :seturl-arg "--push")
    360 
    361 (transient-define-infix magit-remote.<remote>.push ()
    362   :class 'magit--git-variable
    363   :scope #'magit--read-remote-scope
    364   :variable "remote.%s.push")
    365 
    366 (transient-define-infix magit-remote.<remote>.tagopt ()
    367   :class 'magit--git-variable:choices
    368   :scope #'magit--read-remote-scope
    369   :variable "remote.%s.tagOpt"
    370   :choices '("--no-tags" "--tags"))
    371 
    372 ;;; Transfer Utilities
    373 
    374 (defun magit--push-remote-variable (&optional branch short)
    375   (unless branch
    376     (setq branch (magit-get-current-branch)))
    377   (magit--propertize-face
    378    (if (or (not branch) magit-prefer-push-default)
    379        (if short "pushDefault" "remote.pushDefault")
    380      (if short "pushRemote" (format "branch.%s.pushRemote" branch)))
    381    'bold))
    382 
    383 (defun magit--select-push-remote (prompt-suffix)
    384   (let* ((branch (or (magit-get-current-branch)
    385                      (user-error "No branch is checked out")))
    386          (remote (magit-get-push-remote branch))
    387          (changed nil))
    388     (when (or current-prefix-arg
    389               (not remote)
    390               (not (member remote (magit-list-remotes))))
    391       (setq changed t)
    392       (setq remote
    393             (magit-read-remote (format "Set %s and %s"
    394                                        (magit--push-remote-variable)
    395                                        prompt-suffix)))
    396       (setf (magit-get (magit--push-remote-variable branch)) remote))
    397     (list branch remote changed)))
    398 
    399 ;;; _
    400 (provide 'magit-remote)
    401 ;;; magit-remote.el ends here