2015-03-16 31 views
7

Ho un problema nel creare un hook pre-receive su un ramo remoto git, facendo ciò che voglio.Git: hook di pre-ricezione per consentire solo le unioni e non il commit diretto nel master

Qual è il problema?

I commit diretti al ramo master non devono essere consentiti. Dovrebbero essere consentite solo le fusioni nel ramo principale.

Soluzione

La mia soluzione fino ad ora è quello di verificare, se ci sono cambiamenti nella spinta da un utente, in cui è interessato il maestro. Ma il problema è che non posso differenziare se la modifica è un commit diretto o un'unione.

#!/bin/sh 
while read oldrefid newrefid refname 
do 
if [ "$refname" = "refs/heads/master" ]; then 
     echo $(git merge-base $oldrefid $newrefid) 
     echo "---- Direct commit to master branch is not allowed ----" 
     echo "Changes only with a merge from another branch" 
     exit 1 
fi 
done 

Qualcuno ha un'idea, come verificare se il cambiamento è un'unione?

Grazie!

risposta

4

Ciò che si ottiene in un hook di pre-ricezione è il precedente e il nuovo suggerimento del ramo, quindi è necessario controllare l'elenco di commit aggiunti e vedere se uno di essi non è univoco :

nonmerges=$(git rev-list --no-merges --first-parent $oldrefid..$newrefid | wc -l) 
[ "$nonmerges" -eq 0 ] && exit 0 

--first-parent limita la potenza di commit dalla linea principale, vale a dire i commit che ha ottenuto unite a (raggiungibile tramite secondo terzo/... genitore /) vengono saltati.

Eventualmente complicazione: fusioni veloci (difficili da distinguere da una serie di normali commit).

+0

"Unioni veloci (difficili da distinguere da una serie di normali commit)" AFAIK non c'è modo di distinguere una serie di commit da una fusione veloce. È semplicemente un aggiornamento del ramo in cui ci uniamo al commit in questione. –

+0

@Zeeker è possibile indovinare una fusione veloce in avanti controllando se esistono gli stessi commit su un altro ramo. In alcune circostanze questo può essere una prova sufficiente. Tuttavia, è tutt'altro che a prova di errore. –

+0

Vedo che sarebbe abbastanza inverosimile. Almeno a livello locale il reflog potrebbe renderlo più intelligente, ma questo ovviamente esclude qualsiasi telecomando a cui spingiamo. –

6

Ecco la risposta breve: guardare il valore prodotto da:

git rev-list --count --max-parents=1 $oldrefid..$newrefid 

Si desidera che questo sia pari a zero. Continua a leggere per la spiegazione (e avvertenze).


Il tuo ciclo ha il diritto contorno:

  • leggere tutti gli aggiornamenti ref;
  • per coloro che aggiornano il/i succursale (o altri riferimenti) a cui tieni, effettua un controllo.

Il trucco sta nell'esecuzione del controllo. Considera le altre due informazioni che ricevi, vale a dire i vecchi e nuovi ID SHA-1, e che in questi ganci, uno (ma non entrambi) di questi due ID SHA-1 può essere tutto 0 s (che significa che l'arbitro sta creato o cancellato).

Per insistere sul fatto che il cambiamento non è una creazione o una cancellazione, il test dovrebbe garantire che né SHA-1 sia tutto zeri. (Se sei disposto ad assumere che sia necessario solo il controllo dell'eliminazione, puoi semplicemente controllare che il nuovo SHA-1 non sia completamente zero, ma se la creazione potrebbe verificarsi, che è il caso solo se in qualche modo il ramo master viene eliminato dopo tutto, ad esempio, da qualcuno che si connette al server che riceve le push e lo elimina manualmente, sarà comunque necessario assicurarsi che il vecchio SHA-1 non sia completamente zero per il test finale. Chiaramente questo tipo di cancellazione è possibile, la domanda è se si desidera scrivere il codice per gestire il caso.)

In ogni caso, la spinta più tipica aggiorna semplicemente il riferimento. Si noti che tutti i nuovi commit sono già stati scritti nel repository (saranno garbage collection se rifiutate la spinta), così il vostro compito a questo punto è quello di:

  • trovare e verificare eventuali impegna che il riferimento usato per nominare, che non chiamerà più (questi sono i commit che verrebbero rimossi da una push non veloce); e
  • trovare e verificare qualsiasi commit che il riferimento chiamerà ora, che non ha usato per nominare (questi sono i nuovi commit che verrebbero aggiunti da un push, sia che si tratti di avanzamento rapido o meno: ricorda che un nuovo push potrebbe rimuovere uno o più commit aggiungendo uno o più, allo stesso tempo).

per trovare queste due serie di commit, si dovrebbe usare git rev-list, dato che è proprio il suo lavoro: per produrre un elenco di SHA-1 specificati da qualche espressione. Le due espressioni che vuoi qui sono "tutti gli ID di commit reperibili da una revisione, che non sono già in grado di trovare da qualche altro ID". Nei termini git rev-list questi sono git rev-list $r1 ^$r2, o equivalentemente, git rev-list $r2..$r1, per due identificatori di revisione $r1 e $r2. I due revspec sono, ovviamente, solo i vecchi e nuovi ID per la proposta di push.

L'ordine di questi due ID determina quale set di commit è git rev-list: quelli che verrebbero rimossi - questo set è vuoto per un'operazione di avanzamento rapido - e quelli che verrebbero aggiunti.


In questo particolare caso, il vostro obiettivo non è quello di produrre queste liste di se stessi impegna (anche se questo avrebbe funzionato), ma piuttosto, per selezionare qualcosa da queste liste.

È possibile che si desideri impedire l'eliminazione del commit (ad esempio, per applicare l'avanzamento rapido anche se l'utente che esegue lo push ha specificato un flag di forzatura). In questo caso, è sufficiente verificare che l'elenco "da rimuovere" sia vuoto. Puoi farlo assicurandoti che l'elenco sia effettivamente vuoto, o più semplice in uno script di shell: con git rev-list contati per te e controlla che il numero risultante sia zero.

Desiderate sicuramente evitare le aggiunte non unite, ma consentire le aggiunte che lo sono. In questo caso, l'aggiunta di --max-parents=1 (che può anche essere digitata --no-merges) dice a git rev-list di sopprimere i commit che hanno due o più genitori, cioè unioni. L'aggiunta di --count ti fornisce un conteggio dei commit che soddisfano questo vincolo "non unire perché zero o un genitore". Se questo conteggio è zero, allora qualsiasi commit aggiunto deve per definizione essere unito.

Quindi:

n=$(git rev-list --count --max-parents=1 $oldrefid..$newrefid) 
if [ $n -gt 0 ]; then 
    echo "disallowed: push adds $n non-merge commit(s)" 1>&2 
    exit 1 
fi 

per esempio, sarà sufficiente far rispettare questo vincolo specifico.


Quasi, ma non del tutto equivalente, è possibile scrivere git rev-list $r1 --not $r2: la differenza è che l'effetto di --not indugia, in modo che se si dovesse aggiungere un altro ID revisione $r3 il --not si applicherà a r3. Cioè, git rev-list A ^B C significa yes-A, not-B, yes-C ma A --not B C significa yes-A, not-B, not-C. Si noti che nella sintassi rev-list, B..A significa A ^B, ad esempio, esattamente B è invertito.

+0

Grazie! Anche lavorato! +1 –