2024年3月,我负责的支付模块需要合并feature/payment-v2到develop。feature分支有87个commit,develop有23个新commit。我习惯性用了git rebase develop,结果冲突解决到第15个commit时,手滑执行了git rebase --abort。更糟的是,之前已经用git push --force推送过部分rebase后的commit。最终导致200+commit丢失,团队回滚花了2小时,线上支付延迟30分钟。
这不是个例。根据GitLab 2023年调查,42%的团队因rebase操作不当导致过代码丢失。本文用真实数据告诉你:什么时候用rebase,什么时候用merge,以及如何用脚本避免手滑。
核心矛盾:
我们团队用Git 2.43.0,在4核8G服务器上对100个commit的分支做压测:
| 操作 | 平均耗时(秒) | 冲突率 | 历史可读性(1-10) | 回滚难度 |
|---|---|---|---|---|
| merge | 0.8 | 12% | 4 | 低 |
| rebase | 3.2 | 38% | 9 | 高 |
| squash merge | 0.5 | 8% | 8 | 中 |
结论:没有银弹。需要根据场景选策略。
适用场景:feature分支开发周期>3天,多人协作,需要保留完整开发上下文。
配置:
# 全局配置,避免自动merge commit
git config --global merge.ff false
git config --global pull.rebase false
# 合并命令
git checkout develop
git merge --no-ff feature/payment-v2
# --no-ff强制生成merge commit,即使可以fast-forward
优点:
git revert <merge-commit-hash>缺点:
适用场景:feature分支开发周期<2天,单人开发,追求线性历史。
完整操作:
# 1. 先拉取最新develop
git checkout develop
git pull --rebase # 用rebase拉取,避免merge commit
# 2. 切回feature分支,rebase到develop
git checkout feature/payment-v2
git rebase develop
# 3. 解决冲突(如果有)
# 每个冲突commit都需要:git add 解决后的文件; git rebase --continue
# 4. 强制推送(必须确保无人基于此分支开发)
git push --force-with-lease # 比--force安全,会检查远程是否被更新
冲突处理脚本(解决N次冲突的痛点):
#!/bin/bash
# rebase-with-auto-resolve.sh
# 自动尝试用"ours"策略解决冲突,失败则退出
# 使用:./rebase-with-auto-resolve.sh develop
TARGET_BRANCH=$1
if [ -z "$TARGET_BRANCH" ]; then
echo "Usage: $0 "
exit 1
fi
git rebase $TARGET_BRANCH
if [ $? -eq 0 ]; then
echo "Rebase成功,无冲突"
exit 0
fi
# 自动解决冲突:保留当前分支的修改
git diff --name-only --diff-filter=U | while read file; do
git checkout --ours "$file"
git add "$file"
done
git rebase --continue
if [ $? -ne 0 ]; then
echo "自动解决失败,请手动处理"
exit 1
fi
echo "Rebase完成,冲突已自动解决(使用ours策略)"
注意:ours策略会丢弃目标分支的修改,仅适用于确信自己修改正确的情况。
适用场景:feature分支开发完成,合并到master时希望压缩commit。
# 将feature分支的所有commit压缩成一个
git checkout master
git merge --squash feature/payment-v2
git commit -m "feat: 支付模块v2(合并自feature/payment-v2)"
效果:
结合以上三种,用脚本自动判断策略:
#!/bin/bash
# auto-merge.sh - 自动选择合并策略
# 依赖:git 2.43.0+, 需要jq解析JSON
BRANCH=$1
TARGET=${2:-develop}
# 获取分支信息
COMMIT_COUNT=$(git rev-list --count $BRANCH ^$TARGET)
AUTHOR_COUNT=$(git log --format="%an" $BRANCH ^$TARGET | sort -u | wc -l)
LAST_MODIFIED=$(git log -1 --format="%ct" $BRANCH)
# 策略判断
if [ $COMMIT_COUNT -le 5 ] && [ $AUTHOR_COUNT -eq 1 ]; then
# 短期单人分支:rebase
echo "策略:rebase (commits=$COMMIT_COUNT, authors=$AUTHOR_COUNT)"
git checkout $BRANCH
git rebase $TARGET
if [ $? -eq 0 ]; then
git checkout $TARGET
git merge $BRANCH
else
echo "Rebase冲突,切换为merge"
git rebase --abort
git checkout $TARGET
git merge --no-ff $BRANCH
fi
elif [ $COMMIT_COUNT -le 20 ]; then
# 中等分支:squash merge
echo "策略:squash merge (commits=$COMMIT_COUNT)"
git checkout $TARGET
git merge --squash $BRANCH
git commit -m "feat: 合并$BRANCH ($COMMIT_COUNT commits)"
else
# 长期分支:merge
echo "策略:merge --no-ff (commits=$COMMIT_COUNT)"
git checkout $TARGET
git merge --no-ff $BRANCH
fi
配置到CI:
# .gitlab-ci.yml 片段
merge-job:
stage: merge
script:
- chmod +x auto-merge.sh
- ./auto-merge.sh feature/payment-v2 develop
only:
- merge_requests
when: manual
在支付模块重构项目中(3个月,6人团队,200+分支):
| 指标 | 使用前(纯merge) | 使用后(自动化策略) | 提升 |
|---|---|---|---|
| 平均合并耗时 | 4.2分钟 | 1.8分钟 | 57% |
| 冲突率 | 22% | 11% | 50% |
| 历史可读性评分 | 3.5 | 8.2 | 134% |
| 回滚次数 | 12次 | 2次 | 83% |
数据来源:GitLab API统计,每个merge request记录耗时和冲突次数。可读性由团队5人盲评打分。
坑1:rebase后force push覆盖他人代码
现象:同事A rebase feature分支并force push,同事B基于旧feature分支开发,push时被拒绝,但B不知道原因。
解决方案:
--force-with-lease代替--force,它会检查远程分支是否被更新#!/bin/bash
# .git/hooks/pre-push
# 防止force push到保护分支
while read local_ref local_sha remote_ref remote_sha
do
if [[ $remote_ref =~ ^refs/heads/(master|develop)$ ]]; then
echo "错误:不允许force push到保护分支 $remote_ref"
exit 1
fi
done
坑2:rebase过程中断导致分支状态混乱
现象:rebase到一半,执行git rebase --abort,但工作目录残留冲突标记。
解决方案:
git rebase --quit完全退出(Git 2.12+)git branch backup-feature feature/payment-v2坑3:squash merge后丢失commit信息
现象:squash merge后,feature分支的每个commit信息丢失,无法追溯具体改动。
解决方案:
git merge --squash feature/payment-v2
git log feature/payment-v2 --oneline --not master | sed 's/^/ /' > /tmp/commit-list.txt
git commit -e -F /tmp/commit-list.txt -m "feat: 支付模块v2"
坑4:rebase后CI/CD流水线失效
现象:rebase改变了commit hash,CI/CD无法关联到原始MR。
解决方案:
git commit --allow-empty -m "ci: retrigger"only: [merge_requests]配置,确保每次push都触发坑5:大型仓库rebase性能问题
现象:1000+commit的仓库,rebase耗时5分钟以上。
解决方案:
git rebase --onto减少commit范围git merge替代没有完美的工作流,只有适合团队的选择:
最后一句:永远不要在公共分支上rebase。如果你不确定,用merge。
专注技术分享与实战