iT邦幫忙

2025 iThome 鐵人賽

DAY 22
0
Software Development

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

Day 22-深入一點點認識 Git:為什麼我不再使用 git checkout

  • 分享至 

  • xImage
  •  

在許多比較早期的 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 如下:
https://ithelp.ithome.com.tw/upload/images/20250922/20178513pXsjsGj9zp.png

切換分支

回顧 Day 9 的文章,我們知道 HEAD 這項參考用來指出目前在哪個分支,而切換分支其實就是更改 HEAD 指向的參考。

例如現在我們製作 feature 分支:

git branch feature

剛建立時,mainfeature 兩項參考都指向同一個 commit,而 HEAD 指向 main
https://ithelp.ithome.com.tw/upload/images/20250922/20178513IdCCxJUXcy.png

透過 git switch 轉到 feature 分支:

git switch feature

切換後,HEAD 改指向 feature 這項參考:
https://ithelp.ithome.com.tw/upload/images/20250922/20178513S2F4y2MxyN.png

改用 git checkout,也可以切換分支,例如現在切換回 main 分支:

git checkout main

HEAD 改指回 main 參考:
https://ithelp.ithome.com.tw/upload/images/20250922/20178513lT461pYGpN.png

兩次切換分支的終端機畫面如下:
https://ithelp.ithome.com.tw/upload/images/20250922/20178513gkxrYzjwF6.png

更改檔案內容

現在我們針對 file.txt 進行第三次更動,但本次更動只加到預存區,不形成 commit:

echo "Third version" > file.txt
git add file.txt

最後一次更動則只在工作目錄:

echo "Fourth version" > file.txt

現在我們沒有要切換分支,所以也把 feature 這項參考拿掉:

git branch -d feature

此時 git 物件結構長這樣:
https://ithelp.ithome.com.tw/upload/images/20250922/20178513Bq2lF06GXv.png

目前工作目錄的版本內容為 "Fourth version"、預存區版本內容為 "Third version",如下:
https://ithelp.ithome.com.tw/upload/images/20250922/20178513bAy45Ntldy.png

  • 把工作目錄變成預存區版本

如果想要把工作目錄的檔案改成預存區版本,則可使用以下指令:

git restore file.txt

觀察整體結構,可發現變化如下:
https://ithelp.ithome.com.tw/upload/images/20250922/20178513scVKvv48TU.png

要達到同樣功能,也可以用 git checkout 指令:

git checkout -- file.txt

結果相同如下:
https://ithelp.ithome.com.tw/upload/images/20250922/20178513zCgucZnZpJ.png

  • 把預存區跟工作目錄都改成當下最新 commit 版本

現在我們希望只保留最新 commit 版本,捨棄預存區跟工作目錄版本,可以使用以下指令:

git restore --source=HEAD --staged --worktree file.txt

發生的變化如下:
https://ithelp.ithome.com.tw/upload/images/20250922/20178513FBp5bivRVK.png

同樣的目標也可以用 git checkout 達成:

git checkout HEAD -- file.txt

示意圖如下:
https://ithelp.ithome.com.tw/upload/images/20250922/20178513Dg7lANS4V7.png

  • 改成特定 commit 內容
    在預存區內容為 "Third version"、工作目錄內容為 "Fourth version" 時,我們依序把兩者都變成一個 commit。

首先把本來就在預存區的版本變成commit:

git commit -m "Third commit"

再來把工作目錄的版本也變成 commit:

git add file.txt
git commit -m "Fourth commit"

現在共有四個 commit 如下:
https://ithelp.ithome.com.tw/upload/images/20250922/20178513lGQqBldCFy.png

目前 commit、預存區與工作目錄內容如下:
https://ithelp.ithome.com.tw/upload/images/20250922/20178513xuwoLwTTcm.png

如果我們想退回第一個 commit 4e661dd,可以用以下指令:

git restore --source=4e661dd file.txt

發生的變化如下:
https://ithelp.ithome.com.tw/upload/images/20250922/20178513r0Q5oqJCBT.png

可發現預存區跟 commit 內容,還有 HEADmain 兩參考指向的標的都不便,只有工作目錄內容變成跟指定 commit 相同。

如果要用 git checkout 達成同樣目標呢?

git checkout 4e661dd -- file.txt

但這道指令不只影響工作目錄,也影響預存區,如下:
https://ithelp.ithome.com.tw/upload/images/20250922/20178513SGC1axSdsi.png

  • 斷頭問題
    那如果單純用 git checkout <commit> 切到指定 commit,會發生什麼事呢?

現在我們一樣在第四個 commit ed1ea06 想切回第一個 commit 4e661dd,因而輸入以下指令:

git checkout 4e661dd

這時候終端機出現看起來很可怕的訊息:
https://ithelp.ithome.com.tw/upload/images/20250922/20178513OLWxKTpMPh.png

這是怎麼回事呢?

只要我們打開 .git/ 資料夾,便可以觀察到:

.git/
├── ...
├── refs/
│   ├── heads/
│   │    └── main       # ed1ea06e0f3db055d12ef2c0ea26348ebdab08d1
│   └── tags/
│
├── ...
└── HEAD                # 4e661dd7f311990102cb56ed907afcffa137f6f8

畫成圖示如下:
https://ithelp.ithome.com.tw/upload/images/20250922/20178513GRra0njVYB.png

HEAD 自己跑到新 commit,沒有把 main 這個參考帶著走啊!這其實不是什麼可怕的事情,我們可以透過 git switchgit checkout 指令,讓 HEAD 再次指向分支。

比如說,如果我們下:

git checkout main

或者新版的:

git switch main

就會讓 HEAD 回去指向 main
https://ithelp.ithome.com.tw/upload/images/20250922/20178513Kkf8Np1SNB.png

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

或者可以用以下指令,原地生出一個新分支,假設命名為 feature

git checkout -b feature

或者新版的:

git switch -c feature

這時終端機畫面如下:
https://ithelp.ithome.com.tw/upload/images/20250922/20178513EUAnyG20Rp.png

.git/ 資料夾如下:

.git/
├── ...
├── refs/
│   ├── heads/
│   │    └── feature    # 4e661dd7f311990102cb56ed907afcffa137f6f8
│   │    └── main       # ed1ea06e0f3db055d12ef2c0ea26348ebdab08d1
│   └── tags/
│
├── ...
└── HEAD                # ref: refs/heads/feature

圖示如下:
https://ithelp.ithome.com.tw/upload/images/20250922/2017851351tC8j48g1.png

小結

git checkout 其實包含了以下兩種功能:

  1. 切換分支:即 2.23 版之後的 git 中的 git switch
  2. 改變檔案內容:即 2.23 版之後的 git 中的 git restore

其中要改變檔案內容的作法包含:

  • 把工作目錄變成預存區版本:git checkout -- <filename>,同 git restore <filename>
  • 把預存區跟工作目錄都改成當下最新 commit 版本:git checkout HEAD -- <filename>(連同預存區一起影響),同 git restore --source=HEAD --staged --worktree <filename>(不影響預存區)。
  • 改成特定 commit 內容:git checkout <commit> -- <filename>,同 git restore --source=<commit> <filename>

而如果直接用 git checkout <commit> 會形成斷頭問題,這時有至少以下兩種解方:

  1. git switchgit checkoutHEAD 切回去對應分支參考。
  2. git switch -cgit branch -b,直接在 HEAD 指向的 commit 上新建一個分支參考。

因為 git checkout 同時包含兩種功能,在我的習慣中,都會用 git switch 切換分支,避免混淆。

參考資料

  1. git-restore
  2. git-switch
  3. git checkout
  4. What's the difference between 'git switch' and 'git checkout' ?
  5. 切換分支或某個commit id - git checkout介紹
  6. 【冷知識】斷頭(detached HEAD)是怎麼一回事?

上一篇
Day 21-深入一點點認識 Git:用 git 指令把檔案刪掉
系列文
深入一點點認識 Git22
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言