使用 git worktree 建立多個工作區

除了 git stash 外的另一種方案

kmsheng
8 min readApr 2, 2019

本文是基於 git 版本 2.21.0 撰寫,如果你的 git 低於這個版本有可能無法使用 worktree 或是不支援部分參數。

什麼是 worktree ?

以前在做 git initgit clone 後,git 只允許你在同一個 repo 使用一個目錄對應一個分支,支援 worktree 後你可以自行新增目錄對應到不同分支或 commit。

為什麼要用 worktree ?

你可能會遇到一種狀況,當你很認真的在某個 feature 分支寫了很多程式,但是還沒有 commit 到 repo,所以 repo 上面有滿滿的已更動或未追蹤檔案。

這時你老闆出現了。「喂 ~ 那個誰 ! 線上程式爆炸了,趕快修一下。」

可是你的程式還沒 commit 完啊 ! 怎麼辦 ?

這時可能有幾種解法:

  1. clone 一個同樣的 repo 到別的目錄,分支切到 master 去處理爆炸的事情。
  2. 把沒 commit 的東西全部透過 git add . && git stash 收起來,分支切到 master 去處理爆炸的事情,處理好之後再切回 feature 分支做 git stash pop

解法 1 的缺點是如果 repo 太大,再 clone 一次會很耗時間。解法 2 的缺點是你的編輯器如果會自動偵測檔案消失或變動的話,你可能再 checkout 過去就會看到一大堆訊息。

所以考慮以上因素,這時 worktree 就很有用了 !

worktree 可以讓你直接開一個獨立的工作區域切換出去做別的事,未 commit 的檔案原封不動留在原來的 repo。

worktree 的相關指令

底下用 https://github.com/kmsheng/git-worktree-demo 這個 repo 說明,原始的 repo git-worktree-demo 稱作 main working tree,後來透過 git-work-tree 指令建立出來的工作區叫 linked working tree,兩者之間有對應關係。

add <path> [commit-ish]

新增一個工作區,用法有幾種變化

$ git worktree add space1
# 因為分支 space1 在本地或遠端都不存在,所以 git 幫我建了一個目錄名為
# space1 並且用當前分支 HEAD 建了一個本地分支 space1 對應到目錄 space1
# 一個成功建立好的 worktree 目錄底下會有一個 .git 的 "檔案",裡面會紀錄
# main working tree 到 linked working tree 的路徑。
# 例如:
# gitdir: ~/git-worktree-demo/.git/worktrees/space1
$ git worktree add space2 feat2
# git 幫我建了一個 space2 的工作區並且對應到 feat2 分支
# 因為 feat2 這個分支有存在於遠端,所以會被綁定
# 分支搜尋的順序為先本地、後遠端,所以如果本地有同名分支會優先被綁定。
$ git worktree add --detach detached-space
# 因為有加 --detach 參數,所以 git 建立了一個工作區名為 detached-space,直接對應到 HEAD 的 commit,不另外建立本地分支。
$ git worktree add --lock -q -b feat2-cloned space3 feat2
# 使用 --lock 直接建立一個被鎖住的工作區,效果等同於 git worktree add 再 git work tree lock
# 使用 -q 參數對 git 消音 ( 提示訊息將不會顯示 )
# 使用 -b 參數指定要建立的分支名稱,以上面指令為例,會拿 feat2 去建立 feat2-cloned 分支。
# git 預設不會覆蓋同名分支,使用 -B 參數可以強制覆蓋。

move

改變工作區的路徑

$ git worktree move space1 s1
# 把工作區目錄由 space1 改成 s1
$ git worktree move s1 space1
# 改回來

list

列出工作區

$ git worktree list
# 可以在 main working tree 和 linked working tree 列清單
git-worktree-demo ee1bbbb [master]
git-worktree-demo/detached-space ee1bbbb (detached HEAD)
git-worktree-demo/space1 ee1bbbb [space1]
git-worktree-demo/space2 cfcc123 [feat2]
/Users/kmsheng/work/git-worktree-demo/space3 cfcc123 [feat2-cloned]
$ git worktree list --porcelain
# 有參數 --porcfelain,列出比較好剖析的格式
worktree /Users/kmsheng/work/git-worktree-demo
HEAD ee1bbbb754664f0a637810669d97bd1e398b4999
branch refs/heads/master
worktree /Users/kmsheng/work/git-worktree-demo/detached-space
HEAD ee1bbbb754664f0a637810669d97bd1e398b4999
detached
worktree /Users/kmsheng/work/git-worktree-demo/space1
HEAD ee1bbbb754664f0a637810669d97bd1e398b4999
branch refs/heads/space1
worktree /Users/kmsheng/work/git-worktree-demo/space2
HEAD cfcc123d362706b6e46abc27ea25177c8f7fbe97
branch refs/heads/feat2
worktree /Users/kmsheng/work/git-worktree-demo/space3
HEAD cfcc123d362706b6e46abc27ea25177c8f7fbe97
branch refs/heads/feat2-cloned

remove

移除一個工作區

$ git worktree remove space3
# 刪除 space3 工作區
$ git worktree remove -f space3
# 如果 space3 裡面還有東西沒 commit,使用一個 -f 來強制刪除目錄
$ git worktree remove -f -f space3
# 如果要刪除被鎖起來的目錄,需要指定兩次 -f

prune

大量移除路徑不存在的工作區

$ rm -rf detached-space$ git worktree prune -n
# 有 -n 就不會真的刪,先告訴你有哪些工作區會被刪掉
$ git worktree prune -v
# 要清除了,並且把清除解果印出來

lock

鎖住一個工作區,即使路徑被刪除, prune 指令也刪不到他。

如果你的工作區路徑是指到隨身碟或是 NFS 的路徑,在工作期間你可以考慮把工作區 lock 起來。

$ git worktree lock space2 --reason "flashdisk"
# 把 space2 lock 起來
# 使用 --reason 參數可以把為什麼要鎖的原因寫上來
#
# 目前還找不到一個方法可以簡單列出被 lock 的 worktree
# tree .git/worktrees
.git/worktrees
├── space1
│ ├── HEAD
│ ├── ORIG_HEAD
│ ├── commondir
│ ├── gitdir
│ ├── index
│ └── logs
│ └── HEAD
└── space2
├── HEAD
├── ORIG_HEAD
├── commondir
├── gitdir
├── index
├── locked <----- space2 的 locked 檔
└── logs
└── HEAD

unlock

解除工作區的鎖定狀態,此時這個工作區可以被刪了。

$ git worktree unlock space2

常見錯誤

要注意不能兩個目錄切到同一個分支,假設我的 main working tree 在 master,我卻要把某個工作區的分支切換到 master 就會發生錯誤。

fatal: 'master' is already checked out at 'git-worktree-demo'

--

--