當我們正在進行某項工作,突然因為老闆/專案經理等人要求或種種原因,要先把手上事情擱著,先處理更急的事情(作業系統中的「上下文交換(context switch)」?),那既有進度要怎麼辦?
第一個方法是先 git commit
成一個版本,之後再回來用 git reset
把這暫時的版本退掉,但這樣如果沒弄好(忘記退版或沒整理好 commits),就會變成有個「事情做到一半的 commit」,有沒有什麼可以先把進度存著、但不要成為 commit 的方法?
要達到這個目的,可以改用 git stash
指令,以下透過動手做實驗,觀察 git stash
的運作方式跟 git commit
有何不同。
現在假設我們同時在 main
跟 feature
兩分支上工作。首先是在 main
分支上建立檔案:
echo "Hello, world" > hello_world.txt
接著把 hello_world.txt
加進預存區:
git add hello_world.txt
再形成一個 commit:
git commit -m "Initial commit"
再創建 feature
分支:
git branch feature
切換到 feature
分支上:
git switch feature
在 feature
分支建立同樣名為 hello_world.txt
的檔案,但內文與 main
分支不同,為 "Feature version"
:
echo "Feature version" > hello_world.txt
在這裡一樣把檔案加到預存區:
git add hello_world.txt
再形成一個 commit:
git commit -m "Feature change"
現在 .git/
資料夾長這樣:
.git/
├── hooks/
├── info/
│
├── logs/
│ ├── refs/
│ │ └── heads/ # 有main跟feature兩分支的歷史紀錄
│ │ ├── feature
│ │ └── main
│ └── HEAD # 隨著分支切換,會有兩分支的commit在歷史紀錄中
│
├── objects/
│ ├── 9e/ # tree物件
│ │ └── 206bdfe0f75cf9011bc37c3a77528b50993a7d
│ ├── 70/ # commit物件
│ │ └── 1703a5c1a78cac150449da66bb8975249db1f7
│ ├── a5/ # blob物件
│ │ └── c19667710254f835085b99726e523457150e03
│ ├── db/ # blob物件
│ │ └── c69068b98a226af5471ad0543d5ad3316b4cb7
│ ├── e5/ # tree物件
│ │ └── 9896eb1f48ef9c75e675692e23966e5ba7cdfb
│ ├── fe/ # commit物件
│ │ └── a8f64e7cfad13fe622553464a3df357a28c919
│ └── info/
│ └── pack/
│
├── refs/
│ ├── heads/
│ │ ├── feature # 指向fea8f64e7cfad13fe622553464a3df357a28c919
│ │ └── main # 指向701703a5c1a78cac150449da66bb8975249db1f7
│ └── tags/
│
├── COMMIT_EDITMSG
├── config
├── description
├── HEAD # ref: refs/heads/feature
└── index
圖示如下:
這時切回 main
分支:
git switch main
假設繼續針對這個 hello_world.txt
做一些改動:
echo "Main version" > hello_world.txt
此時老闆突然說 feature
分支還有一些急事要處理,要我們先擱置 main
分支上的事情,於是切回 feature
分支:
git switch feature
這時卻發現:切不過去!
怎麼會這樣?
這其實是 git 在保護我們!在 main
分支上,我們已經針對 hello_world.txt
做了一些改動,但並沒有將這個改動變成一個 commit,如果直接切到 feature
分支,feature
分支並沒有記錄到 main
上的最新改動,因此如果貿然切換分支,main
的最新版本會被 feature
的舊版本蓋過去(如提示訊息說的 would be overwritten
)。
因此,要切換分支前,我們得先確保 main
分支上已經記錄了最新的版本。
既然得先形成一個版本,那就直接把 main
的最新版變成一個 commit、等在 feature
分支完成工作要切回來 main
分支時,再 git reset
不就得了?
可以!但在這篇文章,我們將探討另外一招,不會形成真正的 commit,但會把最新狀態保留下來,如此可避免 commit 中出現「事情做一半」版本的困擾。
要把狀態暫存,但用不形成 commit 可以用 git stash
,把 main
分支上仍在進行中(Work In Progress,簡稱 WIP)的工作目錄與預存區暫存,就可以順利切到 feature
分支:
現在我們在 feature
分支上也做一些改動:
echo "Feature second version" > hello_world.txt
假設又有臨時任務要切回 main
分支,也可以下 git stash
後切回(沒有 git stash
一樣切不回):
上半部因沒有 git stash
,切不回 main
分支;下半部有經過 git stash
才能切
這時候透過 git stash list
可以查看目前有的暫存狀態:
現在我們在 main
分支,要把剛剛在 main
分支的進度(也就是 stash@{1}
,表示距離當下前前一個 stash)拿出來繼續,可以用以下指令:
git stash pop stash@{1}
這時就可以回到 main
分支剛剛暫存的狀態(內文為 "Main version"
)繼續工作。目前從暫存回來的狀態還沒被 git add
、也還沒被 git commit
:
而因為 stash@{1}
已經被撿回來用,所以這筆暫存不見,在 git stash list
指令中,只剩下 feature
分支上的 stash:
現在我們把 main
分支上的改動直接加到預存區:
git add hello_world.txt
並讓其形成一個 commit:
git commit -m "Modified on main branch"
因為 main
分支上已經存成一個版本,可以輕鬆切回 feature
分支:
在 feature
分支上把剛剛的暫存狀態 stash@{0}
以 git stash pop
叫回來:
就可以在 feature
分支上次暫存的狀態(內文為 "Feature second version"
)繼續工作啦!
在以下狀況時,將無法順利切換分支:
為了避免另一分支的版本蓋過目前分支的,將會無法順利切換,解法有二:
一、git commit
先在原分支以 git commit
做成一個版本再切換分支,待之後從其他分支切回來,再以 git reset
復原。
二、git stash
把當前工作目錄與預存區狀態存到一個可用 git stash list
查詢的地方,待從其他分支切回來,直接用 git stash pop
指令復原,就不會形成事情做一半的 commit。