¿Cómo funciona git calcular los hashes de archivos?

124

Los hash SHA1 almacenados en los objetos del árbol (tal como los devuelve git ls-tree) no coinciden con los hashes SHA1 del contenido del archivo (tal como los devuelve sha1sum)

$ git cat-file blob 4716ca912495c805b94a88ef6dc3fb4aff46bf3c | sha1sum
de20247992af0f949ae8df4fa9a37e4a03d7063e  -

¿Cómo funciona git calcular los hashes de archivos? ¿Comprime el contenido antes de calcular el hash?

netvope
fuente
1
Para más detalles, también vea progit.org/book/ch9-2.html
netvope
55
El enlace de netvope parece estar muerto ahora. Creo que esta es la nueva ubicación: git-scm.com/book/en/Git-Internals-Git-Objects que es §9.2 de git-scm.com/book
Rhubbarb

Respuestas:

122

Git prefija el objeto con "blob", seguido de la longitud (como un entero legible para humanos), seguido de un carácter NUL

$ echo -e 'blob 14\0Hello, World!' | shasum 8ab686eafeb1f44702738c8b0f24f2567c36da6d

Fuente: http://alblue.bandlem.com/2011/08/git-tip-of-week-objects.html

Leif Gruenwoldt
fuente
2
También vale la pena mencionar que reemplaza "\ r \ n" con "\ n", pero deja solo "\ r" s aislados.
user420667
8
^ corrección al comentario anterior: a veces git realiza el reemplazo anterior, dependiendo de la configuración de eol / autocrlf de uno.
user420667
55
También puede comparar esto con la salida de echo 'Hello, World!' | git hash-object --stdin. Opcionalmente, puede especificar --no-filterspara asegurarse de que no se produzca la conversión de crlf, o especificar --path=somethi.ngque deje que git use el filtro especificado a través de gitattributes(también @ user420667). Y -wpara enviar realmente el blob a .git/objects(si está en un repositorio de git).
Tobias Kienzler
Expresando la equivalencia, para que tenga sentido: echo -e 'blob 16\0Hello, \r\nWorld!' | shasum == echo -e 'Hello, \r\nWorld!' | git hash-object --stdin --no-filters y también será equivalente con \ny 15.
Peter Krauss
1
echoagrega una nueva línea a la salida, que también se pasa a git. Por eso son 14 personajes. Para utilizar eco sin un salto de línea, escrituraecho -n 'Hello, World!'
Bouke Versteegh
36

Solo estoy ampliando la respuesta @Leif Gruenwoldty detallando lo que está en la referencia proporcionada por@Leif Gruenwoldt

Hazlo tu mismo..

  • Paso 1. Cree un documento de texto vacío (el nombre no importa) en su repositorio
  • Paso 2. Etapa y comprometer el documento
  • Paso 3. Identifica el hash del blob ejecutando git ls-tree HEAD
  • Paso 4. Encuentra el hash del blob para ser e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
  • Paso 5. Sal de tu sorpresa y lee a continuación

¿Cómo calcula GIT sus hashes de confirmación?

    Commit Hash (SHA1) = SHA1("blob " + <size_of_file> + "\0" + <contents_of_file>)

El texto blob⎵es un prefijo constante y \0también es constante y es el NULLcarácter. El <size_of_file>y <contents_of_file>varían según el archivo.

Ver: ¿Cuál es el formato de archivo de un objeto git commit?

Y eso es todo amigos!

¡Pero espera! , ¿notó que <filename>no es un parámetro utilizado para el cálculo de hash? Dos archivos podrían tener el mismo hash si su contenido es igual a la fecha y hora en que se crearon y su nombre. Esta es una de las razones por las que Git maneja los movimientos y renombra mejor que otros sistemas de control de versiones.

Hágalo usted mismo (Ext)

  • Paso 6. Cree otro archivo vacío con otro diferente filenameen el mismo directorio
  • Paso 7. Compara los hash de tus dos archivos.

Nota:

El enlace no menciona cómo treese hashiza el objeto. No estoy seguro del algoritmo y los parámetros, sin embargo, según mi observación, probablemente calcula un hash basado en todo blobsy trees(sus hashes probablemente) contiene

Lordbalmon
fuente
SHA1("blob" + <size_of_file>- ¿hay espacio adicional entre blob y tamaño? ¿El tamaño es decimal? ¿Tiene el prefijo cero?
osgx
1
@osgx Hay. La referencia y mis pruebas lo confirman. He corregido la respuesta. El tamaño parece ser el número de bytes como entero sin prefijo.
Samuel Harmer
13

git hash-object

Esta es una forma rápida de verificar su método de prueba:

s='abc'
printf "$s" | git hash-object --stdin
printf "blob $(printf "$s" | wc -c)\0$s" | sha1sum

Salida:

f2ba8f84ab5c1bce84a7b441cb1959cfc7093b7f
f2ba8f84ab5c1bce84a7b441cb1959cfc7093b7f  -

donde sha1sumestá en GNU Coreutils.

Luego se trata de comprender el formato de cada tipo de objeto. Ya hemos cubierto lo trivial blob, aquí están los otros:

Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
fuente
Como se mencionó en una respuesta anterior, la longitud debería calcularse como $(printf "\0$s" | wc -c). Tenga en cuenta el carácter vacío agregado. Es decir, si la cadena es 'abc' con el carácter vacío agregado al frente, la longitud arrojará 4, no 3. Luego, los resultados con sha1sum coinciden con git hash-object.
Michael Ekoka
Tienes razón, coinciden. Parece que hay un efecto secundario pernicioso al usar printf en lugar de echo -e aquí. Cuando aplica git hash-object a un archivo que contiene la cadena 'abc', obtiene 8baef1b ... f903, que es lo que obtiene al usar echo -e en lugar de printf. Siempre que echo -e agregue una nueva línea al final de una cadena, parece que para hacer coincidir el comportamiento con printf puede hacer lo mismo (es decir, s = "$ s \ n").
Michael Ekoka
3

Basado en la respuesta de Leif Gruenwoldt , aquí hay un sustituto de la función de shell para git hash-object:

git-hash-object () { # substitute when the `git` command is not available
    local type=blob
    [ "$1" = "-t" ] && shift && type=$1 && shift
    # depending on eol/autocrlf settings, you may want to substitute CRLFs by LFs
    # by using `perl -pe 's/\r$//g'` instead of `cat` in the next 2 commands
    local size=$(cat $1 | wc -c | sed 's/ .*$//')
    ( echo -en "$type $size\0"; cat "$1" ) | sha1sum | sed 's/ .*$//'
}

Prueba:

$ echo 'Hello, World!' > test.txt
$ git hash-object test.txt
8ab686eafeb1f44702738c8b0f24f2567c36da6d
$ git-hash-object test.txt
8ab686eafeb1f44702738c8b0f24f2567c36da6d
Lucas Cimon
fuente
3

Necesitaba esto para algunas pruebas unitarias en Python 3, así que pensé en dejarlo aquí.

def git_blob_hash(data):
    if isinstance(data, str):
        data = data.encode()
    data = b'blob ' + str(len(data)).encode() + b'\0' + data
    h = hashlib.sha1()
    h.update(data)
    return h.hexdigest()

Me apego a los \nfinales de línea en todas partes, pero en algunas circunstancias Git también podría estar cambiando los finales de línea antes de calcular este hash, por lo que es posible que también necesites uno .replace('\r\n', '\n')allí.

Samuel Harmer
fuente