banner
三文字

方寸之间

居善地,心善渊,与善仁,言善信,正善治,事善能,动善时。
github
email
mastodon
website

Git中的一個特殊hash

最近了解了一點 Git 的內部原理,看到了一個特殊的 hash,所以寫了這一篇文章來分享自己的看法。

==============

既然你讀這篇文章,那就意味著你應該比較熟悉 Git 的一系列操作,不過,在你使用 Git 的時候,你有沒有遇到以下 hash:

4b825dc642cb6eb9a060e54bf8d69288fbee4904

可能你會覺得 git 中的每個對象都有一個 hash 值,誰會注意 hash 的數值。確實,沒有人會注意。

但是上面的這個 hash 確實是一個很特別的 hash,接下來就來說明為什麼這個 hash 是一個特殊的存在。

git 中 hash 從哪裡來?#

每個 git 存儲庫,即使是空存儲庫也將包含這段 hash。這可以通過 git show 驗證:

    $ git show 4b825dc642cb6eb9a060e54bf8d69288fbee4904
    tree 4b825dc642cb6eb9a060e54bf8d69288fbee4904

那麼這個 hash 是從哪裡來的呢?在這之前我們需要了解一點 Git 的知識:Git 的核心部分是一個簡單的鍵值對數據庫(key-value data store)。 你可以向 Git 倉庫中插入任意類型的內容,它會返回一個唯一的鍵,通過該鍵可以在任意時刻再次取回該內容。

我們可以使用git hash-object命令來存儲一個對象並獲取該對象的鍵。

    $ echo 'test' | git hash-object -w --stdin
    9daeafb9864cf43055ae93beb0afd6c7d144bfa4

Git 內部存儲的數據類似下面這樣,其中每個對象都有其對應的 hash 值:

Git 數據模型 from ProGit.v2

ps:如果你仍然好奇,Pro Git 的 Git Internals 章節有更多詳細介紹。

那麼接下來說正題,這個特殊 hash 是如何產生的呢?它實際上是一棵空樹的哈希值。可以通過為空字符串的/dev/null創建對象哈希來驗證:

    $ git hash-object -t tree /dev/null
    4b825dc642cb6eb9a060e54bf8d69288fbee4904
    //或者
    $ echo -n '' | git hash-object -t tree --stdin
    4b825dc642cb6eb9a060e54bf8d69288fbee4904

空樹 hash 的特殊用處#

空樹 hash 可以與git diff一起使用。例如,如果你想檢查目錄中的空白錯誤,您可以使用 --check 選項並將 HEAD 與空樹進行比較:

    $ echo "test  " > readme.md
    $ git add . && git commit -m "init"
    [master 6d8e897] init
     1 file changed, 1 insertion(+), 3 deletions(-)
    $ git diff $(git hash-object -t tree /dev/null) HEAD --check -- readme.md
    readme.md:1: trailing whitespace.
    +test

在編寫 git hooks 時,空樹 hash 也非常有用。一個相當常見的用法是在使用類似於以下的代碼在接受新提交之前驗證它們:

    for changed_file in $(git diff --cached --name-only --diff-filter=ACM HEAD)
    do
      if ! validate_file "$changed_file"; then
        echo "Aborting commit"
        exit 1
      fi
    done

如果有以前的提交,這可以正常工作,但是如果沒有提交,則 HEAD 引用將不存在。為了解決這個問題,可以在檢查初始提交時使用空樹 hash:

    if git rev-parse --verify -q HEAD > /dev/null; then
      against=HEAD
    else
      # Initial commit: diff against an empty tree object
      against="$(git hash-object -t tree /dev/null)"
    fi
    
    for changed_file in $(git diff --cached --name-only --diff-filter=ACM "$against")
    do
      if ! validate_file "$changed_file"; then
        echo "Aborting commit"
        exit 1
      fi
    done

參考#

https://git-scm.com/book/en

https://floatingoctothorpe.uk/2017/empty-trees-in-git.html


首發於個人博客:方寸之間

載入中......
此文章數據所有權由區塊鏈加密技術和智能合約保障僅歸創作者所有。