repository.rs

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