【问题标题】:git pre-push hook: run test on each new commitgit pre-push hook:在每个新提交上运行测试
【发布时间】:2020-08-20 10:18:06
【问题描述】:

上下文

我想确保我推送的每个提交都通过测试。

我想在我的(客户端)端检查这一点,即在提交之前甚至推送(所以我不想依赖 CI 工具)。

问题

目前,我已经实现了一个 pre-commit 挂钩来运行我的测试,因此我什至无法提交一个损坏的状态。

但是,我的测试套件需要几秒钟才能运行。在编写提交消息之前,我需要等待很多时间。这使它成为每天impractical to use;既是因为我经常提交,而且有时我故意要提交一个损坏的状态以便以后压扁(我知道git commit --no-verify,但这不是重点)。

问题

因此,我不想检查每个提交一次(在创建时),我想在推送之前对它们进行批量测试。

如何实现一个 pre-push 钩子来运行我的测试套件每个新提交要推送?

(为简单起见,假设通过测试意味着test/run_tests.sh 返回0。)

【问题讨论】:

  • pre-push hook 传递本地和远程 ID。要在每次提交上运行测试,请在 git rev-list <remote-id>..<local-id> 上运行一个循环。在循环中检查每个提交到一个临时目录,运行测试并删除该目录。
  • @phd:这里有一点问题:远程 ID 可能在本地不存在。当然,如果是这种情况,那么推送可能一开始就会被拒绝为非快进。

标签: git githooks git-push


【解决方案1】:

感谢phd的提示(in comments)和对git自己示例的无耻掠夺,我起草了以下./.git/hooks/pre-push钩子(我事先注意了chmod +x)。

它似乎在普通情况下可以完成这项工作,我们将看到它随着时间的推移会如何发展。无论如何,欢迎改进!

#!/usr/bin/sh

# An example hook script to verify that each commit that is about to be pushed
# pass the `./run_tests` suite. Called by "git push" after it has checked the
# remote status, but before anything has been pushed.
# If the test suite (and so the script) exits with a non-zero status, nothing
# will be pushed.
#
# In any case, we revert to the pre `$ git push` state.


# Retrieve arguments
remote="$1"
url="$2"

z40=0000000000000000000000000000000000000000 # SHA of a non existing commit


# Save current "git state"
current_branch=$(git rev-parse --abbrev-ref HEAD)

STASH_NAME="pre-push-$(date +%s)"
git stash save -q --keep-index $STASH_NAME


# Do wonders
while read local_ref local_sha remote_ref remote_sha
do
        if [ "$local_sha" = $z40 ]
        then
                # Handle delete
                continue # to the next branch
        elif [ "$remote_sha" = $z40 ]
        then
                # New branch, examine all commits
                range="$local_sha"
        else
                # Update to existing branch, examine new commits
                range="$remote_sha..$local_sha"
        fi

        # Retrieve list of commit in "chronological" order
        commits=$(git rev-list --reverse $range)

        # Loop over each commit
        for commit in $commits
        do
            git checkout $commit

            # Run the tests
            ./test/run_tests.sh

            # Retrieve exit code
            is_test_passed=$?

            # Stop iterating if error
            if [ $is_test_passed -ne 0 ]
            then
                echo -e "Aborting push: Test failed for commit $commit,"\
                  "with following error trace:\n"
                # something like: tail test/run_tests.log
                break 2
            fi
        done
done


# Revert to pre-push state
git checkout $current_branch

STASH_NUM=$(git stash list | grep $STASH_NAME | sed -re 's/stash@\{(.*)\}.*/\1/')
if [ -n "$STASH_NUM" ]
then
    git stash pop -q stash@{$STASH_NUM}
fi
#removed fi

# Return exit code
exit $is_test_passed

【讨论】:

  • 大部分都不错。我改进了一些小的 shell 和 git 习惯用法:使用continue 代替exit,使用elif 并避免过度缩进,使用git rev-parse 获取当前分支,直接使用$? 代替echo $?
【解决方案2】:

根据 ebosi 的回答,我添加了一些您可能感兴趣的修改:

  1. 每次迭代提交循环时,我都会重新编译测试二进制文件(正确提交的正确测试版本)。这是在 shell 函数“recompile_test”中完成的(注意:您可能需要仔细检查在此函数范围内定义的变量“is_test_passed”在整个脚本本身的范围内是否仍然有效)
  2. 如果测试成功,我会显示一个很好的“OK 消息”,以及提交消息的提醒:echo "`git log -1 --oneline $commit`\n"
  3. 如果测试失败,我不会中断并退出!我显示一个不错的“KO 消息”,以及提交消息的提醒。并像 ebosi 在他的回答中所做的那样对测试的痕迹进行总结。
  4. 走到尽头。如果最后一次提交通过了测试,我仍然可以推送。
  5. 完成调试后,我还在这里和那里添加了几个“> /dev/null”
  6. 还必须修改 sed 指令,使其在 mac os x 上工作(catalina 版本 10.15.7,sed 是内置的)

# An example hook script to verify that each commit that is about to be pushed
# pass the `./run_tests` suite. Called by "git push" after it has checked the
# remote status, but before anything has been pushed.
# If the test suite (and so the script) exits with a non-zero status, nothing
# will be pushed.
#
# In any case, we revert to the pre `$ git push` state.


# Retrieve arguments
remote="$1"
url="$2"

z40=0000000000000000000000000000000000000000 # SHA of a non existing commit


# Save current "git state"
current_branch=$(git rev-parse --abbrev-ref HEAD)

STASH_NAME="pre-push-$(date +%s)"
git stash save -q --keep-index $STASH_NAME

recompile_test()
{
   echo "\n\033[32mRecompiling tests suite...\033[0m"
   make test 2>&1 > /dev/null
   # Retrieve exit code
   is_test_passed=$?

   if [ $is_test_passed -ne 0 ]
   then
       echo "\033[31m[\033[mKO\033[31m] Aborting push: Tests failed for commit $commit\n"\
           "\t\033[31mCOULD NOT COMPILE TEST SUITE\n\033[0m"
       break
   fi
}

# Do wonders
while read local_ref local_sha remote_ref remote_sha
do
       if [ "$local_sha" = $z40 ]
       then
               # Handle delete
               continue # to the next branch
       elif [ "$remote_sha" = $z40 ]
       then
               # New branch, examine all commits
               range="$local_sha"
       else
               # Update to existing branch, examine new commits
               range="$remote_sha..$local_sha"
       fi

       # Retrieve list of commit in "chronological" order
       commits=$(git rev-list --reverse $range)

       # Loop over each commit
       for commit in $commits
       do
           git checkout $commit > /dev/null 2>&1

           recompile_test;

           echo "\n\033[32mRunning tests...\033[0m"
           # Run the tests
           ./tester 2>&1 > ./tests_logs/last_test_output.log

           # Retrieve exit code
           is_test_passed=$?

           # Stop iterating if error
           if [ $is_test_passed -ne 0 ]
           then
               echo "\033[31m[\033[mKO\033[31m]\033[0m Aborting push: \033[31mTests failed for commit $commit\n\033[0m"
               echo "commit brief:"
               echo "\033[38;5;160m`git log -1 --oneline $commit`\033[0m\n"
               echo "\t\033[31mERROR TRACE:\n\033[38;5;132m"
               cat ./tests_logs/last_test_output.log
               echo "\033[0m"
           else
               echo "\033[32m[\033[0mOK\033[32m] \033[0mTrying to push: \033[32mTests Passed for commit $commit\n\033[0m"
               echo "commit brief:"
               echo "`git log -1 --oneline $commit`\n"
           fi
       done
done

#added so it doesnt prevent us to checkout... we rm if the file exists
[ -f tests_logs/output.xml ] && rm tests_logs/output.xml
# Revert to pre-push state
git checkout $current_branch 2>&1 > /dev/null

STASH_NUM=$(git stash list | grep $STASH_NAME | sed -e 's/stash@{\(.*\)}.*/\1/')
if [ -n "$STASH_NUM" ]
then
   git stash pop -q stash@{$STASH_NUM}
fi

if [ $is_test_passed -ne 0 ]
then
   echo "\033[31m[\033[0mKO\033[31m] \033[0mPUSH ABORTED (failed on last commit)\n"
else
   echo "\033[32m[\033[0mOK\033[32m] \033[0mPUSH POSSIBLE (final commit successful)\n"
fi
# Return exit code
exit $is_test_passed```

【讨论】:

    猜你喜欢
    • 2018-07-20
    • 2015-08-19
    • 1970-01-01
    • 2020-02-05
    • 2015-02-18
    • 2016-07-15
    • 2021-12-31
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多