Skip to content

Where people and agents ship software together. The open-source git platform for the whole job: issues, agents, checks and deploys to the edge.

257 linesCodeBlameRaw
1//! Work items (`g1t_contracts::work::items`): any tracked work of a
2//! workspace, linked to the repositories it concerns
3//! (migrations/0049_work_items.sql).
4//!
5//! A work item is numbered in its workspace's sequence (`work_counters`,
6//! numbering.rs), which new pull requests share, and links repositories in
7//! `work_item_repos` (links.rs): its origin, its targets and those it
8//! touched. Who may see one is `access::item_role`: the workspace's
9//! members, and anyone who can read its origin. What `#N` names is
10//! refs.rs; what landing one does, landing.rs. Labels and milestones are
11//! the workspace's, and pull requests carry them too (labels.rs,
12//! milestones.rs). Moving repositories' issues in is migration.rs.
13//!
14//! This module is the boundary. Its RPC methods (`open_work_item` and the
15//! other `*_work_item` methods, labels, milestones, `resolve_ref`,
16//! `closed_by_commit`, `legacy_item_map`, `migrate_work_items`) are the
17//! service's; the rest of the crate (pull requests, merging, pushes,
18//! coordination) reaches work items only through the functions below, and
19//! reads them in its own queries only through the fragments in [`sql`]. A
20//! later split of work items into a service of their own turns each
21//! function into an RPC method and each fragment into a copy the caller
22//! keeps from `work_item.*` events.
23//!
24//! Work items call out the other way at a few points, which a split would
25//! turn into events: what a close means for claims and blocked work
26//! (`Work::item_resolved`), the session a close was made in
27//! (`Work::caller_session`, `Work::trailer_session`), and repositories and
28//! their settings. `repo_workspaces`, where each repository is, is kept
29//! here and read by anyone.
30
31use g1t_contracts::access::Capability;
32use g1t_contracts::repos::Repo;
33use g1t_contracts::work::refs::Ref;
34use g1t_contracts::{Outcome, User, Viewer};
35use worker::Result;
36
37use crate::Work;
38
39/// Unwraps an `Outcome`, returning its failure from the enclosing method.
40macro_rules! check {
41 ($outcome:expr) => {
42 match $outcome {
43 Outcome::Ok(value) => value,
44 Outcome::Fail(failure) => return Ok(Outcome::Fail(failure)),
45 }
46 };
47}
48
49mod changes;
50mod dependencies;
51mod labels;
52mod landing;
53mod links;
54mod migration;
55mod milestones;
56mod numbering;
57mod plan;
58mod plan_settle;
59mod reading;
60mod refs;
61mod rows;
62pub(crate) mod sql;
63#[cfg(test)]
64mod tests;
65
66pub(crate) use labels::Labeled;
67pub(crate) use links::MARK_DELETED;
68pub(crate) use landing::{Landing, PastLanding};
69pub(crate) use migration::{DELETED, RENAMED, TRANSFERRED};
70pub(crate) use reading::{held_rows, select};
71pub(crate) use refs::{MAX_CLOSES, Named, NamedItems};
72pub(crate) use rows::{Held, path_of, repo_readable, workspace_of};
73
74/// Work items and people made up for other modules' tests.
75#[cfg(test)]
76pub(crate) mod rows_for_test {
77 pub(crate) use super::rows::{held_for_test, person_for_test};
78}
79
80// --- Reading ---------------------------------------------------------------
81
82/// The work item numbered `number` in `workspace`, whoever is asking.
83pub(crate) async fn by_number(work: &Work, workspace: &str, number: u32) -> Result<Option<Held>> {
84 work.item(workspace, number).await
85}
86
87/// A work item by id, whoever is asking.
88pub(crate) async fn by_id(work: &Work, id: &str) -> Result<Option<Held>> {
89 work.item_by_id(id).await
90}
91
92/// The work items of these ids, in no particular order.
93pub(crate) async fn by_ids(work: &Work, ids: &[String]) -> Result<Vec<Held>> {
94 work.items_by_ids(ids).await
95}
96
97/// A work item, if `viewer` may do `capability` on it; not found when they
98/// may not see it.
99pub(crate) async fn for_viewer(work: &Work, viewer: &Viewer, workspace: &str, number: u32, capability: Capability) -> Result<Outcome<Held>> {
100 work.item_for(viewer, workspace, number, capability).await
101}
102
103/// A pull request's own work item, if it has one.
104pub(crate) async fn for_pull(work: &Work, pull_id: &str) -> Result<Option<Held>> {
105 match work.pull_item_id(pull_id).await? {
106 Some(id) => work.item_by_id(&id).await,
107 None => Ok(None),
108 }
109}
110
111// --- Numbers and repositories ----------------------------------------------
112
113/// The next number of `workspace`'s sequence, for a pull request of
114/// `repo_id` or (without one) a work item.
115pub(crate) async fn next_number(work: &Work, workspace: &str, repo_id: Option<&str>) -> Result<u32> {
116 work.next_number(workspace, repo_id).await
117}
118
119/// Records which workspace `repo` is in, its path and whether it is
120/// private, as repos just said.
121pub(crate) async fn remember_repo(work: &Work, repo: &Repo) -> Result<()> {
122 work.remember_repo(repo).await
123}
124
125/// The workspace repository `repo_id` is in.
126pub(crate) async fn repo_workspace(work: &Work, repo_id: &str) -> Result<Option<String>> {
127 work.repo_workspace(repo_id).await
128}
129
130/// `repo.created`: where the repository is, and for a workspace's first,
131/// the default labels.
132pub(crate) async fn repo_created(work: &Work, data: &serde_json::Value) -> Result<()> {
133 work.repo_created(data).await
134}
135
136/// `repo.restored`: where the repository is again, and what it kept for
137/// itself, placed.
138pub(crate) async fn repo_restored(work: &Work, data: &serde_json::Value) -> Result<()> {
139 work.repo_restored(data).await
140}
141
142/// `repo.visibility_changed`: whether its work items are public.
143pub(crate) async fn repo_visibility(work: &Work, data: &serde_json::Value) -> Result<()> {
144 work.repo_visibility(data).await
145}
146
147// --- References and landing ------------------------------------------------
148
149/// What `reference` names, read in repository `repo_id` of `workspace`,
150/// with the repository it is read in (another of the workspace's, for
151/// `owner/name#N`): none for another workspace's, or a repository not
152/// known.
153pub(crate) async fn resolve_in(work: &Work, repo_id: &str, workspace: &str, reference: &Ref) -> Result<Option<(String, Named)>> {
154 work.resolve_in(repo_id, workspace, reference).await
155}
156
157/// The work item `reference` names, read in `repo`, by id: one of its
158/// workspace's, or none.
159pub(crate) async fn referenced(work: &Work, repo: &Repo, reference: &Ref) -> Result<Option<String>> {
160 work.ref_item(repo, reference).await
161}
162
163/// The work items `text`, written in `repo`, closes and is part of, with
164/// `own` (a pull request's own work item) closed first: those each of
165/// `who` may close, or see (refs.rs `admits`).
166pub(crate) async fn named(work: &Work, repo: &Repo, own: Option<&str>, text: &str, who: &[&User]) -> Result<NamedItems> {
167 work.named_items(repo, own, text, who).await
168}
169
170/// Whether each of `who` may have `held` closed by naming it (`closing`),
171/// or linked as something a change is part of.
172pub(crate) fn admits(held: &Held, who: &[&User], closing: bool) -> bool {
173 refs::admits(held, who, closing)
174}
175
176/// Lands `closes` (work items, by id) in `repo` by `landing` and links
177/// `parts` (landing.rs). Returns the work items it closed.
178#[allow(clippy::too_many_arguments)]
179pub(crate) async fn land(
180 work: &Work,
181 repo: &Repo,
182 landing: &Landing,
183 actor: &User,
184 closes: &[String],
185 parts: &[String],
186 session: Option<&str>,
187 agent: bool,
188) -> Result<Vec<Held>> {
189 work.land(repo, landing, actor, closes, parts, session, agent).await
190}
191
192/// Links `repo` to `held` as touched (it has a pull request for it),
193/// unless it is linked already.
194pub(crate) async fn touch(work: &Work, held: &Held, repo: &Repo, actor: &User) -> Result<()> {
195 work.touch_item(held, repo, actor).await
196}
197
198/// Where the other work items of `held`'s workspace landed, newest first.
199pub(crate) async fn landed_before(work: &Work, held: &Held, limit: u32) -> Result<Vec<PastLanding>> {
200 work.landed_before(held, limit).await
201}
202
203// --- Its conversation ------------------------------------------------------
204
205/// Notes something that happened to a work item, in its conversation.
206pub(crate) async fn note<'a>(work: &Work, item_id: &str, author: impl Into<crate::rows::By<'a>>, text: &str) -> Result<()> {
207 work.note_item(item_id, author, text).await
208}
209
210/// A statement that marks a work item updated at `at`, for a caller's
211/// batch (a comment on it).
212pub(crate) fn stamp(work: &Work, item_id: &str, at: &str) -> Result<worker::D1PreparedStatement> {
213 work.db.prepare("UPDATE work_items SET updated_at = ? WHERE id = ?").bind(&[at.into(), item_id.into()])
214}
215
216// --- Labels and milestones -------------------------------------------------
217
218/// Sets the labels of a work item or pull request to `wanted`.
219pub(crate) async fn relabel(work: &Work, actor: &User, labeled: &Labeled, wanted: &[String]) -> Result<Outcome<Vec<String>>> {
220 work.relabel(actor, labeled, wanted).await
221}
222
223/// Puts a work item or pull request in milestone `number` (0: none).
224pub(crate) async fn set_milestone(work: &Work, actor: &User, labeled: &Labeled, number: u32) -> Result<Outcome<()>> {
225 work.set_milestone(actor, labeled, number).await
226}
227
228// --- What blocks what ------------------------------------------------------
229
230/// The open work items `held` blocks.
231pub(crate) async fn dependents(work: &Work, held: &Held) -> Result<Vec<Held>> {
232 work.dependents(held).await
233}
234
235/// Those of `numbers` whose work item in `workspace` is still open,
236/// leaving out `cleared`.
237pub(crate) async fn still_open(work: &Work, workspace: &str, numbers: &[u32], cleared: Option<u32>) -> Result<Vec<u32>> {
238 work.still_open(workspace, numbers, cleared).await
239}
240
241/// Those of `ids` (repositories) that `viewer` may read, as recorded in
242/// `repo_workspaces`.
243pub(crate) async fn readable_repo_ids(work: &Work, viewer: Option<&User>, ids: &[String]) -> Result<std::collections::HashSet<String>> {
244 work.readable_repo_ids(viewer, ids).await
245}
246
247/// The work items of `workspace` that `numbers` names and `viewer` may
248/// see, in no particular order.
249pub(crate) async fn visible_numbered(work: &Work, workspace: &str, numbers: &[u32], viewer: Option<&User>) -> Result<Vec<Held>> {
250 work.visible_numbered(workspace, numbers, viewer).await
251}
252
253/// The work items that name `held` in their `blocked_by`, those `viewer`
254/// may see.
255pub(crate) async fn blocked_items(work: &Work, held: &Held, viewer: Option<&User>) -> Result<Vec<g1t_contracts::work::ItemBrief>> {
256 work.blocked_items(held, viewer).await
257}