简介:一份面向开发团队(尤其是 Git 使用新人)的分支管理规范文档,系统梳理 master、develop、feature、bugfix、release、hotfix 六类分支的职责、创建来源与合并去向,并结合常用命令介绍命名规则、切换方法、合并时机和发布流程。文档明确:功能分支应从 develop 创建、自测通过后合并回 develop;普通 bug 从 develop 创建 bugfix 分支,修复后同样合并回 develop;紧急线上问题从 master 创建 hotfix 分支,先合并至 master 打标签上线,再合并回 develop;发布前从 develop 拉出 release 分支,经历测试和缺陷修复后,同步合并至 master 和 develop,合并后分支应及时清理。压缩包内含 1 个 doc 文档,体积约 239KB,正文以章节结构展开,还包含必读文章推荐、git flow 工具的安装与简化操作,以及发布 Release 和 Hotfix 的完整步骤,适合技术负责人或团队新人直接作为规范参考。已有 2178 人学习浏览,文档基于作者近一年的实战经验沉淀,既有理论框架又有可落地细节,是搭建团队 Git 协作流程的实用蓝本。
1. 分支管理规范不是流程文档,是团队的后悔药
一个十人不到的研发团队,最容易翻车的往往不是代码质量,而是 Git 分支乱到没人敢合并。你问某条需求在哪,答案要么是“我本地还没推”,要么是“好像有个 feature 分支,但不记得叫啥了”;功能上线后想回滚,发现主干上混着没测完的提交,根本不敢 revert。GIT 分支流程开发规范解决的就是这件事:它不改变你写代码的方式,但改变你提交、合并、发布的路径,让每一次变更都有迹可循。这篇文章写给正在从“单打独斗”走向“多人协作”的团队,也写给想给仓库立规矩却又不知道从哪下手的同学。落地优先,不搞理论堆砌。
2. 分支模型怎么定:先把主干保护起来
2.1 为什么主干分支必须稳
很多团队刚建仓库时只有一条 main(或 master),所有人直接往上推,推坏了再修。这种模式在小项目里勉强能用,一旦并行需求超过两三条,主干就成了谁手快谁先合、谁出错谁背锅的赛跑场。所以分支管理规范的第一条原则不是“多建分支”,而是“主干要稳”。主干分支只接受经过验证的提交,日常开发一律不让直接触达。
常见的落法是参考 Git Flow 的骨架,但按团队规模做裁剪:保留 main 和 develop 作为长期分支,feature、release、hotfix 作为短期分支。长期分支的生命周期跟项目走,短期分支跟需求或缺陷走,合并完之后该删就删。这个模型的优势在于它把“开发中”和“已就绪”两种状态物理隔开,main 永远对应线上或即将上线的版本,develop 永远对应下一次迭代的集成现场。
2.2 五类分支的角色、命名与生命周期
定了主干稳定原则之后,分支类型和命名规则就成了下一件必须写进规范的事。命名乱是团队协作里最消耗耐心的琐碎问题,规范统一后,光是看分支名就能判断这条分支在干什么、从哪来、要合到哪去。
| 分支类型 | 命名规范 | 来源 | 合并去向 | 生命周期 |
|---|---|---|---|---|
| 主干分支 | main | 仓库初始化 | 不接受直接推送 | 永久 |
| 开发分支 | develop | 从main切出 | 合并回main | 永久 |
| 功能分支 | feature/编号-描述 | 从develop切出 | 合并回develop | 需求完成后删除 |
| 发布分支 | release/版本号 | 从develop切出 | 合并回main与develop | 发布完成后删除 |
| 热修分支 | hotfix/编号-描述 | 从main切出 | 合并回main与develop | 修复上线后删除 |
命名里的编号建议直接用需求单号或缺陷单号,比如feature/1024-user-login。这样看分支名能回溯到业务上下文,之后写提交信息、查历史、找责任人都有依据。描述部分用短横线连接,全部小写,不要带空格和中文,避免在 Windows 和 macOS 上出现换行解析问题。
2.3 分支生命周期的两个硬规则
分支规范里最容易执行不到位的是清理环节。feature 分支合并进 develop 之后,它就已经完成了使命,留在远端只会让仓库分支列表越来越长,还会让后来的同事误以为这条分支还在维护。
两个硬规则值得直接写进团队规范:第一条,短期分支合并完成后由合并者顺手删除远端分支;本地分支在切换回 develop 后用git branch -d清理。第二条,分支超过两周没有提交且关联需求已关闭的,由仓库管理员确认后强制清理。这两条配合分支保护规则一起用,仓库长期保持干净状态,新人进来一眼能看懂,老人不需要靠记忆找分支。
3. 按流程把分支跑起来:从创建到热修的命令序列
3.1 初始化:从空仓库到 develop 分支
分支规范落地的前置条件是仓库结构本身符合规范。新仓库第一次提交时,先建一个无内容的 README 作为初始提交,之后创建 develop 分支,这样之后所有 feature 和 hotfix 分支的切换都有干净的起点。
# 空仓库初始化:先建初始提交,再切开发分支 git init touch README.md git add README.md git commit -m "chore: 初始化仓库" git branch -M main git checkout -b develop git push -u origin main develop这里第一行git branch -M main把默认分支名统一为 main,避免不同成员本地默认名不一致,后续 push 时候还要猜。git checkout -b develop从 main 切出开发分支,git push -u origin main develop一次把两条长期分支都推到远端并建立跟踪。到此,远端仓库就有了 main 和 develop 两条长期分支,后续短期分支都从这两条往下切。
3.2 功能分支的日常:提交、同步与 rebase
功能开发阶段最容易出乱子的是“分支越拖越旧”。一个功能写了两周,develop 上已经合了别人十几个提交,这时候你想再把 feature 合回去,冲突规模早已失控。所以规范里要约定:功能分支每天至少从 develop 同步一次,同步用 rebase 而不是 merge。
# 切回功能分支后,从 develop 拉取最新提交并变基 git checkout feature/1024-user-login git fetch origin develop git rebase origin/develop # 如果有冲突,解决后继续 git add . git rebase --continue命令的含义是:把当前 feature 分支上独有的提交,挪动到 origin/develop 当前提交之后的快照上,让功能分支历史是一条直线。这样做的收益在于合并回 develop 时不会生成多余的 merge commit,冲突也被提前分散到每天的同步里,而不是最后一天集中爆发。参数说明:git fetch只更新远端仓库在本地的记录,不改变工作区;git rebase origin/develop是变基操作,没有把 develop 合入 feature,所以不会残留无意义的合并节点。
3.3 合并进 develop:用 --no-ff 保住需求边界
功能开发完成并自测通过后,合入 develop。合并方式这里有一个重要的参数选择:--no-ff。
# 先切到 develop 并同步最新代码 git checkout develop git pull --rebase origin develop # 合并功能分支,保留独立提交线 git merge --no-ff feature/1024-user-login git push origin develop--no-ff的含义是不使用快进方式合并,即使当前 develop 可以直接指向 feature 分支的末端,也强制生成一个 merge commit。这个参数据我的经验值得成为规范标配,它保留了一个稳定的“需求提交容器”,之后做发布回溯时,git log --oneline --graph里能清楚看到某条需求是什么时候进入 develop 的。合并后 push 之前,记得先git pull --rebase,避免本地 develop 落后远端却强行合并出分叉,这个动作看着多余,实际上省掉很多“push 被拒”的尴尬。
3.4 发布与热修:两个特殊流程的命令
发布分支和热修分支都是带有明确时效的短生命周期分支,但它们的来源和归处完全不同。发布分支从 develop 冻结,只修 bug,不接新功能;热修分支从 main 直接切出,解决线上紧急问题后同时合回 main 和 develop,防止线下环境漏掉这处修复。
# 发布分支:从 develop 冻结,发布后合并回 main 与 develop git checkout -b release/1.2.0 develop # ... 只修 bug,不合新功能 ... git checkout main git merge --no-ff release/1.2.0 git tag -a v1.2.0 -m "release 1.2.0" git checkout develop git merge --no-ff release/1.2.0 # 热修分支:从 main 切出,修复后合并回 main 与 develop git checkout -b hotfix/2099-payment-fix main git commit -m "fix: 修复支付回调验签失败" git checkout main git merge --no-ff hotfix/2099-payment-fix git tag -a v1.2.1 -m "hotfix 1.2.1" git checkout develop git merge --no-ff hotfix/2099-payment-fix这个流程里最容易漏掉的动作是热修完成后没有合并回 develop。线上发现的问题往往在生产环境才暴露,develop 里同样存在这段有缺陷的代码,只合 main 不回 develop,下次发布时这个 bug 会原样再犯一遍。发布分支同理,发布测试中修的几个小 bug 若不留回 develop,等于白修。所以热修和发布的合并目标必须是双份的,这个靠肌肉记忆不可靠,建议写进团队检查单或者 CI 脚本里做自动检测。
4. 保护与自动化:让规范从“自觉”变成“强制”
4.1 分支保护规则:前端拦截硬编码配置
依赖自觉执行的分支规范,总会在周五下午被赶需求的人打破。所以规范要落地成纪律,必须在远端仓库配置分支保护规则。常见做法是在 Git 平台的仓库设置里,对 main 和 develop 开启“保护分支”:禁止直接推送,所有变更必须通过合并请求进入。主干分支的 push 权限收窄到仓库管理员,甚至设为管理员也不能直接推。单靠这一条规则,就能把绝大多数不合规提交挡在仓库之外。
保护规则还有一个容易忽略的附加项:要求合并请求通过 CI 检查和至少一人评审后才允许合并。这条参数对应的是“已验证”的流程目标,CI 检查至少包含构建成功和单元测试通过,评审保证至少有一个其他成员看过变更,避免“自己写自己合”造成的历史盲区。配置好之后,凡是想直接 push main 的人都会收到远端拒绝的提示,这个提示本身就是最好的规则教育。
4.2 钩子脚本:本地先拦一道
远端规则管的是 push 那一刻,但本地 hook 可以在提交之前就把不合规内容拦下。pre-push 钩子是性价比最高的强化手段,它会在git push触发时执行一段脚本,按团队规范校验当前分支名和提交信息格式,校验不通过就拒绝推送。
#!/bin/sh # .git/hooks/pre-push 示例:检查分支名和提交信息格式 # 安装方式:复制到 .git/hooks/ 目录并添加执行权限 remote="$1" url="$2" # 获取即将推送的分支名 branch=$(git symbolic-ref --short HEAD 2>/dev/null) if [ -z "$branch" ]; then echo "错误: 无法识别当前分支名" exit 1 fi # 检查长期分支:只允许 main 和 develop 推送 if [ "$branch" = "main" ] || [ "$branch" = "develop" ]; then echo "警告: $branch 是长期分支,不允许直接推送" echo "请通过合并请求合入变更" exit 1 fi # 检查短期分支命名的前缀合法性 case "$branch" in feature/*|release/*|hotfix/*) ;; *) echo "错误: 分支名必须以 feature/、release/ 或 hotfix/ 开头" exit 1 ;; esac # 检查最近一条提交信息是否违反提交格式 last_commit=$(git log -1 --format=%s) if ! echo "$last_commit" | grep -qE "^(feat|fix|docs|style|refactor|test|chore):"; then echo "错误: 提交信息缺少 type 前缀,例如 feat: 加登录弹窗" exit 1 fi exit 0脚本里的三段检查分别对应本章前面的三个规范点:长期分支带头保护、短期分支按类型命名、提交信息用约定式格式。执行顺序上,这里先从最外层的分支名查起,再逐条深入提交信息,每一条失败都给出明确的中文提示,成员被拦下来的时候知道自己改什么,而不是摸不着头脑。
这个脚本也有边界要说清楚:.git/hooks/目录不随克隆传给下一个开发者,需要团队成员手动复制或用一次性的初始化脚本写入配置。如果团队仓库可以接受引入第三方工具,也可以用现成的 pre-commit 框架托管同一套钩子,但核心逻辑和返回码约定不变。
4.3 提交信息规范:让 git log 当日报读
很多团队管了分支,却漏了提交信息。其实提交信息是分支流程的“末梢神经”,也是后期回溯问题时最可靠的下钻路径。提交信息不规范,git log --oneline就是一篇没有章法的流水账,遇到紧急热修根本找不到对应的那条变更。
规范的核心是约定式提交的前缀,每一条提交信息必须匹配type(scope): 描述这种格式。常用 type:feat表示新功能,fix表示缺陷修复,docs表示文档变更,refactor表示重构,test表示测试相关,chore表示构建或杂项。scope 可写模块名,也可省略,描述用一个动词开头,控制在五十字符以内。
这套格式的好处是脚本可解析、日志可读、发布日志可自动生成。配合前面 pre-push 钩子里的正则校验,就能让团队在根上调整到高一致性状态。实际操作中可以在团队文档里放一段示例模板,让成员提交时照着写,两周后习惯就养成了。
5. 分支合并与回溯:五个高频翻车点和排查方法
5.1 现象:rebase 后 push 被远端拒绝
这个翻车现场几乎每个团队都经历过一次。成员在 feature 分支上执行git rebase origin/develop之后,本地历史被重写,接着 push 时远端提示“拒绝推送”。原因是在执行 rebase 之前,feature 分支已经被 push 到远端并可能被他人拉取过,本地员用自己的新历史覆盖远端,Git 认为这是对他人的潜在破坏,于是拒绝。
解决的方法是 push 时加--force-with-lease,这个参数比--force安全一个量级:它会先对比远端引用是否还是自己上次 fetch 时的哪个版本,只有在远端没有被其他人推进时才会强推,从而避免覆盖他人的新提交。血泪经验是,rebase 之前先开会知会,尽量只 rebase 自己独占的分支,共享分支上尽量用git merge origin/develop而不是 rebase。
5.2 现象:切分支后代码“丢失”,提交找不回来
这条常发生在直接复制粘贴命令的场景。成员执行git checkout -b feature/xxx,切换到新分支时提示本地有未提交改动,习惯性用了git checkout .或git stash,结果写了半天代码“没了”。如果只是 checkout 切分支,改动还留在工作区;一旦用了git stash,改动被收进暂存堆里,git stash pop才能恢复。
排查顺序建议是:先git stash list看有没有暂存堆;再git reflog看分支切换历史,reflog 是 Git 的后悔药,记录着 HEAD 过去所有移动。如果改动曾经进入历史提交,用git reset --hard HEAD@{n}就找得回来;如果只是未提交的工作区文件,回退窗口有限,所以最稳的做法是养成提交后再切分支的习惯,或者用git stash并立刻在便利贴上记下 stash 编号。这条建议身边的同事已经用了好几年,客观上救回过几个需求周期。
5.3 现象:同一个文件反复冲突,怎么解都解不完
冲突集中在少量文件上,背后一定是格式或依赖层面的问题。最常见的是package-lock.json、yarn.lock、go.sum这类锁文件,以及格式化工具全局刷过的文件。多人并行改依赖时,每次合并都冲突一次,哪怕内容差异只有一两行。
处理锁文件冲突的方式是:选中其中一边的完整版本,回到 develop 重新安装依赖并提交。具体命令是在合并冲突后执行git checkout --theirs package-lock.json(或--ours,取决于目标分支方向),然后重新npm install或yarn install让工具自己纠偏。预防层面,把锁文件纳入合并策略,必要时用.gitattributes标记为merge=union,但会更冒险,建议只在团队熟练掌握以后再启用。
5.4 现象:ssh 认证失败,clone 拉不下仓库
分支流程再规范,第一步仓库拉不下来说什么都没用。git 使用 SSH 协议时提示权限拒绝,常见原因有三类:本机缺少公钥、公钥没有添加到远端账号、或者本机存在多个 SSH key 导致用了错误的身份。
先排查本机是否有公钥:ls ~/.ssh,没有就执行ssh-keygen -t ed25519 -C "you@example.com"生成密钥对。然后复制~/.ssh/id_ed25519.pub内容,粘贴到 Git 平台的 SSH 公钥设置页。如果配置了多个密钥,需要在~/.ssh/config里按 Host 指定IdentityFile。排查消息上,ssh -T git@git.example.com是一条很直接的连通性测试命令,返回欢迎语说明认证链路已通,剩下的问题基本只在 HTTPS 跟 SSH 的混用上。
5.5 现象:保护分支被“绕过”,合并记录像雪花一样乱
保护规则配置好之后,仍然可能遇到不合规的合并记录被推到远端。最常见的路径不是绕过规则,而是管理者在合并请求页面勾选了“合并后删除源分支”的同时,手工在本地通过git push origin main强推了本地 main,保护策略只拦截普通 push 和部分平台上的强推,管理员身份下有些行为的判定会放宽。
解决方式是收敛权限:保护规则里把 main 的强推权限也关掉,管理者平时发布只用合并请求或定期从 release 分支同步。另外给团队约法三章,线上紧急修复一律走 hotfix 分支并保留合并请求记录,到排查问题时才有关键证据链。这两条配合下来,绕过的空间基本被堵死。
6. 从规范到习惯:一条命令守住提交线
规范文件写得再漂亮,最终都要落到工具链上才能被长期执行。这里分享一个我一直在用的做法:把第五章的 pre-push 检查代码封装成一个独立的可执行脚本文件,放在项目根目录里的scripts/git-check.sh,然后在.git/hooks/pre-push里只保留一行调用。
#!/bin/sh # .git/hooks/pre-push 的简化版 # 用它调用团队公共规范脚本,避免每个克隆仓库各维护一份 exec sh "$(git rev-parse --show-toplevel)/scripts/git-check.sh" "$@"这样做的好处是:当团队新增一个提交信息前缀类型,或者在分支命名规则里增加新约束时,只需修改scripts/git-check.sh,新克隆的仓库自动拉到最新逻辑。旧仓库由管理员批量执行一次git config core.hooksPath scripts/git-hooks,朝core.hooksPath指向统一目录切换,之后不需要每个成员手动把 hook 复制到.git/hooks。
验证这套检查是否生效,最直接的方法是故意推一个不合规的分支:比如在本地新建一个test-branch,提交一条不带 type 前缀的信息,然后 push。命令被拦截说明钩子生效,检查项的报错文字也能顺手验证是否足够清晰。一个新成员要独立走完一个需求周期:建 feature 分支、每天 rebase develop、提合并请求、等评审、合入、删分支,全过程都不需要问“我该推哪个分支”,这套规范才算真的立住了。
我第一次在团队推行这套规范的时候,把 main 保护规则打开的那天,两个月里第一次有人跑过来问“为什么我 push 不了了?”,我告诉他这不是 push 坏了,是规则起作用了。之后再也没有人因为误推 main 而在群里道歉。分支流程的价值不是限制动作,而是在动作出错的时候,给每个人一张可以往回找的路线图。希望帮到你。
本文还有配套的精品资源,点击获取