;;; magit-remote.el --- transfer Git commits -*- lexical-binding: t -*- ;; Copyright (C) 2008-2018 The Magit Project Contributors ;; ;; You should have received a copy of the AUTHORS.md file which ;; lists all contributors. If not, see http://magit.vc/authors. ;; Author: Jonas Bernoulli ;; Maintainer: Jonas Bernoulli ;; Magit is free software; you can redistribute it and/or modify it ;; under the terms of the GNU General Public License as published by ;; the Free Software Foundation; either version 3, or (at your option) ;; any later version. ;; ;; Magit is distributed in the hope that it will be useful, but WITHOUT ;; ANY WARRANTY; without even the implied warranty of MERCHANTABILITY ;; or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public ;; License for more details. ;; ;; You should have received a copy of the GNU General Public License ;; along with Magit. If not, see http://www.gnu.org/licenses. ;;; Commentary: ;; This library implements remote commands. ;;; Code: (require 'magit) ;;; Options (defcustom magit-remote-add-set-remote.pushDefault 'ask-if-unset "Whether to set the value of `remote.pushDefault' after adding a remote. If `ask', then always ask. If `ask-if-unset', then ask, but only if the variable isn't set already. If nil, then don't ever set. If the value is a string, then set without asking, provided that the name of the added remote is equal to that string and the variable isn't already set." :package-version '(magit . "2.4.0") :group 'magit-commands :type '(choice (const :tag "ask if unset" ask-if-unset) (const :tag "always ask" ask) (string :tag "set if named") (const :tag "don't set"))) (defcustom magit-remote-popup-show-variables t "Whether the `magit-remote-popup' shows Git variables. When set to nil, no variables are displayed directly in this popup, instead the sub-popup `magit-remote-config-popup' has to be used to view and change remote related variables." :package-version '(magit . "2.12.0") :group 'magit-commands :type 'boolean) ;;; Commands (defvar magit-remote-config-variables) ;;;###autoload (autoload 'magit-remote-popup "magit-remote" nil t) (magit-define-popup magit-remote-popup "Popup console for remote commands." :man-page "git-remote" :default-arguments '("-f") :variables (lambda () (and magit-remote-popup-show-variables magit-remote-config-variables)) :switches '("Switches for add" (?f "Fetch after add" "-f")) :actions '((?a "Add" magit-remote-add) (?C "Configure..." magit-remote-config-popup) (?r "Rename" magit-remote-rename) (?p "Prune stale branches" magit-remote-prune) (?k "Remove" magit-remote-remove) (?P "Prune stale refspecs" magit-remote-prune-refspecs)) :max-action-columns 2) (defun magit-read-url (prompt &optional initial-input) (let ((url (magit-read-string-ns prompt initial-input))) (if (string-prefix-p "~" url) (expand-file-name url) url))) ;;;###autoload (defun magit-remote-add (remote url &optional args) "Add a remote named REMOTE and fetch it." (interactive (list (magit-read-string-ns "Remote name") (magit-read-url "Remote url") (magit-remote-arguments))) (if (pcase (list magit-remote-add-set-remote.pushDefault (magit-get "remote.pushDefault")) (`(,(pred stringp) ,_) t) ((or `(ask ,_) `(ask-if-unset nil)) (y-or-n-p (format "Set `remote.pushDefault' to \"%s\"? " remote)))) (progn (magit-call-git "remote" "add" args remote url) (setf (magit-get "remote.pushDefault") remote) (magit-refresh)) (magit-run-git-async "remote" "add" args remote url))) ;;;###autoload (defun magit-remote-rename (old new) "Rename the remote named OLD to NEW." (interactive (let ((remote (magit-read-remote "Rename remote"))) (list remote (magit-read-string-ns (format "Rename %s to" remote))))) (unless (string= old new) (magit-call-git "remote" "rename" old new) (magit-remote--cleanup-push-variables old new) (magit-refresh))) ;;;###autoload (defun magit-remote-remove (remote) "Delete the remote named REMOTE." (interactive (list (magit-read-remote "Delete remote"))) (magit-call-git "remote" "rm" remote) (magit-remote--cleanup-push-variables remote) (magit-refresh)) (defun magit-remote--cleanup-push-variables (remote &optional new-name) (magit-with-toplevel (when (equal (magit-get "remote.pushDefault") remote) (magit-set new-name "remote.pushDefault")) (dolist (var (magit-git-lines "config" "--name-only" "--get-regexp" "^branch\.[^.]*\.pushRemote" (format "^%s$" remote))) (magit-call-git "config" (and (not new-name) "--unset") var new-name)))) (defconst magit--refspec-re "\\`\\(\\+\\)?\\([^:]+\\):\\(.*\\)\\'") ;;;###autoload (defun magit-remote-prune (remote) "Remove stale remote-tracking branches for REMOTE." (interactive (list (magit-read-remote "Prune stale branches of remote"))) (magit-run-git-async "remote" "prune" remote)) ;;;###autoload (defun magit-remote-prune-refspecs (remote) "Remove stale refspecs for REMOTE. A refspec is stale if there no longer exists at least one branch on the remote that would be fetched due to that refspec. A stale refspec is problematic because its existence causes Git to refuse to fetch according to the remaining non-stale refspecs. If only stale refspecs remain, then offer to either delete the remote or to replace the stale refspecs with the default refspec. Also remove the remote-tracking branches that were created due to the now stale refspecs. Other stale branches are not removed." (interactive (list (magit-read-remote "Prune refspecs of remote"))) (let* ((tracking-refs (magit-list-remote-branches remote)) (remote-refs (magit-remote-list-refs remote)) (variable (format "remote.%s.fetch" remote)) (refspecs (magit-get-all variable)) stale) (dolist (refspec refspecs) (when (string-match magit--refspec-re refspec) (let ((theirs (match-string 2 refspec)) (ours (match-string 3 refspec))) (unless (if (string-match "\\*" theirs) (let ((re (replace-match ".*" t t theirs))) (--some (string-match-p re it) remote-refs)) (member theirs remote-refs)) (push (cons refspec (if (string-match "\\*" ours) (let ((re (replace-match ".*" t t ours))) (--filter (string-match-p re it) tracking-refs)) (list (car (member ours tracking-refs))))) stale))))) (if (not stale) (message "No stale refspecs for remote %S" remote) (if (= (length stale) (length refspecs)) (magit-read-char-case (format "All of %s's refspecs are stale. " remote) nil (?s "replace with [d]efault refspec" (magit-set-all (list (format "+refs/heads/*:refs/remotes/%s/*" remote)) variable)) (?r "[r]emove remote" (magit-call-git "remote" "rm" remote)) (?a "or [a]abort" (user-error "Abort"))) (if (if (= (length stale) 1) (pcase-let ((`(,refspec . ,refs) (car stale))) (magit-confirm 'prune-stale-refspecs (format "Prune stale refspec %s and branch %%s" refspec) (format "Prune stale refspec %s and %%i branches" refspec) nil refs)) (magit-confirm 'prune-stale-refspecs nil (format "Prune %%i stale refspecs and %i branches" (length (cl-mapcan (lambda (s) (copy-sequence (cdr s))) stale))) nil (mapcar (pcase-lambda (`(,refspec . ,refs)) (concat refspec "\n" (mapconcat (lambda (b) (concat " " b)) refs "\n"))) stale))) (pcase-dolist (`(,refspec . ,refs) stale) (magit-call-git "config" "--unset" variable (regexp-quote refspec)) (magit--log-action (lambda (refs) (format "Deleting %i branches" (length refs))) (lambda (ref) (format "Deleting branch %s (was %s)" ref (magit-rev-parse "--short" ref))) refs) (dolist (ref refs) (magit-call-git "update-ref" "-d" ref))) (user-error "Abort"))) (magit-refresh)))) ;;;###autoload (defun magit-remote-set-head (remote &optional branch) "Set the local representation of REMOTE's default branch. Query REMOTE and set the symbolic-ref refs/remotes//HEAD accordingly. With a prefix argument query for the branch to be used, which allows you to select an incorrect value if you fancy doing that." (interactive (let ((remote (magit-read-remote "Set HEAD for remote"))) (list remote (and current-prefix-arg (magit-read-remote-branch (format "Set %s/HEAD to" remote) remote nil nil t))))) (magit-run-git "remote" "set-head" remote (or branch "--auto"))) ;;;###autoload (defun magit-remote-unset-head (remote) "Unset the local representation of REMOTE's default branch. Delete the symbolic-ref \"refs/remotes//HEAD\"." (interactive (list (magit-read-remote "Unset HEAD for remote"))) (magit-run-git "remote" "set-head" remote "--delete")) ;;; Configure (defvar magit-remote-config--remote nil) ;;;###autoload (defun magit-remote-config-popup (remote) "Popup console for setting remote variables." (interactive (list (if (or current-prefix-arg (and (eq magit-current-popup 'magit-remote-popup) magit-remote-popup-show-variables)) (magit-read-remote "Configure remote") (magit-remote-config--remote-1)))) (let ((magit-remote-config--remote remote)) (magit-invoke-popup 'magit-remote-config-popup nil nil))) (defvar magit-remote-config-variables '((lambda () (concat (propertize "Configure " 'face 'magit-popup-heading) (propertize (magit-remote-config--remote) 'face 'magit-branch-remote))) (?u "remote.%s.url" magit-set-remote*url magit-format-remote*url) (?U "remote.%s.fetch" magit-set-remote*fetch magit-format-remote*fetch) (?s "remote.%s.pushurl" magit-set-remote*pushurl magit-format-remote*pushurl) (?S "remote.%s.push" magit-set-remote*push magit-format-remote*push) (?O "remote.%s.tagOpt" magit-cycle-remote*tagOpt magit-format-remote*tagOpt))) (defvar magit-remote-config-popup `(:man-page "git-remote" :variables ,magit-remote-config-variables :setup-function magit-remote-config-popup-setup)) (defun magit-remote-config-popup-setup (val def) (magit-popup-default-setup val def) (setq-local magit-remote-config--remote magit-remote-config--remote)) (defun magit-remote-config--remote (&optional prompt) (if prompt (or (and (not current-prefix-arg) (or magit-remote-config--remote (magit-remote-config--remote-1))) (magit-read-remote prompt)) (or magit-remote-config--remote (magit-remote-config--remote-1) ""))) (defun magit-remote-config--remote-1 () (let ((remote (magit-get-upstream-remote))) (if (or (not remote) (equal remote ".")) (and (magit-remote-p "origin") "origin") remote))) (defun magit-set-remote*url (remote urls) "Set the variable `url' for the remote named REMOTE to URLS." (interactive (magit-remote-config--read-args "url" "Urls: ")) (magit-remote-config--set-url remote "url" urls)) (defun magit-set-remote*fetch (remote values) "Set the variable `fetch' for the remote named REMOTE to VALUES." (interactive (magit-remote-config--read-args "fetch" "Fetch specs: ")) (magit-set-all values "remote" remote "fetch") (magit-refresh)) (defun magit-set-remote*pushurl (remote urls) "Set the variable `pushurl' for the remote named REMOTE to URLS." (interactive (magit-remote-config--read-args "pushurl" "Urls: ")) (magit-remote-config--set-url remote "pushurl" urls "--push")) (defun magit-set-remote*push (remote values) "Set the variable `push' for the remote named REMOTE to VALUES." (interactive (magit-remote-config--read-args "push" "Push specs: ")) (magit-set-all values "remote" remote "push") (magit-refresh)) (defun magit-cycle-remote*tagOpt (remote) (interactive (list (magit-remote-config--remote))) (magit--set-popup-variable (format "remote.%s.tagOpt" remote) '("--no-tags" "--tags") nil)) (defun magit-format-remote*url () (magit-remote-config--format-variable "url")) (defun magit-format-remote*fetch () (magit-remote-config--format-variable "fetch")) (defun magit-format-remote*pushurl () (magit-remote-config--format-variable "pushurl")) (defun magit-format-remote*push () (magit-remote-config--format-variable "push")) (defun magit-format-remote*tagOpt () (let ((remote (magit-remote-config--remote))) (magit--format-popup-variable:choices (format "remote.%s.tagOpts" remote) '("--no-tags" "--tags") nil nil (+ (length remote) 16)))) (defun magit-remote-config--read-args (var prompt) (let* ((remote (magit-remote-config--remote (format "Set `%s' of remote" var))) (value (magit-get-all "remote" remote var))) (list remote (mapcar (lambda (url) (if (string-prefix-p "~" url) (expand-file-name url) url)) (completing-read-multiple prompt nil nil nil (and value (mapconcat #'identity value ","))))))) (defun magit-remote-config--set-url (remote var values &optional arg) (let ((old (magit-get-all "remote" remote var))) (dolist (v (-difference values old)) (magit-call-git "remote" "set-url" arg "--add" remote v)) (dolist (v (-difference old values)) (magit-call-git "remote" "set-url" arg "--delete" remote (concat "^" (regexp-quote v) "$")))) (magit-refresh)) (defun magit-remote-config--format-variable (variable) (magit--format-popup-variable:values (format "remote.%s.%s" (magit-remote-config--remote) variable) 25)) ;;; _ (provide 'magit-remote) ;;; magit-remote.el ends here