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}