iT邦幫忙

2025 iThome 鐵人賽

DAY 17
0
Software Development

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

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

  • 分享至 

  • xImage
  •  

Medium 好讀版點此。


在 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

圖示如下:
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

最下面兩個為 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 物件(下圖橘色圈圈):
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 add 與 git 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/refs 跟 refs/ 的 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 add 才 git stash,會指向經 git stash 之後才生成之 tree 物件;如果未經 git add 即 git stash,則會指向原本就已經存在的 tree 物件。

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

參考資料

  1. git-stash

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

尚未有邦友留言

立即登入留言