在 Day 16 文章中,我們示範了用 git stash
暫存進度,但不會形成 commit。在這個單元,我們將深入探討 git stash
指令對 .git/
資料夾造成的影響,藉此觀察 git stash
指令到底把進度暫存到哪。
先在 main
跟 feature
都各建立一個 commit。
main
分支建立 commit:echo "Hello, world" > hello_world.txt
把檔案加到預存區:
git add hello_world.txt
形成一個 commit:
git commit -m "Initial commit"
feature
分支也建立 commit:feature
分支:git branch feature
切換到 feature
分支:
git switch feature
在 feature
分支建立與 main
分支上同名的檔案:
echo "Feature version" > hello_world.txt
把檔案加到預存區:
git add hello_world.txt
在 feature
分支上形成一個 commit:
git commit -m "Feature change"
目前資料夾結構如下:
.git/
├── hooks/
├── info/
│
├── logs/
│ ├── refs/
│ │ └── heads/
│ │ ├── feature
│ │ └── main
│ └── HEAD
│
├── objects/
│ ├── 9e/ # tree物件
│ │ └── 206bdfe0f75cf9011bc37c3a77528b50993a7d
│ ├── a5/ # blob物件
│ │ └── c19667710254f835085b99726e523457150e03
│ ├── cc/ # commit物件
│ │ └── 745c806c5dabd2ca4bbdc10e9fa2a60245cff3
│ ├── ce/ # commit物件
│ │ └── 7da55f5b7e61fe3954f39bf9cf18845b5b8a44
│ ├── db/ # blob物件
│ │ └── c69068b98a226af5471ad0543d5ad3316b4cb7
│ ├── e5/ # tree物件
│ │ └── 9896eb1f48ef9c75e675692e23966e5ba7cdfb
│ └── info/
│ └── pack/
│
├── refs/
│ ├── heads/
│ │ ├── feature # 指向cc745c806c5dabd2ca4bbdc10e9fa2a60245cff3
│ │ └── main # 指向ce7da55f5b7e61fe3954f39bf9cf18845b5b8a44
│ └── tags/
│
├── COMMIT_EDITMSG
├── config
├── description
├── HEAD
└── index
圖示如下:
先切回 main
分支:
git switch main
在 main
分支上做改動:
echo "Main version" > hello_world.txt
切換到 feature
分支前,先做 git stash
:
這時候 .git/
資料夾長什麼樣子呢?
.git/
├── hooks/
├── info/
│
├── logs/
│ ├── refs/
│ │ ├── heads/
│ │ │ ├── feature
│ │ │ └── main
│ │ └── stash # 內文為0000000000000000000000000000000000000000 56aa497609b28445020882744648ce5964ee0805 Ralph <ralph@ralphmail.com> 1755743231 +0800 WIP on main: ce7da55 Initial commit
│ └── HEAD
│
├── objects/
│ ├── 4b/ # 新tree物件
│ │ └── 17fc94a51a7fd3dfddf8e223352cce1a5b21c2
│ ├── 9e/
│ │ └── 206bdfe0f75cf9011bc37c3a77528b50993a7d
│ ├── 50/ # 新blob物件
│ │ └── 55b9198f7069db185ec0f12534e44bfd5c9623
│ ├── 56/ # 新commit物件
│ │ └── aa497609b28445020882744648ce5964ee0805
│ ├── a5/
│ │ └── c19667710254f835085b99726e523457150e03
│ ├── cc/
│ │ └── 745c806c5dabd2ca4bbdc10e9fa2a60245cff3
│ ├── ce/
│ │ └── 7da55f5b7e61fe3954f39bf9cf18845b5b8a44
│ ├── db/
│ │ └── c69068b98a226af5471ad0543d5ad3316b4cb7
│ ├── dd/ # 新commit物件
│ │ └── 7b0929cbfede635b9d7eb63c2a1f579087c0bc
│ ├── e5/
│ │ └── 9896eb1f48ef9c75e675692e23966e5ba7cdfb
│ └── info/
│ └── pack/
│
├── refs/
│ ├── heads/
│ │ ├── feature
│ │ └── main
│ ├── tags/
│ └── stash # 指向56aa497609b28445020882744648ce5964ee0805
│
├── COMMIT_EDITMSG
├── config
├── description
├── HEAD
├── index
└── ORIG_HEAD # 指向ce7da55f5b7e61fe3954f39bf9cf18845b5b8a44
居然多了好幾個物件!再透過 git cat-file
指令檢視,看起來好像多了一個 commit...哦不,是兩個 commit:
這樣看起來,連同 refs/
中多出來的 stash
這個參考,好像可以畫成以下結構:
所以真的有多兩個 commit 嗎?我們再用 git log
看看:
沒有新的 commit!所以這兩個多出來的 commit 到底是什麼?
我們先下以下指令觀察:
git log refs/stash
得到的結果如下,這樣與這次 stash
有關的三個 commit 都出來了,由下而上包含:
cd7da55...
:在 git stash
前最後的 commit。dd7b092...
:在 git stash
當下,預存區 index
的快照。56aa497...
:stash
這個 commit,上代包含另外兩個 commit。以目前來說,工作目錄是我有做的更動,因此最終指向的 blob 物件內容為 "Main Version"
。
實際打開 .git/logs/refs/stash
看到的紀錄,跟 Day 11 介紹 git commit 之後 logs/
裡存的內容十分相似,依序為上代雜湊碼 0000000...
、自己的雜湊碼 56aa497...
、作者資訊 Ralph...
、commit 訊息 WIP on main...
:
0000000000000000000000000000000000000000 56aa497609b28445020882744648ce5964ee0805 Ralph <ralph@ralphmail.com> 1755743231 +0800 WIP on main: ce7da55 Initial commit
現在切回 feature
分支:
git switch feature
再更改 feature
分支上的檔案:
echo "Feature second version" > hello_world.txt
這次做 git add
:
git add hello_world.txt
這時候再做 git stash
呢?
此時 .git/
資料夾長這樣:
.git/
├── hooks/
├── info/
│
├── logs/
│ ├── refs/
│ │ ├── heads/ # 沒有新的commit,因此這邊無變化
│ │ │ ├── feature
│ │ │ └── main
│ │ └── stash # 多一行56aa497609b28445020882744648ce5964ee0805 2881edecc2e2c832858db96df2d29abc511d3a93 Ralph <ralph@ralphmail.com> 1755748433 +0800 WIP on feature: cc745c8 Feature change
│ └── HEAD
│
├── objects/
│ ├── 4b/
│ │ └── 17fc94a51a7fd3dfddf8e223352cce1a5b21c2
│ ├── 9e/
│ │ └── 206bdfe0f75cf9011bc37c3a77528b50993a7d
│ ├── 28/ # 新commit物件
│ │ └── 81edecc2e2c832858db96df2d29abc511d3a93
│ ├── 50/
│ │ └── 55b9198f7069db185ec0f12534e44bfd5c9623
│ ├── 56/
│ │ └── aa497609b28445020882744648ce5964ee0805
│ ├── 85/ # 新blob物件
│ │ └── a9b9543ee7fdc04fee7d31a56519e594337ca3
│ ├── a5/
│ │ └── c19667710254f835085b99726e523457150e03
│ ├── b9/ # 新tree物件
│ │ └── bd12ed57019858943deb7b65df681942fdab8e
│ ├── cc/
│ │ └── 745c806c5dabd2ca4bbdc10e9fa2a60245cff3
│ ├── ce/
│ │ └── 7da55f5b7e61fe3954f39bf9cf18845b5b8a44
│ ├── db/
│ │ └── c69068b98a226af5471ad0543d5ad3316b4cb7
│ ├── dd/
│ │ └── 7b0929cbfede635b9d7eb63c2a1f579087c0bc
│ ├── e5/
│ │ └── 9896eb1f48ef9c75e675692e23966e5ba7cdfb
│ ├── e6/ # 新commit物件
│ │ └── 747c3633027d55a67eb1d52e1fd36c12719d42
│ └── info/
│ └── pack/
│
├── refs/
│ ├── heads/
│ │ ├── feature
│ │ └── main
│ ├── tags/
│ └── stash # 內文為2881edecc2e2c832858db96df2d29abc511d3a93
│
├── COMMIT_EDITMSG
├── config
├── description
├── HEAD
├── index
└── ORIG_HEAD # 內文仍為cc745c806c5dabd2ca4bbdc10e9fa2a60245cff3
根據多次 git cat-file
指令:
可繪製出下列關係圖,新的 stash
在上半部:
而如果再下 git log refs/stash
呢?
最下面兩個為 main
跟 feature
分支上最新的 commit,往上 e6747c3...
是預存區經 git stash
形成的 commit、最上面的 2881ede...
則是工作目錄經 git stash
形成的 commit。
值得留意的是,在 main
分支上,因為沒有 git add
就直接 git stash
,所以索引的 commit 仍是指向前一次 git add
做出的 tree 物件(下圖紫色圈圈);在 feature
分支上,在 git stash
前有 git add
,因此索引的 commit 是指向經 git stash
之後才出現的 tree 物件(下圖橘色圈圈):
現在切回 main
分支:
git switch main
以 git stash list
觀察目前有哪些 stash
:
在 main
分支上的 stash
是「上上一個」,因此為 stash@{1}
,接著把下列指令把該暫存工作狀態拿回來:
git stash pop stash@{1}
這時 .git/
資料夾變成:
.git/
├── hooks/
├── info/
│
├── logs/
│ ├── refs/
│ │ ├── heads/
│ │ │ ├── feature
│ │ │ └── main
│ │ └── stash # 變成0000000000000000000000000000000000000000 2881edecc2e2c832858db96df2d29abc511d3a93 Ralph <ralph@ralphmail.com> 1755748433 +0800 WIP on feature: cc745c8 Feature change
│ └── HEAD
│
├── objects/ # 無變化,做出來的物件不會消失
│ ├── ...
│ └── info/
│ └── pack/
│
├── refs/
│ ├── heads/
│ │ ├── feature
│ │ └── main
│ ├── tags/
│ └── stash # 仍為2881edecc2e2c832858db96df2d29abc511d3a93
│
├── COMMIT_EDITMSG
├── config
├── description
├── HEAD
├── index
└── ORIG_HEAD # 內文仍為cc745c806c5dabd2ca4bbdc10e9fa2a60245cff3
在 /logs/refs/stash
中,變成只有下面這條:
0000000000000000000000000000000000000000 2881edecc2e2c832858db96df2d29abc511d3a93 Ralph <ralph@ralphmail.com> 1755748433 +0800 WIP on feature: cc745c8 Feature change
在 main
分支上的 stash
commit 紀錄消失了,2881ede...
變成首個 stash
有關的 commit。
在 main
分支上下 git add
與 git commit
後,切回 feature
分支:
接著用以下指令,把 feature
分支上的 stash
抓回來:
git stash pop stash@{0}
此時 .git/
資料夾變成:
.git/
├── hooks/
├── info/
│
├── logs/
│ ├── refs/
│ │ └── heads/ # stash檔案消失
│ │ ├── feature
│ │ └── main
│ └── HEAD
│
├── objects/ # 無變化
│ ├── ...
│ └── info/
│ └── pack/
│
├── refs/ # stash檔案消失
│ ├── heads/
│ │ ├── feature
│ │ └── main
│ └── tags/
│
├── COMMIT_EDITMSG
├── config
├── description
├── HEAD
├── index
└── ORIG_HEAD # 內文仍為cc745c806c5dabd2ca4bbdc10e9fa2a60245cff3
/logs/refs
跟 refs/
的 stash
檔案都消失了!但是之前因為兩次 git stash
做出來的物件全部都還在。
使用 git stash
將檔案暫存時,一共會生成以下 git 物件:
index
暫存狀態的 commit 物件,上代為對應分支上最新的 commit 物件。stash
這項參考指向此,上代包含 3. 中代表預存區 index
的 commit 物件,以及對應分支上最新的 commit 物件。由 3. 生成的 commit 物件會指向最近一次放在預存區 index
對應的 tree 物件,如果是經 git add
才 git stash
,會指向經 git stash
之後才生成之 tree 物件;如果未經 git add
即 git stash
,則會指向原本就已經存在的 tree 物件。
當以 git stash pop
把暫存狀態抓回來後,.git/refs
與 .git/logs/refs
的 stash
檔案會把被重新拉回去的暫存狀態資訊移除,但因為 git stash
指令生成的 git 物件不會消失。