Comment git calcule-t-il les hachages de fichiers?

124

Les hachages SHA1 stockés dans les objets de l'arborescence (tels que renvoyés par git ls-tree) ne correspondent pas aux hachages SHA1 du contenu du fichier (tels que renvoyés par sha1sum)

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

Comment git calcule-t-il les hachages de fichiers? Compresse-t-il le contenu avant de calculer le hachage?

netvope
la source
1
Pour plus de détails, voir également progit.org/book/ch9-2.html
netvope
5
Le lien de netvope semble désormais mort. Je pense que c'est le nouvel emplacement: git-scm.com/book/en/Git-Internals-Git-Objects qui est §9.2 de git-scm.com/book
Rhubbarb

Réponses:

122

Git préfixe l'objet avec "blob", suivi de la longueur (comme un entier lisible par l'homme), suivi d'un caractère NUL

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

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

Leif Gruenwoldt
la source
2
Il convient également de mentionner qu'il remplace "\ r \ n" par "\ n", mais laisse les "\ r" isolés seuls.
user420667
8
^ correction du commentaire ci-dessus: parfois git effectue le remplacement ci-dessus, en fonction de ses paramètres eol / autocrlf.
user420667
5
Vous pouvez également comparer cela à la sortie de echo 'Hello, World!' | git hash-object --stdin. Vous pouvez éventuellement spécifier --no-filterspour vous assurer qu'aucune conversion crlf ne se produit, ou spécifier --path=somethi.ngde laisser git utiliser le filtre spécifié via gitattributes(également @ user420667). Et -wpour soumettre réellement le blob .git/objects(si vous êtes dans un dépôt git).
Tobias Kienzler
Exprimer l'équivalence, pour avoir un sens: echo -e 'blob 16\0Hello, \r\nWorld!' | shasum == echo -e 'Hello, \r\nWorld!' | git hash-object --stdin --no-filters et il sera également équivalent avec \net 15.
Peter Krauss
1
echoajoute une nouvelle ligne à la sortie, qui est également passée dans git. C'est pourquoi ses 14 personnages. Pour utiliser l'écho sans nouvelle ligne, écrivezecho -n 'Hello, World!'
Bouke Versteegh
36

Je ne fais que développer la réponse @Leif Gruenwoldtet détailler ce qui se trouve dans la référence fournie par@Leif Gruenwoldt

Fais le toi-même..

  • Étape 1. Créez un document texte vide (le nom n'a pas d'importance) dans votre référentiel
  • Étape 2. Mettre en scène et valider le document
  • Étape 3. Identifiez le hachage de l'objet blob en exécutant git ls-tree HEAD
  • Étape 4. Recherchez le hachage de l'objet blob e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
  • Étape 5. Sortez de votre surprise et lisez ci-dessous

Comment GIT calcule ses hachages de commit

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

Le texte blob⎵est un préfixe constant et \0est également constant et est le NULLcaractère. Les <size_of_file>et <contents_of_file>varient en fonction du fichier.

Voir: Quel est le format de fichier d'un objet git commit?

Et c'est tout le monde!

Mais attendez! , avez-vous remarqué que le <filename>n'est pas un paramètre utilisé pour le calcul de hachage? Deux fichiers peuvent potentiellement avoir le même hachage si leur contenu est le même indépendamment de la date et de l'heure de création et de leur nom. C'est l'une des raisons pour lesquelles Git gère mieux les déplacements et les renomme que les autres systèmes de contrôle de version.

Faites-le vous-même (Ext)

  • Étape 6. Créez un autre fichier vide avec un autre filenamedans le même répertoire
  • Étape 7. Comparez les hachages de vos deux fichiers.

Remarque:

Le lien ne mentionne pas comment l' treeobjet est haché. Je ne suis pas certain de l'algorithme et des paramètres, mais d'après mon observation, il calcule probablement un hachage basé sur tous les blobset trees(leurs hachages probablement) qu'il contient

Lordbalmon
la source
SHA1("blob" + <size_of_file>- y a-t-il un espace supplémentaire entre le blob et la taille? La taille est-elle décimale? Est-il préfixé par zéro?
osgx
1
@osgx Il y en a. La référence et mes tests le confirment. J'ai corrigé la réponse. La taille semble être le nombre d'octets sous forme d'entier sans préfixe.
Samuel Harmer
13

git hash-object

C'est un moyen rapide de vérifier votre méthode de test:

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

Production:

f2ba8f84ab5c1bce84a7b441cb1959cfc7093b7f
f2ba8f84ab5c1bce84a7b441cb1959cfc7093b7f  -

sha1sumest dans GNU Coreutils.

Ensuite, il s'agit de comprendre le format de chaque type d'objet. Nous avons déjà couvert le trivial blob, voici les autres:

Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
la source
Comme mentionné dans une réponse précédente, la longueur devrait plutôt être calculée comme $(printf "\0$s" | wc -c). Notez le caractère vide ajouté. Autrement dit, si la chaîne est 'abc' avec le caractère vide ajouté devant, la longueur donnerait 4, pas 3. Ensuite, les résultats avec sha1sum correspondent à git hash-object.
Michael Ekoka
Vous avez raison, ils correspondent. Il semble qu'il y ait un effet secondaire pernicieux à utiliser printf plutôt que echo -e ici. Lorsque vous appliquez git hash-object à un fichier contenant la chaîne 'abc', vous obtenez 8baef1b ... f903 qui est ce que vous obtenez en utilisant echo -e plutôt que printf. A condition que echo -e ajoute une nouvelle ligne à la fin d'une chaîne, il semble que pour faire correspondre le comportement avec printf, vous pouvez faire de même (c'est-à-dire s = "$ s \ n").
Michael Ekoka
3

Basé sur la réponse de Leif Gruenwoldt , voici un substitut de la fonction shell à 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/ .*$//'
}

Tester:

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

J'en avais besoin pour certains tests unitaires dans Python 3, alors j'ai pensé le laisser ici.

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()

Je m'en tiens aux \nfins de ligne partout, mais dans certaines circonstances, Git peut également changer vos fins de ligne avant de calculer ce hachage, vous pouvez donc en avoir besoin .replace('\r\n', '\n').

Samuel Harmer
la source