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