Comment assigner un Git SHA1 à un fichier sans Git?


138

Si je comprends bien, lorsque Git attribue un hachage SHA1 à un fichier, ce SHA1 est unique au fichier en fonction de son contenu.

En conséquence, si un fichier passe d'un référentiel à un autre, le SHA1 du fichier reste le même car son contenu n'a pas changé.

Comment Git calcule-t-il le condensé SHA1? Le fait-il sur le contenu complet du fichier non compressé?

Je voudrais émuler l'assignation de SHA1 en dehors de Git.




Réponses:


256

Voici comment Git calcule le SHA1 pour un fichier (ou, en termes Git, un "blob"):

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

Vous pouvez donc facilement le calculer vous-même sans avoir installé Git. Notez que "\ 0" est l'octet NULL, pas une chaîne de deux caractères.

Par exemple, le hachage d'un fichier vide:

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

$ touch empty
$ git hash-object empty
e69de29bb2d1d6434b8b29ae775ad8c2e48c5391

Un autre exemple:

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

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

Voici une implémentation Python:

from hashlib import sha1
def githash(data):
    s = sha1()
    s.update("blob %u\0" % len(data))
    s.update(data)
    return s.hexdigest()

Cette réponse suppose-t-elle Python 2? Quand j'essaye ceci sur Python 3, j'obtiens une TypeError: Unicode-objects must be encoded before hashingexception sur la première s.update()ligne.
Mark Booth

3
Avec python 3, vous devez encoder les données: s.update(("blob %u\0" % filesize).encode('utf-8'))pour éviter le TypeError.
Mark Booth

L'encodage en utf-8 fonctionnera, mais il est probablement préférable de le construire simplement à partir d'une chaîne d'octets en premier lieu (l'encodage utf-8 fonctionne car aucun des caractères Unicode n'est non ASCII).
torek le

Une chose supplémentaire à mentionner est que git hash-object semble aussi remplacer "\ r \ n" par "\ n" dans le contenu des données. Cela pourrait très bien dépouiller entièrement les "\ r", je n'ai pas vérifié cela.
user420667

1
J'ai mis une implémentation Python 2 + 3 (les deux en un) d'un générateur de hachage de fichier et d' arbre ici: github.com/chris3torek/scripts/blob/master/githash.py (le hacheur d' arbre lit une arborescence de répertoires).
torek

17

Un petit goodie: en coquille

echo -en "blob ${#CONTENTS}\0$CONTENTS" | sha1sum

1
Je compare echo -en "blob ${#CONTENTS}\0$CONTENTS" | sha1sumà la sortie de git hash-object path-to-fileet ils produisent des résultats différents. Cependant, echo -e ...produit les résultats corrects, sauf qu'il y a une fin - ( negit hash-object produit aucun caractère de fin). Est-ce quelque chose dont je devrais m'inquiéter?
FrustratedWithFormsDesigner

2
@FrustratedWithFormsDesigner: La fin -est utilisée par sha1sumsi elle a calculé le hachage à partir de stdin et non à partir d'un fichier. Rien à craindre. Chose étrange à propos du -n, qui devrait supprimer la nouvelle ligne normalement ajoutée par echo. Votre fichier a-t-il par hasard une dernière ligne vide, que vous avez oublié d'ajouter dans votre CONTENTSvariable?
knittl

Oui, vous avez raison. Et je pensais que la sortie de sha1sum doit seulement être le hachage, mais il est pas difficile de l' enlever avec sed ou quelque chose.
FrustratedWithFormsDesigner

@FrustratedWithFormsDesigner: Vous obtiendrez le même résultat si vous utilisez à la cat file | sha1sumplace de sha1sum file(plus de processus et de tuyauterie cependant)
knittl

9

Vous pouvez créer une fonction shell bash pour le calculer assez facilement si vous n'avez pas installé git.

git_id () { printf 'blob %s\0' "$(ls -l "$1" | awk '{print $5;}')" | cat - "$1" | sha1sum | awk '{print $1}'; }

1
Un peu plus court: (stat --printf="blob %s\0" "$1"; cat "$1") | sha1sum -b | cut -d" " -f1.
sschuberth

4

Jetez un œil à la page de manuel de git-hash-object . Vous pouvez l'utiliser pour calculer le hachage git de n'importe quel fichier particulier. Je pense que git alimente plus que le contenu du fichier dans l'algorithme de hachage, mais je ne sais pas avec certitude, et s'il alimente des données supplémentaires, je ne sais pas ce que c'est.


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

C'est une solution en F #.


J'ai toujours des problèmes avec les trémas: calcGitSHA1 ("ü"). ShouldBeEqualTo ("0f0f3e3b1ff2bc6722afc3e3812e6b782683896f") Mais ma fonction donne 0d758c9c7bc06c1e307f05d92d896aaf6aaf2c. Des idées sur la façon dont git hash-object gère les trémas?
forki23

il doit gérer le blob comme un flux d'octets, ce qui signifie que ü a probablement la longueur 2 (unicode), la propriété Length de F♯ renverra la longueur 1 (car ce n'est qu'un caractère visible)
knittl

Mais System.Text.Encoding.ASCII.GetBytes ("ü") renvoie un tableau d'octets avec 1 élément.
forki23

L'utilisation de UTF8 et 2 comme longueur de chaîne donne un tableau d'octets: [98; 108; 111; 98; 32; 50; 0; 195; 188] et par conséquent un SHA1 de 99fe40df261f7d4afd1391fe2739b2c7466fe968. Ce qui n'est pas non plus le git SHA1.
forki23

1
Vous ne devez jamais appliquer de résumés aux chaînes de caractères. Au lieu de cela, vous devez les appliquer aux chaînes d'octets (tableaux d'octets) que vous pouvez obtenir en convertissant une chaîne de caractères en octets à l'aide d'un codage explicite.
dolmen

2

Implémentation complète 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() 

2
Ce que vous voulez vraiment, c'est l'encodage ASCII. UTF8 fonctionne uniquement ici car il est compatible avec ASCII et "blob x \ 0" ne contient que des caractères avec le code <= 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";

En tant que commande shell:

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

1

Et en Perl (voir aussi Git :: PurePerl sur 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();

1

En utilisant Ruby, vous pouvez faire quelque chose comme ceci:

require 'digest/sha1'

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

1

Un petit script Bash qui devrait produire une sortie identique à git hash-object:

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

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}`)
}

-4

Il est intéressant de noter qu'évidemment, Git ajoute un caractère de nouvelle ligne à la fin des données avant qu'elles ne soient hachées. Un fichier ne contenant rien que "Hello World!" obtient un hachage blob de 980a0d5 ..., identique à celui-ci:

$ php -r 'echo sha1("blob 13" . chr(0) . "Hello World!\n") , PHP_EOL;'

4
Cette nouvelle ligne est ajoutée par votre éditeur de texte, pas par git hash-object. Notez que faire echo "Hello World!" | git hash-object --stdindonne 980a0d5..., tandis que utiliser echo -ndonne un hachage de à la c57eff5...place.
bdesham
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.