rhq_core/
workspace.rs

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    /// Returns a list of managed repositories.
47    /// Note that this method returns None if cache has not created yet.
48    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    /// Save current state of workspace to cache file.
129    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}