Git 稀疏检出:目录看不见了,为什么 git ls-files 仍然能列出来

10-01 4阅读

先分清本地看见什么与仓库记录什么

大型仓库同时放着网页、接口和文档,当前只修改网页时,可以用稀疏检出减少工作区实际展开的文件。操作之后,文档目录可能从文件管理器里消失,但这并不表示提交已经删除文档。磁盘上能看见的路径,与 Git 索引保存的跟踪清单,是两种不同的观察对象。

cone 模式以目录为选择单位。选择 apps/web 会展开这个目录下面的全部文件,同时保留仓库根目录,以及通向目标的各级祖先目录中的直属文件。因此根目录 README 和 apps/README 仍然出现是预期行为,不能把它理解成只允许一个目录存在的严格隔离。

在一次性仓库里观察三份证据

下面脚本需要 Bash 和 Git 二点三十九及以上版本。保存后用 bash 运行,它在系统临时目录创建独立仓库,退出时只清理自己建立的目录。脚本隔离常见 Git 仓库环境变量,跳过全局与系统配置,并在每次调用中关闭钩子和提交签名,身份仅写进实验仓库。

Git 稀疏检出:目录看不见了,为什么 git ls-files 仍然能列出来

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 状态没有意外删除。最后恢复一次并检查内容,能避免把“目录暂时消失”误报成文件丢失,也能确认所选范围确实支持接下来要完成的开发任务。

参考资料

文章版权声明:除非注明,否则均为云鹊BLOG原创文章,转载或复制请以超链接形式并注明出处。