在 git hook 中操作 repo 时 -C 和 --git-dir 之间的区别

Posted

技术标签:

【中文标题】在 git hook 中操作 repo 时 -C 和 --git-dir 之间的区别【英文标题】:Difference between -C and --git-dir when manipulating repo in git hook 【发布时间】:2017-07-06 19:26:03 【问题描述】:

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

当从命令行手动运行时,挂钩按预期工作,但是当挂钩由 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

从命令行运行脚本将按预期工作,但是当你推送到 repo 时,钩子会失败。评论 1 和取消评论 2 在这两种情况下都有效。

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

这是 Ubuntu 16.04 上的 git 2.7.4。

【问题讨论】:

知道GIT_DIR 环境变量在钩子中设置可能会有所帮助。您可能希望在运行结帐命令时取消设置。 如果你被古版本的 git 卡住了,值得一提:git-scm.com/docs/git/1.8.5 中引入了“git -C 目录”选项(因为在 git 1.8.3 中我得到了“未知选项:-C” ) 【参考方案1】:

两者之间的字面区别:

git -C <em>directory git-sub-command ...</em>

和:

git --git-dir <em>directory git-sub-command ...</em>

是前端设置程序git首先使用操作系统级别的“更改目录”操作(os.chdir来自Python,chdir()来自C等),并设置环境变量@987654329 @第二个。无论哪种情况,它都会找到子命令并运行它。 (请注意,您实际上可以两者都。)This is documented,包括多个-C 选项的效果以及-C--git-dir 之间的交互。

然而,这只是将问题推低了一层:现在您需要知道git-checkout(位于git --exec-path 目录中)对$GIT_DIR 的作用与对当前工作目录的作用不同。直接答案在the top level git command documentation, under the ENVIRONMENT VARIABLES section:

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

这就是Jan Krüger's comment 的用武之地。当你编写一个Git 钩子时,你必须意识到Git 可能会为你设置一些环境变量。如果$GIT_DIR 设置为相对路径名,并且您没有覆盖它,并且确实更改了目录,那么您将更改所有各种 Git 子命令定位存储库的方式。因此,您必须 un-设置它(以获取默认的 $GIT_DIR-not-set 行为),或将其显式设置为绝对路径(以在目录更改时保留它),或显式设置它指向其他存储库的相对或绝对路径,具体取决于您想要的行为。

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

【讨论】:

【参考方案2】:

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

"git rebase -x"(man)在用C重写命令时错误地开始导出GIT_DIRGIT_WORK_TREE环境变量,已更正。

参见Elijah Newren (newren) 的commit 434e063(2021 年 12 月 4 日)。(由 Junio C Hamano -- gitster -- 合并于 commit 57f28f4,2021 年 12 月 21 日)

sequencer:不要为 'exec' 导出 GIT_DIRGIT_WORK_TREE

签字人:Elijah Newren签字人:Johannes Schindelin签字人:Johannes Altmanninger委托人:菲利普伍德

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

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

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

这是从原始遗留的在 shell 中实现的 rebase 的行为回归。 它在实践中可能很少引起问题,特别是因为由该错误区域引起的大多数小问题过去都已修复,掩盖了观察到的特定错误而没有修复真正的潜在问题。

或许值得解释一下我们是如何得出目前的情况的。sequencer.cGIT_DIRGIT_WORK_TREE的设定源于一系列历史事故:

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

当引入 rebase--helper 内置函数以允许用 C 代码逐步替换 shell 时,我们有一个半壳半 C 的实现。 特别是,commit 18633e1 ("rebase -i: use the rebase--helper builtin", 2017-02-09, Git v2.13.0-rc0 -- merge 列在batch #1) 添加了对 exec git rebase 的调用--helper ... 这导致 rebase--helper 从 shell 继承 GIT_DIR 环境变量。 git 的设置会将环境变量从绝对路径更改为相对路径(“.git”),但会保持设置。 这意味着当rebase --exec 命令通过run_command_v_opt(...运行时,它们将继承GIT_DIR 设置。

在commit 09d7b6c(“sequencer: pass absolute GIT_DIR to exec commands”,2017-10-31,Git v2.16.0-rc0 -- merge列在batch #1)中,有人指出GIT_DIR 导致某些命令出现问题;例如git rebase --exec 'cd subdir && 'git describe'(man) ... 将有GIT_DIR=.git,由于对子目录的更改而无效。 与其质疑为什么设置了GIT_DIR,不如说是让sequencer更改GIT_DIR成为绝对路径并通过argv_array_pushf(&amp;child_env,"GIT_DIR=%s",absolute_path(get_git_dir()显式导出它)); run_command_v_opt_cd_env(..., child_env.argv)

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

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

【讨论】:

以上是关于在 git hook 中操作 repo 时 -C 和 --git-dir 之间的区别的主要内容,如果未能解决你的问题,请参考以下文章

git hook 自动部署

Android Git Hooks

Android Git Hooks

Android Git Hooks

手写 git hooks 脚本(pre-commitcommit-msg)

通过Gradle Plugin实现Git Hooks检测机制