Files
clawsync/crates/clawsync-onion/src/selector.rs
osobhandClaude Sonnet 4.6 1c107fe58a Apply rustfmt to entire workspace
Runs cargo fmt --all; all 573 tests still passing, clippy still clean.
No logic changes — formatting only.

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
2026-04-04 20:31:33 -05:00

149 lines
5.1 KiB
Rust

//! Sync selector: filter which revisions to include in a sync operation.
//!
//! Used with `--branch` CLI flag to sync only a subset of the DAG.
use clawhdf5_onion::format::NO_PARENT;
use clawhdf5_onion::writer::OnionFile;
use crate::error::SyncOnionError;
use crate::packet::OnionLayerPacket;
/// Selector controlling which revisions participate in a sync.
#[derive(Debug, Clone)]
pub enum SyncSelector {
/// All revisions on all branches (default).
All,
/// Only revisions on the named branch.
Branch(String),
/// Only revisions up to (and including) a specific revision number.
UpTo(u64),
/// Only the named branch, up to a specific revision.
BranchAt(String, u64),
}
/// Filter a set of packets according to `selector`, keeping only those that
/// match. This is applied on the sender side before transmission.
pub fn filter_packets(
packets: Vec<OnionLayerPacket>,
selector: &SyncSelector,
onion: &OnionFile,
) -> Result<Vec<OnionLayerPacket>, SyncOnionError> {
match selector {
SyncSelector::All => Ok(packets),
SyncSelector::Branch(name) => {
let branch = onion
.branch_by_name(name)
.ok_or_else(|| SyncOnionError::BranchNotFound(name.clone()))?;
let branch_id = branch.id;
Ok(packets
.into_iter()
.filter(|p| p.branch_id == branch_id)
.collect())
}
SyncSelector::UpTo(max_rev) => Ok(packets
.into_iter()
.filter(|p| p.revision <= *max_rev)
.collect()),
SyncSelector::BranchAt(name, max_rev) => {
let branch = onion
.branch_by_name(name)
.ok_or_else(|| SyncOnionError::BranchNotFound(name.clone()))?;
let branch_id = branch.id;
Ok(packets
.into_iter()
.filter(|p| p.branch_id == branch_id && p.revision <= *max_rev)
.collect())
}
}
}
/// Compute the remote HEAD for a given selector on the local `OnionFile`.
///
/// Returns [`NO_PARENT`] if no revisions match (full sync needed).
pub fn remote_head_for_selector(_selector: &SyncSelector, remote_revision_count: u64) -> u64 {
// For a simple linear sync, remote HEAD = remote_revision_count - 1
// (or NO_PARENT if empty).
if remote_revision_count == 0 {
NO_PARENT
} else {
remote_revision_count - 1
}
}
// ─────────────────────────────────────────────────────────────────────────────
// Tests
// ─────────────────────────────────────────────────────────────────────────────
#[cfg(test)]
mod tests {
use super::*;
use crate::differ::diff_revisions;
use clawhdf5_onion::format::NO_PARENT;
use clawhdf5_onion::writer::OnionFile;
use tempfile::NamedTempFile;
fn make_onion(n: u8) -> OnionFile {
let f = NamedTempFile::new().unwrap();
let h5 = f.path().with_extension("h5");
std::fs::write(&h5, b"\x89HDF\r\n\x1a\n").unwrap();
let mut onion = OnionFile::create(&h5, 4096).unwrap();
for i in 0..n {
let mut s = onion.begin_session(None).unwrap();
s.record_page(0, &vec![i; 4096]);
onion.commit_session(s, None).unwrap();
}
onion
}
#[test]
fn selector_all_passes_everything() {
let onion = make_onion(4);
let packets = diff_revisions(&onion, NO_PARENT).unwrap();
let filtered = filter_packets(packets, &SyncSelector::All, &onion).unwrap();
assert_eq!(filtered.len(), 4);
}
#[test]
fn selector_up_to() {
let onion = make_onion(5);
let packets = diff_revisions(&onion, NO_PARENT).unwrap();
let filtered = filter_packets(packets, &SyncSelector::UpTo(2), &onion).unwrap();
assert_eq!(filtered.len(), 3); // revisions 0, 1, 2
}
#[test]
fn selector_branch_main() {
let onion = make_onion(3);
let packets = diff_revisions(&onion, NO_PARENT).unwrap();
// All revisions are on main (branch_id = 0)
let filtered =
filter_packets(packets, &SyncSelector::Branch("main".to_string()), &onion).unwrap();
assert_eq!(filtered.len(), 3);
}
#[test]
fn selector_branch_nonexistent_errors() {
let onion = make_onion(2);
let packets = diff_revisions(&onion, NO_PARENT).unwrap();
let result = filter_packets(
packets,
&SyncSelector::Branch("no-such".to_string()),
&onion,
);
assert!(matches!(result, Err(SyncOnionError::BranchNotFound(_))));
}
#[test]
fn remote_head_empty() {
assert_eq!(remote_head_for_selector(&SyncSelector::All, 0), NO_PARENT);
}
#[test]
fn remote_head_non_empty() {
assert_eq!(remote_head_for_selector(&SyncSelector::All, 5), 4);
}
}