repository.rs

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