第4章 用Git和GitLab管理AI协同变更
第3章已经形成一项经过人工审查的修改计划。计划要求保留触发条件,只使串口日志显示剩余冷却时间。本章把计划登记为GitLab议题,并允许AI在受控范围内实施修改。完成的变化通过合并请求(Merge Request,MR)进入团队评审。
这次修改规模很小,却包含一次工程变更的完整过程:
问题和验收条件
→ 议题
→ 任务分支
→ AI实施
→ 人工检查差异
→ 构建和实机验证
→ 暂存与提交
→ 合并请求
→ 同伴评审
→ 合并
这一过程建立了后续项目的基本规则。AI可以辅助开发,变更必须由人定义、由Git记录。变更还须经过证据验证和团队评审。
早期协作常把需求、补丁和讨论分散在邮件或文件中。代码已经改变时,评审者往往难以追溯修改理由。议题与合并请求把问题、差异和证据连接起来,但也会增加记录和评审成本。本课程只保留能够支持责任追踪的必要环节。
完成本章后,应当能够:
- 说明工作区、暂存区和提交之间的关系;
- 把修改计划写成边界清楚、可以验收的议题;
- 从议题建立任务分支,并保持主分支稳定;
- 在
Cline实施模式中限制AI的文件范围和命令权限; - 阅读AI产生的源代码差异,识别越界修改和无关修改;
- 使用交互式暂存组织原子提交;
- 编写包含验证证据的GitLab合并请求;
- 对同伴变更提出可以执行的评审意见;
- 区分恢复工作区、撤销提交、解决冲突和回滚已合并变更。
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 议题记录尚未完成的工程问题
议题不是聊天记录,也不是一句“增加日志”。它至少要回答五个问题:
- 当前存在什么问题;
- 问题对开发或用户有什么影响;
- 本次变更包含什么、不包含什么;
- 什么结果可以判定任务完成;
- 用什么证据证明结果成立。
第3章形成的计划可以整理为下面的议题。
# 冷却期日志显示剩余时间
## 问题
设备进入通知冷却期后,串口只显示cooldown=true。
调试人员无法判断距离下一次允许通知还有多长时间。
## 范围
- 允许修改firmware/event_policy.h;
- 允许修改firmware/sound_event_terminal.ino;
- 可以补充与本变更直接相关的说明。
## 不在本次范围内
- 不修改trigger_threshold;
- 不修改cooldown_ms;
- 不改变事件触发和通知行为;
- 不修改模型、音频前端和通知客户端;
- 不引入第三方依赖。
## 验收条件
- [ ] 非冷却状态下,剩余时间为0;
- [ ] 事件触发后,日志显示大于0且逐步减小的剩余毫秒数;
- [ ] 冷却结束后,剩余时间回到0;
- [ ] 原有正例、冷却期和冷却结束三种行为保持不变;
- [ ] 固件构建通过;
- [ ] 实机验证记录已附在合并请求中。
## 完成定义
- 差异经过人工检查;
- 提交只包含本议题所需内容;
- 同伴完成评审;
- 合并请求满足课程仓库的合并条件。
验收条件要描述可观察结果。例如“日志显示剩余毫秒数”可以观察,“提高可维护性”不能直接验收。若一个议题无法在一次课程迭代内完成,应继续拆分。
4.1.3 设置负责人、标签和里程碑
在校内GitLab中新建议题,并设置:
- 负责人:实际实施本任务的学生;
- 标签:
type::improvement、area::firmware; - 里程碑:本课程的第一个协作练习里程碑;
- 截止时间:由教师根据课堂安排设置。
标签用于分类,里程碑用于组织一个阶段内的多个议题。课程初期只保留少量、含义明确的标签,避免把时间消耗在维护复杂的分类体系上。
记录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
检查四类问题:
- 是否只修改了批准文件;
- 是否存在与议题无关的格式化;
- 是否改变阈值、冷却时间或通知条件;
- 新增的剩余时间在时间边界上是否可能发生下溢或错误换算。
如果差异较长,可先用:
git diff --word-diff
但最终仍要阅读普通统一差异。摘要和解释可以辅助理解,不能代替查看源代码。
4.5 审查源代码差异
4.5.1 从验收条件反推代码语义
本次变化应满足下面的语义,而不限定AI必须采用某一种写法:
如果当前不在冷却期:
remaining_ms = 0
如果当前仍在冷却期:
remaining_ms = 冷却结束时刻 - 当前时刻
如果冷却期已经结束:
remaining_ms = 0
审查时重点确认:
- 使用的时间单位是否与
cooldown_ms一致; - 计算是否复用项目已有的时间来源;
- 冷却结束的边界条件是否明确;
- 日志读取是否改变事件状态;
- 只为显示信息而读取状态,不应触发通知;
- 变量命名是否表达单位,例如
remaining_ms。
嵌入式计时器可能发生回绕。对于本项目使用的无符号时间差写法,应保持与原有策略一致;若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
记录命令、结果和时间。构建失败时:
- 阅读第一条实际错误;
- 定位错误文件和行号;
- 判断它是否由本次差异引入;
- 把错误原文和允许范围交给AI分析;
- 只批准与错误直接相关的修正;
- 再次检查完整差异。
不要把整个终端历史无选择地交给模型,也不要让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
常用选择包括:
y:暂存当前补丁块;n:不暂存当前补丁块;s:尝试拆分补丁块;q:退出。
完成后分别检查:
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 先核对任务,再阅读差异
评审按以下顺序进行:
- 阅读议题和验收条件;
- 确认目标分支为
main; - 查看提交数量和提交信息;
- 阅读每一个文件差异;
- 检查构建与实机证据;
- 对照验收条件逐项判断;
- 给出批准、评论或请求修改。
只看最终代码容易忽略范围,只看AI摘要容易忽略真实变化。议题说明“为什么改”,差异说明“实际改了什么”,验证记录说明“变化是否成立”。
4.9.2 写出可以执行的评审意见
有效评审意见包含位置、问题、影响和建议。例如:
event_policy.h中的剩余时间计算使用了绝对结束时刻比较。
设备计时器回绕时可能错误地保持冷却状态。
请沿用原策略的无符号时间差方法,并补充冷却结束边界测试。
下面的意见信息不足:
这里不太好,请优化。
评审者不应直接扩大议题。例如“顺便重构整个策略类”不是本次合并的必要条件,应登记为新的议题。
4.9.3 修改后重新验证
作者收到请求修改后:
- 确认意见对应哪个验收条件或工程风险;
- 在同一任务分支完成小范围修正;
- 重新查看差异;
- 重复受影响的构建与测试;
- 提交并推送;
- 在讨论中说明修正和验证结果。
评审讨论解决后,才进入合并。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
解决冲突不能简单选择“保留双方”。两个版本即使都能编译,组合后也可能违反延迟、内存或接口约束。正确步骤是:
- 阅读两个分支关联的议题;
- 确认冲突位置承担的产品或技术约束;
- 形成同时满足当前需求的最终内容;
- 删除冲突标记;
- 重新构建和执行受影响测试;
- 暂存并提交冲突解决结果。
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 综合实践
- 从本组项目选择一项小而完整的变化,建立议题。议题应包含问题、范围、排除项、验收条件和完成定义。
- 在一个混合了功能修改、格式变化和个人配置的练习分支中,设计原子提交方案。说明每项内容进入、移出或延后处理的依据。
- 评审另一组的合并请求。至少提出一条指向具体差异、说明影响并给出验证方法的意见,再记录作者的处理结果。
- 设计一次同一位置的分支冲突。依据两项议题形成最终语义,保存冲突解决、重新验证和提交记录。
- 为一个已经合并的可逆变化编写回滚方案。比较恢复工作区、反向提交和重新发布的适用条件。
4.15 拓展阅读
- Git项目:《Pro Git》“记录每次更新到仓库”和“分支”章节;
- GitLab官方文档:议题、合并请求评审与受保护分支。