多人协作开发中,功能分支合并到主分支时经常出现历史混乱、分叉等问题。本文分享一套标准操作流程,帮你实现个人分支历史纯净 、主分支线性历史 、合并粒度可控。
先看问题
假设你从主分支 main1 拉了一个功能分支 feat/user 来开发用户管理模块:
main1: a---b---c---x---y (同事陆续提交了 x、y)
↑
feat/user: \---d---e---f---g (你的多个 commit)
开发完了,要合并回去,但问题来了:
feat/user上有好几个 commit,合并后主分支会变得很乱main1期间被同事提交了不少新代码,不能直接 merge- 你只想合并其中几个 commit,不是整个分支
怎么办?
三条合并原则
在动手之前,先记住这三条原则:
- 功能分支保持不变 ------ 不把主分支代码合进来,保持个人代码纯净
- 主分支一条直线 ------ 合并后历史是线性的,没有分叉
- 单 commit 合并 ------ 无论功能分支有多少 commit,合到主分支只体现为一条记录
八步完成合并
下面是完整的操作流程,跟着走就行。
第 1 步:确定要合并的 commit 范围
在功能分支上查看提交记录:
bash
git checkout feat/user
git log --oneline
输出类似这样:
a1b2c3d 功能4 - 完成数据导出(不需要)
e4f5g6h 功能3 - 完成数据筛选 (结束)
i7j8k9l 功能2 - 完成列表展示
m0n1o2p 功能1 - 完成页面框架 (开始)
q3r4s5t main1 的基 commit(不要)
记下起止 hash :开始 m0n1o2p,结束 e4f5g6h。
第 2 步:同步主分支最新代码
bash
git checkout main1
git pull origin main1
这一步很关键。同事可能已经往
main1推了新代码,同步一下避免后续冲突。
第 3 步:创建临时合并分支并记录起点
从 main1 最新代码创建临时分支,在此分支上完成 cherry-pick 和压缩操作,不直接操作 main1
bash
git checkout -b merge-for-main1
为什么要建临时分支?因为所有 cherry-pick 和压缩操作都在这里完成,不直接动 main1,安全可控。
创建好之后,立刻记录当前 HEAD 的 hash,后面压缩 commit 时要用到:
bash
# 记录起点 hash 到变量
$START_MAIN1 = git rev-parse HEAD
echo $START_MAIN1
这个 hash 就是
main1当前最新 commit 的位置,后面reset --soft会回到这个点。一定要记下来,否则压缩时找不到起点。
第 4 步:cherry-pick 指定范围的 commit
bash
git cherry-pick m0n1o2p^..e4f5g6h
语法解释:开始^..结束 表示从"开始"到"结束"(都包含)的所有 commit。
如果遇到冲突,解决后执行:
bash
git add .
git cherry-pick --continue --no-edit
注意:cherry-pick 后 commit hash 会变,但代码内容不变。
第 5 步:压缩成一个 commit
用第 3 步记录的起点 hash 做软重置,把所有 cherry-pick 的 commit 暂存到 staging area,然后重新提交:
bash
git reset --soft $START_MAIN1
git commit -m "feat: 用户功能模块完整实现"
reset --soft不会丢代码,只是把 HEAD 指针回到起点,所有变更保留在暂存区。再commit一下,多个 commit 就变成了一个,干净利落。
第 6 步:推送临时分支到远程
bash
git push origin merge-for-main1
第 7 步:合并到主分支
根据仓库配置,分两种情况:
情况 A:main1 没有保护(可以直接 push)
bash
git checkout main1
git merge merge-for-main1 --ff-only
git push origin main1
情况 B:main1 有保护(需要走 合并请求 流程)
在 Git 网页上操作:
- 打开仓库 → Pull Requests → New Pull Request
- Source 选
merge-for-main1,Target 选main1 - 合并方式选 Rebase and merge (推荐)或 Fast-forward merge
- 提交 合并请求,等审查通过
切记:不要选 "Create a merge commit",否则会产生分叉。
第 8 步:清理临时分支
bash
git checkout main1
git branch -D merge-for-main1
git push origin --delete merge-for-main1
一张图看懂整个流程
初始状态:
main1: a---b---c---x---y
↑
feat/user: \---d---e---f---g
创建临时分支:
merge-for-main1: x---y
cherry-pick d..f:
merge-for-main1: x---y---d'---e'---f'
压缩成单 commit:
merge-for-main1: x---y---S
Rebase and merge 后:
main1: a---b---c---x---y---S (一条直线!)
冲突处理速查表
cherry-pick 过程中遇到冲突不用慌:
bash
# 查看哪些文件冲突了
git status
# 手动解决冲突后
git add <冲突文件>
# 继续 cherry-pick
git cherry-pick --continue --no-edit
# 实在搞不定?中止操作
git cherry-pick --abort
合并后各分支状态
| 分支 | 最终状态 |
|---|---|
feat/user |
完全不变,保持原有多个 commit,可继续开发 |
main1 |
一条直线,新增 1 个 commit |
merge-for-main1 |
已删除(用完即弃) |
快速参考命令
把下面这段存下来,下次直接用:
bash
# 1. 查看 需要合并的commit hash
git log feat/user --oneline
# 2. 创建合并临时分支
git checkout main1
git pull origin main1
git checkout -b merge-for-main1
$START_MAIN1 = git rev-parse HEAD
echo $START_MAIN1
git cherry-pick <开始>^..<结束>
git reset --soft $START
git commit -m "feat: xxx"
git push origin merge-for-main1
# 3. 合并到 main1
git merge merge-for-main1
# 4. 清理
git checkout main1
git branch -D merge-for-main1
git push origin --delete merge-for-main1
写在最后
这套流程的核心思路是:用临时分支做缓冲,cherry-pick 精选代码,压缩成单个 commit 后合入主分支。
这样既保证了主分支历史的干净整洁,又保留了功能分支的完整记录,还能精确控制合并范围。团队协作中统一这套规范,代码审查和版本追溯都会轻松很多。