2010-03-26 12 views
23

Provenendo da CVS, abbiamo una politica che i messaggi di commit devono essere contrassegnati con un numero di errore (suffisso semplice "... [9999]"). Uno script CVS verifica questo durante il commit e rifiuta il commit se il messaggio non è conforme.Come verifichiamo i messaggi di commit per un push?

Il gancio di git commit-msg esegue questa operazione dal lato dello sviluppatore, ma per noi è utile avere il controllo automatico dei sistemi e ricordarcelo.

Durante una git push, commit-msg non viene eseguito. C'è un altro hook durante il push che potrebbe controllare i messaggi di commit?

Come verifichiamo i messaggi di commit durante una git push?

risposta

23

Utilizzando il gancio aggiornamento

Sai di ganci - Si prega di leggere il documentation su di loro! L'hook che vorresti è l'aggiornamento, che viene eseguito una volta per ref. (Il gancio di pre-ricezione viene eseguito una volta per l'intera spinta) Ci sono tonnellate e tonnellate di domande e risposte su questi ganci già su SO; a seconda di cosa vuoi fare, puoi probabilmente trovare indicazioni su come scrivere il gancio se ne hai bisogno.

a sottolineare che questo è davvero possibile, una citazione dalla documentazione:

Questo gancio può essere usato per prevenire l'aggiornamento forzato su alcuni refs facendo in modo che il nome dell'oggetto è un oggetto commit che è un discendente dell'oggetto commit nominato dal vecchio nome dell'oggetto. Vale a dire, per applicare una politica di "solo avanzamento rapido".

Può anche essere utilizzato per registrare il vecchio stato ... nuovo.

E specificità:

Il gancio eseguita una volta per ogni ref essere aggiornato, e richiede tre parametri:

  • il nome del ref fase di aggiornamento,
  • la nome dell'oggetto vecchio memorizzato nel riferimento
  • e il nuovo nome oggetto da memorizzare nel rif.

Così, per esempio, se si vuole fare in modo che nessuno dei soggetti di commit sono più lungo di 80 caratteri, un'implementazione molto rudimentale sarebbe:

#!/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 

Naturalmente, questo è un esempio di giocattolo; nel caso generale, dovresti utilizzare un output di registro contenente il messaggio di commit completo, dividerlo per commit e chiamare il tuo codice di verifica su ogni singolo messaggio di commit.

Perché si desidera che il gancio di aggiornamento

Questo è stato discusso/chiarito nei commenti; ecco un riassunto.

Il gancio di aggiornamento viene eseguito una volta per ref. Un riferimento è un puntatore a un oggetto; in questo caso, stiamo parlando di rami e tag, e in genere solo di diramazioni (le persone non spingono spesso i tag, dato che di solito si limitano a contrassegnare le versioni).

Ora, se un utente sta spingendo gli aggiornamenti di due rami, maestro e sperimentale:

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

Supponiamo che X è il "cattivo" commit, vale a dire quella che verrebbe a mancare il gancio commit-msg. Chiaramente non vogliamo accettare la spinta da padroneggiare. Quindi, l'hook di aggiornamento lo rifiuta. Ma non c'è niente di sbagliato con i commit su experimental! Il gancio di aggiornamento accetta quello. Pertanto, origin/master rimane invariato, ma di origine/sperimentale viene aggiornato:

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

Il pre-ricezione gancio viene eseguito solo una volta, proprio prima di iniziare a aggiornare arbitri (prima della prima volta che si esegue il gancio di aggiornamento). Se lo avessi usato, avresti dovuto far fallire l'intera spinta, dicendo che poiché c'era un messaggio di commit errato sul master, in qualche modo non ti fidi più che i commit su experimental siano buoni anche se i loro messaggi sono a posto!

+0

Penso che l'hook che l'OP sta cercando sia pre-ricevuto, dal momento che lui/lei vuole rifiutare l'intero push a seconda del messaggio di commit. Tuttavia, AFAIK, né pre-ricezione né aggiornamento ricevono il messaggio di commit come input. Quindi usare commit-msg sarà probabilmente la soluzione migliore. –

+0

@ Can: Sono abbastanza sicuro che l'OP vuole aggiornare, non pre-ricevere. "L'intera spinta" significa la spinta per tutti i rami. Se l'utente tenta di inviare aggiornamenti a tre rami e solo uno contiene messaggi di commit non validi, gli altri due devono ancora essere accettati! – Cascabel

+0

@ Can: E no, il messaggio di commit non fa parte dell'input, ma i nomi dei vecchi e nuovi oggetti (commit) (SHA1s) lo sono. Si noti che il hook di aggiornamento viene eseguito subito prima dell'aggiornamento dei riferimenti (dopo che gli oggetti commit sono stati ricevuti). L'hook può quindi utilizzare git log per ispezionare qualsiasi cosa voglia sui commit tra vecchio e nuovo, inclusi i loro messaggi di commit. – Cascabel

1

È necessario creare una sceneggiatura per la pre-ricezione.

In questo script si riceve la revisione vecchia e nuova. È possibile controllare tutti i commit e restituire false se uno di questi è sbagliato.

5

Si potrebbe fare con il seguente pre-receive hook. Come hanno notato altre risposte, questo è un approccio conservativo, tutto o niente. Si noti che protegge solo il ramo master e non pone vincoli sui messaggi di commit sui rami degli argomenti.

#! /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; 

Richiede tutti i commit di una spinta per avere un certo numero di bug da qualche parte nei rispettivi messaggi di commit, non solo la punta. Per esempio:

$ 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'

dire che risolvere il problema schiacciando i due commit insieme e spingendo il risultato:

$ 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

Questa è una versione pitone di pre-receive, che mi ha portato un po 'per finire, la speranza potrebbe aiutare gli altri. Lo uso principalmente con Trac, ma potrebbe essere facilmente modificato per altri scopi.

Ho anche messo giù le istruzioni per modificare il messaggio di commit storico, che è un po 'più complicato di quanto pensassi.

#!/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() 
1

Non hai parlato di quello che è il tuo bug tracker, ma se è JIRA, poi l'add-on chiamato Commit Policy può fare questo per senza alcuna programmazione.

È possibile impostare una condizione di commit che richiede che il messaggio di commit corrisponda a un'espressione regolare. In caso contrario, il push viene rifiutato e lo sviluppatore deve modificare (correggere) il messaggio di commit, quindi premere nuovamente.

Problemi correlati