Skip to main content

ex_cli/git/
repo.rs

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        // Check if the directory exists in the .gitignore file.
36        let path = path.strip_prefix(&self.root).map_err(|e| (e, path))?;
37        let ignored = self.repo.is_path_ignored(path)?;
38        Ok(ignored)
39    }
40
41    pub fn test_allowed(&mut self, flags: &GitFlags, path: &Path) -> MyResult<Option<GitFlags>> {
42        // Test the flags, and return the flags for display purposes.
43        let path = path.strip_prefix(&self.root).map_err(|e| (e, path))?;
44        let status = self.repo.status_file(path)?;
45        if status.is_index_new() {
46            let statuses = self.statuses.get_or_insert_with(|| {
47                Self::read_statuses(&self.repo)
48            });
49            if let Some(status) = statuses.get(path) {
50                let result = flags.test_allowed(status);
51                return Ok(result);
52            }
53        }
54        let result = flags.test_allowed(&status);
55        Ok(result)
56    }
57
58    fn read_statuses(repo: &Repository) -> GitStatusMap {
59        let mut statuses = GitStatusMap::new();
60        let mut options = StatusOptions::new();
61        options.include_unmodified(true);
62        options.include_untracked(true);
63        options.include_ignored(true);
64        options.renames_head_to_index(true);
65        options.renames_index_to_workdir(true);
66        options.renames_from_rewrites(true);
67        if let Ok(entries) = repo.statuses(Some(&mut options)) {
68            for entry in entries.iter() {
69                if let Some(path) = Self::read_path(&entry) {
70                    statuses.insert(path.to_path_buf(), entry.status());
71                }
72            }
73        }
74        statuses
75    }
76
77    fn read_path<'a>(entry: &'a StatusEntry) -> Option<&'a Path> {
78        let delta = entry.head_to_index()?;
79        let old = delta.old_file().path()?;
80        let new = delta.new_file().path()?;
81        if new != old {
82            Some(new)
83        } else {
84            None
85        }
86    }
87}