diff --git a/ci-broker.md b/ci-broker.md index bea74d56..dbbaed8a 100644 --- a/ci-broker.md +++ b/ci-broker.md @@ -236,6 +236,8 @@ The Git repository must exist. ~~~scenario then directory reppy exists then directory reppy/.git exists +then file reppy/file.dat exists +then file reppy/foobar does not exist when I run, in reppy, git show then stdout matches regex ^commit ~~~ @@ -1230,6 +1232,53 @@ triggers: ~~~ +## Filter predicate `HasFile` + +_Want:_ We can allow an event if its commit contains a file or +directory by this name. + +_Why:_ This is so that the user can choose a suitable adapter. + +~~~scenario +given a Radicle node, with CI configured with broker.yaml and adapter dummy.sh +given a Git repository xyzzy in the Radicle node + +given file config.yaml from filter-hasfile-missing.yaml +when I run cibtool --db ci-broker.db event add --repo xyzzy --ref main --commit HEAD --kind branch-created +when I run ./env.sh cib --config config.yaml queued +when I run cibtool --db ci-broker.db run list --json +then stdout doesn't contain "main" + +given file config.yaml from filter-hasfile.yaml +when I run cibtool --db ci-broker.db event add --repo xyzzy --ref main --commit HEAD --kind branch-created +when I run ./env.sh cib --config config.yaml queued +when I run cibtool --db ci-broker.db run list --json +then stdout contains "main" +~~~ + +~~~{#filter-hasfile.yaml .file .json} +db: ci-broker.db +adapters: + default: + command: ./adapter.sh +triggers: + - adapter: default + filters: + - !HasFile "file.dat" +~~~ + +~~~{#filter-hasfile-missing.yaml .file .json} +db: ci-broker.db +adapters: + default: + command: ./adapter.sh +triggers: + - adapter: default + filters: + - !HasFile "does-not-exist" +~~~ + + # Acceptance criteria for test tooling The event synthesizer is a helper to feed the CI broker node events in diff --git a/src/filter.rs b/src/filter.rs index 60d3c02f..81e6c990 100644 --- a/src/filter.rs +++ b/src/filter.rs @@ -4,7 +4,7 @@ use serde::{Deserialize, Serialize}; use radicle::{ cob::patch::PatchId, - git::RefString, + git::{raw::ObjectType, RefString}, node::NodeId, prelude::{Profile, RepoId}, storage::git::Repository, @@ -79,6 +79,9 @@ pub enum EventFilter { /// Change originated from specific node. Node(NodeId), + /// Commit in change contains a file or directory with this name. + HasFile(PathBuf), + /// Allow any event. Allow, @@ -117,19 +120,21 @@ impl EventFilter { from_node, repo, branch, - .. + tip, }) => match self { Self::Node(wanted) => from_node == wanted, Self::Repository(wanted) => repo == wanted, Self::Branch(wanted) => branch == wanted, Self::DefaultBranch => is_default_branch(repo, branch), Self::BranchCreated => true, + Self::HasFile(wanted) => has_file(repo, tip, wanted), _ => false, }, CiEvent::V1(CiEventV1::BranchUpdated { from_node, repo, branch, + tip, .. }) => match self { Self::Node(wanted) => from_node == wanted, @@ -137,43 +142,49 @@ impl EventFilter { Self::Branch(wanted) => branch == wanted, Self::DefaultBranch => is_default_branch(repo, branch), Self::BranchUpdated => true, + Self::HasFile(wanted) => has_file(repo, tip, wanted), _ => false, }, CiEvent::V1(CiEventV1::BranchDeleted { from_node, repo, branch, - .. + tip, }) => match self { Self::Node(wanted) => from_node == wanted, Self::Repository(wanted) => repo == wanted, Self::Branch(wanted) => branch == wanted, Self::DefaultBranch => is_default_branch(repo, branch), Self::BranchDeleted => true, + Self::HasFile(wanted) => has_file(repo, tip, wanted), _ => false, }, CiEvent::V1(CiEventV1::PatchCreated { from_node, repo, patch, + new_tip, .. }) => match self { Self::Node(wanted) => from_node == wanted, Self::Repository(wanted) => repo == wanted, Self::Patch(wanted) => *patch == PatchId::from(wanted), Self::PatchCreated => true, + Self::HasFile(wanted) => has_file(repo, new_tip, wanted), _ => false, }, CiEvent::V1(CiEventV1::PatchUpdated { from_node, repo, patch, + new_tip, .. }) => match self { Self::Node(wanted) => from_node == wanted, Self::Repository(wanted) => repo == wanted, Self::Patch(wanted) => *patch == PatchId::from(wanted), Self::PatchUpdated => true, + Self::HasFile(wanted) => has_file(repo, new_tip, wanted), _ => false, }, }; @@ -212,6 +223,35 @@ fn get_default_branch(repo_id: &RepoId) -> Result bool { + fn helper( + repo_id: &RepoId, + oid: Oid, + filename: &Path, + ) -> Result> { + let profile = Profile::load()?; + let repo = Repository::open(profile.storage.path().join(repo_id.canonical()), *repo_id)?; + + let obj = repo.backend.find_object(*oid, Some(ObjectType::Commit))?; + let commit = if let Ok(commit) = obj.into_commit() { + commit + } else { + return Ok(false); + }; + let tree = commit.tree()?; + let entry = if let Ok(entry) = tree.get_path(filename) { + entry + } else { + return Ok(false); + }; + let obj = entry.to_object(&repo.backend)?; + let ok = obj.into_blob().is_ok(); + Ok(ok) + } + + helper(repo_id, *oid, filename).unwrap_or(false) +} + #[derive(Deserialize)] struct Filters { filters: Vec,