The Will Will Web

記載著 Will 在網路世界的學習心得與技術分享

使用 git worktree 管理一個本地儲存庫下的多個工作目錄副本

不知道大家是否曾經有過這樣的需求:在一個 Git Repo 下有好幾個分支,但你在某些特殊情況下需要「同時」開啟不同分支的程式碼做開發,所以你並不想要經常的切換分支,因為當你要切換分支時,在執行 git checkout 之前都要先做一次 git stash 把尚未 commit 的變更先儲存起來,在另一個分支開發完成後又要切換回來,並透過 git stash pop 把暫存的變更復原,這樣的動作實在是太麻煩了。這個需求或許並不常見,但如果你遇到的話,那麼這篇文章應該就能幫助到你。

image

情境描述

如果前言所述,要同時在一個 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,裡面有兩個分支,分別是 masterdevelop 分支,一般來說我們要開發專案時,會先透過 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 命令來達成這個需求,步驟如下:

  1. 先透過 git worktree list 列出當前 Git 所管理的「工作目錄」有幾個(預設當然只有一個)

    git worktree list
    
    G:/Projects/MyRepo  f4128f4 [develop]
    
  2. 接著我們透過 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 暫存變更,直接換個目錄就可以開發了,有沒有超讚的!😎

  3. 再執行一次 git worktree list 列出當前 Git 所管理的「工作目錄」

    git worktree list
    
    G:/Projects/MyRepo         f4128f4 [develop]
    G:/Projects/MyRepo-master  3a8b88c [master]
    

    現在 Git 已經幫我管理兩個工作目錄了,一個是 G:\Projects\MyRepodevelop 分支,另一個是 G:\Projects\MyRepo-mastermaster 分支。

    而這裡真正有趣的地方是,這兩個工作目錄只共用一組「本地儲存庫」(Local Repository),也就是 G:\Projects\MyRepo\.git 這個資料夾,大幅節省了磁碟空間的使用率!

    由於這兩個「工作目錄」共用同一個 Repo 的關係,所以你可以同時在這兩個工作目錄下開發,只要兩邊使用不同的分支開發,基本上是不會互相影響的,你要 git commitgit pushgit pull 都沒差,這樣子使用真的非常方便!

  4. 如果你想要移動工作目錄的路徑,可以透過 git worktree move 來幫助你移動資料夾

    git worktree move ../MyRepo-master ../MyRepo-master2
    

    這樣就可以把 G:\Projects\MyRepo-master 資料夾移動到 G:\Projects\MyRepo-master2 資料夾下了!

    注意: 移動時不能有應用程式鎖定這個資料夾,否則會失敗!

  5. 最後,我們可以透過 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 有兩個命令可以鎖定/解鎖這些額外建立的工作目錄,分別是:

  1. git worktree lock [--reason <string>] <worktree>
  2. git worktree unlock <worktree>

當你鎖定了一個連結的工作目錄,就可以避免這個工作目錄被移動或刪除,可以避免一些意外的發生。

  1. 避免這個工作目錄被移動

    若執行 git worktree move ..\MyRepo-master\ ..\MyRepo-master2\ 會出現以下錯誤訊息:

    fatal: cannot move a locked working tree;
    use 'move -f -f' to override or unlock first
    

    注意: 上述訊息說,如果你要在鎖定的狀態下強制移動目錄位置,要加上兩個 -f 參數才行!

  2. 避免這個工作目錄被刪除

    連結的工作目錄要被刪除有兩種可能,一種是你用 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 並不是把「工作目錄」給鎖定或唯讀,而是把「連結」給鎖定,避免這個連結被移動或刪除!

相關連結

留言評論