fordon_greeman / voidfiles

Branch: 2079ec5
Switch branches/tags
main
theidioticdev add emacs 2079ec5 22h ago
436 lines · 17.3 KB Raw Blame History
1 ;;; magit-remote.el --- Transfer Git commits -*- lexical-binding:t -*-
2
3 ;; Copyright (C) 2008-2026 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 :refresh-suffixes t
70 :value '("-f")
71 ["Variables"
72 :if (##and magit-remote-direct-configure (transient-scope))
73 ("u" magit-remote.<remote>.url)
74 ("U" magit-remote.<remote>.fetch)
75 ("s" magit-remote.<remote>.pushurl)
76 ("S" magit-remote.<remote>.push)
77 ("O" magit-remote.<remote>.tagopt)
78 ("h" magit-remote.<remote>.followremotehead)]
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 (7 "z" "Unshallow remote" magit-remote-unshallow)]
89 [("d u" magit-update-default-branch)]]
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 (cond ((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 (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-str 2 refspec))
181 (ours (match-str 3 refspec)))
182 (unless (if (string-match "\\*" theirs)
183 (let ((re (replace-match ".*" t t theirs)))
184 (seq-some (##string-match-p re %) 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 (seq-filter (##string-match-p re %)
190 tracking-refs))
191 (list (car (member ours tracking-refs)))))
192 stale)))))
193 (if (not stale)
194 (message "No stale refspecs for remote %S" remote)
195 (if (= (length stale)
196 (length refspecs))
197 (magit-read-char-case
198 (format "All of %s's refspecs are stale. " remote) nil
199 (?s "replace with [d]efault refspec"
200 (magit-set-all
201 (list (format "+refs/heads/*:refs/remotes/%s/*" remote))
202 variable))
203 (?r "[r]emove remote"
204 (magit-call-git "remote" "rm" remote))
205 (?a "[a]abort"
206 (user-error "Abort")))
207 (if (if (length= stale 1)
208 (pcase-let ((`(,refspec . ,refs) (car stale)))
209 (magit-confirm 'prune-stale-refspecs
210 (list "Prune stale refspec %s and branch %%s" refspec)
211 (list "Prune stale refspec %s and %%d branches" refspec)
212 nil refs))
213 (magit-confirm 'prune-stale-refspecs nil
214 (format "Prune %%d stale refspecs and %d branches"
215 (length (mapcan (##copy-sequence (cdr %)) stale)))
216 nil
217 (mapcar (pcase-lambda (`(,refspec . ,refs))
218 (concat refspec "\n"
219 (mapconcat (##concat " " %) refs "\n")))
220 stale)))
221 (pcase-dolist (`(,refspec . ,refs) stale)
222 (magit-call-git "config" "--unset" variable
223 (regexp-quote refspec))
224 (magit--log-action
225 (lambda (refs)
226 (format "Deleting %d branches" (length refs)))
227 (lambda (ref)
228 (format "Deleting branch %s (was %s)" ref
229 (magit-rev-parse "--short" ref)))
230 refs)
231 (dolist (ref refs)
232 (magit-call-git "update-ref" "-d" ref)))
233 (user-error "Abort")))
234 (magit-refresh))))
235
236 ;;;###autoload
237 (defun magit-remote-set-head (remote &optional branch)
238 "Set the local representation of REMOTE's default branch.
239 Query REMOTE and set the symbolic-ref refs/remotes/<remote>/HEAD
240 accordingly. With a prefix argument query for the branch to be
241 used, which allows you to select an incorrect value if you fancy
242 doing that."
243 (interactive
244 (let ((remote (magit-read-remote "Set HEAD for remote")))
245 (list remote
246 (and current-prefix-arg
247 (magit-read-remote-branch (format "Set %s/HEAD to" remote)
248 remote nil nil t)))))
249 (magit-run-git "remote" "set-head" remote (or branch "--auto")))
250
251 ;;;###autoload
252 (defun magit-remote-unset-head (remote)
253 "Unset the local representation of REMOTE's default branch.
254 Delete the symbolic-ref \"refs/remotes/<remote>/HEAD\"."
255 (interactive (list (magit-read-remote "Unset HEAD for remote")))
256 (magit-run-git "remote" "set-head" remote "--delete"))
257
258 ;;;###autoload(autoload 'magit-update-default-branch "magit-remote" nil t)
259 (transient-define-suffix magit-update-default-branch ()
260 "Update name of the default branch after upstream changed it."
261 :description "Update default branch"
262 :inapt-if-not #'magit-get-some-remote
263 (interactive)
264 (pcase-let ((`(,_remote ,oldname) (magit--get-default-branch))
265 (`( ,remote ,newname) (magit--get-default-branch t)))
266 (cond
267 ((equal oldname newname)
268 (setq oldname
269 (read-string
270 (format
271 "Name of default branch is still `%s', %s\n%s `%s': " oldname
272 "but the upstreams of some local branches might need updating."
273 "Name of upstream branches to replace with" newname)))
274 (magit--set-default-branch newname oldname)
275 (magit-refresh))
276 (t
277 (unless oldname
278 (setq oldname
279 (magit-read-other-local-branch
280 (format "Name of old default branch to be renamed to `%s'"
281 newname)
282 newname "master")))
283 (cond
284 ((y-or-n-p (format "Default branch changed from `%s' to `%s' on %s.%s?"
285 oldname newname remote " Do the same locally"))
286 (magit--set-default-branch newname oldname)
287 (magit-refresh))
288 ((user-error "Abort")))))))
289
290 ;;;###autoload
291 (defun magit-remote-unshallow (remote)
292 "Convert a shallow remote into a full one.
293 If only a single refspec is set and it does not contain a
294 wildcard, then also offer to replace it with the standard
295 refspec."
296 (interactive (list (or (magit-get-current-remote)
297 (magit-read-remote "Delete remote"))))
298 (let ((refspecs (magit-get-all "remote" remote "fetch"))
299 (standard (format "+refs/heads/*:refs/remotes/%s/*" remote)))
300 (when (and (length= refspecs 1)
301 (not (string-search "*" (car refspecs)))
302 (yes-or-no-p (format "Also replace refspec %s with %s? "
303 (car refspecs)
304 standard)))
305 (magit-set standard "remote" remote "fetch"))
306 (magit-git-fetch "--unshallow" remote)))
307
308 ;;; Configure
309
310 ;;;###autoload(autoload 'magit-remote-configure "magit-remote" nil t)
311 (transient-define-prefix magit-remote-configure (remote)
312 "Configure a remote."
313 :man-page "git-remote"
314 [:description (##concat
315 (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 ("h" magit-remote.<remote>.followremotehead)]
323 (interactive
324 (list (or (and (not current-prefix-arg)
325 (not (and magit-remote-direct-configure
326 (eq transient-current-command 'magit-remote)))
327 (magit-get-current-remote))
328 (magit--read-remote-scope))))
329 (transient-setup 'magit-remote-configure nil nil :scope remote))
330
331 (defun magit--read-remote-scope (&optional obj)
332 (magit-read-remote
333 (if obj
334 (format "Set %s for remote"
335 (format (oref obj variable) "<name>"))
336 "Configure remote")))
337
338 (transient-define-infix magit-remote.<remote>.url ()
339 :class 'magit--git-variable:urls
340 :scope #'magit--read-remote-scope
341 :variable "remote.%s.url"
342 :multi-value t
343 :history-key 'magit-remote.<remote>.*url)
344
345 (transient-define-infix magit-remote.<remote>.fetch ()
346 :class 'magit--git-variable
347 :scope #'magit--read-remote-scope
348 :variable "remote.%s.fetch"
349 :multi-value t)
350
351 (transient-define-infix magit-remote.<remote>.pushurl ()
352 :class 'magit--git-variable:urls
353 :scope #'magit--read-remote-scope
354 :variable "remote.%s.pushurl"
355 :multi-value t
356 :history-key 'magit-remote.<remote>.*url
357 :seturl-arg "--push")
358
359 (transient-define-infix magit-remote.<remote>.push ()
360 :class 'magit--git-variable
361 :scope #'magit--read-remote-scope
362 :variable "remote.%s.push")
363
364 (transient-define-infix magit-remote.<remote>.tagopt ()
365 :class 'magit--git-variable:choices
366 :scope #'magit--read-remote-scope
367 :variable "remote.%s.tagOpt"
368 :choices '("--no-tags" "--tags"))
369
370 (transient-define-infix magit-remote.<remote>.followremotehead ()
371 "How \"git fetch\" handles updates to \"remotes/<remote>/HEAD\".
372
373 This command sets the local value of the Git variable
374 `remote.<remote>.followRemoteHEAD', where <remote> is a stand-in for
375 the actual remote, as displayed in the menu, from which this command
376 is invoked. This variable is documented in (man \"git-config(1)\").
377
378 Unfortunately Git does not provide a variable to set a default for
379 all remotes of all repositories, but you can set the global value for
380 a remote name used in multiple repository, which will then be used as
381 the default for that remote in all repositories. You should consider
382 using \"always\" for remotes named \"origin\".
383
384 git config set --global remote.origin.followRemoteHEAD always"
385 :class 'magit--git-variable:choices
386 :scope #'magit--read-remote-scope
387 :variable "remote.%s.followRemoteHEAD"
388 :choices '("create" "always" "warn")
389 :default "create")
390
391 ;;; Transfer Utilities
392
393 (defun magit--push-remote-variable (&optional branch short)
394 (unless branch
395 (setq branch (magit-get-current-branch)))
396 (magit--propertize-face
397 (if (or (not branch) magit-prefer-push-default)
398 (if short "pushDefault" "remote.pushDefault")
399 (if short "pushRemote" (format "branch.%s.pushRemote" branch)))
400 'bold))
401
402 (defun magit--select-push-remote (prompt-suffix)
403 (let* ((branch (or (magit-get-current-branch)
404 (user-error "No branch is checked out")))
405 (remote (magit-get-push-remote branch))
406 (changed nil))
407 (when (or current-prefix-arg
408 (not remote)
409 (not (member remote (magit-list-remotes))))
410 (setq changed t)
411 (setq remote
412 (magit-read-remote (format "Set %s and %s"
413 (magit--push-remote-variable)
414 prompt-suffix)))
415 (setf (magit-get (magit--push-remote-variable branch)) remote))
416 (list branch remote changed)))
417
418 ;;; _
419 (provide 'magit-remote)
420 ;; Local Variables:
421 ;; read-symbol-shorthands: (
422 ;; ("and$" . "cond-let--and$")
423 ;; ("thread$" . "cond-let--thread$")
424 ;; ("when$" . "cond-let--when$")
425 ;; ("and-let*" . "cond-let--and-let*")
426 ;; ("and-let" . "cond-let--and-let")
427 ;; ("if-let*" . "cond-let--if-let*")
428 ;; ("if-let" . "cond-let--if-let")
429 ;; ("when-let*" . "cond-let--when-let*")
430 ;; ("when-let" . "cond-let--when-let")
431 ;; ("while-let*" . "cond-let--while-let*")
432 ;; ("while-let" . "cond-let--while-let")
433 ;; ("match-string" . "match-string")
434 ;; ("match-str" . "match-string-no-properties"))
435 ;; End:
436 ;;; magit-remote.el ends here