Vam tancar el mòdul 2 amb una promesa: et dèiem que ja sabies què era una branca, tot i que no ho sabessis del tot. «Un fitxer amb un hash a dins», dèiem. Aquesta lliçó complirà aquella promesa fins a l'últim byte.

Les branques són, sense exagerar, la raó per la qual Git va guanyar. Altres sistemes de control de versions ja tenien branques abans que Git, però eren cares, lentes i feien por. En Git són tan barates que crear-ne una és literalment escriure 41 bytes al disc. Aquest canvi de cost no és un detall tècnic: és el que fa possible que un equip com el de l'Ana, en Bruno i la Carla obri una branca per a cada idea, cada correcció i cada experiment sense pensar-s'ho dues vegades.

Però per fer-les servir amb confiança cal entendre què són realment, no la metàfora de l'arbre que surt a tots els tutorials. En aquesta lliçó obrirem el capó: veurem el fitxer, veurem el hash, veurem què és HEAD i per què apunta a una branca i no a un commit, i entendrem què significa exactament que dues branques «divergeixin». Encara no crearem cap branca —això és la lliçó següent—: aquí toca comprendre la maquinària.

Contingut

  1. El problema que resolen les branques
  2. Què és realment una branca: 41 bytes
  3. HEAD: on ets ara mateix
  4. Per què HEAD apunta a una branca i no a un commit
  5. Com avança el punter en confirmar
  6. Per què les branques de Git són barates (i les d'SVN no ho eren)
  7. El graf de commits: l'estructura real
  8. Divergència i ancestre comú
  9. Les branques que ja tens sense saber-ho

  1. El problema que resolen les branques

Recordem el carreró on vam deixar l'equip. L'Ana i en Bruno treballaven tots dos sobre main, cadascun al seu portàtil. L'Ana desenvolupava el comptador de tasques; en Bruno, el filtre de pendents. Cap dels dos podia veure la feina de l'altre i, quan la volguessin ajuntar, es trobarien amb dues versions d'app.js que havien evolucionat per separat.

Sense branques, un desenvolupador té exactament tres opcions dolentes:

Opció Què implica Per què és dolenta
Confirmar-ho tot a l'única línia Cada commit va directe al projecte principal El projecte queda trencat a mitges mentre la funcionalitat no estigui acabada
No confirmar fins a acabar Dies de feina sense cap punt de desament Es perd tot el valor de Git: no hi ha historial, no hi ha marxa enrere
Copiar la carpeta sencera gestor-tasques-v2, gestor-tasques-prova És el versionatge manual que vèiem a la lliçó 01-01, amb tots els seus problemes

Una branca resol les tres alhora: permet confirmar amb freqüència en una línia de treball aïllada que no molesta ningú, i que després es pot integrar al projecte principal quan estigui llesta.

La pregunta és: com aconsegueix Git que això sigui barat? La resposta és al model de dades que ja has estudiat.

  1. Què és realment una branca: 41 bytes

Anem al repositori de l'Ana i mirem dins de .git/. Recorda del model de dades que allà dins hi ha tota la base de dades del repositori.

cd ~/projectes/gestor-tasques
ls .git/refs/heads/
main

Un únic fitxer, anomenat main. Vegem-ne el contingut:

cat .git/refs/heads/main
c5d9b1e7a3f2d8b4e6c1a9f5d3b7e2c8a4f6d1b9

Això és tot. Un hash SHA-1 de 40 caràcters i un salt de línia final. Comprovem-ho amb precisió de comptable:

wc -c .git/refs/heads/main
41 .git/refs/heads/main

Quaranta-un bytes. Quaranta caràcters hexadecimals més el \n del final. Això és una branca en Git: un fitxer de text pla el contingut del qual és el hash d'un commit.

No hi ha metadades, no hi ha llista de commits, no hi ha còpia dels fitxers, no hi ha res més. La branca main no «conté» les quatre confirmacions del projecte: només apunta a l'última, i la resta s'assoleix seguint els enllaços de pare que cada commit porta a dins.

La manera neta de consultar-ho

Llegir fitxers de .git/ a mà està molt bé per entendre-ho, però per al dia a dia Git té una ordre que fa el mateix de manera fiable (i que a més funciona encara que la referència estigui «empaquetada», cosa que veurem d'aquí a un moment):

git rev-parse main
c5d9b1e7a3f2d8b4e6c1a9f5d3b7e2c8a4f6d1b9

El mateix hash. git rev-parse és l'ordre que tradueix qualsevol forma d'anomenar un commit al seu hash complet. Ja la vas fer servir al mòdul 1 amb git rev-parse HEAD; funciona igual amb noms de branca, amb HEAD~2, amb etiquetes i amb tot el que vam veure a la lliçó 02-06.

I si vols confirmar que aquell hash és efectivament un commit i no una altra cosa:

git cat-file -t c5d9b1e
commit
git cat-file -p c5d9b1e
tree 8c4f2a1e9b7d3f5a6c8e0b2d4f6a8c0e2b4d6f8a
parent 4e7f2a9c8b1d5e3f7a2c9d4b6e8f1a3c5d7b9e2f
author Ana Ferrer <[email protected]> 1753876800 +0200
committer Ana Ferrer <[email protected]> 1753876800 +0200

Documenta la instal·lació al README

Res de nou: és l'objecte commit que ja saps llegir. L'important és la conclusió.

Una branca és un punter mòbil a un commit. No és una carpeta, no és una còpia, no és un contenidor. És un nom llegible que guarda un hash.

El cas de les referències empaquetades

Un avís perquè no t'espantis si un dia el fitxer no hi apareix. Git, per no tenir milers de fitxers minúsculs en repositoris grans, agrupa les referències que no canvien sovint en un únic fitxer:

cat .git/packed-refs
# pack-refs with: peeled fully-peeled sorted
c5d9b1e7a3f2d8b4e6c1a9f5d3b7e2c8a4f6d1b9 refs/heads/main

Si git gc s'ha executat (Git ho fa sol de tant en tant), pot ser que .git/refs/heads/main hagi desaparegut i que la seva informació sigui aquí. El significat és idèntic: nom de branca → hash. Per això git rev-parse és més fiable que cat: mira als dos llocs.

  1. HEAD: on ets ara mateix

Si una branca és un punter a un commit, cal alguna cosa que indiqui en quina branca estàs treballant. Aquesta cosa és HEAD, i també és un fitxer:

cat .git/HEAD
ref: refs/heads/main

Fixa-t'hi bé, perquè aquí hi ha la clau de tota la lliçó: HEAD no conté cap hash. Conté la cadena ref: refs/heads/main, que és una referència simbòlica: apunta a una altra referència, no a un objecte.

La cadena d'indirecció completa és aquesta:

graph LR
    HEAD[".git/HEAD<br/>ref: refs/heads/main"] --> BRANCA[".git/refs/heads/main<br/>c5d9b1e…"]
    BRANCA --> COMMIT["objecte commit c5d9b1e<br/>tree + parent + autor + missatge"]
    COMMIT --> TREE["objecte tree<br/>index.html, estils.css,<br/>app.js, README.md"]

Tres salts: HEAD → branca → commit → arbre de fitxers. Quan executes git status i llegeixes On branch main, Git està literalment llegint la primera línia d'aquell fitxer.

També hi ha una ordre per consultar-ho sense obrir fitxers:

git symbolic-ref HEAD
refs/heads/main

I si el que vols és només el nom curt de la branca, que és el que es fa servir en scripts i en els indicadors del terminal:

git branch --show-current
main

HEAD com a forma d'anomenar un commit

Ja has fet servir HEAD moltes vegades —git diff HEAD, HEAD~1, git show HEAD— com si fos el commit actual. I funciona, perquè Git resol la cadena sencera automàticament:

git rev-parse HEAD
c5d9b1e7a3f2d8b4e6c1a9f5d3b7e2c8a4f6d1b9

El mateix hash que git rev-parse main, perquè HEAD apunta a main i main apunta a aquell commit. Però compte, són coses diferents:

Expressió Què retorna Què és al disc
git rev-parse HEAD El hash del commit actual (resolent tota la cadena) .git/HEAD → conté una referència simbòlica
git symbolic-ref HEAD El nom de la branca actual El contingut literal de .git/HEAD
git rev-parse main El hash al qual apunta la branca main .git/refs/heads/main

  1. Per què HEAD apunta a una branca i no a un commit

Aquesta és la pregunta que separa qui «fa servir» Git de qui l'entén. Per què aquesta indirecció? Per què HEAD no guarda directament el hash del commit actual, que seria més simple?

Perquè HEAD ha de saber quina branca ha d'avançar quan confirmes.

Imagina que HEAD guardés directament c5d9b1e. L'Ana fa un commit nou. Git crea l'objecte commit… i ara què? Quina branca ha d'actualitzar? main? Alguna altra que també apuntés allà? No tindria manera de saber-ho.

En guardar ref: refs/heads/main, Git sap exactament dues coses:

  1. El commit pare del nou commit és aquell al qual apunta main (resolent la cadena).
  2. La branca que s'ha d'actualitzar després de crear el commit és main.

Aquesta indirecció és el que converteix una branca en un punter mòbil en lloc d'una simple etiqueta. I és també la raó que existeixi un estat especial anomenat detached HEAD, en què HEAD sí que conté un hash directament i, per tant, no hi ha cap branca a avançar. Ho veurem amb detall a la lliçó següent.

  1. Com avança el punter en confirmar

Vegem la mecànica exacta. L'Ana és a main amb el repositori tal com va quedar al final del mòdul 2:

git log --oneline
c5d9b1e (HEAD -> main) Documenta la instal·lació al README
4e7f2a9 Afegeix l'esborrat de tasques al llistat
8b6d3c2 Afegeix els estils base del llistat
1a4c8d6 Estructura inicial del gestor de tasques

Aquell (HEAD -> main) que has vist tantes vegades ja no és cap adorn: és Git dibuixant-te la cadena d'indirecció. Es llegeix literalment com «HEAD apunta a main, i main és aquí».

Estat al disc abans de confirmar:

.git/HEAD                → ref: refs/heads/main
.git/refs/heads/main     → c5d9b1e…

Ara l'Ana confirma un canvi qualsevol:

git commit -m "Corregeix l'enllaç de la documentació al README"
[main a3f5c9e] Corregeix l'enllaç de la documentació al README
 1 file changed, 1 insertion(+), 1 deletion(-)

Git ha fet, en aquest ordre:

  1. Ha escrit els objectes blob dels fitxers modificats i els tree corresponents.
  2. Ha creat un objecte commit el camp parent del qual és c5d9b1e (el commit al qual apuntava main, obtingut resolent HEAD).
  3. Ha sobreescrit el fitxer .git/refs/heads/main amb el hash nou.

Estat al disc després:

.git/HEAD                → ref: refs/heads/main      ← SENSE CANVIS
.git/refs/heads/main     → a3f5c9e…                  ← ACTUALITZAT

HEAD no s'ha tocat. Continua dient exactament el mateix. El que ha canviat és el fitxer de la branca. Comprova-ho:

cat .git/refs/heads/main
a3f5c9e2b1d47f8e0c6a2b9d3e5f7a1c4b6d8e0f

Aquesta és l'operació fonamental de Git i és sempre la mateixa: crear un commit i moure 41 bytes. Que hi hagi una branca o cinquanta no canvia res.

I aquí convé recordar una cosa del mòdul 1: els commits són immutables, però les branques no. Un commit, un cop creat, no canvia mai —canviar-lo produiria un altre hash i, per tant, un altre commit—. Les branques, en canvi, són punters que es mouen constantment. Aquesta asimetria és la base de tot el que ve en aquest mòdul i al 5.

  1. Per què les branques de Git són barates (i les d'SVN no ho eren)

Ara que saps que una branca són 41 bytes, la comparació amb els sistemes anteriors s'explica sola.

A Subversion (SVN), el sistema centralitzat que va dominar abans de Git i que ja vam comparar a la lliçó 01-01, no existia el concepte de branca com a tal. Una branca era una còpia d'un directori dins del mateix repositori, per convenció en una carpeta anomenada branches/:

repositori/
├── trunk/                    ← la línia principal
├── branches/
│   ├── comptador-tasques/    ← una còpia completa del projecte
│   └── filtre-pendents/      ← una altra còpia completa
└── tags/

Crear una branca era executar svn copy i esperar. És cert que SVN implementava còpies mandroses i no duplicava físicament tots els bytes, però l'operació continuava requerint comunicació amb el servidor, continuava sent lenta en projectes grans i, sobretot, continuava tenint un cost conceptual enorme: fusionar una branca de tornada al tronc era una operació delicada, propensa a errors i que molts equips evitaven activament.

La comparació en detall:

Aspecte Subversion Git
Què és una branca Una còpia d'un directori al repositori Un fitxer de 41 bytes amb un hash
Cost de crear-la Operació al servidor, segons o minuts Escriure 41 bytes en local, mil·lisegons
Requereix xarxa? No
Canviar de branca Actualitzar la còpia de treball des del servidor Reescriure els fitxers del directori de treball, en local
Fusionar Històricament fràgil; fins a la 1.5 calia portar el seguiment a mà Operació de primera classe, amb seguiment automàtic d'ancestres
Pràctica habitual de l'equip Poques branques, de llarga durada, temudes Moltes branques, de vida curta, rutinàries

Mesura-ho tu mateix quan creïs la teva primera branca a la lliçó següent: l'operació és instantània perquè no es copia res. Tots els commits, tots els arbres i tots els blobs ja són a la base de dades d'objectes, compartits per totes les branques. L'única cosa nova és el nom.

I aquest canvi de cost va tenir una conseqüència cultural enorme. Quan crear una branca costa zero, deixes de preguntar-te si val la pena: n'obres una per provar una idea de deu minuts, i si no funciona l'esborres. Aquesta és la mentalitat que aquest mòdul et vol instal·lar.

  1. El graf de commits: l'estructura real

Fins ara hem vist l'historial com una llista, perquè només hi havia una branca. Amb diverses branques, l'historial és el que sempre va ser en realitat: un graf dirigit acíclic (DAG, per les sigles en anglès).

Descomponguem aquest nom, que sona pitjor del que és:

  • Graf: nodes (els commits) units per arestes (els enllaços parent).
  • Dirigit: les arestes tenen sentit, i el sentit és cap enrere. Cada commit coneix el seu pare; cap commit no coneix els seus fills. Per això git log recorre l'historial cap al passat i mai cap al futur.
  • Acíclic: no hi ha cicles. És impossible que un commit sigui el seu propi avantpassat, perquè el seu hash depèn del hash del pare; un cicle exigiria conèixer un hash abans de calcular-lo.

Així es veu el repositori de l'Ana ara mateix, amb una sola línia:

gitGraph
   commit id: "1a4c8d6"
   commit id: "8b6d3c2"
   commit id: "4e7f2a9"
   commit id: "c5d9b1e"

I així es veurà d'aquí a dues lliçons, quan la feina de l'Ana i la d'en Bruno convisquin al mateix repositori en branques diferents:

gitGraph
   commit id: "1a4c8d6"
   commit id: "8b6d3c2"
   commit id: "4e7f2a9"
   commit id: "c5d9b1e"
   branch comptador-tasques
   checkout comptador-tasques
   commit id: "6f2b9d4"
   commit id: "9d1e4b7"
   checkout main
   branch filtre-pendents
   checkout filtre-pendents
   commit id: "3d5b8e1"
   commit id: "7c1f4a9"
   commit id: "b2e6d3f"

Fixa't en un detall important del diagrama: els tres punts de partida (main, comptador-tasques, filtre-pendents) comparteixen els quatre primers commits. No hi ha tres còpies d'1a4c8d6: hi ha un únic objecte commit a .git/objects/ assolible des de tres noms diferents. Això és el que fa que les branques no ocupin espai.

Al terminal, aquest mateix graf es veu amb l'ordre que ja coneixes del mòdul 2 i que a partir d'ara serà la teva eina principal:

git log --oneline --graph --all
* b2e6d3f (filtre-pendents) Corregeix el focus del camp després d'afegir una tasca
* 7c1f4a9 Aplica estil a les tasques completades
* 3d5b8e1 Afegeix el filtre de tasques pendents
| * 9d1e4b7 (comptador-tasques) Marca les tasques com a completades en fer clic
| * 6f2b9d4 Afegeix el comptador de tasques pendents
|/
* c5d9b1e (HEAD -> main) Documenta la instal·lació al README
* 4e7f2a9 Afegeix l'esborrat de tasques al llistat
* 8b6d3c2 Afegeix els estils base del llistat
* 1a4c8d6 Estructura inicial del gestor de tasques

Para atenció a --all. Sense aquesta opció, git log només mostra el que és assolible des de HEAD, és a dir, només els quatre commits de main: la feina de les altres branques seria invisible. Amb --all, Git parteix de totes les referències. És un descuit tan habitual que val la pena que el memoritzis ara.

I fixa't en la línia |/: aquí és on les dues línies de treball s'ajunten cap enrere. Aquest punt té nom propi i és el protagonista de l'apartat següent.

  1. Divergència i ancestre comú

Dues branques divergeixen quan cadascuna té commits que l'altra no té. Dit amb precisió: quan cap de les dues no és assolible des de l'altra seguint enllaços de pare.

És important distingir tres situacions diferents, perquè determinen com es comportarà la fusió que veurem a la lliçó 03-03:

Situació Descripció Exemple
Branca endarrerida Tots els commits d'A són a B, però B en té més main és a c5d9b1e; comptador-tasques té dos commits al damunt
Branca avançada Tots els commits de B són a A, però A en té més El mateix vist des de l'altre costat
Branques divergents Cadascuna té commits propis des que es van separar comptador-tasques i filtre-pendents entre si

Al graf de dalt, comptador-tasques té dos commits que filtre-pendents no té, i filtre-pendents en té tres que comptador-tasques no té. Han divergit.

L'ancestre comú

Quan dues branques divergeixen, el punt on es van separar s'anomena ancestre comú (merge base, en la terminologia de Git). És el commit més recent assolible des de totes dues branques.

Al nostre graf, l'ancestre comú de comptador-tasques i filtre-pendents és c5d9b1e: és l'últim commit que totes dues tenen a la seva història.

graph RL
    A["1a4c8d6"]
    B["8b6d3c2"] --> A
    C["4e7f2a9"] --> B
    D["c5d9b1e<br/><b>ancestre comú</b>"] --> C
    E["6f2b9d4"] --> D
    F["9d1e4b7<br/>comptador-tasques"] --> E
    G["3d5b8e1"] --> D
    H["7c1f4a9"] --> G
    I["b2e6d3f<br/>filtre-pendents"] --> H

(Les fletxes apunten cap al passat, com a la realitat: cada commit assenyala el seu pare.)

Per què importa tant aquest concepte? Perquè és la peça que fa possible fusionar. Per ajuntar dues branques divergents, Git necessita saber, per a cada línia de cada fitxer, qui la va canviar: si només va canviar en una branca, s'agafa aquella versió; si va canviar a les dues de manera diferent, hi ha conflicte. I per saber «qui la va canviar» cal un punt de referència neutral: l'ancestre comú.

Amb tres versions de cada fitxer —la de l'ancestre, la d'una branca i la de l'altra— Git pot raonar. Amb només dues, no podria distingir un canvi d'una eliminació. D'aquí el nom de fusió a tres bandes (three-way merge) que veurem a la lliçó 03-03.

Anticipem la lògica amb un exemple concret sobre una línia de README.md:

Versió Contingut de la línia Conclusió de Git
Ancestre c5d9b1e # Gestor de Tasques Punt de partida
Branca de l'Ana # Gestor de Tasques No l'ha tocada
Branca d'en Bruno # Gestor de Tasques de l'Equip L'ha canviada ell

Resultat: s'agafa la versió d'en Bruno, sense conflicte i sense preguntar. Git sap que l'Ana no va tocar aquella línia perquè coincideix amb l'ancestre. Si les dues versions fossin diferents de l'ancestre i diferents entre si, tindríem un conflicte i caldria una persona: això és la lliçó 03-05.

  1. Les branques que ja tens sense saber-ho

Acabem lligant dos caps que arrossegues des del mòdul 1.

Primer: main no té res d'especial. És una branca exactament igual que qualsevol altra, amb el mateix fitxer de 41 bytes al mateix directori. Git no li dona cap tracte preferent; la seva importància és purament una convenció de l'equip. De fet, el seu nom el vas triar tu a la lliçó 01-06, en posar init.defaultBranch = main (per això el projecte de l'Ana no es diu master).

Segon: la branca inicial existeix abans de tenir contingut. Quan l'Ana va executar git init a la lliçó 02-01, git status va dir On branch main tot i que .git/refs/heads/ era buit. Ara saps exactament per què:

# En un repositori acabat d'inicialitzar, abans de la primera confirmació:
cat .git/HEAD
ref: refs/heads/main
ls .git/refs/heads/
(buit)

HEAD apunta a una branca que encara no existeix com a fitxer. Git en diu una branca òrfena (unborn branch). El fitxer .git/refs/heads/main es crea en el moment de la primera confirmació, i per això aquell commit és especial: no té pare, i git commit l'anuncia amb (root-commit).

És una conseqüència elegant del disseny: com que HEAD guarda un nom i no un hash, pot apuntar perfectament a una cosa que encara no existeix.

Errors Habituals i Consells

Error 1: creure que una branca «conté» commits. És la confusió més freqüent i la font de gairebé tots els malentesos posteriors. Una branca és un punter a un commit; la resta de l'historial es dedueix seguint els pares. Conseqüència pràctica: un mateix commit pot ser en deu branques alhora sense ocupar més espai, i esborrar una branca no esborra cap commit (només el punter, com veurem a la lliçó 03-06).

Error 2: creure que esborrar una branca esborra la feina. Es dedueix del punt anterior. Esborrar .git/refs/heads/experiment elimina 41 bytes. Els commits continuen a .git/objects/, i si no queden referències que els assoleixin acabaran sent eliminats pel recol·lector d'escombraries, però no de manera immediata ni silenciosa. Hi ha xarxa de seguretat.

Error 3: oblidar --all a git log --graph. Sense --all, Git només et mostra el que és assolible des de HEAD i et dona la falsa impressió que la resta de branques no tenen res. Acostuma't a escriure git log --oneline --graph --all com una sola unitat; a la lliçó 06-04 el convertirem en un àlies.

Error 4: confondre HEAD amb la branca actual. HEAD és el fitxer que indica quina és la branca actual. En el 99 % dels casos els pots fer servir indistintament, però quan entris en detached HEAD (lliçó següent) la diferència es torna crítica: allà hi ha HEAD però no hi ha branca.

Consell 1: compte amb HEAD en majúscules. A Linux i macOS, head (minúscules) no és el mateix que HEAD i Git et donarà un error de referència desconeguda. A Windows i a macOS amb sistema de fitxers no sensible a majúscules pot funcionar per accident, cosa que fa la fallada més traïdora quan l'script arriba a una altra màquina. Escriu-ho sempre en majúscules.

Consell 2: no editis els fitxers de .git/refs/ a mà. Els hem llegit per entendre la maquinària, i llegir-los és perfectament segur. Escriure'ls no: Git té bloquejos, registres de canvis (el reflog, que veurem al mòdul 9) i validacions que et saltaries. Per moure una branca a mà existeix git update-ref.

Consell 3: instal·la un indicador de branca al teu terminal. Saber en tot moment en quina branca ets evita la meitat dels ensurts d'aquest mòdul. Moltes configuracions de shell modernes ja el porten; si no, git branch --show-current és l'ordre que se sol fer servir per construir-lo.

Exercicis

Exercici 1: l'autòpsia d'una branca

Sense fer servir git log ni git branch, i utilitzant únicament ordres de «lampisteria» i lectura de fitxers, esbrina al teu propi repositori:

  1. En quina branca ets.
  2. A quin commit apunta aquesta branca.
  3. Quin és el missatge d'aquell commit.
  4. Quin és el hash del seu commit pare.

Exercici 2: verificar que HEAD no es mou

Demostra empíricament el que afirma l'apartat 5: que en confirmar canvia el fitxer de la branca però no .git/HEAD. Dissenya la seqüència d'ordres que ho prova i explica què esperes veure a cada pas.

Exercici 3: raonar sobre el graf

Donat aquest historial, respon sense executar res:

* d4e5f6a (branca-b) Ajusta el peu de pàgina
* c3f8a21 Afegeix el peu de pàgina
| * b1a2c3d (HEAD -> branca-a) Corregeix el títol
| * a9f8e7d Afegeix la capçalera
|/
* 8f1a3b7 (main) Estructura inicial
  1. Quin és l'ancestre comú de branca-a i branca-b?
  2. Quants commits té branca-a que no tingui branca-b?
  3. Apareixeria c3f8a21 a la sortida de git log --oneline sense --all? Per què?
  4. Han divergit main i branca-a?
  5. Quant ocupen al disc els fitxers de les tres branques, en total?

Solucions

Solució 1:

# 1. En quina branca ets: llegim HEAD directament
cat .git/HEAD
# → ref: refs/heads/main

HEAD conté una referència simbòlica, i l'última part de la ruta és el nom de la branca: main.

# 2. A quin commit apunta aquesta branca
cat .git/refs/heads/main
# → c5d9b1e7a3f2d8b4e6c1a9f5d3b7e2c8a4f6d1b9

Si el fitxer no existís perquè les referències estan empaquetades, l'alternativa fiable és git rev-parse main, o buscar la línia corresponent a .git/packed-refs.

# 3 i 4. Missatge i pare: llegim l'objecte commit
git cat-file -p c5d9b1e
tree 8c4f2a1e9b7d3f5a6c8e0b2d4f6a8c0e2b4d6f8a
parent 4e7f2a9c8b1d5e3f7a2c9d4b6e8f1a3c5d7b9e2f
author Ana Ferrer <[email protected]> 1753876800 +0200
committer Ana Ferrer <[email protected]> 1753876800 +0200

Documenta la instal·lació al README

El missatge és Documenta la instal·lació al README i el pare és 4e7f2a9…. Fixa't que has recorregut a mà exactament el mateix camí que recorre git log: HEAD → branca → commit → parent.

Solució 2:

# Estat inicial: anotem els dos valors
cat .git/HEAD
cat .git/refs/heads/main
ref: refs/heads/main
c5d9b1e7a3f2d8b4e6c1a9f5d3b7e2c8a4f6d1b9
# Fem un canvi qualsevol i el confirmem
echo "" >> README.md
git commit -am "Afegeix una línia en blanc al README"
[main a3f5c9e] Afegeix una línia en blanc al README
 1 file changed, 1 insertion(+)
# Tornem a mirar els mateixos dos fitxers
cat .git/HEAD
cat .git/refs/heads/main
ref: refs/heads/main
a3f5c9e2b1d47f8e0c6a2b9d3e5f7a1c4b6d8e0f

.git/HEAD és idèntic; .git/refs/heads/main ha canviat. Confirmar no mou HEAD: mou la branca a la qual HEAD apunta. Aquesta és exactament la raó que HEAD guardi un nom i no un hash.

(Si vols desfer el commit de prova, git reset --hard HEAD~1 ho torna tot a l'estat anterior; ho veurem en profunditat a la lliçó 09-02.)

Solució 3:

  1. L'ancestre comú és 8f1a3b7. És el commit més recent assolible des de les dues branques: branca-a hi arriba per a9f8e7d, i branca-b per c3f8a21.

  2. Dos commits: a9f8e7d i b1a2c3d. Són els que hi ha a la línia de branca-a per damunt del punt de separació.

  3. No apareixeria. Sense --all, git log parteix de HEAD, que apunta a branca-a. Des de b1a2c3d s'arriba a a9f8e7d i d'allà a 8f1a3b7, però mai a c3f8a21: les fletxes van cap al passat i no hi ha cap camí des de branca-a fins a la línia de branca-b.

  4. No han divergit. main és a 8f1a3b7, que és assolible des de branca-a. main està simplement endarrerida: no té cap commit propi que branca-a no tingui. Aquesta situació és justament la que permetrà una fusió fast-forward a la lliçó 03-03.

  5. 123 bytes: tres fitxers de 41 bytes cadascun (main, branca-a i branca-b). Els sis commits, els seus arbres i els seus blobs s'emmagatzemen una sola vegada a .git/objects/ i els comparteixen les tres branques. Aquest és tot el «cost» de tenir tres línies de treball obertes.

Conclusió

Hem obert el capó i a dins no hi havia màgia, sinó un disseny molt simple portat fins a les últimes conseqüències:

  • Una branca és un fitxer de 41 bytes a .git/refs/heads/ (o una línia a .git/packed-refs) que conté el hash d'un commit. Res més. Es consulta de manera fiable amb git rev-parse <branca>.
  • HEAD és un altre fitxer que normalment conté una referència simbòlica del tipus ref: refs/heads/main. Apunta a una branca, no a un commit, i aquesta indirecció és deliberada: és el que permet a Git saber quin punter ha de moure en confirmar.
  • Confirmar mou la branca, no HEAD. L'objecte commit es crea amb el commit actual com a pare i el fitxer de la branca se sobreescriu amb el hash nou. Els commits són immutables; les branques són mòbils.
  • Les branques són barates perquè no copien res. Davant del svn copy de Subversion, que exigia servidor i temps, aquí tot l'historial viu en una base de dades d'objectes compartida i una branca només hi afegeix un nom. Aquest canvi de cost és cultural, no només tècnic.
  • L'historial és un graf dirigit acíclic: els commits apunten cap enrere, als seus pares. git log --oneline --graph --all és la manera de veure'l; no oblidis el --all.
  • Dues branques divergeixen quan cadascuna té commits propis des del seu punt de separació. Aquell punt és l'ancestre comú, i és la peça que farà possible la fusió: amb tres versions d'un fitxer —ancestre, branca A i branca B— Git pot deduir qui va canviar què.

El que ve

Ja saps què és una branca. Toca crear-ne i moure's entre elles, que és on apareixen les preguntes pràctiques: quina diferència hi ha entre git branch i git switch -c? Per què existeix git switch si ja existia git checkout? Què passa amb els canvis que tinc a mitges si canvio de branca? I què dimonis significa aquell avís de HEAD detached at 4e7f2a9 que fa por a tothom?

A la lliçó següent, Creant i Canviant Branques, l'Ana obrirà per fi funcionalitat/comptador-tasques i en Bruno funcionalitat/filtre-pendents, i el repositori deixarà de tenir una única línia de treball. Tot el que has après aquí —el fitxer de 41 bytes, la indirecció de HEAD, l'avanç del punter— ho veuràs succeir en directe.

Dominant Git: De Principiant a Avançat

Mòdul 1: Introducció a Git

Mòdul 2: Operacions Bàsiques de Git

Mòdul 3: Branques i Fusió

Mòdul 4: Treballant amb Repositoris Remots

Mòdul 5: Operacions Avançades de Git

Mòdul 6: Eines i Tècniques de Git

Mòdul 7: Estratègies de Col·laboració i Flux de Treball

Mòdul 8: Bones Pràctiques i Consells de Git

Mòdul 9: Resolució de Problemes i Depuració

Mòdul 10: Git al Món Real

© Copyright 2026. Tots els drets reservats