Skip to content
基本概念
Git 中的分支,本质上是一个指向某次提交的可移动指针。它并不复制任何文件,只是在 .git/refs/heads/ 目录下创建一个 41 字节的文件,文件名为分支名,内容为该分支当前指向的提交对象的 SHA-1 哈希值。例如,refs/heads/main 可能包含:
2c3f4a1b5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a这就表明 main 分支正指向这次提交。正因为分支只记录了一个哈希引用,创建和销毁分支几乎没有开销——这也是 Git 鼓励频繁使用分支的基础。
创建分支
创建分支但不切换:
bash
git branch feature-x该命令会在当前 HEAD 所指向的提交上新建一个名为 feature-x 的指针。执行后,HEAD 仍指向原分支,工作区没有任何变化。
git branch 不带参数时可以列出所有本地分支,当前分支前会带有 * 标记:
bash
$ git branch
feature-x
* main删除分支使用 -d 选项(-D 用于强制删除未合并的分支):
bash
git branch -d feature-x切换分支
创建分支后需要切换过去才能在该分支上工作。切换分支意味着让 HEAD 指向目标分支,并用该分支所指向提交的快照覆盖暂存区和工作目录。
bash
git checkout feature-x更常见的做法是创建的同时立即切换,可以合并为一条命令:
bash
git checkout -b feature-xGit 2.23 引入了 git switch,职责更清晰——只负责分支切换,不再与文件恢复功能混在一起:
bash
git switch -c feature-x # 创建并切换,等同于 checkout -b
git switch feature-x # 切换到已有分支如果需要基于某个特定分支(而非当前分支)创建新分支,可以显式指定起点:
bash
git checkout -b bugfix main执行后,bugfix 与 main 指向同一个提交,但 HEAD 已切换到 bugfix。后续的提交会在 bugfix 上向前推进,不会影响 main。
切换时的约束
切换分支时,如果工作区或暂存区存在未提交的修改,Git 会检查这些修改是否与目标分支的快照冲突。若冲突,切换将被拒绝,并提示先提交或贮藏(stash)这些修改。若没有冲突,部分情况下切换可以成功,修改会被带到目标分支,但这容易造成思路上的混淆——更稳妥的做法是保持工作区干净后再切换。
bash
$ git status
On branch main
nothing to commit, working tree clean
$ git switch -c dev
Switched to a new branch 'dev'合并分支
合并是将一个分支的更改整合到另一个分支的操作。合并总是在当前分支上执行,即把目标分支合并到当前分支。
快进合并
当两个分支的历史没有分叉——也就是说,当前分支是待合并分支的直接祖先时,Git 只需将当前分支的指针“向前移动”到待合并分支的最新提交。这种情况称为快进合并(fast-forward merge)。
假设提交历史如下:
C1 -- C2 (main)
\
C3 -- C4 (feature)main 指向 C2,feature 指向 C4,且 C2 是 C4 的祖先。此时将 feature 合并到 main:
bash
git checkout main
git merge feature输出:
Updating 2c3f4a1..8d9e7b2
Fast-forward
file.txt | 2 ++
1 file changed, 2 insertions(+)合并后,main 指针直接移动到 C4,与 feature 指向同一提交,历史呈一条直线。快进合并不产生新的合并提交,历史线简洁,但在团队协作中有时会刻意避免,因为这样会抹去特性分支的存在痕迹。
如果希望强制生成一个合并提交(即使可以快进),使用 --no-ff 选项:
bash
git merge --no-ff feature这会在合并时创建一个新的提交对象,明确记录“这里发生过一次合并”。
三方合并
当两个分支各自产生了新的提交,历史出现了分叉,快进合并就不再可能。此时 Git 需要执行三方合并(three-way merge)——“三方”指的是两个分支的末端提交以及它们的共同祖先(merge base)。
C1 -- C2 -- C3 (main)
\
C4 -- C5 (feature)执行 git merge feature 时,Git 会找出共同祖先 C2,然后分别计算 C2→C3(main 的变更)和 C2→C5(feature 的变更)的差异,并将两者的修改合并。如果没有冲突,Git 自动生成一个新的合并提交:
bash
git checkout main
git merge feature合并后的历史结构:
C1 -- C2 -- C3 -------- M (main)
\ /
C4 -- C5 (feature)合并提交 M 有两个父提交(C3 和 C5),它同时包含了两个分支的所有更改。提交说明通常自动生成为 “Merge branch 'feature' into main”。
查看合并历史
合并后的分支拓扑可以用 git log 的图形模式直观查看:
bash
git log --graph --oneline --all输出示例:
* a1b2c3d (main) Merge branch 'feature'
|\
| * 8d9e7b2 (feature) add profile page
| * 7c6e5f4 add user avatar
* | 2c3f4a1 update readme
|/
* 1a2b3c4 initial commit--all 会显示所有分支的引用,--graph 在左侧绘制 ASCII 分支图。这个视图是确认合并结果、追溯特性开发轨迹的常用手段。
合并冲突
当两个分支修改了同一个文件的同一区域,或者一方修改了文件而另一方删除了该文件时,Git 无法自动决定哪个版本是正确的。此时合并过程会中断,Git 会在冲突文件中插入标记,等待手动解决。
典型场景:main 分支与 feature 分支各自修改了 config.js 中同一行的 port 值。
执行 git merge feature 时,Git 输出:
Auto-merging config.js
CONFLICT (content): Merge conflict in config.js
Automatic merge failed; fix conflicts and then commit the result.查看冲突状态
使用 git status 可以查看哪些文件处于冲突状态:
bash
$ git status
On branch main
You have unmerged paths.
(fix conflicts and run "git commit")
(use "git merge --abort" to abort the merge)
Unmerged paths:
both modified: config.jsboth modified 表示该文件被双方同时修改。
解决冲突
打开冲突文件,会看到 Git 插入的冲突标记:
javascript
<<<<<<< HEAD
const port = 3000;
=======
const port = 8080;
>>>>>>> feature<<<<<<< HEAD 与 ======= 之间的内容是当前分支(main)的版本,======= 与 >>>>>>> feature 之间是待合并分支的版本。解决冲突就是编辑文件,保留期望的内容,并删除这三行标记符号。例如保留 feature 分支的修改:
javascript
const port = 8080;或者根据实际逻辑组合两个版本的代码。处理完所有冲突文件后,将它们标记为已解决:
bash
git add config.js然后执行 git commit 完成合并提交。此时 Git 会生成合并提交,提交说明可保留默认内容或自定义。
取消合并
如果在解决冲突的过程中决定放弃这次合并,可以在尚未完成提交之前执行:
bash
git merge --abort该命令会将工作区和暂存区恢复到执行 git merge 之前的状态。需要注意,一旦冲突文件被 git add 标记为已解决,--abort 将无法使用,此时需要通过 git reset 等手段撤销。
清理已合并分支
合并完成后,被合并的特性分支通常已经完成了它的使命,可以安全删除:
bash
git branch -d feature如果分支尚未合并到当前分支或上游分支,-d 会拒绝删除并给出提示;改用 -D 可以强制删除。及时清理无用的本地分支可以避免分支列表过长,减少切换分支时的误操作可能。
命令速查
| 命令 | 说明 |
|---|---|
git branch | 列出本地分支 |
git branch <name> | 基于当前 HEAD 创建新分支 |
git branch -d <name> | 删除已合并的分支 |
git checkout <name> | 切换到已有分支 |
git checkout -b <name> | 创建并切换到新分支 |
git switch <name> | 切换到已有分支(Git 2.23+) |
git switch -c <name> | 创建并切换到新分支(Git 2.23+) |
git merge <branch> | 将指定分支合并到当前分支 |
git merge --no-ff <branch> | 合并并强制生成合并提交 |
git merge --abort | 取消未完成的合并 |
git log --graph --oneline --all | 图形化查看所有分支历史 |
注意点
- 切换分支前,尽量保持工作区干净。即便 Git 允许在部分情况下带着未提交修改切换,这些修改被带到目标分支后也容易造成混淆。
git checkout同时承载了分支切换和文件恢复两个职责。较新的 Git 版本推荐使用git switch切换分支,git restore恢复文件,以降低误操作的可能。- 合并总是在当前分支上执行:
git merge feature意味着将feature合并到当前分支,而非反过来。 - 快进合并干净简洁,但会使特性分支的提交边界不可见。团队协作中是否强制生成合并提交,取决于项目对历史记录可追溯性的要求。
- 合并冲突解决后,必须将冲突文件
git add再执行git commit,否则合并始终处于未完成状态。 git merge --abort只在冲突尚未标记为已解决时可用。一旦执行过git add,只能通过重置来撤销合并。
