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
- Magatzem de contingut adreçable per hash
- Els quatre tipus d'objecte
- Com es relacionen: el graf de commits
- SHA-1 i la transició a SHA-256
- Què hi ha dins de
.git/ - Exploració pràctica amb ordres de lampisteria
- Immutabilitat i reescriptura de l'historial
- Per què aquest model ho explica tot
- 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:
- Determinisme. El mateix contingut produeix sempre el mateix hash.
- Dispersió. Canviar un sol bit produeix un hash completament diferent.
- 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.cssno 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:
- 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. - 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.
- 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.
- 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:
- Els blobs d'
index.html,estils.cssiREADME.mdes comparteixen entre les dues confirmacions. No s'ha duplicat res. Nomésapp.jsté dos blobs diferents perquè el seu contingut va canviar. - 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 logcomença pel més recent. - 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?.
- 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:
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:
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:
- 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ó.
- 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.
- Què hi ha dins de
.git/
.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 remotsEls 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.
- 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
Sortida:
Desglossament:
echo "Gestor de tasques"envia aquest text per la sortida estàndard.|el canalitza cap a l'ordre següent.git hash-object --stdinllegeix 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 --stdinEls 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):
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 2b1a3c5Les 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 a3f5c9eLa 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-treeUn 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 completHas 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:
La sintaxi <referència>:<ruta> resol l'arbre i baixa fins al fitxer en un sol pas.
- 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:
- No es pot alterar el passat sense que es noti. Qualsevol canvi invalida tota la cadena posterior.
- 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. - 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:
- Git crea un commit nou, amb el mateix arbre i el mateix pare, però amb el missatge corregit. Té un hash diferent.
- Git mou la branca
mainperquè apunti al nou commit. - 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.
- 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 -po 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 amblsi llegirHEADorefs/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 gcels 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:
- Quants objectes blob diferents hi ha a la base de dades al final? Enumera'ls.
- Quants objectes tree s'han creat en total?
- Quants objectes commit hi ha?
- 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:
- Obtén el hash de la confirmació actual.
- Mostra aquest commit en cru i identifica el hash del seu arbre i el del seu pare.
- Mostra l'arbre i identifica el hash d'un dels seus fitxers.
- Mostra el contingut d'aquest fitxer des de la base de dades d'objectes.
- Comprova que el tipus de l'arbre és
treei el del fitxer,blob. - 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:
- Quins objectes nous es creen i quins queden orfes?
- Canvia el hash de
c1? I el de l'arbre dec2? Justifica-ho. - Què veurà en Bruno quan intenti sincronitzar-se?
- Hauria estat diferent si l'Ana no hagués enviat res encara?
Solucions
Solució a l'Exercici 1
-
Cinc blobs. Un per cada contingut diferent que ha existit:
index.html(no canvia mai): 1 blobapp.js(no canvia mai): 1 blobREADME.md(no canvia mai): 1 blobestils.cssversió original: 1 blobestils.cssversió modificada: 1 blob
Total: 5. El contingut és la clau, no el fitxer.
-
Tres arbres. Un per confirmació. L'arbre arrel canvia a totes tres perquè a la segona canvia el hash d'
estils.cssi a la tercera s'afegeix una entrada nova (estils-antic.css). Com que el projecte no té subdirectoris, només hi ha arbre arrel. -
Tres commits, un per confirmació.
-
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
-
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 quec3apuntava ac2, cal crear tambéc3'apuntant ac2'. Queden orfes els commitsc2ic3originals. Els arbres i els blobs no canvien: es reutilitzen íntegres perquè el contingut dels fitxers no s'ha tocat. -
c1no canvia: el seu hash depèn del seu propi contingut i del seu pare, i res d'això no s'ha modificat. L'arbre dec2tampoc 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. -
En Bruno veurà historials divergents. El seu
mainlocal apunta ac3, mentre que la branca compartida apunta ac3'. 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'ic3'al mateix historial. Resoldre-ho requereix que en Bruno alineï la seva branca amb la reescrita, cosa que es tracta al mòdul 9. -
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
- Què és Git?
- Instal·lant Git
- Terminologia Bàsica de Git
- El Model de Dades de Git
- Configurant Git
- Configuració Inicial
Mòdul 2: Operacions Bàsiques de Git
- Creant un Repositori
- Clonant un Repositori
- Flux de Treball Bàsic de Git
- Preparant i Confirmant Canvis
- Inspeccionant Canvis amb git diff
- Visualitzant l'Historial de Confirmacions
Mòdul 3: Branques i Fusió
- Entenent les Branques
- Creant i Canviant Branques
- Fusionant Branques
- Estratègies de Fusió
- Resolent Conflictes de Fusió
- Gestió de Branques
Mòdul 4: Treballant amb Repositoris Remots
- Entenent els Repositoris Remots
- Afegint un Repositori Remot
- Autenticació amb Repositoris Remots
- Obtenint i Baixant Canvis
- Enviant Canvis
- Rastrejant Branques
Mòdul 5: Operacions Avançades de Git
- Rebase
- Rebase Interactiu
- Cherry-Picking de Confirmacions
- Desant Canvis Temporals
- Etiquetant Confirmacions
- Revertint Confirmacions
Mòdul 6: Eines i Tècniques de Git
- Usant Git Hooks
- Git Bisect
- Git Blame
- Git Log i Àlies
- Submòduls de Git
- Múltiples Còpies de Treball amb git worktree
Mòdul 7: Estratègies de Col·laboració i Flux de Treball
- Forks i Pull Requests
- Revisions de Codi amb Git
- Flux de Treball Git Flow
- GitHub Flow
- Trunk Based Development
- Integració Contínua amb Git
Mòdul 8: Bones Pràctiques i Consells de Git
- Escrivint Bons Missatges de Confirmació
- Mantenint un Historial Net
- Ignorant Fitxers amb .gitignore
- Atributs de Fitxer amb .gitattributes
- Bones Pràctiques de Seguretat
- Consells de Rendiment
Mòdul 9: Resolució de Problemes i Depuració
- Problemes Habituals de Git
- Desfent Canvis
- Resolent Divergències amb el Remot
- Recuperant Confirmacions Perdudes
- Tractant amb Repositoris Corruptes
- Tècniques Avançades de Depuració
