1use std::fmt::Arguments;
2use std::path::{Path, PathBuf};
3
4use glob::Pattern;
5use walkdir::{DirEntry, WalkDir, WalkDirIterator};
6
7use cache::Cache;
8use config::Config;
9use printer::Printer;
10use query::Query;
11use remote::Remote;
12use repository::Repository;
13use vcs::{self, Vcs};
14
15
16pub struct Workspace {
17 cache: Cache,
18 config: Config,
19 printer: Printer,
20}
21
22impl Workspace {
23 pub fn new() -> ::Result<Self> {
24 let config = Config::new(None)?;
25 let cache = Cache::new(None)?;
26 Ok(Workspace {
27 cache: cache,
28 config: config,
29 printer: Printer::default(),
30 })
31 }
32
33 pub fn set_root_dir<P: Into<PathBuf>>(&mut self, root: P) {
34 self.config.root_dir = root.into();
35 }
36
37 pub fn verbose_output(mut self, verbose: bool) -> Self {
38 self.printer.verbose = verbose;
39 self
40 }
41
42 pub fn print(&self, args: Arguments) {
43 self.printer.print(args)
44 }
45
46 pub fn repositories(&self) -> Option<&[Repository]> {
49 self.cache
50 .get_opt()
51 .map(|cache| cache.repositories.as_slice())
52 }
53
54 pub fn config(&self) -> &Config {
55 &self.config
56 }
57
58 pub fn import_repositories<P: AsRef<Path>>(&mut self, root: P, depth: Option<usize>) -> ::Result<()> {
59 for path in collect_repositories(root, depth, &self.config.exclude_patterns) {
60 if let Some(repo) = self.new_repository_from_path(&path)? {
61 self.add_repository(repo);
62 }
63 }
64 Ok(())
65 }
66
67
68 pub fn add_repository(&mut self, repo: Repository) {
69 let ref mut repos = self.cache.get_mut().repositories;
70 if let Some(r) = repos.iter_mut().find(|r| r.is_same_local(&repo)) {
71 self.printer.print(format_args!(
72 "Overwrite existed entry: {}\n",
73 repo.path_string()
74 ));
75 *r = repo;
76 return;
77 }
78
79 self.printer
80 .print(format_args!("Add new entry: {}\n", repo.path_string()));
81 repos.push(repo);
82 }
83
84 pub fn add_repository_if_exists(&mut self, path: &Path) -> ::Result<()> {
85 let repo = match self.new_repository_from_path(path)? {
86 Some(repo) => repo,
87 None => {
88 self.printer.print(format_args!(
89 "Ignored: {} is not a repository\n",
90 path.display()
91 ));
92 return Ok(());
93 }
94 };
95 self.add_repository(repo);
96 Ok(())
97 }
98
99 pub fn drop_invalid_repositories(&mut self) {
100 let mut new_repo = Vec::new();
101 for repo in &self.cache.get_mut().repositories {
102 let repo = match repo.clone().refresh() {
103 Some(r) => r,
104 None => continue,
105 };
106 if self.config
107 .exclude_patterns
108 .iter()
109 .all(|ex| !ex.matches(&repo.path_string()))
110 {
111 new_repo.push(repo.clone());
112 } else {
113 self.printer
114 .print(format_args!("Dropped: {}\n", repo.path_string()));
115 }
116 }
117 self.cache.get_mut().repositories = new_repo;
118 }
119
120 pub fn sort_repositories(&mut self) {
121 self.cache
122 .get_mut()
123 .repositories
124 .sort_by(|a, b| a.name().cmp(b.name()));
125 }
126
127
128 pub fn save_cache(&mut self) -> ::Result<()> {
130 self.cache.dump()?;
131 Ok(())
132 }
133
134 pub fn resolve_query(&self, query: &Query) -> ::Result<PathBuf> {
135 let root = &self.config.root_dir;
136 let host = query.host().unwrap_or_else(|| &self.config.host);
137 let path = root.join(host).join(query.path());
138 Ok(path)
139 }
140
141 pub fn default_host(&self) -> &str {
142 &self.config.host
143 }
144
145 pub fn for_each_repo<F: Fn(&Repository) -> ::Result<()>>(&self, f: F) -> ::Result<()> {
146 let repos = self.repositories()
147 .ok_or("The cache has not initialized yet")?;
148 for repo in repos {
149 f(&repo)?;
150 }
151 Ok(())
152 }
153
154 fn new_repository_from_path(&self, path: &Path) -> ::Result<Option<Repository>> {
155 let vcs = match vcs::detect_from_path(&path) {
156 Some(vcs) => vcs,
157 None => return Ok(None),
158 };
159 let remote = match vcs.get_remote_url(&path)? {
160 Some(remote) => remote,
161 None => return Ok(None),
162 };
163 Repository::new(path, vcs, Remote::new(remote)).map(Some)
164 }
165
166 pub fn create_empty_repository(&mut self, path: &Path, vcs: Vcs) -> ::Result<()> {
167 self.printer.print(format_args!(
168 "Creating an empty repository at \"{}\" (VCS: {:?})\n",
169 path.display(),
170 vcs
171 ));
172 if vcs::detect_from_path(path).is_some() {
173 self.printer.print(format_args!(
174 "[info] The repository {} has already existed.\n",
175 path.display()
176 ));
177 return Ok(());
178 }
179 vcs.do_init(path)?;
180 let repo = Repository::new(path, vcs, None)?;
181 self.add_repository(repo);
182
183 Ok(())
184 }
185
186 pub fn clone_repository(&mut self, remote: Remote, dest: &Path, vcs: Vcs) -> ::Result<()> {
187 self.printer.print(format_args!(
188 "[info] Clone from {} into {} by using {:?}\n",
189 remote.url(),
190 dest.display(),
191 vcs,
192 ));
193 if vcs::detect_from_path(&dest).is_some() {
194 self.printer.print(format_args!(
195 "The repository {} has already existed.\n",
196 dest.display()
197 ));
198 return Ok(());
199 }
200 vcs.do_clone(&dest, &remote.url(), &[] as &[String])?;
201 let repo = Repository::new(dest, vcs, remote)?;
202 self.add_repository(repo);
203 Ok(())
204 }
205}
206
207
208fn collect_repositories<P>(root: P, depth: Option<usize>, excludes: &[Pattern]) -> Vec<PathBuf>
209where
210 P: AsRef<Path>,
211{
212 let filter = {
213 let root = root.as_ref();
214 move |entry: &DirEntry| {
215 if entry.path() == root {
216 return true;
217 }
218 !entry
219 .path()
220 .parent()
221 .map(|path| vcs::detect_from_path(&path).is_some())
222 .unwrap_or(false)
223 && entry
224 .path()
225 .canonicalize()
226 .ok()
227 .map(|path| {
228 let path = path.to_str().unwrap().trim_left_matches(r"\\?\");
229 excludes.iter().all(|ex| !ex.matches(path))
230 })
231 .unwrap_or(false)
232 }
233 };
234
235 let mut walkdir = WalkDir::new(root.as_ref()).follow_links(true);
236 if let Some(depth) = depth {
237 walkdir = walkdir.max_depth(depth);
238 }
239 walkdir
240 .into_iter()
241 .filter_entry(filter)
242 .filter_map(Result::ok)
243 .filter(|entry| vcs::detect_from_path(entry.path()).is_some())
244 .map(|entry| entry.path().into())
245 .collect()
246}