4. 版本控制策略与分支模型
AUTOSAR 工程与普通应用代码仓库有一个显著差异:仓库里同时存在“人写的文本”和“工具生成的二进制/大体积产物”。前者包括 .arxml 配置、.epc 工程描述、.c/.h 生成代码、.m 脚本;后者包括 .a2l、.hex、.s19、.elf、标定数据 .dcm/.cdfx、BSW 库文件、以及 DaVinci/EB 工具导出的压缩包。若不做区分地全部交给 Git 直接管理,仓库会在几十次提交后膨胀到数 GB,克隆时间从分钟级恶化到小时级,合并冲突也会从“文本行冲突”变成“二进制无法合并”。因此本节的核心不是“用不用 Git”,而是按文件类型分层管理、按变更影响面选择分支、按冲突性质选择解决手段。
一、AUTOSAR 配置文件的版本管理:文本与二进制分层
先建立一条判断规则:能被人读懂、能按行 diff 的文件走普通 Git;不能被 diff、体积大或由工具再生的文件走 Git LFS 或干脆不入库。典型分层如下:
- 必须入库且走普通 Git:
.arxml(ECU 提取、SWC 描述、系统描述)、.epc、.c/.h生成代码、.m脚本、.json/.yaml配置、.ld链接脚本、.mk构建脚本。 - 入库但走 Git LFS:
.a2l、.hex、.s19、.elf、.bin、.dcm、.cdfx、.zip工具包、.lib/.a预编译库。 - 不入库,由构建再生:
build/、*.o、*.d、日志、临时导出目录。用.gitignore排除。
对 .arxml 这类 XML 文件,还要注意“工具重排”问题:同一份配置经不同版本工具保存后,元素顺序、命名空间前缀、空行可能变化,导致 diff 噪声巨大。实践中有两种缓解方式:一是统一工具版本并在提交前用固定格式化脚本规整;二是对关键 .arxml 采用“语义化提交”,即每次提交只改一个逻辑对象(如只改一个 CanIf 的 CanIfInitCfg),避免一次提交混入多个模块的自动重排。提交信息建议带模块前缀,例如 CanIf: 修正 RxPdu 到 CanIfRxPduCfg 的映射,便于回溯。
二、Git LFS 处理大文件:安装、跟踪与迁移
Git LFS 的原理是:仓库中只保存一个指向实际大文件的“指针文件”,真实内容存放在 LFS 存储(本地或远端)。使用前需确认 git lfs version 可用,然后按扩展名跟踪:
git lfs install
git lfs track "*.a2l"
git lfs track "*.hex"
git lfs track "*.s19"
git lfs track "*.elf"
git lfs track "*.dcm"
git lfs track "*.cdfx"
git add .gitattributes
git commit -m "chore: 启用 Git LFS 跟踪标定与可执行产物"
关键点有三。第一,.gitattributes 必须入库,否则其他成员克隆后不会自动走 LFS。第二,若仓库已经用普通 Git 提交过大文件,需要迁移历史,否则历史对象仍占空间:
git lfs migrate import --include="*.a2l,*.hex,*.s19,*.elf" --everything
git push --force-with-lease origin main
第三,迁移会重写历史,必须提前通知所有协作者,迁移后各自重新克隆或执行 git fetch --all && git reset --hard origin/main,否则会出现“本地历史与远端不一致”的推送失败。LFS 的配额和带宽通常有限,建议只跟踪真正必要的大文件,不要把每次构建的 .hex 都长期保留,可用标签(tag)标记发布版本,日常构建产物通过 CI 归档而非全部入库。
三、分支策略:按变更影响面选择
AUTOSAR 项目的分支不宜过细,否则 .arxml 的跨模块引用会在合并时反复冲突。推荐“主干 + 短生命周期特性分支 + 发布分支”的三层结构:
- main:受保护,只接受来自 release 或 hotfix 的合并,每次合并打 tag,例如
R1.2.0。 - develop:日常集成主线,所有 feature 分支合并到这里,CI 在此触发完整构建与
.arxml校验。 - feature/*:生命周期控制在 1~3 天,命名体现模块与对象,如
feature/CanIf-RxPdu。合并前先git rebase develop,把冲突解决在本地。 - release/*:从 develop 拉出后冻结,只接受缺陷修复,不再合入新功能。
- hotfix/*:从 main 拉出,修复后同时回合 main 与 develop,避免修复丢失。
对 .arxml 这类“多人可能同时改同一模块”的文件,建议在团队内约定“模块所有权”:同一时间只允许一个人修改某个 CanIf 或 EcuC 的配置,其他人通过接口描述或变更单协调。这比事后解决冲突成本低得多。
四、合并冲突解决:从定位到验证
当 git merge develop 报冲突时,先看冲突文件类型。文本冲突用 git status 定位,打开文件会看到 <<<<<<< HEAD、=======、>>>>>>> develop 三段标记。对 .arxml,不要机械地“两边都留”,而要判断语义:例如一边新增了 CanIfRxPduCfg,另一边修改了同一 PDU 的 CanIfRxPduCanId,正确做法是保留新增节点并采用修改后的 CanId,然后删除冲突标记。解决后执行:
git add path/to/CanIf_Cfg.arxml
git rebase --continue # 若在 rebase 中
# 或
git commit # 若在 merge 中
二进制冲突(如 .a2l、.hex)无法逐行合并,通常选择“以某一侧为准”或“重新生成”。若 .a2l 是由 .elf 通过工具生成的,正确做法是:先解决 .elf 对应的源码与配置冲突,重新构建生成 .elf,再用工具重新导出 .a2l,而不是在 Git 里手动选一侧。合并完成后必须做验证闭环:重新运行 CI 的 .arxml 校验、生成代码、编译链接,并比对最终 .hex 的校验和是否与预期一致。只有校验和一致或差异可解释,才认为冲突解决正确。
git config merge.ours.driver true 用于某些“只允许单侧更新”的生成文件,或在 .gitattributes 中为特定文件声明 merge=ours,可减少无意义的二进制冲突。但这类规则必须团队统一,不能个人私自设置。git lfs migrate 和 git push --force 都会重写历史。执行前务必确认没有未推送的本地提交,并通知所有协作者;否则他人推送会被拒绝,甚至误覆盖他人工作。把以上四点串起来,就是一条可执行的治理链:先按文件类型分层(普通 Git / LFS / 不入库),再按变更影响面选分支(feature→develop→release→main),最后按冲突性质解决(文本语义合并、二进制重新生成),并用 CI 校验和验证收口。这样既控制了仓库体积,也把合并冲突从“不可控的二进制灾难”收敛为“可定位、可验证的工程问题”。