1use crate::error::MyResult;
2use crate::git::flags::GitFlags;
3use git2::{Repository, RepositoryOpenFlags, Status, StatusEntry, StatusOptions};
4use std::collections::HashMap;
5use std::path::{Path, PathBuf};
6
7type GitStatusMap = HashMap<PathBuf, Status>;
8
9pub struct GitRepo {
10 repo: Repository,
11 root: PathBuf,
12 statuses: Option<GitStatusMap>,
13}
14
15impl GitRepo {
16 pub fn open_repository<'a, I>(path: &Path, ceiling: I) -> Option<Self> where
17 I: IntoIterator<Item = &'a PathBuf>,
18 {
19 let flags = RepositoryOpenFlags::empty();
20 if let Ok(repo) = Repository::open_ext(path, flags, ceiling) {
21 if let Some(root) = repo.workdir() {
22 let root = root.to_path_buf();
23 let repo = Self { repo, root, statuses: None };
24 return Some(repo);
25 }
26 }
27 None
28 }
29
30 pub fn get_root(&self) -> &Path {
31 &self.root
32 }
33
34 pub fn test_ignored(&self, path: &Path) -> MyResult<bool> {
35 let path = path.strip_prefix(&self.root).map_err(|e| (e, path))?;
36 let ignored = self.repo.is_path_ignored(path)?;
37 Ok(ignored)
38 }
39
40 pub fn test_allowed(&mut self, flags: &GitFlags, path: &Path) -> MyResult<Option<GitFlags>> {
41 let path = path.strip_prefix(&self.root).map_err(|e| (e, path))?;
42 let status = self.repo.status_file(path)?;
43 if status.is_index_new() {
44 let statuses = self.statuses.get_or_insert_with(|| {
45 Self::read_statuses(&self.repo)
46 });
47 if let Some(status) = statuses.get(path) {
48 let result = flags.test_allowed(status);
49 return Ok(result);
50 }
51 }
52 let result = flags.test_allowed(&status);
53 Ok(result)
54 }
55
56 fn read_statuses(repo: &Repository) -> GitStatusMap {
57 let mut statuses = GitStatusMap::new();
58 let mut options = StatusOptions::new();
59 options.include_unmodified(true);
60 options.include_untracked(true);
61 options.include_ignored(true);
62 options.renames_head_to_index(true);
63 options.renames_index_to_workdir(true);
64 options.renames_from_rewrites(true);
65 if let Ok(entries) = repo.statuses(Some(&mut options)) {
66 for entry in entries.iter() {
67 if let Some(path) = Self::read_path(&entry) {
68 statuses.insert(path.to_path_buf(), entry.status());
69 }
70 }
71 }
72 statuses
73 }
74
75 fn read_path<'a>(entry: &'a StatusEntry) -> Option<&'a Path> {
76 let delta = entry.head_to_index()?;
77 let old = delta.old_file().path()?;
78 let new = delta.new_file().path()?;
79 if new != old {
80 Some(new)
81 } else {
82 None
83 }
84 }
85}