CLASE 04
Git Internals
ReferenciasPuedes usar git log 1a410e para echar un vistazo a lo largo de toda tu historia, recorriendo y encontrando todos los objetos.
Pero para ello necesitas recordar que el ultimo commit es 1a410e.
En Git, esto es lo que se conoce como “referencias” o “refs”,
en la carpeta .git/refs puedes encontrar archivos con valores checksum SHA-1
y nombres simples que puedes usar en lugar de esos strings largos de checksums.
En el proyecto actual, la carpeta aun no contiene archivos:
find .git/refs
.git/refs
.git/refs/heads
.git/refs/tags
find .git/refs -type f
Para crear una nueva referencia que te sirva de ayuda para recordar cual es tu ultimo commit es tan simple como:
echo "1a410efbd13591db07496d4f82dfc14aad14979" > .git/refs/heads/master
A partir de ese momento puedes usar esa referencia en lugar del valor checksum SHA-1:
git log --pretty=oneline master # historial en una linea para la referencia "master"
1a410efbd13591db07496d4f82dfc14aad14979 third commit
cac0cab538b970a346d349e0d0e4196f5f3b1955 second commit
fdf4fc3344e67ab068f836878b6c4951e3b15f3d first commit
No es conveniente editar directamente los archivos de referencias.
Git suministra un comando mucho mas seguro para hacer esto: git update-ref:
git update-ref refs/heads/master 1a410efbd13591db07496d4f82dfc14aad14979
Esto es basicamente lo que es una rama en Git: un simple puntero o referencia a la cabeza de una linea de trabajo. Para crear una rama nueva desde el segundo commit:
git update-ref refs/heads/test cac0ca # actualizar referencia con el nombre corto del checksum
Y la rama contendra unicamente trabajo desde esa confirmacion hacia atras.
Cuando lanzas
git branch <nombre>, Git ejecuta internamentegit update-refpara anadir el checksum SHA-1 del ultimo commit de la rama en que te encuentras a cualquier nueva referencia que quieras crear.
¿Como sabe Git cual es el checksum SHA-1 del ultimo commit cuando ejecutas git branch <nombre>?
La respuesta es el archivo HEAD.
El archivo HEAD es una referencia simbolica a la rama donde te encuentras en cada momento.
A diferencia de una referencia normal, la referencia simbolica contiene un enlace a otra referencia en vez del valor checksum SHA-1 de un objeto.
cat .git/HEAD
ref: refs/heads/master
Si lanzas git checkout test, Git actualiza el archivo .git/HEAD:
cat .git/HEAD
ref: refs/heads/test
Cuando lanzas git commit, se crea un nuevo objeto commit teniendo como padre el commit al que referencia en este momento el HEAD.
Puedes leer y cambiar este archivo de forma segura con el comando git symbolic-ref:
git symbolic-ref HEAD
refs/heads/master
# ---
git symbolic-ref HEAD refs/heads/test
cat .git/HEAD
ref: refs/heads/test
Pero no puedes fijar una referencia simbolica fuera de
refs/. Ej:git symbolic-ref HEAD testno funcionara.
Las etiquetas funcionan de forma muy parecida a los commits, pero apuntan a un objeto de etiqueta en lugar de directamente a un commit. El objeto etiqueta contiene un marcador, una fecha, un mensaje y un enlace al commit al que apunta. Es como una referencia permanente que siempre apunta al mismo commit.
Como vimos anteriormente en el EXTRA 01, hay dos tipos: anotadas y ligeras. Para crear una etiqueta ligera:
git update-ref refs/tags/v1.0 cac0cab538b970a346d349e0d0e4196f5f3b1955
Para crear una etiqueta anotada, Git crea un objeto etiqueta y luego escribe una referencia apuntando al objeto en lugar de directamente al commit.
Puedes comprobarlo con -a:
git tag -a v1.1 1a410efbd13591db07496d4f82dfc14aad14979 -m "test tag"
El checksum SHA-1 del objeto creado:
cat .git/refs/tags/v1.1
9585191f37f7b0fb9444511119c5069a8c2e6f28
Si ejecutas git cat-file sobre ese checksum SHA-1,
veras que el objeto apunta a su vez al checksum SHA-1 del commit etiquetado:
git cat-file -p 9585191f37f7b0fb9444511119c5069a8c2e6f28
object 1a410efbd13591db07496d4f82dfc14aad14979
type commit
tag v1.1
tagger Scott Chacon <schacon@gmail.com> Mon Mar 17 21:52:11 2008
test tag
El objeto etiqueta no necesita apuntar necesariamente a un commit, puedes etiquetar cualquier tipo de objeto Git.
El tercer tipo de referencia que puedes observar es la referencia a un remoto.
Si añades un remoto y envias datos a el, Git almacenara en .git/refs/remotes/<nombre> el ultimo valor para cada rama presente en ese remoto.
Por ejemplo, si añades un remoto denominado origin y envias la rama master:
git remote add origin git@github.com:schacon/simplegit-progit.git
git push origin master
Puedes confirmar cual es la rama master en el remoto origin revisando el archivo .git/refs/remotes/origin/master.
Las referencias a remotos son distintas de las ramas normales: Git las trata como de solo lectura.
Puedes hacer git checkout a una, pero Git no actualizara HEAD hacia ella, de modo que nunca la actualizaras con un git commit.
Actuan simplemente como marcadores del ultimo estado conocido de cada rama en cada servidor remoto declarado.
tar -xzf 05-referencias-head-tags.tar.gzcd 05-referencias-head-tags/code .) y lee el README.mdGit Internals
PackfilesEn este momento, el repositorio de pruebas tiene 11 objetos: 4 blobs, 3 trees, 3 commits y 1 etiqueta.
find .git/objects -type f
.git/objects/01/55eb4229851634a0f03eb265b69f5a2d56f341 # tree 2
.git/objects/1a/410efbd13591db07496601ebc7a059dd55cfe9 # commit 3
.git/objects/1f/7a7a472abf3dd9643fd615f6da379c4acb3e3a # test.txt v2
.git/objects/3c/4e9cd789d88d8d89c1073707c3585e41b0e614 # tree 3
.git/objects/83/baae61804e65cc73a7201a7252750c76066a30 # test.txt v1
.git/objects/95/85191f37f7b0fb9444f35a9bf50de191beadc2 # tag
.git/objects/ca/c0cab538b970a37ea1e769cbbde608743bc96d # commit 2
.git/objects/d6/70460b4b4aece5915caf5c68d12f560a9fe3e4 # 'test content'
.git/objects/d8/329fc1cc938780ffdd9f94e0d364e0ea74f579 # tree 1
.git/objects/fa/49b077972391ad58037050f2a75f74e3671e92 # new.txt
.git/objects/fd/f4fc3344e67ab068f836878b6c4951e3b15f3d # commit 1
Git comprime todos esos objetos con zlib, por lo que ocupan muy poco. Sin embargo, entre todos suman solamente 925 bytes.
Para demostrar lo interesante del formato, añadiremos el archivo repo.rb de la libreria Git, un archivo de codigo fuente de unos 22KB:
curl https://raw.githubusercontent.com/mojombo/grit/master/lib/grit/repo.rb > repo.rb # Descargar repo.rb
git add repo.rb
git commit -m 'added repo.rb'
Si revisas el tree resultante, podras observar el valor checksum SHA-1 del objeto blob correspondiente a repo.rb:
git cat-file -p master^{tree}
100644 blob fa49b077972391ad58037050f2a75f74e3671e92 new.txt
100644 blob 033b4468fa6b2a9547a70d88d1bbe0e4f3c6b1b4 repo.rb
100644 blob a3f4d07325967247213ae12ccd6933e966fb5fb5 text.txt
Usemos cat-file para ver como de grande es:
git cat-file -s 033b4468fa6b2a9547a70d88d1bbe8bf3f9ed0d5
22044
Ahora modifica un poco el archivo y comprueba lo que sucede:
echo '# testing' >> repo.rb
git commit -am 'modified repo a bit'
Revisando el tree creado por este ultimo commit, veras algo interesante: el objeto blob es ahora completamente diferente.
git cat-file -p master^{tree}
100644 blob fa49b077972391ad58037050f2a75f74e3671e92 new.txt
100644 blob b042a60ef7dff760008df33cee372b945b6e884e repo.rb # Antes empezaba con 033b44
100644 blob e3f094f522629ae358806b17daf78246c27c007b test.txt
Aunque solo has añadido una linea al final de un archivo que ya contenia 400 lineas, Git ha almacenado el resultado como un objeto completamente nuevo que ocupa 22KB.
git cat-file -s b042a60ef7dff760008df33cee372b945b6e884e
22054
Hasta ahora, Git ha estado almacenando cada version completa del archivo como un objeto independiente.
Esto se conoce como formato relajado (loose). En disco tienes dos versiones de repo.rb practicamente identicas ocupando 22KB cada una.
¿No seria practico si Git pudiera almacenar uno de ellos completo y luego solo las diferencias del segundo con respecto al primero?
Git puede hacerlo. El formato inicial es el formato relajado, pero Git suele agrupar varios de estos objetos en un unico archivo binario denominado empaquetador (packfile) para ahorrar espacio y hacer mas eficiente su almacenamiento.
Esto sucede cada vez que tienes demasiados objetos en formato relajado, cuando lo llamas manualmente con git gc, o antes de enviar cualquier cosa a un servidor remoto:
git gc # garbage collector
Counting objects: 18, done.
Delta compression using up to 8 threads.
Compressing objects: 100% (14/14), done.
Writing objects: 100% (18/18), done.
Total 18 (delta 3), reused 0 (delta 0)
Tambien podemos ejecutar
git gcen cualquier momento que nosotros queramos.
Tras esto, si miras los objetos presentes en la carpeta, veras que han desaparecido la mayoria de los que habia antes y han aparecido un par de objetos nuevos:
find .git/objects -type f
.git/objects/bd/9dbf5ae1a885ea15267232646202b5ae5fe37
.git/objects/d6/704ab6b4e5c015af25cfcd0812f550a9fa3e4
.git/objects/info/packs
.git/objects/pack/pack-978e03765c4f561011e6998c8de9e30080905b8b.idx
.git/objects/pack/pack-978e03765c4f561011e6998c8de9e30080905b8b.pack
Los objetos que permanecen sin empaquetar son aquellos no referenciados por ningun commit: en este caso, los ejemplos de prueba que creamos sueltos anteriormente.
El nuevo archivo empaquetador contiene todos los objetos eliminados del sistema de archivo. El indice es un archivo que contiene las posiciones de cada uno de esos objetos dentro del empaquetador, permitiendonos buscarlos rapidamente.
Lo interesante es que aunque los objetos originales ocupaban 22KB cada uno, el nuevo archivo empaquetador ocupa apenas 7KB. Empaquetando los objetos, has conseguido reducir a la mitad el uso de disco.
Puedes comprobarlo mirando en el interior del archivo empaquetador con el comando de fontaneria git verify-pack:
git verify-pack -v .git/objects/pack/pack-978e03944f5c581011e6998cd0e9e30000905586.idx
2431da676938450a4d72e260db3bf7b0f587bbc1 commit 223 155 12
69bcdaff5328278ab1c0812ce0e07fa7d26a96d7 commit 214 152 167
80d02664cb23ed55b226516648c7ad5d0a3deb90 commit 214 145 319
...
...
d982c7cb2c2a972ee391a85da481fc1f9127a01d tree 6 17 1314 1 \
deef2e1b793907545e50a2ea2ddb5ba6c58c4506
3c4e9cd789d88d8d89c1073707c3585e41b0e614 tree 8 19 1331 1 \
deef2e1b793907545e50a2ea2ddb5ba6c58c4506
0155eb4229851634a0f03eb265b69f5a2d56f341 tree 71 76 1350
83baae61804e65cc73a7201a7252750c76066a30 blob 10 19 1426
fa49b077972391ad58037050f2a75f74e3671e92 blob 9 18 1445
b042a60ef7dff760008df33cee372b945b6e884e blob 22054 5799 1463 # <- version 2
033b4468fa6b2a9547a70d88d1bbe8bf3f9ed0d5 blob 9 20 7262 1 \ # <- version 1
b042a60ef7dff760008df33cee372b945b6e884e # referencia a la version 2
1f7a7a472abf3dd9643fd615f6da379c4acb3e3a blob 10 19 7282
non delta: 15 objects
chain length = 1: 3 objects
.git/objects/pack/pack-978e03944f5c581011e6998cd0e9e30000905586.pack: ok
El objeto blob 033b4 (la primera version de repo.rb), tiene una referencia al blob b042a (la segunda version).
La 3ra columna refleja el tamaño del objeto dentro del paquete, podemos ver que b042a ocupa 22KB, pero 033b4 solo 9B.
Esto se debe a que lo mas probable es que queramos recuperar la version mas reciente del archivo, entonces Git se anticipa a esto usando la version sin diferencia para el mas reciente.
Entrar al kahoot y esperar el codigo de la sala, o presionar la opcion “Aprende” para responder de forma individual.