X
    Categories: blog

Git commit: prima liberi poi ordinati

Git è uno strumente potente.

Per quanta attenzione venga posta all’uso per evitare danni al proprio codice sorgente (e ai propri collaboratori), è facile che non venga usato nel migliore dei modi. Abbiamo già discusso di quanto sia utile scrivere messaggi di commit in maniera dettagliata e puntuale, ma quando è corretto fare le commit? Si includono tutte le modifiche o si suddividono in più commit? Come si gestiscono le modifiche su più fronti e quelle che richiedono più giorni per essere completate?

In questo articolo andremo a risolvere questo annoso problema, indicando le best practice e i comandi per applicarle. Verrà definita un’importante differenza: l’uso di Git durante lo sviluppo e alla pubblicazione del codice sul server del repository.

Atomic Commit

L’approccio migliore per rendere le proprie commit facilmente interpretabili è quello di crearle il più possibile piccole e “atomiche”. Una modifica può essere considerata “atomica” se riguarda esclusivamente un singolo bug o incarico.

Facciamo qualche esempio.

Ti è stato richiesto di modificare una funzionalità esistente e, in corso d’opera, identifichi un bug fino a quel momento sconosciuto. Una volta corretto il bug e completate le modifiche, una scelta comune è creare un’unica commit con tutti i cambiamenti apportati. Tuttavia, cosa succederebbe se la correzione portasse in realtà maggior complessità senza risolvere il problema?

La soluzione più pulita sarebbe il rollback ma così si rischierebbe di perdere lo sviluppo della nuova funzionalità. Altrimenti si potrebbe fare una seconda commit che corregge la correzione, che comunque non sarebbe un buon approccio…

Se invece si suddividesse il codice in due commit separate, una per la feature e una per il bugfix, il rollback sarebbe semplice da applicare. Si potrebbe fare la giusta correzione senza toccare le altre modifiche.

Questa scelta potrebbe rendere la tua storia delle commit (o log) più verbosa, ma al termine renderà il tuo intero progetto più flessibile per i bugfix, migrazioni future e rollback.

Non essere fiscale durante lo sviluppo

Diversamente, durante lo sviluppo è normalissimo dividere il codice in più commit per mantenere un ordine mentale e per registrare il raggiungimento di “milestone” personali. Soprattutto se bisogna implementare modifiche complesse o richiedenti diverse giornate di lavoro. E non è un errore. “Losing is fun” vale solo per i videogiochi, meglio poter continuare dall’ultimo checkpoint.

Un altro pensiero comune è fare le commit “al termine del giorno di lavoro”. Puoi fare anche questo, ma fermati sempre a considerare quali modifiche hai fatto. Appartengono tutte allo stesso contesto oppure fanno parte di due contesti differenti? Nel secondo caso chiudi la giornata con due commit invece di una!

Le ultime due cose da tenere sempre a mente.

  1. Prima di iniziare un incarico valuta la complessità e la dimensione delle modifiche da fare, molte volte risulterà conveniente spostarsi in un branch temporaneo di lavoro.
  2. Il push deve essere fatto solo quando Git in locale è stabile: le commit sono scritte al meglio e non contengono funzionalità sviluppate solo in parte. Il log può essere modificato senza rischi solo finché non si trova sul server del repository.

Lo so, all’inizio non è facile ricordarsene ma prevenire è sempre meglio che curare.

Mantieni sempre ordinato il log

Ora sappiamo che durante lo sviluppo è possibile fare, con un po’ di criterio, “quante commit si vuole” ma come risultato finale desideriamo un log con sole commit atomiche. Per questo dobbiamo trasformare l’albero di versionamento creato durante lo sviluppo per modificare e accorpare le varie commit.

Mostrerò ora una serie di modi per modificare e unificare le proprie commit una volta concluso lo sviluppo.

Merge con squash

Utilizzabile se tutto il lavoro è stato fatto in un branch locale composto da diverse commit che insieme completano un singolo task. Queste devono essere accorpate in un’unica commit ed aggiunte nel branch principale.

$ git checkout 
$ git merge --squash
$ git commit

L’opzione squash raggruppa tutte le commit in una sola.

Al termine del merge è possibile cancellare il branch di lavoro senza alcun problema.

Reset (–soft)

Nel caso in cui le commit siano state fatte sul branch principale di sviluppo si hanno due possibilità.

La prima consiste nello spostare le commit su un nuovo branch di lavoro locale per poi usare il merge come al punto precedente. Si può compiere seguendo queste istruzioni (oppure utilizzando gitk) ma normalmente non è l’approccio migliore.

Per fortuna è possibile giungere ad un risultato simile mantenendo tutte le commit sul branch principale.

$ git reset --soft 
$ git commit

In questo caso utilizziamo il comando reset per cancellare le ultime commit mantenendo le modifiche in stage. Poi creiamo una nuova commit atomica. Al posto di <commit-target> si può usare il codice SHA1 della commit a cui fare ritorno oppure, più semplicemente, si può usare HEAD~x per richiedere la cancellazione delle ultime x commit.

Per scrivere con più facilità il nuovo messaggio è possibile utilizzare un comando simile a git commit –edit -m “$(git log –format=%B –reverse HEAD..HEAD@{1})”, così che il messaggio venga precompilato con tutti i testi delle commit precedenti.

In questo caso consiglio di fare molta attenzione: con il comando reset si rischia sempre di cancellare una commit di troppo. L’opzione –soft garantisce di mantenere l’ultima versione del codice.

Rebase, il coltellino svizzero

In qualsiasi situazione è sempre possibile utilizzare il rebase interattivo. Un comando molto più flessibile dei precedenti, scritto appositamente per modificare il log. Consiglio di prenderci confidenza qualsiasi siano le necessità, risulta utile in numerosi casi.

$ git rebase -i <commit-target>

Viene aperto un editor testuale (attenzione, su linux l’editor predefinito è quasi sempre vim) in cui possiamo ridefinire le ultime commit, scelte dal <commit-target> specificato. Per le nostre necessità è sufficiente indicare squash su tutte le commit da unire tranne la prima. fixup ha lo stesso effetto di squash ma scarta il testo della commit. Si possono anche riordinare le commit, però considera che potrebbero esserci dei conflitti da correggere manualmente.

Come riprodurre gli esempi

Non hai bisogno di usare un vero repository. Posizionati in una nuova cartella ed esegui i seguenti comandi:

$ git init
$ git commit -m "Prima commit, con modifiche"
$ git commit -m "Seconda commit, senza modifiche" --allow-empty
$ git log

Un’unica nota: consiglio di fare almeno una commit con modifiche. Se sono tutte vuote non c’è niente con cui lavorare con lo squash.

Per visualizzare il log in una versione più compatta (usata in questo articolo) puoi usare git log –pretty=format:’%h %<(20)%an %s’ -10.

Se ti trovi su Windows, bè… mi dispiace per te. Il carattere ~ (tilde) puoi scriverlo solo con Alt+96. Su Linux invece si può scrivere con AltGr+ì.

Per creare le gif ho utilizzato terminalizer, strumento open source per registrare una sessione in console e condividerla come gif.

Fonti

Gioele Masini: Lavoro in Maggioli dal 2014. Ho contribuito a progetti come Revisal, SCACCo e Consolidato enti principalmente lato client, in cui ho gestito l'infrastruttura e gli aspetti che preferisco: interfaccia ed esperienza utente. Tra lavoro e tempo libero proseguo con lo studio, alternando magistrale in Ingegneria e Scienze Informatiche e libri sul design, e gioco a baseball.
Related Post