Skip to content
326 linesCodeBlameRaw

Pick any line to see why it is the way it is: the commit, the pull request and issue it came from, and what the agent was thinking.

The statement is a month at a time, a line per kind of charge1//! `github.event`: the webhook-shaped payload GitHub gives a workflow,
2//! built from g1t's own records so `github.event.pull_request.number`,
3//! `github.event.issue.labels.*.name` and the like read as they do there.
4
g1t is the stored author of what it opens; the person who asked is requested_by and keeps the author's rights5use g1t_contracts::{PrincipalKind, User};
The statement is a month at a time, a line per kind of charge6use g1t_contracts::repos::{Commit, Repo};
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar7use g1t_contracts::work::{Comment, Issue, MilestoneRef, Pull, PullStatus, State};
The statement is a month at a time, a line per kind of charge8use serde_json::{Value, json};
9
10use crate::{API, SITE};
11
12pub fn repository(repo: &Repo) -> Value {
13 let full_name = format!("{}/{}", repo.namespace, repo.name);
14 json!({
15 "id": repo.id,
16 "node_id": repo.id,
17 "name": repo.name,
18 "full_name": full_name,
19 "private": repo.is_private,
20 "owner": { "login": repo.namespace, "type": "Organization" },
21 "html_url": format!("{SITE}/{full_name}"),
22 "url": format!("{API}/repos/{full_name}"),
23 "clone_url": format!("{SITE}/{full_name}.git"),
24 "description": repo.description,
25 "default_branch": repo.default_branch,
26 "fork": false,
27 "visibility": if repo.is_private { "private" } else { "public" },
28 })
29}
30
31pub fn user(login: &str) -> Value {
32 json!({ "login": login, "type": "User", "html_url": format!("{SITE}/{login}") })
33}
34
g1t is the stored author of what it opens; the person who asked is requested_by and keeps the author's rights35/// Someone as GitHub names them. g1t, the author of what it makes and
36/// files, is a `Bot`, as an app is there.
The statement is a month at a time, a line per kind of charge37fn person(user: &User) -> Value {
g1t is the stored author of what it opens; the person who asked is requested_by and keeps the author's rights38 let mut named = self::user(&user.username);
39 if matches!(user.kind, PrincipalKind::Agent | PrincipalKind::System) {
40 named["type"] = json!("Bot");
41 }
42 named
The statement is a month at a time, a line per kind of charge43}
44
45fn labels(names: &[String]) -> Value {
46 Value::Array(names.iter().map(|name| json!({ "name": name })).collect())
47}
48
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar49/// A milestone as an issue or a pull request names it.
50fn milestone(milestone: Option<&MilestoneRef>) -> Value {
51 milestone.map_or(Value::Null, |milestone| json!({ "number": milestone.number, "title": milestone.title }))
52}
53
The statement is a month at a time, a line per kind of charge54pub fn commit(repo: &Repo, commit: &Commit) -> Value {
55 json!({
56 "id": commit.hash,
57 "tree_id": commit.tree_hash,
58 "message": commit.message,
59 "timestamp": commit.authored_at,
60 "url": format!("{SITE}/{}/{}/commit/{}", repo.namespace, repo.name, commit.hash),
61 "author": { "name": commit.author.name, "email": commit.author.email },
62 "committer": { "name": commit.author.name, "email": commit.author.email },
63 "distinct": true,
64 })
65}
66
67pub fn push(repo: &Repo, git_ref: &str, before: Option<&str>, after: &str, commits: &[Commit], pusher: &str) -> Value {
68 let zero = "0000000000000000000000000000000000000000";
69 let commits: Vec<Value> = commits.iter().map(|c| commit(repo, c)).collect();
70 json!({
71 "ref": git_ref,
72 "before": before.unwrap_or(zero),
73 "after": after,
74 "created": before.is_none(),
75 "deleted": false,
76 "forced": false,
77 "base_ref": null,
78 "compare": format!("{SITE}/{}/{}/commit/{after}", repo.namespace, repo.name),
79 "head_commit": commits.first().cloned().unwrap_or(Value::Null),
80 "commits": commits,
81 "pusher": { "name": pusher, "email": null },
82 "repository": repository(repo),
83 "sender": user(pusher),
84 })
85}
86
87pub fn issue(repo: &Repo, issue: &Issue) -> Value {
88 let full_name = format!("{}/{}", repo.namespace, repo.name);
89 json!({
90 "id": issue.id,
91 "number": issue.number,
92 "title": issue.title,
93 "body": issue.body,
94 "state": if issue.state == State::Open { "open" } else { "closed" },
95 "state_reason": issue.reason,
96 "labels": labels(&issue.labels),
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar97 "milestone": milestone(issue.milestone.as_ref()),
The statement is a month at a time, a line per kind of charge98 "user": person(&issue.author),
g1t is the stored author of what it opens; the person who asked is requested_by and keeps the author's rights99 // g1t's own field: for an issue its agent filed, who it worked for.
100 "requested_by": issue.requested_by.as_ref().map(person),
The statement is a month at a time, a line per kind of charge101 "assignees": issue.assignees.iter().map(|a| user(a)).collect::<Vec<_>>(),
102 "comments": issue.comment_count,
103 "created_at": issue.created_at,
104 "updated_at": issue.updated_at,
105 "closed_at": issue.closed_at,
106 "html_url": format!("{SITE}/{full_name}/issues/{}", issue.number),
107 "url": format!("{API}/repos/{full_name}/issues/{}", issue.number),
108 })
109}
110
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar111/// A pull request, with its own labels.
112pub fn pull(repo: &Repo, pull: &Pull) -> Value {
The statement is a month at a time, a line per kind of charge113 let full_name = format!("{}/{}", repo.namespace, repo.name);
114 let head_ref = head_ref(pull);
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar115 let base_ref = pull.base_branch(&repo.default_branch);
The statement is a month at a time, a line per kind of charge116 let head_repo = match &pull.fork {
117 Some(fork) => json!({ "full_name": format!("{}/{}", fork.namespace, fork.name), "fork": true }),
118 None => json!({ "full_name": full_name, "fork": false }),
119 };
120 json!({
121 "id": pull.id,
122 "number": pull.number,
123 "title": pull.title,
124 "body": pull.body,
125 "state": if pull.status.is_active() { "open" } else { "closed" },
126 "draft": pull.status == PullStatus::Draft,
127 "merged": pull.status == PullStatus::Merged,
128 "merged_at": pull.merged_at,
129 "merged_by": pull.merged_by.as_deref().map(user),
130 "merge_commit_sha": if pull.status == PullStatus::Merged { pull.head_commit.clone() } else { None },
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar131 "labels": labels(&pull.labels),
132 "milestone": milestone(pull.milestone.as_ref()),
The statement is a month at a time, a line per kind of charge133 "user": person(&pull.author),
g1t is the stored author of what it opens; the person who asked is requested_by and keeps the author's rights134 // g1t's own field: for a change g1t made, who asked for it.
135 "requested_by": pull.requested_by.as_ref().map(person),
The statement is a month at a time, a line per kind of charge136 "assignees": pull.assignees.iter().map(|a| user(a)).collect::<Vec<_>>(),
137 "requested_reviewers": pull.reviewers.iter().map(|r| user(r)).collect::<Vec<_>>(),
138 "head": {
139 "ref": head_ref,
140 "sha": pull.head_commit,
141 "label": format!("{}:{head_ref}", repo.namespace),
142 "repo": head_repo,
143 },
144 "base": {
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar145 "ref": base_ref,
The statement is a month at a time, a line per kind of charge146 "sha": pull.merge_base,
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar147 "label": format!("{}:{base_ref}", repo.namespace),
The statement is a month at a time, a line per kind of charge148 "repo": repository(repo),
149 },
150 "html_url": format!("{SITE}/{full_name}/pull/{}", pull.number),
151 "url": format!("{API}/repos/{full_name}/pulls/{}", pull.number),
152 "issue_url": pull.issue.map(|n| format!("{API}/repos/{full_name}/issues/{n}")),
153 })
154}
155
Merge branch 'claude/cool-newton-7nm8r9' (#36): real GitHub Actions on g1t, managed deploy workflows, g1t/deploy and the Sysbox runner 0.2.0. The merge keeps both sides' webhook events and API operations, says agent images need tar, guards the job engine's stop to Unix so the runner builds on Windows again, and keeps the moves tests' previews from going idle as the calendar passes their fixture's date.156/// What a `labeled`, `unlabeled`, `milestoned`, `demilestoned`,
157/// `assigned` or base change was about, from the g1t event: `label`,
158/// `milestone`, `assignee` (the first of those newly assigned; g1t sends
159/// one event for them all, GitHub one each), and for a new base,
160/// `changes.base.ref.from` is not known, so only the new base is in
161/// `pull_request.base`.
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar162pub fn changed(payload: &mut Value, data: &Value) {
Merge branch 'claude/cool-newton-7nm8r9' (#36): real GitHub Actions on g1t, managed deploy workflows, g1t/deploy and the Sysbox runner 0.2.0. The merge keeps both sides' webhook events and API operations, says agent images need tar, guards the job engine's stop to Unix so the runner builds on Windows again, and keeps the moves tests' previews from going idle as the calendar passes their fixture's date.163 if let Some(first) = data["added"].as_array().and_then(|added| added.first()).and_then(Value::as_str) {
164 payload["assignee"] = user(first);
165 }
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar166 if let Some(label) = data.get("label").filter(|label| label.is_object()) {
167 payload["label"] = json!({ "name": label["name"], "color": label["color"] });
168 }
169 if let Some(milestone) = data.get("milestone").filter(|milestone| milestone.is_object()) {
170 payload["milestone"] = json!({ "number": milestone["number"], "title": milestone["title"] });
171 }
172}
173
The statement is a month at a time, a line per kind of charge174/// The branch a pull request comes from, or a name for its fork.
175pub fn head_ref(pull: &Pull) -> String {
176 pull.branch.clone().unwrap_or_else(|| format!("pull/{}", pull.number))
177}
178
179pub fn comment(repo: &Repo, number: u32, comment: &Comment, on_pull: bool) -> Value {
180 let full_name = format!("{}/{}", repo.namespace, repo.name);
181 let page = if on_pull { "pull" } else { "issues" };
182 json!({
183 "id": comment.id,
184 "body": comment.body,
185 "user": person(&comment.author),
186 "created_at": comment.created_at,
187 "updated_at": comment.created_at,
188 "path": comment.path,
189 "line": comment.line,
190 "html_url": format!("{SITE}/{full_name}/{page}/{number}#{}", comment.id),
191 })
192}
193
194/// An issue as `issue_comment` gives it for a pull request: the pull
195/// request's number and title, with `pull_request` set.
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar196pub fn pull_as_issue(repo: &Repo, pull: &Pull) -> Value {
The statement is a month at a time, a line per kind of charge197 let full_name = format!("{}/{}", repo.namespace, repo.name);
198 json!({
199 "id": pull.id,
200 "number": pull.number,
201 "title": pull.title,
202 "body": pull.body,
203 "state": if pull.status.is_active() { "open" } else { "closed" },
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar204 "labels": labels(&pull.labels),
205 "milestone": milestone(pull.milestone.as_ref()),
The statement is a month at a time, a line per kind of charge206 "user": person(&pull.author),
g1t is the stored author of what it opens; the person who asked is requested_by and keeps the author's rights207 "requested_by": pull.requested_by.as_ref().map(person),
The statement is a month at a time, a line per kind of charge208 "html_url": format!("{SITE}/{full_name}/pull/{}", pull.number),
209 "pull_request": {
210 "url": format!("{API}/repos/{full_name}/pulls/{}", pull.number),
211 "html_url": format!("{SITE}/{full_name}/pull/{}", pull.number),
212 "merged_at": pull.merged_at,
213 },
214 })
215}
216
Merge Actions: cross-repo workflows and actions, release and deployment triggers, step timeouts217/// A comment as the event that deleted it kept it (`comment.deleted`'s
218/// `comment`), since it can no longer be read.
219pub fn deleted_comment(repo: &Repo, number: u32, kept: &Value, on_pull: bool) -> Value {
220 let full_name = format!("{}/{}", repo.namespace, repo.name);
221 let page = if on_pull { "pull" } else { "issues" };
222 let id = kept["id"].as_str().unwrap_or_default();
223 let login = kept["author"]["username"].as_str().unwrap_or_default();
224 json!({
225 "id": id,
226 "body": kept["body"],
227 "user": user(login),
228 "created_at": kept["createdAt"],
229 "updated_at": kept["createdAt"],
230 "path": kept["path"],
231 "line": kept["line"],
232 "html_url": format!("{SITE}/{full_name}/{page}/{number}#{id}"),
233 })
234}
235
236/// A release, from what a `release.*` event carries
237/// (`g1t_contracts::about::Release`).
238pub fn release(repo: &Repo, release: &Value) -> Value {
239 let full_name = format!("{}/{}", repo.namespace, repo.name);
240 let tag = release["tagName"].as_str().unwrap_or_default();
241 let id = release["id"].as_str().unwrap_or_default();
242 json!({
243 "id": id,
244 "node_id": id,
245 "tag_name": tag,
246 "target_commitish": release["target"],
247 "name": release["name"].as_str().unwrap_or(tag),
248 "body": release["body"],
249 "draft": release["draft"].as_bool().unwrap_or(false),
250 "prerelease": release["prerelease"].as_bool().unwrap_or(false),
251 "created_at": release["createdAt"],
252 "published_at": release["publishedAt"],
253 "author": user(release["author"].as_str().unwrap_or(&repo.namespace)),
254 "assets": [],
255 "html_url": format!("{SITE}/{full_name}/releases/tag/{tag}"),
256 "url": format!("{API}/repos/{full_name}/releases/{id}"),
257 })
258}
259
260/// A deployment, from what `deployment.created` and
261/// `deployment_status.created` carry (`RepoDeployment`, already in GitHub's
262/// spelling).
263pub fn deployment(repo: &Repo, deployment: &Value) -> Value {
264 let full_name = format!("{}/{}", repo.namespace, repo.name);
265 let id = deployment["id"].as_str().unwrap_or_default();
266 json!({
267 "id": id,
268 "node_id": id,
269 "sha": deployment["sha"],
270 "ref": deployment["ref"],
271 "task": deployment["task"].as_str().unwrap_or("deploy"),
272 "payload": deployment.get("payload").filter(|p| p.is_object()).cloned().unwrap_or_else(|| json!({})),
273 "environment": deployment["environment"],
274 "original_environment": deployment["environment"],
275 "description": deployment["description"],
276 "creator": user(deployment["creator"].as_str().unwrap_or("g1t")),
277 "transient_environment": deployment["transient_environment"].as_bool().unwrap_or(false),
278 "production_environment": deployment["production_environment"].as_bool().unwrap_or(false),
279 "created_at": deployment["created_at"],
280 "updated_at": deployment["updated_at"],
281 "url": format!("{API}/repos/{full_name}/deployments/{id}"),
282 "statuses_url": format!("{API}/repos/{full_name}/deployments/{id}/statuses"),
283 })
284}
285
286/// A deployment's status (`DeploymentStatus`), for `deployment_status`.
287pub fn deployment_status(repo: &Repo, status: &Value, deployment: &Value) -> Value {
288 let full_name = format!("{}/{}", repo.namespace, repo.name);
289 let deployment_id = deployment["id"].as_str().unwrap_or_default();
290 let id = status["id"].as_str().unwrap_or_default();
291 json!({
292 "id": id,
293 "node_id": id,
294 "state": status["state"],
295 "description": status["description"].as_str().unwrap_or_default(),
296 "environment": deployment["environment"],
297 "environment_url": status["environment_url"].as_str().unwrap_or_default(),
298 "log_url": status["log_url"].as_str().unwrap_or_default(),
299 "target_url": status["log_url"].as_str().unwrap_or_default(),
300 "creator": user(status["creator"].as_str().unwrap_or("g1t")),
301 "created_at": status["created_at"],
302 "updated_at": status["created_at"],
303 "deployment_url": format!("{API}/repos/{full_name}/deployments/{deployment_id}"),
304 "url": format!("{API}/repos/{full_name}/deployments/{deployment_id}/statuses/{id}"),
305 })
306}
307
Merge branch 'claude/cool-newton-7nm8r9' (#36): real GitHub Actions on g1t, managed deploy workflows, g1t/deploy and the Sysbox runner 0.2.0. The merge keeps both sides' webhook events and API operations, says agent images need tar, guards the job engine's stop to Unix so the runner builds on Windows again, and keeps the moves tests' previews from going idle as the calendar passes their fixture's date.308
309/// A review's `state` as GitHub's `pull_request_review` event spells it.
310pub(crate) fn review_state(verdict: g1t_contracts::work::Verdict) -> &'static str {
311 match verdict {
312 g1t_contracts::work::Verdict::Approve => "approved",
313 g1t_contracts::work::Verdict::RequestChanges => "changes_requested",
314 }
315}
316
317#[cfg(test)]
318mod review_tests {
319 use super::*;
320
321 #[test]
322 fn review_states_are_spelled_as_githubs() {
323 assert_eq!(review_state(g1t_contracts::work::Verdict::Approve), "approved");
324 assert_eq!(review_state(g1t_contracts::work::Verdict::RequestChanges), "changes_requested");
325 }
326}

This file's history is long; its oldest lines are credited to the oldest commit read.