不知道大家是否曾經有過這樣的需求:在一個 Git Repo 下有好幾個分支,但你在某些特殊情況下需要「同時」開啟不同分支的程式碼做開發,所以你並不想要經常的切換分支,因為當你要切換分支時,在執行 git checkout
之前都要先做一次 git stash
把尚未 commit 的變更先儲存起來,在另一個分支開發完成後又要切換回來,並透過 git stash pop
把暫存的變更復原,這樣的動作實在是太麻煩了。這個需求或許並不常見,但如果你遇到的話,那麼這篇文章應該就能幫助到你。
情境描述
如果前言所述,要同時在一個 Repo 開發兩個不同的分支,最簡單的方法,當然就是 git clone
兩次啦,例如:
git clone https://github.com/user/MyRepo.git MyRepo-BranchA
git clone https://github.com/user/MyRepo.git MyRepo-BranchB
不過,這樣的做法會造成硬碟空間的浪費,而且如果你的 Repo 很大,那麼每次 git clone
都要花很多時間,這樣的做法實在不是太理想!
解決方案
在 Git 中有個 git worktree
命令,他可以幫你在一個「本地儲存庫」(Local Repository) 下管理多個 Working Tree (工作目錄),對於沒用過的人,可能對這種應用情境不是很好理解,以下我就用一個例子來說明這個命令的用法。
假設你有一個 Git Repo,裡面有兩個分支,分別是 master
與 develop
分支,一般來說我們要開發專案時,會先透過 git clone
將版本庫複製回來:
git clone https://github.com/user/MyRepo.git
這個命令預設會在當前資料夾下建立一個 MyRepo
目錄,我們假設為 G:\Projects\MyRepo
目錄,接著我們進入這個目錄:
cd G:\Projects\MyRepo
接著我們透過 git branch
命令來查看目前的分支狀態:
* develop
master
這代表我們現在正在 develop
分支,但我同時也想開發 master
怎麼辦?這時你就可以透過 git worktree
命令來達成這個需求,步驟如下:
-
先透過 git worktree list
列出當前 Git 所管理的「工作目錄」有幾個(預設當然只有一個)
git worktree list
G:/Projects/MyRepo f4128f4 [develop]
-
接著我們透過 git worktree add
命令建立「第二個」工作目錄
由於是「第二個」工作目錄,所以我會想把工作目錄建立在「另一個」資料夾,例如:G:\Projects\MyRepo-master
,所以我們可以這樣做:
git worktree add ../MyRepo-master master
執行結果的訊息如下:
Preparing worktree (checking out 'master')
HEAD is now at 3a8b88c Initial commit
這個命令是直接在 G:\Projects\MyRepo-master
資料夾取出一個 master
分支的工作目錄!
相對的,你也可以建立一個全新的分支,藉此在新的工作目錄下進行開發。舉個例子,假設你正在開發新功能,但是突然需要去修一個 master
分支的 Bug,你不希望目前開發到一版的工作目錄被改變,而是希望另開一個工作目錄作開發,此時 git worktree
就超級好用的,你可以這樣用:
git worktree add -b hotfix/v2.0-bug ../MyRepo-master master
這個命令所代表的意思就是:在 G:\Projects\MyRepo-master
資料夾下建立一個基於 master
分支的 hotfix/v2.0-bug
新分支的工作目錄(講起來好繞口),然後你可以直接到 G:\Projects\MyRepo-master
目錄進行開發工作。因此你不需要先在 G:\Projects\MyRepo
工作目錄 Commit 任何東西,也不需要用 git stash
暫存變更,直接換個目錄就可以開發了,有沒有超讚的!😎
-
再執行一次 git worktree list
列出當前 Git 所管理的「工作目錄」
git worktree list
G:/Projects/MyRepo f4128f4 [develop]
G:/Projects/MyRepo-master 3a8b88c [master]
現在 Git 已經幫我管理兩個工作目錄了,一個是 G:\Projects\MyRepo
的 develop
分支,另一個是 G:\Projects\MyRepo-master
的 master
分支。
而這裡真正有趣的地方是,這兩個工作目錄只共用一組「本地儲存庫」(Local Repository),也就是 G:\Projects\MyRepo\.git
這個資料夾,大幅節省了磁碟空間的使用率!
由於這兩個「工作目錄」共用同一個 Repo 的關係,所以你可以同時在這兩個工作目錄下開發,只要兩邊使用不同的分支開發,基本上是不會互相影響的,你要 git commit
或 git push
或 git pull
都沒差,這樣子使用真的非常方便!
-
如果你想要移動工作目錄的路徑,可以透過 git worktree move
來幫助你移動資料夾
git worktree move ../MyRepo-master ../MyRepo-master2
這樣就可以把 G:\Projects\MyRepo-master
資料夾移動到 G:\Projects\MyRepo-master2
資料夾下了!
注意: 移動時不能有應用程式鎖定這個資料夾,否則會失敗!
-
最後,我們可以透過 git worktree remove
來刪除附加的工作目錄
git worktree remove ../MyRepo-master
不過你要注意,你只能刪除工作目錄中 Staged 的檔案,如果有一些尚未 Commit 的檔案,這個命令就會失敗。
如果你要強制刪除工作目錄,可以加上 -f
參數:
git worktree remove -f ../MyRepo-master
如果你是手動刪除了 G:\Projects\MyRepo-master
資料夾的話,那麼你可以透過 git worktree prune
來清除 Git 無法管理的工作目錄!
附加知識
以我們上面的例子來說,在 G:\Projects\MyRepo-master
目錄下是沒有「本地儲存庫」的,但該目錄為什麼還能版控呢?那是因為這個 G:\Projects\MyRepo-master
目錄下有一個 .git
檔案,是「檔案」喔!其內容如下:
gitdir: G:/Projects/MyRepo/.git/worktrees/MyRepo-master
所以這就是一個連結而已,這個連結指向了 G:\Projects\MyRepo\.git\worktrees\MyRepo-master
這個資料夾,而這個資料夾就是用來管理這個「工作目錄」下的版控狀態!
補充說明
其實 git worktree
有兩個命令可以鎖定/解鎖這些額外建立的工作目錄,分別是:
git worktree lock [--reason <string>] <worktree>
git worktree unlock <worktree>
當你鎖定了一個連結的工作目錄,就可以避免這個工作目錄被移動或刪除,可以避免一些意外的發生。
-
避免這個工作目錄被移動
若執行 git worktree move ..\MyRepo-master\ ..\MyRepo-master2\
會出現以下錯誤訊息:
fatal: cannot move a locked working tree;
use 'move -f -f' to override or unlock first
注意: 上述訊息說,如果你要在鎖定的狀態下強制移動目錄位置,要加上兩個 -f
參數才行!
-
避免這個工作目錄被刪除
連結的工作目錄要被刪除有兩種可能,一種是你用 git worktree remove ../MyRepo-master
來手動刪除,另一種是透過 git worktree prune
來清除找不到的做目錄。
如果你連結的工作目錄設定在 USB 這種移動式的磁碟機上,當你沒有插入 USB 的時候執行 git worktree prune
就會意外的刪除這個 G:\Projects\MyRepo\.git\worktrees\MyRepo-master
資料夾,但這個資料夾包含了工作目錄的版控資訊,理論上你不應該刪除這個資訊,此時就可以用 git worktree lock
來鎖定這個工作目錄,這樣就可以避免這個工作目錄被刪除了!
簡單來說,這裡的 git worktree lock
並不是把「工作目錄」給鎖定或唯讀,而是把「連結」給鎖定,避免這個連結被移動或刪除!
相關連結