Git pre-commit hook + docker =不同的git状态

我想在pre-commit git钩子中运行一个脚本。 我想要该脚本从一个泊坞窗图像中运行。 这个预先提交钩子的代码示例:

# 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 commit --all时, git status的输出在pre-commit hook中不同于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选项; 这些变化没有上演。

我错过了什么?

TL; DR:添加-e GIT_INDEX_FILE 。 但是,您可能想要允许其他Gitvariables。 或者,您可以简单地禁止这样的提交,或者使用完全不同的机制(参见下面的描述中的最后段落)。

描述

当你使用git commit --all ,Git会创build一个临时索引来保存这个staged文件,因为它们还没有在普通索引中进行。 如果提交成功 ,这个临时索引最终将成为常规索引。 在此之前,这不是常规指数。

现在,您的预先提交脚本包含以下行:

 docker run -v ${repo_root}:${repo_root} -w ${repo_root} ... 

-v选项在运行docker映像的subprocess中挂载文件系统, -w将其设置为工作目录。 但是, docker命令过滤了subprocess中的环境,将所有未使用--env--env-file (– --env-e的缩写forms)显式启用的可疑variables剥离。

顶层的git文档页面有一个关于环境variables的部分 ,其中包括:

GIT_INDEX_FILE

    该环境允许指定备用索引文件。 如果未指定,则使用$GIT_DIR/index的缺省值。

git commit --allbuild立一个临时索引时,它使用这个环境variables来引导它的所有子命令来查看临时索引而不是$GIT_DIR/index

值得注意的是,Git也设置了$GIT_DIR ,但它可能已经设置为. ,所以当docker run时,Git会查找当前工作目录中的资源库,通过-w将其设置为资源库根目录,这样就可以剥离了. 原来是无害的。 尽pipe如此,你可能要考虑通过所有的Git的环境variables。 这并不像盲目地传递文档中列出的所有东西那么简单:例如,如果需要导出GIT_ALTERNATE_OBJECT_DIRECTORIESpath,则需要GIT_ALTERNATE_OBJECT_DIRECTORIESpath中的每个元素都装入到docker实例中, -v选项。

幸运的是,当GIT_INDEX_FILE被设置在这个特定的点时,它被设置为forms.git/ temporary-name的path名,所以不需要挂载额外的文件系统来获取正在运行的映像中的Git临时索引。 这是因为这个名字将被重命名为.git/index ,Git希望确保重命名可以作为一个primefaces文件系统操作完成,这要求它与.git本身存在于相同的挂载点上。 事实上,对于git commit --all这只是.git/index.lock ,虽然git commit --only使用其他名称: – --onlyforms需要多个临时索引文件,而用于提交的不是一个这将成为成功的正常指标。

最后 – 这完全独立于Docker – 请注意,可能要求Git提交与工作树中的内容不匹配的暂存文件。 例如,使用git add -p ,可以很容易地在索引中存储HEAD版本和工作树版本之间仅有一些差异的文件版本。 我猜你打算让docker环境运行一些testing什么是承诺。 没关系,但要注意“要做什么”并不一定就是“工作树中的东西”。 当使用--all和临时索引时,临时索引包含要提交的内容,临时索引刚刚工作树构build,因此它们将匹配; 但是当使用 – 所有和使用真正的索引时,或者仅在使用不同的临时索引时,“将要提交的内容”不一定与工作树匹配。

编写一个可以看到“要提交什么”的预先提交钩子是棘手的,但并非不可能。 一种方法是将索引中的任何内容提取到与当前工作树无关的临时目录中。 然后,您可以在临时目录上运行testing系统,不受存储库自身的干扰,也不会受到当前工作树的干扰。 如果你在预先提交的脚本中这样做,你可以挂载(通过-v-w )这个临时目录,而不用担心在docker镜像里面运行任何 Git命令。

附注: #! 线

您的示例可能会被修改为StackOverflow发布的目的,但在:

 # pre-commit hook #!/bin/bash git status 

#! 行已经变得毫无用处。 这些行必须是脚本的第一行。 原因在于内核(Linux或从其派生的Unix)运行这样的脚本的方式是检查文件的前几个字节。 如果前两个字节是#! ,第一的其余部分(在一定的合理范围内)(直到第一个换行符)被当作翻译的名字,也许是这个翻译的select。 然后内核运行解释器,而不是脚本,从#!传递选项#! 如果有的话,那么脚本的名称。

如果第一行不以#!开始#! 但是,内核只是拒绝直接运行文件( execveENOEXEC失败)。 shell发现错误,自己检查文件,并决定该文件是否是一个shell脚本…如果是的话, shell自己运行该文件。 这里的主要问题是shell可能会select错误的shell来运行该文件。 有一个#!正确的解释器的名字来避免这一点。