วิธีกำหนด Git SHA1 ให้กับไฟล์ที่ไม่มี Git


138

ตามที่ฉันเข้าใจเมื่อ Git กำหนดแฮช SHA1 ให้กับไฟล์ SHA1 นี้จะไม่ซ้ำกันกับไฟล์ตามเนื้อหา

ดังนั้นหากไฟล์ย้ายจากที่เก็บหนึ่งไปยังอีกที่หนึ่ง SHA1 สำหรับไฟล์จะยังคงเหมือนเดิมเพราะเนื้อหาไม่เปลี่ยนแปลง

Git คำนวณ SHA1 แยกย่อยอย่างไร มันทำกับเนื้อหาไฟล์ที่ไม่มีการบีบอัดหรือไม่?

ฉันต้องการเลียนแบบการมอบหมายด้านนอกของ GA SHA1




คำตอบ:


255

นี่คือวิธีที่ Git คำนวณ SHA1 สำหรับไฟล์ (หรือในคำศัพท์ Git คือ "blob"):

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

ดังนั้นคุณสามารถคำนวณได้เองโดยไม่ต้องติดตั้ง Git โปรดทราบว่า "\ 0" คือ NULL-byte ไม่ใช่สตริงสองอักขระ

ตัวอย่างเช่นแฮชของไฟล์ว่าง:

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
กับงูหลาม 3 คุณจำเป็นต้องเข้ารหัสข้อมูล: เพื่อหลีกเลี่ยงการs.update(("blob %u\0" % filesize).encode('utf-8')) TypeError
Mark Booth

การเข้ารหัสแบบ utf-8 จะใช้งานได้ แต่น่าจะดีกว่าที่จะสร้างจากสตริงไบต์ในตอนแรก (การเข้ารหัส utf-8 ใช้งานได้เนื่องจากไม่มีอักขระ Unicode ที่ไม่ใช่ ASCII)
torek

อีกสิ่งหนึ่งที่ควรค่าแก่การกล่าวถึงก็คือดูเหมือนว่า git hash-object นั้นจะแทนที่ "\ r \ n" ด้วย "\ n" ในเนื้อหาของข้อมูล มันอาจตัด "\ r" ทั้งหมดฉันไม่ได้ตรวจสอบมัน
user420667

1
ฉันวาง Python 2 + 3 (ทั้งสองอย่างไว้ในหนึ่ง) การใช้งานไฟล์และตัวสร้างแฮชทรีขึ้นมาที่นี่: github.com/chris3torek/scripts/blob/master/githash.py ( ต้นไม้มีแฮอ่านทรีไดเรกทอรี)
torek

17

Goodie น้อย: ในเปลือก

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: การติดตาม-ถูกใช้โดยsha1sumหากคำนวณแฮชจาก stdin และไม่ใช่จากไฟล์ ไม่มีอะไรต้องกังวล. สิ่งที่แปลก แต่เกี่ยวกับการ-nที่ควรระงับการขึ้นบรรทัดใหม่ตามปกติโดย echo ไฟล์ของคุณมีโอกาสว่างเปล่าหรือไม่มีบรรทัดสุดท้ายที่คุณลืมเพิ่มในCONTENTSตัวแปรหรือไม่?
knittl

ใช่คุณถูกต้อง และฉันคิดว่าผลลัพธ์ของ sha1sum ควรเป็นเพียงแฮช แต่มันไม่ยากเลยที่จะเอามันออกด้วย sed หรืออะไรบางอย่าง
FrustratedWithFormsDesigner

@FrustratedWithFormsDesigner: คุณจะได้รับผลเช่นเดียวกันถ้าคุณใช้cat file | sha1sumแทนsha1sum file(กระบวนการมากขึ้นและท่อแม้ว่า)
knittl

8

คุณสามารถสร้างฟังก์ชั่น bash shell เพื่อคำนวณมันได้อย่างง่ายดายหากคุณไม่ได้ติดตั้งคอมไพล์

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

ลองดูที่หน้าคนสำหรับการคอมไพล์แฮชวัตถุ คุณสามารถใช้มันเพื่อคำนวณแฮชคอมไพล์ของไฟล์เฉพาะใด ๆ ฉันคิดว่าคอมไพล์ฟีดมากกว่าเนื้อหาของไฟล์ลงในอัลกอริทึมแฮช แต่ฉันไม่รู้แน่นอนและถ้ามันฟีดข้อมูลพิเศษฉันไม่รู้ว่ามันคืออะไร


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 ("ü"). ควร beEqualTo ("0f0f3e3b1ff2bc6722afc3e3812e6b782683896f") แต่ฟังก์ชั่นของฉันให้ ความคิดใดที่ git hash-object จัดการ umlauts?
forki23

มันควรจัดการ blob เป็น bytestream ซึ่งหมายความว่าüอาจมีความยาว 2 (unicode) คุณสมบัติความยาวของ F will จะกลับมามีความยาว 1 (เนื่องจากเป็นอักขระที่มองเห็นได้เพียงตัว
knittl

แต่ System.Text.Encoding.ASCII.GetBytes ("ü") จะส่งคืนอาร์เรย์ไบต์ที่มี 1 องค์ประกอบ
forki23

การใช้ UTF8 และ 2 เป็นความยาวสตริงจะให้อาร์เรย์แบบไบต์: [98; 108; 111; 98; 32; 50; 0; 195; 188] และดังนั้นจึงเป็น SHA1 ของ 99fe40df261f7d4afd1391fe2739b2c7466fe968 ซึ่งยังไม่คอมไพล์ SHA1
forki23

1
คุณต้องไม่นำข้อมูลสรุปไปใช้กับสตริงอักขระ แต่คุณต้องใช้มันกับสตริงไบต์ (ไบต์อาร์เรย์) ซึ่งคุณอาจได้รับโดยการแปลงสตริงอักขระเป็นไบต์โดยใช้การเข้ารหัสที่ชัดเจน
dolmen

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 เท่านั้น
Ferdinand Beyer

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

การใช้ 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

ในจาวาสคริปต์

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!" รับ hash blob จาก 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...แทน
bdesham
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.