如何使用 Git 进行子模块稀疏校验?

How to do submodule sparse-checkout with Git?

有很多关于稀疏结帐的文章和问题。不幸的是我没有找到具体的例子。我想让下面的示例工作:

创建子模块

cd ~
mkdir sub && cd $_
git init 
mkdir foo && touch $_/foo
mkdir bar && touch $_/bar
git add .
git commit -am "Initial commit"

创建项目

cd ~
mkdir project && cd $_
git init
git submodule add ../sub sub
git config -f .gitmodules submodule.sub.shallow true
git config -f .gitmodules submodule.sub.sparsecheckout true
echo foo/* > .git/modules/sub/info/sparse-checkout
git commit -am "Initial commit"
git submodule update
cd sub
git checkout .

这一点我希望 sub 文件夹只包含 foo/foo 而不是 bar。不幸的是它不起作用:

$ ls
bar/ foo/

我怎样才能让它发挥作用?

git submodule add 自己检出子模块。

对我来说成功的是:

git init
# I did not find a way to add submodule in 1 step without checking out
git clone --depth=1 --no-checkout ../sub sub
git submodule add ../sub sub
git submodule absorbgitdirs
# note there is no "submodule.sub.sparsecheckout" key
git -C sub config core.sparseCheckout true
# note quoted wildcards to avoid their expansion by shell
echo 'foo/*' >>.git/modules/sub/info/sparse-checkout
git submodule update --force --checkout sub

添加到max630's

  • since Git 2.25 (Q1 2020), you would use the new command git sparse-checkout

  • 在 Git 2.28(2020 年第 3 季度)中,记录了 sparse checkout 设置对子模块的影响。

意思是如果你让主存储库本身稀疏地检出,除了子模块(已经稀疏,如 max630 的回答),在主存储库上使用 git sparse-checkout 不会对子模块产生负面影响(即. 正在进行工作时误将其删除)。

commit e7d7c73 (10 Jun 2020) by Elijah Newren (newren)
(由 Junio C Hamano -- gitster -- in commit 81be89e 合并,2020 年 6 月 22 日)

git-sparse-checkout: clarify interactions with submodules

Signed-off-by: Elijah Newren
Reviewed-by: Derrick Stolee

Ignoring the sparse-checkout feature momentarily, if one has a submodule and creates local branches within it with unpushed changes and maybe adds some untracked files to it, then we would want to avoid accidentally removing such a submodule.

So, for example with git.git, if you run

git checkout v2.13.0

then the sha1collisiondetection/ submodule is NOT removed even though it did not exist as a submodule until v2.14.0.

Similarly, if you only had v2.13.0 checked out previously and ran

git checkout v2.14.0

the sha1collisiondetection/ submodule would NOT be automatically initialized despite being part of v2.14.0.

In both cases, git requires submodules to be initialized or deinitialized separately.

Further, we also have special handling for submodules in other commands such as clean, which requires two --force flags to delete untracked submodules, and some commands have a --recurse-submodules flag.

sparse-checkout is very similar to checkout, as evidenced by the similar name -- it adds and removes files from the working copy.

However, for the same avoid-data-loss reasons we do not want to remove a submodule from the working copy with checkout, we do not want to do it with sparse-checkout either.

So submodules need to be separately initialized or deinitialized; changing sparse-checkout rules should not automatically trigger the removal or vivification of submodules.

I believe the previous wording in git sparse-checkout about submodules was only about this particular issue.

Unfortunately, the previous wording could be interpreted to imply that submodules should be considered active regardless of sparsity patterns.

Update the wording to avoid making such an implication.

It may be helpful to consider two example situations where the differences in wording become important:

In the future, we want users to be able to run commands like

git clone --sparse=moduleA --recurse-submodules $REPO_URL

and have sparsity paths automatically set up and have submodules within the sparsity paths be automatically initialized.

We do not want all submodules in any path to be automatically initialized with that command.

Similarly, we want to be able to do things like

git -c sparse.restrictCmds grep --recurse-submodules $REV $PATTERN

and search through $REV for $PATTERN within the recorded sparsity patterns.

We want it to recurse into submodules within those sparsity patterns, but do not want to recurse into directories that do not match the sparsity patterns in search of a possible submodule.

因此 the documentation 现在包括:

If your repository contains one or more submodules, then submodules are populated based on interactions with the git submodule command.
Specifically, git submodule init -- <path> will ensure the submodule at <path> is present, while git submodule deinit [-f] -- <path> will remove the files for the submodule at <path> (including any untracked files, uncommitted changes, and unpushed history).
Similar to how sparse-checkout removes files from the working tree but still leaves entries in the index, deinitialized submodules are removed from the working directory but still have an entry in the index.

Since submodules may have unpushed changes or untracked files, removing them could result in data loss.
Thus, changing sparse inclusion/exclusion rules will not cause an already checked out submodule to be removed from the working copy.
Said another way, just as checkout will not cause submodules to be automatically removed or initialized even when switching between branches that remove or add submodules, using sparse-checkout to reduce or expand the scope of "interesting" files will not cause submodules to be automatically deinitialized or initialized either.

Further, the above facts mean that there are multiple reasons that "tracked" files might not be present in the working copy: sparsity pattern application from sparse-checkout, and submodule initialization state.
Thus, commands like git grep that work on tracked files in the working copy may return results that are limited by either or both of these restrictions.


在 Git 2.31(2021 年第一季度)中,“git grep"(man) 已调整为仅限于稀疏结帐路径。

因为您可能需要 git grep 在稀疏检出子模块中,这很重要。

参见 commit 42d906b (09 Feb 2021) by Matheus Tavares (matheustavares)
(由 Junio C Hamano -- gitster -- in commit 628c13c 合并,2021 年 2 月 25 日)

grep: honor sparse-checkout on working tree searches

Suggested-by: Elijah Newren
Signed-off-by: Matheus Tavares
Reviewed-by: Elijah Newren

On a sparse checked out repository, git grep(man) (without --cached) ends up searching the cache when an entry matches the search pathspec and has the SKIP_WORKTREE bit set.

This is confusing both because the sparse paths are not expected to be in a working tree search (as they are not checked out), and because the output mixes working tree and cache results without distinguishing them.
(Note that grep also resorts to the cache on working tree searches that include --assume-unchanged paths.
But the whole point in that case is to assume that the contents of the index entry and the file are the same.
This does not apply to the case of sparse paths, where the file isn't even expected to be present.)

Fix that by teaching grep to honor the sparse-checkout rules for working tree searches.
If the user wants to grep paths outside the current sparse-checkout definition, they may either update the sparsity rules to materialize the files, or use --cached to search all blobs registered in the index.