我想在pre-commit git hook中运行一个脚本。我希望该脚本在docker镜像中运行。此预提交挂钩的示例代码:
# pre-commit hook
#!/bin/bash
repo_root=$(git rev-parse --show-toplevel)
docker run -v ${repo_root}:${repo_root} -w ${repo_root} <my_docker_image> <path_to_my_script.py>
my_script.py
在内部运行git status
以确定在预提交挂钩中处理哪些文件。
问题:当我运行git status
时,git commit --all
的输出在预提交挂钩中与在docker容器内的输出不同。
例如:
# pre-commit hook
#!/bin/bash
git status
echo "------------------------------------"
repo_root=$(git rev-parse --show-toplevel)
docker run -v ${repo_root}:${repo_root} -w ${repo_root} <my_docker_image> git status
我希望通过运行git commit --all
,在docker容器中运行git status
,我可以看到所有更改都已上演。
但是,更改不会在docker容器中暂存。我之前写的代码打印出以下内容:
Changes to be committed:
(use "git reset HEAD <file>..." to unstage)
modified: tools/git-hooks/pre-commit
------------------------------------
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git checkout -- <file>..." to discard changes in working directory)
modified: tools/git-hooks/pre-commit
换句话说:在docker内部,git status
未检测到--all
选项;这些变化没有上演。
我错过了什么?
答案 0 :(得分:3)
TL; DR:添加-e GIT_INDEX_FILE
。但是,您可能希望允许其他Git变量。或者,您可以简单地禁止此类提交,或使用完全不同的机制(请参阅下面描述中的最后几段)。
当您使用git commit --all
时,Git会创建一个临时索引来保存暂存文件,因为它们尚未在普通索引中暂存。如果提交成功 ,此临时索引最终将成为常规索引 。在那之前,它不是常规指数。
现在,您的预提交脚本包含以下行:
docker run -v ${repo_root}:${repo_root} -w ${repo_root} ...
-v
选项在运行docker镜像的子进程中安装文件系统,-w
将其设置为工作目录。但是,docker
命令会过滤子进程中的环境,剥离未使用--env
或--env-file
明确启用的所有可疑变量(--env
的缩写形式为{{} 1}})。
top level git
documentation page has a section on environment variables包括:
-e
此环境允许指定备用索引文件。如果未指定,则使用默认值
GIT_INDEX_FILE
。
当$GIT_DIR/index
设置临时索引时,它使用此环境变量指示其所有子命令查看临时索引而不是git commit --all
。
值得注意的是,Git还设置了$GIT_DIR/index
,但它可能已将其设置为$GIT_DIR
,因此当.
将其删除时,Git会查找存储库从当前工作目录开始,您通过docker run
设置为存储库根目录,因此剥离-w
结果是无害的。尽管如此,您可能想要考虑通过所有Git的环境变量。它并不像盲目地传递文档中列出的所有内容那样简单:例如,如果需要导出.
路径,则需要将该路径中的每个元素安装到也可以使用更多GIT_ALTERNATE_OBJECT_DIRECTORIES
选项来使用docker实例。
幸运的是,在此特定点设置-v
时,它会设置为GIT_INDEX_FILE
形式的路径名,因此无需安装其他文件系统将Git临时索引导入到运行映像中。这是因为此名称将重命名为.git/temporary-name
,Git希望确保重命名可以作为原子文件系统操作完成,这要求它与.git/index
存在于同一个挂载点上本身。事实上,对于.git
,它只是git commit --all
,尽管.git/index.lock
使用其他名称:git commit --only
表单需要多个临时索引文件,而另一个用于提交不会成为成功的正常指标。
最后 - 这完全独立于Docker-note,可以让Git提交 不匹配的分阶段文件 什么&#39;现在在工作树上。例如,使用--only
,您可以轻松地在索引中存储一个文件版本,该文件只有某些 git add -p
版本之间的差异和工作树版本。我猜你计划让docker环境对要提交的内容进行某种测试。这很好 - 但请注意&#34;要做什么&#34;不一定是&#34;工作树中的内容&#34;。使用HEAD
和临时索引时,它是包含要提交内容的临时索引,而临时索引只是从工作树构建的,因此它们是会匹配;但是当不使用--all
,使用真实索引,或者使用--all
使用不同的临时索引时,&#34;要提交的内容&#34;不一定与工作树相匹配。
编写一个好的预提交钩子可以看到&#34;要提交的内容&#34;这很棘手,但并非不可能。一种方法是将索引中的任何内容提取到与当前工作树无关的临时目录中。然后,您可以在临时目录上运行测试系统,不受存储库本身的干扰,也不会受到当前工作树的干扰。如果您在预提交脚本中执行此操作,则可以挂载(通过--only
和-v
)此临时目录,而不必担心在docker中运行任何 Git命令图像。
-w
行您的示例可能会针对StackOverflow发布目的进行修改,但在:
#!
# pre-commit hook
#!/bin/bash
git status
行变得毫无用处。这些行必须是脚本的第一行行。原因是内核(Linux或从中派生它的Unix)运行这样的脚本的方式是检查文件的前几个字节。如果前两个字节是#!
,那么第一个行的其余部分 - 在第一个换行符之前的所有内容(在一定的合理限度内)都被视为解释器的名称,也许该解释器的选项。然后内核运行解释器而不是脚本,从#!
行传递选项(如果有的话),然后是脚本的名称。
如果第一行不以#!
开头,则内核只是拒绝直接运行该文件(#!
失败并显示execve
)。 shell发现错误,检查文件本身,并确定文件是否是shell脚本......如果是, shell本身运行该文件。这里的主要问题是shell可能选择了错误的shell来运行该文件。使用带有正确解释器名称的ENOEXEC
行可以避免这种情况。