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

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

![image](https://stwillblogassets.blob.core.windows.net/files/images/external/stwillblogassets.blob.core.windows.net/f40f4191e4c2dbedb368-20231029051121199.jpg)

### 情境描述

如果前言所述，要同時在一個 Repo 開發兩個不同的分支，最簡單的方法，當然就是 `git clone` 兩次啦，例如：

```bash
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` 將版本庫複製回來：

```bash
git clone https://github.com/user/MyRepo.git
```

這個命令預設會在當前資料夾下建立一個 `MyRepo` 目錄，我們假設為 `G:\Projects\MyRepo` 目錄，接著我們進入這個目錄：

```batch
cd G:\Projects\MyRepo
```

接著我們透過 `git branch` 命令來查看目前的分支狀態：

```bash
* develop
  master
```

這代表我們現在正在 `develop` 分支，但我同時也想開發 `master` 怎麼辦？這時你就可以透過 `git worktree` 命令來達成這個需求，步驟如下：

1.  先透過 `git worktree list` 列出當前 Git 所管理的「工作目錄」有幾個（預設當然只有一個）
    
    ```bash
    git worktree list
    ```
    
    ```
    G:/Projects/MyRepo  f4128f4 [develop]
    ```
    
2.  接著我們透過 `git worktree add` 命令建立「第二個」工作目錄
    
    由於是「第二個」工作目錄，所以我會想把工作目錄建立在「另一個」資料夾，例如：`G:\Projects\MyRepo-master`，所以我們可以這樣做：
    
    ```bash
    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` 就超級好用的，你可以這樣用：
    
    ```bash
    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 所管理的「工作目錄」
    
    ```bash
    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` 都沒差，這樣子使用真的非常方便！
    
4.  如果你想要移動工作目錄的路徑，可以透過 `git worktree move` 來幫助你移動資料夾
    
    ```bash
    git worktree move ../MyRepo-master ../MyRepo-master2
    ```
    
    這樣就可以把 `G:\Projects\MyRepo-master` 資料夾移動到 `G:\Projects\MyRepo-master2` 資料夾下了！
    
    > 注意: 移動時不能有應用程式鎖定這個資料夾，否則會失敗！
    
5.  最後，我們可以透過 `git worktree remove` 來刪除附加的工作目錄
    
    ```bash
    git worktree remove ../MyRepo-master
    ```
    
    不過你要注意，你只能刪除工作目錄中 Staged 的檔案，如果有一些尚未 Commit 的檔案，這個命令就會失敗。
    
    如果你要強制刪除工作目錄，可以加上 `-f` 參數：
    
    ```bash
    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` 並不是把「工作目錄」給鎖定或唯讀，而是把「連結」給鎖定，避免這個連結被移動或刪除！
    

### 相關連結

-   [git-worktree(1) Manual Page](https://git-scm.com/docs/git-worktree)
