Radish alpha
h
Radicle Heartwood Protocol & Stack
Radicle
Git (anonymous pull)
Log in to clone via SSH
radicle/git/canonical: Fix glob rewrite rule for trailing asterisk
Adrian Duke committed 3 days ago
commit fb40bdc0ac152dedbfec27f33d6e417462be7e75
parent 455138f4a7114437b5ca686991227f3531cad915
3 files changed +86 -13
modified CHANGELOG.md
@@ -60,6 +60,13 @@ COB type names and payload IDs remain unchanged for backwards compatibility.
- Delete invalid node addresses, of the IPv6 type, from the SQLite address book.
  Entries deleted include ones that have an address of the form `[]:port`, or
  that have an invalid address within the `[]`.
+
- Correctly handle the canonical reference rules that have a glob star suffix,
+
  e.g., `refs/heads/main*`, `refs/heads/main-*`, etc.
+
  Previously, these would be expanded into sub-component searches. For example,
+
  in the previous examples, the search would be expanded to
+
  `refs/heads/main**/*` and `refs/heads/main-**/*`.
+
  Now, the rule will match by prefix, e.g. `refs/heads/main-*` will match
+
  `refs/heads/main-a`, `refs/heads/main-b`, `refs/heads/main-deadbeef`, etc.

## 1.8.0

modified crates/radicle/src/git/canonical/rules.rs
@@ -54,25 +54,18 @@ fn matches(pattern: &RawPattern, refname: &Qualified) -> bool {
    // that namespace, even if they are further down the hierarchy.
    // Thus, the following rules are applied:
    //
-
    //   - a trailing `*` changes to `**/*`
+
    //   - a trailing `*` matches anything that starts with the prefix
    //   - a `*` in between path components changes to `**`
-
    let spec = match pattern.as_str().split_once(ASTERISK) {
-
        None => pattern.to_string(),
-
        // Expand `refs/tags/*` to `refs/tags/**/*`
-
        Some((prefix, "")) => {
-
            let mut spec = prefix.to_string();
-
            spec.push_str("**/*");
-
            spec
-
        }
-
        // Expand `refs/tags/*/v1.0` to `refs/tags/**/v1.0`
+
    match pattern.as_str().split_once(ASTERISK) {
+
        None => pattern.as_str() == refname.as_str(),
+
        Some((prefix, "")) => refname.as_str().starts_with(prefix),
        Some((prefix, suffix)) => {
            let mut spec = prefix.to_string();
            spec.push_str("**");
            spec.push_str(suffix);
-
            spec
+
            fast_glob::glob_match(&spec, refname.as_str())
        }
-
    };
-
    fast_glob::glob_match(&spec, refname.as_str())
+
    }
}

/// Patterns are ordered by their specificity.
modified crates/radicle/src/git/canonical/rules/test.rs
@@ -477,3 +477,76 @@ fn special_branches() {
    assert!(Pattern::new((*SIGREFS_PARENT).clone().into()).is_err());
    assert!(Pattern::new((*IDENTITY_ROOT).clone().into()).is_err());
}
+

+
#[test]
+
fn matches_expands_globs_appropriately() {
+
    let exact = qualified_pattern!("refs/heads/main");
+
    assert!(matches(&exact, &git::fmt::qualified!("refs/heads/main")));
+
    assert!(!matches(&exact, &git::fmt::qualified!("refs/heads/main-2")));
+
    assert!(!matches(&exact, &git::fmt::qualified!("refs/heads/other")));
+

+
    let trailing_slash = qualified_pattern!("refs/heads/*");
+
    assert!(matches(
+
        &trailing_slash,
+
        &git::fmt::qualified!("refs/heads/main")
+
    ));
+
    assert!(matches(
+
        &trailing_slash,
+
        &git::fmt::qualified!("refs/heads/feature/1")
+
    ));
+
    assert!(!matches(
+
        &trailing_slash,
+
        &git::fmt::qualified!("refs/tags/main")
+
    ));
+

+
    let trailing_text = qualified_pattern!("refs/heads/feature-*");
+
    assert!(matches(
+
        &trailing_text,
+
        &git::fmt::qualified!("refs/heads/feature-")
+
    ));
+
    assert!(matches(
+
        &trailing_text,
+
        &git::fmt::qualified!("refs/heads/feature-1")
+
    ));
+
    assert!(matches(
+
        &trailing_text,
+
        &git::fmt::qualified!("refs/heads/feature-1/sub")
+
    ));
+
    assert!(!matches(
+
        &trailing_text,
+
        &git::fmt::qualified!("refs/heads/feature")
+
    ));
+

+
    // Because `*` expands to `**`, it matches zero or more path components.
+
    let middle = qualified_pattern!("refs/heads/*/main");
+
    assert!(matches(
+
        &middle,
+
        &git::fmt::qualified!("refs/heads/alice/main")
+
    ));
+
    assert!(matches(
+
        &middle,
+
        &git::fmt::qualified!("refs/heads/alice/bob/main")
+
    ));
+

+
    //
+
    // NOTE(Ade): In git this won't match as glob '*' isn't inclusive of zero matches like `**`
+
    // See: https://git.kernel.org/pub/scm/git/git.git/tree/refspec.c?h=v2.54.0&id=94f057755b7941b321fd11fec1b2e3ca5313a4e0#n298
+
    //
+
    assert!(matches(&middle, &git::fmt::qualified!("refs/heads/main")));
+

+
    assert!(!matches(
+
        &middle,
+
        &git::fmt::qualified!("refs/heads/alice/dev")
+
    ));
+

+
    // HardenedBSD glob expansion issue
+
    let hbsd = qualified_pattern!("refs/heads/quarterly/hardened/15-stable/main*");
+
    assert!(matches(
+
        &hbsd,
+
        &git::fmt::qualified!("refs/heads/quarterly/hardened/15-stable/main-2026q2")
+
    ));
+
    assert!(matches(
+
        &hbsd,
+
        &git::fmt::qualified!("refs/heads/quarterly/hardened/15-stable/main/2026q2")
+
    ));
+
}