1use anyhow::Result;
2use git2::{Repository as LibGitRepository, RepositoryOpenFlags as LibGitRepositoryOpenFlags};
3use parking_lot::Mutex;
4use std::{ffi::OsStr, path::Path, sync::Arc};
5use util::ResultExt;
6
7#[derive(Clone)]
8pub struct GitRepository {
9 // Path to folder containing the .git file or directory
10 content_path: Arc<Path>,
11 // Path to the actual .git folder.
12 // Note: if .git is a file, this points to the folder indicated by the .git file
13 git_dir_path: Arc<Path>,
14 scan_id: usize,
15 libgit_repository: Arc<Mutex<LibGitRepository>>,
16}
17
18impl GitRepository {
19 pub fn open(dotgit_path: &Path) -> Option<GitRepository> {
20 LibGitRepository::open(&dotgit_path)
21 .log_err()
22 .and_then(|libgit_repository| {
23 Some(Self {
24 content_path: libgit_repository.workdir()?.into(),
25 git_dir_path: dotgit_path.canonicalize().log_err()?.into(),
26 scan_id: 0,
27 libgit_repository: Arc::new(parking_lot::Mutex::new(libgit_repository)),
28 })
29 })
30 }
31
32 pub fn manages(&self, path: &Path) -> bool {
33 path.canonicalize()
34 .map(|path| path.starts_with(&self.content_path))
35 .unwrap_or(false)
36 }
37
38 pub fn in_dot_git(&self, path: &Path) -> bool {
39 path.canonicalize()
40 .map(|path| path.starts_with(&self.git_dir_path))
41 .unwrap_or(false)
42 }
43
44 pub fn content_path(&self) -> &Path {
45 self.content_path.as_ref()
46 }
47
48 pub fn git_dir_path(&self) -> &Path {
49 self.git_dir_path.as_ref()
50 }
51
52 pub fn scan_id(&self) -> usize {
53 self.scan_id
54 }
55
56 pub(super) fn set_scan_id(&mut self, scan_id: usize) {
57 self.scan_id = scan_id;
58 }
59
60 pub fn with_repo<F: FnOnce(&mut git2::Repository)>(&mut self, f: F) {
61 let mut git2 = self.libgit_repository.lock();
62 f(&mut git2)
63 }
64
65 pub async fn load_head_text(&self, file_path: &Path) -> Option<String> {
66 fn logic(repo: &LibGitRepository, file_path: &Path) -> Result<Option<String>> {
67 let object = repo
68 .head()?
69 .peel_to_tree()?
70 .get_path(file_path)?
71 .to_object(&repo)?;
72
73 let content = match object.as_blob() {
74 Some(blob) => blob.content().to_owned(),
75 None => return Ok(None),
76 };
77
78 let head_text = String::from_utf8(content.to_owned())?;
79 Ok(Some(head_text))
80 }
81
82 match logic(&self.libgit_repository.lock(), file_path) {
83 Ok(value) => return value,
84 Err(err) => log::error!("Error loading head text: {:?}", err),
85 }
86 None
87 }
88}
89
90impl std::fmt::Debug for GitRepository {
91 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
92 f.debug_struct("GitRepository")
93 .field("content_path", &self.content_path)
94 .field("git_dir_path", &self.git_dir_path)
95 .field("scan_id", &self.scan_id)
96 .field("libgit_repository", &"LibGitRepository")
97 .finish()
98 }
99}