Git pre-commit/pre-push 挂钩到 运行 提交单元测试,工作树上有未提交的更改

Git pre-commit/pre-push hook to run unit tests on commits, with uncommited changes on the working tree

我目前有一个简单的 pre-commit 挂钩用于我的项目(在 R 中,虽然这是这个问题的附带)运行s 单元测试:

#!/bin/sh
a=$(Rscript tests/testthat.R)
exit $a

其中 tests/testthat.R 是处理所有测试的包装文件。

这个钩子有一个问题,但是:如果我进行部分提交(这样仍有未提交的更改),测试将 运行 工作树的当前状态,包括未提交的变化。

所以,假设我完成了 "Step 1" 正在做的事情,但忘记提交。然后我从 "Step 2" 开始,但后来意识到我忘记提交 "Step 1"。如果 "Step 2" 由于我未完成的更改而当前处于损坏状态,我将无法对 "Step 1" 进行部分提交,因为测试将检测到 "Step 2" 有缺陷。

那么,有没有办法在实际提交的版本上挂钩 运行?我的想法是一个挂钩,它可以有效地临时签出提交,运行对该签出进行测试,删除签出,然后定义是否允许提交通过。但是鉴于这个钩子在提交完成之前触发,我假设不可能检查出来。

我也愿意接受 pre-push 钩子。这似乎更合理,因为钩子接收被推送的提交的 SHA,所以我上面的想法似乎更合理:获取最新的 SHA,创建一个临时目录,检查 SHA,运行 测试,删除目录。但后来我的问题变成了这是否实际上是建议的方法,或者是否由于我的无知而使事情过于复杂。

article 建议在 运行 测试之前存储未提交的更改,然后在测试 运行 后取消存储它们。相关代码片段:

# pre-commit.sh
STASH_NAME="pre-commit-$(date +%s)"
git stash save -q --keep-index $STASH_NAME

# Test prospective commit
...

STASHES=$(git stash list)
if [[ $STASHES == "$STASH_NAME" ]]; then
  git stash pop -q
fi

最终 git stash manual page 描述了这个确切的用例:

You can use git stash push --keep-index when you want to make two or more commits out of the changes in the work tree, and you want to test each change before committing:

# ... hack hack hack ...
$ git add --patch foo            # add just first part to the index
$ git stash push --keep-index    # save all other changes to the stash
$ edit/build/test first part
$ git commit -m 'First part'     # commit fully tested change
$ git stash pop                  # prepare to work on all other changes
# ... repeat above five steps until one commit remains ...
$ edit/build/test remaining parts
$ git commit foo -m 'Remaining parts'

就这么简单

git stash push --keep-index
# 
# testing...
#
git stash pop

不过,在钩子中使用它有一个极端情况的风险:你可能有一个你忘记的旧的、不相关的存储,并且可能想要进行干净的提交(不留下任何未提交的更改)。

在这种情况下,对 git stash push --keep-index 的调用实际上不会创建存储(返回 "No local changes to save")。但是当测试完成后,git stash pop 会找到旧的 stash,至少会导致头痛。

所以我实际的 pre-commit 钩子看起来像:

#/bin/sh

hasChanges=$(git diff)    
if [ -n "$hasChanges" ]; then
    git stash push --keep-index
fi

#
# testing...
#

if [ -n "$hasChanges" ]; then
    git stash pop
fi

exit $testSuccess

基本上,使用git diff查看跟踪文件是否有任何变化。如果有,请将它们藏起来,然后弹出。否则,不要为隐藏操作而烦恼。