config

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

magit-remote.el (16000B)


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