diff --git a/src/origin.rs b/src/origin.rs index 2ef3495..b7d1256 100644 --- a/src/origin.rs +++ b/src/origin.rs @@ -2,9 +2,3 @@ mod descr; pub use self::descr::Descr; pub mod store; - -/// A View of an origin is used to read the files of that origin at a particular point in time. A -/// single instance of a View should remain consistent across its lifetime. -pub trait View { - fn open(&self, path: &str) -> &dyn std::io::Read; -} diff --git a/src/origin/store.rs b/src/origin/store.rs index 0d0a4de..4afcfb4 100644 --- a/src/origin/store.rs +++ b/src/origin/store.rs @@ -1,16 +1,10 @@ use std::error::Error; use super::Descr; -use super::View; #[derive(Clone,Copy)] pub struct Limits {} -pub enum GetViewError { - TODO, - Unexpected(Box), -} - #[derive(Debug)] pub enum SyncError { InvalidURL, @@ -18,15 +12,29 @@ pub enum SyncError { Unexpected(Box), } +#[derive(Debug)] +pub enum WriteFileError { + FileNotFound, + Unexpected(Box), +} + pub trait Store { - fn get_view(&self, descr: Descr) -> Result<&dyn View, GetViewError>; + + fn write_file( + &self, + descr: Descr, + path: &str, + into: &mut W, + ) -> Result<(), WriteFileError> + where W: std::io::Write + ?Sized; + fn sync(&self, descr: Descr, limits: Limits) -> Result<(), SyncError>; } pub mod git { - use std::path::Path; - use super::{Store as originStore, *}; + use std::path::{Path, PathBuf}; + use super::*; use gix::clone::Error as gixCloneErr; use gix::progress::Discard; @@ -54,38 +62,49 @@ pub mod git { pub fn new(dir_path: &Path) -> std::io::Result { std::fs::create_dir_all(dir_path)?; - println!("{}", &dir_path.display()); Ok(Store{dir_path}) } + + fn repo_path(&self, descr: Descr) -> PathBuf { + self.dir_path.clone().join(descr.id()) + } + + fn branch_ref(branch_name: &str) -> String { + format!("origin/{branch_name}") + } } - impl<'a> originStore for Store<'a> { + impl<'a> super::Store for Store<'a> { fn sync(&self, descr: Descr, _limits: Limits) -> Result<(), SyncError> { let should_interrupt = &core::sync::atomic::AtomicBool::new(false); - let Descr::Git{url, ..} = descr; - - let path = &self.dir_path.clone().join(descr.id()); + let repo_path = &self.repo_path(descr); // if the path doesn't exist then use the gix clone feature to clone it into the // directory. - if std::fs::read_dir(path).is_err() { + if std::fs::read_dir(repo_path).is_err() { - std::fs::create_dir_all(path) + std::fs::create_dir_all(repo_path) .map_err(|e| SyncError::Unexpected(Box::from(e)))?; - gix::prepare_clone_bare(url, path)? + let Descr::Git{url, branch_name} = descr; + + let (repo, _) = gix::prepare_clone_bare(url, repo_path)? .fetch_only(Discard, should_interrupt) .map_err(|_| SyncError::InvalidURL)?; + repo.try_find_reference(&Self::branch_ref(branch_name)) + .map_err(|e| SyncError::Unexpected(Box::from(e)))? + .ok_or(SyncError::InvalidBranchName)?; + return Ok(()); } let direction = gix::remote::Direction::Fetch; - let repo = gix::open(path) + let repo = gix::open(repo_path) .map_err(|e| SyncError::Unexpected(Box::from(e)))?; let remote = repo.find_default_remote(direction) @@ -103,10 +122,52 @@ pub mod git { Ok(()) } - fn get_view(&self, _descr: Descr) -> Result<&dyn View, GetViewError> { - Err(GetViewError::TODO) - } + fn write_file( + &self, + descr: Descr, + path: &str, + into: &mut W, + ) -> Result<(), WriteFileError> + where W: std::io::Write + ?Sized { + + let Descr::Git{branch_name, ..} = descr; + let repo_path = &self.repo_path(descr); + let branch_ref = &Self::branch_ref(branch_name); + + let mut clean_path = Path::new(path); + clean_path = clean_path.strip_prefix("/").unwrap_or(clean_path); + + let repo = gix::open(repo_path) + .map_err(|e| WriteFileError::Unexpected(Box::from(e)))?; + + let commit_object_id = repo + .find_reference(branch_ref) + .map_err(|e| WriteFileError::Unexpected(Box::from(e)))? + .peel_to_id_in_place() + .map_err(|e| WriteFileError::Unexpected(Box::from(e)))?; + + let tree_object_id = repo.find_object(commit_object_id) + .map_err(|e| WriteFileError::Unexpected(Box::from(e)))? + .try_to_commit_ref() + .map_err(|e| WriteFileError::Unexpected(Box::from(e)))? + .tree(); + + let file_object = repo.find_object(tree_object_id) + .map_err(|e| WriteFileError::Unexpected(Box::from(e)))? + .peel_to_tree() + .map_err(|e| WriteFileError::Unexpected(Box::from(e)))? + .lookup_entry_by_path(clean_path) + .map_err(|e| WriteFileError::Unexpected(Box::from(e)))? + .ok_or(WriteFileError::FileNotFound)? + .object() + .map_err(|e| WriteFileError::Unexpected(Box::from(e)))?; + + into.write_all(file_object.data.as_ref()) + .map_err(|e| WriteFileError::Unexpected(Box::from(e)))?; + + Ok(()) + } } #[cfg(test)] @@ -118,12 +179,14 @@ pub mod git { use tempdir::TempDir; #[test] - fn sync() { + fn basic() { let tmp_dir = TempDir::new("origin_store_git").unwrap(); + let curr_dir = format!("file://{}", std::env::current_dir().unwrap().display()); + let descr = Descr::Git{ - url: "https://github.com/mediocregopher/priority-finder.git", + url: curr_dir.as_ref(), branch_name: "master", }; @@ -133,6 +196,23 @@ pub mod git { store.sync(descr, limits).expect("sync should succeed"); store.sync(descr, limits).expect("second sync should succeed"); + + let assert_write = |path: &str| { + let mut into: Vec = vec![]; + store.write_file(descr, path, &mut into).expect("write should succeed"); + assert!(into.len() > 0); + }; + + assert_write("src/lib.rs"); + assert_write("/src/lib.rs"); + + // File doesn't exist + let mut into: Vec = vec![]; + assert!(matches!( + store.write_file(descr, "DNE", &mut into), + Err::<(), super::WriteFileError>(super::WriteFileError::FileNotFound), + )); + assert_eq!(into.len(), 0); } } }