Le Impostazioni Git che Uso Davvero
Qualche anno fa ho impostato un sistema a due repository per questo sito: uno per i contenuti, l’altro per il tema (come submodule). È un’architettura pulita in teoria, ma in pratica gestire due repository significa destreggiarsi continuamente tra aggiornamenti di branch, conflitti sul puntatore del submodule e push su due remote in sequenza. Ho cominciato a portarmi in giro un file .gitconfig tra macchine diverse, modificandolo un po’ alla volta man mano che scoprivo impostazioni che eliminano l’attrito.
Questo non è una guida a ogni opzione di git, ci vorrebbe un libro. È invece un tour personale delle impostazioni che ho davvero integrato nel mio flusso di lavoro, perché contano e cosa è cambiato da quando sono attive.
La Colla del Flusso: push.autoSetupRemote e pull.rebase
Queste due stanno insieme perché riguardano i confini dei branch: come li mandi su e come li riporti giù.
push.autoSetupRemote
La vecchia frustrazione: creare un branch locale, scrivere codice, eseguire git push e ricevere:
|
|
Ogni volta la stessa cerimonia inutile. La soluzione:
|
|
Ora git push su un nuovo branch funziona e basta. Git deduce il branch di tracciamento automaticamente. La prima volta che ho usato questa impostazione con i branch di contenuto Hugo, creando content/nuovo-articolo il lunedì e facendo subito push, ho capito di aver eliminato forse cinquanta digitazioni l’anno di --set-upstream.
pull.rebase
La storia lineare conta di più su un sito a singolo autore. Senza pull.rebase = true, fare pull dei cambiamenti crea commit di merge come “Merge branch ‘main’ of origin/main” anche quando stai solo sincronizzando. Questi commit non rappresentano lavoro: sono rumore. Intasano git log, rendono il bisect più lento e aggiungono confusione al grafo.
|
|
Con questa impostazione attiva, fare pull ribasa il lavoro locale sopra la punta remota. Quando gestisci un sito Hugo con un submodule del tema, questo è particolarmente utile: gli aggiornamenti del puntatore del submodule restano ordinati nel log invece di nascondersi dentro commit di merge.
Identità e Fiducia: commit.gpgsign
Firmare i commit con GPG non riguarda la conformità o il superamento di audit. Riguarda la provenienza. Ogni commit sui miei repository porta la mia firma crittografica, la prova che viene da me e non è stata manomessa.
|
|
La prima volta che si configura, è necessario che gpg-agent sia in esecuzione e che la chiave sia importata localmente. Una volta fatto, ogni commit viene firmato automaticamente. Se vuoi approfondire le chiavi GPG, ho scritto di gestire le password con password-store, che copre la configurazione delle chiavi.
Ridurre l’Attrito: help.autocorrect, fetch.prune e fetch.prunetags
Tre piccole impostazioni che risparmiano digitazioni quotidiane e carichi mentali.
help.autocorrect
I refusi capitano. Scrivo spesso git statsu invece di git status, o git comit invece di git commit. Il vecchio comportamento è un messaggio di errore con una correzione suggerita. Con:
|
|
Git autocorregge ed esegue il comando dopo un conto alla rovescia di 1 secondo:
|
|
Il 10 qui significa 1 secondo (git misura in decimi). È abbastanza lungo da notarlo e interrompere se la correzione è sbagliata, ma abbastanza breve da non spezzare il ritmo.
fetch.prune e fetch.prunetags
Quando si eliminano branch sul remote, i riferimenti obsoleti si accumulano nel repository locale. Queste impostazioni li puliscono:
|
|
Ora git fetch rimuove silenziosamente i branch locali che non esistono più upstream, e fa lo stesso per i tag. Per un repository del tema Hugo, i vecchi tag di release vengono rimossi dal server, e prunetags fa sì che non restino nel tuo output di git tag.
Quando Arrivano i Conflitti: merge.conflictstyle e rerere
Due impostazioni che rendono i conflitti di merge meno dolorosi.
merge.conflictstyle = zdiff3
I marcatori di conflitto normalmente mostrano due lati:
|
|
Vedi cosa hai scritto tu e cosa hanno scritto loro, ma non da dove entrambi sono partiti. Lo stile di conflitto zdiff3 mostra l’antenato comune:
|
|
La sezione ||||||| base è ciò che entrambi i lati hanno modificato. Ora vedi la storia completa: tu hai cambiato questa riga in X, loro in Y, e l’originale era Z. L’intenzione diventa ovvia.
|
|
rerere.enabled e rerere.autoupdate
rerere sta per “reuse recorded resolution” (riutilizza la risoluzione registrata). Quando risolvi un conflitto manualmente, git ricorda la risoluzione. La prossima volta che si presenta lo stesso conflitto:
|
|
Git lo risolve automaticamente. Questo è prezioso quando si ribasa un branch di contenuto su un main che include un aggiornamento del puntatore del submodule. La prima volta che c’è un conflitto nel riferimento del submodule, lo risolvi tu. Su un secondo branch con lo stesso conflitto, rerere lo gestisce silenziosamente, e autoupdate mette in staging la risoluzione così non devi fare git add manualmente.
Orientarsi: Visualizzazione Branch, Log e Diff
Quattro impostazioni che rendono più veloce leggere la storia del repository.
branch.sort e column.ui
|
|
Di default, git branch elenca i branch in ordine alfabetico. Inutile dopo dieci branch. L’ordinamento per -committerdate mostra i più recenti per primi, il branch su cui probabilmente stai per passare è in cima.
column.ui = auto mostra i branch su più colonne quando il terminale è abbastanza largo, riducendo lo scorrimento di una lista lunga.
log.abbrevCommit e log.follow
|
|
abbrevCommit mostra SHA corti (7 caratteri) invece degli hash completi da 40 caratteri. Log più puliti, più veloci da leggere.
follow traccia i file attraverso i rename. Quando esegui git log -- path/to/article.md e quel file si chiamava prima old-article.md, il log non si interrompe, segue il file attraverso il rename e mostra la storia completa.
diff.mnemonicPrefix, diff.renames e diff.wordRegex
|
|
mnemonicPrefix cambia le intestazioni dei diff da a/ e b/ a etichette più descrittive come i/ (index), w/ (working tree), o/ (object), c/ (commit). Più informazioni a colpo d’occhio.
renames = true rileva i rename dei file e li mostra come file.old => file.new invece di una cancellazione e una creazione. Diff più puliti.
wordRegex definisce cosa conta come “parola” nei diff a livello di parola (git diff --word-diff). Il pattern [^[:space:]] tratta qualsiasi carattere non-spazio come una parola, il che significa che la punteggiatura ottiene i propri confini, utile per file ricchi di testo come gli articoli Markdown.
Le Abbreviazioni che si Guadagnano il Posto: Alias
Tengo gli alias al minimo di proposito. Gli alias che nascondono cosa fa git sono rumore; questi due valgono il risparmio di battiture.
lg: Log formattato con grafo
|
|
Eseguendo git lg si ottiene un grafo colorato con hash, date, messaggi, autori e puntatori ai branch:
|
|
Molto meglio del semplice git log.
sw: Abbreviazione per switch
|
|
git sw main è più veloce da digitare di git switch main, e git sw -c feature/nuovo crea e passa in un solo comando.
Una Giornata Tipo
Ecco come queste impostazioni lavorano insieme nella pratica.
Mattina: sincronizzo con il repository. git fetch parte silenziosamente, fetch.prune e prunetags puliscono branch cancellati e tag vecchi senza che ci pensi.
Inizio l’articolo della settimana per il sito Hugo. Creo un nuovo branch di contenuto:
|
|
Niente --set-upstream dopo. push.autoSetupRemote ci pensa lui.
Scrivo, committo. Il commit viene firmato con GPG automaticamente. Nessun passaggio extra.
|
|
Push quando ho finito:
|
|
Nessun errore di upstream. Funziona e basta.
Più tardi, il branch main riceve un aggiornamento del submodule del tema. Ribaso il mio lavoro sopra:
|
|
C’è un conflitto nel puntatore del submodule, il repository del tema è andato avanti. merge.conflictstyle = zdiff3 mi mostra esattamente cosa è cambiato. Lo risolvo una volta.
Più tardi, un secondo branch ha lo stesso conflitto del submodule. rerere ricorda la mia risoluzione e la applica automaticamente con rerere.autoupdate. Nessuna ri-risoluzione.
Prima di unire su main, controllo il grafo:
|
|
L’output ordinato mostra la timeline chiaramente.
Merge su main:
|
|
Se c’è un conflitto qui (improbabile dato che abbiamo ribassato), zdiff3 mostra il contesto dell’antenato.
Fatto. Tutte le impostazioni hanno lavorato insieme in modo trasparente.
La Configurazione Completa
Ecco il blocco completo così come sta nel mio ~/.gitconfig:
|
|
Prendi quello che ti serve. Lascia quello che non si adatta al tuo flusso di lavoro.
Una Configurazione che Evolve
Questa non è la configurazione git “corretta”, è quella che si adatta a come lavoro io. La sto raffinando da anni e continuerà a evolversi man mano che trovo nuovi punti di attrito e scopro nuove impostazioni. L’obiettivo non è seguire la mia configurazione alla lettera, ma pensare a cosa ti rallenta e cercare l’impostazione git che lo risolve.
Se usi qualcuna di queste impostazioni, o se ne hai trovate altre che hanno cambiato il tuo flusso di lavoro, fammelo sapere nei commenti. Sono sempre curioso di sapere come gli altri configurano i loro strumenti.