-C 和 --git-dir 在 git hook 中操作存储库时的区别



我正在编写一个 git 接收后钩子,它将克隆一个单独的存储库作为部署的一部分。它将存储库克隆到某个文件夹,并在后续 git 命令中使用-C选项将目录设置为签出存储库的目录(如手册页中所述)。

从命令行手动运行时,钩子按预期工作,但是当钩子由 git 运行时(即收到推送时),命令失败并显示fatal: Not a git repository: '.'.当我将-C换成--git-dir时,它可以工作。

这很容易重现,创建一个裸存储库git init --bare并使用内容制作一个可执行钩子:

#!/bin/bash
set -xe
SOME_REPO_URL=???? # Some repo that is not this one
repopath=/tmp/somerepo
git clone $SOME_REPO_URL $repopath
# 1: This fails when run through the git hook
git -C $repopath checkout -b somebranch HEAD~1
# 2: This works every time
# git --git-dir $repopath/.git checkout -b somebranch HEAD~1

从命令行运行脚本将按预期工作,但当您推送到存储库时,挂钩将失败。在这两种情况下,1注释和取消注释2都有效。

我找不到任何文档表明这是预期行为 - 解释将不胜感激。

这是 Ubuntu 16.04 上的 git 2.7.4。

两者之间的字面区别:

git -Cdirectory git-sub-command ...

和:

git --git-dirdirectory git-sub-command ...

是前端安装程序git第一个使用操作系统级别的"更改目录到"操作(来自 Python 的os.chdirchdir()来自 C 等),并为第二个设置环境变量$GIT_DIR。 无论哪种情况,它都会找到子命令并运行它。 (请注意,您实际上可以同时执行这两项操作。 这是有记录的,包括多个-C选项的效果以及-C--git-dir之间的相互作用。

然而,这只是将问题向下推了一个级别:现在你需要知道git-checkout(在git --exec-path目录中找到)对$GIT_DIR的作用与对当前工作目录的不同之处。 直接答案位于顶级命令文档中git"环境变量"部分下:

GIT_DIR
如果设置了GIT_DIR环境变量,则它指定要使用的路径,而不是存储库基础的默认.git--git-dir命令行选项也会设置此值。

这就是扬·克鲁格的评论所在。 当你编写 Git 钩子时,你必须意识到 Git 可能会为你设置一些环境变量。 如果$GIT_DIR设置为相对路径名,并且您不覆盖它,并且您确实更改了目录,您将更改所有各种 Git 子命令查找存储库的方式。 因此,您必须取消设置它(以获得默认的$GIT_DIR-not-set 行为),或者将其显式设置为绝对路径(以在目录更改中保留它),或者将其显式设置为某个其他存储库的路径(相对或绝对),具体取决于您想要的行为。

请注意,--work-tree设置$GIT_WORK_TREE,还有其他类似的变量,但是 - 至少在迄今为止的所有 Git 版本中 -$GIT_DIR是 Git 钩子中唯一"为您预设"(或"为您的烦恼":-)

的变量。

如果您从那时起(2017 年)升级了 Git,请确保使用最新的 Git 2.35(2022 年第 1 季度):

"git rebase -x">(man)错误地开始导出GIT_DIRGIT_WORK_TREE当命令用 C 重写时,该变量已得到纠正。

参见提交 434e063(2021 年 12 月 4 日),作者:Elijah Newren (newren).
(由 Junio C Hamano --gitster-- 合并于 提交 57f28f4,2021 年 12 月 21 日)

sequencer:不要导出GIT_DIRGIT_WORK_TREE"exec">

签名者: 以利亚·纽伦
签名者: 约翰内斯·辛德林

签名者:约翰内斯·阿尔特曼宁格
签名者: 菲利普·伍德

git rebase --exec(man)执行的命令可以在该环境中给出与外部不同的行为,因为sequencer.c导出GIT_DIRGIT_WORK_TREE.
例如,如果相关脚本调用类似

git -C ../otherdir log --format=%H --no-walk

用户可能会惊讶地发现上面的命令没有显示来自../otherdir的提交哈希,因为$GIT_DIR阻止了自动gitdir检测并使-C选项无用

这是原始遗留的 shell-in-shell 变基变本的行为回归.
在实践中可能很少引起问题,特别是因为由此错误区域引起的大多数小问题在过去已经以一种掩盖观察到的特定错误的方式进行了修复,而没有修复真正的潜在问题。


sequencer.c所做的GIT_DIRGIT_WORK_TREE的设置源于一系列历史事故:

  • 当 rebase 作为 shell 命令实现时,它会调用git-sh-setup,其中包括设置GIT_DIR- 但不会导出它.
    这意味着当rebase --exec命令通过/bin/sh -c $COMMAND运行时,它们不会继承GIT_DIR设置。
    在运行$COMMAND中未设置GIT_DIR的事实是我们想要恢复的行为。

  • 当引入内置rebase--helper以允许用 C 代码增量替换 shell 时,我们有一个半 shell 半 C.
    特别是提交 18633e1("rebase -i:使用 rebase --helper builtin",2017-02-09,Git v2.13.0-rc0 - 合并在批处理 #1 中列出)添加了对 exec git rebase--helper ...
    这导致 rebase--helper 从 shell 继承GIT_DIR环境变量.
    git 的安装程序会将环境变量从绝对路径更改为相对路径(".git"),但会保留它设置.
    这意味着当rebase --exec命令通过run_command_v_opt(运行时......他们将继承GIT_DIR设置。

  • 在提交 09d7b6c ("sequencer:将绝对GIT_DIR传递给 exec 命令", 2017-10-31, Git v2.16.0-rc0 -- 合并列在批处理 #1 中),注意到该GIT_DIR导致某些命令出现问题; 例如git rebase --exec'cd subdir && 'git describe'(man) ...
    GIT_DIR=.git由于子目录的更改而无效.
    该提交没有质疑为什么设置GIT_DIR,而是将音序器更改为绝对路径GIT_DIR并通过argv_array_pushf(&child_env,"GIT_DIR=%s",absolute_path(get_git_dir())显式导出它);run_command_v_opt_cd_env(...,child_env.argv)

  • 在提交 ab5e67d ("sequencer: 将绝对GIT_WORK_TREE传递给 exec 命令", 2018-07-14, Git v2.19.0-rc0 -- merge) 中,注意到当设置了GIT_DIRGIT_WORK_TREE没有时,我们不会发现GIT_WORK_TREE,而只是假设它是 '.'。
    如果尝试从子目录运行命令,这是不正确的.
    但是,该提交不是质疑为什么设置GIT_DIR,而是将GIT_WORK_TREE添加到要导出的内容列表中。

如果不是因为sequencer.c在此期间开始导出GIT_DIRGIT_WORK_TREE,上述每个问题都会在git-rebase(man)成为完全内置时自动修复
现在停止导出它们。

最新更新