summaryrefslogtreecommitdiffstats
path: root/content/it/articles/my-git-workflow/index.md
blob: a482a622552754f7dce73e6dc0ab1f0aef930fa9 (plain)
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
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
+++
title = "Le Impostazioni Git che Uso Davvero"
author = "Danilo M."
type = "tech"
date = "2026-04-28T19:53:46+02:00"
draft = false
excerpt = "Un giro nel mio ~/.gitconfig: le impostazioni su cui mi appoggio ogni giorno e perché ognuna si guadagna il suo posto."
tags = ["git", "workflow", "linux", "gpg", "hugo", "configurazione", "howto"]
categories = ["DIY", "Code"]
+++

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.

<!--more-->

## 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:

```
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
[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
[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
[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](/it/articles/manage-your-passwords-with-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
[help]
    autocorrect = 10
```

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

```
$ 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
[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:

```
<<<<<<< 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:

```
<<<<<<< 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
[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
[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
[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
[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
[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
[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:

```
* 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
[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
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
git commit -m "Aggiungo prima bozza articolo git config"
```

Push quando ho finito:

```bash
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
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
git lg
```

L'output ordinato mostra la timeline chiaramente.

Merge su main:

```bash
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
[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.