在预提交挂钩中调用 git
Calling git in pre-commit hook
我得到 运行 宁我的 git 预提交挂钩的奇怪结果,例如当我这样做时
git diff --name-only 在终端中它似乎给出不同的结果
当它在 .git/hooks/pre-commit
中执行时
所以我的问题是:
- 我可以在 git 钩子中调用 git 吗?
- 如果 1. 没问题:如果我做 git 提交,那么什么时候调用预提交挂钩
-am"bla"?特别是 git 是否先进行暂存然后调用预提交挂钩?
我问这个是因为我试过 2 或 3 次这个:
我修改了一个文件,我手动 运行 脚本,它打印出来
#! /bin/sh -xv
files=$(git diff --name-only)
+ git diff --name-only
+ files=path/to/file.h
echo $files
+ echo path/to/file.h
path/to/file.h
...
当我执行 git commit -am"eh" 时,输出不同
#! /bin/sh -xv
files=$(git diff --name-only)
+ git diff --name-only
+ files=
echo $files
+ echo
是的,更改似乎已经缓存。使用 git diff --cached --name-only
列出即将提交的文件。
- Am I allowed to call git inside git hooks?
是的,但是您必须谨慎行事,因为环境中设置了很多东西并且您正在处理正在完成的事情:
GIT_DIR
设置为 Git 目录的路径。
GIT_WORKTREE
可以设置为工作树的路径(来自 git --work-tree
)。
- 其他 Git 变量,例如
GIT_NO_REPLACE_OBJECTS
,也可以从命令行设置。
(如果您继续使用当前存储库,则应保留这些设置,但如果您使用 不同的 存储库,则清除它们。)
- If 1. is ok: when exactly is pre-commit hook called if I do git commit -am"bla"? In particular does git do staging first and then it calls the pre-commit hook or not?
这很复杂。
git commit
内部使用了三个"modes"。 (对此没有任何承诺,但这就是多年来实施的方式,所以这三种模式的东西看起来相当稳定。)模式是:
git commit
没有 -a
、--include
、--only
、and/or 任何命令行指定的文件名。这是默认或正常模式。底层实现细节就不展示了。
git commit
与 -a
或命令行指定的文件名。这分为两个子模式:
- 这样的提交
--include
,或
--only
这样的提交。
至此,底层实现就透彻了。
这里的底层实现细节涉及 Git 调用的东西,不同的是 index,staging area,和(现在很少)缓存,它通常作为名为 $GIT_DIR/index
的文件实现(其中 $GIT_DIR
是关于第 1 点的注释中的环境变量)。通常,只有其中之一:the 索引。它包含您打算提交的内容。1 当您 运行 git commit
, Git 将打包 中的任何内容 作为下一次提交的索引。
但是,在ofgit commit
的操作过程中,最多可能有三个个索引文件。对于正常的 git commit
只有一个索引,您的预提交挂钩可以使用它甚至可以更新它。 (我建议不要更新它,原因我们稍后会看到。)
但是,如果您执行 git commit -a
或 git commit --include file.ext
,现在有 两个 索引文件。有准备提交的内容——常规索引——和一个extra索引,它是原始索引加上执行git add
在 file.ext
或所有文件上(相当于 git add -u
)。所以现在有两个个索引文件。
在此模式下,Git 将常规索引文件 保留为 常规索引文件。这个文件像往常一样在 $GIT_DIR/index
中。 second 索引文件,以及额外添加的内容,位于 $GIT_DIR/index.lock
中,环境变量 GIT_INDEX_FILE
设置为保存该路径。如果提交 失败 ,Git 将删除 index.lock
文件,一切都会像你根本没有 运行 git commit
一样.如果提交 成功 ,Git 会将 index.lock
重命名为 index
,释放锁并更新 the (标准,常规)索引全部在一个动作中。
最后,还有 第三种 模式,例如,当您 运行 git commit --only file.ext
时会得到这种模式。在这里,现在有三个个索引文件:
$GIT_DIR/index
: 标准索引,保持它通常做的事情。
$GIT_DIR/index.lock
:file.ext
已被 git add
编辑的标准索引的副本。
$GIT_DIR/index<em>后缀</em>
:HEAD
提交的副本2 file.ext
已被 git add
编辑。
环境变量GIT_INDEX_PATH
指向这第三个索引。如果提交成功,Git会将index.lock
文件重命名为index
,使其成为索引。如果提交失败,Git 将删除 index.lock
文件,以便索引恢复到开始之前的状态。 (在任何一种情况下,Git 都会删除第三个索引,它现在已经达到了它的目的。)
请注意,从预提交挂钩中,您可以检测 git commit
是标准提交(GIT_INDEX_FILE
未设置或设置为 $GIT_DIR/index
)还是两个特殊提交之一模式。在标准模式下,如果你想更新the索引,你可以照常进行。在这两种特殊模式下,您可以使用 git add
修改 GIT_INDEX_FILE
命名的文件,这将修改提交的内容;如果您使用 --include
样式提交,这也会修改将成为成功标准索引的内容。但是如果你处于--only
模式,修改提议的提交不会影响标准index
,也不 将 成为 标准索引的 index.lock
。
考虑一个具体的例子,假设用户做了:
git add file1 file2
以便标准索引匹配 HEAD
除了 file1
和 file2
。那么用户 运行s:
git commit --only file3
因此建议的提交是 HEAD
的副本,添加了 file3
, 和 ,如果此提交成功,Git 将将标准索引替换为 file1
、file2
和 file3
都已添加的索引(但由于 file3
将匹配新的 HEAD
提交,因此只有文件1 和 2 将在新索引中 修改 。
现在假设您的提交挂钩 运行s git add file4
并且整个过程成功(新提交成功)。 git add
步骤会将 file4
的工作树版本复制到临时索引中,以便提交将同时具有 file3
和 file4
已更新。然后 Git 将重命名 index.lock
文件,以便 file3
将匹配新的 HEAD
提交。但是 index.lock
中的 file4
从未更新过,因此它 不会 匹配 HEAD
提交。在用户看来,file4
不知何故被还原了! git status
将显示对它的未决更改,暂存提交,而 git diff --cached
将显示 HEAD
和索引之间的区别是 file4
已更改回匹配 HEAD~1
.
中的 file4
您 可以 对此模式进行预提交挂钩测试,并在此模式下拒绝 git add
文件,以避免出现问题。 (或者,你甚至可以偷偷地添加 file4
到 index.lock
,使用第二个 git add
命令!)但是通常最好让你的钩子拒绝提交,并建议用户自己做任何 git add
s,这样你就不必首先知道所有这些实施秘密 about git commit
。
1索引还包含一些额外信息:缓存数据关于工作树。这就是它有时被称为缓存的原因。我在这里描述的这些额外副本是通过复制原始索引制作的,因此额外副本也具有相同的缓存数据,除非它们通过 git add
.
更新。
2未指定 Git 是否通过内部等价物制作此副本:
TMP=$GIT_DIR/index<digits>
cp $GIT_DIR/index $TMP
GIT_INDEX_FILE=$TMP git reset
GIT_INDEX_FILE=$TMP git add file3
或其他一些方式(例如,git read-tree
的内部等价物),但是由于这个特定的副本总是在进程结束时被删除,所以这无关紧要:任何缓存信息工作树变得无关紧要。
我得到 运行 宁我的 git 预提交挂钩的奇怪结果,例如当我这样做时 git diff --name-only 在终端中它似乎给出不同的结果 当它在 .git/hooks/pre-commit
中执行时所以我的问题是:
- 我可以在 git 钩子中调用 git 吗?
- 如果 1. 没问题:如果我做 git 提交,那么什么时候调用预提交挂钩 -am"bla"?特别是 git 是否先进行暂存然后调用预提交挂钩?
我问这个是因为我试过 2 或 3 次这个: 我修改了一个文件,我手动 运行 脚本,它打印出来
#! /bin/sh -xv
files=$(git diff --name-only)
+ git diff --name-only
+ files=path/to/file.h
echo $files
+ echo path/to/file.h
path/to/file.h
...
当我执行 git commit -am"eh" 时,输出不同
#! /bin/sh -xv
files=$(git diff --name-only)
+ git diff --name-only
+ files=
echo $files
+ echo
是的,更改似乎已经缓存。使用 git diff --cached --name-only
列出即将提交的文件。
- Am I allowed to call git inside git hooks?
是的,但是您必须谨慎行事,因为环境中设置了很多东西并且您正在处理正在完成的事情:
GIT_DIR
设置为 Git 目录的路径。GIT_WORKTREE
可以设置为工作树的路径(来自git --work-tree
)。- 其他 Git 变量,例如
GIT_NO_REPLACE_OBJECTS
,也可以从命令行设置。
(如果您继续使用当前存储库,则应保留这些设置,但如果您使用 不同的 存储库,则清除它们。)
- If 1. is ok: when exactly is pre-commit hook called if I do git commit -am"bla"? In particular does git do staging first and then it calls the pre-commit hook or not?
这很复杂。
git commit
内部使用了三个"modes"。 (对此没有任何承诺,但这就是多年来实施的方式,所以这三种模式的东西看起来相当稳定。)模式是:
git commit
没有-a
、--include
、--only
、and/or 任何命令行指定的文件名。这是默认或正常模式。底层实现细节就不展示了。git commit
与-a
或命令行指定的文件名。这分为两个子模式:- 这样的提交
--include
,或 --only
这样的提交。
至此,底层实现就透彻了。- 这样的提交
这里的底层实现细节涉及 Git 调用的东西,不同的是 index,staging area,和(现在很少)缓存,它通常作为名为 $GIT_DIR/index
的文件实现(其中 $GIT_DIR
是关于第 1 点的注释中的环境变量)。通常,只有其中之一:the 索引。它包含您打算提交的内容。1 当您 运行 git commit
, Git 将打包 中的任何内容 作为下一次提交的索引。
但是,在ofgit commit
的操作过程中,最多可能有三个个索引文件。对于正常的 git commit
只有一个索引,您的预提交挂钩可以使用它甚至可以更新它。 (我建议不要更新它,原因我们稍后会看到。)
但是,如果您执行 git commit -a
或 git commit --include file.ext
,现在有 两个 索引文件。有准备提交的内容——常规索引——和一个extra索引,它是原始索引加上执行git add
在 file.ext
或所有文件上(相当于 git add -u
)。所以现在有两个个索引文件。
在此模式下,Git 将常规索引文件 保留为 常规索引文件。这个文件像往常一样在 $GIT_DIR/index
中。 second 索引文件,以及额外添加的内容,位于 $GIT_DIR/index.lock
中,环境变量 GIT_INDEX_FILE
设置为保存该路径。如果提交 失败 ,Git 将删除 index.lock
文件,一切都会像你根本没有 运行 git commit
一样.如果提交 成功 ,Git 会将 index.lock
重命名为 index
,释放锁并更新 the (标准,常规)索引全部在一个动作中。
最后,还有 第三种 模式,例如,当您 运行 git commit --only file.ext
时会得到这种模式。在这里,现在有三个个索引文件:
$GIT_DIR/index
: 标准索引,保持它通常做的事情。$GIT_DIR/index.lock
:file.ext
已被git add
编辑的标准索引的副本。$GIT_DIR/index<em>后缀</em>
:HEAD
提交的副本2file.ext
已被git add
编辑。
环境变量GIT_INDEX_PATH
指向这第三个索引。如果提交成功,Git会将index.lock
文件重命名为index
,使其成为索引。如果提交失败,Git 将删除 index.lock
文件,以便索引恢复到开始之前的状态。 (在任何一种情况下,Git 都会删除第三个索引,它现在已经达到了它的目的。)
请注意,从预提交挂钩中,您可以检测 git commit
是标准提交(GIT_INDEX_FILE
未设置或设置为 $GIT_DIR/index
)还是两个特殊提交之一模式。在标准模式下,如果你想更新the索引,你可以照常进行。在这两种特殊模式下,您可以使用 git add
修改 GIT_INDEX_FILE
命名的文件,这将修改提交的内容;如果您使用 --include
样式提交,这也会修改将成为成功标准索引的内容。但是如果你处于--only
模式,修改提议的提交不会影响标准index
,也不 将 成为 标准索引的 index.lock
。
考虑一个具体的例子,假设用户做了:
git add file1 file2
以便标准索引匹配 HEAD
除了 file1
和 file2
。那么用户 运行s:
git commit --only file3
因此建议的提交是 HEAD
的副本,添加了 file3
, 和 ,如果此提交成功,Git 将将标准索引替换为 file1
、file2
和 file3
都已添加的索引(但由于 file3
将匹配新的 HEAD
提交,因此只有文件1 和 2 将在新索引中 修改 。
现在假设您的提交挂钩 运行s git add file4
并且整个过程成功(新提交成功)。 git add
步骤会将 file4
的工作树版本复制到临时索引中,以便提交将同时具有 file3
和 file4
已更新。然后 Git 将重命名 index.lock
文件,以便 file3
将匹配新的 HEAD
提交。但是 index.lock
中的 file4
从未更新过,因此它 不会 匹配 HEAD
提交。在用户看来,file4
不知何故被还原了! git status
将显示对它的未决更改,暂存提交,而 git diff --cached
将显示 HEAD
和索引之间的区别是 file4
已更改回匹配 HEAD~1
.
file4
您 可以 对此模式进行预提交挂钩测试,并在此模式下拒绝 git add
文件,以避免出现问题。 (或者,你甚至可以偷偷地添加 file4
到 index.lock
,使用第二个 git add
命令!)但是通常最好让你的钩子拒绝提交,并建议用户自己做任何 git add
s,这样你就不必首先知道所有这些实施秘密 about git commit
。
1索引还包含一些额外信息:缓存数据关于工作树。这就是它有时被称为缓存的原因。我在这里描述的这些额外副本是通过复制原始索引制作的,因此额外副本也具有相同的缓存数据,除非它们通过 git add
.
2未指定 Git 是否通过内部等价物制作此副本:
TMP=$GIT_DIR/index<digits>
cp $GIT_DIR/index $TMP
GIT_INDEX_FILE=$TMP git reset
GIT_INDEX_FILE=$TMP git add file3
或其他一些方式(例如,git read-tree
的内部等价物),但是由于这个特定的副本总是在进程结束时被删除,所以这无关紧要:任何缓存信息工作树变得无关紧要。