为什么 git 提交不了空目录?这篇先看 add、status、clone 的表现,再看暂存区的结构,甚至手工造出一个带空 tree 的提交,最后验证两种常见的占位做法。
$ 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,让目录里的其它文件都被忽略,既保留目录又不会误提交运行时产生的文件。