3. 版本控制与模型差异管理

Simulink 模型是二进制格式的 .slx 文件(本质是 ZIP 压缩包),Git 无法像对待纯文本那样逐行 diff。如果直接把 .slx 交给 Git,得到的只是“二进制文件已更改”这一句无信息量的提示,评审者看不到任何实质差异。因此,Simulink 的版本控制必须解决一个核心问题:把不可读的二进制差异,转换成可读、可评审、可合并的模型级差异。本节围绕这条主线,讲清 Git 集成方式、模型比较机制、合并与冲突解决策略,以及版本标记与发布基线的落地方法。

一、Git 集成:让 Git 认识 Simulink 模型

Git 集成分两个层面。第一层是仓库结构约定:模型文件、生成的代码、缓存产物必须分开管理。典型做法是在仓库根目录放置 .gitignore,排除 slprj/、*.slxc、*.autosave、*.slx.r20xx* 等派生文件,只提交 .slx、.m、.mat(仅限必要的参数数据)、.sldd 数据字典等源文件。缓存文件一旦入库,每次仿真都会产生噪声提交,污染历史。

第二层是配置 Git 使用 Simulink 的差异驱动。在 .gitattributes 中声明:

*.slx diff=slx
*.mdl diff=mdl
*.sldd diff=slx

再在本地或全局 Git 配置中注册驱动,把 diff=slx 映射到 MATLAB 提供的模型比较命令(如 visdiff 或 slxmlcomp 的封装脚本)。这样执行 git diff 时,Git 会调用 Simulink 比较工具,而不是输出二进制乱码。需要注意:命令行比较依赖 MATLAB 安装与许可证,CI 服务器上必须同样配置,否则流水线中的差异检查会失败。

二、Simulink 模型比较:从“文件差异”到“模型差异”

Simulink 比较工具(Model Comparison)不是比较字节,而是把两个 .slx 分别解析成模型对象树,再按对象类型和路径逐层比对。它识别的差异粒度包括:

  • 模块级:新增、删除、移动、重命名模块,以及模块类型替换。
  • 参数级:Gain 的增益值、Saturation 的上下限、Solver 的步长与求解器类型等。
  • 信号与连线级:信号线增删、信号名变更、总线元素顺序变化。
  • 接口级:Inport/Outport 数量与端口号、子系统接口、引用模型的端口映射。
  • 配置集级:Configuration Parameters 中求解器、代码生成、诊断项的差异。

比较结果通常以三栏或双栏视图呈现:左为基准版本,右为对比版本,中间列出差异条目,并按“新增/删除/修改”着色。评审时应重点关注接口差异和参数差异,因为这两类最容易在合并时被静默覆盖,导致模型能打开但行为已变。

Simulink 模型差异比较与合并决策链 基准版本 main Controller.slx @ v1.4.0 Gain Kp = 1.20 Inport: 3 个 Solver: ode45, 1e-3 特性分支 feature Controller.slx @ v1.5.0 Gain Kp = 1.35 Inport: 4 个(新增 Reset) Solver: ode45, 1e-3 Simulink 模型比较引擎 解析 .slx → 对象树 按路径匹配模块 / 参数 / 连线 输出差异条目与类型 差异条目(示例) 参数修改:Kp 1.20→1.35 需评审,可能影响稳定性 接口新增:Reset 端口 需同步更新引用模型 求解器一致:无差异 可自动通过 合并决策 参数差异 → 人工确认后取分支值 接口差异 → 合并后重跑接口一致性检查

三、合并策略:先小步、后合并,接口优先

Simulink 模型合并比文本合并困难得多,因为模型是图结构,同一处修改可能以不同拓扑表达。实践中推荐三条策略。

策略一:短生命周期分支 + 频繁同步。 特性分支存活时间控制在数天内,每天从 main 拉取一次并做一次模型比较,把差异控制在可人工审阅的规模。分支存活越久,模型结构漂移越大,合并成本呈非线性上升。

策略二:按子系统划分修改边界。 团队约定不同成员尽量修改不同子系统,避免多人同时改动同一层级的同一模块。若必须并行修改同一子系统,应提前拆分接口,把公共部分冻结为引用模型或库模块。

策略三:接口先行冻结。 在合并前先比较 Inport/Outport、总线定义、引用模型端口映射。接口一致是合并的前提;接口不一致时,先解决接口,再合并内部逻辑。合并完成后必须重新打开模型,检查连线是否断裂、端口号是否错位。

四、冲突解决:识别、取舍、验证

Simulink 的冲突通常分三类,处理方式不同:

冲突类型典型表现处理方式
参数冲突同一 Gain 在两分支被改为不同值由参数负责人裁决,保留一个值并记录理由
结构冲突同一位置一方新增模块、一方删除连线以接口契约为准,必要时重建该子系统
配置集冲突Solver 或代码生成选项不一致以发布基线配置为准,禁止分支私改

解决冲突时不要依赖 Git 的“自动合并成功”提示。正确做法是:以 main 为基准,把分支模型与基准做一次完整比较,逐条确认差异条目;对每条差异明确“保留、丢弃、改写”三种处置之一,并在提交信息中写明依据。合并后必须执行一次模型更新(Ctrl+D)和一次回归仿真,确认无未连接端口、无代数环、无采样时间冲突。

不要用“复制整个 .slx 覆盖”的方式解决冲突。这会丢失另一分支的全部修改,且无法从提交历史中还原被覆盖的模型结构。

五、模型版本标记与发布基线

版本标记解决“这个模型对应哪个发布版本”的问题。推荐使用语义化版本 + Git 标签 + 模型内版本常量三重标记:

  • Git 标签:在 main 上打 v1.5.0 这类附注标签,标签信息中写明本次发布包含的模型清单与变更摘要。
  • 模型内版本常量:在模型工作区或数据字典中定义 MODEL_VERSION = '1.5.0',并在模型注释或版本模块中引用,使模型自身携带版本信息。
  • 发布基线文件:维护一份 release_baseline.m 或 baseline.json,记录本次发布锁定的模型版本、数据字典版本、配置集名称与求解器设置。

发布基线的关键作用是可复现:任何人检出该标签后,应能用基线文件还原出与发布时一致的模型配置。基线中至少应包含:模型文件列表及其 Git 提交哈希、数据字典版本、Configuration Set 名称、依赖的库模块版本、以及本次发布对应的回归测试用例集标识。当回归测试全部通过后,才允许打标签并生成基线;标签一旦推送,不再移动。

建议在 CI 中加入一条检查:若 main 上的模型发生变更但未更新版本常量或基线文件,则流水线告警。这能防止“模型改了、版本没动”的隐性漂移。