iT邦幫忙

2025 iThome 鐵人賽

DAY 12
0
Software Development

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

Day 12-深入一點點認識 Git:更改檔案內容後,.git/ 資料夾發生的變化

  • 分享至 

  • xImage
  •  

在 Day 10 文章中,我們觀察到輸入 git add 指令後,objects/ 資料夾會出現一個新的 blob 物件,物件相關資訊存在 index 裡;在 Day 11 文章中,我們則知道再輸入 git commit 指令後,.git/ 資料夾還會跑出記錄 commit 訊息、歷程紀錄的檔案,/objects 資料夾則出現新的 tree 與 commit 物件 。

那如果檔案經過更改,再重新輸入 git addgit commit 指令後,./git 資料夾又會怎麼變化?

前置準備

我們先依序在一個經 git init 的資料夾中,透過 git addgit commit 建立第一個 commit 如下:
https://ithelp.ithome.com.tw/upload/images/20250912/20178513V2qonAnWvG.png

目前的 .git/ 資料夾結構如下:

.git/
├── hooks/             
├── info/
│
├── logs/
│   ├── refs/
│   │   └── heads/
│   │       └── main  # 出現0000000000000000000000000000000000000000 f45eed0f0713df58489b2e88d06abc8f2dfc19fb Ralph <ralph@ralphmail.com> 1755133596 +0800 commit (initial): Initial commit
│   └── HEAD   # 出現0000000000000000000000000000000000000000 f45eed0f0713df58489b2e88d06abc8f2dfc19fb Ralph <ralph@ralphmail.com> 1755133596 +0800 commit (initial): Initial commit
│
├── objects/
│   ├── 55/                                         # tree物件
│   │   └── a0a031d13860599847a316c42433ea148735d5
│   ├── af/                                         # blob物件
│   │   └── 5626b4a114abcb82d63db7c8082c3c4756e51b
│   ├── f4/                                         # commit物件
│   │   └── 5eed0f0713df58489b2e88d06abc8f2dfc19fb
│   ├── info/
│   └── pack/
│
├── refs/
│   ├── heads/
│   │   └── main    # 指向f45eed0f0713df58489b2e88d06abc8f2dfc19fb
│   └── tags/
│
├── COMMIT_EDITMSG  # Initial commit
├── config
├── description
├── HEAD
└── index    # 100644 af5626b4a114abcb82d63db7c8082c3c4756e51b 0       hello_world.txt

圖示如下:
https://ithelp.ithome.com.tw/upload/images/20250912/20178513W6BL38Q3fM.png

實驗開始

我們用以下指令把 hello_world.txt 的檔案內容從 Hello, world! 改為 Hello, world(把驚嘆號拿掉):

echo "Hello, world" > hello_world.txt

git add

現在再次輸入 git add "hello_world.txt",把修改後的檔案再次放到預存區,這時 ./git 資料夾變成下面這樣:

.git/
├── hooks/             
├── info/
│
├── logs/
│   ├── refs/
│   │   └── heads/
│   │       └── main   # 仍為0000000000000000000000000000000000000000 f45eed0f0713df58489b2e88d06abc8f2dfc19fb Ralph <ralph@ralphmail.com> 1755133596 +0800 commit (initial): Initial commit
│   └── HEAD       # 仍為0000000000000000000000000000000000000000 f45eed0f0713df58489b2e88d06abc8f2dfc19fb Ralph <ralph@ralphmail.com> 1755133596 +0800 commit (initial): Initial commit
│
├── objects/
│   ├── 55/
│   │   └── a0a031d13860599847a316c42433ea148735d5
│   ├── a5/                                         # 新增的blob物件
│   │   └── c19667710254f835085b99726e523457150e03
│   ├── af/
│   │   └── 5626b4a114abcb82d63db7c8082c3c4756e51b
│   ├── f4/
│   │   └── 5eed0f0713df58489b2e88d06abc8f2dfc19fb
│   ├── info/
│   └── pack/
│
├── refs/
│   ├── heads/
│   │   └── main    # 依舊指向f45eed0f0713df58489b2e88d06abc8f2dfc19fb
│   └── tags/
│
├── COMMIT_EDITMSG  # 仍為Initial commit
├── config
├── description
├── HEAD
└── index    # 變成100644 a5c19667710254f835085b99726e523457150e03 0       hello_world.txt

./objects 出現一個新的 blob 物件 a5c1966...,其他的舊 git 物件都還在,做出新的 blob 物件不會取代既存物件;至於 index 內容,經 git ls-files --stage 檢視,會發現當中的檔案即為新的 blob 物件 a5c1966...

git commit

再輸入以下指令:

git commit -m "Remove exclamation mark"

形成第二筆快照,以 git log 觀察如下:
https://ithelp.ithome.com.tw/upload/images/20250912/20178513b4e2hkgRQ9.png

此時 ./git 資料夾變成下面這樣:

.git/
├── hooks/             
├── info/
│
├── logs/
│   ├── refs/
│   │   └── heads/
│   │       └── main   # 新增e0aeb87bb971732ad85f07cdba279cea5051462f Ralph <ralph@ralphmail.com> 1755135215 +0800 commit: Remove exclamation mark
│   └── HEAD           # 新增e0aeb87bb971732ad85f07cdba279cea5051462f Ralph <ralph@ralphmail.com> 1755135215 +0800 commit: Remove exclamation mark
│
├── objects/
│   ├── 55/
│   │   └── a0a031d13860599847a316c42433ea148735d5
│   ├── a5/
│   │   └── c19667710254f835085b99726e523457150e03
│   ├── af/
│   │   └── 5626b4a114abcb82d63db7c8082c3c4756e51b
│   ├── e0/                                          # 新增的commit物件
│   │   └── aeb87bb971732ad85f07cdba279cea5051462f
│   ├── e5/                                          # 新增的tree物件
│   │   └── 9896eb1f48ef9c75e675692e23966e5ba7cdfb
│   ├── f4/
│   │   └── 5eed0f0713df58489b2e88d06abc8f2dfc19fb
│   ├── info/
│   └── pack/
│
├── refs/
│   ├── heads/
│   │   └── main     # 改為指向e0aeb87bb971732ad85f07cdba279cea5051462f
│   └── tags/
│
├── COMMIT_EDITMSG   # 改為Remove exclamation mark
├── config
├── description
├── HEAD
└── index            # 維持100644 a5c19667710254f835085b99726e523457150e03 0       hello_world.txt

.git/ 資料夾變化詳述如下:

  • /objects 資料夾
    多了 e0aeb87...e59896e... 兩個 git 物件,經 git cat-file -t <object hash> 得知前者為 commit 物件、後者為 tree 物件。

  • /refs 資料夾
    /heads 資料夾裡的 main 變為指向最新的 commit 物件 e0aeb87...

  • /logs 資料夾
    /heads 資料夾裡的 main ,還有 HEAD 檔案皆著如下內容:

0000000000000000000000000000000000000000 f45eed0f0713df58489b2e88d06abc8f2dfc19fb Ralph <ralph@ralphmail.com> 1755133596 +0800 commit (initial): Initial commit
f45eed0f0713df58489b2e88d06abc8f2dfc19fb e0aeb87bb971732ad85f07cdba279cea5051462f Ralph <ralph@ralphmail.com> 1755135215 +0800 commit: Remove exclamation mark

因為 HEAD 指向 main 分支,所以兩者歷史相同。

第一條 0000000... f45eed0... Ralph <ralph@ralphmail.com> 1755133596 +0800 commit (initial): Initial commit 為第一個 commit、第二條 f45eed0... e0aeb87... Ralph <ralph@ralphmail.com> 1755135215 +0800 commit: Remove exclamation mark 為第二個 commit。

值得留意的是,第二條 commit 最開頭是 f45eed0...,表示這個 commit 的上代(parent)是雜湊碼為 f45eed0... 的 commit。

  • COMMIT_EDITMSG 檔案
    變成最新的 commit 訊息 "Remove exclamation mark"

  • index
    變成 a5c1966... 這個新的 blob 物件。

目前整體結構圖示如下:
https://ithelp.ithome.com.tw/upload/images/20250912/20178513epYoqU9afE.png

同時有變跟不變的檔案

現不改變 hello_world.txt 檔案(內文保持 Hello, world),但新增一個名為 picture.png 的圖片如下:
https://ithelp.ithome.com.tw/upload/images/20250912/20178513eCGuf4y69S.png

此時如果輸入 git add . 把所有的東西都放到預存區,那會發生以下何者?

  1. picture.png 連同沒有發生變化的 hello_world.txt 都被放到預存區。
  2. 只有新增的 picture.png 被放到預存區?

找到答案快的方式就是做實驗,那我們直接下 git add . 指令後,觀察 /.git 資料夾的變化吧!

.git/
├── ...
│
├── objects/
│   ├── 42/                                           # 新增的blob物件
│   │   └── 4e4101990ef0de3b2a01388025c2bc57c45739
│   ├── 55/
│   │   └── a0a031d13860599847a316c42433ea148735d5
│   ├── a5/
│   │   └── c19667710254f835085b99726e523457150e03
│   ├── af/
│   │   └── 5626b4a114abcb82d63db7c8082c3c4756e51b
│   ├── e0/
│   │   └── aeb87bb971732ad85f07cdba279cea5051462f
│   ├── e5/
│   │   └── 9896eb1f48ef9c75e675692e23966e5ba7cdfb
│   ├── f4/
│   │   └── 5eed0f0713df58489b2e88d06abc8f2dfc19fb
│   ├── info/
│   └── pack/
│
└── ...

多了一個 424e410... 物件!如果再輸入 git ls-files --stage 則會跑出:
https://ithelp.ithome.com.tw/upload/images/20250912/20178513w2bLBoZZCA.png

多了一個 424e410... 的 blob 物件,對應檔案為 picture.png;另在 hello_world.txt 移除驚嘆號後跑出的 blob 物件 a5c1966... 依然還在!

但因為 hello_world.txt 內容沒有發生變化,因此雜湊碼是同一個,表示仍為原來的 blob 物件,/objects 也確實沒有新的 hello, world.txt 相關物件。

而輸入 git status 則會發現目前 picture.png 已經進入預存區:
https://ithelp.ithome.com.tw/upload/images/20250912/20178513zn0OZEV1Nf.png

再輸入 git commit 後,.git/objects 資料夾變成這樣:

.git/
├── ...
│
├── objects/
│   ├── 2a/                                         # 新增的tree物件
│   │   └── 18a7597d187a1be5c8442173b990dc41dab8f3
│   ├── 04/                                         # 新增的commit物件
│   │   └── c9e78f6ad4bff981b44069199febb2e2dc272c
│   ├── 42/
│   │   └── 4e4101990ef0de3b2a01388025c2bc57c45739
│   ├── 55/
│   │   └── a0a031d13860599847a316c42433ea148735d5
│   ├── a5/
│   │   └── c19667710254f835085b99726e523457150e03
│   ├── af/
│   │   └── 5626b4a114abcb82d63db7c8082c3c4756e51b
│   ├── e0/
│   │   └── aeb87bb971732ad85f07cdba279cea5051462f
│   ├── e5/
│   │   └── 9896eb1f48ef9c75e675692e23966e5ba7cdfb
│   ├── f4/
│   │   └── 5eed0f0713df58489b2e88d06abc8f2dfc19fb
│   ├── info/
│   └── pack/
│
└── ...

哇!多了一個新的 tree 跟 blob 物件,現在我們可以再透過 git cat-file -p 找到的資訊,繪製關係圖。

透過 git cat-file -p 繪製關係圖

透過上述資料夾結構,我們知道 04c9e78... 是最新的 commit,再用 git cat-file -p 得知其上代(parent)為 e0aeb87...(下圖黃色箭頭處)、指向的 tree 物件為 2a18a75...(下圖綠色箭頭處):

https://ithelp.ithome.com.tw/upload/images/20250912/201785137fyjd2bBsa.png

圖示如下:
https://ithelp.ithome.com.tw/upload/images/20250912/20178513yIIuiZqH7u.png

現在關鍵的問題來了!2a18a75... 這個 tree 物件指向的目錄結構,包含新增的檔案 picture.png 與上一個 commit 就有、且內容不變的 hello_world.txt,那 tree 會指向誰?

我們以 git cat-file -p 2a18a75 找答案:
https://ithelp.ithome.com.tw/upload/images/20250912/20178513npy4r0BqZo.png

原來 2a18a75... 這個 tree 物件同時指向 a5c1966...424e410... 兩個 blob 物件。

https://ithelp.ithome.com.tw/upload/images/20250912/20178513jCWUZpaOup.png

由這樣的實驗可以印證 Day 3 文章所述,只要 blob 物件內容未改變,新的 tree 物件都會指向舊有 commit 中的 blob 物件。

小結

當檔案發生改變、重新 commit 時, .git/ 資料夾發生以下變化:

  1. /logs:記錄 HEADmain 分支從舊到新歷次 commit 資訊。
  2. /objects 資料夾新增最新 commit 與 tree 的物件;所有舊的 blob、tree 與 commit 物件皆存在。
  3. /refs/heads/ 改為指向最新的 commit 物件。
  4. COMMIT_EDITMSG:改成最新的 commit 訊息。
  5. index:變成最近一次 git add 之後形成的新 blob 物件。

如果新 commit 中同時有發生改變與未發生改變的檔案,則未發生改變者不會做出新 blob 物件,新做出來的 tree 物件會指向舊的 blob 物件。

參考資料

  1. git-commit
  2. Git Internals - Git Objects
  3. git-cat-file

上一篇
Day 11-深入一點點認識 Git:git commit 之後,.git/ 資料夾發生了什麼變化?
下一篇
Day 13 - 深入一點點認識 Git:git reset 三種不同模式與 git revert 對 .git/ 資料夾的影響
系列文
深入一點點認識 Git15
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言