Radish alpha
r
rad:zwTxygwuz5LDGBq255RA2CbNGrz8
Radicle CI broker
Radicle
Git
has_file
Merged liw opened 1 year ago

Signed-off-by: Lars Wirzenius liw@liw.fi

2 files changed +92 -3 c7ae4fbb 623e590c
modified 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
modified 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<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>,