¿Cómo asignar un Git SHA1 a un archivo sin Git?

138

Según tengo entendido, cuando Git asigna un hash SHA1 a un archivo, este SHA1 es único para el archivo en función de su contenido.

Como resultado, si un archivo se mueve de un repositorio a otro, el SHA1 para el archivo permanece igual ya que su contenido no ha cambiado.

¿Cómo calcula Git el resumen de SHA1? ¿Lo hace en el contenido completo del archivo sin comprimir?

Me gustaría emular la asignación de SHA1 fuera de Git.

git-noob
fuente

Respuestas:

255

Así es como Git calcula el SHA1 para un archivo (o, en términos de Git, un "blob"):

sha1("blob " + filesize + "\0" + data)

Por lo tanto, puede calcularlo usted mismo sin tener que instalar Git. Tenga en cuenta que "\ 0" es el byte NULL, no una cadena de dos caracteres.

Por ejemplo, el hash de un archivo vacío:

sha1("blob 0\0") = "e69de29bb2d1d6434b8b29ae775ad8c2e48c5391"

$ touch empty
$ git hash-object empty
e69de29bb2d1d6434b8b29ae775ad8c2e48c5391

Otro ejemplo:

sha1("blob 7\0foobar\n") = "323fae03f4606ea9991df8befbb2fca795e648fa"

$ echo "foobar" > foo.txt
$ git hash-object foo.txt 
323fae03f4606ea9991df8befbb2fca795e648fa

Aquí hay una implementación de Python:

from hashlib import sha1
def githash(data):
    s = sha1()
    s.update("blob %u\0" % len(data))
    s.update(data)
    return s.hexdigest()
Fernando Beyer
fuente
¿Esta respuesta supone Python 2? Cuando intento esto en Python 3 obtengo una TypeError: Unicode-objects must be encoded before hashingexcepción en la primera s.update()línea.
Mark Booth
3
Con python 3 necesita codificar los datos: s.update(("blob %u\0" % filesize).encode('utf-8'))para evitar el TypeError.
Mark Booth
La codificación como utf-8 funcionará, pero probablemente sea mejor construirla desde una cadena de bytes en primer lugar (la codificación utf-8 funciona porque ninguno de los caracteres unicode no son ASCII).
torek
Una cosa adicional que vale la pena mencionar es que git hash-object también parece reemplazar "\ r \ n" con "\ n" en el contenido de los datos. Es muy posible que elimine los "\ r" por completo, no lo comprobé.
user420667
1
Puse una implementación de Python 2 + 3 (ambos en uno) de un archivo y generador de hash de árbol aquí: github.com/chris3torek/scripts/blob/master/githash.py (el hasher del árbol lee un árbol de directorios).
torek
17

Un pequeño regalo: con cáscara

echo -en "blob ${#CONTENTS}\0$CONTENTS" | sha1sum
knittl
fuente
1
Me estoy comparando echo -en "blob ${#CONTENTS}\0$CONTENTS" | sha1sumcon la salida de git hash-object path-to-filey producen resultados diferentes. Sin embargo, echo -e ...produce los resultados correctos, excepto que hay un final - ( nogit hash-object produce caracteres finales). ¿Es esto algo de lo que debería preocuparme?
FrustratedWithFormsDesigner
2
@FrustratedWithFormsDesigner: el seguimiento -se usa sha1sumsi calcula el hash desde stdin y no desde un archivo. Nada de que preocuparse. Sin embargo, hay algo extraño en el -n, que debería suprimir la nueva línea normalmente agregada por echo. ¿Su archivo tiene una última línea vacía, que olvidó agregar en su CONTENTSvariable?
knittl
Si, estas en lo correcto. Y pensé que la salida de sha1sum solo debería ser el hash, pero no es difícil eliminarlo con sed o algo así.
FrustratedWithFormsDesigner
@FrustratedWithFormsDesigner: obtendrá el mismo resultado si lo usa en cat file | sha1sumlugar de sha1sum file(aunque más procesos y tuberías)
knittl
8

Puede hacer que bash shell funcione para calcularlo con bastante facilidad si no tiene instalado git.

git_id () { printf 'blob %s\0' "$(ls -l "$1" | awk '{print $5;}')" | cat - "$1" | sha1sum | awk '{print $1}'; }
CB Bailey
fuente
1
Un poco más corto: (stat --printf="blob %s\0" "$1"; cat "$1") | sha1sum -b | cut -d" " -f1.
sschuberth
4

Eche un vistazo a la página de manual de git-hash-object . Puede usarlo para calcular el hash git de cualquier archivo en particular. Yo creo que alimenta git algo más que el contenido del archivo en el algoritmo de hash, pero no sé a ciencia cierta, y si no se alimentan de datos adicionales, no sé lo que es.

Dale Hagglund
fuente
2
/// Calculates the SHA1 for a given string
let calcSHA1 (text:string) =
    text 
      |> System.Text.Encoding.ASCII.GetBytes
      |> (new System.Security.Cryptography.SHA1CryptoServiceProvider()).ComputeHash
      |> Array.fold (fun acc e -> 
           let t = System.Convert.ToString(e, 16)
           if t.Length = 1 then acc + "0" + t else acc + t) 
           ""
/// Calculates the SHA1 like git
let calcGitSHA1 (text:string) =
    let s = text.Replace("\r\n","\n")
    sprintf "blob %d%c%s" (s.Length) (char 0) s
      |> calcSHA1

Esta es una solución en F #.

forki23
fuente
Todavía tengo problemas con las diéresis: calcGitSHA1 ("ü"). ShouldBeEqualTo ("0f0f3e3b1ff2bc6722afc3e3812e6b782683896f") Pero mi función da 0d758c9c7bc06c1e307f05d92d896aaf0a8 ¿Alguna idea de cómo git hash-object maneja las diéresis?
forki23
debería manejar el blob como bytestream, lo que significa que ü probablemente tiene una longitud 2 (unicode), la propiedad Longitud de F♯ devolverá la longitud 1 (porque es solo un carácter visible)
knittl
Pero System.Text.Encoding.ASCII.GetBytes ("ü") devuelve una matriz de bytes con 1 elemento.
forki23
El uso de UTF8 y 2 como longitud de cadena da una matriz de bytes: [98; 108; 111; 98; 32; 50; 0; 195; 188] y por lo tanto un SHA1 de 99fe40df261f7d4afd1391fe2739b2c7466fe968. Que tampoco es el git SHA1.
forki23
1
Nunca debe aplicar resúmenes a cadenas de caracteres. En su lugar, debe aplicarlos a cadenas de bytes (matrices de bytes) que puede obtener al convertir una cadena de caracteres a bytes utilizando una codificación explícita.
dolmen
2

Implementación completa de Python3:

import os
from hashlib import sha1

def hashfile(filepath):
    filesize_bytes = os.path.getsize(filepath)

    s = sha1()
    s.update(b"blob %u\0" % filesize_bytes)

    with open(filepath, 'rb') as f:
        s.update(f.read())

    return s.hexdigest() 
Tomer
fuente
2
Lo que realmente quieres es la codificación ASCII. UTF8 solo funciona aquí porque es compatible con ASCII y "blob x \ 0" solo contiene caracteres con el código <= 127.
Ferdinand Beyer
1

En perl:

#!/usr/bin/env perl
use Digest::SHA1;

my $content = do { local $/ = undef; <> };
print Digest::SHA1->new->add('blob '.length($content)."\0".$content)->hexdigest(), "\n";

Como un comando de shell:

perl -MDigest::SHA1 -E '$/=undef;$_=<>;say Digest::SHA1->new->add("blob ".length()."\0".$_)->hexdigest' < file
dolmen
fuente
1

Y en Perl (ver también Git :: PurePerl en http://search.cpan.org/dist/Git-PurePerl/ )

use strict;
use warnings;
use Digest::SHA1;

my @input = &lt;&gt;;

my $content = join("", @input);

my $git_blob = 'blob' . ' ' . length($content) . "\0" . $content;

my $sha1 = Digest::SHA1->new();

$sha1->add($git_blob);

print $sha1->hexdigest();
Alec el friki
fuente
1

Usando Ruby, podrías hacer algo como esto:

require 'digest/sha1'

def git_hash(file)
  data = File.read(file)
  size = data.bytesize.to_s
  Digest::SHA1.hexdigest('blob ' + size + "\0" + data)
end
Leif
fuente
1

Un pequeño script de Bash que debería producir resultados idénticos para git hash-object:

#!/bin/sh
( 
    echo -en 'blob '"$(stat -c%s "$1")"'\0';
    cat "$1" 
) | sha1sum | cut -d\  -f 1
Fordi
fuente
0

En JavaScript

const crypto = require('crypto')
const bytes = require('utf8-bytes')

function sha1(data) {
    const shasum = crypto.createHash('sha1')
    shasum.update(data)
    return shasum.digest('hex')
}

function shaGit(data) {
    const total_bytes = bytes(data).length
    return sha1(`blob ${total_bytes}\0${data}`)
}
EnZo
fuente
-4

Es interesante notar que, obviamente, Git agrega un carácter de nueva línea al final de los datos antes de que se mezcle. Un archivo que no contiene nada más que "Hello World!" obtiene un hash blob de 980a0d5 ..., que es el mismo que este:

$ php -r 'echo sha1("blob 13" . chr(0) . "Hello World!\n") , PHP_EOL;'
Empujar
fuente
44
Esa nueva línea está siendo agregada por su editor de texto, no por git hash-object. Tenga en cuenta que hacer echo "Hello World!" | git hash-object --stdinda 980a0d5..., mientras que el uso echo -nda un hash de en su c57eff5...lugar.
bdesham