use std::fmt::Arguments; use std::path::{Path, PathBuf}; use glob::Pattern; use walkdir::{DirEntry, WalkDir, WalkDirIterator}; use cache::Cache; use config::Config; use printer::Printer; use query::Query; use remote::Remote; use repository::Repository; use vcs::{self, Vcs}; pub struct Workspace { cache: Cache, config: Config, printer: Printer, } impl Workspace { pub fn new() -> ::Result { let config = Config::new(None)?; let cache = Cache::new(None)?; Ok(Workspace { cache: cache, config: config, printer: Printer::default(), }) } pub fn set_root_dir>(&mut self, root: P) { self.config.root_dir = root.into(); } pub fn verbose_output(mut self, verbose: bool) -> Self { self.printer.verbose = verbose; self } pub fn print(&self, args: Arguments) { self.printer.print(args) } /// Returns a list of managed repositories. /// Note that this method returns None if cache has not created yet. pub fn repositories(&self) -> Option<&[Repository]> { self.cache .get_opt() .map(|cache| cache.repositories.as_slice()) } pub fn config(&self) -> &Config { &self.config } pub fn import_repositories>(&mut self, root: P, depth: Option) -> ::Result<()> { for path in collect_repositories(root, depth, &self.config.exclude_patterns) { if let Some(repo) = self.new_repository_from_path(&path)? { self.add_repository(repo); } } Ok(()) } pub fn add_repository(&mut self, repo: Repository) { let ref mut repos = self.cache.get_mut().repositories; if let Some(r) = repos.iter_mut().find(|r| r.is_same_local(&repo)) { self.printer.print(format_args!( "Overwrite existed entry: {}\n", repo.path_string() )); *r = repo; return; } self.printer .print(format_args!("Add new entry: {}\n", repo.path_string())); repos.push(repo); } pub fn add_repository_if_exists(&mut self, path: &Path) -> ::Result<()> { let repo = match self.new_repository_from_path(path)? { Some(repo) => repo, None => { self.printer.print(format_args!( "Ignored: {} is not a repository\n", path.display() )); return Ok(()); } }; self.add_repository(repo); Ok(()) } pub fn drop_invalid_repositories(&mut self) { let mut new_repo = Vec::new(); for repo in &self.cache.get_mut().repositories { let repo = match repo.clone().refresh() { Some(r) => r, None => continue, }; if self.config .exclude_patterns .iter() .all(|ex| !ex.matches(&repo.path_string())) { new_repo.push(repo.clone()); } else { self.printer .print(format_args!("Dropped: {}\n", repo.path_string())); } } self.cache.get_mut().repositories = new_repo; } pub fn sort_repositories(&mut self) { self.cache .get_mut() .repositories .sort_by(|a, b| a.name().cmp(b.name())); } /// Save current state of workspace to cache file. pub fn save_cache(&mut self) -> ::Result<()> { self.cache.dump()?; Ok(()) } pub fn resolve_query(&self, query: &Query) -> ::Result { let root = &self.config.root_dir; let host = query.host().unwrap_or_else(|| &self.config.host); let path = root.join(host).join(query.path()); Ok(path) } pub fn default_host(&self) -> &str { &self.config.host } pub fn for_each_repo ::Result<()>>(&self, f: F) -> ::Result<()> { let repos = self.repositories() .ok_or("The cache has not initialized yet")?; for repo in repos { f(&repo)?; } Ok(()) } fn new_repository_from_path(&self, path: &Path) -> ::Result> { let vcs = match vcs::detect_from_path(&path) { Some(vcs) => vcs, None => return Ok(None), }; let remote = match vcs.get_remote_url(&path)? { Some(remote) => remote, None => return Ok(None), }; Repository::new(path, vcs, Remote::new(remote)).map(Some) } pub fn create_empty_repository(&mut self, path: &Path, vcs: Vcs) -> ::Result<()> { self.printer.print(format_args!( "Creating an empty repository at \"{}\" (VCS: {:?})\n", path.display(), vcs )); if vcs::detect_from_path(path).is_some() { self.printer.print(format_args!( "[info] The repository {} has already existed.\n", path.display() )); return Ok(()); } vcs.do_init(path)?; let repo = Repository::new(path, vcs, None)?; self.add_repository(repo); Ok(()) } pub fn clone_repository(&mut self, remote: Remote, dest: &Path, vcs: Vcs) -> ::Result<()> { self.printer.print(format_args!( "[info] Clone from {} into {} by using {:?}\n", remote.url(), dest.display(), vcs, )); if vcs::detect_from_path(&dest).is_some() { self.printer.print(format_args!( "The repository {} has already existed.\n", dest.display() )); return Ok(()); } vcs.do_clone(&dest, &remote.url(), &[] as &[String])?; let repo = Repository::new(dest, vcs, remote)?; self.add_repository(repo); Ok(()) } } fn collect_repositories

(root: P, depth: Option, excludes: &[Pattern]) -> Vec where P: AsRef, { let filter = { let root = root.as_ref(); move |entry: &DirEntry| { if entry.path() == root { return true; } !entry .path() .parent() .map(|path| vcs::detect_from_path(&path).is_some()) .unwrap_or(false) && entry .path() .canonicalize() .ok() .map(|path| { let path = path.to_str().unwrap().trim_left_matches(r"\\?\"); excludes.iter().all(|ex| !ex.matches(path)) }) .unwrap_or(false) } }; let mut walkdir = WalkDir::new(root.as_ref()).follow_links(true); if let Some(depth) = depth { walkdir = walkdir.max_depth(depth); } walkdir .into_iter() .filter_entry(filter) .filter_map(Result::ok) .filter(|entry| vcs::detect_from_path(entry.path()).is_some()) .map(|entry| entry.path().into()) .collect() }