magit-remote.el (16090B)
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 (list "Prune stale refspec %s and branch %%s" refspec) 210 (list "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