repository.rs

  1use anyhow::Result;
  2use collections::HashMap;
  3use git2::Repository as LibGitRepository;
  4use parking_lot::Mutex;
  5use std::{
  6    path::{Path, PathBuf},
  7    sync::Arc,
  8};
  9use util::ResultExt;
 10
 11#[async_trait::async_trait]
 12pub trait GitRepository: Send + Sync + std::fmt::Debug {
 13    fn manages(&self, path: &Path) -> bool;
 14
 15    fn in_dot_git(&self, path: &Path) -> bool;
 16
 17    fn content_path(&self) -> &Path;
 18
 19    fn git_dir_path(&self) -> &Path;
 20
 21    fn scan_id(&self) -> usize;
 22
 23    fn set_scan_id(&mut self, scan_id: usize);
 24
 25    fn reopen_git_repo(&mut self) -> bool;
 26
 27    fn git_repo(&self) -> Arc<Mutex<LibGitRepository>>;
 28
 29    fn boxed_clone(&self) -> Box<dyn GitRepository>;
 30
 31    async fn load_head_text(&self, relative_file_path: &Path) -> Option<String>;
 32}
 33
 34#[derive(Clone)]
 35pub struct RealGitRepository {
 36    // Path to folder containing the .git file or directory
 37    content_path: Arc<Path>,
 38    // Path to the actual .git folder.
 39    // Note: if .git is a file, this points to the folder indicated by the .git file
 40    git_dir_path: Arc<Path>,
 41    scan_id: usize,
 42    libgit_repository: Arc<Mutex<LibGitRepository>>,
 43}
 44
 45impl RealGitRepository {
 46    pub fn open(dotgit_path: &Path) -> Option<Box<dyn GitRepository>> {
 47        LibGitRepository::open(&dotgit_path)
 48            .log_err()
 49            .and_then::<Box<dyn GitRepository>, _>(|libgit_repository| {
 50                Some(Box::new(Self {
 51                    content_path: libgit_repository.workdir()?.into(),
 52                    git_dir_path: dotgit_path.canonicalize().log_err()?.into(),
 53                    scan_id: 0,
 54                    libgit_repository: Arc::new(parking_lot::Mutex::new(libgit_repository)),
 55                }))
 56            })
 57    }
 58}
 59
 60#[async_trait::async_trait]
 61impl GitRepository for RealGitRepository {
 62    fn manages(&self, path: &Path) -> bool {
 63        path.canonicalize()
 64            .map(|path| path.starts_with(&self.content_path))
 65            .unwrap_or(false)
 66    }
 67
 68    fn in_dot_git(&self, path: &Path) -> bool {
 69        path.canonicalize()
 70            .map(|path| path.starts_with(&self.git_dir_path))
 71            .unwrap_or(false)
 72    }
 73
 74    fn content_path(&self) -> &Path {
 75        self.content_path.as_ref()
 76    }
 77
 78    fn git_dir_path(&self) -> &Path {
 79        self.git_dir_path.as_ref()
 80    }
 81
 82    fn scan_id(&self) -> usize {
 83        self.scan_id
 84    }
 85
 86    async fn load_head_text(&self, relative_file_path: &Path) -> Option<String> {
 87        fn logic(repo: &LibGitRepository, relative_file_path: &Path) -> Result<Option<String>> {
 88            const STAGE_NORMAL: i32 = 0;
 89            let index = repo.index()?;
 90            let oid = match index.get_path(relative_file_path, STAGE_NORMAL) {
 91                Some(entry) => entry.id,
 92                None => return Ok(None),
 93            };
 94
 95            let content = repo.find_blob(oid)?.content().to_owned();
 96            let head_text = String::from_utf8(content)?;
 97            Ok(Some(head_text))
 98        }
 99
100        match logic(&self.libgit_repository.as_ref().lock(), relative_file_path) {
101            Ok(value) => return value,
102            Err(err) => log::error!("Error loading head text: {:?}", err),
103        }
104        None
105    }
106
107    fn reopen_git_repo(&mut self) -> bool {
108        match LibGitRepository::open(&self.git_dir_path) {
109            Ok(repo) => {
110                self.libgit_repository = Arc::new(Mutex::new(repo));
111                true
112            }
113
114            Err(_) => false,
115        }
116    }
117
118    fn git_repo(&self) -> Arc<Mutex<LibGitRepository>> {
119        self.libgit_repository.clone()
120    }
121
122    fn set_scan_id(&mut self, scan_id: usize) {
123        self.scan_id = scan_id;
124    }
125
126    fn boxed_clone(&self) -> Box<dyn GitRepository> {
127        Box::new(self.clone())
128    }
129}
130
131impl std::fmt::Debug for RealGitRepository {
132    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
133        f.debug_struct("GitRepository")
134            .field("content_path", &self.content_path)
135            .field("git_dir_path", &self.git_dir_path)
136            .field("scan_id", &self.scan_id)
137            .field("libgit_repository", &"LibGitRepository")
138            .finish()
139    }
140}
141
142#[derive(Debug, Clone)]
143pub struct FakeGitRepository {
144    content_path: Arc<Path>,
145    git_dir_path: Arc<Path>,
146    scan_id: usize,
147    state: Arc<Mutex<FakeGitRepositoryState>>,
148}
149
150#[derive(Debug, Clone, Default)]
151pub struct FakeGitRepositoryState {
152    pub index_contents: HashMap<PathBuf, String>,
153}
154
155impl FakeGitRepository {
156    pub fn open(
157        dotgit_path: &Path,
158        scan_id: usize,
159        state: Arc<Mutex<FakeGitRepositoryState>>,
160    ) -> Box<dyn GitRepository> {
161        Box::new(FakeGitRepository {
162            content_path: dotgit_path.parent().unwrap().into(),
163            git_dir_path: dotgit_path.into(),
164            scan_id,
165            state,
166        })
167    }
168}
169
170#[async_trait::async_trait]
171impl GitRepository for FakeGitRepository {
172    fn manages(&self, path: &Path) -> bool {
173        path.starts_with(self.content_path())
174    }
175
176    fn in_dot_git(&self, path: &Path) -> bool {
177        path.starts_with(self.git_dir_path())
178    }
179
180    fn content_path(&self) -> &Path {
181        &self.content_path
182    }
183
184    fn git_dir_path(&self) -> &Path {
185        &self.git_dir_path
186    }
187
188    fn scan_id(&self) -> usize {
189        self.scan_id
190    }
191
192    async fn load_head_text(&self, path: &Path) -> Option<String> {
193        let state = self.state.lock();
194        state.index_contents.get(path).cloned()
195    }
196
197    fn reopen_git_repo(&mut self) -> bool {
198        true
199    }
200
201    fn git_repo(&self) -> Arc<Mutex<LibGitRepository>> {
202        unimplemented!()
203    }
204
205    fn set_scan_id(&mut self, scan_id: usize) {
206        self.scan_id = scan_id;
207    }
208
209    fn boxed_clone(&self) -> Box<dyn GitRepository> {
210        Box::new(self.clone())
211    }
212}