跳到内容
CADCAMFUN
文档

文档

项目历史

本地 Git 兼容项目快照、结构差异、可审查分支和可选的 GitHub 镜像。

从英文自动翻译。 查看英文原文

工作副本和历史记录

应用程序的 SQLite 记录是当前项目的工作副本。 @cadcamfun/vcs 通过注入的对象后端存储与 Git 兼容的 blob、树、提交和引用; sidecar 将该后端保留在 SQLite 中。历史操作可以在本地进行,无需 Git 可执行文件或 GitHub 帐户。

提交快照项目及其相关的设计/制造资源。分支指向提交,而 HEAD 标识签出分支。资源版本表与该项目级历史记录共存;绘图版本和项目提交具有不同的范围。

提交和分支工作流程

  1. 读 GET /api/projects/:id/vcs/status 查看当前分支、HEAD 和更改的路径。
  2. 创建一个分支 POST /vcs/branches,然后检查一下 POST /vcs/checkout。这里的分支路径是相对于项目的API前缀的。
  3. 编辑项目资源并提交 POST /vcs/commit 和一个非空消息。
  4. 检查 GET /vcs/log 和 GET /vcs/diff。日志支持修订、限制和可选的跟踪路径。
  5. 打开项目拉取请求,审查其结构更改,并在解决阻塞审查或冲突后进行合并。

检出、恢复和移动工作分支的操作会在需要时拒绝脏工作副本。恢复记录代表早期内容的新提交,可以选择限制为选定的路径。它保留了后来的历史,而不是重写它。

快照内容

项目存储库布局
project.json
drawings/<id>.json
components/<id>.json
toolpaths/<id>.json
materials/<id>.json
tools/<id>.json
machine-configs/<id>.json
costs/<id>.json
.cadcamfun/workflows/<name>.yml

每个实体一个 JSON 文件将不相关的更改分开。快照包括刀具路径引用的库项目和生产成本估算,因此恢复的制造数据可以保留其依赖性。用作文件名的实体标识符仅限于安全字母、数字、下划线和连字符,最多为 128 个字符。

JSON 键以递归方式排序,文件使用稳定的漂亮打印和最后的换行符。数组保留其顺序。额外的文本文件可以与包级快照一起传输;公共提交端点仅接受额外的文件 .cadcamfun/workflows/ 使用安全的 YAML 文件名。

对象使用 git 的松散对象格式逐字节,因此对象 ID 与由 git。树条目要么是文件(模式 100644)或目录(模式 40000)。提交签名记录姓名、电子邮件、Unix 秒时间和 +hhmm/-hhmm 抵消。分支存储在 refs/heads/,项目的默认分支是 main (packages/vcs/src/object.ts, repository.ts).

差异和合并语义

该存储库计算合并基础,直接处理未更改的边,并对更改的 JSON 执行结构三向合并。独立的对象属性编辑可以合并而不会发生冲突。通过 JSON 指针报告对同一标量或数组的并发不兼容编辑;数组不会逐个元素合并。

实体差异包括路径、资源种类、标识符和名称(如果可用)、添加/修改/删除状态以及之前/之后值的属性更改。非 JSON 文件使用文件级冲突行为。具有冲突的存储库合并会返回它们的路径和指针,而无需编写合并的提交。

拉取请求和权限

项目拉取请求位于 /api/projects/:id/pulls。打开一个记录标题、正文、头部、基础和作者类型。详细响应包括拉取请求、评论和更改。评论支持 comment, approve, 和 request-changes,带有可选路径和属性指针。

每个审阅者的最新非评论裁决控制请求更改块。当任何此类判决请求更改时,合并将被拒绝。当没有阻止判决时,实施不需要积极批准。合并的拉取请求差异使用记录的合并提交,因此以后的分支移动不会更改该审查记录。

读取历史记录、提交、分支、打开拉取请求和审查需要项目访问权限。工作副本移动和共享分支维护需要所有者或组织管理员/经理(由处理程序强制执行)。脏操作或冲突操作返回 409;受保护的操作返回 403;缺少引用或资源返回 404;其他无效操作返回400。

AI 参与者无法提交、恢复或合并到 main。助理使用提案分支并打开拉取请求供人员审查。审核和通知记录可识别助理发起的变更。

可选的 GitHub 镜像

GET 和 PUT /api/projects/:id/vcs/remote 读取或设置 owner/repo GitHub 镜像; null 将其分离。推拉使用 POST /vcs/push 和 /vcs/pull,用户的 GitHub 令牌位于 x-github-token 为了那个电话。

推送传输可到达的对象并更新远程分支,而不会覆盖未拉取的远程提交。 Pull 获取远程分支并将其合并到本地,拒绝脏工作副本。不同的不兼容 JSON 编辑会返回冲突,而不是默默地替换本地内容。

sidecar 传输使用 GitHub REST 对象/引用操作。 GitHub 令牌是根据请求提供的,不会存储为项目数据。本地拉取请求是应用程序审核记录;它不会自动成为 GitHub 拉取请求。

快照和恢复

snapshotToFiles 将项目快照转换为规范文件内容; filesToSnapshot 重建域列表。 ProjectVcsRepo 读取当前数据库资源并应用恢复的快照。整个项目恢复可以更改多个相关表,因此在切换历史记录之前检查其差异并清除工作副本。

传输和对象 API

VCS包导出对象编码和读取, Repository、存储适配器、规范 JSON 和合并助手、快照转换、实体差异、GitHub 同步和传输接口。测试可以注入内存中对象后端或伪造传输,而不是访问用户的存储库。

实施切入点

packages/vcs/src/repository.ts 拥有提交、引用、遍历、恢复和合并; json.ts 拥有结构合并; snapshot.ts 定义跟踪资源; diff.ts 描述实体变化; github.ts 和 transport.ts 实施镜像。边车 project-vcs.ts 添加参与者策略和持久性,同时 http/vcs.ts 提供运输授权和事件。

历史操作和自动化

工作流定义随项目进行版本控制,并针对提交的文件运行。记录历史记录后,提交可能会触发这些工作流程;失败的工作流程不会撤消提交。在诊断项目状态时,保持本地资源保存、项目提交、工作流程结果和远程同步不同。

尝试没有应用程序数据的存储库

对象存储接缝允许集成练习完全在内存中提交和分支。在依赖于的工作区中运行它 @cadcamfun/vcs, 例如 apps/sidecar。它不会触及桌面的项目数据库。

两次提交和一个提案分支
import { Repository, memoryBackend, canonicalJson } from "@cadcamfun/vcs"

const repo = new Repository(memoryBackend())
const author = { name: "Workshop", email: "workshop@example.invalid", time: 0, tz: "+0000" }
repo.commit({ "project.json": canonicalJson({ name: "Blank", revision: 1 }) }, {
  message: "Initial blank", author
})
repo.createBranch("proposal/wider-blank")
repo.checkout("proposal/wider-blank")
repo.commit({ "project.json": canonicalJson({ name: "Blank", revision: 2 }) }, {
  message: "Record revised blank", author
})
console.log(repo.currentBranch()) // proposal/wider-blank
console.log(repo.log().length) // 2

Repository.commit 接收该提交的完整文件树,而不是补丁。 sidecar 根据工作数据库记录和携带的文本文件构建这棵树。包级集成必须保留它想要保留的文件。内存中的示例不应用 sidecar 所有权或 AI 参与者策略;这些是由项目服务和 HTTP 层强制执行的。

来源: packages/vcs/src/store.ts, repository.ts, 和 apps/sidecar/src/services/project-vcs.ts.

刻意查看、恢复和同步

在切换分支之前记录工作副本更改。新分支需要可解析的源提交,因此在分支之前使用其第一次提交来初始化未提交的项目。创建分支只是创建一个引用;签出是一项单独的操作,它将其快照应用于工作数据库。

对于拉取请求,将头部与其合并基础进行比较并检查属性级别的差异。后来的评论并没有清除之前的请求更改判决:审阅者必须提交后续的批准判决。合并的 PR 可以根据存储库祖先快进或创建合并提交;检查记录的合并提交,而不是假设固定数量的父级。

合并到签出的基础时,服务需要一个干净的工作副本并应用新的基础快照。当签出另一个分支时,合并 PR 的基础会更新该分支的引用,而不会替换当前工作数据库。随后的结账会加载更新后的基础。

推和拉同步提交的历史记录。未保存的 UI 内容和未提交的数据库状态不是远程快照。远程提前拒绝是首先获取/合并的请求;它不是覆盖远程分支的指令。

来源: packages/vcs/src/repository.ts, github.ts, 和 apps/sidecar/src/services/project-vcs.ts.

从历史故障中恢复

失败下一步行动
NothingToCommit将数据库工作文件与 HEAD 进行比较。再次保存相同的资源值不会创建不同的树。
无效或现有分支选择一个新的安全参考名称。名称不能包含遍历、双斜杠、引用表达式片段或尾部斜杠/.lock 后缀。
未提交的更改在签出、拉取或受保护的工作副本操作之前提交或故意恢复/丢弃预期资源。
合并与路径/指针的冲突检查基础/头部历史记录中的这些属性,创建经过审查的解决方案,然后重试。失败的存储库合并未创建部分合并提交。
审稿人要求更改解决反馈并从该审阅者那里获取更新的无评论判决。
AI 演员无法对主要角色采取行动使用人工智能提案分支和人工审查的拉取请求。更改确认标志不会删除参与者限制。

来源: packages/vcs/src/repository.ts, apps/sidecar/src/services/project-vcs.ts, 和 http/vcs.ts。这 API参考 列出实际的请求和响应模式。

存储库布局

项目作为每个实体一个 JSON 文件存储在存储库中。因此,编辑工程图永远不会触及刀具路径文件,并且布局在 GitHub 上自然读取。 snapshotToFiles 从a写入这个布局 ProjectSnapshot, 和 filesToSnapshot 读回来。

/ project.json 图纸/<id>.json组件/<id>.json 工具路径/<id>.json材料/<id>.json 工具/<id>.json机器配置/<id>.json 成本/<id>.json .cadcamfun/工作流程/<名称>.yml 每个实体一个文件 → 差异和合并保留在更改内容的本地
快照列表目录内容
projectproject.json项目元数据。
drawings, components, toolpaths相同的名字设计、制造实体。
materials, tools, machineConfigsmaterials/, tools/, machine-configs/刀具路径引用的库项目,与项目一起进行版本控制,因此恢复的刀具路径仍然可以找到它们。
costscosts/刀具路径的生产成本估算。
extra任何路径逐字存储的文本文件:工作流程、自述文件、生成的 G 代码。

代码中的提交、分支、差异和合并

这 Repository 类适用于任何存储后端。 sidecar 用 SQLite 支持它(表 GitObject 和 GitRef); memoryBackend() 用于测试和实验。以下示例已完成,其输出显示在注释中。

history-demo.ts
import { Repository, memoryBackend, snapshotToFiles, describeDiff } from "@cadcamfun/vcs"

const repo = new Repository(memoryBackend())
const me = { name: "Ada", email: "ada@example.com", time: 1767225600, tz: "+0100" }
const files = (width: number, height: number) => snapshotToFiles({
  project: { id: "p1", name: "Bracket" },
  drawings: [{ id: "d1", name: "Plate", width, height }],
  components: [], toolpaths: [],
})

const base = repo.commit(files(40, 25), { message: "base", author: me })
repo.currentBranch()                    // "main"

repo.createBranch("a"); repo.createBranch("b")
repo.checkout("a"); const a = repo.commit(files(50, 25), { message: "a: width 50", author: me })
repo.checkout("b"); repo.commit(files(40, 30), { message: "b: height 30", author: me })

describeDiff(repo, base, a)
// [{ path: "drawings/d1.json", kind: "drawings", id: "d1", name: "Plate",
//    status: "modified", changes: [{ pointer: "/width", before: 40, after: 50 }] }]

repo.mergeInto("a", "b", me)            // { kind: "merge", oid: "46e7…" }
// different properties of the same entity merge cleanly

repo.checkout("b"); repo.commit(files(60, 30), { message: "b: width 60", author: me })
repo.mergeInto("a", "b", me)
// { kind: "conflict", conflicts: [{ path: "drawings/d1.json", pointers: ["/width"] }] }

repo.createBranch("bad..name")          // throws: Invalid branch name bad..name
repo.commit(sameFilesAsHead, { message: "x", author: me })
// throws: No changes since the last commit   (unless allowEmpty: true)

合并在 JSON 属性级别上是三向的。当双方改变一个实体的不同属性时,合并成功。当两者将相同的属性更改为不同的值时,结果是 conflict 列出每个文件内的 RFC 6901 指针。相等长度的数组按索引进行比较,因此移动的顶点显示为一个更改;不同长度的数组作为一个整体进行比较。

合并结果意义
up-to-date没有什么可以合并的;返回当前提交。
fast-forward分支指针向前移动;没有创建合并提交。
merge创建了合并提交。
conflict没有提交; conflicts 列出要解析的路径和指针。
存储库方法目的
commit(files, { message, author, allowEmpty? })在当前分支上写入完整的树作为新的提交。
log(rev = "HEAD", limit?) / fileLog(path, rev?, limit?)一个文件的分支/的历史记录。
createBranch(name, from = "HEAD"), deleteBranch, checkout分公司管理。名称不得包含 .. 或 //, 结束于 / 或 .lock,或包含 @{.
tag(name, rev = "HEAD"), tags()轻量级标签下 refs/tags/.
diff(from, to)文件级更改:使用 Blob ID 添加、修改、删除。
restorePaths(rev, paths, author, message?)新提交从旧版本恢复所选文件。
revertTo(rev, author, message?)新提交的树等于旧版本;历史不会被改写。
merge(theirs, author) / mergeInto(into, theirs, author)三路合并到当前分支/命名分支。
isAncestor, mergeBase图形查询。

应用程序执行的规则

sidecar 的项目历史服务在存储库顶部添加规则:

故障代码什么时候
blockedAI 参与者尝试提交或合并 main (消息说要使用 ai/* 分支并打开拉取请求),或者在审阅者的最新结论是合并拉取请求时 request-changes.
dirty工作副本有未提交的更改,操作将覆盖它们:“N 个文件中未提交的更改;首先提交或丢弃它们”。
conflict合并产生了冲突;失败带有冲突列表。
nothing-to-commit自上次提交以来,树没有变化。
not-found未知的分支、提交、对象或拉取请求。
remoteGitHub 同步失败。
invalid其他无效输入,例如审查或合并未打开的拉取请求。

只有每个审稿人最新的非评论裁决才有效。请求更改并随后获得批准的审阅者不再阻止合并。提交、拉取请求事件和评论都会作为实时事件发布,因此桌面和配套版本无需轮询即可更新。

与 git 字节兼容

对象 ID 的计算方式与 git 的计算方式完全相同。通过 Git Data API 推送到 GitHub 可以使双方的每个提交 ID 保持一致,并且 git clone 镜像显示与应用程序相同的历史记录。 GitHub 仅接受快进参考更新;当远程移动时,首先在本地拉取并合并。

真正的提交对象

POST /api/projects/:id/vcs/commit
{
  "oid": "44d2d03d23b27fbbc4b388e32e15dfb0f39fff87",
  "parents": [],
  "message": "First version\n",
  "author": { "name": "Local user", "email": "local@cadcamfun.local", "time": 1792037121 }
}
  • oid 是存储在中的提交对象的 40 字符十六进制 ID GitObject.
  • 第一次提交没有父母;合并提交有两个。
  • 与 Git 一样,消息存储时带有尾随换行符。
  • 没有 x-session-token,作者是安装的本地配置文件。 time 以 Unix 秒为单位。
路线身体必需的
POST …/vcs/commitmessage, authorKind, filesmessage
POST …/vcs/branchesname, fromname
POST …/vcs/checkoutbranchbranch
POST …/vcs/restorerev, pathsrev
POST …/pullstitle, body, head, base, authorKindtitle, head

在 3D

提交图 拖动进行轨道旋转·滚动进行缩放·右键拖动进行平移
The last 14 commits of origin/main from git log --format=%h|%p: each pull request branch commit (back lane) is merged into main (front lane, orange merge commits), then the docs commits. A project history in the app is the same kind of graph: commits with parents, refs pointing at them.