文章目录
- [Git 分支基础:branch、checkout、merge 和冲突解决](#Git 分支基础:branch、checkout、merge 和冲突解决)
-
- 一、为什么需要分支
- [二、分支的本质:master、HEAD 和提交指针](#二、分支的本质:master、HEAD 和提交指针)
- 三、创建、查看与切换分支
- 四、在不同分支上提交代码
- [五、合并分支与 Fast-forward](#五、合并分支与 Fast-forward)
- 六、删除分支
- 七、合并冲突是怎么产生的
- 八、手动解决冲突并提交结果
- 九、本文总结
Git 分支基础:branch、checkout、merge 和冲突解决
前面已经把 Git 的本地提交、版本回退和撤销操作梳理了一遍。到这里,Git 已经不只是"保存历史版本"的工具了,它真正强大的地方开始出现:分支。
分支可以让我们在不影响主线代码的情况下并行开发。比如我要开发一个新功能,但这个功能两三天内都不能完成,如果直接在主分支上提交半成品代码,其他人拉到这份代码后就可能被影响;如果一直不提交,又有丢进度的风险。分支正好解决这个矛盾:我可以开一条自己的开发线,想提交就提交,等功能稳定后再合并回主线。
下面统一使用 master 作为主分支名。如果你的仓库默认分支叫 main,把命令里的 master 替换成 main 即可。
一、为什么需要分支
可以把分支理解成同一个项目里的"不同时间线"。主分支上继续保持稳定,我在新分支上大胆修改、提交、验证;等这条新时间线上的工作完成后,再把它合并回主分支。
这就带来几个很实际的好处:
- 开发新功能不会污染主分支,主分支可以尽量保持可运行、可发布的状态。
- 多人开发时互相影响更小,每个人可以在自己的分支上提交阶段性代码。
- 修复问题更灵活,可以临时拉出一个修复分支,修完再合并回去。
- 实验成本更低,不确定的想法可以先在分支里尝试,失败了直接丢掉分支。
分支的直观效果大概如下:

在没有创建其他分支之前,我们所有提交都在同一条时间线上。Git 默认的主分支通常叫 master,也就是说,目前只有一条线在往前走。
二、分支的本质:master、HEAD 和提交指针
理解 Git 分支,最关键的一句话是:分支本质上就是一个指向某次提交的指针。
每提交一次,Git 都会生成一个新的提交对象。主分支 master 会指向当前主分支上的最新提交。继续提交时,master 这个指针就继续往前移动。
这里还要理解一个非常重要的指针:HEAD。
严格来说,HEAD 通常不是直接指向某次提交,而是指向当前所在的分支;当前分支再指向最新提交。也就是说:
HEAD指向master,表示当前正在master分支上工作。master指向某个提交,表示master当前停在哪个版本。- 当切换到
dev分支后,HEAD就会改为指向dev。

可以直接查看 .git 目录里的文件来验证这个关系:
cpp
hyb@139-159-150-152:~/gitcode$ cat .git/HEAD
ref: refs/heads/master
hyb@139-159-150-152:~/gitcode$ cat .git/refs/heads/master
5476bdeb12510f7cd72ac4766db7988925ebd302
第一条命令说明:当前 HEAD 指向 refs/heads/master,也就是当前分支是 master。
第二条命令说明:master 这个分支指针指向某个具体的提交 ID。
所以,分支不是把整个项目复制了一份。创建分支时,Git 只是多创建了一个指针,因此 Git 创建、切换、删除分支都非常快。
三、创建、查看与切换分支
创建分支使用 git branch <分支名>,查看当前分支使用 git branch。
例如创建一个 dev 分支:
cpp
hyb@139-159-150-152:~/gitcode$ git branch
* master
hyb@139-159-150-152:~/gitcode$ git branch dev
hyb@139-159-150-152:~/gitcode$ git branch
dev
* master
git branch 输出里的 * 表示当前所在分支。这里虽然创建了 dev,但 * 仍然在 master 前面,说明当前还在 master 分支上。
创建完成后,也可以在 .git/refs/heads/ 下看到这个新分支:
cpp
hyb@139-159-150-152:~/gitcode$ ls .git/refs/heads/
dev master
hyb@139-159-150-152:~/gitcode$ cat .git/refs/heads/*
5476bdeb12510f7cd72ac4766db7988925ebd302
5476bdeb12510f7cd72ac4766db7988925ebd302
可以看到,刚创建出来的 dev 和 master 指向同一个提交。这也很合理:我们是在当前版本上新建的分支,新分支一开始当然和主分支站在同一个位置。
此时 HEAD 仍然指向 master:
cpp
hyb@139-159-150-152:~/gitcode$ cat .git/HEAD
ref: refs/heads/master
这个状态可以理解为:master 和 dev 都指向同一个提交,但我当前正在 master 上工作。

切换分支使用 git checkout <分支名>:
cpp
hyb@139-159-150-152:~/gitcode$ git checkout dev
Switched to branch 'dev'
hyb@139-159-150-152:~/gitcode$ git branch
* dev
master
hyb@139-159-150-152:~/gitcode$ cat .git/HEAD
ref: refs/heads/dev
现在 * 出现在 dev 前面,.git/HEAD 也变成了 refs/heads/dev,说明当前已经切换到了 dev 分支。

如果想创建分支并立即切换,可以使用:
cpp
git checkout -b dev1
它等价于下面两步:
cpp
git branch dev1
git checkout dev1
补充一下,新版本 Git 还提供了 git switch,比如 git switch dev、git switch -c dev1。不过 checkout 仍然很常见,很多项目文档和历史命令里都会出现,所以这里先围绕 checkout 来理解分支切换。
四、在不同分支上提交代码
切换到 dev 分支后,我们修改 ReadMe 文件并提交一次:
cpp
hyb@139-159-150-152:~/gitcode$ vim ReadMe
hyb@139-159-150-152:~/gitcode$ cat ReadMe
hello bit
hello git
hello world
hello version1
hello version2
hello version3
write aaa for new branch
hyb@139-159-150-152:~/gitcode$ git add .
hyb@139-159-150-152:~/gitcode$ git commit -m "modify ReadMe"
[dev 3740dce] modify ReadMe
这次提交发生在 dev 分支上,所以向前移动的是 dev 指针,而不是 master 指针。
现在切回 master:
cpp
hyb@139-159-150-152:~/gitcode$ git checkout master
Switched to branch 'master'
hyb@139-159-150-152:~/gitcode$ cat ReadMe
hello bit
hello git
hello world
hello version1
hello version2
hello version3
会发现刚才新增的 write aaa for new branch 不见了。它并没有丢,只是它属于 dev 分支上的新提交,而当前 master 还停在原来的提交位置。
再切回 dev:
cpp
hyb@139-159-150-152:~/gitcode$ git checkout dev
Switched to branch 'dev'
hyb@139-159-150-152:~/gitcode$ cat ReadMe
hello bit
hello git
hello world
hello version1
hello version2
hello version3
write aaa for new branch
内容又出现了。这个现象正好说明:切换分支,本质上是在切换工作区对应的版本状态。
也可以查看两个分支当前指向的提交:
cpp
hyb@139-159-150-152:~/gitcode$ cat .git/refs/heads/dev
bdaf528ffbb8e05aee34d37685408f0e315e31a4
hyb@139-159-150-152:~/gitcode$ cat .git/refs/heads/master
5476bdeb12510f7cd72ac4766db7988925ebd302
此时 dev 和 master 指向的提交已经不同:

所以,在 master 上看不到 dev 的新内容并不奇怪。HEAD 指向哪个分支,工作区就会呈现哪个分支当前对应的内容。
五、合并分支与 Fast-forward
在 dev 上完成开发后,如果希望 master 也拥有这次修改,就需要把 dev 合并到 master。
注意合并时的语义:git merge dev 表示把 dev 合并到当前分支 。所以要先切到目标分支 master,再执行合并。
cpp
hyb@139-159-150-152:~/gitcode$ git branch
* dev
master
hyb@139-159-150-152:~/gitcode$ git checkout master
Switched to branch 'master'
hyb@139-159-150-152:~/gitcode$ git merge dev
Updating 16623e1..3740dce
Fast-forward
ReadMe | 1 +
hyb@139-159-150-152:~/gitcode$ cat ReadMe
hello bit
hello git
hello world
hello version1
hello version2
hello version3
write aaa for new branch
合并完成后,master 也能看到 dev 上新增的内容了。

这里出现了一个关键词:Fast-forward。
Fast-forward 可以理解为"快进合并" 。当前例子里,master 从创建 dev 之后没有产生新的提交,而 dev 在 master 的基础上往前走了一步。因此合并时,Git 不需要生成新的合并提交,只要把 master 指针直接移动到 dev 当前指向的提交即可。
这种合并速度很快,历史也很简洁。不过不是所有合并都能 Fast-forward。如果两个分支从同一个提交分开后,各自都有了新的提交,就可能需要普通合并,甚至可能产生冲突。
六、删除分支
当 dev 已经合并到 master 后,如果这个分支不再需要,就可以删除它。
删除分支使用:
cpp
git branch -d <分支名>
不过有一个限制:不能删除当前正在使用的分支。
例如当前还在 dev 分支时,删除 dev 会失败:
cpp
hyb@139-159-150-152:~/gitcode$ git branch
* dev
master
hyb@139-159-150-152:~/gitcode$ git branch -d dev
error: Cannot delete branch 'dev' checked out at '/home/hyb/gitcode'
正确做法是先切到其他分支,比如 master,再删除 dev:
cpp
hyb@139-159-150-152:~/gitcode$ git checkout master
Switched to branch 'master'
hyb@139-159-150-152:~/gitcode$ git branch -d dev
Deleted branch dev (was bdaf528).
hyb@139-159-150-152:~/gitcode$ git branch
* master
删除分支后,只是删除了 dev 这个指针;已经合并进 master 的提交不会丢。

这也是 Git 推荐大量使用分支的原因:创建分支成本低,合并完成后删除分支也很轻量。开发一个功能就新建一个分支,完成后合并并删除,主线会更加干净。
七、合并冲突是怎么产生的
前面的合并很顺利,是因为 master 没有新的提交,Git 可以直接快进。但实际开发里,经常会出现这样的情况:
- 从
master拉出一个dev1分支。 dev1修改了ReadMe的某一行并提交。master也修改了ReadMe的同一行并提交。- 合并时,Git 不知道应该保留哪一边,于是产生冲突。
先创建并切换到 dev1:
cpp
hyb@139-159-150-152:~/gitcode$ git checkout -b dev1
Switched to a new branch 'dev1'
hyb@139-159-150-152:~/gitcode$ git branch
* dev1
master
在 dev1 上修改 ReadMe,把原来的内容改成 bbb,然后提交:
cpp
hyb@139-159-150-152:~/gitcode$ cat ReadMe
hello bit
hello git
hello world
hello version1
hello version2
hello version3
write bbb for new branch
hyb@139-159-150-152:~/gitcode$ git add .
hyb@139-159-150-152:~/gitcode$ git commit -m "modify ReadMe"
[dev1 0854245] modify ReadMe
再切回 master,此时 master 上还是原来的 aaa:
cpp
hyb@139-159-150-152:~/gitcode$ git checkout master
Switched to branch 'master'
hyb@139-159-150-152:~/gitcode$ cat ReadMe
hello bit
hello git
hello world
hello version1
hello version2
hello version3
write aaa for new branch
然后在 master 上把同一行改成 ccc,并提交:
cpp
hyb@139-159-150-152:~/gitcode$ git branch
dev1
* master
hyb@139-159-150-152:~/gitcode$ vim ReadMe
hyb@139-159-150-152:~/gitcode$ cat ReadMe
hello bit
hello git
hello world
hello version1
hello version2
hello version3
write ccc for new branch
hyb@139-159-150-152:~/gitcode$ git add .
hyb@139-159-150-152:~/gitcode$ git commit -m "modify ReadMe"
[master c10f6d0] modify ReadMe
现在两个分支已经产生了分叉:dev1 有自己的新提交,master 也有自己的新提交,而且它们修改的是同一个位置。

此时在 master 上合并 dev1:
cpp
hyb@139-159-150-152:~/gitcode$ git merge dev1
Auto-merging ReadMe
CONFLICT (content): Merge conflict in ReadMe
Automatic merge failed; fix conflicts and then commit the result.
hyb@139-159-150-152:~/gitcode$ git status
On branch master
You have unmerged paths.
(fix conflicts and run "git commit")
(use "git merge --abort" to abort the merge)
Unmerged paths:
(use "git add <file>..." to mark resolution)
both modified: ReadMe
no changes added to commit (use "git add" and/or "git commit -a")
Git 已经说得很清楚:ReadMe 被两个分支同时修改了,需要我们手动处理。
八、手动解决冲突并提交结果
发生冲突后,直接打开冲突文件,会看到 Git 加入的冲突标记:
cpp
hyb@139-159-150-152:~/gitcode$ cat ReadMe
hello bit
hello git
hello world
hello version1
hello version2
hello version3
<<<<<<< HEAD
write ccc for new branch
=======
write bbb for new branch
>>>>>>> dev1
这些标记的含义是:
<<<<<<< HEAD到=======之间,是当前分支的内容,也就是master上的内容。=======到>>>>>>> dev1之间,是要合并进来的dev1分支内容。- 这些标记只是 Git 帮我们标出来的冲突范围,最终必须删除,不能保留在代码里。
解决冲突不是简单地"选一个版本"那么死板,而是要根据业务含义决定最终内容。这里假设我们希望保留 dev1 的写法,把文件改成:
cpp
hyb@139-159-150-152:~/gitcode$ cat ReadMe
hello bit
hello git
hello world
hello version1
hello version2
hello version3
write bbb for new branch
冲突文件改完后,还需要重新 add,表示这个冲突已经解决:
cpp
hyb@139-159-150-152:~/gitcode$ git add .
hyb@139-159-150-152:~/gitcode$ git commit -m "merge ReadMe"
[master 2976afc] merge ReadMe
这里的提交很重要。解决冲突后必须再次提交,合并才算真正完成。
合并完成后的状态如下:

也可以用 git log --graph 查看分支合并历史:
cpp
hyb@139-159-150-152:~/gitcode$ git log --graph --pretty=oneline --abbrev-commit
* 2976afc (HEAD -> master) merge ReadMe
|\
| * c594fd1 (dev1) modify ReadMe
* | c10f6d0 modify ReadMe
|/
确认合并完成后,如果 dev1 已经不再需要,也可以删除:
cpp
hyb@139-159-150-152:~/gitcode$ git branch -d dev1
Deleted branch dev1 (was c594fd1).
如果冲突解决过程中发现自己合并错了,不想继续处理,可以用下面的命令中止本次合并,回到合并前的状态:
cpp
git merge --abort
这个命令在实际开发里很有用。尤其是冲突太多、越改越乱时,与其硬着头皮继续,不如先中止合并,重新整理思路后再来。
九、本文总结
- 分支的本质是指向提交的指针,创建分支不是复制整个项目,所以速度非常快。
HEAD表示当前所在位置,通常指向当前分支,当前分支再指向最新提交。git branch可以查看或创建分支,git checkout可以切换分支,git checkout -b可以创建并切换分支。- 在不同分支上提交代码,会让不同分支指向不同的提交,因此切换分支时工作区内容也会变化。
git merge <分支名>的含义是:把指定分支合并到当前分支。- Fast-forward 合并本质上是移动当前分支指针,历史更简洁,但不是所有合并都能快进。
- 删除分支使用
git branch -d <分支名>,但不能删除当前正在使用的分支。 - 当两个分支修改了同一文件的同一位置时,就可能产生合并冲突。
- 冲突解决流程是:查看冲突文件,删除冲突标记,保留最终内容,重新
git add,再git commit。
分支是 Git 后续协作流程的基础。只要把"分支是指针""HEAD 指向当前分支""合并是把另一条线的提交整合到当前线"这几件事想明白,后面再看分支策略、stash、多人协作和企业级分支模型就会顺很多。