W jaki sposób git oblicza skróty plików?

124

Skróty SHA1 przechowywane w obiektach drzewa (zwrócone przez git ls-tree) nie pasują do skrótów SHA1 zawartości pliku (zwróconych przez sha1sum)

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

W jaki sposób git oblicza skróty plików? Czy kompresuje zawartość przed obliczeniem skrótu?

netvope
źródło
1
Aby uzyskać więcej informacji, zobacz także progit.org/book/ch9-2.html
netvope
5
Wydaje się, że łącze netvope jest teraz martwe. Myślę, że jest to nowa lokalizacja: git-scm.com/book/en/Git-Internals-Git-Objects, czyli §9.2 z git-scm.com/book
Rhubbarb.

Odpowiedzi:

122

Git poprzedza obiekt ciągiem „blob”, po którym następuje długość (jako czytelna dla człowieka liczba całkowita), po której następuje znak NUL

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

Źródło: http://alblue.bandlem.com/2011/08/git-tip-of-week-objects.html

Leif Gruenwoldt
źródło
2
Warto również wspomnieć, że zamienia "\ r \ n" na "\ n", ale pozostawia pojedyncze "\ r" same.
user420667
8
^ poprawka do powyższego komentarza: czasami git zastępuje powyższy, w zależności od ustawień eol / autocrlf.
user420667
5
Możesz również porównać to z danymi wyjściowymi programu echo 'Hello, World!' | git hash-object --stdin. Opcjonalnie możesz określić, --no-filtersaby upewnić się, że konwersja crlf nie nastąpi, lub określić, --path=somethi.ngaby git używał filtru określonego przez gitattributes(również @ user420667). I -wrzeczywiście złożyć blob .git/objects(jeśli w repo git).
Tobias Kienzler
Wyrażenie równoważności, aby miało sens: echo -e 'blob 16\0Hello, \r\nWorld!' | shasum == echo -e 'Hello, \r\nWorld!' | git hash-object --stdin --no-filters i będzie również równoważne z \ni 15.
Peter Krauss
1
echodołącza znak nowej linii do wyjścia, który jest również przekazywany do git. Dlatego ma 14 znaków. Aby użyć echa bez znaku nowej linii, napiszecho -n 'Hello, World!'
Bouke Versteegh
36

Rozszerzam tylko odpowiedź @Leif Gruenwoldti szczegółowo opisuję, co jest w referencji dostarczonej przez@Leif Gruenwoldt

Zrób to sam..

  • Krok 1. Utwórz pusty dokument tekstowy (nazwa nie ma znaczenia) w swoim repozytorium
  • Krok 2. Przygotuj i zatwierdź dokument
  • Krok 3. Zidentyfikuj skrót obiektu BLOB, wykonując git ls-tree HEAD
  • Krok 4. Znajdź hash obiektu blob e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
  • Krok 5. Wyrwij się z zaskoczenia i przeczytaj poniżej

Jak GIT oblicza skróty zatwierdzeń

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

Tekst blob⎵jest stałym przedrostkiem, a \0także jest stały i jest NULLznakiem. <size_of_file>I <contents_of_file>różnią się w zależności od pliku.

Zobacz: Jaki jest format pliku obiektu git commit?

I to wszystko ludzie!

Ale poczekaj! , czy zauważyłeś, że <filename>parametr nie jest używany do obliczenia skrótu? Dwa pliki mogą potencjalnie mieć ten sam skrót, jeśli ich zawartość jest taka sama, niezależnie od daty i godziny ich utworzenia oraz nazwy. Jest to jeden z powodów, dla których Git radzi sobie z przenoszeniem i zmianą nazw lepiej niż inne systemy kontroli wersji.

Zrób to sam (rozszerzenie)

  • Krok 6. Utwórz kolejny pusty plik z innym filenamew tym samym katalogu
  • Krok 7. Porównaj skróty obu plików.

Uwaga:

Odnośnik nie wspomina o tym, jak treeobiekt jest zahaszowany. Nie jestem pewien algorytmu i parametrów, ale z moich obserwacji prawdopodobnie wylicza hash na podstawie wszystkich blobsi trees(prawdopodobnie ich hashów), które zawiera

Lordbalmon
źródło
SHA1("blob" + <size_of_file>- czy między blobem a rozmiarem jest dodatkowa spacja? Czy rozmiar jest dziesiętny? Czy ma przedrostek zerowy?
osgx
1
@osgx Jest. Potwierdzają to referencje i moje testy. Poprawiłem odpowiedź. Wydaje się, że rozmiar to liczba bajtów jako liczba całkowita bez prefiksu.
Samuel Harmer
13

git hash-object

To szybki sposób na zweryfikowanie metody testowej:

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

Wynik:

f2ba8f84ab5c1bce84a7b441cb1959cfc7093b7f
f2ba8f84ab5c1bce84a7b441cb1959cfc7093b7f  -

gdzie sha1sumjest w GNU Coreutils.

Następnie sprowadza się do zrozumienia formatu każdego typu obiektu. Już omówiliśmy trywialne blob, oto inne:

Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
źródło
Jak wspomniano w poprzedniej odpowiedzi, długość powinna być raczej obliczana jako $(printf "\0$s" | wc -c). Zwróć uwagę na dodany pusty znak. Oznacza to, że jeśli napisem jest „abc” z dodanym pustym znakiem na początku, długość dałoby 4, a nie 3. Następnie wyniki z sha1sum pasują do git hash-object.
Michael Ekoka,
Masz rację, pasują. Wygląda na to, że użycie printf zamiast echo -e ma tu trochę zgubny efekt uboczny. Kiedy zastosujesz obiekt haszujący git do pliku zawierającego ciąg 'abc', otrzymasz 8baef1b ... f903, co jest tym, co otrzymujesz, używając echo -e zamiast printf. Zakładając, że echo -e dodaje znak nowej linii na końcu łańcucha, wydaje się, że aby dopasować zachowanie do printf, możesz zrobić to samo (tj. S = "$ s \ n").
Michael Ekoka,
3

Opierając się na odpowiedzi Leifa Gruenwoldta , oto substytut funkcji powłoki 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/ .*$//'
}

Test:

$ echo 'Hello, World!' > test.txt
$ git hash-object test.txt
8ab686eafeb1f44702738c8b0f24f2567c36da6d
$ git-hash-object test.txt
8ab686eafeb1f44702738c8b0f24f2567c36da6d
Lucas Cimon
źródło
3

Potrzebowałem tego do niektórych testów jednostkowych w Pythonie 3, więc pomyślałem, że zostawię to tutaj.

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

\nWszędzie trzymam się końcówek linii, ale w niektórych okolicznościach Git może również zmieniać zakończenia linii przed obliczeniem tego skrótu, więc możesz .replace('\r\n', '\n')tam również potrzebować .

Samuel Harmer
źródło