第 5 篇:Git 分支基础:branch、checkout、merge 和冲突解决

文章目录

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

可以看到,刚创建出来的 devmaster 指向同一个提交。这也很合理:我们是在当前版本上新建的分支,新分支一开始当然和主分支站在同一个位置。

此时 HEAD 仍然指向 master

cpp 复制代码
hyb@139-159-150-152:~/gitcode$ cat .git/HEAD
ref: refs/heads/master

这个状态可以理解为:masterdev 都指向同一个提交,但我当前正在 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 devgit 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

此时 devmaster 指向的提交已经不同:

所以,在 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 之后没有产生新的提交,而 devmaster 的基础上往前走了一步。因此合并时,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、多人协作和企业级分支模型就会顺很多。

相关推荐
凯尔萨厮4 小时前
Git(学习笔记)
笔记·git·学习
一技安身6 小时前
【Git】零基础入门实战教程(Windows 完整版,适配竞赛仓库)
windows·git
LXY_BUAA9 小时前
Git_在飞牛中部署 GitLab_20260817
git·gitlab
阳墨余11 小时前
Git 同时管理 Gitee 和 GitHub 的 SSH 配置实战
git·gitee·github
fengyehongWorld19 小时前
Git 认证凭据管理
git
暮云星影1 天前
git版本发布
git·gitee·github·gitea
乌夷1 天前
Git Flow 分支模型
git
独隅1 天前
VS Code Git 工作树多分支并行开发实战评测
git
暮云星影1 天前
git仓库分支管理
git·gitee·github·gitea
互联网中的一颗神经元2 天前
ZZ — Git 速查表
大数据·git·elasticsearch