第4章 用Git和GitLab管理AI协同变更

第3章已经形成一项经过人工审查的修改计划。计划要求保留触发条件,只使串口日志显示剩余冷却时间。本章把计划登记为GitLab议题,并允许AI在受控范围内实施修改。完成的变化通过合并请求(Merge Request,MR)进入团队评审。

这次修改规模很小,却包含一次工程变更的完整过程:

问题和验收条件
→ 议题
→ 任务分支
→ AI实施
→ 人工检查差异
→ 构建和实机验证
→ 暂存与提交
→ 合并请求
→ 同伴评审
→ 合并

这一过程建立了后续项目的基本规则。AI可以辅助开发,变更必须由人定义、由Git记录。变更还须经过证据验证和团队评审。

早期协作常把需求、补丁和讨论分散在邮件或文件中。代码已经改变时,评审者往往难以追溯修改理由。议题与合并请求把问题、差异和证据连接起来,但也会增加记录和评审成本。本课程只保留能够支持责任追踪的必要环节。

完成本章后,应当能够:

4.1 把修改计划转成议题

4.1.1 先把第2章提交纳入个人课程仓库

第1章环境卡应提供教师为个人或小组分配的可写课程仓库。学生此前只从该仓库读取,本章先确认自己对远程项目有开发权限,但没有直接推送受保护main的权限。

第2章的手工修改此时只存在于本地lab/ch02-event-policy分支。先推送该分支:

git switch lab/ch02-event-policy
git push -u origin lab/ch02-event-policy

在GitLab创建一个简短的准备议题。议题说明“把第2章已完成构建和实机验证的冷却参数修改纳入个人课程仓库”。再从该分支向main创建合并请求。附上第2章的验证记录,经同伴确认后合并。

同步本地主分支:

git switch main
git pull --ff-only
git log -2 --oneline

此时main应包含第2章提交。第3章的lab/ch03-ai-workbench没有代码变化,不需要合并。若环境卡提供的是只读公共仓库,应按课程流程取得个人可写项目。该流程可以使用GitLab分叉或作业仓库。学生不得向只读公共项目推送。

这一步把前两章的个人练习接入团队工作流。下面建立的AI协同任务将从更新后的main开始,因此合并请求只显示本章的新变化。

4.1.2 议题记录尚未完成的工程问题

议题不是聊天记录,也不是一句“增加日志”。它至少要回答五个问题:

  1. 当前存在什么问题;
  2. 问题对开发或用户有什么影响;
  3. 本次变更包含什么、不包含什么;
  4. 什么结果可以判定任务完成;
  5. 用什么证据证明结果成立。

第3章形成的计划可以整理为下面的议题。

# 冷却期日志显示剩余时间

## 问题
设备进入通知冷却期后,串口只显示cooldown=true。
调试人员无法判断距离下一次允许通知还有多长时间。

## 范围
- 允许修改firmware/event_policy.h;
- 允许修改firmware/sound_event_terminal.ino;
- 可以补充与本变更直接相关的说明。

## 不在本次范围内
- 不修改trigger_threshold;
- 不修改cooldown_ms;
- 不改变事件触发和通知行为;
- 不修改模型、音频前端和通知客户端;
- 不引入第三方依赖。

## 验收条件
- [ ] 非冷却状态下,剩余时间为0;
- [ ] 事件触发后,日志显示大于0且逐步减小的剩余毫秒数;
- [ ] 冷却结束后,剩余时间回到0;
- [ ] 原有正例、冷却期和冷却结束三种行为保持不变;
- [ ] 固件构建通过;
- [ ] 实机验证记录已附在合并请求中。

## 完成定义
- 差异经过人工检查;
- 提交只包含本议题所需内容;
- 同伴完成评审;
- 合并请求满足课程仓库的合并条件。

验收条件要描述可观察结果。例如“日志显示剩余毫秒数”可以观察,“提高可维护性”不能直接验收。若一个议题无法在一次课程迭代内完成,应继续拆分。

4.1.3 设置负责人、标签和里程碑

在校内GitLab中新建议题,并设置:

标签用于分类,里程碑用于组织一个阶段内的多个议题。课程初期只保留少量、含义明确的标签,避免把时间消耗在维护复杂的分类体系上。

记录GitLab分配的议题编号。下文以#12为例,实际操作时应替换为自己的编号。

4.2 从主分支建立任务分支

4.2.1 先同步再分支

任务分支必须从经过确认的主分支建立。执行:

git switch main
git pull --ff-only
git status --short

git pull --ff-only只允许快进更新。如果本地主分支已经产生了额外提交,命令会停止,而不是自动形成一次未经审查的合并。

工作区干净后建立分支:

git switch -c issue-12-cooldown-remaining-log

检查分支起点:

git branch --show-current
git log -1 --oneline

分支名称应表达任务,而不是表达开发者姓名。课程仓库可以统一采用:

issue-<编号>-<简短任务>

4.2.2 为什么不直接修改main

主分支代表团队当前认可的稳定状态。任务分支提供一个隔离空间,使开发者可以修改、验证和讨论而不影响其他成员。

main:       A──B
                \
task branch:     C──D

只有当C和D通过验证与评审后,才合并回main。分支不能自动保证质量,它使变更能够先被隔离和审查。

早期的协作开发常让成员轮流修改共享目录,或直接向稳定版本提交。前一种方式难以保存完整历史,后一种方式会使未完成变化影响团队。版本控制引入分支后,不同任务可以沿各自的提交路径推进。Git以引用记录分支,建立任务分支的成本较低。

合并请求在分支之上增加了讨论、评审和自动检查。它解决的是“谁可以把什么变化纳入共同基线”,而不只是“怎样合并代码”。相应代价是评审等待和分支过期。任务应保持小而完整,评审者也应及时给出有依据的意见。

4.3 用Git识别变更所处的位置

4.3.1 四种常见状态

对一个已经被Git跟踪的文件,可以用四种状态理解它:

状态 含义 主要检查命令
未修改 与当前提交一致 git status
已修改、未暂存 工作区存在变化 git diff
已暂存 已选择进入下一次提交 git diff --staged
已提交 已形成带标识的项目快照 git show

工作区、暂存区和提交可以表示为:

编辑文件             选择本次提交内容           形成快照
工作区 ──git add──→ 暂存区 ──git commit──→ 本地仓库

暂存区不是“等待上传的文件夹”,而是下一次提交内容的精确清单。同一个文件中的部分修改也可以被选择进入暂存区。

4.3.2 建立AI实施前基线

在允许AI修改前执行:

git status --short
git diff
git diff --staged

三条命令都应无输出。再保存基线提交:

git rev-parse --short HEAD

把输出写入实验记录。例如:

分支:issue-12-cooldown-remaining-log
基线:<实际提交短标识>
议题:#12

基线使后续审查能够回答:本次变化究竟从哪个版本开始。

4.4 允许AI实施一个受限任务

4.4.1 从规划模式切换到实施模式

只有在以下条件全部满足时,才进入Cline实施模式:

将第3章通过审查的计划作为上下文,并提交下面的实施指令:

请按已经批准的计划实施议题#12。

目标:
冷却期日志显示距离下一次允许通知的剩余毫秒数。

只允许修改:
- firmware/event_policy.h
- firmware/sound_event_terminal.ino

不得修改:
- trigger_threshold
- cooldown_ms
- 事件触发和通知条件
- 模型、音频前端、通知客户端
- 依赖和构建配置

实施要求:
1. 先说明准备执行的第一步;
2. 每次只完成一个小步骤;
3. 修改后列出实际变化的文件;
4. 不自动提交,不推送,不创建合并请求;
5. 只可运行课程仓库已有的构建命令;
6. 烧录和实机操作等待人工完成。

验证要求:
- 非冷却状态剩余时间为0;
- 冷却期内剩余时间大于0并随时间减小;
- 冷却结束后回到0;
- 原有事件行为不变。

“不自动提交”十分重要。AI产生的内容要先留在工作区,由学生检查后再决定哪些内容可以进入提交。

4.4.2 逐项批准工具调用

实施过程中,将工具调用分为三类:

操作 本章处理方式 原因
读取两个允许文件、检索符号 可以批准 属于任务必要上下文
修改两个允许文件 检查具体补丁后批准 可能改变产品行为
安装依赖、访问网络、提交、推送 拒绝 超出本次任务

若AI提出修改第三个文件,先停止实施并要求它说明必要性。只有当人工重新评估议题并明确扩大范围后,才可继续。不能用“AI认为需要”替代范围变更决策。

4.4.3 AI输出不是已完成成果

AI完成文件编辑后,暂时不要继续让它修复或优化。先回到终端执行:

git status --short
git diff --stat
git diff -- firmware/event_policy.h
git diff -- firmware/sound_event_terminal.ino

检查四类问题:

  1. 是否只修改了批准文件;
  2. 是否存在与议题无关的格式化;
  3. 是否改变阈值、冷却时间或通知条件;
  4. 新增的剩余时间在时间边界上是否可能发生下溢或错误换算。

如果差异较长,可先用:

git diff --word-diff

但最终仍要阅读普通统一差异。摘要和解释可以辅助理解,不能代替查看源代码。

4.5 审查源代码差异

4.5.1 从验收条件反推代码语义

本次变化应满足下面的语义,而不限定AI必须采用某一种写法:

如果当前不在冷却期:
    remaining_ms = 0

如果当前仍在冷却期:
    remaining_ms = 冷却结束时刻 - 当前时刻

如果冷却期已经结束:
    remaining_ms = 0

审查时重点确认:

嵌入式计时器可能发生回绕。对于本项目使用的无符号时间差写法,应保持与原有策略一致;若AI改用绝对时间比较,必须说明回绕条件下是否仍然正确。计时器原理的深入分析在《感知与异构计算系统原型》中展开,本章关注接口行为和回归验证。

4.5.2 发现无关修改时收敛

如果AI同时完成了大范围重命名、注释改写和函数重排,可以要求它只撤销无关部分。也可以由学生通过差异逐项恢复。

恢复整个未暂存文件:

git restore firmware/sound_event_terminal.ino

交互式恢复部分修改:

git restore -p firmware/event_policy.h

使用交互式命令时,每一个补丁块都要先阅读。若无法判断某个补丁是否必要,应保留现场并向教师或同伴说明,而不是批量接受。

4.6 构建和实机验证

4.6.1 先构建再烧录

执行课程仓库统一命令:

python tools/course.py build

记录命令、结果和时间。构建失败时:

  1. 阅读第一条实际错误;
  2. 定位错误文件和行号;
  3. 判断它是否由本次差异引入;
  4. 把错误原文和允许范围交给AI分析;
  5. 只批准与错误直接相关的修正;
  6. 再次检查完整差异。

不要把整个终端历史无选择地交给模型,也不要让AI在没有错误证据时反复改写代码。

4.6.2 执行三组实机测试

连接课程硬件,执行:

python tools/course.py ports
python tools/course.py flash
python tools/course.py monitor

用第2章相同的声音事件完成三组测试:

编号 操作 预期日志 预期通知行为
T1 在非冷却状态产生目标声音 remaining_ms=0或进入冷却前为0 产生一次通知
T2 冷却期内重复产生目标声音 remaining_ms大于0并随时间减小 不产生重复通知
T3 等待冷却结束后再次产生目标声音 remaining_ms=0 再次产生通知

实机记录必须写实际结果,不能把预期值复制为测试结果。建议记录:

| 编号 | 固件提交/工作区 | 实际结果 | 通过 | 证据 |
|---|---|---|---|---|
| T1 | <提交或WORKTREE> | <实际观察> | 是/否 | <日志或照片路径> |

如果T2的剩余时间没有下降,先检查日志输出频率和时间采样位置;如果通知行为改变,则本次变更没有满足“只增加可观察性”的范围,应停止提交。

4.6.3 验证Git差异没有继续扩大

测试工具可能产生构建目录、串口日志或本地配置。再次执行:

git status --short

课程仓库应通过.gitignore排除可再生成的构建文件和本地秘密。测试记录、必要的配置样例与验证说明,应按项目规则进入版本控制。

4.7 组织原子提交

4.7.1 原子提交的判断标准

原子提交表示一个提交只完成一个可以说明、可以验证、可以撤销的逻辑变化。它不等于“只改一个文件”,也不等于“提交越小越好”。

本次功能可能同时需要修改策略接口和主程序日志。两个文件共同完成一个可验证目标,可以进入同一个提交。与本议题无关的格式化、课程笔记和个人配置不能混入。

4.7.2 交互式暂存

先查看全部变化:

git diff

再逐块选择:

git add -p

常用选择包括:

完成后分别检查:

git diff
git diff --staged

git diff显示尚未进入提交的工作区变化;git diff --staged显示下一次提交的准确内容。提交前的主要审查对象是后者。

4.7.3 编写能够解释意图的提交信息

提交信息可写为:

git commit -m "feat(firmware): report remaining cooldown time"

课程建议采用:

<类型>(<范围>): <完成的变化>

常用类型:

类型 用途
feat 新增用户或开发者可观察能力
fix 修复缺陷
docs 修改文档
test 增加或修正测试
refactor 不改变外部行为的结构调整
chore 构建、配置等维护任务

提交后执行:

git show --stat
git show
git status --short

确认提交内容与议题一致,工作区没有遗漏。

4.8 推送分支并创建合并请求

4.8.1 第一次推送任务分支

git push -u origin issue-12-cooldown-remaining-log

-u建立本地分支与远程分支的跟踪关系,后续可以直接使用git push和git pull。

推送不是发布,也不是合并。它只是让远程GitLab保存任务分支,使团队能够共同审查。

4.8.2 合并请求说明

在GitLab中从任务分支向main创建合并请求。标题应表达实际变化,例如:

显示通知冷却期剩余时间

说明可采用下面的结构:

## 关联任务
Closes #12

## 变化
- 暴露冷却期剩余毫秒数;
- 在策略日志中输出remaining_ms;
- 保持阈值、冷却时长和通知条件不变。

## 验证
- [ ] `python tools/course.py build`
- [ ] T1:非冷却状态
- [ ] T2:冷却期内
- [ ] T3:冷却结束后

## 实际结果
<填写实际命令、板卡/固件信息、日志摘要和证据路径>

## 风险
- 时间边界与计时器回绕;
- 日志输出频率增加;
- 策略接口变化影响调用方。

## AI协同说明
- AI参与:按批准计划修改两个文件;
- 人工完成:范围定义、差异审查、构建、烧录、实机验证和提交;
- 拒绝或修正的AI建议:<如实填写>。

合并请求说明不需要粘贴完整AI对话。它应使评审者不依赖对话也能理解变更。

4.9 开展同伴评审

4.9.1 先核对任务,再阅读差异

评审按以下顺序进行:

  1. 阅读议题和验收条件;
  2. 确认目标分支为main;
  3. 查看提交数量和提交信息;
  4. 阅读每一个文件差异;
  5. 检查构建与实机证据;
  6. 对照验收条件逐项判断;
  7. 给出批准、评论或请求修改。

只看最终代码容易忽略范围,只看AI摘要容易忽略真实变化。议题说明“为什么改”,差异说明“实际改了什么”,验证记录说明“变化是否成立”。

4.9.2 写出可以执行的评审意见

有效评审意见包含位置、问题、影响和建议。例如:

event_policy.h中的剩余时间计算使用了绝对结束时刻比较。
设备计时器回绕时可能错误地保持冷却状态。
请沿用原策略的无符号时间差方法,并补充冷却结束边界测试。

下面的意见信息不足:

这里不太好,请优化。

评审者不应直接扩大议题。例如“顺便重构整个策略类”不是本次合并的必要条件,应登记为新的议题。

4.9.3 修改后重新验证

作者收到请求修改后:

  1. 确认意见对应哪个验收条件或工程风险;
  2. 在同一任务分支完成小范围修正;
  3. 重新查看差异;
  4. 重复受影响的构建与测试;
  5. 提交并推送;
  6. 在讨论中说明修正和验证结果。

评审讨论解决后,才进入合并。GitLab界面位置会随版本和校内部署配置变化,实际操作以课程环境为准。

4.10 合并、同步和清理

合并请求满足下列条件后可由有权限的成员合并:

合并后在本地执行:

git switch main
git pull --ff-only
git log --oneline --decorate -5
git branch -d issue-12-cooldown-remaining-log

-d只删除已经合并的本地分支;若Git判断分支未合并,它会拒绝删除。远程任务分支可由GitLab在合并时清理。

再运行一次构建和关键实机测试,确认主分支上的结果与合并请求一致。议题因Closes #12而关闭后,检查其中的验收条件和证据仍可访问。

4.11 冲突、恢复与回滚

4.11.1 冲突是两项意图无法自动合并

当两条分支修改同一位置时,Git可能要求人工解决冲突:

<<<<<<< HEAD
当前分支内容
=======
另一分支内容
>>>>>>> other-branch

解决冲突不能简单选择“保留双方”。两个版本即使都能编译,组合后也可能违反延迟、内存或接口约束。正确步骤是:

  1. 阅读两个分支关联的议题;
  2. 确认冲突位置承担的产品或技术约束;
  3. 形成同时满足当前需求的最终内容;
  4. 删除冲突标记;
  5. 重新构建和执行受影响测试;
  6. 暂存并提交冲突解决结果。

AI可以解释冲突两侧的差异并提出候选方案,但最终选择必须由理解两个任务目标的人完成。

4.11.2 不同阶段使用不同恢复方法

情形 常用方法 结果
放弃未暂存修改 git restore <文件> 文件恢复到暂存区或当前提交状态
取消暂存但保留工作区修改 git restore --staged <文件> 修改回到工作区
撤销一个已经共享的普通提交 git revert <提交> 新增一个反向提交,保留历史
暂时切走未完成工作 git stash push 临时保存工作区和暂存区变化

不要用改写公共历史的方式处理已经推送并被团队使用的提交。回滚前先识别准确提交、影响范围和恢复后的验证项目。

4.11.3 主分支保护

课程GitLab应由教师或项目维护者保护main:

保护分支不能替代评审,它把“先评审后进入主分支”变为仓库可执行的规则。

4.12 本章工程检查

在本章结束时,用下面的清单检查第一次协作闭环:

[ ] 议题描述了问题、范围、验收条件和完成定义
[ ] 任务分支从最新main建立
[ ] AI只修改批准范围
[ ] 人工阅读了完整差异
[ ] 构建结果来自实际命令
[ ] 三组实机测试有实际记录
[ ] 提交内容单一且可解释
[ ] 合并请求关联议题并说明AI参与边界
[ ] 同伴完成评审
[ ] 变更已经合并,主分支重新验证

4.13 本章小结

本章完成了第一次由AI参与、由Git约束的工程变更。议题定义目标与验收条件,任务分支用于隔离变化。Git差异呈现AI的实际修改,构建与实机测试提供验证证据。合并请求和同伴评审决定成果能否进入主分支。

这条工作流将在后续章节反复使用。项目规模扩大后,任务内容和自动化程度会发生变化。基本结构仍是“先定义、再变更、后验证、经评审”。

第5章将转入贯穿本书的智能感知终端。学生先确定用户问题、应用边界和最小可行产品,再建立代码骨架。产品定义将保存到新的项目仓库。

4.14 综合实践

  1. 从本组项目选择一项小而完整的变化,建立议题。议题应包含问题、范围、排除项、验收条件和完成定义。
  2. 在一个混合了功能修改、格式变化和个人配置的练习分支中,设计原子提交方案。说明每项内容进入、移出或延后处理的依据。
  3. 评审另一组的合并请求。至少提出一条指向具体差异、说明影响并给出验证方法的意见,再记录作者的处理结果。
  4. 设计一次同一位置的分支冲突。依据两项议题形成最终语义,保存冲突解决、重新验证和提交记录。
  5. 为一个已经合并的可逆变化编写回滚方案。比较恢复工作区、反向提交和重新发布的适用条件。

4.15 拓展阅读