在 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_DIR
和GIT_WORK_TREE
环境变量,已更正。
参见Elijah Newren (newren
) 的commit 434e063(2021 年 12 月 4 日)。(由 Junio C Hamano -- gitster
-- 合并于 commit 57f28f4,2021 年 12 月 21 日)
sequencer
:不要为 'exec' 导出GIT_DIR
和GIT_WORK_TREE
签字人:Elijah Newren签字人:Johannes Schindelin签字人:Johannes Altmanninger委托人:菲利普伍德
从
git rebase --exec
(man) 执行的命令可以在该环境中提供与在其外部不同的行为,因为sequencer.c
会同时导出GIT_DIR
和GIT_WORK_TREE
. 例如,如果相关脚本调用类似git -C ../otherdir log --format=%H --no-walk
用户可能会惊讶地发现上面的命令没有显示来自
../otherdir
的提交哈希,因为$GIT_DIR
阻止了自动gitdir
检测并且使-C
选项无用 .这是从原始遗留的在 shell 中实现的 rebase 的行为回归。 它在实践中可能很少引起问题,特别是因为由该错误区域引起的大多数小问题过去都已修复,掩盖了观察到的特定错误而没有修复真正的潜在问题。
或许值得解释一下我们是如何得出目前的情况的。
sequencer.c
对GIT_DIR
和GIT_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 absoluteGIT_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(&child_env,
"GIT_DIR=%s",
absolute_path(get_git_dir()
显式导出它));run_command_v_opt_cd_env(
...,child_env
.argv)在commit ab5e67d("
sequencer
: pass absoluteGIT_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_DIR
和GIT_WORK_TREE
在此期间。 现在停止导出它们。
【讨论】:
以上是关于在 git hook 中操作 repo 时 -C 和 --git-dir 之间的区别的主要内容,如果未能解决你的问题,请参考以下文章