repository.rs

  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}