Git 稀疏检出:目录看不见了,为什么 git ls-files 仍然能列出来
先分清本地看见什么与仓库记录什么
大型仓库同时放着网页、接口和文档,当前只修改网页时,可以用稀疏检出减少工作区实际展开的文件。操作之后,文档目录可能从文件管理器里消失,但这并不表示提交已经删除文档。磁盘上能看见的路径,与 Git 索引保存的跟踪清单,是两种不同的观察对象。
cone 模式以目录为选择单位。选择 apps/web 会展开这个目录下面的全部文件,同时保留仓库根目录,以及通向目标的各级祖先目录中的直属文件。因此根目录 README 和 apps/README 仍然出现是预期行为,不能把它理解成只允许一个目录存在的严格隔离。
在一次性仓库里观察三份证据
下面脚本需要 Bash 和 Git 二点三十九及以上版本。保存后用 bash 运行,它在系统临时目录创建独立仓库,退出时只清理自己建立的目录。脚本隔离常见 Git 仓库环境变量,跳过全局与系统配置,并在每次调用中关闭钩子和提交签名,身份仅写进实验仓库。
AI生成概念示意图,非真实界面
(
set -euo pipefail
demo=$(mktemp -d "${TMPDIR:-/tmp}/sparse-demo.XXXXXX")
trap 'rm -rf -- "$demo"' EXIT
unset GIT_DIR GIT_WORK_TREE GIT_INDEX_FILE GIT_COMMON_DIR
unset GIT_OBJECT_DIRECTORY GIT_ALTERNATE_OBJECT_DIRECTORIES
unset GIT_CONFIG GIT_CONFIG_COUNT GIT_CONFIG_PARAMETERS
export GIT_CONFIG_NOSYSTEM=1 GIT_CONFIG_GLOBAL=/dev/null
git() {
command git -c core.hooksPath=/dev/null -c commit.gpgSign=false \
-c tag.gpgSign=false -c core.autocrlf=false "$@"
}
git init -q --template= --initial-branch=main "$demo/repo"
cd -- "$demo/repo"
git config --local user.name 'Tutorial'
git config --local user.email 'tutorial@example.invalid'
mkdir -p apps/web/src apps/api docs shared
printf 'root readme\n' > README.md
printf 'apps readme\n' > apps/README.md
printf 'web entry\n' > apps/web/index.html
printf 'web source\n' > apps/web/src/main.js
printf 'api source\n' > apps/api/server.js
printf 'guide\n' > docs/guide.md
printf 'shared source\n' > shared/lib.js
git add -- .
git commit -qm 'create isolated sample'
original_head=$(git rev-parse HEAD)
original_tracked=$(git ls-files)
git sparse-checkout set --cone --no-sparse-index apps/web
printf 'selected directories:\n'
git sparse-checkout list
printf 'visible files:\n'
find . -path './.git' -prune -o -type f -print | LC_ALL=C sort
printf 'tracked files:\n'
git ls-files
test -f README.md
test -f apps/README.md
test -f apps/web/src/main.js
test ! -e docs/guide.md
test ! -e apps/api/server.js
test ! -e shared/lib.js
test "$(git ls-files)" = "$original_tracked"
test -z "$(git status --porcelain)"
printf 'hidden file in commit: '
git show HEAD:docs/guide.md
git sparse-checkout disable
test -f docs/guide.md
test -f apps/api/server.js
test -f shared/lib.js
test "$(git rev-parse HEAD)" = "$original_head"
test "$(git ls-files)" = "$original_tracked"
test -z "$(git status --porcelain)"
printf 'restored: all seven tracked files, clean state\n'
)visible files 下应有四条路径:根目录说明、apps 说明、网页入口和网页源码。tracked files 下仍有七条路径,包含接口、文档和公共库。随后 git show 还能读出文档里的 guide,证明原提交仍保存内容;状态检查为空,则说明本次收缩没有被当成待提交删除。
目录选择不会把仓库裁成另一个项目
git ls-files 默认列出索引中的已跟踪路径,不能拿它直接统计当前磁盘上有几个源码文件。需要检查实际展开范围时,可查看文件系统;需要检查已跟踪范围时,则查看索引清单。两种结果不同很正常,排障时应先写清要回答的是哪一个问题。
示例显式关闭稀疏索引,使输出与传统索引形式容易对照。稀疏索引是另一项减少索引工作量的机制,不应与工作区是否展开某个目录混为一谈。此处只展示路径可见性,没有测量启动速度、内存或磁盘收益,不能据此承诺某个项目会快多少。
sparse-checkout list 显示选择的递归目录,并不是全部可见文件的完整清单。祖先目录直属文件仍可能存在,构建脚本也可能继续需要未选择的公共依赖。如果网页构建找不到 shared,应按项目依赖把它加入选择范围,再验证构建,而不是在本地创建一个空目录掩盖缺失。
恢复之前与切换之后都核对状态
disable 会关闭稀疏检出并重新展开已跟踪文件。脚本随后检查三份隐藏文件重新出现,并确认 HEAD、跟踪清单和状态都没有变化。这里没有创建新的业务提交,也没有重新拉取远程仓库;恢复来自本地已有的仓库内容。
真实项目中,调整范围前应先查看已修改、未跟踪和被忽略的文件,并保存需要保留的本地内容。不要把稀疏检出当作备份:缩小范围时,被移出目录中的某些忽略文件可能被清理,已有修改也可能使操作无法按预期收缩。实验使用干净仓库,就是为了不把这些状态混进基础结论。
稀疏检出也不是访问控制。未展开的跟踪文件仍可通过仓库命令读取,已有历史和对象不会仅因隐藏目录就消失。若目标是减少网络传输,应另外研究部分克隆;若目标是限制成员能读什么,应在仓库权限与拆分策略上解决,不能依赖工作区外观。
验收时把三项证据放在一起:可见文件符合 cone 规则,跟踪清单仍完整,Git 状态没有意外删除。最后恢复一次并检查内容,能避免把“目录暂时消失”误报成文件丢失,也能确认所选范围确实支持接下来要完成的开发任务。


