tipos tag remove qué oneline log hace existen etiquetas crear git git-ls-tree

tag - ¿Cuál es el formato interno de un objeto de árbol git?



tipos de etiquetas en git (5)

¿Cuál es el formato del contenido de un objeto de árbol git?

El contenido de un objeto blob es blob [size of string] NUL [string] , pero ¿qué es para un objeto de árbol?


@lemiorhan respuesta es correcta, pero se pierde pequeños detalles importantes. El formato del árbol es:

[mode] [file/folder name]/0[SHA-1 of referencing blob or tree]

Pero lo importante es que [SHA-1 of referencing blob or tree] está en forma binaria, no en hexadecimal. Este es un fragmento de Python para analizar el objeto de árbol en entradas:

entries = [ line[0:2]+(line[2].encode(''hex''),) for line in re.findall(''(/d+) (.*?)/0(.{20})'', body, re.MULTILINE) ]


Como se sugirió, Pro Git explica bien la estructura. Para mostrar un árbol bastante impreso, use:

git cat-file -p 4c975c5f5945564eae86d1e933192c4a9096bfe5

para mostrar el mismo árbol en su forma original, pero sin comprimir, use:

git cat-file tree 4c975c5f5945564eae86d1e933192c4a9096bfe5

La estructura es esencialmente la misma, con hashes almacenados como nombres de archivo binarios y terminados en nulo.


El formato de un objeto de árbol:

tree [content size]/0[Entries having references to other trees and blobs]

El formato de cada entrada que tiene referencias a otros árboles y blobs:

[mode] [file/folder name]/0[SHA-1 of referencing blob or tree]

Escribí un guión desinflando los objetos del árbol. Emite de la siguiente manera:

tree 192/0 40000 octopus-admin/0 a84943494657751ce187be401d6bf59ef7a2583c 40000 octopus-deployment/0 14f589a30cf4bd0ce2d7103aa7186abe0167427f 40000 octopus-product/0 ec559319a263bc7b476e5f01dd2578f255d734fd 100644 pom.xml/0 97e5b6b292d248869780d7b0c65834bfb645e32a 40000 src/0 6e63db37acba41266493ba8fb68c76f83f1bc9dd

El número 1 como primer carácter de un modo muestra que es una referencia a un blob / archivo. El ejemplo anterior, pom.xml es un blob y los otros son árboles.

Tenga en cuenta que agregué nuevas líneas y espacios después de /0 por el bien de la impresión bonita. Normalmente, todo el contenido no tiene nuevas líneas. También convertí 20 bytes (es decir, el SHA-1 de referencia de blobs y árboles) en una cadena hexadecimal para visualizar mejor.


Expresado como un patrón tipo BNF, un árbol git contiene datos de la forma

(?<tree> tree (?&SP) (?&decimal) /0 (?&entry)+ ) (?<entry> (?&octal) (?&SP) (?&strnull) (?&sha1bytes) ) (?<strnull> [^/0]+ /0) (?<sha1bytes> (?s: .{20})) (?<decimal> [0-9]+) (?<octal> [0-7]+) (?<SP> /x20)

Es decir, un árbol git comienza con un encabezado de

  1. el tree cadena literal
  2. ESPACIO ( es decir, el byte 0x20 )
  3. Longitud decimal codificada en ASCII de los contenidos no comprimidos

Después de un terminador NUL ( es decir , el byte 0x00 ), el árbol contiene una o más entradas del formulario

  1. Modo octal codificado en ASCII
  2. ESPACIO
  3. nombre
  4. NUL
  5. Hash SHA1 codificado como 20 bytes sin firmar

Git luego alimenta los datos del árbol al desinflado zlib’s para un almacenamiento compacto.

Recuerda que los blobs de git son anónimos. Los árboles Git asocian nombres con hashes SHA1 de otro contenido que pueden ser blobs, otros árboles, etc.

Para demostrarlo, considere el árbol asociado con la etiqueta v2.7.2 de git, que es posible que desee explorar en GitHub .

$ git rev-parse v2.7.2^{tree} 802b6758c0c27ae910f40e1b4862cb72a71eee9f

El código siguiente requiere que el objeto árbol esté en formato "suelto". No sé de una manera de extraer un solo objeto sin formato de un archivo de paquete, así que primero ejecuté git unpack-objects en los archivos del paquete desde mi clon a un nuevo repositorio. Tenga en cuenta que esto expandió un directorio .git que comenzó alrededor de 90 MB para resultar de aproximadamente 1,8 GB.

ACTUALIZACIÓN: Gracias a max630 por mostrar cómo desempaquetar un solo objeto .

#! /usr/bin/env perl use strict; use warnings; use subs qw/ git_tree_contents_pattern read_raw_tree_object /; use Compress::Zlib; my $treeobj = read_raw_tree_object; my $git_tree_contents = git_tree_contents_pattern; die "$0: invalid tree" unless $treeobj =~ /^$git_tree_contents/z/; die "$0: unexpected header" unless $treeobj =~ s/^(tree [0-9]+)/0//; print $1, "/n"; # e.g., 100644 SP .gitattributes /0 sha1-bytes while ($treeobj) { # /s is important so . matches any byte! if ($treeobj =~ s/^([0-7]+) (.+?)/0(.{20})//s) { my($mode,$name,$bytes) = (oct($1),$2,$3); printf "%06o %s %s/t%s/n", $mode, ($mode == 040000 ? "tree" : "blob"), unpack("H*", $bytes), $name; } else { die "$0: unexpected tree entry"; } } sub git_tree_contents_pattern { qr/ (?(DEFINE) (?<tree> tree (?&SP) (?&decimal) /0 (?&entry)+ ) (?<entry> (?&octal) (?&SP) (?&strnull) (?&sha1bytes) ) (?<strnull> [^/0]+ /0) (?<sha1bytes> (?s: .{20})) (?<decimal> [0-9]+) (?<octal> [0-7]+) (?<SP> /x20) ) (?&tree) /x; } sub read_raw_tree_object { # $ git rev-parse v2.7.2^{tree} # 802b6758c0c27ae910f40e1b4862cb72a71eee9f # # NOTE: extracted using git unpack-objects my $tree = ".git/objects/80/2b6758c0c27ae910f40e1b4862cb72a71eee9f"; open my $fh, "<", $tree or die "$0: open $tree: $!"; binmode $fh or die "$0: binmode: $!"; local $/; my $treeobj = uncompress <$fh>; die "$0: uncompress failed" unless defined $treeobj; $treeobj }

Mira el git ls-tree nuestro pobre hombre en acción. La salida es idéntica, excepto que produce el marcador y la longitud del tree .

$ diff -u <(cd ~/src/git; git ls-tree 802b6758c0) <(../rawtree) --- /dev/fd/63 2016-03-09 14:41:37.011791393 -0600 +++ /dev/fd/62 2016-03-09 14:41:37.011791393 -0600 @@ -1,3 +1,4 @@ +tree 15530 100644 blob 5e98806c6cc246acef5f539ae191710a0c06ad3f .gitattributes 100644 blob 1c2f8321386f89ef8c03d11159c97a0f194c4423 .gitignore 100644 blob e5b4126bec557db55924b7b60ed70349626ea2c4 .mailmap


Intento elaborar un poco más sobre @lemiorhan respuesta, por medio de un informe de prueba.

Crear un informe de prueba

Crea un proyecto de prueba en una carpeta vacía:

$ echo ciao > file1 $ mkdir folder1 $ echo hello > folder1/file2 $ echo hola > folder1/file3

Es decir:

$ find -type f ./file1 ./folder1/file2 ./folder1/file3

Crea el repositorio local de Git:

$ git init $ git add . $ git write-tree 0b6e66b04bc1448ca594f143a91ec458667f420e

El último comando devuelve el hash del árbol de nivel superior.

Lee el contenido de un árbol

Para imprimir el contenido de un árbol en formato de lectura humana, use:

$ git ls-tree 0b6e66 100644 blob 887ae9333d92a1d72400c210546e28baa1050e44 file1 040000 tree ab39965d17996be2116fe508faaf9269e903c85b folder1

En este caso, 0b6e66 son los primeros seis caracteres del árbol superior. Puedes hacer lo mismo con la folder1 .

Para obtener el mismo contenido pero en formato sin formato, use:

$ git cat-file tree 0b6e66 100644 file1 ▒z▒3=▒▒▒$ ▒►Tn(▒▒♣D40000 folder1 ▒9▒]▒k▒◄o▒▒▒i▒♥▒[%

El contenido es similar al almacenado físicamente como un archivo en formato comprimido, pero falla la cadena inicial:

tree [content size]/0

Para obtener el contenido real, necesitamos descomprimir el archivo que almacena el objeto de árbol c1f4bf . El archivo que queremos es - dado el formato de ruta 2/38 -:

.git/objects/0b/6e66b04bc1448ca594f143a91ec458667f420e

Este archivo está comprimido con zlib, por lo tanto, obtenemos su contenido con:

$ openssl zlib -d -in .git/objects/0b/6e66b04bc1448ca594f143a91ec458667f420e tree 67 100644 file1 ▒z▒3=▒▒▒$ ▒►Tn(▒▒♣D40000 folder1 ▒9▒]▒k▒◄o▒▒▒i▒♥▒[%

Aprendemos que el tamaño del contenido del árbol es 67.

Tenga en cuenta que, dado que el terminal no está hecho para imprimir binarios, podría comerse una parte de la cadena o mostrar otro comportamiento extraño. En este caso, realice los comandos anteriores con | od -c | od -c o use la solución manual en la siguiente sección.

Genere manualmente el contenido del objeto del árbol

Para comprender el proceso de generación de árboles podemos generarlo nosotros mismos a partir de su contenido legible por humanos, por ejemplo, para el árbol superior:

$ git ls-tree 0b6e66 100644 blob 887ae9333d92a1d72400c210546e28baa1050e44 file1 040000 tree ab39965d17996be2116fe508faaf9269e903c85b folder1

Cada objeto hash ASCII SHA-1 se convierte y se almacena en formato binario. Si lo que necesita es solo una versión binaria de los hashes ASCII, puede hacerlo con:

$ echo -e "$(echo ASCIIHASH | sed -e ''s/..///x&/g'')"

Por lo tanto, el blob 887ae9333d92a1d72400c210546e28baa1050e44 se convierte en

$ echo -e "$(echo 887ae9333d92a1d72400c210546e28baa1050e44 | sed -e ''s/..///x&/g'')" ▒z▒3=▒▒▒$ ▒►Tn(▒▒♣D

Si queremos crear todo el objeto de árbol, aquí hay un awk one-liner:

$ git ls-tree 0b6e66 | awk -b ''function bsha(asha)/ {patsplit(asha, x, /../); h=""; for(j in x) h=h sprintf("%c", strtonum("0x" x[j])); return(h)}/ {t=t sprintf("%d %s/0%s", $1, $4, bsha($3))} END {printf("tree %s/0%s", length(t), t)}'' tree 67 100644 file1 ▒z▒3=▒▒▒$ ▒►Tn(▒▒♣D40000 folder1 ▒9▒]▒k▒◄o▒▒▒i▒♥▒[%

La función bsha convierte los algoritmos hash SHA-1 ASCII en binarios. El contenido del árbol se coloca primero en la variable t y luego su longitud se calcula e imprime en la sección END{...} .

Como se observó anteriormente, la consola no es muy adecuada para imprimir binarios, por lo que es posible que deseemos reemplazarlos por su equivalente en formato /x## :

$ git ls-tree 0b6e66 | awk -b ''function bsha(asha)/ {patsplit(asha, x, /../); h=""; for(j in x) h=h sprintf("%s", "//x" x[j]); return(h)}/ {t=t sprintf("%d %s/0%s", $1, $4, bsha($3))} END {printf("tree %s/0%s", length(t), t)}'' tree 187 100644 file1 /x88/x7a/xe9/x33/x3d/x92/xa1/xd7/x24/x00/xc2/x10/x54/x6e/x28/xba/xa1/x05/x0e/x4440000 folder1 /xab/x39/x96/x5d/x17/x99/x6b/xe2/x11/x6f/xe5/x08/xfa/xaf/x92/x69/xe9/x03/xc8/x5b%

El resultado debe ser un buen compromiso para comprender la estructura del contenido del árbol. Compare el resultado anterior con la estructura de contenido general del árbol

tree [content size]/0[Object Entries]

donde cada entrada de objeto es como:

[mode] [Object name]/0[SHA-1 in binary format]

Los modos son un subconjunto de modos de sistema de archivos UNIX. Ver el manual Tree Objects on Git para más detalles.

Necesitamos asegurarnos de que los resultados sean consistentes. Con este fin, podríamos comparar la suma de comprobación del árbol generado por awk con la suma de comprobación del árbol almacenado de Git.

En cuanto a este último:

$ openssl zlib -d -in .git/objects/0b/6e66b04bc1448ca594f143a91ec458667f420e | shasum 0b6e66b04bc1448ca594f143a91ec458667f420e *-

En cuanto al árbol hecho en casa:

$ git ls-tree 0b6e66 | awk -b ''function bsha(asha)/ {patsplit(asha, x, /../); h=""; for(j in x) h=h sprintf("%c", strtonum("0x" x[j])); return(h)}/ {t=t sprintf("%d %s/0%s", $1, $4, bsha($3))} END {printf("tree %s/0%s", length(t), t)}'' | shasum 0b6e66b04bc1448ca594f143a91ec458667f420e *-

La suma de comprobación es la misma.

Calcula la suma de comprobación del objeto de árbol

La manera más o menos oficial de obtenerlo es:

$ git ls-tree 0b6e66 | git mktree 0b6e66b04bc1448ca594f143a91ec458667f420e

Para calcularlo manualmente, necesitamos canalizar el contenido del árbol generado por el script en el comando shasum . En realidad, ya hemos hecho esto anteriormente (para comparar el contenido generado y almacenado). El resultado fue:

0b6e66b04bc1448ca594f143a91ec458667f420e *-

y es lo mismo que con git mktree .

Objetos embalados

Puede encontrar que, para su repositorio, no puede encontrar los archivos .git/objects/XX/XXX... almacenando los objetos Git. Esto sucede porque algunos o todos los objetos "sueltos" se han empaquetado en uno o más archivos .git/objects/pack/*.pack .

Para descomprimir el repositorio, primero mueva los archivos del paquete fuera de su posición original, luego git-desempaquete los objetos.

$ mkdir .git/pcache $ mv .git/objects/pack/*.pack .git/pcache/ $ git unpack-objects < .git/pcache/*.pack

Para volver a embalar cuando haya terminado con los experimentos:

$ git gc