iT邦幫忙

2025 iThome 鐵人賽

DAY 11
0
Software Development

深入一點點認識 Git系列 第 11

Day 11-深入一點點認識 Git:git commit 之後,.git/ 資料夾發生了什麼變化?

  • 分享至 

  • xImage
  •  

在 Day 10 的文章中,我們發現在經過 git add 指令後,.git/ 資料夾會發生下列兩大變化:

  1. objects/ 資料夾跑出一個 blob 物件。
  2. 出現 index 檔案,這檔案就是預存區(staging area),內有上述 blob 物件相關資訊。

那當我們再下 git commit 指令,.git/ 資料夾又會如何變化呢?

先回到 git add

首先在一個經過 git init 的資料夾中,以下列指令生成一個內容為 Hello, world!、名稱為 hello.txt 的文字檔(檔案內容跟 Day 10 文章相同,但檔名不同)。

echo "Hello, world!" > hello.txt

接著把這個檔案加進預存區:

git add hello.txt

目前 git status 顯示 hello.txt 在預存區中:
https://ithelp.ithome.com.tw/upload/images/20250911/20178513aQ5T9Mw8HM.png

值得留意的是,如果我們打開 .git/ 資料夾,會發現即使跟 Day 10 在不同時間做、甚至連檔名也不同,但因為檔案內容一樣,因此產生的 blob 物件依然有相同的雜湊碼,印證 Day 3、Day 4 時所說的:只要內容一樣,雜湊碼就會一樣。

.git/
├── hooks/
├── info/
│
├── objects/
│   ├── af/                         # blob物件雜湊碼與Day 10文章相同
│   │   └── 5626b4a114abcb82d63db7c8082c3c4756e51b
│   ├── info/
│   └── pack/
│
├── refs/
│   ├── heads/
│   └── tags/
│
├── config
├── description
├── HEAD
└── index                          # 預存區(staging area)

複習:用 git log 觀察 git commit 後的變化

接下來我們以下列指令,形成第一筆 commit 快照:

git commit -m "Initial commit"

我們來看終端機畫面顯示什麼:
https://ithelp.ithome.com.tw/upload/images/20250911/20178513DS4vtOFeev.png

  • git commit -m "Initial commit":這是我們輸入的指令。
  • [main (root-commit) 753522a] Initial commit:在 main 分支上的第一個 commit,此 commit 的雜湊碼前七碼為 753522a,訊息如上一行所輸入,為 Initial commit
  • 1 file changed, 1 insertion(+):跟前一個 commit(因這是第一個 commit,所以為空)相比,多了一行。
  • create mode 100644 hello.txt:以 100644 檔案模式(即正常不可執行檔,如 Day 10〈預存區 index 檔案出現了〉段落所描述)把 hello.txt 加進 commit。

此時用 git log 指令,可發現在 main 分支上成功建立第一筆 commit:
https://ithelp.ithome.com.tw/upload/images/20250911/20178513MtYgCXLotE.png

使用 git status 檢查則會發現:目前預存區已經沒有東西要進到 commit:
https://ithelp.ithome.com.tw/upload/images/20250911/20178513yxuUv3YZBV.png

.git/ 資料夾在 git commit 之後的變化

在終端機上,git commit 之後的訊息量看起來比 git add 多了不少,那在 .git/ 資料夾中,又發生了什麼變化呢?

.git/
├── hooks/
├── info/
│
├── logs/                                          # 新的資料夾
│   └── refs/
│   │    └── heads/
│   │         └── main
│   └── HEAD
│
├── objects/                    
│   ├── 75/                                         # 新的物件
│   │   └── 3522ad325474542413cebc5d71b0e11371bc03
│   ├── af/
│   │   └── 5626b4a114abcb82d63db7c8082c3c4756e51b
│   ├── ec/                                         # 新的物件
│   │   └── 947e3dd7a7752d078f1ed0cfde7457b21fef58
│   ├── info/
│   └── pack/
│
├── refs/
│   └── heads/
│        └── main    # 指向最新commit:753522ad325474542413cebc5d71b0e11371bc03
│   └── tags/
│
├── COMMIT_EDITMSG                                  # 新的檔案
├── config
├── description
├── HEAD
└── index                                           # index還在

一共有五個值得觀察的點,現在讓我們一一剖析:

  • 多了 logs/ 資料夾

有了 commit 之後就有歷程紀錄了,而所有變化的歷史都儲存在這,資料夾內共包含:

/refs/heads/main:裡面的內容為:

0000000000000000000000000000000000000000 753522ad325474542413cebc5d71b0e11371bc03 Ralph <ralph@ralphmail.com> 1755055384 +0800 commit (initial): Initial commit

HEAD:裡面的內容也是:

0000000000000000000000000000000000000000 753522ad325474542413cebc5d71b0e11371bc03 Ralph <ralph@ralphmail.com> 1755055384 +0800 commit (initial): Initial commit

現在我們一一拆解:

  • 0000000000000000000000000000000000000000:代表上代(parent)commit 的 SHA-1 值,但因為這是第一個 commit,所以就用 0000000... 表示。
  • 753522ad325474542413cebc5d71b0e11371bc03:最新 commit 的 SHA-1 值。
  • Ralph <ralph@ralphmail.com> 1755055384 +0800:作者資訊,包含:
    • 作者名字 Ralph
    • 電子郵件 <ralph@ralphmail.com>
    • 以時間戳記(timestamp)表示的 commit 的時間點 1755055384
    • 時區 +0800
  • commit (initial): Initial commit:跟 commit 有關的資訊,commit (initial) 為自動產生,表示這是第一個 commit,而使用者建立時輸入的 commit 訊息為 Initial commit

/refs/heads/main 中記錄所有在 main 分支上的 commit 歷史,而 HEAD 記錄的是 HEAD 這項參考的歷史。因為目前 HEAD 指向 main 分支,所以兩者內容一樣。

在終端機中,我們可以用 git reflog 查看裡面的資訊:
https://ithelp.ithome.com.tw/upload/images/20250911/20178513WMZXgW2k91.png

HEAD@{0} 表示 HEAD 這個參考目前所在的 commit,即雜湊碼為 753522a、commit 訊息為 Initial commit 者。

  • objects/ 資料夾裡面有兩個新物件

現在 objects/ 多了兩個物件,可以用 Day 10 學到的指令 git cat-file -t 來查看兩者的類別:
https://ithelp.ithome.com.tw/upload/images/20250911/2017851357sT2Ornps.png

原來 753522a 是 commit 物件,而 ec947e3 是 tree 物件!(其實以目前情況來說,透過 git log 看到 753522a 就知道誰是 commit、另外一個一定是 tree,但隨著結構逐漸變複雜,我們就沒辦法再這麼輕鬆推斷出物件類別)。

如果改用 git cat-file -p 查看物件內容,會看到什麼呢?

首先來看 753522a 這個 commit:
https://ithelp.ithome.com.tw/upload/images/20250911/201785135udZyxyws6.png

上面寫著如作者資訊、提交 commit 者資訊、commit 訊息等,但除此之外,還顯示著一樣重要資訊,且看黃箭頭指向的反白處:
https://ithelp.ithome.com.tw/upload/images/20250911/20178513KSOWjVf4Uv.png

有一個 tree 跟後面的雜湊碼 ec947e3...,這不就是剛剛說的 tree 物件嗎?

沒錯,在 Day 3 的文章中,我們提到一個 commit 形成時,這個新 commit 物件指向一個 tree 物件,而 753522a... 這個 commit 物件就是指向 ec947e3... 這個 tree 物件!可以畫出結構圖如下:
https://ithelp.ithome.com.tw/upload/images/20250911/20178513RHn6amyeD1.png

那再用 git cat-file -p 觀察 ec947e3 這個 tree 物件會看到什麼?
https://ithelp.ithome.com.tw/upload/images/20250911/20178513nmftvj2UOn.png

居然寫著 af5626b... 這個 blob 物件的檔案模式、物件種類、雜湊碼與檔案名稱!我們可以再延伸整個結構如下:
https://ithelp.ithome.com.tw/upload/images/20250911/20178513s3R38pBBW8.png

  • 多了 refs/heads/main

目前 HEAD 指向 main 分支,而 main 分支這個參考正指向最新 commit,因此內容為最新 commit 的雜湊碼:753522a...
https://ithelp.ithome.com.tw/upload/images/20250911/20178513gjFSOqickr.png

  • COMMIT_EDITMSG

裡面寫著最近一次 commit 的訊息,也就是 Initial commit

  • index

輸入 git ls-files --stage 一樣可以看到 100644 af5626b4a114abcb82d63db7c8082c3c4756e51b 0 hello_world.txt,跟 commit 之前相同。

再做一個新 commit

現在我們透過以下指令,創建內容跟剛剛一樣、但檔名不同的檔案:

echo "Hello, world!" > greetings.txt

經過 git addgit commit 後,先觀察 git log:
https://ithelp.ithome.com.tw/upload/images/20250911/201785135bzGYWkHSm.png

形成新的 commit,雜湊碼為 40d267c...,那現在 .git/ 資料夾長怎樣呢?

.git/
├── hooks/
├── info/
│
├── logs/
│   └── refs/
│   │    └── heads/
│   │         └── main                              # 多了一筆歷史紀錄
│   └── HEAD                                        # 多了一筆歷史紀錄
│
├── objects/
│   ├── 40/                                         # 新的物件
│   │   └── d267c0427c565d46a699ef1d4569f123bbcdeb                    
│   ├── 75/
│   │   └── 3522ad325474542413cebc5d71b0e11371bc03
│   ├── af/
│   │   └── 5626b4a114abcb82d63db7c8082c3c4756e51b
│   ├── da/                                         # 新的物件
│   │   └── 9a0a8a3b11f59c960217746f7291895a78d3fe
│   ├── ec/
│   │   └── 947e3dd7a7752d078f1ed0cfde7457b21fef58
│   ├── info/
│   └── pack/
│
├── refs/
│   └── heads/
│        └── main                     # 改指向最新commit:40d267c...
│   └── tags/
│
├── COMMIT_EDITMSG                                  # 變成Add greetings file
├── config
├── description
├── HEAD
└── index                                           # index還在
  • logs/ 資料夾

/refs/heads/mainHEAD 都多了一筆新的歷史紀錄,因此現在內容為:

0000000000000000000000000000000000000000 753522ad325474542413cebc5d71b0e11371bc03 Ralph <ralph@ralphmail.com> 1755055384 +0800 commit (initial): Initial commit
753522ad325474542413cebc5d71b0e11371bc03 40d267c0427c565d46a699ef1d4569f123bbcdeb Ralph <ralph@ralphmail.com> 1755069489 +0800 commit: Add greetings file

新增的第二筆資料開頭為 753522a...,表示雜湊碼 753522a... 的 commit 是它的上代,而新 commit 自己的雜湊碼則為 40d267c...

如果用 git reflog 在終端機查看歷史紀錄,則會看到:
https://ithelp.ithome.com.tw/upload/images/20250911/201785139mmpF7WJOF.png

現在 HEAD40d267c 這個 commit 上,而前一步(HEAD@{1})時的 HEAD753522a 這個 commit 上。

  • objects/ 資料夾

透過 git cat-file -p 指令查看新增物件的內容:
https://ithelp.ithome.com.tw/upload/images/20250911/20178513R5G2cXWPBm.png

可繪製新的結構圖如下:
https://ithelp.ithome.com.tw/upload/images/20250911/20178513spSnAOAIuF.png

非常特別的是,tree 物件跟 commit 物件都有新的,但兩者都指向同一個 blob 物件,為什麼?

blob 物件
新的 greetings.txt 檔案內容跟 hello.txt 一樣,都是 "Hello, world!",而 blob 物件只關注檔案內容,不管檔案名稱等元資料(metadata),因此兩個檔案的 blob 物件會是同一個。

tree 物件
雖然 blob 物件相同,但 tree 關注的是目錄(directory)結構,即便 greetings.txthello.txt 內容相同,但整體來說,目錄就是多一個檔案,整體結構改變,也就形成新的 tree 物件。
https://ithelp.ithome.com.tw/upload/images/20250911/20178513ON1LoCJulJ.png
因目錄結構不同,因此就算兩檔案內容一樣、共享 blob 物件,tree 物件依然是不同的

commit 物件
既然兩次 commit 指向的 tree 物件不同,commit 物件自然也不相同。值得留意的是,因為 commit 亦包含許多元資料,尤其兩相異 commit 的建立「時間」不可能相同,因此基本上每個 commit 都會形成自己的物件。

  • refs/heads/main

改成指向最新的 commit:40d267c...。

  • COMMIT_EDITMSG

變成最新的 commit 訊息 Add greetings file

  • index

透過 git ls-files --stage 指令觀察,可發現兩筆雜湊碼相同的檔案都在預存區:
https://ithelp.ithome.com.tw/upload/images/20250911/20178513R8HNIamrLY.png

現在我們理應也能完全理解在 Day 2 及 Day 3 時,使用底層管路指令做的事情:

  1. git hash-object:產生一組 blob 物件,並計算物件 ID。(Day 10 文章說明內容)
  2. git update-index --add:把第 1. 步產生的 blob 物件放進預存區。(Day 10 文章說明內容)
  3. git write-tree:把預存區的檔案拿來做出 tree 物件。
  4. git commit-tree:從指定的 tree 物件建立 commit 物件。
  5. git update-ref HEAD:把 HEAD 這個參考(reference)指向 4. 做出來的 commit 物件。

上層的瓷器指令以 git add 一次做完 1. 跟 2.、git commit 一次做完 3. 到 5.。
https://ithelp.ithome.com.tw/upload/images/20250911/201785139LJ0RUVqWV.png

原來當我們下這兩個平常到不行的指令後,git 默默在背後幫我們做了這麼多事情!

下篇文章,我們將一同探討更改既有檔案內容後,./git 資料夾發生的變化。

小結

經過 git commit 指令,會形成一 commit 物件,該物件會指向捕捉當下目錄結構的 tree 物件。

而在形成新 commit 時,.git/ 資料夾會發生以下改變:

  1. 多出 logs/ 資料夾,記錄 HEAD 與分支上的歷史紀錄,以便我們用 git reflog 追蹤。
  2. objects/ 資料夾多出 commit 物件與 tree 物件,可用 git cat-file -p 追蹤物件間的關係。
  3. refs/heads/:分支的參考改指向自己分支上最新的 commit。
  4. COMMIT_EDITMSG:寫著最新的 commit 訊息。
  5. index:保有原在預存區之檔案。

如果再新增一個內容相同的檔案,則會出現新的 commit 物件與 tree 物件,但新的 tree 物件依舊指向舊的 blob 物件。

參考資料

  1. git-commit
  2. git-log
  3. git-reflog
  4. [Git 筆記] Git Reflog
  5. git-cat-file

上一篇
Day 10-深入一點認識 Git:git add 之後,.git/ 資料夾發生了什麼變化?
下一篇
Day 12-深入一點點認識 Git:更改檔案內容後,.git/ 資料夾發生的變化
系列文
深入一點點認識 Git15
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言