为什么 `git update-ref -d` 不删除空的 parent 目录?

Why doesn't `git update-ref -d` delete empty parent directories?

git中有各种类型的引用,其中最常见的一些是分支(存储在.git/refs/heads中),remote-tracking个分支(.git/refs/remotes) , 和标签 (.git/refs/tags).

但也可以创建和使用位于 .git/refs 下其他地方的任意 non-standard 引用。这对于将自定义元数据存储在您不希望用户直接与之交互的存储库中很有用。例如,GitHub 使用这些类型的引用 expose references to pull request branches, and the Emacs git client Magit uses them to save uncommitted changes periodically, when the appropriate setting is enabled。此类引用通常需要使用 git 的 so-called“管道”命令进行操作,因为 user-facing“瓷器”命令不知道或不支持它们。

我正在使用管道命令 git update-ref 与 non-standard refs 玩耍,发现一些奇怪的行为:

$ git init foo && cd foo
$ touch a && git add a && git commit -m init
$ tree .git/refs
.git/refs
├── heads
│   └── master
└── tags

2 directories, 1 file
$ git update-ref refs/foo/bar/baz HEAD
$ tree .git/refs
.git/refs
├── foo
│   └── bar
│       └── baz
├── heads
│   └── master
└── tags

4 directories, 2 files
$ git update-ref -d refs/foo/bar/baz
$ tree .git/refs
.git/refs
├── foo
├── heads
│   └── master
└── tags

3 directories, 1 file

当我创建 ref refs/foo/bar/baz 时,git update-ref 创建了必要的 parent 目录。当我删除 ref 时,它很聪明地删除了 parent 目录 bar,它现在已经变成空的了。但是,删除“grandparent”目录 foo 不够聪明,在删除 bar.

后该目录现在也是空的

这是一个错误吗?

不,这是设计使然。这里是 a comment from the source code:

/*
 * Remove empty parent directories associated with the specified
 * reference and/or its reflog, but spare [logs/]refs/ and immediate
 * subdirs. flags is a combination of REMOVE_EMPTY_PARENTS_REF and/or
 * REMOVE_EMPTY_PARENTS_REFLOG.
 */
static void try_remove_empty_parents(struct files_ref_store *refs,
                     const char *refname,
                     unsigned int flags)
{

如果我将我的 non-standard 引用嵌套得更深一层,例如refs/foo/bar/baz/xyzzy,我注意到 parent 和 grandparent 目录都被删除了,但是 great-grandparent 没有,这使得这种行为是故意的更加明显.

我想这个想法是 .git/refs/ 下的顶级子目录(如我的示例中的 foo)代表一种 ref 而不是 ref 名称的一部分,因此处理它们不同于树下的目录(如我的示例中的 bar)是有道理的。

Git 2.32(2021 年第 2 季度)已正式解决(仅针对 refs/heads):当“git update-ref -d"(man) 删除打包的 ref 时,它在 $GIT_DIR/refs/.

下留下了空目录

参见 commit 5f03e51 (08 May 2021) by Will Chandler (wlvchandler)
(由 Junio C Hamano -- gitster -- in commit 16f9145 合并,2021 年 5 月 16 日)

refs: cleanup directories when deleting packed ref

Signed-off-by: Will Chandler
Reviewed-by: Jeff King

When deleting a packed ref via 'update-ref -d', a lockfile is made in the directory that would contain the loose copy of that ref, creating any directories in the ref's path that do not exist.
When the transaction completes, the lockfile is deleted, but any empty parent directories made when creating the lockfile are left in place.
These empty directories are not removed by 'pack-refs' or other housekeeping tasks and will accumulate over time.

When deleting a loose ref, we remove all empty parent directories at the end of the transaction.

This commit applies the parent directory cleanup logic used when deleting loose refs to packed refs as well.

The test 显示:

directory not created deleting packed ref':

git branch d1/d2/r1 HEAD &&
git pack-refs --all &&
test_path_is_missing .git/refs/heads/d1/d2 &&
git update-ref -d refs/heads/d1/d2/r1 &&
test_path_is_missing .git/refs/heads/d1/d2 &&
test_path_is_missing .git/refs/heads/d1  <==== grand-parent is gone

但是,这不适用于 refs/foo,因为在 OP 中:git update-ref -d refs/foo/bar/baz 会删除 bar/baz,但不会删除 foo/