在 Day 10 文章中,我們觀察到輸入 git add
指令後,objects/
資料夾會出現一個新的 blob 物件,物件相關資訊存在 index
裡;在 Day 11 文章中,我們則知道再輸入 git commit
指令後,.git/
資料夾還會跑出記錄 commit 訊息、歷程紀錄的檔案,/objects
資料夾則出現新的 tree 與 commit 物件 。
那如果檔案經過更改,再重新輸入 git add
與 git commit
指令後,./git
資料夾又會怎麼變化?
我們先依序在一個經 git init
的資料夾中,透過 git add
與 git commit
建立第一個 commit 如下:
目前的 .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
圖示如下:
我們用以下指令把 hello_world.txt
的檔案內容從 Hello, world!
改為 Hello, world
(把驚嘆號拿掉):
echo "Hello, world" > hello_world.txt
現在再次輸入 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 -m "Remove exclamation mark"
形成第二筆快照,以 git log
觀察如下:
此時 ./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 物件。
目前整體結構圖示如下:
現不改變 hello_world.txt
檔案(內文保持 Hello, world
),但新增一個名為 picture.png
的圖片如下:
此時如果輸入 git add .
把所有的東西都放到預存區,那會發生以下何者?
picture.png
連同沒有發生變化的 hello_world.txt
都被放到預存區。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
則會跑出:
多了一個 424e410...
的 blob 物件,對應檔案為 picture.png
;另在 hello_world.txt
移除驚嘆號後跑出的 blob 物件 a5c1966...
依然還在!
但因為 hello_world.txt
內容沒有發生變化,因此雜湊碼是同一個,表示仍為原來的 blob 物件,/objects
也確實沒有新的 hello, world.txt
相關物件。
而輸入 git status
則會發現目前 picture.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
找到的資訊,繪製關係圖。
透過上述資料夾結構,我們知道 04c9e78...
是最新的 commit,再用 git cat-file -p
得知其上代(parent)為 e0aeb87...
(下圖黃色箭頭處)、指向的 tree 物件為 2a18a75...
(下圖綠色箭頭處):
圖示如下:
現在關鍵的問題來了!2a18a75...
這個 tree 物件指向的目錄結構,包含新增的檔案 picture.png
與上一個 commit 就有、且內容不變的 hello_world.txt
,那 tree 會指向誰?
我們以 git cat-file -p 2a18a75
找答案:
原來 2a18a75...
這個 tree 物件同時指向 a5c1966...
與 424e410...
兩個 blob 物件。
由這樣的實驗可以印證 Day 3 文章所述,只要 blob 物件內容未改變,新的 tree 物件都會指向舊有 commit 中的 blob 物件。
當檔案發生改變、重新 commit 時, .git/
資料夾發生以下變化:
/logs
:記錄 HEAD
及 main
分支從舊到新歷次 commit 資訊。/objects
資料夾新增最新 commit 與 tree 的物件;所有舊的 blob、tree 與 commit 物件皆存在。/refs/heads/
改為指向最新的 commit 物件。COMMIT_EDITMSG
:改成最新的 commit 訊息。index
:變成最近一次 git add
之後形成的新 blob 物件。如果新 commit 中同時有發生改變與未發生改變的檔案,則未發生改變者不會做出新 blob 物件,新做出來的 tree 物件會指向舊的 blob 物件。