1. 事件背景
时间:2026-04-28
场景:combine_pay(合单支付)分支需要 rebase 到最新的 main 分支,过程中发生了静默的代码丢失,没有触发 git 冲突警告。
受影响的功能:
- wallet 明细查询接口(2 个方法)
- 支付宝商户 PID 字段逻辑
-
QueryPendingTransactionParam结构体字段缺失
2. 问题现象
预期行为
-
git rebase main后,main 分支已有的 wallet 功能应该完整保留 - 合单支付的新功能应该正常叠加
- 有冲突应该提示人工解决
实际行为
✅ 编译最终通过(人工修复后)
❌ main 分支新增的 wallet 相关代码被静默覆盖,无冲突提示
❌ main 分支新增的支付宝 PID 字段逻辑被覆盖
❌ QueryPendingTransactionParam 缺少 Status 字段
3. 根本原因分析
3.1 Git Diff 的"上下文匹配"机制
⚠️ 核心认知误区:很多人以为 git diff 是按"行号"记录修改,实际上是按上下文匹配。
git diff 记录的修改格式:
在【GetTransactionByID】下面、【HandleRefund】上面的那一行,
要从【旧的7个参数版本】改成【新的 param 版本】
不是:
把第 26 行改成新内容
3.2 为什么没有触发冲突?
冲突只会在以下情况触发:
- ✅ 两个分支修改了同一行且内容不同
- ❌ 一个分支修改了 A 行,另一个分支在 A 行附近新增了 B 行 → 静默覆盖!
3.3 本次事件的精确触发点
// combine_pay 分支做的修改:把7个参数改成1个参数
- QueryPendingTransaction(ctx context.Context, tpwType types.TpwType, tradeType types.TradeType, startTime, endTime time.Time, isPos *bool, lastID int64, limit int)
+ QueryPendingTransaction(ctx context.Context, param *transactionparam.QueryPendingTransactionParam)
这个修改改变了整个 interface 代码块的"结束位置",导致 main 分支在 interface 末尾新增的两行 wallet 方法被 git 判定为"不属于最终代码",从而被静默删除。
4. Git Rebase 工作原理详解
4.1 Rebase = "搬家",不是"合并"
rebase 前:
main: A --- B --- C --- D (HEAD)
\
combine_pay: E --- F (你的提交)
rebase 后:
main: A --- B --- C --- D
\
combine_pay: E' --- F' (E 和 F 在新位置重新执行)
4.2 Rebase 的 3 个步骤
- 找到分叉点:git merge-base main combine_pay → B 提交
- 生成补丁:把 E、F 每个提交都转换成 patch 文件
- 依次应用:在 D 提交上,按顺序重新应用每个 patch
⚠️ 关键:每个 patch 应用时都是独立的上下文,不知道中间 main 加了什么。
4.3 Patch 应用的三种结果
| 场景 | Git 行为 | 开发者可见 |
|---|---|---|
| 修改位置完全不重叠 | 自动合并 ✅ | 无感知 |
| 同一行内容都改了 | 冲突报错 ❌ | 必须手动解决 |
| 你整体替换了代码块,main 在块内加了东西 | 静默覆盖 ⚠️ | 完全看不见! |
第三种情况就是本次事故的元凶!
5. 本次事件时间线还原
时间线(从旧到新):
3d76ac00 main 分支基准(两分支分叉点)
│
├─→ main 分支后续提交:
│ a04f046d feat: wallet查询和回调接口支持返回明细信息 ← 新增2个interface方法
│ 3579a47d feat: 支付宝商户信息新增PID字段
│ ...
│ [main 继续往前发展了 13 个提交]
│
└─→ combine_pay 分支(基于 3d76ac00 开发,与 main 脱节):
fd1983b5 fix: 合单支付
↓↓↓ 这个提交包含:
- QueryPendingTransaction 从7参数改成 param结构体
- 新增合单支付相关接口
- 【不包含】main 后来加的 wallet 方法
- 【不包含】main 后来加的 PID 字段逻辑
rebase 时发生了什么:
- git 把 fd1983b5 这个提交做成 patch
- 在 main 最新代码上应用这个 patch
- 这个 patch 说"Transaction interface 应该是这个样子"
- git 就真的按这个样子替换了 interface
- main 新增的 wallet 代码就没了 🫠
6. 本次问题的解决方案汇总
6.1 已修复的代码变更
| 文件 | 修复内容 |
|---|---|
internal/domain/transaction/svc/transaction.go |
恢复 2 个 wallet 查询方法声明和实现 |
internal/domain/transaction/repo/transaction.go |
恢复 2 个 wallet 查询接口声明 |
internal/infra/persistence/transaction/transaction.go |
恢复 2 个 wallet 查询实现 |
internal/application/assembler/tpw_config.go |
恢复支付宝 PID 逻辑,删除 PayerType 字段 |
internal/domain/transaction/params/transaction.go |
补充 Status 字段到 QueryPendingTransactionParam
|
sql/tables.sql |
同时保留 wallet SQL 和合单表 SQL |
6.2 Vendor 目录处理
- proto 文件和 wallet 相关类型由人工更新 vendor 目录解决
7. 预防措施和最佳实践建议
📌 建议 1:大功能分支优先考虑 merge,不要 rebase
适用场景:分支开发超过 2 周、涉及核心 interface 重构
# 推荐
git checkout combine_pay
git merge main
# 不推荐(风险高)
git checkout combine_pay
git rebase main
理由:merge 是真正的"两边内容做并集",静默覆盖的概率低很多。
📌 建议 2:rebase 前必须做"重叠文件检查"
rebase 前执行这条命令,看看哪些文件两边都改了:
# 列出两个分支都修改过的文件
git diff --name-only HEAD...main
如果输出包含核心文件(如 interface 定义),一定要提高警惕!
📌 建议 3:对共同修改的文件做预检查
# 看看这个文件两边具体差了什么
git diff HEAD main -- internal/domain/transaction/svc/transaction.go
如果发现 main 在你也改过的文件里加了很多东西,考虑改用 merge。
📌 建议 4:rebase 后必须编译+关键测试
不要 rebase 完就直接 push!
git rebase main
# ✅ 必须做的验证
go build ./...
go test ./... -run TestWallet # 跑一下被覆盖功能的关键测试
📌 建议 5:分支不要"离线"太久
- 每周至少 sync 一次 main 分支
- 大功能拆成小 MR 逐步合并,不要攒成一个巨型 MR
- 越晚 rebase,冲突量越大,静默覆盖概率越高
📌 建议 6:核心 interface 尽量"只追加,不修改"
如果必须改 interface 签名:
- 先加新方法,标记旧方法为 deprecated
- 等所有分支都合入后,再删除旧方法
- 不要直接修改已有方法的签名(这是 rebase 噩梦的源头)
8. 总结
本次事件的教训
- git rebase 不是安全的:静默覆盖是真实存在的风险
- 没有冲突 ≠ rebase 成功:最危险的 bug 是那些不报错的 bug
- 核心 interface 的签名修改是高危操作:很容易和其他分支产生"看不见的冲突"
记住这个反直觉的结论
改一个方法的签名,可能会导致别人新增的代码被删除。
而且 git 不会报错 🫠
附录:快速自查清单
每次 rebase 前问自己这 3 个问题:
| 问题 | 是 → 考虑用 merge | 否 → rebase 安全 |
|---|---|---|
| 我的分支开发超过 2 周了吗? | ✅ | ❌ |
| 我改了核心 interface 吗? | ✅ | ❌ |
git diff --name-only HEAD...main 输出超过 5 个文件吗? |
✅ | ❌ |
文档创建时间:2026-04-28
涉及分支:combine_pay, main