Wie berechnet git Datei-Hashes?

124

Die in den Baumobjekten gespeicherten SHA1-Hashes (wie von zurückgegeben git ls-tree) stimmen nicht mit den SHA1-Hashes des Dateiinhalts überein (wie von zurückgegeben sha1sum)

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

Wie berechnet git Datei-Hashes? Komprimiert es den Inhalt, bevor der Hash berechnet wird?

netvope
quelle
1
Weitere Informationen finden Sie auch unter progit.org/book/ch9-2.html
netvope
5
Der Link von netvope scheint jetzt tot zu sein. Ich denke, dies ist der neue Ort: git-scm.com/book/en/Git-Internals-Git-Objects, der §9.2 von git-scm.com/book
Rhubbarb

Antworten:

122

Git stellt dem Objekt "blob" voran, gefolgt von der Länge (als lesbare Ganzzahl), gefolgt von einem NUL-Zeichen

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

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

Leif Gruenwoldt
quelle
2
Erwähnenswert ist auch, dass es "\ r \ n" durch "\ n" ersetzt, aber isolierte "\ r" allein lässt.
user420667
8
^ Korrektur des obigen Kommentars: Manchmal führt Git den obigen Ersatz durch, abhängig von den EOL / Autocrlf-Einstellungen.
user420667
5
Sie können dies auch mit der Ausgabe von vergleichen echo 'Hello, World!' | git hash-object --stdin. Optional können Sie angeben --no-filters, um sicherzustellen, dass keine crlf-Konvertierung stattfindet, oder angeben --path=somethi.ng, dass git den über gitattributes(auch @ user420667) angegebenen Filter verwenden soll. Und -weinreichen tatsächlich den Blob zu .git/objects(wenn Sie sind in einem Git - Repo - Geschäfte).
Tobias Kienzler
Die Äquivalenz ausdrücken, um Sinn zu machen: echo -e 'blob 16\0Hello, \r\nWorld!' | shasum == echo -e 'Hello, \r\nWorld!' | git hash-object --stdin --no-filters und es wird auch äquivalent zu \nund 15.
Peter Krauss
1
echoHängt eine neue Zeile an die Ausgabe an, die ebenfalls an git übergeben wird. Deshalb sind es 14 Zeichen. Um Echo ohne Zeilenumbruch zu verwenden, schreiben Sieecho -n 'Hello, World!'
Bouke Versteegh
36

Ich erweitere nur die Antwort durch @Leif Gruenwoldtund erläutere, was in der Referenz von enthalten ist@Leif Gruenwoldt

Mach es selbst..

  • Schritt 1. Erstellen Sie ein leeres Textdokument (Name spielt keine Rolle) in Ihrem Repository
  • Schritt 2. Das Dokument bereitstellen und festschreiben
  • Schritt 3. Identifizieren Sie den Hash des Blobs durch Ausführen git ls-tree HEAD
  • Schritt 4. Finden Sie den Hash des Blobs e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
  • Schritt 5. Lassen Sie Ihre Überraschung hinter sich und lesen Sie weiter unten

Wie berechnet GIT seine Commit-Hashes?

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

Der Text blob⎵ist ein konstantes Präfix und \0ist auch konstant und ist das NULLZeichen. Das <size_of_file>und<contents_of_file> variieren je nach Datei.

Siehe: Wie lautet das Dateiformat eines Git-Commit-Objekts?

Und das sind alle Leute!

Aber warte! Haben Sie bemerkt, dass dies <filename>kein Parameter ist, der für die Hash-Berechnung verwendet wird? Zwei Dateien können möglicherweise denselben Hash haben, wenn ihr Inhalt unabhängig von Datum und Uhrzeit ihrer Erstellung und ihrem Namen gleich ist. Dies ist einer der Gründe, warum Git Verschiebungen und Umbenennungen besser handhabt als andere Versionskontrollsysteme.

Mach es selbst (ext)

  • Schritt 6. Erstellen Sie eine weitere leere Datei mit einer anderen filename im selben Verzeichnis
  • Schritt 7. Vergleichen Sie die Hashes Ihrer beiden Dateien.

Hinweis:

Der Link erwähnt nicht, wie das treeObjekt gehasht wird. Ich bin mir des Algorithmus und der Parameter nicht sicher, aber meiner Beobachtung nach berechnet er wahrscheinlich einen Hash, der auf allen blobsund trees(ihren Hashes wahrscheinlich) basiert

Lordbalmon
quelle
SHA1("blob" + <size_of_file>- Gibt es ein zusätzliches Leerzeichen zwischen Blob und Größe? Ist die Größe dezimal? Ist es null vorangestellt?
Osgx
1
@osgx gibt es. Die Referenz und meine Tests bestätigen dies. Ich habe die Antwort korrigiert. Die Größe scheint die Anzahl der Bytes als Ganzzahl ohne Präfix zu sein.
Samuel Harmer
13

git hash-object

Dies ist eine schnelle Möglichkeit, Ihre Testmethode zu überprüfen:

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

Ausgabe:

f2ba8f84ab5c1bce84a7b441cb1959cfc7093b7f
f2ba8f84ab5c1bce84a7b441cb1959cfc7093b7f  -

Wo sha1sumist in GNU Coreutils.

Dann kommt es darauf an, das Format jedes Objekttyps zu verstehen. Wir haben das Triviale bereits behandelt blob, hier sind die anderen:

Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
quelle
Wie in einer früheren Antwort erwähnt, sollte die Länge eher als berechnet werden $(printf "\0$s" | wc -c). Beachten Sie das hinzugefügte leere Zeichen. Das heißt, wenn die Zeichenfolge 'abc' mit dem hinzugefügten leeren Zeichen vor sich ist, würde die Länge 4 ergeben, nicht 3. Dann stimmen die Ergebnisse mit sha1sum mit dem Git-Hash-Objekt überein.
Michael Ekoka
Du hast recht, sie passen zusammen. Es scheint, dass die Verwendung von printf anstelle von echo -e hier einen schädlichen Nebeneffekt hat. Wenn Sie git hash-object auf eine Datei anwenden, die die Zeichenfolge 'abc' enthält, erhalten Sie 8baef1b ... f903, was Sie erhalten, wenn Sie echo -e anstelle von printf verwenden. Vorausgesetzt, dass echo -e am Ende einer Zeichenfolge eine neue Zeile hinzufügt, können Sie dasselbe tun, um das Verhalten mit printf abzugleichen (dh s = "$ s \ n").
Michael Ekoka
3

Basierend auf der Antwort von Leif Gruenwoldt ist hier ein Shell-Funktionsersatz für 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/ .*$//'
}

Prüfung:

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

Ich brauchte dies für einige Unit-Tests in Python 3, also dachte ich, ich würde es hier lassen.

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

Ich halte mich \nüberall an Zeilenenden, aber unter bestimmten Umständen ändert Git möglicherweise auch Ihre Zeilenenden, bevor er diesen Hash berechnet, sodass Sie dort möglicherweise auch eine benötigen .replace('\r\n', '\n').

Samuel Harmer
quelle