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

问题描述

我正在编写一个 git pre-commit 脚本,一路上遇到了一些困难。我遇到的第一个是将文件添加到索引后所做的更改。例如:

  • 我写了一个test.py脚本
  • 我使用 git add test.py
  • 将其添加到索引中
  • 我更改了 test.py 中的某些内容(但不要git add 这些更改)
  • 我提交了之前添加文件

然后它触发我的预提交脚本,该脚本恰好读取 test.py 以确保它没有任何问题。问题是,即将提交的 test.py 和我的工作树中的那个是不同的!!所以我的脚本基本上是在检查错误文件,可能会遗漏一些非常重要的代码问题。经过一番研究,我发现有些人在钩子的开头和 git stash push一个 git stash pop,以“确保”预提交脚本正在分析提交的版本文件,但我发现它有点冒险(请参阅下面我为什么这么认为),而且我真的不喜欢在运行由 git 命令触发的脚本时执行 git 命令的想法。所以我的第一个问题是:确保我正在分析正在提交的文件而不是我的工作树中的文件的最佳方法是什么?也许我可以尝试直接读取 .git/objects/*文件

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

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

解决方法

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

这就是为什么您需要确保您的 pre-commit 脚本在索引中的文件上运行,而不是在您的工作树上。实际上,暂存提交与工作树中的实际内容不同是很常见的(例如,考虑 git add -p,它允许您暂存部分文件)。

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

这是一个示例 pre-commit 钩子,如果任何文件包含单词 BAD,它将拒绝提交:

#!/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
)

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

,

这是一个非常困难的问题,如果您想全面解决它(git commitgit commit -agit commit --only foogit commit --include bar,以及您需要的其他项目)有提到)。

有人提出了一个非常好的解决方案,您可以直接使用。我自己从来没有用过它,所以我在这里没有特别推荐它,而是看看https://pre-commit.com/