Salta al contenuto principale
Menu
Nessun articolo trovato per la tua ricerca.
Lingua

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:

1
2
3
4
fatal: The current branch feature/my-new-branch has no upstream branch.
To push the current and set the upstream branch, use:

    git push --set-upstream origin feature/my-new-branch

Ogni volta la stessa cerimonia inutile. La soluzione:

ini
1
2
[push]
    autoSetupRemote = true

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.

ini
1
2
[pull]
    rebase = true

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.

ini
1
2
3
4
5
6
7
[user]
    signingkey = IL_TUO_KEY_ID_GPG
    email = danix@danix.xyz
    name = Danilo M.

[commit]
    gpgsign = true

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:

ini
1
2
[help]
    autocorrect = 10

Git autocorregge ed esegue il comando dopo un conto alla rovescia di 1 secondo:

1
2
3
$ git statsu
WARNING: You called a Git command named 'statsu', which does not exist.
Continuing in 0.9 seconds, assuming that you meant 'status'.

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:

ini
1
2
3
[fetch]
    prune = true
    prunetags = true

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:

1
2
3
4
5
<<<<<<< HEAD
il tuo codice
=======
il loro codice
>>>>>>> branch

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:

1
2
3
4
5
6
7
<<<<<<< HEAD
il tuo codice
||||||| base
codice originale
=======
il loro codice
>>>>>>> branch

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.

ini
1
2
[merge]
    conflictstyle = zdiff3

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:

ini
1
2
3
[rerere]
    enabled = true
    autoupdate = true

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

ini
1
2
3
4
5
[branch]
    sort = -committerdate

[column]
    ui = auto

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

ini
1
2
3
[log]
    abbrevCommit = true
    follow = true

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

ini
1
2
3
4
[diff]
    mnemonicPrefix = true
    renames = true
    wordRegex = [^[:space:]]

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

ini
1
2
[alias]
    lg = log --graph --abbrev-commit --decorate --format=format:'%C(bold blue)%h%C(reset) - %C(bold green)(%ar)%C(reset) %C(white)%s%C(reset) %C(dim white)- %an%C(reset)%C(bold yellow)%d%C(reset)' --all

Eseguendo git lg si ottiene un grafo colorato con hash, date, messaggi, autori e puntatori ai branch:

1
2
3
4
5
6
* abc1234 - (3 hours ago) Aggiungo articolo git config - Danilo M. (HEAD -> master)
* def5678 - (1 day ago) Fix CSS tema - Danilo M.
|\
| * ghi9012 - (2 days ago) WIP: nuova funzione - Danilo M. (content/bozza)
|/
* jkl3456 - (5 days ago) Bump submodule tema - Danilo M. (origin/master)

Molto meglio del semplice git log.

sw: Abbreviazione per switch

ini
1
2
[alias]
    sw = 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:

bash
1
git sw -c content/git-config-in-profondita

Niente --set-upstream dopo. push.autoSetupRemote ci pensa lui.

Scrivo, committo. Il commit viene firmato con GPG automaticamente. Nessun passaggio extra.

bash
1
git commit -m "Aggiungo prima bozza articolo git config"

Push quando ho finito:

bash
1
git push

Nessun errore di upstream. Funziona e basta.

Più tardi, il branch main riceve un aggiornamento del submodule del tema. Ribaso il mio lavoro sopra:

bash
1
2
git fetch
git rebase main

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:

bash
1
git lg

L’output ordinato mostra la timeline chiaramente.

Merge su main:

bash
1
2
git switch main
git merge content/git-config-in-profondita

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:

ini
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
[user]
    name = Danilo M.
    email = danix@danix.xyz
    signingkey = IL_TUO_KEY_ID_QUI

[pull]
    rebase = true

[push]
    autoSetupRemote = true

[commit]
    gpgsign = true

[help]
    autocorrect = 10

[fetch]
    prune = true
    prunetags = true

[merge]
    conflictstyle = zdiff3

[rerere]
    enabled = true
    autoupdate = true

[branch]
    sort = -committerdate

[column]
    ui = auto

[log]
    abbrevCommit = true
    follow = true

[diff]
    mnemonicPrefix = true
    renames = true
    wordRegex = [^[:space:]]

[alias]
    lg = log --graph --abbrev-commit --decorate --format=format:'%C(bold blue)%h%C(reset) - %C(bold green)(%ar)%C(reset) %C(white)%s%C(reset) %C(dim white)- %an%C(reset)%C(bold yellow)%d%C(reset)' --all
    sw = switch

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.