hive-forge: ci-rerun dispatches a fresh run via workflow-dispatch API
The previous implementation POSTed Forgejo's run-page rerun web route,
which is CSRF-gated and answers a bare token POST with 404 — so the verb
never actually re-ran anything against the agent token.
Rework it to dispatch a fresh run of the workflow via the
GitHub-compatible workflow-dispatch API
(POST /repos/<o>/<r>/actions/workflows/<workflow>/dispatches {"ref":<branch>}),
which accepts a plain agent token (verified end-to-end on Forgejo 15.0.3).
A dispatched run is equivalent to the old empty-commit retrigger, minus
the commit.
The branch is resolved from exactly one of --pr (the PR head branch),
--run (branch + workflow looked up from that run in the Actions runs
list), or --branch (directly); --workflow picks the workflow file for
--pr/--branch (default ci.yml). Dispatch re-runs the whole workflow, so
the old --job single-job variant is dropped.
Also add workflow_dispatch to .forgejo/workflows/ci.yml for explicitness
(Forgejo 15.0.3 dispatches the pull_request workflow without it, but the
trigger makes the API path intent-clear and cross-version robust), remove
the now-unused Client::post_web_no_content, and update docs/tools/forge.md.
This commit is contained in:
parent
cd4bdf4eea
commit
c9c59c2a1d
4 changed files with 175 additions and 118 deletions
|
|
@ -3,6 +3,9 @@ name: CI
|
||||||
on:
|
on:
|
||||||
pull_request:
|
pull_request:
|
||||||
branches: ["**"]
|
branches: ["**"]
|
||||||
|
# Lets `hive-forge ci-rerun` re-trigger CI via the workflow-dispatch API
|
||||||
|
# without an empty commit. No effect on the PR-triggered runs above.
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
check:
|
check:
|
||||||
|
|
|
||||||
|
|
@ -76,7 +76,7 @@ hive-forge attach-comment 18042 /path/to/file # upload a file attachment to a c
|
||||||
hive-forge attachment-get <uuid> # download an attachment; prints resolved path to stdout
|
hive-forge attachment-get <uuid> # download an attachment; prints resolved path to stdout
|
||||||
hive-forge artifact-get pr1ma-paper-pdf --run 51 # download a CI run's Actions artifact zip (run number from the run-page URL)
|
hive-forge artifact-get pr1ma-paper-pdf --run 51 # download a CI run's Actions artifact zip (run number from the run-page URL)
|
||||||
hive-forge ci-log --run 51 # print a CI run's job step logs (run number from the run-page URL); --job i / --step i to narrow
|
hive-forge ci-log --run 51 # print a CI run's job step logs (run number from the run-page URL); --job i / --step i to narrow
|
||||||
hive-forge ci-rerun --run 51 # re-run a CI run without an empty commit (--pr n resolves the head run; --job i re-runs one job)
|
hive-forge ci-rerun --pr 42 # re-run CI without an empty commit (dispatches a fresh run; --run n / --branch name also work)
|
||||||
hive-forge subscription --watch # subscribe to repo notifications
|
hive-forge subscription --watch # subscribe to repo notifications
|
||||||
hive-forge subscription --unwatch # unsubscribe
|
hive-forge subscription --unwatch # unsubscribe
|
||||||
hive-forge -r internal/knowledge clone # clone with creds auto-injected
|
hive-forge -r internal/knowledge clone # clone with creds auto-injected
|
||||||
|
|
@ -173,12 +173,17 @@ plain comment show under `last comment`, not `reviews`.
|
||||||
is reliable for live + recently-finished runs; when logs are gone the
|
is reliable for live + recently-finished runs; when logs are gone the
|
||||||
verb says so rather than printing nothing. `--json` dumps the raw
|
verb says so rather than printing nothing. `--json` dumps the raw
|
||||||
run-view response.
|
run-view response.
|
||||||
- `ci-rerun --run <n> [--job i]` re-runs a CI Actions run without
|
- `ci-rerun` re-runs CI without pushing an empty commit (the old
|
||||||
pushing an empty commit (the old retrigger path, which littered PR
|
retrigger path, which littered PR history). Forgejo has no token-usable
|
||||||
history). `<n>` is the same run number `ci-log` / `artifact-get` take;
|
REST endpoint to re-run an *existing* run (the run-page rerun buttons
|
||||||
`--pr <n>` resolves the run from a PR head sha's CI status instead.
|
are CSRF-gated web routes a token POST 404s), so this dispatches a
|
||||||
`--job i` re-runs a single job (0-based); omit to re-run all jobs.
|
**fresh** run of the workflow via the workflow-dispatch API
|
||||||
Forgejo exposes no REST endpoint, so it POSTs the run page's rerun web
|
(`POST …/actions/workflows/<workflow>/dispatches {"ref":"<branch>"}`).
|
||||||
route (which answers with a redirect, not JSON).
|
Resolve the branch with exactly one of: `--pr <n>` (the PR's head
|
||||||
|
branch), `--run <n>` (the same run number `ci-log` / `artifact-get`
|
||||||
|
take — resolves the branch + workflow from that run), or `--branch
|
||||||
|
<name>` (directly). `--workflow <file>` picks the workflow file for
|
||||||
|
`--pr` / `--branch` (default `ci.yml`). Dispatch re-runs the whole
|
||||||
|
workflow — there is no single-job variant.
|
||||||
- Do NOT use raw `curl` for forge access -- the CLI handles auth,
|
- Do NOT use raw `curl` for forge access -- the CLI handles auth,
|
||||||
error checking, and output formatting.
|
error checking, and output formatting.
|
||||||
|
|
|
||||||
|
|
@ -321,26 +321,6 @@ impl Client {
|
||||||
decode_json(resp, &format!("POST {url}"))
|
decode_json(resp, &format!("POST {url}"))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// POST to a base-relative *web* path (NOT under `/api/v1/`) whose
|
|
||||||
/// response carries no useful body — e.g. the Actions run rerun
|
|
||||||
/// endpoints (`<base>/<owner>/<repo>/actions/runs/<run>/rerun`), which
|
|
||||||
/// answer with a redirect to the run page rather than JSON. Same
|
|
||||||
/// token-auth path as `post_json_web` (the web router accepts a
|
|
||||||
/// token-authed doer and skips CSRF for non-session auth); the bodyless
|
|
||||||
/// POST mirrors the form-handler's expectations (run/job come from the
|
|
||||||
/// URL). reqwest follows the redirect, so a 2xx on the final hop is
|
|
||||||
/// success. `path` should start with `/`.
|
|
||||||
///
|
|
||||||
/// # Errors
|
|
||||||
/// Returns an error on a transport failure or a non-2xx final status
|
|
||||||
/// (e.g. `404` for an unknown run).
|
|
||||||
pub fn post_web_no_content(&self, path: &str) -> Result<()> {
|
|
||||||
let url = self.web_url(path);
|
|
||||||
let resp = self.http.post(&url).send().context("POST")?;
|
|
||||||
check_status(resp, &format!("POST {url}"))?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// GET a raw (non-API) URL and return the response body as bytes.
|
/// GET a raw (non-API) URL and return the response body as bytes.
|
||||||
/// The client's auth headers are still sent — Forgejo requires them
|
/// The client's auth headers are still sent — Forgejo requires them
|
||||||
/// for private attachment downloads. Uses the full URL as-is; the
|
/// for private attachment downloads. Uses the full URL as-is; the
|
||||||
|
|
|
||||||
|
|
@ -1,133 +1,202 @@
|
||||||
//! `ci-rerun --run <n> [--job <i>]` / `ci-rerun --pr <n>` — re-run a CI
|
//! `ci-rerun --pr <n>` / `ci-rerun --run <n>` / `ci-rerun --branch <name>`
|
||||||
//! Actions workflow run without pushing an empty commit.
|
//! — re-run CI without pushing an empty commit.
|
||||||
//!
|
//!
|
||||||
//! When a run fails for a transient reason (a remote-builder flap, a
|
//! When a run fails for a transient reason (a remote-builder flap, a
|
||||||
//! cold-daemon window, an `act_runner` hiccup) the only retrigger path used
|
//! cold-daemon window, an `act_runner` hiccup) the only retrigger path used
|
||||||
//! to be an empty commit, which litters PR history. This verb POSTs the
|
//! to be an empty commit, which litters PR history.
|
||||||
//! rerun action directly.
|
|
||||||
//!
|
//!
|
||||||
//! Forgejo exposes no REST endpoint for rerunning a run; the rerun buttons
|
//! Forgejo exposes no token-usable REST endpoint to *re-run an existing run*:
|
||||||
//! on the run page hit web routes. Re-running all jobs is a POST to
|
//! the run-page rerun buttons hit CSRF-gated web routes that a bare token
|
||||||
//! `<base>/<owner>/<repo>/actions/runs/<n>/rerun`, and re-running one job a
|
//! POST answers with `404`. Instead this verb dispatches a **fresh** run of
|
||||||
//! POST to `<base>/<owner>/<repo>/actions/runs/<n>/jobs/<i>/rerun`.
|
//! the workflow via the GitHub-compatible workflow-dispatch API,
|
||||||
|
//! `POST /repos/<owner>/<repo>/actions/workflows/<workflow>/dispatches` with
|
||||||
|
//! `{"ref": "<branch>"}`. That creates a brand-new run on the branch — the
|
||||||
|
//! same effect as the empty-commit trick, minus the commit — and accepts a
|
||||||
|
//! plain agent token (verified end-to-end on Forgejo 15.0.3, which dispatches
|
||||||
|
//! even a `pull_request`-only workflow).
|
||||||
//!
|
//!
|
||||||
//! Both key off the per-repo run NUMBER (the `runs/<n>` the UI shows and
|
//! The branch (and, for `--run`, the workflow file) is resolved from the
|
||||||
//! `pr-status` surfaces as a CI context `target_url`), so `--run` is used
|
//! given handle:
|
||||||
//! directly with no id translation — same convention as `ci-log`. They
|
//! - `--pr <n>` → the PR's head branch; dispatches `--workflow` (default
|
||||||
//! reply with a redirect to the run page rather than a body, so this drives
|
//! `ci.yml`) on it.
|
||||||
//! `Client::post_web_no_content`. The auth path matches `ci-log`'s web POST:
|
//! - `--branch <name>` → dispatches `--workflow` on that branch directly.
|
||||||
//! a token-authed doer, no `_csrf` needed.
|
//! - `--run <n>` → looks the run up in the Actions runs list (by the
|
||||||
|
//! `runs/<n>` tail of its `html_url`, same convention as `ci-log` /
|
||||||
|
//! `artifact-get`) and dispatches the SAME workflow on the SAME branch the
|
||||||
|
//! run used.
|
||||||
//!
|
//!
|
||||||
//! `--pr` is a convenience: it resolves the PR head sha's CI status and
|
//! Dispatch re-runs the whole workflow, so there is no single-job variant.
|
||||||
//! pulls the run number out of the status `target_url`, then re-runs that
|
|
||||||
//! run's jobs.
|
|
||||||
|
|
||||||
use anyhow::{Context as _, Result, bail};
|
use anyhow::{Context as _, Result, bail};
|
||||||
use clap::Args as ClapArgs;
|
use clap::Args as ClapArgs;
|
||||||
use serde_json::Value;
|
use serde_json::{Value, json};
|
||||||
|
|
||||||
use crate::client::Client;
|
use crate::client::Client;
|
||||||
|
|
||||||
#[derive(ClapArgs)]
|
#[derive(ClapArgs)]
|
||||||
pub struct Args {
|
pub struct Args {
|
||||||
/// Workflow run number — the `runs/<n>` in the run-page URL, which
|
/// Re-run CI for this PR: resolves the PR's head branch and dispatches
|
||||||
/// `pr-status` surfaces as a CI context's `target_url`. Mutually
|
/// `--workflow` on it. Mutually exclusive with `--run` / `--branch`.
|
||||||
/// exclusive with `--pr`.
|
#[arg(long, conflicts_with_all = ["run", "branch"])]
|
||||||
#[arg(long, conflicts_with = "pr")]
|
|
||||||
run: Option<u64>,
|
|
||||||
/// Re-run the latest run for this PR's head commit. Resolves the run
|
|
||||||
/// number from the head sha's CI status. Mutually exclusive with
|
|
||||||
/// `--run`.
|
|
||||||
#[arg(long)]
|
|
||||||
pr: Option<u64>,
|
pr: Option<u64>,
|
||||||
/// Re-run only this job index (0-based). Omit to re-run every job in
|
/// Dispatch a fresh run of the workflow that produced this run, on the
|
||||||
/// the run.
|
/// same branch the run used. The run number is the `runs/<n>` in the
|
||||||
#[arg(long)]
|
/// run-page URL — what `pr-status` surfaces as a CI context's
|
||||||
job: Option<u64>,
|
/// `target_url`. Mutually exclusive with `--pr` / `--branch`.
|
||||||
|
#[arg(long, conflicts_with_all = ["pr", "branch"])]
|
||||||
|
run: Option<u64>,
|
||||||
|
/// Dispatch `--workflow` on this branch directly. Mutually exclusive
|
||||||
|
/// with `--pr` / `--run`.
|
||||||
|
#[arg(long, conflicts_with_all = ["pr", "run"])]
|
||||||
|
branch: Option<String>,
|
||||||
|
/// Workflow file to dispatch for `--pr` / `--branch` (the file name under
|
||||||
|
/// `.forgejo/workflows/`). Ignored for `--run`, which resolves the
|
||||||
|
/// workflow from the run itself (falling back to this value).
|
||||||
|
#[arg(long, default_value = "ci.yml")]
|
||||||
|
workflow: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// # Errors
|
/// # Errors
|
||||||
///
|
///
|
||||||
/// Returns an error if neither `--run` nor `--pr` is given, if `--pr` can't
|
/// Returns an error if none of `--pr` / `--run` / `--branch` is given, if a
|
||||||
/// be resolved to a run number (no CI status on the head commit yet), or if
|
/// `--pr` / `--run` handle can't be resolved (unknown PR/run, or a run
|
||||||
/// the rerun POST fails (network, or a non-2xx such as `404` for an unknown
|
/// missing its branch), or if the dispatch POST fails (network, or a non-2xx
|
||||||
/// run).
|
/// such as `404` for an unknown workflow file or branch).
|
||||||
pub fn run(client: &Client, args: Args) -> Result<()> {
|
pub fn run(client: &Client, args: Args) -> Result<()> {
|
||||||
let repo = client.repo();
|
let repo = client.repo();
|
||||||
let run = match (args.run, args.pr) {
|
let (workflow, branch) = match (args.pr, args.run, args.branch.as_deref()) {
|
||||||
(Some(n), _) => n,
|
(Some(pr), _, _) => (args.workflow.clone(), branch_for_pr(client, repo, pr)?),
|
||||||
(None, Some(pr)) => run_number_for_pr(client, repo, pr)?,
|
(_, Some(run), _) => resolve_run(client, repo, run, &args.workflow)?,
|
||||||
(None, None) => bail!("ci-rerun: pass one of --run <n> or --pr <n>"),
|
(_, _, Some(branch)) => (args.workflow.clone(), branch.to_string()),
|
||||||
|
(None, None, None) => {
|
||||||
|
bail!("ci-rerun: pass one of --pr <n>, --run <n>, or --branch <name>")
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let path = match args.job {
|
let path = format!("/repos/{repo}/actions/workflows/{workflow}/dispatches");
|
||||||
Some(job) => format!("/{repo}/actions/runs/{run}/jobs/{job}/rerun"),
|
client
|
||||||
None => format!("/{repo}/actions/runs/{run}/rerun"),
|
.post_no_content(&path, &json!({ "ref": branch }))
|
||||||
};
|
.with_context(|| {
|
||||||
client.post_web_no_content(&path).with_context(|| {
|
format!(
|
||||||
format!(
|
"dispatch workflow {workflow} on {branch} ({repo}) — the workflow \
|
||||||
"rerun run #{run}{} — the run may not exist, or the rerun route may \
|
file or the branch may not exist"
|
||||||
differ on this Forgejo version",
|
)
|
||||||
args.job.map_or_else(String::new, |j| format!(" job {j}"))
|
})?;
|
||||||
)
|
|
||||||
})?;
|
|
||||||
|
|
||||||
match args.job {
|
println!("dispatched a fresh run of {workflow} on {branch} ({repo})");
|
||||||
Some(job) => println!("re-running run #{run} job {job} on {repo}"),
|
|
||||||
None => println!("re-running all jobs of run #{run} on {repo}"),
|
|
||||||
}
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Resolve a PR's latest CI run number from its head sha's combined status.
|
/// Resolve a PR's head branch name (`head.ref`) — the branch a same-repo PR
|
||||||
/// The Actions status `target_url` points at the run page
|
/// pushes to, which is the ref we dispatch the workflow on.
|
||||||
/// (`…/actions/runs/<n>/jobs/<i>`); we parse `<n>` out of it.
|
fn branch_for_pr(client: &Client, repo: &str, pr: u64) -> Result<String> {
|
||||||
fn run_number_for_pr(client: &Client, repo: &str, pr: u64) -> Result<u64> {
|
|
||||||
let pull = client.get_json(&format!("/repos/{repo}/pulls/{pr}"))?;
|
let pull = client.get_json(&format!("/repos/{repo}/pulls/{pr}"))?;
|
||||||
let sha = pull
|
pull.get("head")
|
||||||
.get("head")
|
.and_then(|h| h.get("ref"))
|
||||||
.and_then(|h| h.get("sha"))
|
|
||||||
.and_then(Value::as_str)
|
.and_then(Value::as_str)
|
||||||
.with_context(|| format!("ci-rerun: PR #{pr} has no head.sha"))?;
|
.map(str::to_string)
|
||||||
let combined = client.get_json(&format!("/repos/{repo}/commits/{sha}/status"))?;
|
.with_context(|| format!("ci-rerun: PR #{pr} has no head.ref"))
|
||||||
let statuses = combined
|
|
||||||
.get("statuses")
|
|
||||||
.and_then(Value::as_array)
|
|
||||||
.map(Vec::as_slice)
|
|
||||||
.unwrap_or_default();
|
|
||||||
statuses
|
|
||||||
.iter()
|
|
||||||
.filter_map(|s| s.get("target_url").and_then(Value::as_str))
|
|
||||||
.find_map(run_number_from_url)
|
|
||||||
.with_context(|| {
|
|
||||||
format!("ci-rerun: no Actions run found in PR #{pr}'s CI status (head {sha})")
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Pull the run number out of an Actions run-page URL, i.e. the `<n>` in
|
/// Page the Actions runs list (newest-first) to find the run whose run-page
|
||||||
/// `…/actions/runs/<n>[/…]`. Returns `None` if the URL isn't a run URL.
|
/// `html_url` ends in `/runs/<run-number>`, returning the `(workflow, branch)`
|
||||||
fn run_number_from_url(url: &str) -> Option<u64> {
|
/// to dispatch a fresh run of it. `fallback_workflow` is used when the run
|
||||||
url.split_once("/actions/runs/")
|
/// carries no workflow `path`.
|
||||||
.map(|(_, rest)| rest)
|
fn resolve_run(
|
||||||
.map(|rest| rest.split(['/', '?', '#']).next().unwrap_or(rest))
|
client: &Client,
|
||||||
.and_then(|n| n.parse::<u64>().ok())
|
repo: &str,
|
||||||
|
run_number: u64,
|
||||||
|
fallback_workflow: &str,
|
||||||
|
) -> Result<(String, String)> {
|
||||||
|
const PER_PAGE: u32 = 50;
|
||||||
|
const MAX_PAGES: u32 = 40;
|
||||||
|
for page in 1..=MAX_PAGES {
|
||||||
|
let path = format!("/repos/{repo}/actions/runs?limit={PER_PAGE}&page={page}");
|
||||||
|
let body = client.get_json(&path)?;
|
||||||
|
let runs = body
|
||||||
|
.get("workflow_runs")
|
||||||
|
.and_then(Value::as_array)
|
||||||
|
.cloned()
|
||||||
|
.unwrap_or_default();
|
||||||
|
if runs.is_empty() {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
for run in &runs {
|
||||||
|
if run_number_of(run) == Some(run_number) {
|
||||||
|
return run_dispatch_target(run, fallback_workflow)
|
||||||
|
.with_context(|| format!("ci-rerun: run #{run_number} has no head_branch"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
bail!("ci-rerun: run #{run_number} not found in {repo} (no matching workflow run)");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The per-repo run NUMBER from a run object's `html_url` (`…/runs/<n>` tail),
|
||||||
|
/// matching the `runs/<n>` the UI shows and `pr-status` surfaces.
|
||||||
|
fn run_number_of(run: &Value) -> Option<u64> {
|
||||||
|
run.get("html_url")
|
||||||
|
.and_then(Value::as_str)
|
||||||
|
.and_then(|u| u.rsplit('/').next())
|
||||||
|
.and_then(|s| s.parse::<u64>().ok())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Pull the `(workflow-file, branch)` dispatch target out of a run object:
|
||||||
|
/// `head_branch` is the branch, and the workflow file is the basename of the
|
||||||
|
/// run's `path` (e.g. `.forgejo/workflows/ci.yml` → `ci.yml`), falling back to
|
||||||
|
/// `fallback_workflow` when the run carries no usable `path`. `None` only when
|
||||||
|
/// the run has no `head_branch`.
|
||||||
|
fn run_dispatch_target(run: &Value, fallback_workflow: &str) -> Option<(String, String)> {
|
||||||
|
let branch = run.get("head_branch").and_then(Value::as_str)?;
|
||||||
|
let workflow = run
|
||||||
|
.get("path")
|
||||||
|
.and_then(Value::as_str)
|
||||||
|
.and_then(|p| p.rsplit('/').next())
|
||||||
|
.filter(|s| !s.is_empty())
|
||||||
|
.unwrap_or(fallback_workflow);
|
||||||
|
Some((workflow.to_string(), branch.to_string()))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::run_number_from_url;
|
use super::{run_dispatch_target, run_number_of};
|
||||||
|
use serde_json::json;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn parses_run_number_from_actions_url() {
|
fn parses_run_number_from_html_url() {
|
||||||
|
let run = json!({ "html_url": "http://forge/h/h/actions/runs/750" });
|
||||||
|
assert_eq!(run_number_of(&run), Some(750));
|
||||||
|
let run = json!({ "html_url": "https://forge/o/r/actions/runs/42" });
|
||||||
|
assert_eq!(run_number_of(&run), Some(42));
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
run_number_from_url("http://forge/h/h/actions/runs/750/jobs/0"),
|
run_number_of(&json!({ "html_url": "http://forge/o/r/x" })),
|
||||||
Some(750)
|
None
|
||||||
);
|
);
|
||||||
|
assert_eq!(run_number_of(&json!({})), None);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn extracts_workflow_and_branch() {
|
||||||
|
let run = json!({
|
||||||
|
"head_branch": "atlas/foo",
|
||||||
|
"path": ".forgejo/workflows/ci.yml",
|
||||||
|
});
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
run_number_from_url("https://forge/o/r/actions/runs/42"),
|
run_dispatch_target(&run, "fallback.yml"),
|
||||||
Some(42)
|
Some(("ci.yml".to_string(), "atlas/foo".to_string()))
|
||||||
);
|
);
|
||||||
assert_eq!(run_number_from_url("http://forge/o/r/commit/abc"), None);
|
}
|
||||||
assert_eq!(run_number_from_url("/actions/runs/notanumber/x"), None);
|
|
||||||
|
#[test]
|
||||||
|
fn falls_back_to_default_workflow_without_path() {
|
||||||
|
let run = json!({ "head_branch": "b" });
|
||||||
|
assert_eq!(
|
||||||
|
run_dispatch_target(&run, "fallback.yml"),
|
||||||
|
Some(("fallback.yml".to_string(), "b".to_string()))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn no_branch_means_no_target() {
|
||||||
|
assert_eq!(run_dispatch_target(&json!({}), "ci.yml"), None);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue