bigywater.tech
Git · 12

空目录为什么提交不了

为什么 git 提交不了空目录?这篇先看 add、status、clone 的表现,再看暂存区的结构,甚至手工造出一个带空 tree 的提交,最后验证两种常见的占位做法。

0
git add 空目录的退出码是 0,但没有加入任何东西
空 tree
手工 mktree 可以造出带空目录条目的提交,fsck 不报错,但检出时 git 不创建目录
e69de29
.gitkeep 占位文件就是一个空 blob

现象

实测 · 本机执行输出
  $ ls -d empty_dir logs; git add empty_dir logs; echo "退出码 $?"; git status --short; git status
  empty_dir
  logs
  退出码 0
  On branch main
  
  No commits yet
  
  nothing to commit (create/copy files and use "git add" to track)

git add 一个空目录既不报错也不加入任何东西,git status 也看不到它;提交之后 git ls-tree -r 里没有它,clone 出来的副本里自然也没有。

为什么会这样

实测 · 本机执行输出
  $ git ls-tree -r HEAD
  100644 blob 78981922613b2afb6025042ff6bd878ac1994e85	a.txt
  $ git clone -q . ../l8clone && ls ../l8clone
  fatal: destination path '../l8clone' already exists and is not an empty directory.
  (退出码 128)

本地的空目录一直在磁盘上,但它从没有进入过 commit;换一台机器检出就没有。

暂存区没有目录条目

实测 · 本机执行输出
  $ git ls-files -s
  100644 78981922613b2afb6025042ff6bd878ac1994e85 0	a.txt
  空 tree 是个合法对象(git 内置识别它),但正常的 git add / commit 不会把它作为子目录放进提交:
  $ git mktree < /dev/null; git cat-file -t 4b825dc642cb6eb9a060e54bf8d69288fbee4904; git ls-tree 4b825dc642cb6eb9a060e54bf8d69288fbee4904 | wc -l
  4b825dc642cb6eb9a060e54bf8d69288fbee4904
  tree
         0

暂存区(index)记录的是文件路径,没有“目录”这种条目,空目录里没有文件,就没有任何可记的。空 tree 本身是合法对象,但正常的 add / commit 不会把它作为子目录写进提交。

手工造一个

实测 · 本机执行输出
  $ printf '040000 tree 4b825dc642cb6eb9a060e54bf8d69288fbee4904\tempty\n100644 blob %s\ta.txt\n' $(git rev-parse HEAD:a.txt) | git mktree
  8e39fe22e02289aacbe566aac184e194f5c15e75
  $ git ls-tree 7d2deb29c0d9273b65493c210f38293703aa995a
  100644 blob 78981922613b2afb6025042ff6bd878ac1994e85	a.txt
  040000 tree 4b825dc642cb6eb9a060e54bf8d69288fbee4904	empty
  $ git fsck 2>&1 | head -3
  dangling commit 7d2deb29c0d9273b65493c210f38293703aa995a
  $ git checkout -q 7d2deb29c0d9273b65493c210f38293703aa995a 2>&1; ls -d empty a.txt; git status --short; git checkout -q main
  a.txt
  ls: empty: No such file or directory

用 git mktree 和 git commit-tree 可以造出带空 tree 条目的提交,git 不认为这有问题;但检出这个提交不会创建对应的空目录,所以这条路并不能真正“提交空目录”。

占位文件

实测 · 本机执行输出
  $ git add logs empty_dir && git status --short
  A  empty_dir/.gitkeep
  A  logs/.gitkeep
  $ git ls-files -s logs empty_dir
  100644 e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 0	empty_dir/.gitkeep
  100644 e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 0	logs/.gitkeep
  $ git rev-parse :logs/.gitkeep; git hash-object -t blob /dev/null
  e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
  e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
  常见做法二:目录里放 .gitignore 忽略自己以外的所有文件:
  $ git add cache && git status --short cache; git ls-files cache
  A  cache/.gitignore
  cache/.gitignore

通行做法是放一个被跟踪的占位文件,.gitkeep 只是约定的名字,它的 blob 是空 blob e69de29。另一种是在目录里放 .gitignore,写 * 和 !.gitignore,让目录里的其它文件都被忽略,既保留目录又不会误提交运行时产生的文件。

分步演示

空目录
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / git 2.54.0(Apple Git-157);每个实验在临时目录里新建仓库,HOME 和全局配置指向实验专用的空目录(不读用户自己的 git 配置),提交的作者与时间固定以便复现;需要网络协议的实验用本机的 file:// 或 git daemon 回环端口(9800 以上),不碰任何远程仓库;计时用 time.perf_counter,数字每次运行会有小幅抖动。所有结果来自 git add / status / clone / mktree / commit-tree / checkout 的真实运行
    • 没有验证:其它版本的 git 对空 tree 条目的行为(本文只在 2.54.0 上试过);git archive、sparse-checkout 对空目录的处理;工作树里已有空目录时 git clean -d 会不会删除它;Windows 与 macOS 文件系统的差别
    京ICP备2026067502号-1京公网安备11010502063487号