iT邦幫忙

2025 iThome 鐵人賽

DAY 17
0
Software Development

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

Day 17-深入一點點認識 Git:git stash 把進度存到哪了?

  • 分享至 

  • xImage
  •  

在 Day 16 文章中,我們示範了用 git stash 暫存進度,但不會形成 commit。在這個單元,我們將深入探討 git stash 指令對 .git/ 資料夾造成的影響,藉此觀察 git stash 指令到底把進度暫存到哪。

實驗前置準備

先在 mainfeature 都各建立一個 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

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

實驗開始

先切回 main 分支:

git switch main

main 分支上做改動:

echo "Main version" > hello_world.txt

切換到 feature 分支前,先做 git stash
https://ithelp.ithome.com.tw/upload/images/20250917/20178513RLkGSiKJ5f.png

這時候 .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:
https://ithelp.ithome.com.tw/upload/images/20250917/20178513mPNnD6NtpF.png

這樣看起來,連同 refs/ 中多出來的 stash 這個參考,好像可以畫成以下結構:
https://ithelp.ithome.com.tw/upload/images/20250917/20178513bgCQPrKnQF.png

所以真的有多兩個 commit 嗎?我們再用 git log 看看:
https://ithelp.ithome.com.tw/upload/images/20250917/201785134oxY8G46M6.png

沒有新的 commit!所以這兩個多出來的 commit 到底是什麼?

我們先下以下指令觀察:

git log refs/stash

得到的結果如下,這樣與這次 stash 有關的三個 commit 都出來了,由下而上包含:

  1. cd7da55...:在 git stash 前最後的 commit。
  2. dd7b092...:在 git stash 當下,預存區 index 的快照。
  3. 56aa497...stash 這個 commit,上代包含另外兩個 commit。

https://ithelp.ithome.com.tw/upload/images/20250917/201785130jRQY18ovb.png

以目前來說,工作目錄是我有做的更動,因此最終指向的 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

git add 之後再 git stash

現在切回 feature 分支:

git switch feature

再更改 feature 分支上的檔案:

echo "Feature second version" > hello_world.txt

這次做 git add

git add hello_world.txt

這時候再做 git stash 呢?
https://ithelp.ithome.com.tw/upload/images/20250917/201785135ZFJrcPFv3.png

此時 .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 指令:
https://ithelp.ithome.com.tw/upload/images/20250917/20178513VqlxRQG3SX.png

可繪製出下列關係圖,新的 stash 在上半部:
https://ithelp.ithome.com.tw/upload/images/20250917/2017851331aZikhqQm.png

而如果再下 git log refs/stash 呢?
https://ithelp.ithome.com.tw/upload/images/20250917/20178513B2Y13G23po.png

最下面兩個為 mainfeature 分支上最新的 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 物件(下圖橘色圈圈):
https://ithelp.ithome.com.tw/upload/images/20250917/20178513hp2u4i8QkG.png

現在切回 main 分支:

git switch main

git stash list 觀察目前有哪些 stash
https://ithelp.ithome.com.tw/upload/images/20250917/20178513oFOJQ9W9rM.png

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 addgit commit 後,切回 feature 分支:
https://ithelp.ithome.com.tw/upload/images/20250917/20178513mfpznySLU1.png

接著用以下指令,把 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/refsrefs/stash 檔案都消失了!但是之前因為兩次 git stash 做出來的物件全部都還在。

小結

使用 git stash 將檔案暫存時,一共會生成以下 git 物件:

  1. 代表暫存狀態的 blob 物件。
  2. 指向 1. 的 blob 物件之 tree 物件。
  3. 代表預存區 index 暫存狀態的 commit 物件,上代為對應分支上最新的 commit 物件。
  4. 代表工作目錄暫存狀態的 commit 物件,stash 這項參考指向此,上代包含 3. 中代表預存區 index 的 commit 物件,以及對應分支上最新的 commit 物件。

由 3. 生成的 commit 物件會指向最近一次放在預存區 index 對應的 tree 物件,如果是經 git addgit stash,會指向經 git stash 之後才生成之 tree 物件;如果未經 git addgit stash,則會指向原本就已經存在的 tree 物件。

當以 git stash pop 把暫存狀態抓回來後,.git/refs.git/logs/refsstash 檔案會把被重新拉回去的暫存狀態資訊移除,但因為 git stash 指令生成的 git 物件不會消失。

參考資料

  1. git-stash

上一篇
Day 16-深入一點點認識 Git:用 git stash 先暫存進度
下一篇
Day 18-深入一點點認識 Git:git cherry-pick 到底怎麼搬 commit 的?
系列文
深入一點點認識 Git22
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言