diff options
| author | Kyle Meyer <kyle@kyleam.com> | 2022-01-17 21:08:31 -0500 |
|---|---|---|
| committer | Kyle Meyer <kyle@kyleam.com> | 2022-01-23 16:30:36 -0500 |
| commit | 2d94a9b363a7dc851e4c88dd16552bd6b2fd9238 (patch) | |
| tree | 3f561450b5fffc4ec5c7ca3bb59ae27e765f7a12 | |
| parent | c7fd760d997ebacc82ba2f6dffaace945eb0693d (diff) | |
Add interface to 'git sparse-checkout'
Git has had limited and mostly hidden sparse checkout support for a
long time, but Git 2.25 exposed the functionality via a porcelain
command. Alongside this, it added and promoted a restricted pattern
set ("cone mode") that improves the performance of the sparse
checkouts, particularly with large trees.
Add a new library that provides an interface to 'git sparse-checkout',
supporting only the new cone mode.
Bind magit-sparse-checkout to ">" in magit-mode-map. Aside from the
lack of available letters, this key choice is based on the (perhaps
silly) hope that ">" will be easy enough to associate with a command
that converts the working tree from a bigger checkout to a smaller
one.
Hold off on adding a binding to magit-dispatch given that
magit-dispatch is mostly limited to letters at the moment and
magit-sparse-checkout is unlikely to be a heavily used command.
Note that magit-sparse-checkout-{set,add} read directories with
magit-completing-read-multiple*, which means that directory names
can't include crm-separator characters. However, this same limitation
is already in place for magit-read-files/magit:--.
| -rw-r--r-- | default.mk | 1 | ||||
| -rw-r--r-- | docs/RelNotes/3.4.0.org | 3 | ||||
| -rw-r--r-- | docs/magit.org | 55 | ||||
| -rw-r--r-- | lisp/Makefile | 103 | ||||
| -rw-r--r-- | lisp/magit-mode.el | 1 | ||||
| -rw-r--r-- | lisp/magit-sparse-checkout.el | 153 | ||||
| -rw-r--r-- | lisp/magit.el | 1 |
7 files changed, 266 insertions, 51 deletions
@@ -90,6 +90,7 @@ ELS += magit-patch.el ELS += magit-bisect.el ELS += magit-stash.el ELS += magit-blame.el +ELS += magit-sparse-checkout.el ELS += magit-submodule.el ELS += magit-subtree.el ELS += magit-ediff.el diff --git a/docs/RelNotes/3.4.0.org b/docs/RelNotes/3.4.0.org index e305918..3572db3 100644 --- a/docs/RelNotes/3.4.0.org +++ b/docs/RelNotes/3.4.0.org @@ -13,6 +13,9 @@ - Added new face ~git-rebase-action~ to allow customization of the face used for the action words in git-rebase-todo files. +- New transient ~magit-sparse-checkout~ provides an interface to the + ~git sparse-checkout~ command, introduced in Git v2.25. #4102 + ** Fixes since v3.3.0 - Automatic saving of file-visiting buffers was broken inside remote diff --git a/docs/magit.org b/docs/magit.org index 1634c05..da53f5c 100644 --- a/docs/magit.org +++ b/docs/magit.org @@ -6990,6 +6990,61 @@ Also see [[man:git-worktree]] If the worktree at point is the one whose status is already being displayed in the current buffer, then show it in Dired instead. +** Sparse checkouts + +Sparse checkouts provide a way to restrict the working tree to a +subset of directories. See [[man:git-sparse-checkout]] + +*Warning*: Git introduced the ~git sparse-checkout~ command in version +2.25 and still advertises it as experimental and subject to change. +Magit's interface should be considered the same. In particular, if +Git introduces a backward incompatible change, Magit's sparse checkout +functionality may be updated in a way that requires a more recent Git +version. + +- Key: > (magit-sparse-checkout) :: + + This transient prefix command binds the following suffix commands + and displays them in a temporary buffer until a suffix is invoked. + +- Key: > e (magit-sparse-checkout-enable) :: + + This command initializes a sparse checkout that includes only the + files in the top-level directory. + + Note that ~magit-sparse-checkout-set~ and + ~magit-sparse-checkout-add~ automatically initialize a sparse + checkout if necessary. However, you may want to call + ~magit-sparse-checkout-enable~ explicitly to re-initialize a sparse + checkout after calling ~magit-sparse-checkout-disable~, to pass + additional arguments to ~git sparse-checkout init~, or to execute + the initialization asynchronously. + +- Key: > s (magit-sparse-checkout-set) :: + + This command takes a list of directories and configures the sparse + checkout to include only files in those subdirectories. Any + previously included directories are excluded unless they are in the + provided list of directories. + +- Key: > a (magit-sparse-checkout-add) :: + + This command is like ~magit-sparse-checkout-set~, but instead adds + the specified list of directories to the set of directories that is + already included in the sparse checkout. + +- Key: > r (magit-sparse-checkout-reapply) :: + + This command applies the currently configured sparse checkout + patterns to the working tree. This is useful to call if excluded + files have been checked out after operations such as merging or + rebasing. + +- Key: > d (magit-sparse-checkout-disable) :: + + This command restores the full checkout. To return to the previous + sparse checkout, call ~magit-sparse-checkout-enable~. + ** Bundle Also see [[man:git-bundle]] diff --git a/lisp/Makefile b/lisp/Makefile index b0ef31f..d56731c 100644 --- a/lisp/Makefile +++ b/lisp/Makefile @@ -14,59 +14,60 @@ magit-utils.elc: magit-section.elc: ifeq "$(BUILD_MAGIT_LIBGIT)" "true" magit-libgit.elc: -magit-git.elc: magit-utils.elc magit-section.elc magit-libgit.elc +magit-git.elc: magit-utils.elc magit-section.elc magit-libgit.elc else -magit-git.elc: magit-utils.elc magit-section.elc +magit-git.elc: magit-utils.elc magit-section.elc endif -magit-mode.elc: magit-section.elc magit-git.elc -magit-margin.elc: magit-section.elc magit-mode.elc -magit-process.elc: magit-utils.elc magit-section.elc \ - magit-git.elc magit-mode.elc -magit-transient.elc: magit-git.elc magit-mode.elc magit-process.elc -magit-autorevert.elc: magit-git.elc magit-process.elc -magit-core.elc: magit-margin.elc magit-utils.elc \ - magit-section.elc magit-git.elc \ - magit-transient.elc magit-mode.elc \ - magit-process.elc magit-autorevert.elc -magit-diff.elc: git-commit.elc magit-core.elc -magit-log.elc: magit-core.elc magit-diff.elc -magit-wip.elc: magit-core.elc magit-log.elc -magit-reflog.elc: magit-core.elc magit-log.elc -magit-apply.elc: magit-core.elc magit-diff.elc magit-wip.elc -magit-repos.elc: magit-core.elc -magit.elc: git-commit.elc magit-core.elc magit-diff.elc \ - magit-log.elc magit-apply.elc magit-repos.elc -magit-status.elc: magit.elc -magit-refs.elc: magit.elc -magit-files.elc: magit.elc -magit-reset.elc: magit.elc -magit-branch.elc: magit.elc magit-reset.elc -magit-merge.elc: magit.elc magit-diff.elc -magit-tag.elc: magit.elc -magit-worktree.elc: magit.elc -magit-notes.elc: magit.elc -magit-sequence.elc: magit.elc -magit-commit.elc: magit.elc magit-sequence.elc -magit-remote.elc: magit.elc -magit-clone.elc: magit.elc -magit-fetch.elc: magit.elc -magit-pull.elc: magit.elc magit-remote.elc -magit-push.elc: magit.elc -magit-bisect.elc: magit.elc -magit-stash.elc: magit.elc magit-sequence.elc magit-reflog.elc -magit-blame.elc: magit.elc -magit-obsolete.elc: magit.elc -magit-submodule.elc: magit.elc -magit-patch.elc: magit.elc -magit-subtree.elc: magit.elc -magit-ediff.elc: magit.elc -magit-gitignore.elc: magit.elc -magit-bundle.elc: magit.elc -magit-extras.elc: magit.elc magit-merge.elc -git-rebase.elc: magit.elc -magit-imenu.elc: magit.elc git-rebase.elc -magit-bookmark.elc: magit.elc -magit-obsolete.elc: magit.elc +magit-mode.elc: magit-section.elc magit-git.elc +magit-margin.elc: magit-section.elc magit-mode.elc +magit-process.elc: magit-utils.elc magit-section.elc \ + magit-git.elc magit-mode.elc +magit-transient.elc: magit-git.elc magit-mode.elc magit-process.elc +magit-autorevert.elc: magit-git.elc magit-process.elc +magit-core.elc: magit-margin.elc magit-utils.elc \ + magit-section.elc magit-git.elc \ + magit-transient.elc magit-mode.elc \ + magit-process.elc magit-autorevert.elc +magit-diff.elc: git-commit.elc magit-core.elc +magit-log.elc: magit-core.elc magit-diff.elc +magit-wip.elc: magit-core.elc magit-log.elc +magit-reflog.elc: magit-core.elc magit-log.elc +magit-apply.elc: magit-core.elc magit-diff.elc magit-wip.elc +magit-repos.elc: magit-core.elc +magit.elc: git-commit.elc magit-core.elc magit-diff.elc \ + magit-log.elc magit-apply.elc magit-repos.elc +magit-status.elc: magit.elc +magit-refs.elc: magit.elc +magit-files.elc: magit.elc +magit-reset.elc: magit.elc +magit-branch.elc: magit.elc magit-reset.elc +magit-merge.elc: magit.elc magit-diff.elc +magit-tag.elc: magit.elc +magit-worktree.elc: magit.elc +magit-notes.elc: magit.elc +magit-sequence.elc: magit.elc +magit-commit.elc: magit.elc magit-sequence.elc +magit-remote.elc: magit.elc +magit-clone.elc: magit.elc +magit-fetch.elc: magit.elc +magit-pull.elc: magit.elc magit-remote.elc +magit-push.elc: magit.elc +magit-bisect.elc: magit.elc +magit-stash.elc: magit.elc magit-sequence.elc magit-reflog.elc +magit-blame.elc: magit.elc +magit-obsolete.elc: magit.elc +magit-submodule.elc: magit.elc +magit-patch.elc: magit.elc +magit-subtree.elc: magit.elc +magit-ediff.elc: magit.elc +magit-gitignore.elc: magit.elc +magit-sparse-checkout.elc: magit.elc +magit-bundle.elc: magit.elc +magit-extras.elc: magit.elc magit-merge.elc +git-rebase.elc: magit.elc +magit-imenu.elc: magit.elc git-rebase.elc +magit-bookmark.elc: magit.elc +magit-obsolete.elc: magit.elc ## Build ############################################################# diff --git a/lisp/magit-mode.el b/lisp/magit-mode.el index f32bf0d..95d4898 100644 --- a/lisp/magit-mode.el +++ b/lisp/magit-mode.el @@ -403,6 +403,7 @@ recommended value." (define-key map "%" 'magit-worktree) (define-key map "$" 'magit-process-buffer) (define-key map "!" 'magit-run) + (define-key map ">" 'magit-sparse-checkout) (define-key map (kbd "C-c C-c") 'magit-dispatch) (define-key map (kbd "C-c C-e") 'magit-edit-thing) (define-key map (kbd "C-c C-o") 'magit-browse-thing) diff --git a/lisp/magit-sparse-checkout.el b/lisp/magit-sparse-checkout.el new file mode 100644 index 0000000..fea2743 --- /dev/null +++ b/lisp/magit-sparse-checkout.el @@ -0,0 +1,153 @@ +;;; magit-sparse-checkout.el --- sparse checkout support for Magit -*- lexical-binding: t -*- + +;; Copyright (C) 2022 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: Kyle Meyer <kyle@kyleam.com> +;; Maintainer: Jonas Bernoulli <jonas@bernoul.li> + +;; SPDX-License-Identifier: GPL-3.0-or-later + +;; 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 provides an interface to the `git sparse-checkout' +;; command. It's been possible to define sparse checkouts since Git +;; v1.7.0 by adding patterns to $GIT_DIR/info/sparse-checkout and +;; calling `git read-tree -mu HEAD' to update the index and working +;; tree. However, Git v2.25 introduced the `git sparse-checkout' +;; command along with "cone mode", which restricts the possible +;; patterns to directories to provide better performance. +;; +;; The goal of this library is to support the `git sparse-checkout' +;; command operating in cone mode. + +;;; Code: + +(require 'magit) + +;;; Utilities + +(defun magit-sparse-checkout-enabled-p () + "Return non-nil if working tree is a sparse checkout." + (magit-get-boolean "core.sparsecheckout")) + +(defun magit-sparse-checkout--assert-version () + ;; Older versions of Git have the ability to define sparse checkout + ;; patterns in .git/info/sparse-checkout, but the sparse-checkout + ;; command isn't available until 2.25.0. + (when (magit-git-version< "2.25.0") + (user-error "`git sparse-checkout' not available until Git v2.25"))) + +(defun magit-sparse-checkout--auto-enable () + (if (magit-sparse-checkout-enabled-p) + (unless (magit-get-boolean "core.sparsecheckoutcone") + (user-error + "Magit's sparse checkout functionality requires cone mode")) + ;; Note: Don't use `magit-sparse-checkout-enable' because it's + ;; asynchronous. + (magit-run-git "sparse-checkout" "init" "--cone"))) + +(defun magit-sparse-checkout-directories () + "Return directories that are recursively included in the sparse checkout. +See the `git sparse-checkout' manpage for details about +\"recursive\" versus \"parent\" directories in cone mode." + (and (magit-get-boolean "core.sparsecheckoutcone") + (mapcar #'file-name-as-directory + (magit-git-lines "sparse-checkout" "list")))) + +;;; Commands + +;;;###autoload (autoload 'magit-sparse-checkout "magit-sparse-checkout" nil t) +(transient-define-prefix magit-sparse-checkout () + "Create and manage sparse checkouts." + :man-page "git-sparse-checkout" + ["Actions" + [:if-not magit-sparse-checkout-enabled-p + ("e" "Enable sparse checkout" magit-sparse-checkout-enable)] + [:if magit-sparse-checkout-enabled-p + ("d" "Disable sparse checkout" magit-sparse-checkout-disable) + ("r" "Reapply rules" magit-sparse-checkout-reapply)] + [("s" "Set directories" magit-sparse-checkout-set) + ("a" "Add directories" magit-sparse-checkout-add)]]) + +;;;###autoload +(defun magit-sparse-checkout-enable () + "Convert the working tree to a sparse checkout." + (interactive) + (magit-sparse-checkout--assert-version) + (magit-run-git-async "sparse-checkout" "init" "--cone")) + +;;;###autoload +(defun magit-sparse-checkout-set (directories) + "Restrict working tree to DIRECTORIES. +To extend rather than override the currently configured +directories, call `magit-sparse-checkout-add' instead." + (interactive + (list (magit-completing-read-multiple* + "Include these directories: " + ;; Note: Given that the appeal of sparse checkouts is + ;; dealing with very large trees, listing all subdirectories + ;; may need to be reconsidered. + (magit-revision-directories "HEAD")))) + (magit-sparse-checkout--assert-version) + (magit-sparse-checkout--auto-enable) + (magit-run-git-async "sparse-checkout" "set" directories)) + +;;;###autoload +(defun magit-sparse-checkout-add (directories) + "Add DIRECTORIES to the working tree. +To override rather than extend the currently configured +directories, call `magit-sparse-checkout-set' instead." + (interactive + (list (magit-completing-read-multiple* + "Add these directories: " + ;; Same performance note as in `magit-sparse-checkout-set', + ;; but even more so given the additional processing. + (seq-remove + (let ((re (concat + "\\`" + (regexp-opt (magit-sparse-checkout-directories))))) + (lambda (d) (string-match-p re d))) + (magit-revision-directories "HEAD"))))) + (magit-sparse-checkout--assert-version) + (magit-sparse-checkout--auto-enable) + (magit-run-git-async "sparse-checkout" "add" directories)) + +;;;###autoload +(defun magit-sparse-checkout-reapply () + "Reapply the sparse checkout rules to the working tree. +Some operations such as merging or rebasing may need to check out +files that aren't included in the sparse checkout. Call this +command to reset to the sparse checkout state." + (interactive) + (magit-sparse-checkout--assert-version) + (magit-run-git-async "sparse-checkout" "reapply")) + +;;;###autoload +(defun magit-sparse-checkout-disable () + "Convert sparse checkout to full checkout. +Note that disabling the sparse checkout does not clear the +configured directories. Call `magit-sparse-checkout-enable' to +restore the previous sparse checkout." + (interactive) + (magit-sparse-checkout--assert-version) + (magit-run-git-async "sparse-checkout" "disable")) + +;;; _ +(provide 'magit-sparse-checkout) +;;; magit-sparse-checkout.el ends here diff --git a/lisp/magit.el b/lisp/magit.el index 83cba08..80a1646 100644 --- a/lisp/magit.el +++ b/lisp/magit.el @@ -712,6 +712,7 @@ For X11 something like ~/.xinitrc should work.\n" (require 'magit-subtree) (require 'magit-ediff) (require 'magit-gitignore) + (require 'magit-sparse-checkout) (require 'magit-extras) (require 'git-rebase) (require 'magit-imenu) |
