我们如何验证推送的提交消息?

时间:2010-03-26 14:49:10

标签: git cvs commit push

来自CVS,我们有一个策略,提交消息应该用bug号标记(简单后缀“...... [9999]”)。 CVS脚本在提交期间对此进行检查,如果消息不符合则拒绝提交。

git hook commit-msg在开发人员端执行此操作,但我们发现自动系统检查并提醒我们这一点很有帮助。

在git push期间,不运行commit-msg。在推送期间是否有另一个钩子可以检查提交消息?

我们如何在git push期间验证提交消息?

5 个答案:

答案 0 :(得分:24)

使用更新挂钩

你知道钩子 - 请阅读关于它们的documentation!你可能想要的钩子是更新,每个ref运行一次。 (预接收钩子在整个推动过程中运行一次)SO上已经有大量关于这些钩子的问题和答案;根据你想做的事情,如果你需要,你可以找到关于如何编写钩子的指导。

要强调这确实是可能的,来自文档的引用:

  

此挂钩可用于防止对某些引用进行强制更新,方法是确保对象名称是一个提交对象,该对象是旧对象名称所指定的提交对象的后代。也就是说,执行“仅限快进”政策。

     

它也可以用于记录old..new状态。

具体细节:

  

钩子为每个ref更新执行一次,并采用三个参数:

     
      
  • 要更新的ref的名称,
  •   
  • 存储在ref中的旧对象名称
  •   
  • 以及要存储在ref。
  • 中的新对象名   

因此,举例来说,如果你想确保没有任何提交主题超过80个字符,那么一个非常基本的实现将是:

#!/bin/bash
long_subject=$(git log --pretty=%s $2..$3 | egrep -m 1 '.{81}')
if [ -n "$long_subject" ]; then
    echo "error: commit subject over 80 characters:"
    echo "    $long_subject"
    exit 1
fi

当然,这是一个玩具的例子;在一般情况下,您将使用包含完整提交消息的日志输出,按提交将其拆分,并在每个提交消息上调用验证代码。

为什么要更新挂钩

评论中已对此进行了讨论/澄清;这是一个总结。

更新挂钩每个ref运行一次。 ref是指向对象的指针;在这种情况下,我们讨论的是分支和标签,通常只是分支(人们不经常推送标签,因为它们通常仅用于标记版本)。

现在,如果用户正在将更新推送到两个分支,即master和experimental:

o - o - o (origin/master) - o - X - o - o (master)
 \
  o - o (origin/experimental) - o - o (experimental)

假设X是“坏”提交,即将使commit-msg挂钩失败的提交。显然我们不想接受推动掌握。所以,更新钩子拒绝了。但实验的提交没有错!更新钩子接受那个。因此,origin / master保持不变,但origin / experimental会更新:

o - o - o (origin/master) - o - X - o - o (master)
 \
  o - o - o - o (origin/experimental, experimental)

预接收挂钩仅在开始更新refs之前运行一次(在第一次运行更新挂钩之前)。如果你使用它,你必须会导致整个推到失败,因此说,因为有人掌握不好的提交信息,你会莫名其妙地不再信任该实验的提交是好的,即使他们的消息都是精品!< / p>

答案 1 :(得分:5)

您可以使用以下pre-receive hook执行此操作。正如其他答案所指出的那样,这是一种保守的,全有或全无的方法。请注意,它仅保护主分支,并且不对主题分支上的提交消息施加约束。

#! /usr/bin/perl

my $errors = 0;
while (<>) {
  chomp;
  next unless my($old,$new) =
    m[ ^ ([0-9a-f]+) \s+   # old SHA-1
         ([0-9a-f]+) \s+   # new SHA-1
         refs/heads/master # ref
       \s* $ ]x;

  chomp(my @commits = `git rev-list $old..$new`);
  if ($?) {
    warn "git rev-list $old..$new failed\n";
    ++$errors, next;
  }

  foreach my $sha1 (@commits) {
    my $msg = `git cat-file commit $sha1`;
    if ($?) {
      warn "git cat-file commit $sha1 failed";
      ++$errors, next;
    }

    $msg =~ s/\A.+? ^$ \s+//smx;
    unless ($msg =~ /\[\d+\]/) {
      warn "No bug number in $sha1:\n\n" . $msg . "\n";
      ++$errors, next;
    }
  }
}

exit $errors == 0 ? 0 : 1;

它要求推送中的所有提交在其各自的提交消息中的某处具有错误号,而不仅仅是提示。例如:

$ git log --pretty=oneline origin/master..HEAD
354d783efd7b99ad8666db45d33e30930e4c8bb7 second [123]
aeb73d00456fc73f5e33129fb0dcb16718536489 no bug number

$ git push origin master
Counting objects: 6, done.
Delta compression using up to 2 threads.
Compressing objects: 100% (4/4), done.
Writing objects: 100% (5/5), 489 bytes, done.
Total 5 (delta 0), reused 0 (delta 0)
Unpacking objects: 100% (5/5), done.
No bug number in aeb73d00456fc73f5e33129fb0dcb16718536489:

no bug number

To file:///tmp/bare.git
 ! [remote rejected] master -> master (pre-receive hook declined)
error: failed to push some refs to 'file:///tmp/bare.git'

假设我们通过将两个提交压缩在一起并推送结果来解决问题:

$ git rebase -i origin/master
[...]

$ git log --pretty=oneline origin/master..HEAD
74980036dbac95c97f5c6bfd64a1faa4c01dd754 second [123]

$ git push origin master
Counting objects: 4, done.
Delta compression using up to 2 threads.
Compressing objects: 100% (2/2), done.
Writing objects: 100% (3/3), 279 bytes, done.
Total 3 (delta 0), reused 0 (delta 0)
Unpacking objects: 100% (3/3), done.
To file:///tmp/bare.git
   8388e88..7498003  master -> master

答案 2 :(得分:2)

这是pre-receive的python版本,我花了一段时间才完成,希望它可以帮助别人。我主要将它与Trac一起使用,但它可以很容易地修改用于其他目的。

我还说明了修改历史提交消息的指令,这比我想象的要复杂一点。

#!/usr/bin/env python
import subprocess

import sys 
import re

def main():
    input  = sys.stdin.read()
    oldrev, newrev, refname = input.split(" ")
    separator = "----****----"


    proc = subprocess.Popen(["git", "log", "--format=%H%n%ci%n%s%b%n" + separator, oldrev + ".." +  newrev], stdout=subprocess.PIPE)
    message = proc.stdout.read()
    commit_list = message.strip().split(separator)[:-1] #discard the last line

    is_valid = True

    print "Parsing message:"
    print message

    for commit in commit_list:
        line_list = commit.strip().split("\n")
        hash = line_list[0]
        date = line_list[1]
        content = " ".join(line_list[2:])
        if not re.findall("refs *#[0-9]+", content): #check for keyword
            is_valid = False

    if not is_valid:
        print "Please hook a trac ticket when commiting the source code!!!" 
        print "Use this command to change commit message (one commit at a time): "
        print "1. run: git rebase --interactive " + oldrev + "^" 
        print "2. In the default editor, modify 'pick' to 'edit' in the line whose commit you want to modify"
        print "3. run: git commit --amend"
        print "4. modify the commit message"
        print "5. run: git rebase --continue"
        print "6. remember to add the ticket number next time!"
        print "reference: http://stackoverflow.com/questions/1186535/how-to-modify-a-specified-commit"

        sys.exit(1)

main()

答案 3 :(得分:1)

您需要在预先接收时制作一个脚本。

在此脚本中,您将收到旧版和新版。你可以检查所有提交,如果其中一个是坏的,则返回false。

答案 4 :(得分:1)

您没有提到您的错误跟踪器是什么,但如果它是 JIRA ,那么名为Commit Policy的加载项可以在没有任何编程的情况下执行此操作。

您可以设置一个提交条件,该条件要求提交消息与正则表达式匹配。如果没有,则推送被拒绝,开发人员必须修改(修复)提交消息,然后再次推送。