欢迎。这份文件只写这个仓库特有的规矩 —— 通用的开源礼仪就不重复了。彼此如何相处,见行为准则。
贡献者的路径:
git switch dev && git pull
git switch -c fix/some-thing # 从 dev 开新分支,不要从 main
# 改代码,提交时带 -s(见下面 DCO)
git push -u origin fix/some-thing然后开 PR,base 选 dev,不是 main。CI 通过、review 通过之后由维护者合并。
dev → main 的合并由仓库管理者择期发起,贡献者不需要管。有两条规矩防止两条分支越走越远,都是给管理者的:
- 发版之后紧接着把
main回合进dev。dev → main那个 merge 节点只存在于main,不回合的话main会显示成领先,尽管两边文件一模一样,而且每发一版就多领先一个。 - 紧急修复也走
dev。 直接对main开 PR 是唯一会让两条分支真正分叉的口子,分叉之后就得有人手工对账。 - 发版要把 compose 的固定标签跟上。
docker-compose.yml里UTOPIA_IMAGE默认指向一个固定标签,README 那条「一条命令启动」跑的就是它。发一版的完整动作是:dev → main合并、打标签发布,然后在dev上开一个 PR 把这个固定值改到新标签。rc6、rc7 发版时都漏了这一步,照 README 起来的人跑的是三周前的 rc5。rc8 起的标签是多架构的,固定值从 rc8 起就指在多架构标签上。
两个分支都开了保护:必须走 PR,CI(backend 与 web)必须通过,禁止强推与删除,管理员同样受约束。
docs/decisions/ 是这个项目主要的决策载体。里面记的不是「改了什么」,是**「为什么这样而不是那样」**,以及当时试过、失败了的做法。改动够大时,那篇文档比代码本身更值钱。
依赖:Docker、Rust 1.85+、Node 20+、pnpm。
Rust 解析器读不了的 PDF 会交给 pdftotext。源码部署还需要安装 Poppler 及中文 CMap 数据(Debian:poppler-utils poppler-data);Docker 镜像已包含两者。没装的话那个 PDF 回退测试会跳过;这时一份画了字却读不出来的 PDF 会报读取失败,而不会谎称它是扫描件。
docker compose up -d db # pgvector 版 Postgres
cargo run -p utopia-server # 自动跑迁移,:1516
cd web && pnpm install && pnpm dev # :5173,/api 代理到后端CI 就是下面这几条,本地过了 CI 基本不会红:
cargo fmt --all --check
cargo clippy --workspace --all-targets -- -D warnings
cargo test --workspace
cd web && pnpm install --frozen-lockfile && pnpm build # build 含类型检查这是这个仓库最容易误判的一点。cargo test --workspace 全绿不等于全跑了 —— 一批测试开头长这样:
let Ok(url) = std::env::var("UTOPIA_DATABASE_URL") else {
eprintln!("跳过:未设 UTOPIA_DATABASE_URL");
return Ok(());
};它们守的是编译器看不见的东西:SQL 里的表别名、NULL 参与比较时的行为、INNER JOIN 悄悄滤掉的行、递归 CTE 在菱形继承下会不会把同一个祖先展开两次。cargo check 和 clippy 对这些一个字都不说。
碰了 crates/utopia-store/ 里的 SQL,请把它设上再跑一遍。库要先迁移好——绝大多数连库测试不自己跑迁移,对着空库直接跑会成片报 relation does not exist:
# 1517 是宿主机侧端口:docker-compose.yml 显式避开 5432,以免和本地已经
# 跑着的 PG 撞上。容器内仍是 5432,app 在 compose 网络里走 db:5432;
# 这条 1517 只给宿主机上跑的代码连容器用。
export UTOPIA_DATABASE_URL=postgres://utopia:utopia@localhost:1517/utopia
cargo run -p utopia-store --example migrate # 空库先迁移;CI 的 backend job 也是这么做的
cargo test --workspace迁移编号别撞。 migrations/ 按序号前滚。开 PR 前看一眼 main 上最新的号 —— 两个分支各写一个 0011_ 已经发生过一次,合并之后谁都跑不起来。
UI 文案进 i18n。 web/src/i18n/en.ts 与 zh.ts 两边都要加,不要在组件里硬编码字符串。
代码注释写「为什么」。 这个仓库的注释密度偏高,而且刻意记录踩过的坑(「第一版写的是『或』,结果 Elon Musk 那篇每 6KB 就取一张」)。跟着这个风格走 —— 复述代码在做什么的注释会被要求删掉。
提交信息一句英文,说清动机。 不写长 body。看一眼 git log 就知道调子。
每个 workflow 自己声明 permissions:。 仓库默认现在是读写——有一个 workflow 要把生成的图提交回来。不写 permissions: 块的 workflow 会继承那个默认,于是一个只需要读的任务悄悄拿到了写权限。按这个任务实际需要的最小集写:只做构建或测试的,写 contents: read。
我们用 DCO(开发者原创声明),不用 CLA。你保留自己代码的著作权,只是声明你有权按 Apache-2.0 提交它。
用 -s 提交即可,git 会自动加上署名行:
git commit -s -m "Fix the thing"提交末尾会多出:
Signed-off-by: 你的名字 <你的邮箱>
忘了签的话,最后一个提交用 git commit --amend -s,多个提交用 git rebase --signoff HEAD~3(数字换成实际条数),然后 git push -f。
署名用的名字和邮箱要是真实可联系的。
提交即表示你的贡献按 Apache-2.0 发布。
