Git 中上下文切换的 4 种方式
Git 分支

> 
> 比较 Git 中四种切换分支的方法的优缺点。
> 
> 
> 


![](/data/attachment/album/202105/25/091803a6ww3r7yo32oxdzx.jpg "Computer screen with files or windows open")


所有大量使用 Git 的人都会用到某种形式的上下文切换。有时这只会给你的工作流程增加少量的开销，但有时，这可能是一段痛苦的经历。


让我们用以下这个例子来讨论一些常见的上下文切换策略的优缺点：



> 
> 假设你在一个名为 `feature-X` 的分支中工作。你刚刚发现你需要解决一个无关的问题。这不能在 `feature-X` 分支中完成。你需要在一个新的分支 `feature-Y` 中完成这项工作。
> 
> 
> 


### 方案 1：暂存 + 分支


解决此问题最常见的工作流程可能如下所示：


1. 停止分支 `feature-X` 上的工作
2. `git stash`
3. `git checkout -b feature-Y origin/main`
4. 一顿鼓捣，解决 `feature-Y` 的问题
5. `git checkout feature-X` 或 `git switch -`
6. `git stash pop`
7. 继续在 `feature-X` 中工作


**优点：** 这种方法的优点在于，对于简单的更改，这是一个相当简单的工作流程。它可以很好地工作，特别是对于小型仓库。


**缺点：** 使用此工作流程时，一次只能有一个工作区。另外，根据你的仓库的状态，使用暂存是一个麻烦的环节。


### 方案 2：WIP 提交 + 分支


这个解决方案和前一个非常相似，但是它使用 WIP（<ruby> 正在进行的工作 <rt>  Work in Progress </rt></ruby>）提交而不是暂存。当你准备好切换回来，而不是弹出暂存时，`git reset HEAD~1` 会展开 WIP 提交，你可以自由地继续，就像之前的方案一样，但不会触及暂存。


1. 停止分支 `feature-X` 上的工作
2. `git add -u`（仅仅添加修改和删除的文件）
3. `git commit -m "WIP"`
4. `git checkout -b feature-Y origin/master`
5. 一顿鼓捣，解决 `feature-Y` 的问题
6. `git checkout feature-X` 或 `git switch -`
7. `git reset HEAD~1`


**优点：** 对于简单的更改，这是一个简单的工作流，也适合于小型仓库。你不需要使用暂存。


**缺点：** 任何时候都只能有一个工作区。此外，如果你或你的代码审阅者不够谨慎，WIP 提交可能会合并到最终产品。


使用此工作流时，你**永远**不要想着将 `--hard` 添加到 `git reset`。如果你不小心这样做了，你应该能够使用 `git reflog` 恢复提交，但是你最好完全避免这种情况发生，否则你会听到心碎的声音。


### 方案 3：克隆一个新仓库


在这个解决方案中，不是创建新的分支，而是为每个新的功能分支创建存储库的新克隆。


**优点：** 你可以同时在多个工作区中工作。你不需要 `git stash` 或者是 WIP 提交。


**缺点：** 需要考虑仓库的大小，因为这可能会占用大量磁盘空间（浅层克隆可以帮助解决这种情况，但它们可能并不总是很合适。）此外，你的仓库克隆将互不可知。因为他们不能互相追踪，所以你必须手动追踪你的克隆的源仓库。如果需要 git 钩子，则需要为每个新克隆设置它们。


### 方案 4：git 工作树


要使用此解决方案，你可能需要了解 `git add worktree`。如果你不熟悉 Git 中的工作树，请不要难过。许多人多年来都对这个概念一无所知。


#### 什么是工作树？


将工作树视为仓库中属于项目的文件。本质上，这是一种工作区。你可能没有意识到你已经在使用工作树了。开始使用 Git 时，你将自动获得第一个工作树。



```
$ mkdir /tmp/foo && cd /tmp/foo
$ git init
$ git worktree list
/tmp  0000000 [master]

```

你可以在以上代码看到，甚至在第一次提交前你就有了一个工作树。接下来去尝试再添加一个工作树到你的项目中吧。


#### 添加一个工作树


想要添加一个新的工作树你需要提供:


1. 硬盘上的一个位置
2. 一个分支名
3. 添加哪些分支



```
$ git clone https://github.com/oalders/http-browserdetect.git
$ cd http-browserdetect/
$ git worktree list
/Users/olaf/http-browserdetect  90772ae [master]

$ git worktree add ~/trees/oalders/feature-X -b oalders/feature-X origin/master
$ git worktree add ~/trees/oalders/feature-Y -b oalders/feature-Y e9df3c555e96b3f1

$ git worktree list
/Users/olaf/http-browserdetect       90772ae [master]
/Users/olaf/trees/oalders/feature-X  90772ae [oalders/feature-X]
/Users/olaf/trees/oalders/feature-Y  e9df3c5 [oalders/feature-Y]

```

与大多数其他 Git 命令一样，你需要在仓库路径下使用此命令。一旦创建了工作树，就有了隔离的工作环境。Git 仓库会跟踪工作树在磁盘上的位置。如果 Git 钩子已经在父仓库中设置好了，那么它们也可以在工作树中使用。


请注意到，每个工作树只使用父仓库磁盘空间的一小部分。在这种情况下，工作树需要只大约三分之一的原始磁盘空间。这这非常适合进行扩展。如果你的仓库达到了千兆字节的级别，你就会真正体会到工作树对硬盘空间的节省。



```
$ du -sh /Users/olaf/http-browserdetect
2.9M

$ du -sh /Users/olaf/trees/oalders/feature-X
1.0M

```

**优点：** 你可以同时在多个工作区中工作。你不需要使用暂存。Git 会跟踪所有的工作树。你不需要设置 Git 钩子。这也比 `git clone` 更快，并且可以节省网络流量，因为你可以在飞行模式下执行此操作。你还可以更高效地使用磁盘空间，而无需借助于浅层克隆。


**缺点：** 这是个需要你额外学习和记忆的新东西，但是如果你能养成使用这个功能的习惯，它会给你丰厚的回报。


#### 额外的小技巧


有很多方式可以清除工作树，最受欢迎的方式是使用 Git 来移除工作树：



```
git worktree remove /Users/olaf/trees/oalders/feature-X

```

如果你喜欢 RM 大法，你也可以用 `rm -rf` 来删除工作树。



```
rm -rf /Users/olaf/trees/oalders/feature-X

```

但是，如果执行此操作，则可能需要使用 `git worktree prune` 清理所有剩余的文件。或者你现在可以跳过清理，这将在将来的某个时候通过 `git gc` 自行完成。


#### 注意事项


如果你准备尝试 `git worktree`，请记住以下几点：


* 删除工作树并不会删除该分支。
* 可以在工作树中切换分支。
* 你不能在多个工作树中同时签出同一个分支。
* 像其他命令一样，`git worktree` 需要从仓库内运行。
* 你可以同时拥有许多工作树。
* 要从同一个本地仓库签出创建工作树，否则它们将互不可知。


#### git rev-parse


最后一点注意：在使用 `git worktree` 时，仓库根所在的位置可能取决于上下文。幸运的是，`git rev parse` 可以让你区分这两者。


* 要查找父仓库的根目录，请执行以下操作：

```
git rev-parse --git-common-dir

```
* 要查找你当前所在仓库的根目录，请执行：

```
git rev-parse --show-toplevel

```


### 根据你的需要选择最好的方法


就像很多事情一样，TIMTOWDI（<ruby> 条条大道通罗马 <rt>  there's more than one way to do it </rt></ruby>）。重要的是你要找到一个适合你需要的工作流程。你的需求可能因手头的问题而异。也许你偶尔会发现自己将 `git worktree` 作为版本控制工具箱中的一个方便工具。




---


via: <https://opensource.com/article/21/4/context-switching-git>


作者：[Olaf Alders](https://opensource.com/users/oalders) 选题：[lujun9972](https://github.com/lujun9972) 译者：[Chao-zhi](https://github.com/Chao-zhi) 校对：[wxy](https://github.com/wxy)


本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译，[Linux中国](https://linux.cn/) 荣誉推出


OK
Anyone who spends a lot of time working with Git will eventually need to do some form of context switching. Sometimes this adds very little overhead to your workflow, but other times, it can be a real pain.

Let's discuss the pros and cons of some common strategies for dealing with context switching using this example problem:

Imagine you are working in a branch called

`feature-X`

. You have just discovered you need to solve an unrelated problem. This cannot be done in`feature-X`

. You will need to do this work in a new branch,`feature-Y`

.

## Solution #1: stash + branch

Probably the most common workflow to tackle this issue looks something like this:

- Halt work on the branch
`feature-X`

`git stash`

`git checkout -b feature-Y origin/main`

- Hack, hack, hack…
`git checkout feature-X`

or`git switch -`

`git stash pop`

- Resume work on
`feature-X`


**Pros:** The nice thing about this approach is that this is a fairly easy workflow for simple changes. It can work quite well, especially for small repositories.

**Cons:** When using this workflow, you can have only one workspace at a time. Also, depending on the state of your repository, working with the stash can be non-trivial.

## Solution #2: WIP commit + branch

A variation on this solution looks quite similar, but it uses a WIP (Work in Progress) commit rather than the stash. When you're ready to switch back, rather than popping the stash, `git reset HEAD~1`

unrolls your WIP commit, and you're free to continue, much as you did in the earlier scenario but without touching the stash.

- Halt work on the branch
`feature-X`

`git add -u`

(adds only modified and deleted files)`git commit -m "WIP"`

`git checkout -b feature-Y origin/master`

- Hack, hack, hack…
`git checkout feature-X`

or`git switch -`

`git reset HEAD~1`


**Pros:** This is an easy workflow for simple changes and also good for small repositories. You don't have to work with the stash.

**Cons:** You can have only one workspace at any time. Also, WIP commits can sneak into your final product if you or your code reviewer are not vigilant.

When using this workflow, you *never* want to add a `--hard`

to `git reset`

. If you do this accidentally, you should be able to restore your commit using `git reflog`

, but it's less heartstopping to avoid this scenario entirely.

## Solution #3: new repository clone


In this solution, rather than creating a new branch, you make a new clone of the repository for each new feature branch.

**Pros:** You can work in multiple workspaces simultaneously. You don't need `git stash`

or even WIP commits.

**Cons:** Depending on the size of your repository, this can use a lot of disk space. (Shallow clones can help with this scenario, but they may not always be a good fit.) Additionally, your repository clones will be agnostic about each other. Since they can't track each other, you must track where your clones live. If you need git hooks, you will need to set them up for each new clone.

## Solution #4: git worktree

To use this solution, you may need to learn about `git add worktree`

. Don't feel bad if you're not familiar with worktrees in Git. Many people get by for years in blissful ignorance of this concept.

### What is a worktree?

Think of a worktree as the files in the repository that belong to a project. Essentially, it's a kind of workspace. You may not realize that you're already using worktrees. When using Git, you get your first worktree for free.

```
$ mkdir /tmp/foo && cd /tmp/foo
$ git init
$ git worktree list
/tmp 0000000 [master]
```


As you can see, the worktree exists even before the first commit. Now, add a new worktree to an existing project.

### Add a worktree

To add a new worktree, you need to provide:

- A location on disk
- A branch name
- Something to branch from

```
$ git clone https://github.com/oalders/http-browserdetect.git
$ cd http-browserdetect/
$ git worktree list
/Users/olaf/http-browserdetect 90772ae [master]
$ git worktree add ~/trees/oalders/feature-X -b oalders/feature-X origin/master
$ git worktree add ~/trees/oalders/feature-Y -b oalders/feature-Y e9df3c555e96b3f1
$ git worktree list
/Users/olaf/http-browserdetect 90772ae [master]
/Users/olaf/trees/oalders/feature-X 90772ae [oalders/feature-X]
/Users/olaf/trees/oalders/feature-Y e9df3c5 [oalders/feature-Y]
```


Like with most other Git commands, you need to be inside a repository when issuing this command. Once the worktrees are created, you have isolated work environments. The Git repository tracks where the worktrees live on disk. If Git hooks are already set up in the parent repository, they will also be available in the worktrees.

Don't overlook that each worktree uses only a fraction of the parent repository's disk space. In this case, the worktree requires about one-third of the original's disk space. This can scale very well. Once your repositories are measured in the gigabytes, you'll really come to appreciate these savings.

```
$ du -sh /Users/olaf/http-browserdetect
2.9M
$ du -sh /Users/olaf/trees/oalders/feature-X
1.0M
```


**Pros:** You can work in multiple workspaces simultaneously. You don't need the stash. Git tracks all of your worktrees. You don't need to set up Git hooks. This is also faster than `git clone`

and can save on network traffic since you can do this in airplane mode. You also get more efficient disk space use without needing to resort to a shallow clone.

**Cons:** This is yet another thing to remember. However, if you can get into the habit of using this feature, it can reward you handsomely.

## A few more tips

When you need to clean up your worktrees, you have a couple of options. The preferable way is to let Git remove the worktree:

`git worktree remove /Users/olaf/trees/oalders/feature-X`


If you prefer a scorched-earth approach, `rm -rf`

is also your friend:

`rm -rf /Users/olaf/trees/oalders/feature-X`


However, if you do this, you may want to clean up any remaining files with `git worktree prune`

. Or you can skip the `prune`

now, and this will happen on its own at some point in the future via `git gc`

.

## Notable notes

If you're ready to get started with `git worktree`

, here are a few things to keep in mind.

- Removing a worktree does not delete the branch.
- You can switch branches within a worktree.
- You cannot simultaneously check out the same branch in multiple worktrees.
- Like many other Git commands,
`git worktree`

needs to be run from inside a repository. - You can have many worktrees at once.
- Create your worktrees from the same local checkout, or they will be agnostic about each other.

## git rev-parse

One final note: When using `git worktree`

, your concept of where the root of the repository lives may depend on context. Fortunately, `git rev-parse`

allows you to distinguish between the two.

- To find the parent repository's root:

`git rev-parse --git-common-dir`

- To find the root of the repository you're in:

`git rev-parse --show-toplevel`


## Choose the best method for your needs

As in many things, TIMTOWDI (there's more than one way to do it). What's important is that you find a workflow that suits your needs. What your needs are may vary depending on the problem at hand. Maybe you'll occasionally find yourself reaching for `git worktree`

as a handy tool in your revision-control toolbelt.

## Comments are closed.