如何确保我的git预提交脚本不会被愚弄?

如何确保我的git预提交脚本不会被愚弄?,git,hook,pre-commit-hook,Git,Hook,Pre Commit Hook,我正在编写git预提交脚本,在编写过程中遇到了一些困难。我遇到的第一个问题是将文件添加到索引后所做的更改。例如: 我编写了一个小的test.py脚本 我使用git add test.py将其添加到索引中 我在test.py中更改了一些内容,但不添加这些更改 我提交先前添加的文件 然后它触发我的预提交脚本,该脚本恰好读取test.py,以确保它没有问题。问题是,即将提交的test.py和我工作树中的test.py是不同的!!因此,我的脚本基本上是在检查错误的文件,可能会遗漏代码中一些非常重要的问题

我正在编写git预提交脚本,在编写过程中遇到了一些困难。我遇到的第一个问题是将文件添加到索引后所做的更改。例如:

我编写了一个小的test.py脚本 我使用git add test.py将其添加到索引中 我在test.py中更改了一些内容,但不添加这些更改 我提交先前添加的文件 然后它触发我的预提交脚本,该脚本恰好读取test.py,以确保它没有问题。问题是,即将提交的test.py和我工作树中的test.py是不同的!!因此,我的脚本基本上是在检查错误的文件,可能会遗漏代码中一些非常重要的问题。经过一点研究,我发现有些人在钩子开始时做一个git隐藏推,在钩子结束时做一个git隐藏弹出,为了确保预提交脚本正在分析文件的提交版本,但我发现这有点冒险,请参见下面的原因,我真的不喜欢在运行由git命令触发的脚本时执行git命令。因此,我的第一个问题是:什么是确保我正在分析提交文件而不是工作树中的文件的最佳方法?也许我可以尝试直接读取.git/objects/*文件

那玩意儿让我想知道。。。如果我公司的开发人员正在使用我的预提交脚本,在预提交脚本运行时决定在另一个终端中切换分支,该怎么办?好的,我已经知道了答案,因为我做了一些测试:提交将失败致命:无法锁定ref'HEAD',git stash pop将出现在另一个分支中,并可能导致冲突。另一种情况可能是,开发人员在隐藏推送之后,在我的预提交脚本加载文件之前修改文件,导致我的脚本再次分析错误的文件内容,这基本上是一种竞争条件,其中涉及到人。我确实意识到这些场景有点扭曲,但我公司的开发人员并不都熟悉git,我肯定觉得这是可能发生的事情。。。因此,我的第二个问题是:如何确保在我的预提交完成后工作树保持完整,即使开发人员在此期间做了一些疯狂的事情?我曾希望git在钩子期间创建某种锁文件,以防止开发人员做奇怪的事情,但似乎没有


我想如果有一个好方法来回答我的第一个问题,那么第二个问题就无关紧要了,但我还是问了它以防万一。迫不及待地想看看你们都说了些什么

这是一个非常困难的问题,如果您想全面解决这个问题,那么git commit、git commit-a、git commit-only foo、git commit-include bar以及您提到的其他项目

有人提出了一个非常好的解决方案,你可以直接使用。我自己从来没有用过它,所以我这里不是特别推荐它,但请看一看

然后它触发我的预提交脚本,该脚本恰好读取test.py,以确保它没有问题。问题是,即将提交的test.py和我工作树中的test.py是不同的

这就是为什么需要确保预提交脚本在索引中的文件上运行,而不是在工作树上运行。事实上,分期提交与实际工作树中的不同是很常见的,例如Git AddioP,它允许您分阶段文件。

处理这个问题的一种方法是将索引签出到一个临时目录中,并在那里运行测试。您可以使用该命令将索引的副本签出到临时目录中

下面是一个预提交钩子示例,如果任何文件包含单词BAD,它将拒绝提交:


我相信这也解决了您的第二个问题,即确保您正在测试的树在测试期间保持一致。因为您在一个临时目录中使用存储库的副本,所以不必担心任何更改。

您想解决什么问题?您不能强制开发人员使用预提交挂钩。他们可以简单地禁用它们,用其他东西覆盖它们,或者运行git commit-no-verify。这不是您希望在服务器上的CI管道中执行的操作吗?运行linter,检查代码是否存在编译错误,运行测试。就我个人而言,当提交时间超过一秒钟时,我真的很恼火。有时我只是想快速保存进度,然后再清理;如果预提交钩子拒绝了我的提交,我会对安装钩子的人非常生气。我会说,你正在考虑的这些扭曲的场景永远不会发生,除非你的钩子需要很长时间才能运行,这本身就是一个问题,开发人员可以完全跳过钩子,而无需验证。如果您不想做隐藏的事情,您可以在有未老化的更改时中止提交,然后让开发人员进行修改
在提交之前,一定要正确地准备好所有内容。不过,这可能会让其他人感到恼火,请参见上面的评论。如果确实需要运行某些检查,那么它必须是远程端操作。我确实意识到客户端钩子可以很容易地绕过,并且我们已经在CI管道中执行了许多检查。但是我们的CI由于各种正当原因非常慢,所以我愿意与同事分享这个预提交脚本,以便在到达服务器之前能够识别出基本错误,而服务器几乎一直处于过载状态。我的预提交脚本非常快,但我想处理一些边缘情况,开发人员在许多文件中提交了许多更改,我想对每个文件运行静态分析可能需要很长时间,我不想让他们与git为难,因为他们决定在预提交脚本工作时切换分支。@GinoMempin让开发人员确保在提交之前正确地准备好所有内容,这听起来很烦人。。。。这可能是最好的方法,以确保他们将禁用钩前臂很多!我相信我在某个地方见过这个,但无法复制,所以我放弃了。我错过了尾随,现在它工作得很好!然而,我正在处理一个大项目,签出在0.6到0.7秒之间需要相当长的时间。再加上我的预提交脚本的处理,我担心开发者会完全恼火。。。我想我宁愿选择阅读.git/objects/,因为它看起来要快得多,而且希望是安全的。但是,您的答案仍然正确,所以我为未来的谷歌用户标记为:未来谷歌用户的小更新:不要像我说的那样直接读取.git/objects。如果您不想复制整个索引,只需在命令行中将文件传递给checkout,如下所示:git checkout index-prefix=$mktemp-d precommitXXXXXX-f-file1 file2 file3索引中的文件可以使用git diff index-cached head获得感谢您的回复,我已经听说了预提交框架,它看起来很棒,但我现在不想为外部依赖而烦恼。我不知道你说的是什么意思,如果你想全面解决这个问题,git提交,git提交-a,git提交-only foo,git提交-include bar。git预提交钩子不是在这些情况下触发的吗?虽然预提交钩子确实在这些情况下运行,但它们都会导致预提交钩子必须以不同的方式运行,这取决于您打算在预提交钩子中执行的操作。特别是git commit-only在提交过程中会导致三个索引文件处于活动状态。你必须非常小心使用哪一个用于什么目的。
#!/bin/sh

echo "running checks"

# create a temporary directory
tmpdir=$(mktemp -d precommitXXXXXX)

# make sure we clean it up when we're done
trap "rm -rf $tmpdir" EXIT

# check out the index
git checkout-index --prefix=$tmpdir/ -af

# run tests in a subshell so that we end up back in the current
# directory when everything finishes.
(
  cd $tmpdir
  
  if grep -q BAD *; then
    echo "ERROR: found bad files"
    exit 1
  fi
)