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