mirror of https://github.com/Chizi123/.emacs.d.git

Chizi123
2018-11-18 76bbd07de7add0f9d13c6914f158d19630fe2f62
commit | author | age
5cb5f7 1 ;;; magit-branch.el --- branch support  -*- lexical-binding: t -*-
C 2
3 ;; Copyright (C) 2010-2018  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 ;; Magit is free software; you can redistribute it and/or modify it
12 ;; under the terms of the GNU General Public License as published by
13 ;; the Free Software Foundation; either version 3, or (at your option)
14 ;; any later version.
15 ;;
16 ;; Magit is distributed in the hope that it will be useful, but WITHOUT
17 ;; ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
18 ;; or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public
19 ;; License for more details.
20 ;;
21 ;; You should have received a copy of the GNU General Public License
22 ;; along with Magit.  If not, see http://www.gnu.org/licenses.
23
24 ;;; Commentary:
25
26 ;; This library implements support for branches.  It defines popups
27 ;; and commands for creating, checking out, manipulating, and
28 ;; configuring branches.  Commands defined here are mainly concerned
29 ;; with branches as pointers, commands that deal with what a branch
30 ;; points at, are defined elsewhere.
31
32 ;;; Code:
33
34 (eval-when-compile
35   (require 'subr-x))
36
37 (require 'magit)
38 (require 'magit-collab)
39 (require 'magit-reset)
40
41 ;;; Options
42
43 (defcustom magit-branch-read-upstream-first t
44   "Whether to read upstream before name of new branch when creating a branch.
45
46 `nil'      Read the branch name first.
47 `t'        Read the upstream first.
48 `fallback' Read the upstream first, but if it turns out that the chosen
49            value is not a valid upstream (because it cannot be resolved
50            as an existing revision), then treat it as the name of the
51            new branch and continue by reading the upstream next."
52   :package-version '(magit . "2.2.0")
53   :group 'magit-commands
54   :type '(choice (const :tag "read branch name first" nil)
55                  (const :tag "read upstream first" t)
56                  (const :tag "read upstream first, with fallback" fallback)))
57
58 (defcustom magit-branch-prefer-remote-upstream nil
59   "Whether to favor remote upstreams when creating new branches.
60
61 When a new branch is created, then the branch, commit, or stash
62 at point is suggested as the default starting point of the new
63 branch, or if there is no such revision at point the current
64 branch.  In either case the user may choose another starting
65 point.
66
67 If the chosen starting point is a branch, then it may also be set
68 as the upstream of the new branch, depending on the value of the
69 Git variable `branch.autoSetupMerge'.  By default this is done
70 for remote branches, but not for local branches.
71
72 You might prefer to always use some remote branch as upstream.
73 If the chosen starting point is (1) a local branch, (2) whose
74 name matches a member of the value of this option, (3) the
75 upstream of that local branch is a remote branch with the same
76 name, and (4) that remote branch can be fast-forwarded to the
77 local branch, then the chosen branch is used as starting point,
78 but its own upstream is used as the upstream of the new branch.
79
80 Members of this option's value are treated as branch names that
81 have to match exactly unless they contain a character that makes
82 them invalid as a branch name.  Recommended characters to use
83 to trigger interpretation as a regexp are \"*\" and \"^\".  Some
84 other characters which you might expect to be invalid, actually
85 are not, e.g. \".+$\" are all perfectly valid.  More precisely,
86 if `git check-ref-format --branch STRING' exits with a non-zero
87 status, then treat STRING as a regexp.
88
89 Assuming the chosen branch matches these conditions you would end
90 up with with e.g.:
91
92   feature --upstream--> origin/master
93
94 instead of
95
96   feature --upstream--> master --upstream--> origin/master
97
98 Which you prefer is a matter of personal preference.  If you do
99 prefer the former, then you should add branches such as \"master\",
100 \"next\", and \"maint\" to the value of this options."
101   :package-version '(magit . "2.4.0")
102   :group 'magit-commands
103   :type '(repeat string))
104
105 (defcustom magit-branch-adjust-remote-upstream-alist nil
106   "Alist of upstreams to be used when branching from remote branches.
107
108 When creating a local branch from an ephemeral branch located
109 on a remote, e.g. a feature or hotfix branch, then that remote
110 branch should usually not be used as the upstream branch, since
111 the push-remote already allows accessing it and having both the
112 upstream and the push-remote reference the same related branch
113 would be wasteful.  Instead a branch like \"maint\" or \"master\"
114 should be used as the upstream.
115
116 This option allows specifing the branch that should be used as
117 the upstream when branching certain remote branches.  The value
118 is an alist of the form ((UPSTREAM . RULE)...).  The first
119 matching element is used, the following elements are ignored.
120
121 UPSTREAM is the branch to be used as the upstream for branches
122 specified by RULE.  It can be a local or a remote branch.
123
124 RULE can either be a regular expression, matching branches whose
125 upstream should be the one specified by UPSTREAM.  Or it can be
126 a list of the only branches that should *not* use UPSTREAM; all
127 other branches will.  Matching is done after stripping the remote
128 part of the name of the branch that is being branched from.
129
130 If you use a finite set of non-ephemeral branches across all your
131 repositories, then you might use something like:
132
133   ((\"origin/master\" \"master\" \"next\" \"maint\"))
134
135 Or if the names of all your ephemeral branches contain a slash,
136 at least in some repositories, then a good value could be:
137
138   ((\"origin/master\" . \"/\"))
139
140 Of course you can also fine-tune:
141
142   ((\"origin/maint\" . \"\\\\\\=`hotfix/\")
143    (\"origin/master\" . \"\\\\\\=`feature/\"))
144
145 If you use remote branches as UPSTREAM, then you might also want
146 to set `magit-branch-prefer-remote-upstream' to a non-nil value.
147 However, I recommend that you use local branches as UPSTREAM."
148   :package-version '(magit . "2.9.0")
149   :group 'magit-commands
150   :type '(repeat (cons (string :tag "Use upstream")
151                        (choice :tag "for branches"
152                                (regexp :tag "matching")
153                                (repeat :tag "except"
154                                        (string :tag "branch"))))))
155
156 (defcustom magit-branch-rename-push-target t
157   "Whether the push-remote setup is preserved when renaming a branch.
158
159 The command `magit-branch-rename' renames a branch named OLD to
160 NEW.  This option controls how much of the push-remote setup is
161 preserved when doing so.
162
163 When nil, then preserve nothing and unset `branch.OLD.pushRemote'.
164
165 When `local-only', then first set `branch.NEW.pushRemote' to the
166   same value as `branch.OLD.pushRemote', provided the latter is
167   actually set and unless the former already has another value.
168
169 When t, then rename the branch named OLD on the remote specified
170   by `branch.OLD.pushRemote' to NEW, provided OLD exists on that
171   remote and unless NEW already exists on the remote.
172
173 When `forge-only' and the `forge' package is available, then
174   behave like `t' if the remote points to a repository on a forge
175   (currently Github or Gitlab), otherwise like `local-only'.
176
177 Another supported but obsolete value is `github-only'.  It is a
178   misnomer because it now treated as an alias for `forge-only'."
179   :package-version '(magit . "2.90.0")
180   :group 'magit-commands
181   :type '(choice
182           (const :tag "Don't preserve push-remote setup" nil)
183           (const :tag "Preserve push-remote setup" local-only)
184           (const :tag "... and rename corresponding branch on remote" t)
185           (const :tag "... but only if remote is on a forge" forge-only)))
186
187 (defcustom magit-branch-popup-show-variables t
188   "Whether the `magit-branch-popup' shows Git variables.
189 This defaults to t to avoid changing key bindings.  When set to
190 nil, no variables are displayed directly in this popup, instead
191 the sub-popup `magit-branch-config-popup' has to be used to view
192 and change branch related variables."
193   :package-version '(magit . "2.7.0")
194   :group 'magit-commands
195   :type 'boolean)
196
197 (defcustom magit-published-branches '("origin/master")
198   "List of branches that are considered to be published."
199   :package-version '(magit . "2.13.0")
200   :group 'magit-commands
201   :type '(repeat string))
202
203 ;;; Branch Popup
204
205 (defvar magit-branch-config-variables)
206
207 ;;;###autoload (autoload 'magit-branch-popup "magit" nil t)
208 (magit-define-popup magit-branch-popup
209   "Popup console for branch commands."
210   :man-page "git-branch"
211   :variables (lambda ()
212                (and magit-branch-popup-show-variables
213                     magit-branch-config-variables))
214   :actions `((?b "Checkout"              magit-checkout) nil
215              (?C "Configure..."          magit-branch-config-popup)
216              (?l "Checkout local branch" magit-branch-checkout)
217              (?s "Create new spin-off"   magit-branch-spinoff)
218              (?m "Rename"                magit-branch-rename)
219              (?c "Checkout new branch"   magit-branch-and-checkout)
220              (?n "Create new branch"     magit-branch-create)
221              (?x "Reset"                 magit-branch-reset)
222              (?w "Checkout new worktree" magit-worktree-checkout)
223              (?W "Create new worktree"   magit-worktree-branch)
224              (?k "Delete"                magit-branch-delete)
225              ,@(and (not (require (quote forge) nil t))
226                     '((?y "Checkout pull-request"    magit-checkout-pull-request)
227                       (?Y "Create from pull-request" magit-branch-pull-request))))
228   :default-action 'magit-checkout
229   :max-action-columns 3
230   :setup-function 'magit-branch-popup-setup)
231
232 (defun magit-branch-popup-setup (val def)
233   (magit-popup-default-setup val def)
234   (use-local-map (copy-keymap magit-popup-mode-map))
235   (dolist (ev (-filter #'magit-popup-event-p (magit-popup-get :variables)))
236     (local-set-key (vector (magit-popup-event-key ev))
237                    'magit-invoke-popup-action)))
238
239 ;;; Branch Commands
240
241 ;;;###autoload
242 (defun magit-checkout (revision)
243   "Checkout REVISION, updating the index and the working tree.
244 If REVISION is a local branch, then that becomes the current
245 branch.  If it is something else, then `HEAD' becomes detached.
246 Checkout fails if the working tree or the staging area contain
247 changes.
248 \n(git checkout REVISION)."
249   (interactive (list (magit-read-other-branch-or-commit "Checkout")))
250   (when (string-match "\\`heads/\\(.+\\)" revision)
251     (setq revision (match-string 1 revision)))
252   (magit-run-git "checkout" revision))
253
254 ;;;###autoload
255 (defun magit-branch-create (branch start-point &optional args)
256   "Create BRANCH at branch or revision START-POINT.
257 \n(git branch [ARGS] BRANCH START-POINT)."
258   (interactive (magit-branch-read-args "Create branch"))
259   (magit-call-git "branch" args branch start-point)
260   (magit-branch-maybe-adjust-upstream branch start-point)
261   (magit-refresh))
262
263 ;;;###autoload
264 (defun magit-branch-and-checkout (branch start-point &optional args)
265   "Create and checkout BRANCH at branch or revision START-POINT.
266 \n(git checkout [ARGS] -b BRANCH START-POINT)."
267   (interactive (magit-branch-read-args "Create and checkout branch"))
268   (if (string-match-p "^stash@{[0-9]+}$" start-point)
269       (magit-run-git "stash" "branch" branch start-point)
270     (magit-call-git "checkout" args "-b" branch start-point)
271     (magit-branch-maybe-adjust-upstream branch start-point)
272     (magit-refresh)))
273
274 ;;;###autoload
275 (defun magit-branch-or-checkout (arg &optional start-point)
276   "Hybrid between `magit-checkout' and `magit-branch-and-checkout'.
277
278 Ask the user for an existing branch or revision.  If the user
279 input actually can be resolved as a branch or revision, then
280 check that out, just like `magit-checkout' would.
281
282 Otherwise create and checkout a new branch using the input as
283 its name.  Before doing so read the starting-point for the new
284 branch.  This is similar to what `magit-branch-and-checkout'
285 does."
286   (interactive
287    (let ((arg (magit-read-other-branch-or-commit "Checkout")))
288      (list arg
289            (and (not (magit-rev-verify-commit arg))
290                 (magit-read-starting-point "Create and checkout branch" arg)))))
291   (when (string-match "\\`heads/\\(.+\\)" arg)
292     (setq arg (match-string 1 arg)))
293   (if start-point
294       (magit-branch-and-checkout arg start-point (magit-branch-arguments))
295     (magit-checkout arg)))
296
297 ;;;###autoload
298 (defun magit-branch-checkout (branch &optional start-point)
299   "Checkout an existing or new local branch.
300
301 Read a branch name from the user offering all local branches and
302 a subset of remote branches as candidates.  Omit remote branches
303 for which a local branch by the same name exists from the list
304 of candidates.  The user can also enter a completely new branch
305 name.
306
307 - If the user selects an existing local branch, then check that
308   out.
309
310 - If the user selects a remote branch, then create and checkout
311   a new local branch with the same name.  Configure the selected
312   remote branch as push target.
313
314 - If the user enters a new branch name, then create and check
315   that out, after also reading the starting-point from the user.
316
317 In the latter two cases the upstream is also set.  Whether it is
318 set to the chosen START-POINT or something else depends on the
319 value of `magit-branch-adjust-remote-upstream-alist', just like
320 when using `magit-branch-and-checkout'."
321   (interactive
322    (let* ((current (magit-get-current-branch))
323           (local   (magit-list-local-branch-names))
324           (remote  (--filter (and (string-match "[^/]+/" it)
325                                   (not (member (substring it (match-end 0))
326                                                (cons "HEAD" local))))
327                              (magit-list-remote-branch-names)))
328           (choices (nconc (delete current local) remote))
329           (atpoint (magit-branch-at-point))
330           (choice  (magit-completing-read
331                     "Checkout branch" choices
332                     nil nil nil 'magit-revision-history
333                     (or (car (member atpoint choices))
334                         (and atpoint
335                              (car (member (and (string-match "[^/]+/" atpoint)
336                                                (substring atpoint (match-end 0)))
337                                           choices)))))))
338      (cond ((member choice remote)
339             (list (and (string-match "[^/]+/" choice)
340                        (substring choice (match-end 0)))
341                   choice))
342            ((member choice local)
343             (list choice))
344            (t
345             (list choice (magit-read-starting-point "Create" choice))))))
346   (if (not start-point)
347       (magit-checkout branch)
348     (when (magit-anything-modified-p)
349       (user-error "Cannot checkout when there are uncommitted changes"))
350     (magit-branch-and-checkout branch start-point (magit-branch-arguments))
351     (when (magit-remote-branch-p start-point)
352       (pcase-let ((`(,remote . ,remote-branch)
353                    (magit-split-branch-name start-point)))
354         (when (and (equal branch remote-branch)
355                    (not (equal remote (magit-get "remote.pushDefault"))))
356           (magit-set remote "branch" branch "pushRemote"))))))
357
358 (defun magit-branch-maybe-adjust-upstream (branch start-point)
359   (--when-let
360       (or (and (magit-get-upstream-branch branch)
361                (magit-get-indirect-upstream-branch start-point))
362           (and (magit-remote-branch-p start-point)
363                (let ((name (cdr (magit-split-branch-name start-point))))
364                  (car (--first (if (listp (cdr it))
365                                    (not (member name (cdr it)))
366                                  (string-match-p (cdr it) name))
367                                magit-branch-adjust-remote-upstream-alist)))))
368     (magit-call-git "branch" (concat "--set-upstream-to=" it) branch)))
369
370 ;;;###autoload
371 (defun magit-branch-orphan (branch start-point &optional args)
372   "Create and checkout an orphan BRANCH with contents from revision START-POINT.
373 \n(git checkout --orphan [ARGS] BRANCH START-POINT)."
374   (interactive (magit-branch-read-args "Create and checkout orphan branch"))
375   (magit-run-git "checkout" "--orphan" args branch start-point))
376
377 ;;;###autoload
378 (defun magit-branch-pull-request (pr)
379   "Create and configure a new branch from a pull-request.
380 Please see the manual for more information."
381   (interactive (list (magit-read-pull-request "Branch pull request")))
382   (let-alist pr
383     (let* ((upstream (or (--first (magit--github-url-equal
384                                    (magit-get "remote" it "url")
385                                    .base.repo.ssh_url)
386                                   (magit-list-remotes))
387                          (user-error
388                           "Upstream repository %s not available as a remote"
389                           .base.repo.ssh_url)))
390            (upstream-url (magit-get "remote" upstream "url"))
391            (remote .head.repo.owner.login)
392            (branch (magit--pullreq-branch pr t))
393            (pr-branch .head.ref))
394       (if (magit--pullreq-from-upstream-p pr)
395           (let ((tracking (concat upstream "/" pr-branch)))
396             (unless (magit-branch-p tracking)
397               (magit-call-git "fetch" upstream))
398             (let ((inhibit-magit-refresh t))
399               (magit-branch-create branch tracking))
400             (magit-set upstream "branch" branch "pushRemote")
401             (magit-set upstream "branch" branch "pullRequestRemote"))
402         (if (magit-remote-p remote)
403             (let ((url   (magit-get     "remote" remote "url"))
404                   (fetch (magit-get-all "remote" remote "fetch")))
405               (unless (magit--github-url-equal url .head.repo.ssh_url)
406                 (user-error
407                  "Remote `%s' already exists but does not point to %s"
408                  remote url))
409               (unless (member (format "+refs/heads/*:refs/remotes/%s/*" remote)
410                               fetch)
411                 (magit-call-git "remote" "set-branches"
412                                 "--add" remote pr-branch)
413                 (magit-call-git "fetch" remote)))
414           (magit-call-git
415            "remote" "add" "-f" "--no-tags"
416            "-t" pr-branch remote
417            (cond ((or (string-prefix-p "git@" upstream-url)
418                       (string-prefix-p "ssh://git@" upstream-url))
419                   .head.repo.ssh_url)
420                  ((string-prefix-p "https://" upstream-url)
421                   .head.repo.clone_url)
422                  ((string-prefix-p "git://" upstream-url)
423                   .head.repo.git_url)
424                  (t (error "%s has an unexpected format" upstream-url)))))
425         (magit-call-git "branch" branch (concat remote "/" pr-branch))
426         (if (or .locked (not (equal branch pr-branch)))
427             (magit-set upstream "branch" branch "pushRemote")
428           (magit-set remote "branch" branch "pushRemote"))
429         (magit-set remote "branch" branch "pullRequestRemote"))
430       (magit-set "true" "branch" branch "rebase")
431       (magit-call-git "branch" branch
432                       (concat "--set-upstream-to="
433                               (if magit-branch-prefer-remote-upstream
434                                   (concat upstream "/" .base.ref)
435                                 .base.ref)))
436       (magit-set (number-to-string .number) "branch" branch "pullRequest")
437       (magit-set .title                     "branch" branch "description")
438       (magit-refresh)
439       branch)))
440
441 (defun magit-checkout-pull-request (pr)
442   "Create, configure and checkout a new branch from a pull-request.
443 Please see the manual for more information."
444   (interactive (list (magit-read-pull-request "Checkout pull request")))
445   (magit-checkout
446    (let ((inhibit-magit-refresh t))
447      (magit-branch-pull-request pr))))
448
449 (defun magit-branch-read-args (prompt)
450   (let ((args (magit-branch-arguments)))
451     (if magit-branch-read-upstream-first
452         (let ((choice (magit-read-starting-point prompt)))
453           (if (magit-rev-verify choice)
454               (list (magit-read-string-ns
455                      (if magit-completing-read--silent-default
456                          (format "%s (starting at `%s')" prompt choice)
457                        "Name for new branch")
458                      (let ((def (mapconcat #'identity
459                                            (cdr (split-string choice "/"))
460                                            "/")))
461                        (and (member choice (magit-list-remote-branch-names))
462                             (not (member def (magit-list-local-branch-names)))
463                             def)))
464                     choice args)
465             (if (eq magit-branch-read-upstream-first 'fallback)
466                 (list choice (magit-read-starting-point prompt choice) args)
467               (user-error "Not a valid starting-point: %s" choice))))
468       (let ((branch (magit-read-string-ns (concat prompt " named"))))
469         (list branch
470               (magit-read-starting-point prompt branch)
471               args)))))
472
473 ;;;###autoload
474 (defun magit-branch-spinoff (branch &optional from &rest args)
475   "Create new branch from the unpushed commits.
476
477 Create and checkout a new branch starting at and tracking the
478 current branch.  That branch in turn is reset to the last commit
479 it shares with its upstream.  If the current branch has no
480 upstream or no unpushed commits, then the new branch is created
481 anyway and the previously current branch is not touched.
482
483 This is useful to create a feature branch after work has already
484 began on the old branch (likely but not necessarily \"master\").
485
486 If the current branch is a member of the value of option
487 `magit-branch-prefer-remote-upstream' (which see), then the
488 current branch will be used as the starting point as usual, but
489 the upstream of the starting-point may be used as the upstream
490 of the new branch, instead of the starting-point itself.
491
492 If optional FROM is non-nil, then the source branch is reset
493 to `FROM~', instead of to the last commit it shares with its
494 upstream.  Interactively, FROM is only ever non-nil, if the
495 region selects some commits, and among those commits, FROM is
496 the commit that is the fewest commits ahead of the source
497 branch.
498
499 The commit at the other end of the selection actually does not
500 matter, all commits between FROM and `HEAD' are moved to the new
501 branch.  If FROM is not reachable from `HEAD' or is reachable
502 from the source branch's upstream, then an error is raised."
503   (interactive (list (magit-read-string-ns "Spin off branch")
504                      (car (last (magit-region-values 'commit)))
505                      (magit-branch-arguments)))
506   (when (magit-branch-p branch)
507     (user-error "Cannot spin off %s.  It already exists" branch))
508   (if-let ((current (magit-get-current-branch)))
509       (let ((tracked (magit-get-upstream-branch current))
510             base)
511         (when from
512           (unless (magit-rev-ancestor-p from current)
513             (user-error "Cannot spin off %s.  %s is not reachable from %s"
514                         branch from current))
515           (when (and tracked
516                      (magit-rev-ancestor-p from tracked))
517             (user-error "Cannot spin off %s.  %s is ancestor of upstream %s"
518                         branch from tracked)))
519         (let ((magit-process-raise-error t))
520           (magit-call-git "checkout" args "-b" branch current))
521         (--when-let (magit-get-indirect-upstream-branch current)
522           (magit-call-git "branch" "--set-upstream-to" it branch))
523         (when (and tracked
524                    (setq base
525                          (if from
526                              (concat from "^")
527                            (magit-git-string "merge-base" current tracked)))
528                    (not (magit-rev-eq base current)))
529           (magit-call-git "update-ref" "-m"
530                           (format "reset: moving to %s" base)
531                           (concat "refs/heads/" current) base))
532         (magit-refresh))
533     (magit-run-git "checkout" "-b" branch)))
534
535 ;;;###autoload
536 (defun magit-branch-reset (branch to &optional args set-upstream)
537   "Reset a branch to the tip of another branch or any other commit.
538
539 When the branch being reset is the current branch, then do a
540 hard reset.  If there are any uncommitted changes, then the user
541 has to confirm the reset because those changes would be lost.
542
543 This is useful when you have started work on a feature branch but
544 realize it's all crap and want to start over.
545
546 When resetting to another branch and a prefix argument is used,
547 then also set the target branch as the upstream of the branch
548 that is being reset."
549   (interactive
550    (let* ((atpoint (magit-local-branch-at-point))
551           (branch  (magit-read-local-branch "Reset branch" atpoint)))
552      (list branch
553            (magit-completing-read (format "Reset %s to" branch)
554                                   (delete branch (magit-list-branch-names))
555                                   nil nil nil 'magit-revision-history
556                                   (or (and (not (equal branch atpoint)) atpoint)
557                                       (magit-get-upstream-branch branch)))
558            (magit-branch-arguments)
559            current-prefix-arg)))
560   (unless (member "--force" args)
561     (setq args (cons "--force" args)))
562   (if (equal branch (magit-get-current-branch))
563       (if (and (magit-anything-modified-p)
564                (not (yes-or-no-p "Uncommitted changes will be lost.  Proceed? ")))
565           (user-error "Abort")
566         (magit-reset-hard to)
567         (when (and set-upstream (magit-branch-p to))
568           (magit-set-upstream-branch branch to)))
569     (magit-branch-create branch to args)))
570
571 ;;;###autoload
572 (defun magit-branch-delete (branches &optional force)
573   "Delete one or multiple branches.
574 If the region marks multiple branches, then offer to delete
575 those, otherwise prompt for a single branch to be deleted,
576 defaulting to the branch at point."
577   ;; One would expect this to be a command as simple as, for example,
578   ;; `magit-branch-rename'; but it turns out everyone wants to squeeze
579   ;; a bit of extra functionality into this one, including myself.
580   (interactive
581    (let ((branches (magit-region-values 'branch t))
582          (force current-prefix-arg))
583      (if (> (length branches) 1)
584          (magit-confirm t nil "Delete %i branches" nil branches)
585        (setq branches
586              (list (magit-read-branch-prefer-other
587                     (if force "Force delete branch" "Delete branch")))))
588      (unless force
589        (when-let ((unmerged (-remove #'magit-branch-merged-p branches)))
590          (if (magit-confirm 'delete-unmerged-branch
591                "Delete unmerged branch %s"
592                "Delete %i unmerged branches"
593                'noabort unmerged)
594              (setq force branches)
595            (or (setq branches (-difference branches unmerged))
596                (user-error "Abort")))))
597      (list branches force)))
598   (let* ((refs (mapcar #'magit-ref-fullname branches))
599          (ambiguous (--remove it refs)))
600     (when ambiguous
601       (user-error
602        "%s ambiguous.  Please cleanup using git directly."
603        (let ((len (length ambiguous)))
604          (cond
605           ((= len 1)
606            (format "%s is" (-first #'magit-ref-ambiguous-p branches)))
607           ((= len (length refs))
608            (format "These %s names are" len))
609           (t
610            (format "%s of these names are" len))))))
611     (cond
612      ((string-match "^refs/remotes/\\([^/]+\\)" (car refs))
613       (let* ((remote (match-string 1 (car refs)))
614              (offset (1+ (length remote))))
615         ;; Assume the branches actually still exists on the remote.
616         (magit-run-git-async
617          "push" remote (--map (concat ":" (substring it offset)) branches))
618         ;; If that is not the case, then this deletes the tracking branches.
619         (set-process-sentinel
620          magit-this-process
621          (apply-partially 'magit-delete-remote-branch-sentinel refs))))
622      ((> (length branches) 1)
623       (setq branches (delete (magit-get-current-branch) branches))
624       (mapc 'magit-branch-maybe-delete-pr-remote branches)
625       (mapc 'magit-branch-unset-pushRemote branches)
626       (magit-run-git "branch" (if force "-D" "-d") branches))
627      (t ; And now for something completely different.
628       (let* ((branch (car branches))
629              (prompt (format "Branch %s is checked out.  " branch)))
630         (when (equal branch (magit-get-current-branch))
631           (pcase (if (or (equal branch "master")
632                          (not (magit-rev-verify "master")))
633                      (magit-read-char-case prompt nil
634                        (?d "[d]etach HEAD & delete" 'detach)
635                        (?a "[a]bort"                'abort))
636                    (magit-read-char-case prompt nil
637                      (?d "[d]etach HEAD & delete"     'detach)
638                      (?c "[c]heckout master & delete" 'master)
639                      (?a "[a]bort"                    'abort)))
640             (`detach (unless (or (equal force '(4))
641                                  (member branch force)
642                                  (magit-branch-merged-p branch t))
643                        (magit-confirm 'delete-unmerged-branch
644                          "Delete unmerged branch %s" ""
645                          nil (list branch)))
646                      (magit-call-git "checkout" "--detach"))
647             (`master (unless (or (equal force '(4))
648                                  (member branch force)
649                                  (magit-branch-merged-p branch "master"))
650                        (magit-confirm 'delete-unmerged-branch
651                          "Delete unmerged branch %s" ""
652                          nil (list branch)))
653                      (magit-call-git "checkout" "master"))
654             (`abort  (user-error "Abort")))
655           (setq force t))
656         (magit-branch-maybe-delete-pr-remote branch)
657         (magit-branch-unset-pushRemote branch)
658         (magit-run-git "branch" (if force "-D" "-d") branch))))))
659
660 (put 'magit-branch-delete 'interactive-only t)
661
662 (defun magit-branch-maybe-delete-pr-remote (branch)
663   (when-let ((remote (magit-get "branch" branch "pullRequestRemote")))
664     (let* ((variable (format "remote.%s.fetch" remote))
665            (refspecs (magit-get-all variable)))
666       (unless (member (format "+refs/heads/*:refs/remotes/%s/*" remote)
667                       refspecs)
668         (let ((refspec
669                (if (equal (magit-get "branch" branch "pushRemote") remote)
670                    (format "+refs/heads/%s:refs/remotes/%s/%s"
671                            branch remote branch)
672                  (let ((merge (magit-get "branch" branch "merge")))
673                    (and merge
674                         (string-prefix-p "refs/heads/" merge)
675                         (setq merge (substring merge 11))
676                         (format "+refs/heads/%s:refs/remotes/%s/%s"
677                                 merge remote merge))))))
678           (when (member refspec refspecs)
679             (if (and (= (length refspecs) 1)
680                      (magit-confirm 'delete-pr-remote
681                        (format "Also delete remote %s (%s)" remote
682                                "no pull-request branch remains")))
683                 (magit-call-git "remote" "rm" remote)
684               (magit-call-git "config" "--unset" variable
685                               (regexp-quote refspec)))))))))
686
687 (defun magit-branch-unset-pushRemote (branch)
688   (magit-set nil "branch" branch "pushRemote"))
689
690 (defun magit-delete-remote-branch-sentinel (refs process event)
691   (when (memq (process-status process) '(exit signal))
692     (if (= (process-exit-status process) 0)
693         (magit-process-sentinel process event)
694       (if-let ((rest (-filter #'magit-ref-exists-p refs)))
695           (progn
696             (process-put process 'inhibit-refresh t)
697             (magit-process-sentinel process event)
698             (setq magit-this-error nil)
699             (message "Some remote branches no longer exist.  %s"
700                      "Deleting just the local tracking refs instead...")
701             (dolist (ref rest)
702               (magit-call-git "update-ref" "-d" ref))
703             (magit-refresh)
704             (message "Deleting local remote-tracking refs...done"))
705         (magit-process-sentinel process event)))))
706
707 ;;;###autoload
708 (defun magit-branch-rename (old new &optional force)
709   "Rename the branch named OLD to NEW.
710
711 With a prefix argument FORCE, rename even if a branch named NEW
712 already exists.
713
714 If `branch.OLD.pushRemote' is set, then unset it.  Depending on
715 the value of `magit-branch-rename-push-target' (which see) maybe
716 set `branch.NEW.pushRemote' and maybe rename the push-target on
717 the remote."
718   (interactive
719    (let ((branch (magit-read-local-branch "Rename branch")))
720      (list branch
721            (magit-read-string-ns (format "Rename branch '%s' to" branch)
722                                  nil 'magit-revision-history)
723            current-prefix-arg)))
724   (when (string-match "\\`heads/\\(.+\\)" old)
725     (setq old (match-string 1 old)))
726   (when (equal old new)
727     (user-error "Old and new branch names are the same"))
728   (magit-call-git "branch" (if force "-M" "-m") old new)
729   (when magit-branch-rename-push-target
730     (let ((remote (magit-get-push-remote old))
731           (old-specific (magit-get "branch" old "pushRemote"))
732           (new-specific (magit-get "branch" new "pushRemote")))
733       (when (and old-specific (or force (not new-specific)))
734         ;; Keep the target setting branch specific, even if that is
735         ;; redundant.  But if a branch by the same name existed before
736         ;; and the rename isn't forced, then do not change a leftover
737         ;; setting.  Such a leftover setting may or may not conform to
738         ;; what we expect here...
739         (magit-set old-specific "branch" new "pushRemote"))
740       (when (and (equal (magit-get-push-remote new) remote)
741                  ;; ...and if it does not, then we must abort.
742                  (not (eq magit-branch-rename-push-target 'local-only))
743                  (or (not (memq magit-branch-rename-push-target
744                                 '(forge-only github-only)))
745                      (and (require (quote forge) nil t)
746                           (fboundp 'forge--forge-remote-p)
747                           (forge--forge-remote-p remote))))
748         (let ((old-target (magit-get-push-branch old t))
749               (new-target (magit-get-push-branch new t))
750               (remote (magit-get-push-remote new)))
751           (when (and old-target
752                      (not new-target)
753                      (magit-y-or-n-p (format "Also rename %S to %S on %S"
754                                              old new remote)))
755             ;; Rename on (i.e. within) the remote, but only if the
756             ;; destination ref doesn't exist yet.  If that ref already
757             ;; exists, then it probably is of some value and we better
758             ;; not touch it.  Ignore what the local ref points at,
759             ;; i.e. if the local and the remote ref didn't point at
760             ;; the same commit before the rename then keep it that way.
761             (magit-call-git "push" "-v" remote
762                             (format "%s:refs/heads/%s" old-target new)
763                             (format ":refs/heads/%s" old)))))))
764   (magit-branch-unset-pushRemote old)
765   (magit-refresh))
766
767 ;;;###autoload
768 (defun magit-branch-shelve (branch)
769   "Shelve a BRANCH.
770 Rename \"refs/heads/BRANCH\" to \"refs/shelved/BRANCH\",
771 and also rename the respective reflog file."
772   (interactive (list (magit-read-other-local-branch "Shelve branch")))
773   (let ((old (concat "refs/heads/"   branch))
774         (new (concat "refs/shelved/" branch)))
775     (magit-git "update-ref" new old "")
776     (magit--rename-reflog-file old new)
777     (magit-branch-unset-pushRemote branch)
778     (magit-run-git "branch" "-D" branch)))
779
780 ;;;###autoload
781 (defun magit-branch-unshelve (branch)
782   "Unshelve a BRANCH
783 Rename \"refs/shelved/BRANCH\" to \"refs/heads/BRANCH\",
784 and also rename the respective reflog file."
785   (interactive
786    (list (magit-completing-read
787           "Unshelve branch"
788           (--map (substring it 8)
789                  (magit-list-refnames "refs/shelved"))
790           nil t)))
791   (let ((old (concat "refs/shelved/" branch))
792         (new (concat "refs/heads/"   branch)))
793     (magit-git "update-ref" new old "")
794     (magit--rename-reflog-file old new)
795     (magit-run-git "update-ref" "-d" old)))
796
797 (defun magit--rename-reflog-file (old new)
798   (let ((old (magit-git-dir (concat "logs/" old)))
799         (new (magit-git-dir (concat "logs/" new))))
800     (when (file-exists-p old)
801       (make-directory (file-name-directory new) t)
802       (rename-file old new t))))
803
804 ;;; Config Popup
805
806 (defvar magit-branch-config-branch nil)
807
808 ;;;###autoload
809 (defun magit-branch-config-popup (branch)
810   "Popup console for setting branch variables."
811   (interactive
812    (list (if (or current-prefix-arg
813                  (and (eq magit-current-popup 'magit-branch-popup)
814                       magit-branch-popup-show-variables))
815              (magit-read-local-branch "Configure branch")
816            (magit-get-current-branch))))
817   (let ((magit-branch-config-branch branch))
818     (magit-invoke-popup 'magit-branch-config-popup nil nil)))
819
820 (defvar magit-branch-config-variables
821   '((lambda ()
822       (concat
823        (propertize "Configure " 'face 'magit-popup-heading)
824        (propertize (magit-branch-config-branch) 'face 'magit-branch-local)))
825     (?d "branch.%s.description"
826         magit-edit-branch*description
827         magit-format-branch*description)
828     (?u "branch.%s.merge"
829         magit-set-branch*merge/remote
830         magit-format-branch*merge/remote)
831     (?r "branch.%s.rebase"
832         magit-cycle-branch*rebase
833         magit-format-branch*rebase)
834     (?p "branch.%s.pushRemote"
835         magit-cycle-branch*pushRemote
836         magit-format-branch*pushRemote)
837     "Configure repository defaults"
838     (?\M-r "pull.rebase"
839            magit-cycle-pull.rebase
840            magit-format-pull.rebase)
841     (?\M-p "remote.pushDefault"
842            magit-cycle-remote.pushDefault
843            magit-format-remote.pushDefault)
844     "Configure branch creation"
845     (?U "branch.autoSetupMerge"
846         magit-cycle-branch*autoSetupMerge
847         magit-format-branch*autoSetupMerge)
848     (?R "branch.autoSetupRebase"
849         magit-cycle-branch*autoSetupRebase
850         magit-format-branch*autoSetupRebase)))
851
852 (defvar magit-branch-config-popup
853   `(:man-page "git-branch"
854     :variables ,magit-branch-config-variables
855     :setup-function magit-branch-config-popup-setup))
856
857 (defun magit-branch-config-popup-setup (val def)
858   (magit-popup-default-setup val def)
859   (setq-local magit-branch-config-branch magit-branch-config-branch)
860   (use-local-map (copy-keymap magit-popup-mode-map))
861   (dolist (ev (-filter #'magit-popup-event-p (magit-popup-get :variables)))
862     (local-set-key (vector (magit-popup-event-key ev))
863                    'magit-invoke-popup-action)))
864
865 (defun magit-branch-config-branch (&optional prompt)
866   (if prompt
867       (or (and (not current-prefix-arg)
868                (or magit-branch-config-branch
869                    (magit-get-current-branch)))
870           (magit-read-local-branch prompt))
871     (or magit-branch-config-branch
872         (magit-get-current-branch)
873         "<name>")))
874
875 ;;; Config Commands and Inserters
876
877 ;;;###autoload
878 (defun magit-edit-branch*description (branch)
879   "Edit the description of the current branch.
880 With a prefix argument edit the description of another branch.
881
882 The description for the branch named NAME is stored in the Git
883 variable `branch.<name>.description'."
884   (interactive (list (magit-branch-config-branch "Edit branch description")))
885   (magit-run-git-with-editor "branch" "--edit-description" branch))
886
887 (defun magit-edit-branch*description-check-buffers ()
888   (and buffer-file-name
889        (string-match-p "/\\(BRANCH\\|EDIT\\)_DESCRIPTION\\'" buffer-file-name)
890        (add-hook 'with-editor-post-finish-hook
891                  (lambda ()
892                    (when (derived-mode-p 'magit-popup-mode)
893                      (magit-refresh-popup-buffer)))
894                  nil t)))
895
896 (add-hook 'find-file-hook 'magit-edit-branch*description-check-buffers)
897
898 (defun magit-format-branch*description ()
899   (let* ((branch (magit-branch-config-branch))
900          (width (+ (length branch) 19))
901          (var (format "branch.%s.description" branch)))
902     (concat var " " (make-string (- width (length var)) ?\s)
903             (if-let ((value (magit-get var)))
904                 (propertize (car (split-string value "\n"))
905                             'face 'magit-popup-option-value)
906               (propertize "unset" 'face 'magit-popup-disabled-argument)))))
907
908 ;;;###autoload
909 (defun magit-set-branch*merge/remote (branch upstream)
910   "Set or unset the upstream of the current branch.
911 With a prefix argument do so for another branch.
912
913 When the branch in question already has an upstream then simply
914 unsets it.  Invoke this command again to set another upstream.
915
916 Together the Git variables `branch.<name>.remote' and
917 `branch.<name>.merge' define the upstream branch of the local
918 branch named NAME.  The value of `branch.<name>.remote' is the
919 name of the upstream remote.  The value of `branch.<name>.merge'
920 is the full reference of the upstream branch, on the remote.
921
922 Non-interactively, when UPSTREAM is non-nil, then always set it
923 as the new upstream, regardless of whether another upstream was
924 already set.  When nil, then always unset."
925   (interactive
926    (let ((branch (magit-branch-config-branch "Change upstream of branch")))
927      (list branch (and (not (magit-get-upstream-branch branch))
928                        (magit-read-upstream-branch branch)))))
929   (magit-set-upstream-branch branch upstream)
930   (magit-refresh))
931
932 (defun magit-format-branch*merge/remote ()
933   (let* ((branch (magit-branch-config-branch))
934          (width (+ (length branch) 20))
935          (varM (format "branch.%s.merge" branch))
936          (varR (format "branch.%s.remote" branch))
937          (face (if (equal (magit-get varR) ".")
938                    'magit-branch-local
939                  'magit-branch-remote)))
940     (concat varM (make-string (- width (length varM)) ?\s)
941             (if-let ((value (magit-get varM)))
942                 (propertize value 'face face)
943               (propertize "unset" 'face 'magit-popup-disabled-argument))
944             "\n   " varR (make-string (- width (length varR)) ?\s)
945             (if-let ((value (magit-get varR)))
946                 (propertize value 'face face)
947               (propertize "unset" 'face 'magit-popup-disabled-argument)))))
948
949 ;;;###autoload
950 (defun magit-cycle-branch*rebase (branch)
951   "Cycle the value of `branch.<name>.rebase' for the current branch.
952 With a prefix argument cycle the value for another branch.
953
954 The Git variables `branch.<name>.rebase' controls whether pulling
955 into the branch named NAME is done by rebasing that branch onto
956 the fetched branch or by merging that branch.
957
958 When `true' then pulling is done by rebasing.
959 When `false' then pulling is done by merging.
960
961 When that variable is undefined then the value of `pull.rebase'
962 is used instead.  It defaults to `false'."
963   (interactive (list (magit-branch-config-branch
964                       "Cycle branch.<name>.rebase for")))
965   (magit--set-popup-variable (format "branch.%s.rebase" branch)
966                              '("true" "false")
967                              "false" "pull.rebase"))
968
969 (defun magit-format-branch*rebase ()
970   (let ((branch (magit-branch-config-branch)))
971     (magit--format-popup-variable:choices
972      (format "branch.%s.rebase" branch)
973      '("true" "false")
974      "false" "pull.rebase"
975      (+ (length branch) 20))))
976
977 ;;;###autoload
978 (defun magit-cycle-branch*pushRemote (branch)
979   "Cycle the value of `branch.<name>.pushRemote' for the current branch.
980 With a prefix argument cycle the value for another branch.
981
982 The Git variable `branch.<name>.pushRemote' specifies the remote
983 that the branch named NAME is usually pushed to.  The value has
984 to be the name of an existing remote.
985
986 If that variable is undefined, then the value of the Git variable
987 `remote.pushDefault' is used instead, provided that it is defined,
988 which by default it is not."
989   (interactive (list (magit-branch-config-branch
990                       "Cycle branch.<name>.pushRemote for")))
991   (magit--set-popup-variable (format "branch.%s.pushRemote" branch)
992                              (magit-list-remotes)
993                              "remote.pushDefault"))
994
995 (defun magit-format-branch*pushRemote ()
996   (let ((branch (magit-branch-config-branch)))
997     (magit--format-popup-variable:choices
998      (format "branch.%s.pushRemote" branch)
999      (magit-list-remotes)
1000      nil "remote.pushDefault"
1001      (+ (length branch) 20))))
1002
1003 ;;;###autoload
1004 (defun magit-cycle-pull.rebase ()
1005   "Cycle the repository-local value of `pull.rebase'.
1006
1007 The Git variable `pull.rebase' specifies whether pulling is done
1008 by rebasing or by merging.  It can be overwritten using the Git
1009 variable `branch.<name>.rebase'.
1010
1011 When `true' then pulling is done by rebasing.
1012 When `false' (the default) then pulling is done by merging."
1013   (interactive)
1014   (magit--set-popup-variable "pull.rebase" '("true" "false") "false"))
1015
1016 (defun magit-format-pull.rebase ()
1017   (magit--format-popup-variable:choices
1018    "pull.rebase" '("true" "false") "false" nil 19))
1019
1020 ;;;###autoload
1021 (defun magit-cycle-remote.pushDefault ()
1022   "Cycle the repository-local value of `remote.pushDefault'.
1023
1024 The Git variable `remote.pushDefault' specifies the remote that
1025 local branches are usually pushed to.  It can be overwritten
1026 using the Git variable `branch.<name>.pushRemote'."
1027   (interactive)
1028   (magit--set-popup-variable "remote.pushDefault" (magit-list-remotes)))
1029
1030 (defun magit-format-remote.pushDefault ()
1031   (magit--format-popup-variable:choices
1032    "remote.pushDefault" (magit-list-remotes) nil nil 19))
1033
1034 ;;;###autoload
1035 (defun magit-cycle-branch*autoSetupMerge ()
1036   "Cycle the repository-local value of `branch.autoSetupMerge'.
1037
1038 The Git variable `branch.autoSetupMerge' under what circumstances
1039 creating a branch (named NAME) should result in the variables
1040 `branch.<name>.merge' and `branch.<name>.remote' being set
1041 according to the starting point used to create the branch.  If
1042 the starting point isn't a branch, then these variables are never
1043 set.
1044
1045 When `always' then the variables are set regardless of whether
1046 the starting point is a local or a remote branch.
1047
1048 When `true' (the default) then the variable are set when the
1049 starting point is a remote branch, but not when it is a local
1050 branch.
1051
1052 When `false' then the variables are never set."
1053   (interactive)
1054   (magit--set-popup-variable "branch.autoSetupMerge"
1055                              '("always" "true" "false") "true"))
1056
1057 (defun magit-format-branch*autoSetupMerge ()
1058   (magit--format-popup-variable:choices
1059    "branch.autoSetupMerge" '("always" "true" "false") "true" nil 23))
1060
1061 ;;;###autoload
1062 (defun magit-cycle-branch*autoSetupRebase ()
1063   "Cycle the repository-local value of `branch.autoSetupRebase'.
1064
1065 The Git variable `branch.autoSetupRebase' specifies whether
1066 creating a branch (named NAME) should result in the variable
1067 `branch.<name>.rebase' being set to `true'.
1068
1069 When `always' then the variable is set regardless of whether the
1070 starting point is a local or a remote branch.
1071
1072 When `local' then the variable are set when the starting point
1073 is a local branch, but not when it is a remote branch.
1074
1075 When `remote' then the variable are set when the starting point
1076 is a remote branch, but not when it is a local branch.
1077
1078 When `never' (the default) then the variable is never set."
1079   (interactive)
1080   (magit--set-popup-variable "branch.autoSetupRebase"
1081                              '("always" "local" "remote" "never") "never"))
1082
1083 (defun magit-format-branch*autoSetupRebase ()
1084   (magit--format-popup-variable:choices
1085    "branch.autoSetupRebase"
1086    '("always" "local" "remote" "never")
1087    "never" nil 23))
1088
1089 ;;; _
1090 (provide 'magit-branch)
1091 ;;; magit-branch.el ends here