Rebase ci validate onto bare repo via sha
quire ci validate now works against the git repo in the current
directory, reading ci.fnl from a commit SHA (default HEAD). Removes
validate_file from Ci since all validation goes through git show.
Discovers the repo via git rev-parse --show-toplevel.
Assisted-by: GLM-5.1 via pi
diff --git a/src/bin/quire/commands/ci.rs b/src/bin/quire/commands/ci.rs
index 7480dd9..a286685 100644
--- a/src/bin/quire/commands/ci.rs
+++ b/src/bin/quire/commands/ci.rs
@@ -1,14 +1,22 @@
-use miette::Result;
+use std::path::PathBuf;
-use quire::ci::{Ci, ValidationError};
+use miette::{IntoDiagnostic, Result};
+use quire::ci::Ci;
-/// Validate a ci.fnl file without executing any jobs.
+/// Validate a repo's ci.fnl without executing any jobs.
///
-/// Evaluates the Fennel source to extract the job registration table,
-/// then runs the four structural validations. Prints each job found
-/// and any validation errors.
-pub async fn validate(path: &std::path::Path) -> Result<()> {
- let result = Ci::validate_file(path)?;
+/// Evaluates the Fennel source at the given SHA (or HEAD) to extract
+/// the job registration table, then runs the four structural validations.
+/// Prints each job found and any validation errors.
+pub async fn validate(sha: Option<&str>) -> Result<()> {
+ let repo_path = discover_repo()?;
+ let sha = sha.unwrap_or("HEAD");
+ let ci = Ci::new(repo_path);
+
+ let Some(result) = ci.eval(sha)? else {
+ println!("No ci.fnl found at {sha}.");
+ return Ok(());
+ };
if result.jobs.is_empty() {
println!("No jobs registered.");
@@ -29,16 +37,16 @@ pub async fn validate(path: &std::path::Path) -> Result<()> {
println!("\nValidation errors:");
for err in &errors {
let label = match err {
- ValidationError::Cycle { cycle_jobs } => {
+ quire::ci::ValidationError::Cycle { cycle_jobs } => {
format!("cycle: {}", cycle_jobs.join(" → "))
}
- ValidationError::EmptyInputs { job_id } => {
+ quire::ci::ValidationError::EmptyInputs { job_id } => {
format!("{job_id}: empty inputs")
}
- ValidationError::Unreachable { job_id } => {
+ quire::ci::ValidationError::Unreachable { job_id } => {
format!("{job_id}: unreachable from any source ref")
}
- ValidationError::ReservedSlash { job_id } => {
+ quire::ci::ValidationError::ReservedSlash { job_id } => {
format!("{job_id}: '/' in job id")
}
};
@@ -50,3 +58,19 @@ pub async fn validate(path: &std::path::Path) -> Result<()> {
Ok(())
}
+
+/// Find the git repo root from the current working directory.
+fn discover_repo() -> Result<PathBuf> {
+ let output = std::process::Command::new("git")
+ .args(["rev-parse", "--show-toplevel"])
+ .output()
+ .into_diagnostic()?;
+
+ if !output.status.success() {
+ let stderr = String::from_utf8_lossy(&output.stderr);
+ miette::bail!("not in a git repository: {stderr}");
+ }
+
+ let path = String::from_utf8(output.stdout).into_diagnostic()?;
+ Ok(PathBuf::from(path.trim()))
+}
diff --git a/src/bin/quire/main.rs b/src/bin/quire/main.rs
index 9c3621b..de0eedc 100644
--- a/src/bin/quire/main.rs
+++ b/src/bin/quire/main.rs
@@ -78,11 +78,11 @@ enum RepoCommands {
#[derive(Subcommand)]
enum CiCommands {
- /// Validate a ci.fnl file without running any jobs.
+ /// Validate a repo's ci.fnl without running any jobs.
Validate {
- /// Path to the ci.fnl file to validate.
- #[arg(default_value = ".quire/ci.fnl")]
- path: std::path::PathBuf,
+ /// Commit SHA to validate. Defaults to HEAD.
+ #[arg(short, long)]
+ sha: Option<String>,
},
}
@@ -152,7 +152,7 @@ async fn main() -> Result<()> {
RepoCommands::Rm { name } => commands::repo::rm(&quire, &name).await?,
},
Commands::Ci { command } => match command {
- CiCommands::Validate { path } => commands::ci::validate(&path).await?,
+ CiCommands::Validate { sha } => commands::ci::validate(sha.as_deref()).await?,
},
}
diff --git a/src/ci/mod.rs b/src/ci/mod.rs
index 189d65d..1495d78 100644
--- a/src/ci/mod.rs
+++ b/src/ci/mod.rs
@@ -6,7 +6,7 @@ pub mod run;
pub use graph::{EvalResult, JobDef, ValidationError, eval_ci, validate};
pub use run::{Run, RunMeta, RunState, RunTimes, Runs};
-use std::path::{Path, PathBuf};
+use std::path::PathBuf;
use crate::Result;
use crate::event::{PushEvent, PushRef};
@@ -25,7 +25,7 @@ pub struct Ci {
}
impl Ci {
- pub(crate) fn new(repo_path: PathBuf) -> Self {
+ pub fn new(repo_path: PathBuf) -> Self {
Self { repo_path }
}
@@ -58,16 +58,6 @@ impl Ci {
Ok(Some(result))
}
- /// Evaluate a ci.fnl file from disk and validate the job graph.
- pub fn validate_file(path: &Path) -> Result<EvalResult> {
- let source = fs_err::read_to_string(path)?;
- let name = path.display().to_string();
- let fennel = crate::fennel::Fennel::new()?;
- let result = eval_ci(&fennel, &source, &name)?;
- validate(&result.jobs)?;
- Ok(result)
- }
-
/// Read the contents of `.quire/ci.fnl` at a given commit SHA.
///
/// Returns `Ok(None)` if the file does not exist at that commit,