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 仓库文件分层与分支合并路径 文件分层策略 普通 Git(可 diff) .arxml / .epc / .c / .h .m / .json / .ld / .mk 按行合并,冲突可人工解决 Git LFS(大体积) .a2l / .hex / .s19 / .elf .dcm / .cdfx / .lib / .zip 指针入库,内容存 LFS 不入库(可再生) build/ / *.o / *.d 日志 / 临时导出目录 .gitignore 排除 提交粒度建议 一次提交只改一个逻辑对象 提交信息带模块前缀 分支模型 main(受保护,可发布) develop(集成主线) feature/CanIf-RxPdu feature/EcuC-Extract release/R1.2(冻结) hotfix 从 main 拉出,回合并 main+develop 合并冲突解决 文本冲突(.arxml/.c) 定位 <<<<<<< 标记 保留双方语义,重跑生成 二进制冲突(.a2l/.hex) 无法逐行合并 选一侧或重新生成 工具重排冲突 元素顺序/命名空间变化 统一工具版本+格式化 验证闭环 合并后重跑 CI 生成 比对 .hex 校验和 冲突预防 小步提交 + 频繁 rebase

三、分支策略:按变更影响面选择

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 校验和验证收口。这样既控制了仓库体积,也把合并冲突从“不可控的二进制灾难”收敛为“可定位、可验证的工程问题”。