Як призначити Git SHA1 файлу без Git?


138

Як я розумію, коли Git призначає хеш SHA1 файлу, цей SHA1 унікальний для цього файлу на основі його вмісту.

У результаті, якщо файл переміщується з одного сховища в інший, SHA1 для цього файла залишається таким же, як його вміст не змінювався.

Як Git обчислює дайджест SHA1? Це робить це на повний нестиснений вміст файлу?

Я хотів би наслідувати присвоєння SHA1 поза Git.




Відповіді:


255

Ось так Git обчислює SHA1 для файлу (або, в термінах Git, "крапку"):

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

Таким чином, ви можете легко обчислити його самостійно, не встановлюючи Git. Зауважте, що "\ 0" - це NULL-байт, а не дво символьний рядок.

Наприклад, хеш порожнього файлу:

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

$ touch empty
$ git hash-object empty
e69de29bb2d1d6434b8b29ae775ad8c2e48c5391

Ще один приклад:

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

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

Ось реалізація Python:

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

Ця відповідь припускає Python 2? Коли я спробую це на Python 3, я отримую TypeError: Unicode-objects must be encoded before hashingвиняток у першому s.update()рядку.
Марк Бут

3
За допомогою python 3 потрібно кодувати дані: s.update(("blob %u\0" % filesize).encode('utf-8'))щоб уникнути TypeError.
Марк Бут

Кодування як utf-8 буде спрацьовувати, але, мабуть, краще в першу чергу просто побудувати його з байтового рядка (кодування utf-8 працює, оскільки жоден з символів Unicode не є ASCII).
torek

Ще одне, що варто згадати, - це те, що git-хеш-об’єкт також, здається, замінює "\ r \ n" на "\ n" у вмісті даних. Це може дуже добре зняти "\ r" цілком, я цього не перевіряв.
user420667

1
Я ставлю Python 2 + 3 (обидва в одному) реалізацію файлу та генератора хешу дерева тут: github.com/chris3torek/scripts/blob/master/githash.py (у хешері дерев читається дерево каталогів).
Торек

17

Маленька смакота: в оболонці

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

1
Я порівнюю echo -en "blob ${#CONTENTS}\0$CONTENTS" | sha1sumрезультати виробництва, git hash-object path-to-fileі вони дають різні результати. Однак echo -e ...дає правильні результати, за винятком того, що існує трейлінг - ( неgit hash-object створює жодних символів). Це щось, про що я повинен хвилюватися?
FrustratedWithFormsDesigner

2
@FrustratedWithFormsDesigner: Trailing -використовується, sha1sumякщо він обчислив хеш зі stdin, а не з файлу. Не про що хвилюватися. Дивна річ, але про те -n, що має придушити новий рядок, який зазвичай додається відлунням. Чи має ваш файл випадково порожній останній рядок, який ви забули додати у свою CONTENTSзмінну?
knittl

Так, ви маєте рацію. І я подумав , що вихід sha1sum повинен тільки бути хеш, але це не так важко видалити з допомогою СЕД або що - то.
FrustratedWithFormsDesigner

@FrustratedWithFormsDesigner: Ви отримаєте той самий вихід, якщо будете використовувати cat file | sha1sumзамість sha1sum file(більше процесів та трубопроводів)
knittl

8

Ви можете зробити функцію оболонки bash, щоб обчислити її досить легко, якщо у вас не встановлено git.

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

1
Трохи коротше: (stat --printf="blob %s\0" "$1"; cat "$1") | sha1sum -b | cut -d" " -f1.
sschuberth

4

Погляньте на сторінку man для git-hash-об’єкта . Ви можете використовувати його для обчислення git-хешу будь-якого конкретного файлу. Я думаю, що git подає більше, ніж просто вміст файлу в алгоритм хешу, але я точно не знаю, і якщо він подається з додатковими даними, я не знаю, що це таке.


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

Це рішення у F #.


У мене все ще виникають проблеми з umlauts: calcGitSHA1 ("ü"). Будь-які ідеї, як git-хеш-об'єкт обробляє умлаути?
forki23

він повинен обробляти крапку як бітестрім, це означає, що ü має, мабуть, довжину 2 (unicode), властивість довжини F♯ поверне довжину 1 (тому що це лише один видимий символ)
knittl

Але System.Text.Encoding.ASCII.GetBytes ("ü") повертає байтовий масив з 1 елементом.
forki23

Використання UTF8 та 2 як довжини рядка дає байтовий масив: [98; 108; 111; 98; 32; 50; 0; 195; 188] і для цього SHA1 99fe40df261f7d4afd1391fe2739b2c7466fe968. Що також не є git SHA1.
forki23

1
Ніколи не слід застосовувати дайджести до символьних рядків. Натомість ви повинні застосувати їх до рядків байтів (байтових масивів), які ви можете отримати, перетворивши символьну рядок у байти, використовуючи явне кодування.
долмен

2

Повна реалізація 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
Те, що ви дійсно хочете, це кодування ASCII. UTF8 працює тут лише тому, що він сумісний з ASCII, а "blob x \ 0" містить лише символи з кодом <= 127.
Фердинанд Бейер

1

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

Як команда оболонки:

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

1

І в Perl (див. Також Git :: PurePerl на 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

Використовуючи Ruby, ви можете зробити щось подібне:

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

Маленький сценарій Bash, який повинен давати ідентичний вихід git hash-object:

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

0

У 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

Цікаво відзначити, що очевидно Git додає символ нового рядка до кінця даних, перш ніж вони будуть хешировані. Файл, що не містить нічого, крім "Hello World!" отримує хеш-крап 980a0d5 ..., який такий же, як і цей:

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

4
Цей новий рядок додається вашим текстовим редактором, а не самим git hash-object. Зверніть увагу , що виконання echo "Hello World!" | git hash-object --stdinдає 980a0d5..., при використанні echo -nдає хеш c57eff5...замість цього.
бдешам
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.