Làm cách nào để gán Git SHA1 cho tệp không có Git?


138

Theo tôi hiểu khi Git gán hàm băm SHA1 cho tệp thì SHA1 này là duy nhất cho tệp dựa trên nội dung của nó.

Kết quả là nếu một tệp di chuyển từ kho này sang kho khác thì SHA1 cho tệp vẫn giống như nội dung của nó không thay đổi.

Làm thế nào để Git tính toán tiêu hóa SHA1? Liệu nó làm điều đó trên các nội dung tập tin không nén đầy đủ?

Tôi muốn mô phỏng việc gán SHA1 bên ngoài Git.




Câu trả lời:


255

Đây là cách Git tính toán SHA1 cho một tệp (hoặc, theo thuật ngữ Git, "blob"):

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

Vì vậy, bạn có thể dễ dàng tự tính toán mà không cần cài đặt Git. Lưu ý rằng "\ 0" là byte NULL, không phải là chuỗi hai ký tự.

Ví dụ: hàm băm của một tệp trống:

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

$ touch empty
$ git hash-object empty
e69de29bb2d1d6434b8b29ae775ad8c2e48c5391

Một vi dụ khac:

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

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

Đây là một triển khai Python:

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

Có phải câu trả lời này giả sử Python 2? Khi tôi thử điều này trên Python 3, tôi nhận được một TypeError: Unicode-objects must be encoded before hashingngoại lệ trên s.update()dòng đầu tiên .
Đánh dấu gian hàng

3
Với python 3 bạn cần mã hóa dữ liệu: s.update(("blob %u\0" % filesize).encode('utf-8'))để tránh TypeError.
Đánh dấu gian hàng

Mã hóa dưới dạng utf-8 sẽ hoạt động, nhưng có lẽ tốt hơn là chỉ xây dựng nó từ chuỗi byte ở vị trí đầu tiên (mã hóa utf-8 hoạt động vì không có ký tự unicode nào không phải là ASCII).

Một điều đáng nói nữa là đối tượng băm git dường như cũng thay thế "\ r \ n" bằng "\ n" trong nội dung dữ liệu. Nó rất có thể loại bỏ hoàn toàn "\ r", tôi đã không kiểm tra điều đó.
user420667

1
Tôi đặt một triển khai Python 2 + 3 (cả hai trong một) của trình tạo tệp băm cây ở đây: github.com/chris3torek/scripts/blob/master/githash.py (cây đã đọc cây thư mục).

17

Một chút goodie: trong vỏ

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

1
Tôi đang so sánh echo -en "blob ${#CONTENTS}\0$CONTENTS" | sha1sumvới đầu ra git hash-object path-to-filevà họ tạo ra kết quả khác nhau. Tuy nhiên, echo -e ...tạo ra kết quả chính xác, ngoại trừ có một dấu - ( khônggit hash-object tạo ra các ký tự dấu). Đây có phải là điều tôi nên lo lắng?
Thất vọngWithFormsDesigner

2
@FrustratedWithFormsDesigner: Việc theo dõi -được sử dụng bởi sha1sumnếu nó tính toán hàm băm từ stdin chứ không phải từ một tệp. Không có gì phải lo lắng về. Mặc dù điều kỳ lạ là về điều đó -n, nên loại bỏ dòng mới thường được nối bằng tiếng vang. Có phải tập tin của bạn có một dòng cuối cùng trống mà bạn quên thêm vào CONTENTSbiến của mình không?
knittl

Vâng, bạn đã đúng. Và tôi đã nghĩ rằng đầu ra của sha1sum chỉ nên là hàm băm, nhưng không khó để loại bỏ nó bằng sed hoặc một cái gì đó.
Thất vọngWithFormsDesigner

@FrustratedWithFormsDesigner: Bạn sẽ nhận được cùng một đầu ra nếu bạn sử dụng cat file | sha1sumthay vì sha1sum file(nhiều quy trình và đường ống hơn)
knittl 11/2/2015

8

Bạn có thể tạo hàm bash shell để tính toán nó khá dễ dàng nếu bạn chưa cài đặt git.

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

1
Ngắn hơn một chút : (stat --printf="blob %s\0" "$1"; cat "$1") | sha1sum -b | cut -d" " -f1.
sschuberth

4

Hãy xem trang man cho git-hash-object . Bạn có thể sử dụng nó để tính băm git của bất kỳ tệp cụ thể nào. Tôi nghĩ rằng git cung cấp nhiều hơn chỉ nội dung của tệp vào thuật toán băm, nhưng tôi không biết chắc chắn và nếu nó cung cấp thêm dữ liệu, tôi không biết nó là gì.


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

Đây là một giải pháp trong F #.


Tôi vẫn gặp vấn đề với umlauts: calcGitSHA1 ("ü"). ShouldBeEqualTo ("0f0f3e3b1ff2bc6722afc3e3812e6b782683896f") Bất kỳ ý tưởng làm thế nào git hash-object xử lý umlauts?
forki23

nó nên xử lý blob dưới dạng bytestream, điều đó có nghĩa là ü có thể có độ dài 2 (unicode), thuộc tính Độ dài của F♯ sẽ trả về độ dài 1 (vì nó chỉ có một ký tự hiển thị)
knittl

Nhưng System.Text.Encoding.ASCII.GetBytes ("ü") trả về một mảng byte có 1 phần tử.
forki23

Sử dụng UTF8 và 2 làm độ dài chuỗi cho ra một mảng byte: [98; 108; 111; 98; 32; 50; 0; 195; 188] và có SHA1 là 99fe40df261f7d4afd1391fe2739b2c7466fe968. Mà cũng không phải là git SHA1.
forki23

1
Bạn không bao giờ phải áp dụng tiêu hóa cho chuỗi ký tự. Thay vào đó, bạn phải áp dụng chúng cho các chuỗi byte (mảng byte) mà bạn có thể có được bằng cách chuyển đổi một chuỗi ký tự thành byte bằng cách sử dụng mã hóa rõ ràng.
heo

2

Thực hiện đầy đủ 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
Những gì bạn thực sự muốn là mã hóa ASCII. UTF8 chỉ hoạt động ở đây vì nó tương thích với ASCII và "blob x \ 0" chỉ chứa các ký tự có mã <= 127.
Ferdinand Beyer

1

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

Như một lệnh shell:

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

1

Và trong Perl (xem thêm Git :: PurePerl tại 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

Sử dụng Ruby, bạn có thể làm một cái gì đó như thế này:

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

Một tập lệnh Bash nhỏ sẽ tạo ra đầu ra giống hệt với git hash-object:

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

0

Trong 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

Thật thú vị khi lưu ý rằng rõ ràng Git thêm một ký tự dòng mới vào cuối dữ liệu trước khi nó được băm. Một tệp không chứa gì ngoài "Hello World!" nhận được một băm blob của 980a0d5 ..., giống như cái này:

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

4
Dòng mới đó đang được thêm bởi trình soạn thảo văn bản của bạn, không phải bởi git hash-object. Lưu ý rằng làm echo "Hello World!" | git hash-object --stdincho 980a0d5..., trong khi sử dụng echo -ncho một hàm băm c57eff5...thay thế.
bdesham
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.