Aquesta és la lliçó més important del mòdul i, probablement, del primer terç del curs. Tot el que Git fa —confirmar, ramificar, fusionar, reescriure l'historial— és una conseqüència directa de com desa la informació. Qui entén el model de dades deixa de memoritzar ordres i comença a deduir-les: sap què és possible, què és perillós i per què una ordre té l'efecte que té.

La idea central és sorprenentment simple: Git és un magatzem de contingut adreçable per hash, és a dir, una taula clau-valor on la clau és l'empremta criptogràfica del contingut i el valor és el contingut mateix. Sobre aquesta base mínima es construeixen quatre tipus d'objecte i un grapat de referències, i amb això ja hi és tot. En aquesta lliçó obrirem la caixa: veurem els objectes per dins amb ordres de baix nivell, explorarem la carpeta .git i entendrem per què l'historial és immutable i què significa realment "reescriure'l".

Contingut

  1. Magatzem de contingut adreçable per hash
  2. Els quatre tipus d'objecte
  3. Com es relacionen: el graf de commits
  4. SHA-1 i la transició a SHA-256
  5. Què hi ha dins de .git/
  6. Exploració pràctica amb ordres de lampisteria
  7. Immutabilitat i reescriptura de l'historial
  8. Per què aquest model ho explica tot

  1. Magatzem de contingut adreçable per hash

La idea en una frase

Git desa objectes en una base de dades on la clau de cada objecte és el hash SHA del seu propi contingut.

Un hash és el resultat d'una funció criptogràfica que converteix qualsevol quantitat de dades en una cadena de longitud fixa. Té tres propietats que Git aprofita:

  1. Determinisme. El mateix contingut produeix sempre el mateix hash.
  2. Dispersió. Canviar un sol bit produeix un hash completament diferent.
  3. Resistència a col·lisions. És inviable trobar dos continguts diferents amb el mateix hash.

De la primera propietat se'n deriven dues conseqüències enormes:

  • Deduplicació automàtica. Si estils.css no canvia entre vint confirmacions, el seu contingut es desa una sola vegada. Les vint confirmacions apunten al mateix objecte.
  • Verificació d'integritat. Per comprovar que un objecte no s'ha corromput n'hi ha prou de recalcular-ne el hash i comparar-lo amb la seva clau. Si no coincideixen, hi ha corrupció.

De la segona se'n deriva una altra: qualsevol alteració del contingut canvia l'identificador, així que no es pot modificar res en silenci.

Instantànies, no diferències

Molts sistemes de control de versions desen, per a cada versió, la llista de diferències respecte a l'anterior. Reconstruir un fitxer antic implica aplicar la cadena de diferències des de l'origen.

Git funciona a l'inrevés: cada confirmació registra una instantània completa de l'estat del projecte. Conceptualment, és com si desés una foto de tots els fitxers en aquell moment.

graph TD
    subgraph "Model de diferències (SVN, CVS)"
        D1["v1: fitxer complet"] --> D2["v2: +3 línies, -1 línia"]
        D2 --> D3["v3: +8 línies"]
    end
    subgraph "Model d'instantànies (Git)"
        S1["c1: foto completa"] --> S2["c2: foto completa"]
        S2 --> S3["c3: foto completa"]
    end

L'objecció òbvia és l'espai: no ocupa moltíssim desar el projecte sencer cada vegada? No, per dos motius:

  1. Reutilització per hash. Els fitxers que no canvien no es desen de nou; la instantània simplement els referencia. Si en una confirmació l'Ana només toca estils.css, només es crea contingut nou per a aquest fitxer.
  2. Empaquetament. Periòdicament, Git comprimeix els objectes solts en packfiles que sí que fan servir compressió amb diferències entre objectes semblants. Però això és una optimització d'emmagatzematge invisible al model: conceptualment continuen sent instantànies.

  1. Els quatre tipus d'objecte

La base de dades de Git conté exactament quatre tipus d'objecte. Tots s'identifiquen pel seu hash i tots són immutables.

Tipus Representa Conté
blob El contingut d'un fitxer Els bytes del fitxer, i res més
tree Un directori Llista de noms amb el seu mode, tipus i hash
commit Una confirmació Hash de l'arbre arrel, pares, autor, data i missatge
tag anotat Una etiqueta amb metadades Objecte apuntat, nom, etiquetador, data, missatge i signatura opcional

El blob

Un blob (binary large object) desa el contingut d'un fitxer. Res més: ni el nom, ni els permisos, ni la ruta, ni la data. Només els bytes.

Això té una conseqüència elegant: si l'Ana copia estils.css a estils-backup.css sense canviar res, Git no desa contingut nou. Hi ha un blob i dues entrades de directori que el referencien. El nom del fitxer viu a l'arbre, no al blob.

El tree

Un tree representa un directori. És una llista d'entrades, cadascuna amb quatre dades: mode (permisos), tipus (blob o tree), hash i nom.

Així es veuria l'arbre arrel de gestor-tasques a la seva primera confirmació:

100644 blob a3f5c9e2b1d4...    README.md
100644 blob 7b2e8f1a5c3d...    app.js
100644 blob 2c9d4e6f8a1b...    estils.css
100644 blob e1f3a7b9c2d5...    index.html

Els modes que veuràs a la pràctica són pocs:

Mode Significat
100644 Fitxer normal
100755 Fitxer executable
120000 Enllaç simbòlic
040000 Subdirectori (un altre tree)

Si el projecte tingués una carpeta img/, l'arbre arrel inclouria una entrada de tipus tree apuntant a l'arbre d'aquella carpeta. Els arbres s'imbriquen i formen l'estructura de directoris completa.

El commit

Un commit és l'objecte que dona sentit a l'historial. Conté:

  • El hash de l'arbre arrel, és a dir, la instantània completa del projecte.
  • El hash del seu pare o pares: zero si és la primera confirmació, un en el cas normal, dos o més si és una fusió.
  • L'autor (qui va escriure el canvi) amb la seva data.
  • El confirmador (qui el va registrar) amb la seva data. Normalment coincideixen; se separen en escenaris com el rebase o l'aplicació de pedaços aliens.
  • El missatge.

Un commit real, mostrat en cru, té aquest aspecte:

tree 9f4c2a8e1b7d3f5a6c9e2b4d8f1a3c5e7b9d2f4a
parent 4d8f1a3c5e7b9d2f4a6c8e1b3d5f7a9c2e4b6d8f
author Ana Ferrer <[email protected]> 1751328000 +0200
committer Ana Ferrer <[email protected]> 1751328000 +0200

Afegeix l'esborrat de tasques al llistat

Fixa't en el que no hi ha: ni un sol canvi, ni una diferència, ni una llista de fitxers modificats. Un commit no desa "què va canviar", desa "com va quedar tot". Quan Git et mostra les diferències d'una confirmació, les calcula en aquell moment comparant el seu arbre amb el del seu pare. Aquest detall explica per què git show és una operació de còmput i no una simple lectura.

El tag anotat

Un tag anotat és un objecte que apunta a un altre objecte (gairebé sempre un commit) i hi afegeix metadades: el nom de l'etiqueta, qui la va crear, quan, un missatge i opcionalment una signatura GPG.

És l'únic dels quatre tipus que té una alternativa lleugera: una etiqueta lleugera no crea objecte, és només un fitxer a .git/refs/tags/ amb un hash a dins. Per això, per a versions publicades es recomanen les anotades: deixen rastre auditable. El mòdul 5 ho desenvolupa.

  1. Com es relacionen: el graf de commits

Els quatre tipus d'objecte s'enllacen per hash i formen un graf dirigit acíclic (DAG). Aquest diagrama mostra dues confirmacions consecutives de gestor-tasques, amb la segona modificant només app.js:

graph RL
    C2["commit c2<br/>'Afegeix l'esborrat de tasques'"] --> C1["commit c1<br/>'Versió inicial'"]
    C2 --> T2["tree arrel (c2)"]
    C1 --> T1["tree arrel (c1)"]

    T2 --> B_HTML["blob index.html"]
    T2 --> B_CSS["blob estils.css"]
    T2 --> B_JS2["blob app.js (v2)"]
    T2 --> B_MD["blob README.md"]

    T1 --> B_HTML
    T1 --> B_CSS
    T1 --> B_JS1["blob app.js (v1)"]
    T1 --> B_MD

Observa tres coses fonamentals al diagrama:

  1. Els blobs d'index.html, estils.css i README.md es comparteixen entre les dues confirmacions. No s'ha duplicat res. Només app.js té dos blobs diferents perquè el seu contingut va canviar.
  2. Les fletxes apunten cap enrere. Un commit coneix el seu pare, però un pare no coneix els seus fills. Per això Git recorre l'historial del present cap al passat, i per això git log comença pel més recent.
  3. Es van crear dos arbres arrel diferents encara que tres dels quatre fitxers siguin idèntics: l'arbre conté el hash d'app.js, que va canviar, així que el contingut de l'arbre va canviar i el seu hash també.

Sobre aquest graf es recolzen les referències que vam veure a la lliçó Terminologia Bàsica de Git:

graph RL
    HEAD["HEAD<br/>(fitxer .git/HEAD)"] -.apunta a.-> MAIN["refs/heads/main"]
    MAIN -.apunta a.-> C2["commit c2"]
    TAG["refs/tags/v1.0"] -.apunta a.-> C1["commit c1"]
    C2 --> C1

Una branca no és més que un nom que apunta a un node del graf. Confirmar significa crear un node nou i moure aquest nom. Res més. Per això les branques són barates, com vam avançar a la lliçó Què és Git?.

  1. SHA-1 i la transició a SHA-256

SHA-1: l'identificador clàssic

Git fa servir històricament SHA-1, que produeix hashos de 160 bits representats com a 40 caràcters hexadecimals:

a3f5c9e2b1d47f8e0c6a2b9d3e5f7a1c4b6d8e0f

A la pràctica es fan servir formes abreujades de 7 a 12 caràcters (a3f5c9e), suficients per identificar un objecte sense ambigüitat en repositoris normals. Git allarga automàticament l'abreviatura si detecta risc d'ambigüitat.

Un detall important: el hash no es calcula sobre el contingut pur, sinó sobre una capçalera concatenada amb el contingut. Per a un blob, la dada que es passa per SHA-1 és literalment:

blob <longitud en bytes>\0<contingut>

Aquest \0 és un byte nul separador. Gràcies a la capçalera, un blob i un tree amb els mateixos bytes produeixen hashos diferents.

La col·lisió de SHA-1 i la resposta de Git

El 2017, l'atac SHAttered va demostrar una col·lisió pràctica de SHA-1: dos fitxers PDF diferents amb el mateix hash. Això no va trencar Git immediatament —explotar-ho contra un repositori requereix condicions molt específiques i les referències creuades del graf ho dificulten molt—, però va encendre les alarmes.

La resposta va tenir dues fases:

  1. Detecció de col·lisions. Des de la versió 2.13, Git incorpora una implementació endurida de SHA-1 (SHA-1 collision detection) que reconeix els patrons de l'atac conegut i avorta si els troba. És una mitigació, no una solució.
  2. Migració a SHA-256. Git admet des de la versió 2.29 repositoris amb hashos SHA-256 de 64 caràcters hexadecimals. Es tria en crear el repositori i no es pot canviar després.
Aspecte SHA-1 SHA-256
Longitud del hash 40 caràcters hex (160 bits) 64 caràcters hex (256 bits)
Estat a Git Per defecte Experimental però funcional
Resistència a col·lisions Compromesa el 2017 Sense atacs pràctics coneguts
Interoperabilitat Universal Limitada: no pot comunicar-se amb repositoris SHA-1
Suport a les plataformes Total Parcial o inexistent el 2026

La transició és lenta precisament per la interoperabilitat: un repositori SHA-256 no es pot sincronitzar amb un de SHA-1, i tot l'ecosistema —GitHub, GitLab, eines de CI— l'hauria d'admetre simultàniament. Existeix un disseny de repositoris amb doble hash per permetre la convivència, però encara no està complet.

Per a aquest curs i per a la teva feina diària: fes servir SHA-1, que és el que Git fa per defecte. El que és rellevant és que coneguis el perquè d'aquesta transició i que entenguis que el model de dades no depèn de l'algorisme concret.

  1. Què hi ha dins de .git/

Un repositori acabat de crear té aquesta estructura. La recorrerem:

.git/
├── HEAD                 # on soc: ref: refs/heads/main
├── config               # configuració local del repositori
├── description          # descripció, només la fa servir GitWeb
├── index                # l'àrea de preparació (binari)
├── hooks/               # scripts automàtics (plantilles d'exemple)
├── info/
│   └── exclude          # fitxers ignorats només en local
├── logs/                # el reflog: historial de moviments de les refs
├── objects/             # LA BASE DE DADES: tots els objectes
│   ├── a3/
│   │   └── f5c9e2b1d4...
│   ├── info/
│   └── pack/            # objectes empaquetats i comprimits
└── refs/
    ├── heads/           # branques locals: un fitxer per branca
    ├── tags/            # etiquetes
    └── remotes/         # branques de seguiment dels remots

Els elements que importen de veritat:

Element Què és Conseqüència pràctica
objects/ La base de dades d'objectes És el repositori de veritat; si es perd, es perd l'historial
refs/heads/ Un fitxer per branca, amb un hash a dins Comprova amb els teus propis ulls que una branca són 41 bytes
HEAD Fitxer de text amb ref: refs/heads/main Canviar de branca és reescriure aquesta línia
index L'àrea de preparació Explica per què preparar és una operació separada
config Configuració de nivell local Ho veurem a la lliçó 01-05
logs/ El reflog És la xarxa de seguretat per recuperar feina perduda (mòdul 9)
hooks/ Scripts que Git executa automàticament Mòdul 6

Com es desen els objectes solts

Fixa't en objects/a3/f5c9e2b1d4...: els dos primers caràcters del hash són el nom del directori i els trenta-vuit restants, el del fitxer. És una optimització senzilla: evita tenir centenars de milers de fitxers en un sol directori, cosa que degrada greument el rendiment de molts sistemes de fitxers.

Cada fitxer d'objecte està comprimit amb zlib, així que obrir-lo amb un editor de text només mostra soroll binari. Per llegir-los cal fer servir les ordres de Git, que és justament el que farem ara.

  1. Exploració pràctica amb ordres de lampisteria

Git distingeix dues capes d'ordres:

  • Porcellana (porcelain): la interfície d'usuari habitual (git status, git commit, git log). La seva sortida està pensada per a humans i pot canviar entre versions.
  • Lampisteria (plumbing): ordres de baix nivell que operen directament sobre la base de dades (git hash-object, git cat-file, git rev-parse). La seva sortida és estable i està pensada per a scripts.

En el dia a dia faràs servir la porcellana. Aquí farem servir la lampisteria precisament perquè mostra el model sense adorns.

git hash-object: calcular el hash d'un contingut

echo "Gestor de tasques" | git hash-object --stdin

Sortida:

2b1a3c5e7d9f0b2a4c6e8d0f2a4b6c8e0d2f4a6b

Desglossament:

  • echo "Gestor de tasques" envia aquest text per la sortida estàndard.
  • | el canalitza cap a l'ordre següent.
  • git hash-object --stdin llegeix de l'entrada estàndard i calcula el hash que tindria aquest contingut com a blob. No desa res: només calcula.

Comprova tu mateix el determinisme: executa l'ordre dues vegades, i després canvia-li una sola lletra al text.

echo "Gestor de tasques" | git hash-object --stdin
echo "Gestor de tasquez" | git hash-object --stdin

Els dos hashos seran completament diferents, no semblants. Aquesta és la propietat de dispersió en acció.

Per desar l'objecte a la base de dades s'hi afegeix -w (write):

echo "Gestor de tasques" | git hash-object -w --stdin

Ara l'objecte existeix a .git/objects/. Aquesta ordre requereix ser dins d'un repositori.

git cat-file: llegir un objecte

És l'ordre inversa: donat un hash, mostra què hi ha a dins.

# Tipus de l'objecte: blob, tree, commit o tag
git cat-file -t 2b1a3c5

# Contingut de l'objecte, en format llegible
git cat-file -p 2b1a3c5

# Mida en bytes
git cat-file -s 2b1a3c5

Les tres opcions:

Opció Significa Retorna
-t type blob, tree, commit o tag
-p pretty-print El contingut formatat segons el tipus
-s size Mida en bytes

Aplicat a un commit, -p produeix exactament el bloc tree/parent/author/committer/missatge que vam veure a l'apartat 2. Aplicat a un arbre, produeix la llista d'entrades. Aplicat a un blob, el contingut del fitxer.

git rev-parse: resoldre noms a hashos

Tradueix qualsevol manera de referir-se a un objecte en el seu hash complet.

# A quin commit apunta HEAD?
git rev-parse HEAD

# A quin commit apunta la branca main?
git rev-parse main

# I el pare del commit actual?
git rev-parse HEAD~1

# Expandir un hash abreujat a la seva forma completa
git rev-parse a3f5c9e

La sintaxi de referència que convé reconèixer des d'ara:

Expressió Significa
HEAD La confirmació actual
HEAD~1, HEAD~2 Un, dos passos cap enrere per la primera línia de pares
HEAD^ El primer pare (equival a HEAD~1)
HEAD^2 El segon pare; només existeix en confirmacions de fusió
main La confirmació a la qual apunta la branca main
v1.0 La confirmació a la qual apunta l'etiqueta v1.0

Una ordre molt útil per orientar-se:

# Ruta absoluta del directori .git
git rev-parse --git-dir

# Soc dins d'un repositori Git?
git rev-parse --is-inside-work-tree

Un recorregut complet del graf

Reunint les tres ordres, així es navega el model de dalt cap a baix. Els hashos del teu repositori seran diferents: substitueix-los pels que retorni cada ordre.

# 1. Quin commit és l'actual
git rev-parse HEAD
# → c2a8f1e4b6d9...

# 2. Veure el commit per dins
git cat-file -p c2a8f1e
# → tree 9f4c2a8...
#   parent 4d8f1a3...
#   author Ana Ferrer <[email protected]> 1751328000 +0200
#   committer Ana Ferrer <[email protected]> 1751328000 +0200
#
#   Afegeix l'esborrat de tasques al llistat

# 3. Veure l'arbre arrel que aquest commit referencia
git cat-file -p 9f4c2a8
# → 100644 blob e1f3a7b...    index.html
#   100644 blob 2c9d4e6...    estils.css
#   100644 blob 7b2e8f1...    app.js
#   100644 blob a3f5c9e...    README.md

# 4. Veure el contingut d'app.js en aquest commit
git cat-file -p 7b2e8f1
# → el codi JavaScript complet

Has recorregut a mà el mateix camí que Git recorre internament cada vegada que executes qualsevol ordre: commit → tree → blob. Si aquest recorregut et resulta clar, tens el model mental que sosté la resta del curs.

Existeix una drecera còmoda que combina referència i ruta:

# El blob d'app.js al commit actual, sense buscar hashos a mà
git cat-file -p HEAD:app.js

La sintaxi <referència>:<ruta> resol l'arbre i baixa fins al fitxer en un sol pas.

  1. Immutabilitat i reescriptura de l'historial

Per què l'historial és immutable

Ja tenim totes les peces per entendre la propietat més important de Git.

El hash d'un commit es calcula sobre el seu contingut, i aquest contingut inclou el hash del seu pare. Per tant:

graph RL
    C3["c3<br/>el hash depèn de c2"] --> C2["c2<br/>el hash depèn de c1"]
    C2 --> C1["c1<br/>el hash depèn del seu arbre"]

Si algú modifiqués el missatge de c1, el seu hash canviaria. Aleshores c2 continuaria apuntant a un hash que ja no existeix, així que caldria canviar c2, cosa que canviaria el seu hash, cosa que obligaria a canviar c3, i així fins al final de l'historial.

D'aquí en surten tres garanties:

  1. No es pot alterar el passat sense que es noti. Qualsevol canvi invalida tota la cadena posterior.
  2. El hash d'una confirmació identifica tot l'historial que hi porta. Si l'Ana i en Bruno tenen el mateix hash a la punta de main, tenen exactament el mateix historial fins a aquell punt, byte a byte. És una verificació completa en una comparació de quaranta caràcters.
  3. Els objectes no es modifiquen mai, només es creen. No existeix cap operació a Git que editi un objecte existent.

Aquesta és la raó de la "integritat" que citàvem entre les virtuts de Git a la lliçó 01-01. No és una promesa: és una propietat matemàtica del disseny.

Què significa aleshores "reescriure l'historial"

Si res no es pot modificar, com és possible corregir el missatge d'una confirmació o reordenar l'historial, coses que Git clarament permet?

La resposta és que no es modifica res: es creen objectes nous i es mouen les referències.

Quan dius "corregiré el missatge de l'últim commit", el que passa és:

  1. Git crea un commit nou, amb el mateix arbre i el mateix pare, però amb el missatge corregit. Té un hash diferent.
  2. Git mou la branca main perquè apunti al nou commit.
  3. El commit antic continua existint a la base de dades. Simplement ja no hi ha cap referència que hi arribi.
graph RL
    subgraph "Abans"
        A_MAIN["main"] -.-> A_C2["c2 'Afegex esborat'"]
        A_C2 --> A_C1["c1"]
    end
    subgraph "Després"
        B_MAIN["main"] -.-> B_C2N["c2' 'Afegeix esborrat'"]
        B_C2N --> B_C1["c1"]
        B_C2["c2 (orfe)"] --> B_C1
    end

Els objectes sense referències s'anomenen orfes (unreachable). Romanen a la base de dades i continuen sent accessibles pel seu hash o a través del reflog —el registre de tots els moviments de les referències, que viu a .git/logs/— fins que la recollida d'escombraries (git gc) els elimina, normalment passades un parell de setmanes.

Això té dues implicacions cabdals:

  • Gairebé res no es perd de veritat. Si creus haver destruït feina confirmada, és molt probable que continuï allà. El mòdul 9 dedica una lliçó a recuperar-la.
  • Reescriure l'historial compartit és perillós. Si l'Ana reescriu confirmacions que en Bruno ja té al seu repositori, els hashos d'en Bruno deixen de coincidir amb els de l'Ana: els seus historials divergeixen i reconciliar-los és dolorós. D'aquí la regla d'or que repetirem al mòdul 5:

No reescriguis historial que ja has compartit amb altres.

Les operacions que reescriuen l'historial —rebase, commit --amend, reset sobre confirmacions publicades, filter-repo— es tracten als mòduls 5, 8 i 9. El que necessites ara és entendre per què són diferents de la resta: creen objectes nous i abandonen els antics, en lloc d'afegir al final.

  1. Per què aquest model ho explica tot

Tanquem amb la utilitat pràctica del que hem après. Aquestes preguntes freqüents es responen soles quan es coneix el model:

Pregunta habitual Resposta des del model
Per què crear una branca és instantani? És escriure 41 bytes a refs/heads/; no es copia res
Per què canviar de branca és tan ràpid? Es reescriu HEAD i s'ajusta el directori de treball a l'arbre corresponent
Per què no puc confirmar un directori buit? Els arbres només contenen entrades; sense blobs no hi ha res a registrar
Per què Git no desa els permisos complets d'un fitxer? L'arbre només distingeix executable, no executable i enllaç simbòlic
Per què reanomenar un fitxer no ocupa espai? El blob es reutilitza; només canvia l'entrada de l'arbre
Per què el repositori creix amb imatges i vídeos? Cada versió d'un binari genera un blob nou que no comprimeix bé
Per què en fer rebase canvien tots els hashos? Es creen confirmacions noves amb pares diferents
Per què puc recuperar una branca esborrada? Els commits continuen a objects/; el reflog en conserva el hash
Per què dues persones amb el mateix hash tenen el mateix codi? El hash cobreix l'arbre complet i tota la cadena de pares

Cada vegada que en els mòduls següents un comportament de Git et resulti estrany, torna a aquest model i pregunta't: quins objectes s'estan creant i quines referències s'estan movent? La resposta gairebé sempre apareix sola.

Errors Habituals i Consells

  • Creure que Git desa diferències. És l'error conceptual més estès i ve de l'experiència amb altres sistemes. Git desa instantànies i calcula les diferències quan les hi demanes. Les diferències que veus a git log -p o en una revisió de codi no estan emmagatzemades enlloc.
  • Pensar que el blob desa el nom del fitxer. No el desa. El nom viu a l'arbre. Per això Git no registra els canvis de nom explícitament: els detecta comparant continguts entre dos arbres.
  • Grapejar .git/ a mà. Explorar-la amb ls i llegir HEAD o refs/heads/main és instructiu i segur. Editar o esborrar fitxers a dins és una manera eficaç de corrompre el repositori. Per a tota la resta hi ha ordres.
  • Confiar que els objectes orfes duren per sempre. Sobreviuen a la reescriptura, però git gc els elimina passat un temps (per defecte, 30 dies per als inabastables recents i 90 per al reflog). Si necessites recuperar alguna cosa, fes-ho aviat.
  • Assumir que un hash abreujat és únic per sempre. Set caràcters basten avui al teu repositori; en un repositori amb centenars de milers de confirmacions poden tornar-se ambigus. En desar un hash a la documentació, fes servir la forma completa.
  • Consell: fes aquest experiment una vegada. En qualsevol repositori, executa el recorregut git cat-file -p HEAD → arbre → blob de l'apartat 6. Deu minuts d'exploració manual estalvien mesos de confusió.
  • Consell: quan alguna cosa vagi malament, pensa en referències. La immensa majoria dels ensurts de Git no són pèrdues de dades, sinó referències que apunten a un lloc inesperat. Els objectes gairebé sempre continuen allà.

Exercicis

Exercici 1: Predir hashos i objectes

L'Ana crea un repositori amb index.html, estils.css, app.js i README.md, i fa una primera confirmació. Després modifica només estils.css i fa una segona confirmació. Finalment, copia estils.css a estils-antic.css sense modificar-ne el contingut i fa una tercera confirmació.

Respon:

  1. Quants objectes blob diferents hi ha a la base de dades al final? Enumera'ls.
  2. Quants objectes tree s'han creat en total?
  3. Quants objectes commit hi ha?
  4. A la tercera confirmació, s'ha creat cap blob nou? Per què?

Exercici 2: Exploració pràctica

Sobre qualsevol repositori Git que tinguis a mà (si encara no en tens cap, guarda aquest exercici per després del mòdul 2), executa l'exploració completa i anota els resultats:

  1. Obtén el hash de la confirmació actual.
  2. Mostra aquest commit en cru i identifica el hash del seu arbre i el del seu pare.
  3. Mostra l'arbre i identifica el hash d'un dels seus fitxers.
  4. Mostra el contingut d'aquest fitxer des de la base de dades d'objectes.
  5. Comprova que el tipus de l'arbre és tree i el del fitxer, blob.
  6. Esbrina la mida en bytes d'aquest blob.

Exercici 3: Raonar sobre la reescriptura

L'Ana ha fet tres confirmacions a main: c1, c2 i c3. Ha enviat les tres al repositori compartit i en Bruno ja les té al seu equip. Ara l'Ana s'adona que el missatge de c2 té una falta d'ortografia i el corregeix reescrivint l'historial.

Respon:

  1. Quins objectes nous es creen i quins queden orfes?
  2. Canvia el hash de c1? I el de l'arbre de c2? Justifica-ho.
  3. Què veurà en Bruno quan intenti sincronitzar-se?
  4. Hauria estat diferent si l'Ana no hagués enviat res encara?

Solucions

Solució a l'Exercici 1

  1. Cinc blobs. Un per cada contingut diferent que ha existit:

    • index.html (no canvia mai): 1 blob
    • app.js (no canvia mai): 1 blob
    • README.md (no canvia mai): 1 blob
    • estils.css versió original: 1 blob
    • estils.css versió modificada: 1 blob

    Total: 5. El contingut és la clau, no el fitxer.

  2. Tres arbres. Un per confirmació. L'arbre arrel canvia a totes tres perquè a la segona canvia el hash d'estils.css i a la tercera s'afegeix una entrada nova (estils-antic.css). Com que el projecte no té subdirectoris, només hi ha arbre arrel.

  3. Tres commits, un per confirmació.

  4. No s'ha creat cap blob nou a la tercera confirmació. El contingut d'estils-antic.css és idèntic al d'estils.css, i Git desa el contingut indexat pel seu hash: ja existeix. L'única cosa nova és una entrada a l'arbre amb un altre nom apuntant al mateix blob. Aquest és l'exemple canònic de la deduplicació per contingut.

Solució a l'Exercici 2

# 1. Hash de la confirmació actual
git rev-parse HEAD
# → 8c4f2a1e9b7d3f5a6c8e0b2d4f6a8c0e2b4d6f8a

# 2. El commit en cru
git cat-file -p HEAD
# → tree 3f7a9c1e5b8d2f4a6c9e1b3d5f7a9c1e3b5d7f9a
#   parent 1e5b8d2f4a6c9e1b3d5f7a9c1e3b5d7f9a1c3e5b
#   author ...
#   committer ...
#
#   Missatge de la confirmació

# 3. L'arbre
git cat-file -p 3f7a9c1
# → 100644 blob 9c1e3b5...    README.md
#   100644 blob 5f7a9c1...    app.js
#   ...

# 4. Contingut del fitxer
git cat-file -p 5f7a9c1
# → (el codi font)

# Drecera equivalent sense buscar hashos:
git cat-file -p HEAD:app.js

# 5. Tipus
git cat-file -t 3f7a9c1     # → tree
git cat-file -t 5f7a9c1     # → blob

# 6. Mida
git cat-file -s 5f7a9c1     # → 412 (per exemple)

Solució a l'Exercici 3

  1. Es crea un commit nou, anomenem-lo c2', amb el mateix arbre i el mateix pare (c1) però amb un missatge diferent, i per tant un hash diferent. Com que c3 apuntava a c2, cal crear també c3' apuntant a c2'. Queden orfes els commits c2 i c3 originals. Els arbres i els blobs no canvien: es reutilitzen íntegres perquè el contingut dels fitxers no s'ha tocat.

  2. c1 no canvia: el seu hash depèn del seu propi contingut i del seu pare, i res d'això no s'ha modificat. L'arbre de c2 tampoc no canvia: el missatge forma part de l'objecte commit, no de l'arbre. És un bon recordatori que els quatre tipus d'objecte són independents entre si i només s'enllacen per hash.

  3. En Bruno veurà historials divergents. El seu main local apunta a c3, mentre que la branca compartida apunta a c3'. Git li dirà que les branques han divergit i que tenen confirmacions diferents per cada banda. Si intenta integrar sense compte, acabarà amb la feina duplicada: c2, c3, c2' i c3' al mateix historial. Resoldre-ho requereix que en Bruno alineï la seva branca amb la reescrita, cosa que es tracta al mòdul 9.

  4. Sí, hauria estat completament diferent. Si les confirmacions només existissin a l'equip de l'Ana, reescriure-les no tindria cap conseqüència: ningú més no té els hashos antics, així que no hi ha divergència possible. Aquesta és exactament la frontera de la regla d'or: reescriure historial local és segur i recomanable; reescriure historial compartit és perillós.

Conclusió

Git és, en el fons, una base de dades clau-valor on la clau és el hash del contingut. Sobre aquesta idea mínima s'aixequen quatre tipus d'objecte: el blob desa el contingut d'un fitxer, el tree representa un directori i dona nom als blobs, el commit apunta a un arbre arrel i als seus pares afegint-hi autor, data i missatge, i el tag anotat marca un punt de l'historial amb metadades. Enllaçats per hash, formen un graf dirigit acíclic sobre el qual les branques i HEAD no són més que noms que apunten a nodes.

D'aquest disseny se'n deriven directament les propietats que fan de Git el que és: deduplicació (el contingut idèntic es desa una sola vegada), integritat verificable (el hash delata qualsevol corrupció) i immutabilitat (canviar el passat invalida tota la cadena posterior). I de la immutabilitat se'n deriva el significat exacte de "reescriure l'historial": no es modifica res, es creen objectes nous i s'abandonen els antics, que sobreviuen orfes fins a la recollida d'escombraries. Aquesta és la base de la regla d'or —no reescriguis el que ja has compartit— i també de la bona notícia que gairebé res no es perd de veritat.

Has vist a més la distinció entre ordres de porcellana i de lampisteria, i has recorregut el graf a mà amb git rev-parse, git cat-file i git hash-object.

Amb el model mental assentat, tornem a la superfície. Abans que l'Ana pugui crear el repositori de gestor-tasques necessita ajustar com es comporta Git al seu equip. A Configurant Git veurem el mecanisme de configuració: els tres nivells de git config, on viu cada fitxer, quin valor guanya quan hi ha conflicte i com mantenir perfils separats per a la feina i els projectes personals.

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