use std::fmt;
use crate::object_store::ObjectMeta;
pub(crate) const PROTECTED_MARKER_SEGMENT: &str = "PROTECTED#";
pub(crate) fn is_protected_marker_segment(last_segment: &str) -> bool {
last_segment == PROTECTED_MARKER_SEGMENT
}
pub(crate) fn entries_have_protected_marker(entries: &[ObjectMeta]) -> bool {
entries.iter().any(|entry| {
entry
.key
.rsplit_once('/')
.is_some_and(|(_, last)| is_protected_marker_segment(last))
})
}
pub(crate) fn join(prefix: Option<&str>, suffix: &str) -> String {
match prefix {
Some(p) if !p.is_empty() => {
if suffix.is_empty() {
format!("{p}/")
} else {
format!("{p}/{suffix}")
}
}
_ => suffix.to_owned(),
}
}
pub(crate) fn ref_listing_prefix(prefix: Option<&str>, ref_path: &str) -> String {
debug_assert!(
!ref_path.is_empty(),
"ref_listing_prefix: ref_path is empty"
);
debug_assert!(
!ref_path.ends_with('/'),
"ref_listing_prefix: ref_path already ends with '/' (helper appends one)"
);
match prefix {
Some(p) if !p.is_empty() => format!("{p}/{ref_path}/"),
_ => format!("{ref_path}/"),
}
}
pub(crate) fn is_valid_bundle_stem(stem: &str) -> bool {
stem.len() == 40 && stem.bytes().all(|b| matches!(b, b'0'..=b'9' | b'a'..=b'f'))
}
pub(crate) fn bundle_key(
prefix: Option<&str>,
ref_name: impl fmt::Display,
sha: impl fmt::Display,
) -> String {
match prefix {
Some(p) if !p.is_empty() => format!("{p}/{ref_name}/{sha}.bundle"),
_ => format!("{ref_name}/{sha}.bundle"),
}
}
#[cfg(test)]
mod tests {
use super::{bundle_key, is_valid_bundle_stem, join, ref_listing_prefix};
#[test]
fn joins_prefix_and_suffix_with_slash() {
assert_eq!(join(Some("acme"), "HEAD"), "acme/HEAD");
assert_eq!(
join(Some("acme/repo"), "refs/heads/main/"),
"acme/repo/refs/heads/main/"
);
}
#[test]
fn empty_prefix_yields_suffix_verbatim() {
assert_eq!(join(Some(""), "HEAD"), "HEAD");
assert_eq!(join(None, "HEAD"), "HEAD");
assert_eq!(join(Some(""), "refs/heads/main/"), "refs/heads/main/");
assert_eq!(join(None, "refs/heads/main/"), "refs/heads/main/");
}
#[test]
fn empty_suffix_yields_listing_prefix_with_trailing_slash() {
assert_eq!(join(Some("acme"), ""), "acme/");
}
#[test]
fn empty_prefix_and_suffix_yields_empty_string() {
assert_eq!(join(Some(""), ""), "");
assert_eq!(join(None, ""), "");
}
#[test]
fn bundle_key_with_prefix() {
let sha = "0123456789abcdef0123456789abcdef01234567";
assert_eq!(
bundle_key(Some("acme"), "refs/heads/main", sha),
format!("acme/refs/heads/main/{sha}.bundle"),
);
}
#[test]
fn bundle_key_without_prefix() {
let sha = "0123456789abcdef0123456789abcdef01234567";
assert_eq!(
bundle_key(None, "refs/heads/main", sha),
format!("refs/heads/main/{sha}.bundle"),
);
}
#[test]
fn is_valid_bundle_stem_accepts_lower_hex_40() {
assert!(is_valid_bundle_stem(
"0123456789abcdef0123456789abcdef01234567"
));
assert!(is_valid_bundle_stem(
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
));
}
#[test]
fn is_valid_bundle_stem_rejects_wrong_length_and_charset() {
assert!(!is_valid_bundle_stem(
"0123456789abcdef0123456789abcdef0123456"
));
assert!(!is_valid_bundle_stem(
"0123456789abcdef0123456789abcdef012345670"
));
assert!(!is_valid_bundle_stem(
"0123456789ABCDEF0123456789abcdef01234567"
));
assert!(!is_valid_bundle_stem(
"not-a-valid-sha-not-a-valid-sha-not-aval"
));
assert!(!is_valid_bundle_stem(""));
}
#[test]
fn ref_listing_prefix_with_prefix() {
assert_eq!(
ref_listing_prefix(Some("acme"), "refs/heads/main"),
"acme/refs/heads/main/",
);
}
#[test]
fn ref_listing_prefix_without_prefix() {
assert_eq!(
ref_listing_prefix(None, "refs/heads/main"),
"refs/heads/main/"
);
assert_eq!(
ref_listing_prefix(Some(""), "refs/heads/main"),
"refs/heads/main/",
);
}
#[test]
fn ref_listing_prefix_matches_join_with_trailing_slash() {
for prefix in [None, Some(""), Some("acme"), Some("acme/repo")] {
for ref_path in ["refs/heads/main", "refs/heads/feature/x"] {
assert_eq!(
ref_listing_prefix(prefix, ref_path),
join(prefix, &format!("{ref_path}/")),
);
}
}
}
#[test]
#[should_panic(expected = "ref_path is empty")]
fn ref_listing_prefix_panics_on_empty_ref_path() {
let _ = ref_listing_prefix(Some("acme"), "");
}
#[test]
#[should_panic(expected = "ref_path already ends with '/'")]
fn ref_listing_prefix_panics_on_trailing_slash_ref_path() {
let _ = ref_listing_prefix(Some("acme"), "refs/heads/main/");
}
#[test]
fn bundle_key_empty_prefix_matches_none() {
let sha = "0123456789abcdef0123456789abcdef01234567";
assert_eq!(
bundle_key(Some(""), "refs/heads/main", sha),
bundle_key(None, "refs/heads/main", sha),
);
}
}