20260605 ROA增量验证和rsync host失败短路

This commit is contained in:
2026-06-06 15:16:38 +08:00
parent e597c7c124
commit 4045f9a3a5
27 changed files with 2850 additions and 221 deletions

View File

@@ -50,6 +50,16 @@ pub trait RsyncFetcher: Send + Sync {
fn dedup_key(&self, rsync_base_uri: &str) -> String {
normalize_rsync_base_uri(rsync_base_uri)
}
/// Return an optional failure-only deduplication key.
///
/// This key is only used to short-circuit repeated failed rsync fallbacks. It must
/// not be used to reuse successful fetch results unless it is identical to
/// `dedup_key`, because a successful fetch for one publication point does not imply
/// that another publication point under the same host has been fetched.
fn failure_dedup_key(&self, _rsync_base_uri: &str) -> Option<String> {
None
}
}
/// A simple "rsync" implementation backed by a local directory.

View File

@@ -15,23 +15,25 @@ use crate::fetch::rsync::{
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum RsyncScopePolicy {
Host,
PublicationPoint,
ModuleRoot,
}
impl Default for RsyncScopePolicy {
fn default() -> Self {
Self::PublicationPoint
Self::Host
}
}
impl RsyncScopePolicy {
pub fn parse_cli_value(value: &str) -> Result<Self, String> {
match value {
"host" => Ok(Self::Host),
"publication-point" => Ok(Self::PublicationPoint),
"module-root" => Ok(Self::ModuleRoot),
_ => Err(format!(
"invalid --rsync-scope: {value}; expected publication-point or module-root"
"invalid --rsync-scope: {value}; expected host, publication-point, or module-root"
)),
}
}
@@ -296,7 +298,9 @@ impl SystemRsyncFetcher {
fn scope_fetch_uri(&self, rsync_base_uri: &str) -> String {
match self.config.scope_policy {
RsyncScopePolicy::PublicationPoint => normalize_rsync_base_uri(rsync_base_uri),
RsyncScopePolicy::Host | RsyncScopePolicy::PublicationPoint => {
normalize_rsync_base_uri(rsync_base_uri)
}
RsyncScopePolicy::ModuleRoot => rsync_module_root_uri(rsync_base_uri)
.unwrap_or_else(|| normalize_rsync_base_uri(rsync_base_uri)),
}
@@ -348,6 +352,21 @@ impl RsyncFetcher for SystemRsyncFetcher {
fn dedup_key(&self, rsync_base_uri: &str) -> String {
self.scope_fetch_uri(rsync_base_uri)
}
fn failure_dedup_key(&self, rsync_base_uri: &str) -> Option<String> {
match self.config.scope_policy {
RsyncScopePolicy::Host => rsync_host_scope_uri(rsync_base_uri),
RsyncScopePolicy::PublicationPoint | RsyncScopePolicy::ModuleRoot => None,
}
}
}
fn rsync_host_scope_uri(rsync_base_uri: &str) -> Option<String> {
let parsed = url::Url::parse(rsync_base_uri).ok()?;
if parsed.scheme() != "rsync" {
return None;
}
Some(format!("rsync://{}/", parsed.host_str()?))
}
struct TempDir {
@@ -535,12 +554,57 @@ mod tests {
}
#[test]
fn system_rsync_dedup_key_uses_publication_point_scope_by_default() {
fn rsync_host_scope_uri_returns_host_only() {
assert_eq!(
rsync_host_scope_uri("rsync://example.net/repo/ta/ca/publication-point/"),
Some("rsync://example.net/".to_string())
);
assert_eq!(rsync_host_scope_uri("https://example.net/repo"), None);
}
#[test]
fn system_rsync_dedup_key_uses_host_scope_by_default() {
let fetcher = SystemRsyncFetcher::new(SystemRsyncConfig::default());
assert_eq!(
fetcher.dedup_key("rsync://example.net/repo/ta/ca/publication-point/"),
"rsync://example.net/repo/ta/ca/publication-point/"
);
assert_eq!(
fetcher.failure_dedup_key("rsync://example.net/repo/ta/ca/publication-point/"),
Some("rsync://example.net/".to_string())
);
}
#[test]
fn system_rsync_host_scope_does_not_widen_success_fetch_scope() {
let fetcher = SystemRsyncFetcher::new(SystemRsyncConfig {
scope_policy: RsyncScopePolicy::Host,
..SystemRsyncConfig::default()
});
assert_eq!(
fetcher.dedup_key("rsync://example.net/repo/ta/ca/publication-point/"),
"rsync://example.net/repo/ta/ca/publication-point/"
);
assert_eq!(
fetcher.scope_fetch_uri("rsync://example.net/repo/ta/ca/publication-point/"),
"rsync://example.net/repo/ta/ca/publication-point/"
);
assert_eq!(
fetcher.failure_dedup_key("rsync://example.net/repo/ta/ca/publication-point/"),
Some("rsync://example.net/".to_string())
);
}
#[test]
fn system_rsync_dedup_key_uses_publication_point_scope_when_configured() {
let fetcher = SystemRsyncFetcher::new(SystemRsyncConfig {
scope_policy: RsyncScopePolicy::PublicationPoint,
..SystemRsyncConfig::default()
});
assert_eq!(
fetcher.dedup_key("rsync://example.net/repo/ta/ca/publication-point/"),
"rsync://example.net/repo/ta/ca/publication-point/"
);
}
#[test]