第4章:Git进阶操作:分支管理策略、合并与变基、解决冲突实战

说实话,很多嵌入式工程师对Git的认知就停留在「git add、git commit、git push」三板斧。但真正到了HUD这种多团队协作、多版本并行的项目里,分支管理才是真正的硬功夫。我见过太多因为分支混乱导致代码覆盖、版本回退困难的惨案。

这一章,咱们就聊聊分支管理那些事儿。我会把我在HUD项目里踩过的坑、总结的经验,一股脑倒出来。

4.1 分支管理策略:选对模型,少走弯路

分支策略说白了就是一套规矩。大家按规矩来,代码就不会打架。我个人最推荐的是两种:Git Flow 和 Trunk-Based Development。但嵌入式项目有它的特殊性,我一般会做点调整。

核心原则:分支策略没有银弹。适合团队的就是最好的。

4.1.1 Git Flow 在嵌入式项目中的变体

Git Flow 有五个核心分支:master、develop、feature、release、hotfix。但在HUD这种嵌入式项目里,我建议做以下调整:

  • master:只存放发布版本。每次发布打tag,比如 v1.0.0、v1.1.0。
  • develop:日常开发的主干。所有feature分支从这里拉出,合并回来。
  • feature/xxx:每个功能一个分支。比如 feature/can-bus-driver、feature/hud-display。
  • release/x.x.x:发布前准备。只修bug,不加新功能。
  • hotfix/x.x.x:线上紧急修复。从master拉出,修完合并回master和develop。

我在项目中遇到过一个问题:嵌入式项目经常有硬件依赖,feature分支开发周期可能长达几周。这时候如果develop分支更新太快,合并时冲突会非常痛苦。我的建议是:feature分支定期(比如每周)从develop rebase一次,保持同步。

4.1.2 Trunk-Based Development 的适用场景

如果你的团队很小(3-5人),或者你们在做原型验证,Trunk-Based Development 更合适。说白了就是所有人都在一个主干上开发,通过短生命周期的分支(通常不超过一天)来管理。

嗯,这里要注意:嵌入式项目里,硬件BSP(板级支持包)的改动往往牵一发动全身。如果团队超过10人,我建议还是用Git Flow的变体更稳妥。

4.2 合并 vs 变基:选哪个?

这个问题几乎每次培训都会被问到。我的回答是:看场景。别迷信任何一种方式。

操作 优点 缺点 推荐场景
git merge 保留完整历史,容易回溯 历史图复杂,分支多了像蜘蛛网 公共分支(develop、master)
git rebase 历史线性,干净整洁 改写历史,多人协作时危险 个人feature分支

4.2.1 什么时候用 merge?

当你把feature分支合并回develop时,我建议用merge。为什么呢?因为merge会保留一个「合并提交」,你一眼就能看出这个功能是什么时候合进来的。万一出了问题,回退也方便。

# 切到develop,拉取最新代码
git checkout develop
git pull origin develop

# 合并feature分支,保留合并记录
git merge --no-ff feature/can-bus-driver
# --no-ff 强制生成合并提交,即使可以快进

我曾经有一次没加 --no-ff,结果feature分支只有一次提交,合并后历史完全看不出来这是个独立功能。后来查问题的时候,花了半天才定位到是哪次提交引入的bug。从那以后,公共分支合并我必加 --no-ff。

4.2.2 什么时候用 rebase?

你个人开发时,rebase 是你的好帮手。比如你从develop拉出feature分支,开发了两天,这时候develop已经更新了。你想想看,如果直接merge,你的分支历史里会多出一个「合并develop」的提交,看着就乱。

# 在feature分支上,把develop的最新改动rebase进来
git checkout feature/can-bus-driver
git rebase develop

# 如果遇到冲突,解决后继续
git add .
git rebase --continue

小技巧:rebase 之前,先给自己的分支打个tag或者备份。万一rebase过程中搞砸了,还能用 git reflog 找回来。

4.3 解决冲突实战:别慌,有套路

冲突是Git里最让人头疼的事。但说白了,冲突就是两个人改了同一份文件的同一行。Git不知道听谁的,只好让你来定。

我在HUD项目里遇到过最夸张的一次冲突:两个同事同时修改了can_bus.c里的初始化函数,一个改了波特率配置,一个改了过滤器设置。结果冲突标记占了200多行。嗯,当时我花了半小时才理清楚。

4.3.1 冲突标记长什么样?

<<<<<<< HEAD
// 当前分支的代码
can_init(500000, CAN_MODE_NORMAL);
=======
// 合并进来的分支的代码
can_init(250000, CAN_MODE_LOOPBACK);
>>>>>>> feature/test-mode

看到这个标记,别慌。按以下步骤来:

  1. 理解上下文:先搞清楚HEAD和feature分支各自改了啥。
  2. 沟通:如果拿不准,找改代码的人聊一下。我一般会直接喊一声:「老王,你那个波特率改500K是为什么?」
  3. 手动合并:删掉冲突标记,保留正确的代码。有时候两个都要保留,有时候只留一个。
  4. 测试:合并完一定要编译通过,最好跑一下单元测试。

4.3.2 解决冲突的实用命令

# 查看冲突文件
git status

# 查看冲突详情
git diff

# 使用图形化工具(我推荐meld或kdiff3)
git mergetool

# 标记冲突已解决
git add <file>

# 继续合并或变基
git merge --continue
# 或
git rebase --continue

警告:千万不要在解决冲突时,顺手改其他不相关的代码。我见过有人解决冲突时「顺便」重构了函数,结果引入了一个隐蔽的bug,排查了两天。解决冲突的原则是:只解决冲突,不做额外修改

4.3.3 如何减少冲突?

说实话,冲突完全避免是不可能的。但我们可以通过一些习惯来减少冲突的频率和复杂度:

  • 小步提交:每次提交只改一个功能点。别攒了一周才提交一次。
  • 频繁同步:每天至少从develop拉取一次最新代码。我习惯早上到公司第一件事就是 git pull。
  • 模块化设计:把代码拆成独立的模块,每个人负责不同的模块。比如A负责CAN驱动,B负责显示驱动,冲突自然就少了。
  • 代码所有权:每个文件指定一个负责人。改文件前先跟负责人打个招呼。

4.4 实战演练:模拟一个冲突场景

光说不练假把式。咱们模拟一个真实场景:你和同事同时修改了 main.c 里的系统初始化函数。

# 1. 创建实验仓库
mkdir hud-conflict-demo
cd hud-conflict-demo
git init

# 2. 创建初始文件
echo "void system_init() {" > main.c
echo "    can_init();" >> main.c
echo "    display_init();" >> main.c
echo "}" >> main.c
git add main.c
git commit -m "初始版本"

# 3. 同事从develop拉出feature分支
git checkout -b feature/add-sensor

# 4. 同事修改了main.c,添加传感器初始化
# 修改后内容:
# void system_init() {
#     can_init();
#     sensor_init();  // 新增
#     display_init();
# }
git add main.c
git commit -m "添加传感器初始化"

# 5. 你回到develop,也做了修改
git checkout master
# 修改后内容:
# void system_init() {
#     can_init();
#     display_init();
#     log_init();  // 新增
# }
git add main.c
git commit -m "添加日志初始化"

# 6. 尝试合并,冲突出现
git merge feature/add-sensor
# 你会看到:Auto-merging main.c
# CONFLICT (content): Merge conflict in main.c

这时候打开 main.c,你会看到冲突标记。手动解决后,保留两行新增代码:

void system_init() {
    can_init();
    sensor_init();
    display_init();
    log_init();
}

然后执行:

git add main.c
git commit -m "合并feature/add-sensor,解决冲突"

我的习惯:每次解决完冲突,我都会在提交信息里注明「解决冲突」以及冲突的原因。比如「解决冲突:sensor_init和log_init的插入顺序」。这样以后查历史时,一眼就能看出当时发生了什么。

好了,这一章的内容就这些。分支管理、合并变基、冲突解决,这三板斧练好了,你在Git上的功力就能上一个台阶。下一章咱们聊聊持续集成流水线的搭建,那可是把这一切自动化的关键。