1use anyhow::Result;
2use collections::HashMap;
3use parking_lot::Mutex;
4use std::{
5 path::{Component, Path, PathBuf},
6 sync::Arc,
7};
8
9pub use git2::Repository as LibGitRepository;
10
11#[async_trait::async_trait]
12pub trait GitRepository: Send {
13 fn reload_index(&self);
14
15 fn load_index_text(&self, relative_file_path: &Path) -> Option<String>;
16}
17
18#[async_trait::async_trait]
19impl GitRepository for LibGitRepository {
20 fn reload_index(&self) {
21 if let Ok(mut index) = self.index() {
22 _ = index.read(false);
23 }
24 }
25
26 fn load_index_text(&self, relative_file_path: &Path) -> Option<String> {
27 fn logic(repo: &LibGitRepository, relative_file_path: &Path) -> Result<Option<String>> {
28 const STAGE_NORMAL: i32 = 0;
29 let index = repo.index()?;
30
31 // This check is required because index.get_path() unwraps internally :(
32 check_path_to_repo_path_errors(relative_file_path)?;
33
34 let oid = match index.get_path(&relative_file_path, STAGE_NORMAL) {
35 Some(entry) => entry.id,
36 None => return Ok(None),
37 };
38
39 let content = repo.find_blob(oid)?.content().to_owned();
40 Ok(Some(String::from_utf8(content)?))
41 }
42
43 match logic(&self, relative_file_path) {
44 Ok(value) => return value,
45 Err(err) => log::error!("Error loading head text: {:?}", err),
46 }
47 None
48 }
49}
50
51#[derive(Debug, Clone, Default)]
52pub struct FakeGitRepository {
53 state: Arc<Mutex<FakeGitRepositoryState>>,
54}
55
56#[derive(Debug, Clone, Default)]
57pub struct FakeGitRepositoryState {
58 pub index_contents: HashMap<PathBuf, String>,
59}
60
61impl FakeGitRepository {
62 pub fn open(state: Arc<Mutex<FakeGitRepositoryState>>) -> Arc<Mutex<dyn GitRepository>> {
63 Arc::new(Mutex::new(FakeGitRepository { state }))
64 }
65}
66
67#[async_trait::async_trait]
68impl GitRepository for FakeGitRepository {
69 fn reload_index(&self) {}
70
71 fn load_index_text(&self, path: &Path) -> Option<String> {
72 let state = self.state.lock();
73 state.index_contents.get(path).cloned()
74 }
75}
76
77fn check_path_to_repo_path_errors(relative_file_path: &Path) -> Result<()> {
78 match relative_file_path.components().next() {
79 None => anyhow::bail!("repo path should not be empty"),
80 Some(Component::Prefix(_)) => anyhow::bail!(
81 "repo path `{}` should be relative, not a windows prefix",
82 relative_file_path.to_string_lossy()
83 ),
84 Some(Component::RootDir) => {
85 anyhow::bail!(
86 "repo path `{}` should be relative",
87 relative_file_path.to_string_lossy()
88 )
89 }
90 Some(Component::CurDir) => {
91 anyhow::bail!(
92 "repo path `{}` should not start with `.`",
93 relative_file_path.to_string_lossy()
94 )
95 }
96 Some(Component::ParentDir) => {
97 anyhow::bail!(
98 "repo path `{}` should not start with `..`",
99 relative_file_path.to_string_lossy()
100 )
101 }
102 _ => Ok(()),
103 }
104}