magit-worktree.el (7752B)
1 ;;; magit-worktree.el --- worktree support -*- lexical-binding: t -*- 2 3 ;; Copyright (C) 2010-2021 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 ;; SPDX-License-Identifier: GPL-3.0-or-later 12 13 ;; Magit is free software; you can redistribute it and/or modify it 14 ;; under the terms of the GNU General Public License as published by 15 ;; the Free Software Foundation; either version 3, or (at your option) 16 ;; any later version. 17 ;; 18 ;; Magit is distributed in the hope that it will be useful, but WITHOUT 19 ;; ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 20 ;; or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public 21 ;; License for more details. 22 ;; 23 ;; You should have received a copy of the GNU General Public License 24 ;; along with Magit. If not, see http://www.gnu.org/licenses. 25 26 ;;; Commentary: 27 28 ;; This library implements support for `git-worktree'. 29 30 ;;; Code: 31 32 (require 'magit) 33 34 ;;; Options 35 36 (defcustom magit-worktree-read-directory-name-function 'read-directory-name 37 "Function used to read a directory for worktree commands. 38 This is called with one argument, the prompt, and can be used 39 to e.g. use a base directory other than `default-directory'. 40 Used by `magit-worktree-checkout' and `magit-worktree-branch'." 41 :package-version '(magit . "3.0.0") 42 :group 'magit-commands 43 :type 'function) 44 45 ;;; Commands 46 47 ;;;###autoload (autoload 'magit-worktree "magit-worktree" nil t) 48 (transient-define-prefix magit-worktree () 49 "Act on a worktree." 50 :man-page "git-worktree" 51 [["Create new" 52 ("b" "worktree" magit-worktree-checkout) 53 ("c" "branch and worktree" magit-worktree-branch)] 54 ["Commands" 55 ("m" "Move worktree" magit-worktree-move) 56 ("k" "Delete worktree" magit-worktree-delete) 57 ("g" "Visit worktree" magit-worktree-status)]]) 58 59 ;;;###autoload 60 (defun magit-worktree-checkout (path branch) 61 "Checkout BRANCH in a new worktree at PATH." 62 (interactive 63 (let ((branch (magit-read-branch-or-commit "Checkout"))) 64 (list (funcall magit-worktree-read-directory-name-function 65 (format "Checkout %s in new worktree: " branch)) 66 branch))) 67 (magit-run-git "worktree" "add" (expand-file-name path) branch) 68 (magit-diff-visit-directory path)) 69 70 ;;;###autoload 71 (defun magit-worktree-branch (path branch start-point &optional force) 72 "Create a new BRANCH and check it out in a new worktree at PATH." 73 (interactive 74 `(,(funcall magit-worktree-read-directory-name-function 75 "Create worktree: ") 76 ,@(magit-branch-read-args "Create and checkout branch") 77 ,current-prefix-arg)) 78 (magit-run-git "worktree" "add" (if force "-B" "-b") 79 branch (expand-file-name path) start-point) 80 (magit-diff-visit-directory path)) 81 82 ;;;###autoload 83 (defun magit-worktree-move (worktree path) 84 "Move WORKTREE to PATH." 85 (interactive 86 (list (magit-completing-read "Move worktree" 87 (cdr (magit-list-worktrees)) 88 nil t nil nil 89 (magit-section-value-if 'worktree)) 90 (funcall magit-worktree-read-directory-name-function 91 "Move worktree to: "))) 92 (if (file-directory-p (expand-file-name ".git" worktree)) 93 (user-error "You may not move the main working tree") 94 (let ((preexisting-directory (file-directory-p path))) 95 (when (and (zerop (magit-call-git "worktree" "move" worktree 96 (expand-file-name path))) 97 (not (file-exists-p default-directory)) 98 (derived-mode-p 'magit-status-mode)) 99 (kill-buffer) 100 (magit-diff-visit-directory 101 (if preexisting-directory 102 (concat (file-name-as-directory path) 103 (file-name-nondirectory worktree)) 104 path))) 105 (magit-refresh)))) 106 107 (defun magit-worktree-delete (worktree) 108 "Delete a worktree, defaulting to the worktree at point. 109 The primary worktree cannot be deleted." 110 (interactive 111 (list (magit-completing-read "Delete worktree" 112 (cdr (magit-list-worktrees)) 113 nil t nil nil 114 (magit-section-value-if 'worktree)))) 115 (if (file-directory-p (expand-file-name ".git" worktree)) 116 (user-error "Deleting %s would delete the shared .git directory" worktree) 117 (let ((primary (file-name-as-directory (caar (magit-list-worktrees))))) 118 (magit-confirm-files (if magit-delete-by-moving-to-trash 'trash 'delete) 119 (list "worktree")) 120 (when (file-exists-p worktree) 121 (let ((delete-by-moving-to-trash magit-delete-by-moving-to-trash)) 122 (delete-directory worktree t magit-delete-by-moving-to-trash))) 123 (if (file-exists-p default-directory) 124 (magit-run-git "worktree" "prune") 125 (let ((default-directory primary)) 126 (magit-run-git "worktree" "prune")) 127 (when (derived-mode-p 'magit-status-mode) 128 (kill-buffer) 129 (magit-status-setup-buffer primary)))))) 130 131 (defun magit-worktree-status (worktree) 132 "Show the status for the worktree at point. 133 If there is no worktree at point, then read one in the 134 minibuffer. If the worktree at point is the one whose 135 status is already being displayed in the current buffer, 136 then show it in Dired instead." 137 (interactive 138 (list (or (magit-section-value-if 'worktree) 139 (magit-completing-read 140 "Show status for worktree" 141 (cl-delete (directory-file-name (magit-toplevel)) 142 (magit-list-worktrees) 143 :test #'equal :key #'car))))) 144 (magit-diff-visit-directory worktree)) 145 146 ;;; Sections 147 148 (defvar magit-worktree-section-map 149 (let ((map (make-sparse-keymap))) 150 (define-key map [remap magit-visit-thing] 'magit-worktree-status) 151 (define-key map [remap magit-delete-thing] 'magit-worktree-delete) 152 map) 153 "Keymap for `worktree' sections.") 154 155 (defun magit-insert-worktrees () 156 "Insert sections for all worktrees. 157 If there is only one worktree, then insert nothing." 158 (let ((worktrees (magit-list-worktrees))) 159 (when (> (length worktrees) 1) 160 (magit-insert-section (worktrees) 161 (magit-insert-heading "Worktrees:") 162 (let* ((cols 163 (mapcar 164 (pcase-lambda (`(,path ,barep ,commit ,branch)) 165 (cons (cond 166 (branch (propertize 167 branch 'font-lock-face 168 (if (equal branch (magit-get-current-branch)) 169 'magit-branch-current 170 'magit-branch-local))) 171 (commit (propertize (magit-rev-abbrev commit) 172 'font-lock-face 'magit-hash)) 173 (barep "(bare)")) 174 path)) 175 worktrees)) 176 (align (1+ (-max (--map (string-width (car it)) cols))))) 177 (pcase-dolist (`(,head . ,path) cols) 178 (magit-insert-section (worktree path) 179 (insert head) 180 (insert (make-string (- align (length head)) ?\s)) 181 (insert (let ((r (file-relative-name path)) 182 (a (abbreviate-file-name path))) 183 (if (< (string-width r) (string-width a)) r a))) 184 (insert ?\n)))) 185 (insert ?\n))))) 186 187 ;;; _ 188 (provide 'magit-worktree) 189 ;;; magit-worktree.el ends here