前几天处理一个线上问题,主分支代码被一条开发中的功能分支污染了一半,团队在犹豫是直接回滚还是切分支修复。当时手上另一个功能还没开发完,测试又在催着一个紧急bug——这正是Git创建分支、切换分支、合并分支、删除分支整套操作最典型的实战现场。
很多同学对分支操作的印象停留在“用会命令”层面:会 git branch,会 git merge,真到了需要决策的时候就含糊了。比如:功能开发到一半,线上出了bug,是 commit 还是 stash?合并时出现冲突,直接改文件还是回滚重来?一条分支合并完了,到底该删还是留?删的时候又提示 “not fully merged”,强删之后代码还能不能找回来?
这篇文章不打算按教科书顺序讲命令,而是一边推演实际的开发场景,一边把 Git 分支从创建、切换、合并、冲突解决到删除的完整操作链路捋清楚。适合刚学会 Git 基础命令、但对分支使用没有形成体系的同学,也适合被分支合并坑过几次、想系统补齐分支管理知识的开发者。文中所有命令我都按自己平时实际操作的习惯来写,并且会解释每个关键步骤背后的原因。
1. 先搞清楚一件事:分支不是文件夹,是一根会移动的指针
我第一次接触 Git 时,下意识把分支理解成“代码的多个副本文件夹”,后面发现这个理解会带来很多误判。比如说,有人会以为 git checkout 切换分支是“把文件夹换一套”,合并分支是“把两个文件夹合并”,删除分支是“删掉一套代码”。听起来好像能解释现象,但一旦遇到“为什么切换分支后文件有时候变有时候不变”这种问题,就解释不通了。
1.1 从提交链理解分支的本质
Git 里的每个提交(commit)都像一个快照指针,它指向一棵文件树,同时记录父提交的哈希值。多个提交通过父子关系串成一条有向链。分支的本质,就是这条提交链上的一个“可移动指针”,默认指向链的末端最新提交。
当你执行:
bash复制git branch feature/login
这行命令的真实动作,是在 .git/refs/heads/ 目录下新建了一个名为 feature/login 的引用文件,里面的内容就是当前 HEAD 指向的那个提交哈希。没有复制任何文件,也没有创建任何目录。
所以 Git 创建分支才会那么快,哪怕仓库里有几十万个文件,创建一百条分支也是瞬间完成的事。这一点和 SVN 的目录分支有本质区别,也是理解后续“删除分支丢不丢代码”的关键。
1.2 HEAD、工作区和分支三者的关系
分支指针本身没有内容,它只是“告诉我当前提交是哪一条链的末端”。真正决定你工作区里是什么文件的,是 HEAD。HEAD 可以理解为一个“当前所在位置的指示器”,它通常指向某个分支,而分支再指向某个提交。
当你在两条分支之间切换时,Git 做的事情是:
- 把
HEAD指向新的分支; - 用新分支指向的那个提交里的文件树,去对比当前工作区;
- 如果工作区是干净的,就直接把文件内容更新为目标提交对应的内容。
这也是很多人踩坑的地方:切换分支时,Git 会拒绝执行,如果你当前分支有未提交的修改,并且这些修改会和目标分支的文件产生冲突。Git 不希望你带着一堆没保存的改动直接在两个环境之间跳来跳去,这很容易把修改搞丢。
1.3 用指针模型看合并冲突
理解了“分支是指针”之后,再去看合并就清楚很多。
合并时,Git 并不是把两份代码机械地拼在一起,而是找到两个分支的“分叉点”,这个分叉点是它们共同的祖先提交(merge base)。Git 会把从分叉点开始,当前分支的改动和待合并分支的改动都取出来,尝试自动把两边的修改叠加到分叉点的代码上。
如果两边改的是不同文件,或者同一文件的不同位置,Git 可以自动完成合并。真正会引爆冲突的,是两边修改了同一个文件的同一片区域,或者一边改了某段代码另一边又删了那段代码。这时候 Git 不知道应该听谁的,只能停下来把问题交给你。这个本质原因很重要——理解了它,你就不会在冲突时怪 Git 太笨,而是会反思团队协作方式是否合理。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 分支创建与切换的完整命令链:从一条命令到一套习惯
场景还原:假设你正在 develop 分支上开发一个电商项目,产品经理说要给用户中心增加“优惠券列表”功能。正常的做法不是直接在 develop 上写,而是拉一条专用的功能分支 feature/coupon-list。
2.1 创建分支的两种方式,别用混了
bash复制# 方式一:先创建,再切换
git branch feature/coupon-list
git checkout feature/coupon-list
# 方式二:创建并切换(推荐)
git checkout -b feature/coupon-list
# 新版本 Git 等价写法
git switch -c feature/coupon-list
很多老教程只教 git checkout -b,但 Git 2.23 之后官方推荐用 git switch 和 git restore 来区分两类语义完全不同的操作。git checkout 这个命令历史上承担了太多职责:既能切分支,又能恢复工作区文件,还能检出历史版本。职责太多就容易混淆,所以我现在的习惯是:
- 分支切换:一律用
git switch - 文件还原:一律用
git restore - 旧命令兼容:遇到别的同事用
git checkout也能看懂
git branch feature/coupon-list 和 git checkout -b feature/coupon-list 的区别在于:前者只创建不切换,适合你在当前分支还有事情没处理完、只是想先“占个坑”的场景;后者创建完直接切过去,适合开始新功能开发。日常开发中,90% 的情况你会用第二种。
2.2 切换分支前必须养成的检查习惯
我见过太多人辛辛苦苦写了一下午代码,切分支时报错,心里一慌直接用了 git checkout -f 或各种强制手段,结果代码全部丢失。这个问题其实可以在根源上避免:切换分支前,先看一眼仓库状态。
bash复制git status
git status 输出的信息量很大,关键看三行:
Changes not staged for commit:有文件改动但还没git add;Changes to be committed:已经git add但还没commit;nothing to commit, working tree clean:工作区干净,可以放心切换分支。
只有当工作区干净,或者当前的修改不涉及目标分支要改的那批文件时,git switch 才能顺利执行。如果 Git 拒绝切换,先别慌,按下面的优先级处理:
- 如果手上改动是一个完整的小功能,直接
git commit提交到当前分支; - 如果改动写到一半,还不想形成一次提交,用
git stash暂时保存; - 如果改动已经不需要了,才考虑
git restore或git checkout --丢弃。
注意:千万不要在切换分支时依赖
-f强制操作,强制切换的代价是工作区被目标分支内容覆盖,未提交的修改很难找回。
2.3 switch 和 checkout 的细节差异
我刻意在这里多说一句 switch,是因为实际团队协作中,总有人因为混用命令导致操作失误。git checkout feature/coupon-list 看起来没问题,但如果你不小心把分支名写成了文件名,比如仓库里恰好有个目录叫 feature,git checkout feature 就变成了“把文件 feature 从暂存区恢复”的操作,而不是切换分支。
git switch 的语义就单纯很多:它只负责“切换分支或创建新分支并切换”。对于新手来说,把“切分支”和“改文件”这两类操作分开,能少踩很多坑:
| 操作目的 | 推荐命令 | 旧式命令 |
|---|---|---|
| 切换到已有分支 | git switch feature/coupon-list |
git checkout feature/coupon-list |
| 创建并切换新分支 | git switch -c feature/coupon-list |
git checkout -b feature/coupon-list |
| 恢复工作区文件 | git restore file.txt |
git checkout -- file.txt |
| 丢弃暂存区内容 | git restore --staged file.txt |
git reset HEAD file.txt |
这个习惯的价值,在多人协作、长时间不开新分支时尤其明显。命令含义清晰,别人 review 你的操作记录或者你自己排查历史问题时,能大大减少认知负担。
3. 合并分支:fast-forward、auto-merge、冲突到底是怎么发生的
develop 分支上的同事可能同时在推进“商品详情页优化”,两周后你的优惠券列表写完,需要把 feature/coupon-list 合并回 develop。这时候执行:
bash复制git switch develop
git merge feature/coupon-list
人很容易把 merge 想象成“强行使两套代码黏在一起”,其实 Git 的合并远比这聪明,它先寻找共同祖先,再计算差异,再尝试自动合并。整个合并过程可能产生三种不同结果,这三种结果对应了你在团队里会遇到的各种 merge 场景。
3.1 三种合并结果的内部原理
第一种是 fast-forward(快进合并)。特点是从 develop 拉出 feature/coupon-list 之后,develop 本身没有产生任何新的提交,整条提交链仍然是线性延伸。这时候 Git 不需要做真正的“合并”,只需要把 develop 这个指针沿着提交链往前移动到 feature/coupon-list 所在的位置。它不会产生新的合并提交,历史非常干净。
第二种是 auto-merge(自动合并)。特点是 develop 在分叉之后也前进了,两条提交链各自都有新提交,但改动区域没有重叠。Git 会尝试把两边的改动都叠加到共同祖先上,如果成功,会自动生成一个合并提交(merge commit)。你可以设置默认使用 --no-ff 来强制走这种模式,哪怕可以快进也生成一个合并节点,目的是让“功能开发”的边界在历史里更清晰。
第三种就是 merge conflict(合并冲突)。两边改了同一处,Git 不敢替你决定,停下来把冲突标记写进文件,要求人工决策。
很多初学者只关心结果,不关心过程,所以遇到冲突就手足无措。我建议你多用 git log --graph --oneline --all 观察合并前后的历史结构,看得多了一眼就能判断自己刚才的合并属于哪种类型。
3.2 为什么有时候推荐 --no-ff
默认情况下,如果能快进,git merge 会直接快进,不会产生一个合并提交节点。这在简单的个人项目里问题不大,但在团队项目里会带来一个麻烦:你无法从历史图上看出“优惠券列表”这个功能是什么时候作为一个整体被合入的。
用 --no-ff 强制生成合并节点,等于在一次功能开发完成时打上一个“功能边界”标签:
bash复制git merge --no-ff feature/coupon-list
这样做的好处是方便回滚。合并后如果发现功能有问题,你只需要定位到那次合并提交,git revert 这个合并节点,整个功能就整体回退了,不需要一个个挑提交。坏处是提交历史会多一些合并节点,没有纯线性的历史那么清爽。
我的个人偏好是:集成分支(develop、master)上合入功能分支时用 --no-ff,功能分支内部的小步骤则随意提交,保持自然。如果团队有强制的历史线性要求,可以全用快进合并加 rebase,但那是另一个话题了。
3.3 合并后 Git 到底干了什么
合并不止是改变指针,它会真正修改你的工作区和暂存区。成功合并且无冲突时,目标分支的代码已经出现在工作区里,你可以直接继续写代码或运行测试。如果合并后发现代码有问题,可以通过:
bash复制git merge --abort
放弃这次合并,回到合并前的状态。需要注意的是,git merge --abort 只在你还没解决冲突、还在合并过程中时才有效。如果已经解决冲突并且完成了合并提交,就不再是“合并进行中”的状态,此时要回滚只能 git revert 或 git reset。
合并本质上是一次新的“提交生成”过程。自动合并成功时会自动生成一次提交;冲突时需要你手动解决、手动提交。理解这一点,会让你对“删代码是否安全”更有底气。
4. 冲突解决全过程:一条 feature 分支合并的真实推演
理论讲完,来一次实战推演。假设 develop 分支上用户中心的 user_profile.html 文件里有这样一段代码:
html复制<div class="card">
<h3>用户等级</h3>
<p>普通会员</p>
</div>
你负责的优惠券功能,把它改成了:
html复制<div class="card">
<h3>用户优惠券</h3>
<p>你有 3 张优惠券可用</p>
</div>
另一个同事做等级展示改造,同时把这块区域改成了:
html复制<div class="card">
<h3>会员等级</h3>
<p>黄金会员</p>
</div>
你们都是从同一个 develop 提交点拉出的分支,等两条分支合并时,Git 发现同一块区域被两种方式修改了,无法判断谁覆盖谁,于是产生了冲突。
4.1 冲突标记长什么样
执行合并时,Git 会列出冲突文件:
text复制Auto-merging user_profile.html
CONFLICT (content): Merge conflict in user_profile.html
Automatic merge failed; fix conflicts and then commit the result.
打开 user_profile.html,文件里会出现类似下面的内容:
html复制<<<<<<< HEAD
<div class="card">
<h3>用户优惠券</h3>
<p>你有 3 张优惠券可用</p>
</div>
=======
<div class="card">
<h3>会员等级</h3>
<p>黄金会员</p>
</div>
>>>>>>> feature/user-level
含义非常直白:
<<<<<<< HEAD到=======之间,是当前分支(也就是你正在合并的分支,develop)里的内容;=======到>>>>>>> feature/user-level之间,是待合并分支(feature/user-level)里的内容。
你要做的不是执行哪个命令,而是打开文件,人工判断保留哪边,或者把两边内容融合成一种新的写法。
4.2 三步走解决一次冲突
第一步:手动编辑文件。比如在这个场景里,产品最终决定“优惠券入口”和“会员等级”都要展示,那就把冲突标记删掉,改成一个两者兼容的版本:
html复制<div class="card">
<h3>用户优惠券</h3>
<p>你有 3 张优惠券可用</p>
<p>会员等级:黄金会员</p>
</div>
第二步:标记为“已解决”:
bash复制git add user_profile.html
第三步:完成合并提交(不传 -m 的话会打开编辑器,默认信息是 Merge 相关的描述,建议保留):
bash复制git commit
这里有个很多新手的误区:以为冲突解决后直接执行 git commit 会被拒绝。实际上,只有当你完成 git add 之后,Git 才会允许你提交合并结果。而且这次提交不需要额外加 -m,Git 会生成一条合并提交信息,你只需要保存退出即可。如果执行 git commit 之前想反悔,可以 git merge --abort 退出整个合并流程。
4.3 冲突解决的常见错误和更好的工具
新手最常见的错误是:手工编辑的时候把 <<<<<<<、=======、>>>>>>> 这些标记也当成代码保留下来了。这种文件在大部分语言里会导致编译或运行错误,而且错误信息往往不直观。解决完后务必搜索一下项目里是否还有这些标记:
bash复制grep -rn '^<<<<<<<\|^=======\|^>>>>>>>' src/
或者用带冲突高亮功能的编辑器全局搜索。
文件数量多、冲突区域复杂的时候,手工编辑效率很低。我推荐用可视化的合并工具,比如:
bash复制git mergetool
它会自动调用你配置好的工具(如 VS Code、Beyond Compare、Meld)打开每一个冲突文件。视觉上分成三栏:当前分支、共同祖先、待合并分支,下面还有一个可编辑的结果区域。这种工具对“复杂冲突选边”的场景特别友好,能直观看到哪一行来自哪个分支。
4.4 从根源上减少冲突的思路
冲突无法完全避免,但可以明显降低发生频率。我在实际团队里发现冲突多半不是 Git 操作问题,而是任务拆分问题。两个人同时改同一个文件同一段逻辑,大概率是需求边界没有划清楚。技术手段上能做的有三件事:
- 功能分支保持“短命”,尽量在两三天内合回集成分支,减少分叉时间;
- 公共模块、公共页面的改动提前在团队里同步,别攒到最后一次性合并;
- 每天从集成分支拉取最新代码到自己的功能分支,确保分叉点不会越拉越远。
其中第三点非常重要。如果不定期同步,分叉点可能是一个月前的提交,意味着你和别人累积了一个月的差异在同一时间点爆发,冲突范围会非常大。保持分叉点尽量新,冲突数量和难度都会大幅下降。
5. 临门一脚的紧急 bug:stash + 临时分支的完整抢救链路
回到开头那个场景:你正在 feature/coupon-list 上开发优惠券列表,写到一半,测试突然说生产环境用户登录后首页空白,问题定位到是登录模块的 session 处理有 bug。你需要立刻切一条紧急修复分支,但当前功能的代码又没写完,还不能形成一次干净提交。
5.1 git stash 保存现场
处理这种“半路杀出程咬金”的情况,最稳的方式是把当前改动暂时保存到堆栈里:
bash复制git stash push -m "coupon-list wip"
执行完后,你的工作区会回到当前分支最近一次提交的状态,看起来“干干净净”。想执行任何切换、合并操作都没问题。确认一下保存是否成功:
bash复制git stash list
输出中能看到你刚才保存的那条记录,说明现场被完整保留。这个操作的本质是:把工作区和暂存区的修改打包成一个 stash 提交对象,保存到 refs/stash 里,然后清空当前工作区。
5.2 从哪拉 bug 修复分支是个决策点
很多人到这里有个疑问:紧急 bug 修复分支应该从哪个版本拉?答案是“线上正在运行的那个版本”。如果生产环境跑的是 master 分支的某个发布标签 v1.2.0,那么修复分支应该从那个标签拉,而不是从你正在开发的 develop 或 feature/coupon-list 拉,因为后者可能包含一堆还没上线的新功能。
bash复制git switch -c hotfix/session-fix v1.2.0
这样你的修复分支只包含相对于线上版本的必要改动,不会把一堆开发中的功能卷进去。等修复完成、测试验证通过后,把它合并回 develop 和 master 两条主线:
bash复制git switch master
git merge --no-ff hotfix/session-fix
git tag v1.2.1
git switch develop
git merge --no-ff hotfix/session-fix
合并到 develop 的意义是保证后续新功能分支在集成时不会丢失这个修复。
5.3 回到原分支并恢复现场
修复完成、发布之后,回到原来开发的分支:
bash复制git switch feature/coupon-list
git stash pop
git stash pop 会把你之前保存的修改重新应用到当前工作区。这里要注意,执行 stash pop 后如果提示冲突,说明你在离开期间这个分支上某些文件发生了变化,导致你原来保存的修改已经不能平滑地贴合到新代码上。处理方式和普通合并冲突一样,手动解决后 git add,然后 git stash drop 清掉这条 stash 记录。
5.4 stash 是栈,不是保险箱
stash 的常用命令里,有一个容易踩坑的点:git stash pop 和 git stash apply 的区别经常被忽视。pop 恢复完会自动删除最新一条 stash 记录,apply 恢复完不会删除。这意味着,如果你只是想临时把改动“铺开”看一下,可能不想删 stash,用 apply 更安全。但如果你有多条 stash 并存,频繁 apply 会导致你分不清哪条是新哪条是旧。
我见过同事把 stash 当长期代码保存工具用,一存就是几个月,最后自己都忘了里面是什么。这里给个建议:stash 只适合“短时间暂停”,超过一两天不处理、又要继续开发的现场,宁可开一条 WIP 分支提交上去,也不要一直放在 stash 里。原因很实际:分支提交是显性可见的,别人能看到你在做什么;stash 是隐形的,你自己都很容易遗忘。
6. 分支删除:-d 和 -D 的区别,以及强删之后如何补救
功能合并完了,分支的历史使命结束,该清理了。这一步看着简单,但恰恰是多人协作时最容易被忽略、也最容易出事故的地方。
6.1 标准删除流程
删除本地分支:
bash复制git branch -d feature/coupon-list
这里的 -d 是 --delete 的缩写。Git 在执行删除前会做一个安全检查:这个分支是否已经被合并到当前分支。如果检查通过,分支指针被移除,任务完成。
如果功能分支已经合并到远程仓库,你还需要删除远程分支:
bash复制git push origin --delete feature/coupon-list
本地分支删除后,可能还会在本地缓存里残留一些已经不存在于远程的同名分支记录,可以用下面的命令清理:
bash复制git remote prune origin
如果你习惯用可视化工具,在 VS Code 的源代码管理面板里也可以右键删除分支。但底层原理是一样的:先检查是否已合并,未合并会拒绝删除。
6.2 什么时候会用到 -D 强删
-d 删除被拒绝时,Git 会返回一条很明确的提示:
text复制error: The branch 'feature/coupon-list' is not fully merged.
If you are sure you want to delete it, run 'git branch -D feature/coupon-list'.
意思是:这条分支还有未被合并的提交。你确定不后悔,就再执行一次 git branch -D 强制删除。
-D 就是 --delete --force,它跳过了“是否已合并”的安全检查,直接移除分支指针。什么情况下应该强制删除?
- 功能做了一半,团队决定彻底放弃,不再开发了;
- 这条分支上的提交内容已经在其他地方用 cherry-pick 方式复制过了;
- 你自己确认过分支上的所有提交都不需要保留。
千万不要在“不确定”的时候用 -D。一旦强删,分支名没了,分支记录里的提交链如果没有任何其他引用,就成了“悬空提交”,在 Git 的常规视图里完全不可见。虽然数据不一定立刻消失,但找回成本和当时的心情压力都会很大。
6.3 强删之后的补救操作
这条可能是很多人不知道、但关键时候能救命的经验:被删掉的分支,其实很长一段时间内仍然可以从本地的对象库里找回来。因为删除分支只是删掉了一个引用,并没有立刻删除提交对象。你可以用:
bash复制git reflog
查看本地的操作历史,找到删除前那条分支最后一次指向的提交哈希。这个哈希如果还在,直接重新创建分支:
bash复制git switch -c feature/coupon-list <commit-hash>
如果 reflog 里已经找不到(比如过了很久,reflog 过期了,或者仓库做过了 gc 清理),还可以用 git fsck --lost-found 扫描悬空提交。原理是 Git 在回收对象前,会先把它标记为 dangling,这个命令能扫描出未被任何分支引用的提交对象。
注意:删除远程分支后,如果别人本地还保留了这条分支的跟踪引用,他们仍然能在本地找到代码。所以团队协作里,“远程分支误删除”并不是世界末日,只要有人本地有最新代码,都可以重新推一个分支回去。
6.4 删除分支的保护意识
不是所有分支都适合随便删除。像 master、develop、release 这类长期存在的集成分支,就不应该使用 git branch -d 或 -D 来处理。这类分支要么在远程仓库里设置为“受保护分支”,禁止直接推送;要么在团队规范里明确禁止删除。
我在本地环境里的习惯是:长期分支(master、develop)永远不删,功能分支(feature/*)合并后立即清理,修复分支(hotfix/*)发布并打标签后立即清理。利用分支名前缀来区分生命周期,看名字就知道这条分支是“用完即弃”还是“常驻不回”。
7. 分支管理与团队协作:选对策略比敲对命令更重要
到这里,很多读者可能已经把创建、切换、合并、删除的命令都跑通了。但项目越做越大的时候你会发现,真正让团队痛苦的不是命令不会敲,而是“谁在什么分支上开发、什么时候合并、什么时候删除”没有一套共识。这一节分享一下我在不同团队规模下实践过的分支管理经验。
7.1 三种常见分支组织方式对比
我把日常接触到的团队分支策略梳理成三类,各有适用场景:
| 策略 | 特点 | 适合团队规模 | 发布频率 |
|---|---|---|---|
| 集中式单分支 | 所有人都在 master 上提交,用标签标记版本 |
1-5 人小团队,产品原型阶段 | 极高频,随时发布 |
| 功能分支 + 主干合并 | master 长期可用,每个需求拉 feature/* 分支,合并后删除 |
5-20 人产品团队 | 每周或每两周 |
| Git Flow 完整策略 | master + develop + feature/* + release/* + hotfix/* 多重分支 |
20 人以上,有明确版本周期 | 月度或版本化发布 |
前两类很容易理解,第三类 Git Flow 需要多说一句。它把分支角色分得很细:
master永远对应线上可发布状态;develop是日常集成分支;feature/*是功能开发分支,从develop拉出,合回develop后删除;release/*是从develop拉出的发布准备分支,只做 bug 修复和版本号调整,稳定后合回master与develop;hotfix/*是从master拉出的紧急修复分支,修复完同时合回master和develop。
这种策略体系庞大但它确实适合版本发布节奏固定的产品。如果你们是一个每周发版、甚至每天发版的互联网应用,Git Flow 会显得笨重,我建议直接走简化版:master 或 trunk 作为主干,功能分支短命存在,配合 CI 自动构建和自动测试,快速合入快速发布。
7.2 功能分支的生命周期模板
我把个人比较推崇的一条功能分支完整生命周期列在这里,可以直接复制到团队规范里:
- 从最新的
develop拉出分支:git switch -c feature/xxx develop; - 开发期间每天同步主干:
git switch develop && git pull && git switch feature/xxx && git merge develop; - 功能完成,跑完本地测试,推送到远程:
git push -u origin feature/xxx; - 发起合并请求,请同事代码评审;
- 评审通过后合入
develop,在远程界面上删除该功能分支; - 本地同步删除:
git branch -d feature/xxx。
第 2 步是很多人会跳过的一步。跳过它的结果就是:你的功能分支分叉点越来越老,最后合并时的冲突范围像滚雪球一样膨胀。养成“每天从主干拉最新代码”这个习惯,能减少大部分不必要的冲突解决成本。
7.3 分支命名规范的价值
分支命名看起来是小事,实际影响很大。我看到过很多仓库里出现 test、fix、123、new 这种含糊分支名,过两周没人知道它对应的需求和目标版本是什么。建议用 类型/简要描述 的结构,例如:
feature/coupon-list:新功能;bugfix/login-session:普通 bug 修复;hotfix/pay-timeout:线上紧急修复;release/v1.3.0:版本发布准备;chore/update-deps:依赖升级、构建配置等杂务。
这样命名之后,git branch 列表本身就能承担一部分项目管理的功能,哪些功能在开发、哪些在待发布、哪些是紧急修复,一眼扫过去清清楚楚。而且配合删除策略,前缀本身就暗示了分支的生命周期长短:hotfix/* 和 feature/* 必然短命,release/* 在版本发布前存在,develop 和 master 则是永久分支。
7.4 团队里最容易忽略的“分支卫生”
最后聊一个日常最容易忽略、但对仓库健康度影响很大的事:分支清理。
很多团队代码仓库里累积了几百条没人管的远程分支——功能合并了,忘了删;老版本修复分支,也没人清理。每次拉取远程分支列表都变得很长,新人根本找不到哪个分支是当前有效的。
我的习惯做法是:
- 每次功能合入后第一时间删远程分支;
- 定期执行
git remote prune origin清理本地过期的远程跟踪分支; - 在 CI 或代码托管平台开启自动删除合并后分支的选项;
- 每隔一两个月让团队做一次“分支大扫除”,把没人认领的死分支统一备份(打 tag 或者推到特定备份分支)后删除。
如果你发现一条分支上确实有过功能代码,但功能已经通过另外的方式上线了,又不敢确定能否删除,最稳妥的办法是给这个分支打一个标签再删:
bash复制git tag archive/feature-coupon-list feature/coupon-list
git branch -D feature/coupon-list
这样既不会让分支列表越来越长,又有“后悔药”可以吃。
做了几年 Git 相关的技术管理工作后,我最大的感触是:分支操作本身没有多难,真正体现功力的,是在各种不确定场景下还能保持仓库历史干净、团队协作不互相阻塞。上面这些方法没有哪一条是银弹,但你只要把“分支是指针”“短命分支”“及时合并及时删”这几个理念内化到日常习惯里,遇到任何复杂 Git 问题都不会再慌。
