feat: HasFile event filter predicte

Signed-off-by: Lars Wirzenius <liw@liw.fi>
This commit is contained in:
Lars Wirzenius
2025-02-04 18:46:19 +02:00
parent c7ae4fbb23
commit 623e590c2c
2 changed files with 92 additions and 3 deletions
+49
View File
@@ -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
+43 -3
View File
@@ -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<String, Box<dyn std::error::Er
Ok(def)
}
fn has_file(repo_id: &RepoId, oid: &Oid, filename: &Path) -> bool {
fn helper(
repo_id: &RepoId,
oid: Oid,
filename: &Path,
) -> Result<bool, Box<dyn std::error::Error>> {
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<EventFilter>,