简体   繁体   English

Git pre-commit/pre-push hook 在提交时运行单元测试,工作树上未提交的更改

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

I currently have a simple pre-commit hook for my project (in R, though that's incidental to this question) which runs unit tests:我目前有一个简单的pre-commit钩子用于我的项目(在 R 中,尽管这是这个问题的附带问题),它运行单元测试:

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

Where tests/testthat.R is a wrapper file that handles all the tests.其中tests/testthat.R是处理所有测试的包装文件。

This hook has a problem, though: if I make partial commits (such that there are still changes which remain uncommitted), the tests will run on the current state of the working tree, including the uncommitted changes.但是,这个钩子有一个问题:如果我进行部分提交(这样仍有未提交的更改),测试将在工作树的当前 state 上运行,包括未提交的更改。

So, let's say I finish "Step 1" of something I'm doing but forget to commit it.所以,假设我完成了我正在做的事情的“第 1 步”,但忘记提交它。 I then start on "Step 2", but then realize I forgot to commit "Step 1".然后我从“第 2 步”开始,但后来意识到我忘了提交“第 1 步”。 If "Step 2" is currently in a broken state due to my unfinished changes, I won't be able to do a partial commit for "Step 1" because the tests will detect that "Step 2" is defective.如果“步骤 2”由于我未完成的更改而当前处于损坏的 state 中,我将无法对“步骤 1”进行部分提交,因为测试会检测到“步骤 2”有缺陷。

So, is there a way for the hook to run on the version which is actually being committed?那么,有没有办法让钩子在实际提交的版本上运行? My thought would be a hook which effectively temporarily checks out a commit, runs the tests on that checkout, deletes the checkout, and then defines whether to allow the commit to go through.我的想法是一个钩子,它可以有效地临时签出提交,在该签出上运行测试,删除签出,然后定义是否允许通过提交到 go。 But given that this hook triggers before the commit is done, I'm assuming it's impossible to check it out.但是鉴于这个钩子在提交完成之前触发,我假设不可能检查它。

I'd also be open to a pre-push hook.我也愿意接受pre-push钩。 This seems more plausible since the hook receives the SHA's for the commits being pushed, so my idea above seems more reasonable: get the latest SHA, create a temporary directory, checkout that SHA, run the tests, delete the directory.这似乎更合理,因为钩子接收被推送的提交的 SHA,所以我上面的想法似乎更合理:获取最新的 SHA,创建一个临时目录,签出该 SHA,运行测试,删除目录。 But then my question becomes whether that's actually the suggested method or if I'm overcomplicating things due to my ignorance.但是后来我的问题变成了这实际上是建议的方法,还是由于我的无知而使事情变得过于复杂。

This article suggests to stash the changes that are not being committed before running the tests, and then un-stash them after tests are run.本文建议在运行测试之前存储未提交的更改,然后在运行测试后取消存储它们。 Relevant code snippet:相关代码片段:

# 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

Ends up that the git stash manual page describes this exact use-case:最后git stash手册页描述了这个确切的用例:

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:当您想从工作树中的更改中进行两次或多次提交时,您可以使用git stash push --keep-index ,并且您想在提交之前测试每个更改:

 #... 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'

So simply do所以简单地做

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

Using this in a hook has an edge-case risk, though: you might have an old, unrelated stash you've forgotten about, and might want to make a clean commit (leaving no uncommitted changes).但是,在钩子中使用它有一个边缘情况风险:您可能有一个旧的、不相关的存储,您已经忘记了,并且可能想要进行干净的提交(不留下未提交的更改)。

In this case, the call to git stash push --keep-index won't actually create a stash (returning "No local changes to save").在这种情况下,对git stash push --keep-index的调用实际上不会创建存储(返回“没有要保存的本地更改”)。 But when the tests are complete, git stash pop will find the old stash, leading to at the very least a headache.但是当测试完成后, git stash pop会找到旧的 stash,至少会让人头疼。

So my actual pre-commit hook looks like:所以我实际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

Basically, use git diff to see if there are any changes to tracked files.基本上,使用git diff来查看跟踪文件是否有任何更改。 If there are, stash and later pop them.如果有,请将它们存储起来,然后再弹出它们。 Otherwise, don't bother with the stash operations.否则,不要打扰隐藏操作。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM