在代码仓库中创建新分支是在使用 Git 时非常常见的任务。它是将不相关的更改彼此分离的主要机制之一。它通常也是决定什么被合并到主分支的主要指示器。
如果没有分支,一切都必须进行 cherry-pick,否则你所有的工作都将有效地合并为 squash rebase。问题是,分支继承了它们所 fork 的分支的工作,这可能导致你意外地推送了你从未打算放入新分支的提交。
解决方案是始终从 main 分支 fork(除非你不想这样做)。这是一个很容易说的规则,但不幸的是它也同样容易忘记,所以了解规则背后的原因可能会有所帮助。
分支不是文件夹
很自然地会将 Git 分支视为文件夹。
但它不是。
当你创建一个分支时,你并没有创建一个干净的环境,即使它看起来像是那样。一个分支继承了其父分支包含的所有数据。如果父分支是 main 分支,那么你的新分支包含你项目的共同历史记录。但是如果父分支是另一个从 main 分支分出来的分支,那么你的新分支将包含 main 分支的历史记录以及另一个分支的历史记录。我经常用乐高积木来思考,所以这里有一个视觉示例,它不是那些复杂的 Git 节点图(但实际上是,暗地里是)。
假设你的 main 分支是一个乐高底板。

(Seth Kenlon CC BY-SA 4.0)
当你从 main
分支创建一个分支时,你添加了一块积木。假设你添加了一个名为 blue
的分支。

(Seth Kenlon BY-SA 4.0)
blue
分支包含底板的历史记录以及你在 blue
分支上所做的任何工作。在代码中,这是目前发生的情况
$ git branch
* main
$ git checkout -b blue
分支的分支
如果你在 blue
分支中又创建了一个分支,那么你是在 main
和 blue
的基础上构建的。假设你创建了一个名为 red
的分支,因为你想开始构建一个新功能。

(Seth Kenlon CC BY-SA 4.0)
这本身并没有什么问题,只要你理解你的 red
分支是建立在 blue
分支之上的。你在 blue
分支中所做的所有工作也存在于 red
分支中。只要你不想让 red
成为一个全新的开始,只包含 main
分支的历史记录,这是一种完全可以接受的构建 repo 的方法。但是请注意,项目所有者无法,例如,在不接受一堆 blue
更改的情况下接受 red
更改,至少不经过很多麻烦是不行的。
干净的分割
如果你实际上想做的是将 blue
和 red
开发为单独的功能,以便项目所有者可以选择合并其中一个而不是另一个,那么你需要这两个分支都只基于 main
。这很容易做到。你只需先检出 main
分支,然后从那里创建你的新分支。
$ git branch
* blue
main
$ git checkout main
$ git checkout -b red
这是乐高积木中的样子

(Seth Kenlon CC BY-SA 4.0)
现在你可以只将 blue
交付给项目所有者,或者只交付 red
,或者两者都交付,项目所有者可以决定将什么附加到官方仓库中的 main
分支。更好的是,blue
和 red
都可以继续单独开发。即使你完成了 blue
并将其合并到 main
中,一旦 red
的开发人员合并了来自 main
的更改,那么之前的 blue
就可以用于新的 red
开发。

(Seth Kenlon CC BY-SA 4.0)
分支示例
这是一个这个原则的简单演示。首先,创建一个带有 main 分支的 Git 仓库
$ mkdir example
$ cd example
$ git init -b main
用一个示例文件填充你新生的项目
$ echo "Hello world" > example.txt
$ git add example.txt
$ git commit -m 'Initial commit'
然后检出一个名为 blue
的分支,并进行一个你不想保留的愚蠢提交
$ git checkout -b blue
$ fortune > example.txt
$ git add example.txt
$ git commit -m 'Unwisely wrote over the contents of example.txt'
查看日志
$ git log --oneline
ba9915d Unwisely wrote over the contents of example.txt
55d4811 Initial commit
首先,假设你很高兴继续在 blue
之上进行开发。创建一个名为 red
的分支
$ git checkout -b red
查看日志
$ git log --oneline
ba9915d Unwisely wrote over the contents of example.txt
55d4811 Initial commit
你的新 red
分支,以及你在 red
中开发的任何内容,都包含你在 blue
中进行的提交。如果那是你想要的,那么你可以继续开发。但是,如果你打算重新开始,那么你需要从 main
而不是 blue
创建 red
分支。
现在检出你的 main 分支
$ git checkout main
查看日志
$ git log --oneline
55d4811 Initial commit
到目前为止看起来不错。blue
分支与 main
分支隔离,因此它是一个干净的基础,可以从中向不同的方向分支。是时候重置演示了。因为你还没有在 red
分支上做任何事情,所以你可以安全地删除它。如果这发生在现实生活中,并且你已经开始在 red
分支上开发,那么你必须将你的更改从 red cherry-pick 到一个新分支。
不过,这只是一个演示,所以可以安全地删除 red
分支
$ git branch -D red
现在创建一个名为 red
的新分支。这个版本的 red
旨在作为一个全新的开始,与 blue
分支不同。
$ git checkout -b red
$ git log --oneline
55d4811 Initial commit
尝试进行新的提交
$ echo "hello world" >> example.txt
$ git add example.txt
$ git commit -m 'A new direction'
查看日志
$ git checkout -b red
$ git log --oneline
de834ff A new direction
55d4811 Initial commit
最后再看一下 blue
分支
$ git checkout blue
$ git log --oneline
ba9915d Unwisely wrote over the contents of example.txt
55d4811 Initial commit
red
分支有它自己的历史记录。
blue
分支有它自己的历史记录。
两个不同的分支,都基于 main
分支。
谨慎 fork
和许多 Git 用户一样,我发现使用 Git 感知提示符更容易跟踪我当前的分支。在阅读了 Moshe Zadka 关于它的文章后,我一直在使用 Starship.rs,我发现它非常有用,尤其是在对一个打包项目进行大量更新时,该项目要求所有合并请求都只包含一个分支上的一个提交。
随着数百个更新在 20 个或更多参与者之间进行,管理这种情况的唯一方法是经常检出 main 分支,pull,并创建一个新分支。Starship 立即提醒我当前的分支以及该分支的状态。
从 main 分支还是从另一个分支 fork 新分支取决于你想实现什么。重要的是你要明白,你在哪里创建分支很重要。请注意你当前的分支。
2 条评论