dotemacs

My Emacs configuration
git clone git://git.entf.net/dotemacs
Log | Files | Refs | LICENSE

magit-remote.el (14756B)


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