更改请求
会话工作如何通过评审进入默认分支。
更改请求(CR)会将一个 git 分支合并到另一个分支。Beeuni 会将会话分支(head_ref)上的更改创建为 CR,目标是项目的默认分支(base_ref,通常为 main)。CR 行是元数据;合并、差异和冲突检查会针对项目仓库作为真实的 git 操作运行。CR 是会话工作进入默认分支的唯一途径。
为什么工作要经过 CR
会话在沙箱中运行,并使用以会话 ID 命名的独立分支。沙箱不会永久存在,但分支会保留:git 是会话工作唯一持久的记录。每个新会话都从默认分支开始。在 CR 完成合并之前,工作会留在自己的分支上,未经审查,其他代理、触发器和协作者都不可见。这适用于所有更改:代码、代理、技能和清单(beeuni.yaml)——没有例外。
代理必须遵守的要求
代理必须打开 CR,才能将任何更改落到项目的默认分支上。代理不得合并自己的 CR——合并由用户决定。请遵循以下约定:
- 在会话分支(
$BEEUNI_BRANCH_NAME)上提交。使用小型、可工作的提交。不要重写历史记录或强制推送。 - 推送分支:
git push origin HEAD。 - 打开 CR:
beeuni cr open --title "..." --description "..."。在会话沙箱中,--head和--session默认为$BEEUNI_BRANCH_NAME和$BEEUNI_SESSION_ID。--base默认为项目的默认分支。 - 告知用户 CR 编号,以便用户审查。
- 停止操作。不要自行合并 CR。
反模式
- 强制推送到默认分支。 即使后端允许,这也会破坏审查约定。
- “代码在我的分支上,你自己拉取吧。” 沙箱停止后,会话分支就会消失,除非 CR 已先将其合并。
- 通过文件、粘贴内容或存档发送更改。 CR 系统已经解决了这个问题。
数据模型
CR 存储在 change_requests 表中。
| 列 | 类型 | 说明 |
|---|---|---|
cr_id |
uuid | 主键。REST API 的标识符。 |
project_id |
uuid | CR 所属的项目。 |
number |
integer | 每个项目的显示编号(#1、#2……)。在项目内唯一。不会重新使用。 |
title |
text | 必填。 |
description |
text | 默认为空字符串。 |
base_ref |
text | 被合并到的分支。通常为 main。 |
head_ref |
text | 被合并的来源分支。在会话中,这是会话 ID(UUID)。 |
status |
enum | open、merged 或 closed。 |
head_commit_sha |
text | 对于打开的 CR,每次读取时都会根据实时 head_ref 最新提交刷新。对于已合并的 CR,在合并时记录。 |
base_commit_sha |
text | base_ref 采用相同规则。 |
origin_session_id |
text | 打开 CR 的会话。如果该会话被删除,则设为 null。 |
created_by |
uuid | 创建 CR 的用户。 |
merged_at / merged_by |
timestamp / uuid | 合并 CR 的时间和用户。 |
merge_commit_sha |
text | 合并提交。快速前进时等于 head_commit_sha。 |
closed_at / closed_by |
timestamp / uuid | 不合并而关闭 CR 的时间和用户。 |
metadata |
jsonb | 包含 requested_changes,即由 POST /:crId/request-changes 添加的 {text, by, at} 条目列表。CR 没有单独的评论表。 |
created_at / updated_at |
timestamp | 创建时设置。在每次状态更改或 SHA 刷新时更新。 |
(project_id, number) 上的唯一索引允许你通过短编号而不是 UUID 引用 CR。
生命周期
open ──(merge)──▶ merged (terminal)
open ──(close)──▶ closed ──(reopen)──▶ open
open是初始状态。closed可以撤销。POST /:crId/reopen会将其设回open。merged是终止状态。无法重新打开或关闭已合并的 CR。请针对已合并状态打开新的 CR。
如果某个分支没有领先默认分支的提交,Beeuni 会拒绝创建该分支的 CR。这通常意味着代理已在本地提交,但从未推送。请推送提交,然后重新创建 CR。
SHA 刷新
对于打开的 CR,Beeuni 会在每次 GET 时,根据实时分支刷新 head_commit_sha 和 base_commit_sha。如果仓库无法访问,或某个分支不存在,Beeuni 会跳过刷新,并提供 CR 最后已知的元数据。已合并的 CR 会保留合并时记录的 SHA——Beeuni 不会再次刷新它们。
合并机制
POST /v1/projects/:projectId/change-requests/:crId/merge 会执行以下步骤。
- Beeuni 从
head_ref读取清单(beeuni.yaml),并根据清单架构进行验证。没有清单的分支可以通过验证。无效清单会返回带有code: "MANIFEST_INVALID"的422,并停止合并。 - 如果
head_ref严格领先于base_ref,Beeuni 会将 该分支 快速前进。 - 否则,Beeuni 会创建合并提交。默认消息为
Merge CR #<n>: <title>,也可以在请求正文中通过message覆盖。提交作者为Beeuni <noreply@beeuni.com>。 - 发生冲突时,会返回带有冲突列表的
409。合并前可通过GET /:crId/merge-preview检查相同列表。 - 成功后,Beeuni 会将
status设置为merged,记录merged_at、merged_by和merge_commit_sha,并使项目的 git 缓存失效。
合并状态不是 open 的 CR 会返回 409。
合并预览
GET /:crId/merge-preview 会返回:
| 字段 | 类型 | 含义 |
|---|---|---|
base_sha |
string | base_ref 的当前最新提交。 |
head_sha |
string | head_ref 的当前最新提交。 |
merge_base |
string | null | 共同祖先。如果历史记录不相关,则为 Null。 |
is_up_to_date |
boolean | head_ref 已完全合并到 base_ref。 |
can_merge |
boolean | 没有冲突。 |
can_fast_forward |
boolean | head_ref 严格领先于 base_ref。 |
conflicts |
string[] | 将发生冲突的文件路径。 |
REST API
所有路由都位于 /v1/projects/:projectId/change-requests 下。
| 方法 | 路径 | 说明 |
|---|---|---|
| GET | / |
?status=open|merged|closed|all。不提供筛选条件时返回所有状态。 |
| POST | / |
正文:{title, description?, head_ref, base_ref?, session_id?}。返回 201。 |
| GET | /:crId |
返回 CR。会作为副作用刷新 SHA。 |
| PATCH | /:crId |
编辑 title 或 description。如果不是 open,则 409。 |
| GET | /:crId/diff |
统一补丁:文件列表、添加内容和删除内容。 |
| GET | /:crId/merge-preview |
请参阅上面的“合并预览”。 |
| POST | /:crId/merge |
正文:{message?}。清单无效时返回 422。发生冲突或不是 open 时返回 409。 |
| POST | /:crId/close |
如果已经 merged,则 409。 |
| POST | /:crId/reopen |
如果不是 closed,则 409。 |
| POST | /:crId/request-changes |
正文:{feedback}。追加到 metadata.requested_changes,并唤醒发起会话的代理。如果不是 open,则 409。 |
POST / 会拒绝创建没有领先 base_ref 的提交的 head_ref:422 code: "CR_HEAD_NOT_AHEAD"。这是代理在推送分支之前打开 CR 时看到的错误。
授权
读取路由需要项目的读取权限。写入路由需要写入权限。每个写入操作还需要相应的能力,下面分别列出完整令牌和会话范围令牌所需的能力。
| 操作 | 能力 |
|---|---|
| 打开 CR | project.gitops.push |
| 请求更改 | project.review.act |
| 合并 | project.gitops.merge |
每项操作对应一项能力,无论是完整令牌还是权限范围受限的会话令牌。创建和合并是两个独立的叶子权限,因此令牌可以创建变更请求,却没有合并权限——这正是上文代理授权机制的实现方式。
project.cr.open 和 project.cr.merge 是切换前 project.gitops.push 和 project.gitops.merge 的名称——同一项能力的另一种名称。仍列出其中一个名称的 beeuni.yaml 仍然有效(解析时,授权会被改写为当前使用的名称),但新内容请使用 gitops 名称。
无论获得了什么授权,会话都不能合并自己创建的变更请求。这条规则是结构性的,并非一项能力。