在 git hook 中操作 repo 时 -C 和 --git-dir 的区别
Difference between -C and --git-dir when manipulating repo in git hook
我正在编写一个 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
在这两种情况下都有效。
我找不到任何表明这是预期行为的文档 - 将不胜感激。
这是 git 2.7.4 Ubuntu 16.04。
文字上的区别:
git -C <em>目录git-sub-command ...</em>
和:
git --git-dir <em>目录git-sub-command ...</em>
是前端设置程序git
使用了OS-level"change directory to"操作(os.chdir
来自Python,chdir()
来自C , 等)为第一个,并为第二个设置环境变量 $GIT_DIR
。在任何一种情况下,它都会找到 sub-command 并运行它。 (请注意,您实际上可以同时执行 。)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
If the GIT_DIR
environment variable is set then it specifies a path to use instead of the default .git
for the base of the repository. The --git-dir
command-line option also sets this value.
这就是的用武之地。当你写一个Git钩子时,你必须意识到Git可能会为你设置一些环境变量。如果 $GIT_DIR
设置为相对路径名,并且您没有覆盖它,并且您确实更改了目录,那么您将更改所有各种 Git sub-command 定位存储库的方式。因此,您必须 un-设置它(以获得默认的 $GIT_DIR
-not-set 行为),或者显式地将它设置为绝对路径(以在整个过程中保留它目录更改),或将其显式设置为其他存储库的路径(相对或绝对),具体取决于您想要.
的行为
请注意 --work-tree
设置 $GIT_WORK_TREE
,还有其他类似的变量,但是——至少在迄今为止的所有 Git 版本中——$GIT_DIR
是唯一的"pre-set for you"(或 "for your annoyance" :-))在 Git 钩子中。
如果您从那时起(2017 年)升级了 Git,请确保使用最新的 Git 2.35(2022 年第一季度):
"git rebase -x
"(man) 在用 C 重写命令时错误地开始导出 GIT_DIR
和 GIT_WORK_TREE
环境变量,已更正。
参见 commit 434e063 (04 Dec 2021) by Elijah Newren (newren
)。
(由 Junio C Hamano -- gitster
-- in commit 57f28f4 合并,2021 年 12 月 21 日)
sequencer
: do not export GIT_DIR
and GIT_WORK_TREE
for 'exec'
Signed-off-by: Elijah Newren
Acked-by: Johannes Schindelin
Acked-by: Johannes Altmanninger
Acked-by: Phillip Wood
Commands executed from git rebase --exec
(man) can give different behavior from within that environment than they would outside of it, due to the fact that sequencer.c
exports both GIT_DIR
and GIT_WORK_TREE
.
For example, if the relevant script calls something like
git -C ../otherdir log --format=%H --no-walk
the user may be surprised to find that the command above does not show a commit hash from ../otherdir
, because $GIT_DIR
prevents automatic gitdir
detection and makes the -C
option useless.
This is a regression in behavior from the original legacy implemented-in-shell rebase.
It is perhaps rare for it to cause problems in practice, especially since most small problems that were caused by this area of bugs has been fixed-up in the past in a way that masked the particular bug observed without fixing the real underlying problem.
An explanation of how we arrived at the current situation is perhaps merited.
The setting of GIT_DIR
and GIT_WORK_TREE
done by sequencer.c
arose from a sequence of historical accidents:
When rebase was implemented as a shell command, it would call git-sh-setup
, which among other things would set GIT_DIR
-- but not export it.
This meant that when rebase --exec
commands were run via /bin/sh -c $COMMAND they would not inherit the GIT_DIR
setting.
The fact that GIT_DIR
was not set in the run $COMMAND is the behavior we'd like to restore.
When the rebase--helper
builtin was introduced to allow incrementally replacing shell with C code, we had an implementation that was half shell, half C.
In particular, commit 18633e1 ("rebase -i: use the rebase--helper builtin", 2017-02-09, Git v2.13.0-rc0 -- merge listed in batch #1) added calls to exec git rebase--helper ...
which caused rebase--helper to inherit the GIT_DIR
environment variable from the shell.
git's setup would change the environment variable from an absolute path to a relative one (".git"), but would leave it set.
This meant that when rebase --exec
commands were run via run_command_v_opt(
...) they would inherit the GIT_DIR
setting.
In commit 09d7b6c ("sequencer
: pass absolute GIT_DIR
to exec commands", 2017-10-31, Git v2.16.0-rc0 -- merge listed in batch #1), it was noted that the GIT_DIR
caused problems with some commands; e.g. git rebase --exec
'cd subdir && 'git describe
'(man) ...
would have GIT_DIR=.git
which was invalid due to the change to the subdirectory.
Instead of questioning why GIT_DIR
was set, that commit instead made sequencer change GIT_DIR
to be an absolute path and explicitly export it via argv_array_pushf(&child_env,
"GIT_DIR=%s",
absolute_path(get_git_dir()
)); run_command_v_opt_cd_env(
..., child_env
.argv)
In commit ab5e67d ("sequencer
: pass absolute GIT_WORK_TREE
to exec commands", 2018-07-14, Git v2.19.0-rc0 -- merge), it was noted that when GIT_DIR
is set but GIT_WORK_TREE
is not, that we do not discover GIT_WORK_TREE
but just assume it is '.'.
That is incorrect if trying to run commands from a subdirectory.
However, rather than question why GIT_DIR
was set, that commit instead also added GIT_WORK_TREE
to the list of things to export.
Each of the above problems would have been fixed automatically when git-rebase
(man) became a full builtin, had it not been for the fact that sequencer.c
started exporting GIT_DIR
and GIT_WORK_TREE
in the interim.
Stop exporting them now.
我正在编写一个 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
在这两种情况下都有效。
我找不到任何表明这是预期行为的文档 - 将不胜感激。
这是 git 2.7.4 Ubuntu 16.04。
文字上的区别:
git -C <em>目录git-sub-command ...</em>
和:
git --git-dir <em>目录git-sub-command ...</em>
是前端设置程序git
使用了OS-level"change directory to"操作(os.chdir
来自Python,chdir()
来自C , 等)为第一个,并为第二个设置环境变量 $GIT_DIR
。在任何一种情况下,它都会找到 sub-command 并运行它。 (请注意,您实际上可以同时执行 。)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
If theGIT_DIR
environment variable is set then it specifies a path to use instead of the default.git
for the base of the repository. The--git-dir
command-line option also sets this value.
这就是$GIT_DIR
设置为相对路径名,并且您没有覆盖它,并且您确实更改了目录,那么您将更改所有各种 Git sub-command 定位存储库的方式。因此,您必须 un-设置它(以获得默认的 $GIT_DIR
-not-set 行为),或者显式地将它设置为绝对路径(以在整个过程中保留它目录更改),或将其显式设置为其他存储库的路径(相对或绝对),具体取决于您想要.
请注意 --work-tree
设置 $GIT_WORK_TREE
,还有其他类似的变量,但是——至少在迄今为止的所有 Git 版本中——$GIT_DIR
是唯一的"pre-set for you"(或 "for your annoyance" :-))在 Git 钩子中。
如果您从那时起(2017 年)升级了 Git,请确保使用最新的 Git 2.35(2022 年第一季度):
"git rebase -x
"(man) 在用 C 重写命令时错误地开始导出 GIT_DIR
和 GIT_WORK_TREE
环境变量,已更正。
参见 commit 434e063 (04 Dec 2021) by Elijah Newren (newren
)。
(由 Junio C Hamano -- gitster
-- in commit 57f28f4 合并,2021 年 12 月 21 日)
sequencer
: do not exportGIT_DIR
andGIT_WORK_TREE
for 'exec'Signed-off-by: Elijah Newren
Acked-by: Johannes Schindelin
Acked-by: Johannes Altmanninger
Acked-by: Phillip Wood
Commands executed from
git rebase --exec
(man) can give different behavior from within that environment than they would outside of it, due to the fact thatsequencer.c
exports bothGIT_DIR
andGIT_WORK_TREE
.
For example, if the relevant script calls something likegit -C ../otherdir log --format=%H --no-walk
the user may be surprised to find that the command above does not show a commit hash from
../otherdir
, because$GIT_DIR
prevents automaticgitdir
detection and makes the-C
option useless.This is a regression in behavior from the original legacy implemented-in-shell rebase.
It is perhaps rare for it to cause problems in practice, especially since most small problems that were caused by this area of bugs has been fixed-up in the past in a way that masked the particular bug observed without fixing the real underlying problem.An explanation of how we arrived at the current situation is perhaps merited.
The setting ofGIT_DIR
andGIT_WORK_TREE
done bysequencer.c
arose from a sequence of historical accidents:
When rebase was implemented as a shell command, it would call
git-sh-setup
, which among other things would setGIT_DIR
-- but not export it.
This meant that whenrebase --exec
commands were run via /bin/sh -c $COMMAND they would not inherit theGIT_DIR
setting.
The fact thatGIT_DIR
was not set in the run $COMMAND is the behavior we'd like to restore.When the
rebase--helper
builtin was introduced to allow incrementally replacing shell with C code, we had an implementation that was half shell, half C.
In particular, commit 18633e1 ("rebase -i: use the rebase--helper builtin", 2017-02-09, Git v2.13.0-rc0 -- merge listed in batch #1) added calls to exec git rebase--helper ...
which caused rebase--helper to inherit theGIT_DIR
environment variable from the shell.
git's setup would change the environment variable from an absolute path to a relative one (".git"), but would leave it set.
This meant that whenrebase --exec
commands were run viarun_command_v_opt(
...) they would inherit theGIT_DIR
setting.In commit 09d7b6c ("
sequencer
: pass absoluteGIT_DIR
to exec commands", 2017-10-31, Git v2.16.0-rc0 -- merge listed in batch #1), it was noted that theGIT_DIR
caused problems with some commands; e.g.git rebase --exec
'cd subdir && 'git describe
'(man) ...
would haveGIT_DIR=.git
which was invalid due to the change to the subdirectory.
Instead of questioning whyGIT_DIR
was set, that commit instead made sequencer changeGIT_DIR
to be an absolute path and explicitly export it viaargv_array_pushf(&child_env,
"GIT_DIR=%s",
absolute_path(get_git_dir()
));run_command_v_opt_cd_env(
...,child_env
.argv)In commit ab5e67d ("
sequencer
: pass absoluteGIT_WORK_TREE
to exec commands", 2018-07-14, Git v2.19.0-rc0 -- merge), it was noted that whenGIT_DIR
is set butGIT_WORK_TREE
is not, that we do not discoverGIT_WORK_TREE
but just assume it is '.'.
That is incorrect if trying to run commands from a subdirectory.
However, rather than question whyGIT_DIR
was set, that commit instead also addedGIT_WORK_TREE
to the list of things to export.Each of the above problems would have been fixed automatically when
git-rebase
(man) became a full builtin, had it not been for the fact thatsequencer.c
started exportingGIT_DIR
andGIT_WORK_TREE
in the interim.
Stop exporting them now.