Llevas veintiocho capítulos usando Git. Este es el capítulo donde miramos debajo, no porque haga falta, sino porque lo que se entiende deja de dar miedo 🔦
La carpeta
mkdir ventas-miss-yera cd ventas-miss-yera git init -q printf 'ciudad,monto\nLima,1200\n' > ventas.csv git add ventas.csv git commit -q -m "Primeras ventas de Lima" ls -a ls .git
. .. .git ventas.csv COMMIT_EDITMSG HEAD branches config description hooks index info logs objects refs
Eso es todo tu repositorio. Si copias esa carpeta a otro sitio, te llevas la historia completa; si la borras, tu proyecto vuelve a ser una carpeta normal. Es lo que pasó en la trampa de la portada 💀
Una rama es un archivo de texto
Esto es lo que más sorprende:
cat .git/HEAD
cat .git/refs/heads/main
git log --oneline -1
ref: refs/heads/main 54da6e2f9b8dba1c61a0d04ac23844ad5499c36b 54da6e2 Primeras ventas de Lima
HEAD dice en qué rama estás. La rama es un archivo con
un hash y nada más. Por eso crear una rama es instantáneo
aunque el proyecto pese gigas: se escribe un archivo de 41 caracteres 🪶
git switch -q -c reporte-por-canal cat .git/HEAD ls .git/refs/heads git switch -q main cat .git/HEAD
ref: refs/heads/reporte-por-canal main reporte-por-canal ref: refs/heads/main
El hash sale del contenido
Esta es la idea de la que cuelga todo lo demás. El nombre de cada cosa guardada se calcula a partir de lo que contiene:
printf 'ciudad,monto\nLima,1200\n' | git hash-object --stdin printf 'ciudad,monto\nLima,1200\n' | git hash-object --stdin printf 'ciudad,monto\nLima,1290\n' | git hash-object --stdin
b5256c13309cbcc948c114c9e9ebc2a61de85b07 b5256c13309cbcc948c114c9e9ebc2a61de85b07 75af332b2019896a3976bfa99f247077777e4071
El mismo contenido da el mismo nombre, siempre y en cualquier máquina del mundo. Cambia un dígito y el nombre es otro completamente distinto 🧬
De ahí salen dos cosas importantes: que Git guarde una sola vez un archivo repetido en veinte carpetas, y que no se pueda cambiar el pasado sin que se note, porque cambiar un commit viejo cambiaría su hash y el de todos los que vienen detrás 🔗
Los tres tipos de cosa que guarda
| Se llama | Guarda |
|---|---|
blob | El contenido de un archivo, sin su nombre |
tree | Una carpeta: qué nombres hay y a qué blob apunta cada uno |
commit | Un tree, su padre, el autor, la fecha y el mensaje |
git cat-file -t HEAD git cat-file -p HEAD
commit tree a062f29a96c59f3fa0b44bb53e1804fcf4a79914 author Miss Yera <hola@missyera.com> 1768485600 -0500 committer Miss Yera <hola@missyera.com> 1768485600 -0500 Primeras ventas de Lima
Ahí está un commit entero, por dentro. Un tree, un autor, un
committer y el mensaje. Nada más 📄
git cat-file -p HEAD^{tree}
git cat-file -p $(git rev-parse HEAD:ventas.csv)
100644 blob b5256c13309cbcc948c114c9e9ebc2a61de85b07 ventas.csv ciudad,monto Lima,1200
El árbol dice "aquí hay un archivo llamado ventas.csv cuyo
contenido es este blob", y el blob es el contenido. Nombre y contenido
separados: por eso renombrar un archivo no duplica nada 🪄
Por qué el primer commit no tiene padre
printf 'Arequipa,890\n' >> ventas.csv git add ventas.csv git commit -q -m "Entran las ventas de Arequipa" git cat-file -p HEAD | head -3 git cat-file -p HEAD~1 | head -3
tree d14e7b7375b39679bb634384e6d651f8e899ebf9 parent 54da6e2f9b8dba1c61a0d04ac23844ad5499c36b author Miss Yera <hola@missyera.com> 1768485600 -0500 tree a062f29a96c59f3fa0b44bb53e1804fcf4a79914 author Miss Yera <hola@missyera.com> 1768485600 -0500 committer Miss Yera <hola@missyera.com> 1768485600 -0500
El segundo commit tiene una línea parent y el primero no. Esa
cadena de padres es la historia: no hay una lista guardada en
ningún lado, cada commit apunta al anterior 🔗
Cuando pesa de más
git count-objects -v git gc -q git count-objects -v
count: 6 size: 24 in-pack: 0 packs: 0 size-pack: 0 prune-packable: 0 garbage: 0 size-garbage: 0 count: 0 size: 0 in-pack: 6 packs: 1 size-pack: 1 prune-packable: 0 garbage: 0 size-garbage: 0
gc agrupa y comprime los objetos sueltos. Git lo corre solo de
vez en cuando, y es lo que hay que usar cuando la carpeta pesa, en vez de
borrar nada a mano 🧹
La trampa
Tu repositorio pesa mucho, entras a la carpeta .git a ver qué hay y borras lo que te parece basura.
$ du -sh .git 48M .git $ rm -rf .git/objects/pack $ git status error: object file .git/objects/pack/pack-3c1f80a.pack is empty fatal: loose object 8a2b19c is corrupt
Qué está mal
Esa carpeta no es basura, es el repositorio 📦
Los archivos que ves en tu carpeta son una copia de trabajo; el proyecto de verdad, con toda su historia, vive dentro de .git. Borrar ahí dentro no libera espacio, rompe el repositorio.
Si pesa de más, la respuesta casi nunca está en borrar: está en git gc, que compacta, o en dejar de meter archivos que no deberían entrar, que es 9.
La regla: dentro de .git se mira, no se toca. Y todo lo que hay ahí tiene un comando de Git que lo hace bien.
Comprueba que se entendió
Comprueba que lo tienes
Creas una rama nueva en un proyecto de 3 GB. ¿Qué escribe Git en el disco?
- Un archivo de texto con un hash dentro
- Una copia del proyecto entero
- Nada, la rama solo existe en memoria
- Una copia de los archivos que cambiaron
Ejercicios
1. Mira dentro de la carpeta
Crea el catálogo y lista qué hay en .git.
cd .. mkdir catalogo cd catalogo git init -q printf 'bodega,ciudad\nBodega Inti,Cusco\n' > bodegas.csv git add bodegas.csv git commit -q -m "Catalogo inicial de bodegas" ls .git
COMMIT_EDITMSG HEAD branches config description hooks index info logs objects refs
Ahí está todo el repositorio, y nada de eso se toca a mano.
2. Lee la rama y HEAD
Los dos son archivos de texto.
cat .git/HEAD
cat .git/refs/heads/main
git log --oneline -1
ref: refs/heads/main a9874ae5ade9ebff480ec9f29629b1b651573849 a9874ae Catalogo inicial de bodegas
El hash del archivo y el del log son el mismo.
3. Comprueba que el hash sale del contenido
El mismo texto da siempre el mismo nombre.
printf 'Bodega Inti,Cusco\n' | git hash-object --stdin printf 'Bodega Inti,Cusco\n' | git hash-object --stdin printf 'Bodega Inti,Arequipa\n' | git hash-object --stdin
a75ee2f9c7575eade168c118588516918a7b764e a75ee2f9c7575eade168c118588516918a7b764e ff769435ae848d1920b43c6efbf36af331a720ab
Dos iguales y uno distinto. Ese es todo el truco 🧬
4. Abre un commit por dentro
Qué guarda exactamente un commit.
git cat-file -t HEAD git cat-file -p HEAD
commit tree 4fd89c80cded29655afeee91b1fa83c80d97853d author Miss Yera <hola@missyera.com> 1768485600 -0500 committer Miss Yera <hola@missyera.com> 1768485600 -0500 Catalogo inicial de bodegas
Un árbol, un autor, un committer y el mensaje.
5. Baja hasta el contenido del archivo
Del commit al árbol, y del árbol al contenido.
git cat-file -p HEAD^{tree}
git cat-file -p $(git rev-parse HEAD:bodegas.csv)
100644 blob 9fe960592f714d295228a7d61e886b631b6d8c12 bodegas.csv bodega,ciudad Bodega Inti,Cusco
El nombre vive en el árbol y el contenido en el blob, separados 🪄
6. Cuenta y compacta
Cuántos objetos hay sueltos y qué pasa al compactar.
printf 'Minimarket Sol,Trujillo\n' >> bodegas.csv git add bodegas.csv git commit -q -m "Entra el minimarket de Trujillo" git count-objects -v git gc -q git count-objects -v
count: 6 size: 24 in-pack: 0 packs: 0 size-pack: 0 prune-packable: 0 garbage: 0 size-garbage: 0 count: 0 size: 0 in-pack: 6 packs: 1 size-pack: 1 prune-packable: 0 garbage: 0 size-garbage: 0
Los sueltos se agrupan en un paquete. Eso es lo que hay que correr cuando pesa 🧹
7. Pide un objeto que no existe
Un hash inventado.
git cat-file -p 1111111111111111111111111111111111111111
fatal: Not a valid object name 1111111111111111111111111111111111111111
Como el nombre sale del contenido, un hash inventado no corresponde a nada y Git lo dice sin rodeos 🛑
Lo que te llevas
Una rama es un archivo con un hash. El hash sale del contenido. Y
dentro de .git se mira, no se toca.