web-dev-qa-db-ja.com

プッシュのコミットメッセージを確認するにはどうすればよいですか?

CVSから来て、コミットメッセージはバグ番号(単純なサフィックス "... [9999]")でタグ付けされるべきであるというポリシーがあります。 CVSスクリプトはコミット中にこれをチェックし、メッセージが適合しない場合はコミットを拒否します。

Git hook commit-msgは開発者側でこれを行いますが、自動化されたシステムにこれをチェックして通知させると便利です。

Git Pushの間、commit-msgは実行されません。プッシュ中にコミットメッセージをチェックできる別のフックはありますか?

Gitプッシュ中にコミットメッセージを確認するにはどうすればよいですか?

25
Dale Forester

更新フックの使用

あなたはフックについて知っています-どうぞ、それらについて ドキュメント を読んでください!おそらく必要なフックはupdateで、これは参照ごとに1回実行されます。 (プリレシーブフックはプッシュ全体で1回実行されます)これらのフックに関する質問と回答はすでにSOにあります。やりたいことに応じて、必要に応じてフックの書き方についてのガイダンスを見つけることができます。

これが本当に可能であることを強調するために、ドキュメントからの引用:

このフックを使用して、オブジェクト名が古いオブジェクト名で指定されたコミットオブジェクトの子孫であるコミットオブジェクトであることを確認することにより、特定の参照の強制更新を防ぐことができます。つまり、「早送りのみ」のポリシーを適用することです。

また、古い..新しいステータスをログに記録するために使用することもできます。

そして詳細:

フックは、更新される参照ごとに1回実行され、次の3つのパラメーターを取ります。

  • 更新される参照の名前、
  • 参照に保存されている古いオブジェクト名、
  • 参照に格納される新しいオブジェクト名。

したがって、たとえば、コミットサブジェクトが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

もちろん、それはおもちゃの例です。一般的なケースでは、完全なコミットメッセージを含むログ出力を使用し、コミットごとに分割して、個々のコミットメッセージごとに確認コードを呼び出します。

更新フックが必要な理由

これはコメントで議論/明確化されています。これが要約です。

更新フックは、参照ごとに1回実行されます。 refはオブジェクトへのポインタです。この場合、ブランチとタグについて話しますが、通常はブランチだけです(通常、バージョンをマークするためだけのものであるため、タグを頻繁にプッシュすることはありません)。

ここで、ユーザーがマスターとエクスペリメンタルの2つのブランチに更新をプッシュしている場合:

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

Xが「悪い」コミット、つまりcommit-msgフックに失敗するコミットであると仮定します。明らかに、Push tomasterを受け入れたくありません。したがって、更新フックはそれを拒否します。しかし、実験的なコミットには何の問題もありません!更新フックはそれを受け入れます。したがって、Origin/masterは変更されませんが、Origin/experimentalは更新されます。

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

事前受信フックは、参照の更新を開始する直前(更新フックが最初に実行される前)に1回だけ実行されます。これを使用した場合、プッシュ全体を失敗させる必要があります。つまり、マスターに不正なコミットメッセージがあったため、メッセージが正常であっても、実験的なコミットが適切であるとはどういうわけか信頼できなくなります。

24
Cascabel

あなたは次のようにそれを行うことができます pre-receiveフック 。他の回答が指摘しているように、これは保守的なオールオアナッシングアプローチです。マスターブランチのみを保護し、トピックブランチのコミットメッセージに制約を課さないことに注意してください。

#! /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秒[123] 
 aeb73d00456fc73f5e33129fb0dcb16718536489バグ番号なし
 
オブジェクトのカウント:6、完了。
最大2つのスレッドを使用したデルタ圧縮。
オブジェクトの圧縮:100%(4/4)、完了。
オブジェクトの書き込み:100 %(5/5)、489バイト、完了。
合計5(デルタ0)、再利用0(デルタ0)
オブジェクトの解凍:100%(5/5)、完了。
 aeb73d00456fc73f5e33129fb0dcb16718536489にバグ番号がありません:
 
バグ番号がありません
 
 file:///tmp/bare.git 
に! [リモート拒否]マスター->マスター(受信前フックが拒否されました)
エラー:一部の参照を「file:///tmp/bare.git」にプッシュできませんでした

2つのコミットをまとめて押しつぶし、結果をプッシュすることで問題を修正するとします。

$ git rebase -i Origin/master 
 [...] 
 
 $ git log --pretty = oneline Origin/master..HEAD 
 74980036dbac95c97f5c6bfd64a1faa4c01dd754秒[ 123] 
 
 $ git Push Origin master 
オブジェクトのカウント:4、完了。
最大2つのスレッドを使用したデルタ圧縮。
オブジェクトの圧縮: 100%(2/2)、完了。
オブジェクトの書き込み:100%(3/3)、279バイト、完了。
合計3(デルタ0)、再利用0(デルタ0)
オブジェクトの解凍:100%(3/3)、完了。
 file:///tmp/bare.git 
 8388e88..7498003マスター->マスター
6
Greg Bacon

これはpythonバージョンのpre-receive終了するのに少し時間がかかりましたが、他の人の役に立つことを願っています。私は主に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
Walty Yeung

バグトラッカーについては触れていませんが、[〜#〜] jira [〜#〜]の場合、アドオン名前付き コミットポリシー これはプログラミングなしで実行できます。

コミットメッセージが正規表現と一致することを要求するコミット条件を設定できます。そうでない場合、プッシュは拒否され、開発者はコミットメッセージを修正(修正)してから、もう一度プッシュする必要があります。

1
Ferenc Kiss

事前受信時にスクリプトを作成する必要があります。

このスクリプトでは、新旧のリビジョンを受け取ります。すべてのコミットをチェックして、これの1つが悪い場合はfalseを返すことができます。

1
shingara