Introduzione
Un commit Git fa riferimento a zero, uno o più commit parent. L'intera storia è un grafo aciclico orientato (DAG) di questi link parent. Branch e tag sono semplicemente etichette sui nodi di questo grafo.
Anatomia di un link parent
git cat-file -p HEAD
# tree 9f1a...
# parent b2c3...
# parent d4e5... (solo sui merge commit)
# author Ada ...
# committer Ada ...
Un parent: storia lineare. Due parent: un merge. Zero parent: un commit root. Tre o più: un merge octopus.
Percorrere il grafo
git log --oneline --graph --all
git log --first-parent # segui solo i primi parent
git log --topo-order
git log --date-order
--first-parent su main è ottimo per vedere il "trunk" della storia senza il rumore dei lati di merge.
Query di antenati
git merge-base A B # antenato comune più recente
git merge-base --is-ancestor X Y # exit 0 se X è antenato di Y
git rev-list A..B --count # commit in B non in A
Sintassi di range
A..B: commit raggiungibili da B ma non da A.A...B: differenza simmetrica.^A B: uguale aA..B.B --not A: uguale aA..B.
git log main..feature
git log main...feature --left-right --oneline
Il file commit-graph
Da Git 2.18, Git può scrivere un file commit-graph che pre-calcola dati di antenati per repo enormi:
git commit-graph write --reachable
git config --global core.commitGraph true
git config --global gc.writeCommitGraph true
Questo accelera drasticamente git log --graph, git merge-base e i controlli di raggiungibilità.
Merge octopus
Un singolo merge commit può avere molti parent:
git merge feature1 feature2 feature3
I merge octopus riescono solo senza conflitti. Sono utili per branch di integrazione in alcuni workflow ma raramente necessari.
Visualizzare il grafo
git log --oneline --graph --decorate --all
gitk --all
git log --pretty=format:'%h %p %s'
%p stampa gli SHA dei parent.
Numeri di generazione
Il file commit-graph memorizza un numero di generazione per commit: 1 per commit root, max(generazioni parent) + 1 altrimenti. I numeri di generazione trasformano molte query di antenati in operazioni O(log n). Git moderno (2.27+) scrive date di commit corrette e numeri di generazione v2 quando il commit-graph è abilitato:
git commit-graph write --reachable --changed-paths
git config gc.writeCommitGraph true
git config core.commitGraph true
git log --oneline --graph -n 5
--changed-paths registra inoltre un Bloom filter per commit che elenca i path modificati, accelerando drasticamente le query git log limitate per path.
Errori comuni
Immaginare i branch come stack separati di commit; sono etichette su nodi che possono condividere antenati. Usare HEAD~3 su un merge commit ed essere sorpresi: ~ segue sempre il primo parent. Usa ^2, ^3 per saltare ad altri parent. Dimenticare che il rebase riscrive la topologia del grafo creando nuovi commit con nuovi puntatori parent; i vecchi commit vivono nel reflog. Infine, pensare che il grafo dei commit memorizzi i diff; memorizza solo tree e parent. I diff vengono calcolati su richiesta confrontando i tree.