Git Internals
Objetos y FontaneriaGit es fundamentalmente un sistema de archivos de contenido localizable con una interfaz de usuario de VCS escrita sobre el.
El libro Git Pro ha hablado de como utilizar Git con unos 30 verbos, tales como checkout, branch, remote, etc.
Pero debido al origen de Git como una caja de herramientas para un VCS en lugar de como un sistema completo y amigable,
existen varios verbos que realizan tareas de bajo nivel y que fueron disenados para ser utilizados de forma encadenada al estilo UNIX o para ser utilizados en scripts.
Estos comandos son conocidos como los comandos de fontaneria (plumbing),
mientras que los comandos mas amigables son conocidos como los comandos de porcelana (porcelain).
Los primeros nueve capitulos del libro Git Pro tratan casi exclusivamente los comandos de porcelana. En las clases de Git Internals trataremos los comandos de fontaneria, que te daran acceso a los recursos internos de Git y te ayudaran a comprender como y por que Git hace lo que hace. Son mas utiles como bloques de construccion para nuevas herramientas y scripts personalizados.
.git/Cuando ejecutas git init sobre una carpeta, Git crea la carpeta auxiliar .git/.
Esta es la carpeta donde se ubica practicamente todo lo almacenado y manipulado por Git.
Si deseas hacer una copia de seguridad de tu repositorio, con solo copiar esta carpeta ya tienes tu copia completa.
ls -F1
HEAD
config*
description
hooks/
info/
objects/
refs/
description — usado solo en GitWeb, no necesitas preocuparte por el.config — contiene las opciones de configuracion especificas del proyecto.info/ — guarda un archivo global de exclusion con patrones a ignorar ademas de los presentes en .gitignore.hooks/ — contiene los scripts de enganche, tanto del lado cliente como del servidor.Las cuatro entradas importantes son:
HEAD — apunta a la rama que tengas activa en este momento.index — donde Git almacena la informacion sobre tu area de preparacion (staging area).objects/ — guarda el contenido de tu base de datos.refs/ — guarda las referencias a las confirmaciones (commits).Estas cuatro entradas forman el nucleo de Git. Las iremos viendo en detalle a lo largo de estas clases.
Git es un sistema de archivos de contenido localizable. Lo que significa que en el nucleo de Git hay un simple almacen de datos clave-valor. Puedes insertar cualquier tipo de contenido en el, y te devolvera una clave que puedes usar para recuperar ese contenido cuando quieras.
Para ver esto en accion, usaremos el comando de fontaneria git hash-object,
que toma un contenido, lo almacena en la carpeta .git y devuelve la clave unica con la que fue guardado:
echo 'test content' | git hash-object -w --stdin
d670460b4b4aece5915caf5c68d12f560a9fe3e4
La opcion -w le indica que almacene el objeto, sin ella solo calcularia la clave.
La opcion --stdin le indica que lea el contenido desde la entrada estandar.
El valor devuelto es un checksum SHA-1 de 40 caracteres. Puedes ver el archivo creado en:
find .git/objects -type f
.git/objects/d6/70460b4b4aece5915caf5c68d12f560a9fe3e4
Git usa los dos primeros caracteres como nombre de carpeta y los 38 restantes como nombre de archivo.
Para recuperar el contenido, usas git cat-file con la opcion -p (pretty-print):
git cat-file -p d670460b4b4aece5915caf5c68d12f560a9fe3e4
test content
Tambien puedes preguntar por el tipo del objeto con -t:
git cat-file -t d670460b4b4aece5915caf5c68d12f560a9fe3e4
blob
Esto es un objeto de tipo blob (binary large object): el tipo que Git usa para almacenar el contenido de los archivos. Los blobs almacenan solo el contenido, sin nombre de archivo ni metadata.
Los objetos tree resuelven el problema del nombre de archivo y permiten almacenar grupos de archivos juntos. Un tree es similar a un directorio en UNIX: puede contener referencias a blobs u otros trees.
git cat-file -p master^{tree}
100644 blob a906cb2a4a904a152e80877d4088654daad0c859 README
100644 blob 8f94139338f9404f26296befa88755fc2598c289 Rakefile
040000 tree 99f1a6d12cb4b6f19c8655fca46c3ecf317074e0 libCada linea tiene: modo del archivo, tipo de objeto, SHA-1, y nombre.
La sentencia master^{tree} indica el objeto tree apuntado por el ultimo commit de master.
El modo indica los permisos y tipo de objeto UNIX.
Por ejemplo:
100644: Archivo normal no ejecutable con permisos rw-r--r--.040000: Directorio/carpeta.100755: Archivo ejecutable.120000: Enlace simbolico.Git crea un tree a partir del estado de tu area de preparacion (staging area) o indice (index).
Para crear un tree manualmente, podemos usar update-index actualiza al area de preparacion para los archivos con seguimiento (tracked).
git update-index --add --cacheinfo 100644 \
83baae61804e65cc73a7201a7252750c76066a30 test.txt
Usamos --add porque el archivo que queremos agregar no esta bajo seguimiento (untracked).
La opcion --cacheinfo se usa cuando el archivo esta en la base de datos de Git pero no en el directorio de trabajo.
Luego especificamos el modo, checksum y nombre del archivo:
Luego podemos usar write-tree para escribir el area de preparacion a un objeto tree:
git write-tree # Escribir a un objeto tree
d8329fc1cc938780ffdd9f94e0d364e0ea74f579
# ---
git cat-file -p d8329fc1cc938780ffdd9f94e0d364e0ea74f579 # Ver el contenido del objeto
100644 blob 83baae61804e65cc73a7201a7252750c76066a30 test.txt
Cuando pedimos el contenido del objeto tree, nos devuelve el objeto blob que esta dentro de nuestro objeto tree.
Tambien podemos comprobar si realmente es un objeto tree:
git cat-file -t d8329fc1cc938780ffdd9f94e0d364e0ea74f579
tree
Ahora crearemos un nuevo arbol con una segunda version de test.txt y un nuevo archivo new.txt:
echo 'new file' > new.txt # Crear new.txt
git update-index test.txt # Actualizar el area de preparacion para test.txt
git update-index --add new.txt # Agregar new.txt al area de preparacion
Escribimos el area de preparacion a un objeto tree:
git write-tree
0155eb4229851634a0f03eb265b69f5a2d56f341
Comprobamos su contenido:
git cat-file -p 0155eb4229851634a0f03eb265b69f5a2d56f341
100644 blob fa49b077972391ad58037050f2a75f74e3671e92 new.txt
100644 blob 1f7a7a472abf3dd9643fd615f6da379c4acb3e3a test.txt
Ahora simplemente por probar, incluiremos el tree del ejemplo anterior en el tree que acabamos de crear.
Usamos read-tree que agrega el tree al area de preparacion, y la opcion --prefix para tratarlo como si fuera una subcarpeta.
En la opcion --prefix indicamos el nombre del subdirectorio y el checksum del tree.
git read-tree --prefix=bak d8329fc1cc938780ffdd9f94e0d364e0ea74f579 # Agregar el tree bajo el nombre de "bak"
git write-tree # Escribir el area de preparacion a un arbol
3c4e9cd789d88d8d89c1073707c3585e41b0e614
Ahora comprobemos que tiene dentro nuestro nuevo objeto tree:
git cat-file -p 3c4e9cd789d88d8d89c1073707c3585e41b0e614
040000 tree d8329fc1cc938780ffdd9f94e0d364e0ea74f579 bak
100644 blob fa49b077972391ad58037050f2a75f74e3671e92 new.txt
100644 blob 1f7a7a472abf3dd9643fd615f6737e589b58f5f8 test.txt
Imaginemos que tienes tres trees que representan diferentes snapshots del proyecto, pero el problema sigue siendo el mismo: debes recordar los tres valores checksum SHA-1 para poder acceder a ellas. Tampoco tienes informacion sobre quien guardo las snapshots, cuando, o por que. Para eso existen los objetos commit.
Para crear un objeto commit manualmente, usas git commit-tree pasando el checksum SHA-1 del tree y, opcionalmente, el commit padre:
echo 'first commit' | git commit-tree d8329f
fdf4fc3344e67ab068f836878b6c4951e3b15f3d
Si inspeccionas el objeto:
git cat-file -p fdf4fc3
tree d8329fc1cc938780ffdd9f94e0d364e0ea74f579
author Scott Chacon <schacon@gmail.com> 1243040974 -0700
committer Scott Chacon <schacon@gmail.com> 1243040974 -0700
#
first commit
Un objeto commit contiene:
Puedes encadenar commits pasando el checksum SHA-1 del anterior como padre:
echo 'second commit' | git commit-tree 0155eb -p fdf4fc3
cac0cab538b970a37ea1e769cbbde608743bc96d
echo 'third commit' | git commit-tree 3c4e9c -p cac0cab
1a410efbd13591db07496601ebc7a059dd55cfe9
Ahora si ejecutas git log sobre el ultimo checksum SHA-1, veras un historial real:
git log --stat 1a410e
commit 1a410efbd13591db07496601ebc7a059dd55cfe9
Author: Scott Chacon <schacon@gmail.com>
Date: Fri May 22 18:15:24 2009 -0700
#
third commit
#
bak/test.txt | 1 +
1 file changed, 1 insertion(+)
#
commit cac0cab538b970a37ea1e769cbbde608743bc96d
Author: Scott Chacon <schacon@gmail.com>
Date: Fri May 22 18:14:29 2009 -0700
#
second commit
#
new.txt | 1 +
test.txt | 2 +-
2 files changed, 2 insertions(+), 1 deletion(-)
#
commit fdf4fc3344e67ab068f836878b6c4951e3b15f3d
Author: Scott Chacon <schacon@gmail.com>
Date: Fri May 22 18:09:34 2009 -0700
#
first commit
#
test.txt | 1 +
1 file changed, 1 insertion(+)
Esto es exactamente lo que Git hace cuando ejecutas git add y git commit:
almacena blobs de los archivos modificados, actualiza el area de preparacion, escribe los trees,
y crea los objetos commit con los checksums SHA-1 de los trees de nivel superior y los commits padre.
Ahora comprobemos que todos los objetos blob, tree y commit hasta el momento fueron guardados por Git en .git/objects:
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/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
Hasta ahora deberiamos tener algo como esto:
Internamente, Git construye una cabecera comenzando por el tipo de objeto, seguido del tamano del contenido y un byte nulo.
content = "what is up, doc?"
header = f"blob {len(content)}\x00"
Luego concatena la cabecera con el contenido original para calcular el checksum SHA-1, comprime todo con zlib,
store = header + content
sha1 = checksum(store)
zlib_content = zlib.compress(store)
y escribe el resultado en disco usando los dos primeros caracteres del checksum SHA-1 como carpeta y los 38 restantes como nombre de archivo.
path = ".git/objects" + sha1[0:2] + "/" + sha1[2:38]
with open(path) as file: file.write(zlib_content)
Todos los objetos Git (blob, tree, commit, tag) se almacenan de la misma forma. La unica diferencia es que sus cabeceras comienzan con un tipo diferente:
blob,tree,commitotag.
10.1 a 10.2