git compute file hashes อย่างไร


124

แฮช SHA1 ที่เก็บไว้ในวัตถุโครงสร้าง (ตามที่ส่งคืนโดยgit ls-tree) ไม่ตรงกับแฮช SHA1 ของเนื้อหาไฟล์ (ตามที่ส่งคืนโดยsha1sum)

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

git compute file hashes อย่างไร มันบีบอัดเนื้อหาก่อนคำนวณแฮชหรือไม่?



1
สำหรับรายละเอียดเพิ่มเติมโปรดดูprogit.org/book/ch9-2.html
netvope

5
ลิงค์ของ netvope ดูเหมือนจะตายไปแล้ว ฉันคิดว่านี่คือตำแหน่งใหม่: git-scm.com/book/en/Git-Internals-Git-Objectsซึ่งเป็น§9.2จากgit-scm.com/book
Rhubbarb

คำตอบ:


122

Git นำหน้าวัตถุด้วย "blob" ตามด้วยความยาว (เป็นจำนวนเต็มที่มนุษย์อ่านได้) ตามด้วยอักขระ NUL

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

ที่มา: http://alblue.bandlem.com/2011/08/git-tip-of-week-objects.html


2
นอกจากนี้ยังควรค่าแก่การกล่าวถึงว่ามันแทนที่ "\ r \ n" ด้วย "\ n" แต่ปล่อยให้ "\ r" อยู่โดดเดี่ยว
user420667

8
^ การแก้ไขความคิดเห็นด้านบน: บางครั้ง git จะทำการแทนที่ด้านบนขึ้นอยู่กับการตั้งค่า eol / autocrlf
user420667

5
คุณยังสามารถเปรียบเทียบสิ่งนี้กับผลลัพธ์ของecho 'Hello, World!' | git hash-object --stdin. คุณสามารถเลือกที่จะระบุ--no-filtersเพื่อให้แน่ใจว่าไม่มีการแปลง crlf เกิดขึ้นหรือระบุ--path=somethi.ngให้ git ใช้ตัวกรองที่ระบุผ่านgitattributes(เช่น @ user420667) และ-wส่ง blob ไปให้.git/objects(ถ้าคุณอยู่ใน git repo)
Tobias Kienzler

แสดงความเท่าเทียมกันเพื่อให้สมเหตุสมผล: echo -e 'blob 16\0Hello, \r\nWorld!' | shasum == echo -e 'Hello, \r\nWorld!' | git hash-object --stdin --no-filters และมันจะเทียบเท่ากับ\n15 ด้วย
Peter Krauss

1
echoผนวกบรรทัดใหม่เข้ากับผลลัพธ์ซึ่งจะถูกส่งผ่านไปยังคอมไพล์ นั่นเป็นเหตุผลว่าทำไมมันถึง 14 ตัว หากต้องการใช้เสียงสะท้อนโดยไม่ขึ้นบรรทัดใหม่ให้เขียนecho -n 'Hello, World!'
Bouke Versteegh

36

ฉันกำลังขยายคำตอบโดย@Leif Gruenwoldtและรายละเอียดสิ่งที่อยู่ในข้อมูลอ้างอิงที่จัดทำโดย@Leif Gruenwoldt

ทำด้วยตัวคุณเอง..

  • ขั้นตอนที่ 1. สร้างเอกสารข้อความว่าง (ชื่อไม่สำคัญ) ในที่เก็บของคุณ
  • ขั้นตอนที่ 2. ขั้นตอนและยอมรับเอกสาร
  • ขั้นตอนที่ 3 ระบุแฮชของหยดโดยดำเนินการ git ls-tree HEAD
  • ขั้นตอนที่ 4. ค้นหาแฮชของหยดน้ำที่จะเป็น e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
  • ขั้นตอนที่ 5. เลิกแปลกใจและอ่านด้านล่าง

GIT คำนวณแฮชคอมมิตอย่างไร

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

ข้อความblob⎵เป็นคำนำหน้าค่าคงที่และ\0ยังคงที่และเป็นNULLอักขระ <size_of_file>และ<contents_of_file>แตกต่างกันไปขึ้นอยู่กับไฟล์

ดู: รูปแบบไฟล์ของวัตถุคอมมิตคืออะไร?

และนั่นคือทุกคน!

แต่เดี๋ยวก่อน! คุณสังเกตเห็นไหมว่า<filename>ไม่ใช่พารามิเตอร์ที่ใช้สำหรับการคำนวณแฮช ไฟล์สองไฟล์อาจมีแฮชเหมือนกันหากเนื้อหาไม่แยแสวันที่และเวลาที่สร้างและชื่อ นี่เป็นสาเหตุหนึ่งที่ Git จัดการกับการย้ายและเปลี่ยนชื่อได้ดีกว่าระบบควบคุมเวอร์ชันอื่น ๆ

ทำเอง (Ext)

  • ขั้นตอนที่ 6. สร้างไฟล์เปล่าอื่นที่มีไฟล์อื่นfilenameอยู่ในไดเร็กทอรีเดียวกัน
  • ขั้นตอนที่ 7. เปรียบเทียบแฮชของทั้งสองไฟล์ของคุณ

บันทึก:

ลิงก์ไม่ได้กล่าวถึงวิธีการtreeแฮชวัตถุ ฉันไม่แน่ใจในอัลกอริทึมและพารามิเตอร์อย่างไรก็ตามจากการสังเกตของฉันมันอาจจะคำนวณแฮชตามblobsและtrees(แฮชของพวกเขาอาจ) ที่มีอยู่ทั้งหมด


SHA1("blob" + <size_of_file>- มีช่องว่างเพิ่มเติมระหว่างหยดและขนาดหรือไม่? ขนาดทศนิยมหรือไม่ คำนำหน้าเป็นศูนย์หรือไม่?
osgx

1
@osgx มี. การอ้างอิงและการทดสอบของฉันยืนยันเช่นนั้น ฉันได้แก้ไขคำตอบแล้ว ขนาดดูเหมือนจะเป็นจำนวนไบต์เป็นจำนวนเต็มโดยไม่มีคำนำหน้า
Samuel Harmer

13

git hash-object

นี่เป็นวิธีที่รวดเร็วในการตรวจสอบวิธีทดสอบของคุณ:

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

เอาท์พุท:

f2ba8f84ab5c1bce84a7b441cb1959cfc7093b7f
f2ba8f84ab5c1bce84a7b441cb1959cfc7093b7f  -

อยู่ที่ไหนsha1sumใน GNU Coreutils

จากนั้นก็มาทำความเข้าใจกับรูปแบบของวัตถุแต่ละประเภท เราได้กล่าวถึงเรื่องเล็กน้อยblobแล้วนี่คือสิ่งอื่น ๆ :


$(printf "\0$s" | wc -c)ตามที่ระบุไว้ในคำตอบที่ก่อนหน้านี้มีความยาวค่อนข้างควรจะคำนวณเป็น สังเกตอักขระว่างที่เพิ่มเข้ามา นั่นคือถ้าสตริงเป็น 'abc' โดยเพิ่มอักขระว่างด้านหน้าความยาวจะให้ผลลัพธ์เป็น 4 ไม่ใช่ 3 ผลลัพธ์ที่มี sha1sum จะจับคู่ git hash-object
Michael Ekoka

คุณพูดถูกพวกเขาเข้ากัน ดูเหมือนว่าจะมีผลข้างเคียงที่เป็นอันตรายจากการใช้ printf แทนที่จะเป็น echo -e ที่นี่ เมื่อคุณใช้ git hash-object กับไฟล์ที่มีสตริง 'abc' คุณจะได้รับ 8baef1b ... f903 ซึ่งเป็นสิ่งที่คุณจะได้รับเมื่อใช้ echo -e แทนที่จะเป็น printf หาก echo -e เพิ่มขึ้นบรรทัดใหม่ที่ส่วนท้ายของสตริงดูเหมือนว่าจะตรงกับลักษณะการทำงานกับ printf คุณสามารถทำได้เช่นเดียวกัน (เช่น s = "$ s \ n")
Michael Ekoka

3

จากคำตอบของLeif Gruenwoldtนี่คือฟังก์ชันเชลล์แทน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/ .*$//'
}

ทดสอบ:

$ echo 'Hello, World!' > test.txt
$ git hash-object test.txt
8ab686eafeb1f44702738c8b0f24f2567c36da6d
$ git-hash-object test.txt
8ab686eafeb1f44702738c8b0f24f2567c36da6d

3

ฉันต้องการสิ่งนี้สำหรับการทดสอบหน่วยใน Python 3 ดังนั้นคิดว่าจะปล่อยไว้ที่นี่

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

ฉันยึดติดกับการ\nลงท้ายบรรทัดทุกที่ แต่ในบางสถานการณ์ Git อาจกำลังเปลี่ยนจุดสิ้นสุดบรรทัดของคุณก่อนที่จะคำนวณแฮชนี้ดังนั้นคุณอาจต้องมี.replace('\r\n', '\n')อยู่ในนั้นด้วย

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