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.

g1t.sh

Commit

A workflow can be read on its own: get_workflow (GET /repos/{owner}/{name}/actions/workflows/{workflow}, and get on the MCP workflow tool) names a workflow by its id, file name, path or name and answers with what list_workflows says of it, plus source, its file as written on ref (the default branch unless a branch or tag is given), and the ref read. It needs workflows:read, reads only what the viewer may see, and says which when the branch, tag or file is not there. The OpenAPI document, the CLI's operation table and the MCP reference list it.

syntaqxcommitted Parent85c083aBrowse files
15 files+168−20/15 viewed
+2−0
391391 "GitHub Actions workflows in .g1t/workflows, their runs, and their jobs' logs.",
392392 &[
393393 Op::ListWorkflows,
394+ Op::GetWorkflow,
394395 Op::ListWorkflowRuns,
395396 Op::GetWorkflowRun,
396397 Op::GetJobLogs,
679680 Op::ListWebhookDeliveries => "List webhook deliveries",
680681 Op::RedeliverWebhook => "Redeliver a webhook delivery",
681682 Op::ListWorkflows => "List workflows",
683+ Op::GetWorkflow => "Get a workflow",
682684 Op::ListWorkflowRuns => "List workflow runs",
683685 Op::GetWorkflowRun => "Get a workflow run",
684686 Op::GetJobLogs => "Get a job's log",
+23−1
206206 ListWebhookDeliveries,
207207 RedeliverWebhook,
208208 ListWorkflows,
209+ GetWorkflow,
209210 ListWorkflowRuns,
210211 GetWorkflowRun,
211212 GetJobLogs,
686687 }
687688
688689 impl Op {
689− pub const ALL: [Op; 353] = [
690+ pub const ALL: [Op; 354] = [
690691 Op::Whoami,
691692 Op::GetWorkspace,
692693 Op::CreateWorkspace,
783784 Op::ListWebhookDeliveries,
784785 Op::RedeliverWebhook,
785786 Op::ListWorkflows,
787+ Op::GetWorkflow,
786788 Op::ListWorkflowRuns,
787789 Op::GetWorkflowRun,
788790 Op::GetJobLogs,
11451147 Op::ListWebhookDeliveries => "list_webhook_deliveries",
11461148 Op::RedeliverWebhook => "redeliver_webhook",
11471149 Op::ListWorkflows => "list_workflows",
1150+ Op::GetWorkflow => "get_workflow",
11481151 Op::ListWorkflowRuns => "list_workflow_runs",
11491152 Op::GetWorkflowRun => "get_workflow_run",
11501153 Op::GetJobLogs => "get_job_logs",
15041507 Op::ListWorkflows => {
15051508 "A repository's GitHub Actions workflows, read from .g1t/workflows (GitHub's format, so a repository moves by renaming .github to .g1t) on its default branch: the events that start each, whether it is on, any problem with its file, notes on anything that runs differently on g1t, its manual-run inputs, and its last run."
15061509 }
1510+ Op::GetWorkflow => {
1511+ "One workflow, named by its id, its file name (ci.yml), its path or its name (CI): what list_workflows says of it, with `source`, its file as written on `ref` (the default branch unless you give a branch or tag), and `ref`, the branch or tag read."
1512+ }
15071513 Op::ListWorkflowRuns => {
15081514 "A repository's workflow runs, newest first: of one workflow (its id or file name), a branch, an event, a pull request's number, or a commit."
15091515 }
24702476 ),
24712477 Op::ListWebhooks => object(hook_owner(json!({})), &[]),
24722478 Op::ListWorkflows => repo_only(),
2479+ Op::GetWorkflow => object(
2480+ json!({
2481+ "repo": repo_schema(),
2482+ "workflow": { "type": "string", "description": "The workflow's id, file name (ci.yml), path or name." },
2483+ "ref": { "type": "string", "description": "A branch or tag to read its file on. The default branch if not given." },
2484+ }),
2485+ &["repo", "workflow"],
2486+ ),
24732487 Op::ListWorkflowRuns => object(
24742488 json!({
24752489 "repo": repo_schema(),
45034517 .await
45044518 }
45054519 Op::ListWorkflows => pass(actions, "workflows", &json!({ "repo": repo, "viewer": viewer })).await,
4520+ Op::GetWorkflow => {
4521+ pass(
4522+ actions,
4523+ "workflow",
4524+ &json!({ "repo": repo, "viewer": viewer, "workflow": text(input, "workflow"), "ref": optional_text(input, "ref") }),
4525+ )
4526+ .await
4527+ }
45064528 Op::ListWorkflowRuns => {
45074529 pass(
45084530 actions,
+33−0
22912291 ],
22922292 "notes": "`state` is `active` or `disabled`. `dispatch` holds the `workflow_dispatch` inputs, or is `null` when the workflow cannot be run by hand. See [GitHub Actions](/guides/actions/)."
22932293 },
2294+ "get_workflow": {
2295+ "params": {
2296+ "workflow": "release.yml"
2297+ },
2298+ "query": {
2299+ "ref": "main"
2300+ },
2301+ "response": {
2302+ "id": "wfl_01kpw2c5d8e1f4g7h0j3k6m9nr",
2303+ "path": ".g1t/workflows/release.yml",
2304+ "name": "Release",
2305+ "events": [
2306+ "workflow_dispatch"
2307+ ],
2308+ "state": "active",
2309+ "error": null,
2310+ "notes": [],
2311+ "dispatch": {
2312+ "channel": {
2313+ "type": "choice",
2314+ "options": [
2315+ "beta",
2316+ "stable"
2317+ ],
2318+ "default": "beta"
2319+ }
2320+ },
2321+ "last_run": null,
2322+ "ref": "main",
2323+ "source": "name: Release\non:\n workflow_dispatch:\n inputs:\n channel:\n type: choice\n options: [beta, stable]\n default: beta\njobs:\n release:\n runs-on: ubuntu-latest\n steps:\n - uses: actions/checkout@v4\n - run: ./scripts/release.sh ${{ inputs.channel }}\n"
2324+ },
2325+ "notes": "`workflow` is the workflow's id, file name, path or name. `source` is its file as written on `ref`; `404` when the file is not on that branch or tag."
2326+ },
22942327 "list_workflow_runs": {
22952328 "query": {
22962329 "workflow": "ci.yml",
+1−0
190190 through::<work::Pull>(op, sent)
191191 }
192192 Op::ListWorkflows => through::<Vec<actions::Workflow>>(op, sent),
193+ Op::GetWorkflow => through::<actions::WorkflowDetail>(op, sent),
193194 Op::ListWorkflowRuns => through::<Vec<actions::WorkflowRun>>(op, sent),
194195 Op::GetWorkflowRun => through::<actions::RunDetail>(op, sent),
195196 Op::GetJobLogs => through::<actions::JobLog>(op, sent),
+6−0
712712 ),
713713 route(
714714 "GET",
715+ "/repos/:owner/:name/actions/workflows/:workflow",
716+ Op::GetWorkflow,
717+ &[("ref", "ref")],
718+ ),
719+ route(
720+ "GET",
715721 "/repos/:owner/:name/actions/workflows/:workflow/runs",
716722 Op::ListWorkflowRuns,
717723 &[("branch", "branch"), ("event", "event"), ("per_page", "limit")],
+1−0
194194 default_action: None,
195195 actions: &[
196196 a("list", Op::ListWorkflows, "Workflows on the default branch"),
197+ a("get", Op::GetWorkflow, "One workflow by id, file or name, with its file on a branch or tag"),
197198 a("list_runs", Op::ListWorkflowRuns, "Runs, newest first"),
198199 a("get_run", Op::GetWorkflowRun, "One run with its jobs and steps; an earlier attempt with attempt"),
199200 a("job_logs", Op::GetJobLogs, "A job's log after a sequence number"),
+1−0
392392 | Action | What it does | Required | Scope |
393393 | --- | --- | --- | --- |
394394 | [`list`](/reference/api/actions/list-workflows/) | The workflows, with their events, state, problems, notes on what runs differently, manual-run inputs and last run. | `repo` | `workflows:read` |
395+| [`get`](/reference/api/actions/get-workflow/) | One workflow by its id, file name, path or name, as `list` gives it, with `source`, its file on `ref` (the default branch unless given). | `repo`, `workflow` | `workflows:read` |
395396 | [`list_runs`](/reference/api/actions/list-runs-of-workflow/) | Runs, newest first; filter by `workflow`, `branch`, `event`, `pull` or `sha`. | `repo` | `workflows:read` |
396397 | [`get_run`](/reference/api/actions/get-workflow-run/) | A run with its jobs, their steps and annotations, and its `attempts`; `attempt` reads an earlier attempt, with the jobs it had then. | `repo`, `id` | `workflows:read` |
397398 | [`job_logs`](/reference/api/actions/get-job-logs/) | A job's log after `after`; `done` says if more will come. | `repo`, `job` | `workflows:read` |
+0−0

Binary or large file; its contents are not shown.

+25−0
629629 pub viewer: Viewer,
630630 }
631631
632+/// `workflow`: one workflow, named by its id (`wfl_…`), its file name
633+/// (`ci.yml`), its path or its name, with its file as of `ref` (the
634+/// default branch when absent). Returns `Outcome<WorkflowDetail>`.
635+#[derive(Debug, Serialize, Deserialize)]
636+pub struct WorkflowArgs {
637+ pub repo: RepoPath,
638+ pub viewer: Viewer,
639+ pub workflow: String,
640+ #[serde(default, rename = "ref")]
641+ pub git_ref: Option<String>,
642+}
643+
644+/// A workflow with its file.
645+#[derive(Clone, Debug, Serialize, Deserialize)]
646+#[serde(rename_all = "camelCase")]
647+pub struct WorkflowDetail {
648+ #[serde(flatten)]
649+ pub workflow: Workflow,
650+ /// The branch or tag the file was read from.
651+ #[serde(rename = "ref")]
652+ pub git_ref: String,
653+ /// The file, as written on `ref`.
654+ pub source: String,
655+}
656+
632657 /// `runs`: newest first. Returns `Outcome<Vec<WorkflowRun>>`.
633658 #[derive(Debug, Serialize, Deserialize)]
634659 pub struct RunsArgs {
+1−0
282282 "get_context",
283283 "search",
284284 "list_workflows",
285+ "get_workflow",
285286 "list_workflow_runs",
286287 "get_workflow_run",
287288 "get_job_logs",
+1−0
11221122 ("remove_requested_reviewers", Scope::PullRequestsWrite),
11231123 // Workflows.
11241124 ("list_workflows", Scope::WorkflowsRead),
1125+ ("get_workflow", Scope::WorkflowsRead),
11251126 ("list_workflow_runs", Scope::WorkflowsRead),
11261127 ("get_workflow_run", Scope::WorkflowsRead),
11271128 ("get_job_logs", Scope::WorkflowsRead),
+15−0
474474 body: &[],
475475 },
476476 Operation {
477+ id: "get_workflow",
478+ method: "GET",
479+ path: "/repos/{owner}/{name}/actions/workflows/{workflow}",
480+ tag: "Actions",
481+ summary: "Get a workflow",
482+ scope: Some("workflows:read"),
483+ params: &[
484+ Param { name: "owner", location: In::Path, kind: "string", required: true, values: &[] },
485+ Param { name: "name", location: In::Path, kind: "string", required: true, values: &[] },
486+ Param { name: "workflow", location: In::Path, kind: "string", required: true, values: &[] },
487+ Param { name: "ref", location: In::Query, kind: "string", required: false, values: &[] },
488+ ],
489+ body: &[],
490+ },
491+ Operation {
477492 id: "get_workflow_run",
478493 method: "GET",
479494 path: "/repos/{owner}/{name}/actions/runs/{id}",
+1−0
489489 ["request_reviewers", "pull_requests:write"],
490490 ["remove_requested_reviewers", "pull_requests:write"],
491491 ["list_workflows", "workflows:read"],
492+ ["get_workflow", "workflows:read"],
492493 ["list_workflow_runs", "workflows:read"],
493494 ["get_workflow_run", "workflows:read"],
494495 ["get_job_logs", "workflows:read"],
+1−0
234234 let service = Actions::new(&env)?;
235235 match method.as_str() {
236236 "workflows" => reply(&service.workflows(args(body)?).await?),
237+ "workflow" => reply(&service.workflow(args(body)?).await?),
237238 "runs" => reply(&service.runs(args(body)?).await?),
238239 "run" => reply(&service.run(args(body)?).await?),
239240 "logs" => reply(&service.logs(args(body)?).await?),
+57−1
66 use g1t_contracts::checks::{ActionsChecksArgs, MAX_COMMITS};
77 use g1t_contracts::actions::{
88 Annotation, Job, JobLog, JobLogText, JobLogTextArgs, JobSummary, LogChunk, LogsArgs, MAX_RUN_LOG_BYTES, RunArgs, RunAttempt, RunDetail,
9− RunLogsArgs, RunsArgs, SetWorkflowEnabledArgs, StepState, StepSummary, SummariesArgs, Workflow, WorkflowNote, WorkflowRun, WorkflowsArgs,
9+ RunLogsArgs, RunsArgs, SetWorkflowEnabledArgs, StepState, StepSummary, SummariesArgs, Workflow, WorkflowArgs, WorkflowDetail, WorkflowNote, WorkflowRun,
10+ WorkflowsArgs,
1011 };
1112 use g1t_contracts::{FailureCode, Outcome};
1213 use serde::Deserialize;
125126 }
126127 }
127128
129+/// The workflow `wanted` names: its id, its path, its file name, or its
130+/// name (any case), in that order of preference.
131+fn pick_workflow<'a>(rows: &'a [WorkflowRow], wanted: &str) -> Option<&'a WorkflowRow> {
132+ let wanted = wanted.trim();
133+ let file = wanted.trim_start_matches(".g1t/workflows/");
134+ rows.iter()
135+ .find(|row| row.id == wanted || row.path == wanted)
136+ .or_else(|| rows.iter().find(|row| row.path == format!("{}/{file}", workflow::FOLDER)))
137+ .or_else(|| rows.iter().find(|row| row.path.rsplit('/').next() == Some(file)))
138+ .or_else(|| rows.iter().find(|row| row.name.eq_ignore_ascii_case(wanted)))
139+}
140+
128141 impl Actions {
129142 async fn summary(&self, row: &WorkflowRow) -> Result<Workflow> {
130143 let last_run = self
194207 Ok(Outcome::Ok(out))
195208 }
196209
210+ /// `workflow`: one workflow, by its id, file name, path or name, with its
211+ /// file as of a branch or tag.
212+ pub async fn workflow(&self, a: WorkflowArgs) -> Result<Outcome<WorkflowDetail>> {
213+ let Some(repo) = self.visible_repo(&a.repo, &a.viewer).await? else {
214+ return Ok(fail(FailureCode::NotFound, "There is no such repository."));
215+ };
216+ let ws = self.workspace_actor(&repo.namespace).await?;
217+ if !self.synced(&repo.id).await?
218+ && let Some(ws) = &ws
219+ {
220+ self.sync(&repo, ws).await?;
221+ }
222+ let rows = self
223+ .db
224+ .prepare("SELECT * FROM workflows WHERE repo_id = ? ORDER BY name")
225+ .bind(&[repo.id.as_str().into()])?
226+ .all()
227+ .await?
228+ .results::<WorkflowRow>()?;
229+ let Some(row) = pick_workflow(&rows, &a.workflow) else {
230+ return Ok(fail(FailureCode::NotFound, format!("There is no workflow {} in {}/{}.", a.workflow.trim(), repo.namespace, repo.name)));
231+ };
232+ let workflow = self.summary(row).await?;
233+ let (git_ref, source) = match a.git_ref.as_deref().map(str::trim).filter(|r| !r.is_empty()) {
234+ None => (repo.default_branch.clone(), row.source.clone()),
235+ Some(wanted) => {
236+ let Some(ws) = &ws else {
237+ return Ok(fail(FailureCode::NotFound, "There is no such workspace."));
238+ };
239+ let short = wanted.trim_start_matches("refs/heads/").trim_start_matches("refs/tags/");
240+ let read = self.read_for(&repo, ws, Some(short)).await?;
241+ if read.head.is_none() {
242+ return Ok(fail(FailureCode::NotFound, format!("There is no branch or tag called {short}.")));
243+ }
244+ let Some(file) = read.files.into_iter().find(|file| file.path == row.path) else {
245+ return Ok(fail(FailureCode::NotFound, format!("{} is not on {short}.", row.path)));
246+ };
247+ (short.to_owned(), file.source)
248+ }
249+ };
250+ Ok(Outcome::Ok(WorkflowDetail { workflow, git_ref, source }))
251+ }
252+
197253 pub async fn runs(&self, a: RunsArgs) -> Result<Outcome<Vec<WorkflowRun>>> {
198254 let Some(repo) = self.visible_repo(&a.repo, &a.viewer).await? else {
199255 return Ok(fail(FailureCode::NotFound, "There is no such repository."));

Comments

Sign in to comment.