使用git工作树和git子模块时会出现什么问题?

64
我最近发现了git worktree命令:
新的工作目录与当前仓库相连,共享除HEAD、index等工作目录特有的文件之外的一切内容。
但文档还指出:
…对子模块的支持不完整。不建议对超级项目进行多次检出。
没有进一步解释会出现什么问题。
有人能为我解释一下可能遇到的问题吗?例如,如果我只在这种方式生成的单独工作树中进行不影响子模块的更改,那么我是否安全?
1个回答

55

提交 a83a66a 很清楚:

git-submodule.sh 期望 $GIT_DIR/config 至少对于 submodule.* 部分 是每个工作树,这里我认为我们有两个选择:

  • 要么更新 config.c 以便除了读取共享的 $GIT_DIR/config 外,还要读取每个工作树特定的 $GIT_DIR/config.worktree 并将工作树特定变量存储在新位置,
  • 要么更新 git-submodule.sh 直接从每个工作树的 $GIT_DIR/config.submodule 中直接读写 submodule.*

这些需要时间来适当地解决。同时,请注意告知用户不要在子模块上下文中使用多个工作树。

更普遍地说,应该把这些子模块放在哪里?

有几个选项:
  • 您可能希望将 $SUB 子仓库保留在其他地方(例如集中位置),而不是 $SUPER 中。对于嵌套子模块也是如此,其中一个超级项目可能是另一个超级项目的子模块。
  • 您可能希望将所有 $SUB 子仓库都放在 $SUPER/modules(或 $SUPER 的其他位置)中。
  • 我们甚至可以进一步推进,并将所有 $SUB 子仓库合并到 $SUPER 中,而不是将它们单独存储。但这至少需要启用 ref 命名空间。

这个提交是对commit df56607的回答。


从git用户的角度来看,这意味着git submodule update --init --recursive不知道精确地在哪里检出子模块。
它们是分布在所有工作树上,还是集中在某个地方?目前尚未正式指定。


一年后(并且使用git 2.9),clacke 在评论中指出,混淆已得到解决,但不是最佳方式。
据我所见,子模块现在工作正常,但每个工作树都有自己的子模块仓库集合(位于motherrepo.git/worktree/<worktreename>/modules/<submodule>下),因此如果您有一个大的子模块,则面临严重的磁盘使用问题。

处理子树中的子模块的Git别名

别名 git wtas 需要全局定义git wta,或者至少对所有涉及的仓库都进行定义。不包含任何保证。如果您的路径名称中有空格,您最喜欢的宠物可能会感染疼痛的感染。

它期望您的仓库结构与子模块初始化的非裸仓库中的结构相同,因此如果您有一个裸仓库,则必须模仿该设置。具有名称(而不是路径)foo的子模块位于<your-.git-directory>/modules/foo中(而不是.../foo.git)。如果某个模块在仓库中不存在,它将不会崩溃,而只是跳过它。

还有改进的空间。它无法处理子模块内的子模块,它只能向下一级。尝试将子模块git wta调用更改为git wtas调用可能有效,但我尚未验证。

-- clacke


另请参阅git worktree move(使用 Git 2.17+,2018年第二季度可用)。


实际上,在Git 2.21之前(2019年第一季度),当涉及子模块时,"git worktree remove"和"git worktree move"会拒绝工作。
现在已经放宽了对未初始化子模块的限制。

请参见提交00a6d4d(由Nguyễn Thái Ngọc Duy (pclouds)于2019年1月5日提交)。
(由Junio C Hamano -- gitster --合并于提交726f89c,2019年1月18日)

worktree: allow to (re)move worktrees with uninitialized submodules

Uninitialized submodules have nothing valuable for us to be worried about. They are just SHA-1.
Let "worktree remove" and "worktree move" continue in this case so that people can still use multiple worktrees on repos with optional submodules that are never populated, like sha1collisiondetection in git.git when checked out by doc-diff script.

Note that for "worktree remove", it is possible that a user initializes a submodule (*), makes some commits (but not push), then deinitializes it.
At that point, the submodule is unpopulated, but the precious new commits are still in:

$GIT_COMMON_DIR/worktrees/<worktree>/modules/<submodule>

directory and we should not allow removing the worktree or we lose those commits forever.
The new directory check is added to prevent this.

(*) yes they are screwed anyway by doing this since "git submodule" would add submodule.* in $GIT_COMMON_DIR/config, which is shared across multiple worktrees.
But it does not mean we let them be screwed even more.


在 Git 2.25 之前(2020 年第一季度),如果你设置了 submodule.recurse 配置选项,在已初始化子模块的超级项目中执行 git worktree add 将会失败。

参见 提交 4782cf2ab6(由 Philippe Blain (phil-blain) 于 2019 年 10 月 27 日提交)
(由 Junio C Hamano -- gitster -- 合并于 提交 726f89c,2019 年 12 月 1 日)

worktree: 教授 "add" 忽略 submodule.recurse 配置

"worktree add" 内部调用 "reset --hard",但如果设置了 submodule.recursereset 尝试递归到已初始化的子模块,这使得 start_command 尝试进入不存在的子模块路径并死亡。

通过确保 "worktree add" 中对 reset 的调用不进行递归来解决此问题。

较早版本的解决方法是暂时禁用配置:

git -c submodule.recurse=0 worktree add ...

在 Git 2.26 之前(2020 年第二季度),在初始化子模块的仓库的链接工作树中执行 git checkout --recurse-submodules (或 resetread-tree)会错误地移动主工作树 git 仓库中的子模块 HEAD,并清除链接工作树中子模块的工作目录中的 .git gitfile:

查看 提交 a9472afb63(2020年1月21日)由Philippe Blain (phil-blain)完成。
(由Junio C Hamano -- gitster --合并于提交 ff5134b2,2020年2月5日)

submodule.c: 使用 get_git_dir() 替代 get_git_common_dir()

自从 df56607 (git-common-dir: make "modules/" 为每个工作目录的目录,2014-11-30) 以来, 链接工作树中的子模块被克隆到 $GIT_DIR/modules,即 $GIT_COMMON_DIR/worktrees/<name>/modules

然而,当工作树更新命令 checkoutresetread-tree 学会递归进入子模块时, 并没有遵循这种约定。具体来说,submodule.c::submodule_move_head(在 6e3c159 中引入)和 submodule.c::submodule_unset_core_worktree(在 898c2e6 中重新引入)使用 get_git_common_dir() 而不是 get_git_dir() 来获取子模块存储库的路径。

这意味着,例如,在链接工作树中执行 'git checkout --recurse-submodules <branch>' 将正确地检出 <branch>,将子模块的 HEAD 分离到记录在 <branch> 中的提交,并更新子模块的工作树, 但将移动的子模块 HEAD 是在 $GIT_COMMON_DIR/modules/<name>/ 中的, 即主超级项目工作树的子模块存储库。它还将重写链接工作树中子模块工作树中的 gitfile, 将其指向 $GIT_COMMON_DIR/modules/<name>/。这导致了主超级项目工作树中子模块工作树中的不正确(和令人困惑!)状态。

此外,如果切换到不存在子模块的提交,则会调用 submodule_unset_core_worktree 并错误地从主超级项目工作树的子模块配置文件 $GIT_COMMON_DIR/modules/<name>/config 中删除 'core.wortree'。

通过在 submodule_move_headsubmodule_unset_core_worktree 中都使用 get_git_dir() 构建子模块存储库的路径来修复此问题。


3
谢谢你的研究!但我仍然不确定到底是什么导致了问题,以及哪些操作仍然有效。你能否从 Git 用户的角度尝试描述一下? - Joachim Breitner
2
截至今天,我在这个问题上,因为混淆已经得到解决,但不是最优的方式。据我所见,子模块现在运行良好,但每个工作树都有自己的一组子模块仓库(位于 motherrepo.git/worktree/<worktreename>/modules/<submodule> 下),因此如果您有一个大的子模块,您将面临一些严重的磁盘使用问题。 - clacke
1
@clacke 谢谢。我已经将您的评论包含在答案中以增加可见性。您正在使用 git 2.9 吗? - VonC
2
据我所了解,唯一的问题是子仓库的“管理文件”(例如HEAD等)在不同的工作目录中可能会有所不同,因为这些工作目录可能有不同的关联子仓库提交记录。但是为什么我们不能像处理工作目录本身那样解决这个问题呢?也就是说,将子仓库的“内容”存储在$GIT_COMMON_DIR/modules/<submodule>中,并将管理文件存储在$GIT_COMMON_DIR/worktrees/<worktree>/modules/<submodule>中。这样,工作目录将是独立的,而不会重复存储其子模块的对象。 - ScumCoder
1
@philb感谢您的编辑和对Git本身的贡献!我已经提到了这一点,在https://dev59.com/iVsW5IYBdhLWcg3w66qH#52452587。 - VonC
显示剩余10条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接