use std::{collections::HashSet, path::Path};
use anyhow::{Result, anyhow};
use objects::object::{ChangeId, ContentHash};
use repo::Repository;
pub struct LocalSync {
source: Repository,
}
impl LocalSync {
pub fn open(path: &Path) -> Result<Self> {
let source = Repository::open(path)?;
Ok(Self { source })
}
pub fn source(&self) -> &Repository {
&self.source
}
pub fn list_threads(&self) -> Result<Vec<(String, ChangeId)>> {
let mut threads = Vec::new();
for thread in self.source.refs().list_threads()? {
if let Some(state_id) = self.source.refs().get_thread(&thread)? {
threads.push((thread, state_id));
}
}
Ok(threads)
}
pub fn list_markers(&self) -> Result<Vec<(String, ChangeId)>> {
let mut markers = Vec::new();
for marker in self.source.refs().list_markers()? {
if let Some(state_id) = self.source.refs().get_marker(&marker)? {
markers.push((marker, state_id));
}
}
Ok(markers)
}
pub fn fetch_state(&self, target: &Repository, state_id: &ChangeId) -> Result<usize> {
let mut copied = 0;
let mut visited = HashSet::new();
self.copy_state_recursive(target, state_id, &mut visited, &mut copied, None)?;
Ok(copied)
}
pub fn fetch_state_with_depth(
&self,
target: &Repository,
state_id: &ChangeId,
depth: u32,
) -> Result<usize> {
let mut copied = 0;
let mut visited = HashSet::new();
self.copy_state_recursive(target, state_id, &mut visited, &mut copied, Some(depth))?;
Ok(copied)
}
fn copy_state_recursive(
&self,
target: &Repository,
state_id: &ChangeId,
visited: &mut HashSet<ChangeId>,
copied: &mut usize,
max_depth: Option<u32>,
) -> Result<()> {
if visited.contains(state_id) {
return Ok(());
}
visited.insert(*state_id);
if target.store().has_state(state_id)? {
return Ok(());
}
let state = self
.source
.store()
.get_state(state_id)?
.ok_or_else(|| anyhow!("State {} not found in source", state_id))?;
self.copy_tree_recursive(target, &state.tree, copied)?;
if let Some(provenance_root) = state.provenance {
self.copy_tree_recursive(target, &provenance_root, copied)?;
}
if let Some(context_root) = state.context {
self.copy_tree_recursive(target, &context_root, copied)?;
}
if let Some(depth) = max_depth {
if depth > 0 {
for parent in &state.parents {
self.copy_state_recursive(target, parent, visited, copied, Some(depth - 1))?;
}
} else {
target.set_shallow(state_id, &state.parents)?;
}
} else {
for parent in &state.parents {
self.copy_state_recursive(target, parent, visited, copied, None)?;
}
}
target.store().put_state(&state)?;
*copied += 1;
Ok(())
}
fn copy_tree_recursive(
&self,
target: &Repository,
tree_hash: &ContentHash,
copied: &mut usize,
) -> Result<()> {
if target.store().has_tree(tree_hash)? {
return Ok(());
}
let tree = self
.source
.store()
.get_tree(tree_hash)?
.ok_or_else(|| anyhow!("Tree {} not found in source", tree_hash))?;
for entry in tree.entries() {
match entry.entry_type {
objects::object::EntryType::Blob => {
if !target.store().has_blob(&entry.hash)? {
let blob = self.source.require_blob(&entry.hash)?;
target.store().put_blob(&blob)?;
*copied += 1;
}
}
objects::object::EntryType::Tree => {
self.copy_tree_recursive(target, &entry.hash, copied)?;
}
objects::object::EntryType::Symlink => {
if !target.store().has_blob(&entry.hash)? {
let blob = self.source.require_blob(&entry.hash)?;
target.store().put_blob(&blob)?;
*copied += 1;
}
}
}
}
target.store().put_tree(&tree)?;
*copied += 1;
Ok(())
}
pub fn copy_blob(&self, target: &Repository, hash: &ContentHash) -> Result<bool> {
if target.store().has_blob(hash)? {
return Ok(false);
}
let blob = self.source.require_blob(hash)?;
target.store().put_blob(&blob)?;
Ok(true)
}
}