在許多比較早期的 git 教材中,如果提到要切換分支,都是使用 git checkout
指令,但我的文章卻從來沒用過,而是用 git switch
,為什麼?
其實 git switch
是 git 的第 2.23 版本才出現的指令,在該版本之前確實都是用 git checkout
切換分支,但 git checkout
的功能還很多,以下我們將檢視幾個較常用的,以及在 2.23 版以後,各自對應、且僅有單一功能的指令。
我們先建立在一個資料夾中,建立第一個 commit:
echo "First version" > file.txt
git add file.txt
git commit -m "Initial commit"
再建立第二個 commit:
echo "Second version" > file.txt
git add file.txt
git commit -m "Second commit"
現在我們有兩個 commit 如下:
回顧 Day 9 的文章,我們知道 HEAD
這項參考用來指出目前在哪個分支,而切換分支其實就是更改 HEAD
指向的參考。
例如現在我們製作 feature
分支:
git branch feature
剛建立時,main
跟 feature
兩項參考都指向同一個 commit,而 HEAD
指向 main
:
透過 git switch
轉到 feature
分支:
git switch feature
切換後,HEAD
改指向 feature
這項參考:
改用 git checkout
,也可以切換分支,例如現在切換回 main
分支:
git checkout main
則 HEAD
改指回 main
參考:
兩次切換分支的終端機畫面如下:
現在我們針對 file.txt
進行第三次更動,但本次更動只加到預存區,不形成 commit:
echo "Third version" > file.txt
git add file.txt
最後一次更動則只在工作目錄:
echo "Fourth version" > file.txt
現在我們沒有要切換分支,所以也把 feature
這項參考拿掉:
git branch -d feature
此時 git 物件結構長這樣:
目前工作目錄的版本內容為 "Fourth version"
、預存區版本內容為 "Third version"
,如下:
如果想要把工作目錄的檔案改成預存區版本,則可使用以下指令:
git restore file.txt
觀察整體結構,可發現變化如下:
要達到同樣功能,也可以用 git checkout
指令:
git checkout -- file.txt
結果相同如下:
現在我們希望只保留最新 commit 版本,捨棄預存區跟工作目錄版本,可以使用以下指令:
git restore --source=HEAD --staged --worktree file.txt
發生的變化如下:
同樣的目標也可以用 git checkout
達成:
git checkout HEAD -- file.txt
示意圖如下:
"Third version"
、工作目錄內容為 "Fourth version"
時,我們依序把兩者都變成一個 commit。首先把本來就在預存區的版本變成commit:
git commit -m "Third commit"
再來把工作目錄的版本也變成 commit:
git add file.txt
git commit -m "Fourth commit"
現在共有四個 commit 如下:
目前 commit、預存區與工作目錄內容如下:
如果我們想退回第一個 commit 4e661dd
,可以用以下指令:
git restore --source=4e661dd file.txt
發生的變化如下:
可發現預存區跟 commit 內容,還有 HEAD
與 main
兩參考指向的標的都不便,只有工作目錄內容變成跟指定 commit 相同。
如果要用 git checkout
達成同樣目標呢?
git checkout 4e661dd -- file.txt
但這道指令不只影響工作目錄,也影響預存區,如下:
git checkout <commit>
切到指定 commit,會發生什麼事呢?現在我們一樣在第四個 commit ed1ea06
想切回第一個 commit 4e661dd
,因而輸入以下指令:
git checkout 4e661dd
這時候終端機出現看起來很可怕的訊息:
這是怎麼回事呢?
只要我們打開 .git/
資料夾,便可以觀察到:
.git/
├── ...
├── refs/
│ ├── heads/
│ │ └── main # ed1ea06e0f3db055d12ef2c0ea26348ebdab08d1
│ └── tags/
│
├── ...
└── HEAD # 4e661dd7f311990102cb56ed907afcffa137f6f8
畫成圖示如下:
HEAD
自己跑到新 commit,沒有把 main
這個參考帶著走啊!這其實不是什麼可怕的事情,我們可以透過 git switch
或 git checkout
指令,讓 HEAD
再次指向分支。
比如說,如果我們下:
git checkout main
或者新版的:
git switch main
就會讓 HEAD
回去指向 main
:
圖示如下:
或者可以用以下指令,原地生出一個新分支,假設命名為 feature
:
git checkout -b feature
或者新版的:
git switch -c feature
這時終端機畫面如下:
.git/
資料夾如下:
.git/
├── ...
├── refs/
│ ├── heads/
│ │ └── feature # 4e661dd7f311990102cb56ed907afcffa137f6f8
│ │ └── main # ed1ea06e0f3db055d12ef2c0ea26348ebdab08d1
│ └── tags/
│
├── ...
└── HEAD # ref: refs/heads/feature
圖示如下:
git checkout
其實包含了以下兩種功能:
git switch
。git restore
。其中要改變檔案內容的作法包含:
git checkout -- <filename>
,同 git restore <filename>
。git checkout HEAD -- <filename>
(連同預存區一起影響),同 git restore --source=HEAD --staged --worktree <filename>
(不影響預存區)。git checkout <commit> -- <filename>
,同 git restore --source=<commit> <filename>
。而如果直接用 git checkout <commit>
會形成斷頭問題,這時有至少以下兩種解方:
git switch
或 git checkout
把 HEAD
切回去對應分支參考。git switch -c
或 git branch -b
,直接在 HEAD
指向的 commit
上新建一個分支參考。因為 git checkout
同時包含兩種功能,在我的習慣中,都會用 git switch
切換分支,避免混淆。