Wie ordne ich einer Datei ohne Git einen Git SHA1 zu?

138

Soweit ich weiß, wenn Git einer Datei einen SHA1-Hash zuweist, ist dieser SHA1 aufgrund seines Inhalts für die Datei eindeutig.

Wenn eine Datei von einem Repository in ein anderes verschoben wird, bleibt der SHA1 für die Datei unverändert, da sich der Inhalt nicht geändert hat.

Wie berechnet Git den SHA1-Digest? Tut es das mit dem vollständigen unkomprimierten Dateiinhalt?

Ich möchte die Zuweisung von SHA1s außerhalb von Git emulieren.

git-noob
quelle

Antworten:

255

So berechnet Git den SHA1 für eine Datei (oder, in Git-Begriffen, einen "Blob"):

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

So können Sie es einfach selbst berechnen, ohne Git installiert zu haben. Beachten Sie, dass "\ 0" das NULL-Byte ist und keine zweistellige Zeichenfolge.

Zum Beispiel der Hash einer leeren Datei:

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

$ touch empty
$ git hash-object empty
e69de29bb2d1d6434b8b29ae775ad8c2e48c5391

Ein anderes Beispiel:

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

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

Hier ist eine Python-Implementierung:

from hashlib import sha1
def githash(data):
    s = sha1()
    s.update("blob %u\0" % len(data))
    s.update(data)
    return s.hexdigest()
Ferdinand Beyer
quelle
Geht diese Antwort von Python 2 aus? Wenn ich dies unter Python 3 versuche, wird TypeError: Unicode-objects must be encoded before hashingin der ersten s.update()Zeile eine Ausnahme angezeigt.
Mark Booth
3
Mit Python 3 müssen Sie die Daten verschlüsseln: s.update(("blob %u\0" % filesize).encode('utf-8'))um das zu vermeiden TypeError.
Mark Booth
Die Codierung als utf-8 funktioniert, aber wahrscheinlich ist es besser, sie zunächst nur aus einer Byte-Zeichenfolge zu erstellen (die utf-8-Codierung funktioniert, da keines der Unicode-Zeichen Nicht-ASCII-Zeichen sind).
Torek
Eine weitere erwähnenswerte Sache ist, dass das Git-Hash-Objekt im Inhalt der Daten auch "\ r \ n" durch "\ n" zu ersetzen scheint. Es könnte sehr gut das "\ r" komplett entfernen, das habe ich nicht überprüft.
user420667
1
Ich habe hier oben eine Python 2 + 3-Implementierung (beide in einer) eines Datei- und Baum-Hash-Generators eingefügt : github.com/chris3torek/scripts/blob/master/githash.py (der Baum-Hasher liest einen Verzeichnisbaum).
Torek
17

Ein kleiner Leckerbissen: in der Schale

echo -en "blob ${#CONTENTS}\0$CONTENTS" | sha1sum
stricken
quelle
1
Ich vergleiche echo -en "blob ${#CONTENTS}\0$CONTENTS" | sha1summit der Ausgabe von git hash-object path-to-fileund sie produzieren unterschiedliche Ergebnisse. Allerdings echo -e ...erzeugt die richtigen Ergebnisse, außer es gibt eine Hinter ist - (git hash-object erzeugt keine nachlauf Zeichen). Sollte ich mir darüber Sorgen machen?
FrustratedWithFormsDesigner
2
@FrustratedWithFormsDesigner: Das Trailing -wird verwendet, sha1sumwenn der Hash aus stdin und nicht aus einer Datei berechnet wird. Nichts, über das man sich sorgen sollte. Seltsame Sache an der -n, die die normalerweise durch Echo angehängte Newline unterdrücken sollte. Hat Ihre Datei zufällig eine leere letzte Zeile, die Sie vergessen haben, in Ihre CONTENTSVariable aufzunehmen?
Knittl
Ja, du hast recht. Und ich hatte gedacht, dass die Ausgabe von sha1sum nur der Hash sein sollte, aber es ist nicht schwer, ihn mit sed oder so etwas zu entfernen.
FrustratedWithFormsDesigner
@FrustratedWithFormsDesigner: Sie erhalten die gleiche Ausgabe, wenn Sie cat file | sha1sumanstelle von sha1sum file(mehr Prozesse und Rohrleitungen allerdings) verwenden
knittl
8

Sie können eine Bash-Shell-Funktion erstellen, um sie ganz einfach zu berechnen, wenn Sie kein Git installiert haben.

git_id () { printf 'blob %s\0' "$(ls -l "$1" | awk '{print $5;}')" | cat - "$1" | sha1sum | awk '{print $1}'; }
CB Bailey
quelle
1
Etwas kürzer : (stat --printf="blob %s\0" "$1"; cat "$1") | sha1sum -b | cut -d" " -f1.
Sschuberth
4

Schauen Sie sich die Manpage für Git-Hash-Objekt an . Sie können es verwenden, um den Git-Hash einer bestimmten Datei zu berechnen. Ich denke, dass git mehr als nur den Inhalt der Datei in den Hash-Algorithmus einspeist, aber ich weiß es nicht genau, und wenn es zusätzliche Daten einspeist, weiß ich nicht, was es ist.

Dale Hagglund
quelle
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

Dies ist eine Lösung in F #.

forki23
quelle
Ich habe immer noch Probleme mit Umlauten: calcGitSHA1 ("ü"). ShouldBeEqualTo ("0f0f3e3b1ff2bc6722afc3e3812e6b782683896f") Aber meine Funktion gibt 0d758c9c7bc06c1e307f05d92d896aaf0a8a6. Irgendwelche Ideen, wie Git-Hash-Objekt Umlaute handhabt?
Forki23
es sollte den Blob als Bytestream behandeln, das heißt, ü hat wahrscheinlich die Länge 2 (Unicode), die Eigenschaft Length von F♯ gibt die Länge 1 zurück (weil es nur ein sichtbares Zeichen ist)
knittl
System.Text.Encoding.ASCII.GetBytes ("ü") gibt jedoch ein Byte-Array mit 1 Element zurück.
Forki23
Die Verwendung von UTF8 und 2 als Zeichenfolgenlänge ergibt ein Byte-Array: [98; 108; 111; 98; 32; 50; 0; 195; 188] und dafür eine SHA1 von 99fe40df261f7d4afd1391fe2739b2c7466fe968. Welches ist auch nicht der Git SHA1.
Forki23
1
Sie dürfen niemals Digests auf Zeichenketten anwenden. Stattdessen müssen Sie sie auf Byte-Strings (Byte-Arrays) anwenden, die Sie erhalten können, indem Sie eine Zeichenfolge mit einer expliziten Codierung in Bytes konvertieren.
Dolmen
2

Vollständige Python3-Implementierung:

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
quelle
2
Was Sie wirklich wollen, ist die ASCII-Codierung. UTF8 funktioniert hier nur, weil es mit ASCII kompatibel ist und "blob x \ 0" nur Zeichen mit dem Code <= 127 enthält.
Ferdinand Beyer
1

In 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";

Als Shell-Befehl:

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

Und in Perl (siehe auch Git :: PurePerl unter 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 der Geek
quelle
1

Mit Ruby können Sie Folgendes tun:

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
quelle
1

Ein kleines Bash-Skript, das identische Ausgaben erzeugen sollte wie git hash-object:

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

In 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
quelle
-4

Es ist interessant festzustellen, dass Git offensichtlich am Ende der Daten ein Zeilenumbruchzeichen hinzufügt, bevor sie gehasht werden. Eine Datei, die nichts als "Hallo Welt!" bekommt einen Blob-Hash von 980a0d5 ..., der der gleiche ist wie dieser:

$ php -r 'echo sha1("blob 13" . chr(0) . "Hello World!\n") , PHP_EOL;'
Schubs
quelle
4
Diese neue Zeile wird von Ihrem Texteditor hinzugefügt, nicht von git hash-object. Beachten Sie, dass das Geben von echo "Hello World!" | git hash-object --stdingibt 980a0d5..., während das Verwenden stattdessen echo -neinen Hash von gibt c57eff5....
Bdesham