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.

Commit

Unlinking the last target left to land closes the work item

A work item closes when it has landed in every target it has, but that was only checked as something landed: unlinking the one target it still waited for, or making it touched, left it open for good. Now that closes it, says so, and tells what it blocks, when it has landed somewhere.

G1T-Session:
ses_01m56vxcstesp8b57jt5edge6y
syntaqxcommitted Parent9a74d7eBrowse files
2 files+51−00/2 viewed
+31−0
2525 pub(crate) const PART_SQL: &str = "INSERT INTO work_item_repos (item_id, repo_id, role, added_at, added_by)
2626 VALUES (?1, ?2, 'touched', ?3, ?4) ON CONFLICT (item_id, repo_id) DO NOTHING RETURNING item_id AS value";
2727
28+/// Closes work item `?1` at `?2` once what landed for it is all it waits
29+/// for: open, landed somewhere, and no target left to land in (one was
30+/// just unlinked or made something else). One that never landed stays
31+/// open: it has not been done anywhere.
32+pub(crate) const CLOSE_IF_LANDED: &str = "UPDATE work_items SET state = 'closed', reason = 'completed', closed_at = ?2, updated_at = ?2
33+ WHERE id = ?1 AND state = 'open' AND resolutions <> '[]'
34+ AND NOT EXISTS (SELECT 1 FROM work_item_repos WHERE item_id = ?1 AND role = 'target' AND landed_at IS NULL)
35+ RETURNING id AS value";
36+
2837 impl Work {
2938 /// Records which workspace `repo` is in, its path and whether it is
3039 /// private, as repos just said.
154163 .bind(&[now.as_str().into(), held.item.id.as_str().into()])?
155164 .run()
156165 .await?;
166+ // A target no longer one: what landed may be all it waited for.
167+ let closed = if role == LinkRole::Target && (unlink || a.role != LinkRole::Target) {
168+ self.db
169+ .prepare(CLOSE_IF_LANDED)
170+ .bind(&[held.item.id.as_str().into(), now.as_str().into()])?
171+ .first::<ValueRow>(None)
172+ .await?
173+ .is_some()
174+ } else {
175+ false
176+ };
157177 let Some(after) = self.item_by_id(&held.item.id).await? else {
158178 return Ok(no_item());
159179 };
164184 }
165185 self.publish_item(kind, &after, Some(&a.actor), WorkItemEvent { repo: Some(path), role: Some(role), ..event }).await?;
166186 self.note_item(&after.item.id, &a.actor, &text).await?;
187+ if closed {
188+ self.publish_item(
189+ "work_item.closed",
190+ &after,
191+ Some(&a.actor),
192+ WorkItemEvent { reason: Some(CloseReason::Completed.as_str()), missing: Some(Vec::new()), ..Self::item_event(&after) },
193+ )
194+ .await?;
195+ self.note_item(&after.item.id, &a.actor, "closed this: it has landed in every target it has").await?;
196+ self.item_resolved(&after, &a.actor, true, None).await;
197+ }
167198 Ok(Outcome::Ok(after.shown(Some(&a.actor))))
168199 }
169200 }
+20−0
444444 }
445445
446446 #[test]
447+fn unlinking_the_last_target_left_to_land_closes_what_landed_everywhere_else() {
448+ let db = database();
449+ two_targets(&db);
450+ let close = |item: &str| -> bool {
451+ db.query_row(super::links::CLOSE_IF_LANDED, params![item, "2026-10-18"], |row| row.get::<_, String>(0)).optional().unwrap().is_some()
452+ };
453+ // Nothing landed yet: unlinking targets does not close it.
454+ db.execute("DELETE FROM work_item_repos WHERE item_id = 'wki_2' AND role = 'target'", []).unwrap();
455+ assert!(!close("wki_2"));
456+ // Landed in web2, still to land in api.
457+ assert!(!land(&db, "wki_1", "rep_web2", Some(3), None));
458+ assert!(!close("wki_1"), "api is still to land");
459+ // api is unlinked (or made touched): every target left has landed.
460+ db.execute("UPDATE work_item_repos SET role = 'touched' WHERE item_id = 'wki_1' AND repo_id = 'rep_api'", []).unwrap();
461+ assert!(close("wki_1"));
462+ assert_eq!(strings(&db, "SELECT state || ' ' || reason FROM work_items WHERE id = 'wki_1'"), ["closed completed"]);
463+ assert!(!close("wki_1"), "once");
464+}
465+
466+#[test]
447467 fn with_no_targets_what_closes_it_closes_it_and_another_repository_is_touched() {
448468 let db = database();
449469 two_targets(&db);

Comments

Sign in to comment.