Skip to main content

diffler_core/
repo.rs

1//! Repository discovery.
2
3use std::path::{Path, PathBuf};
4
5use thiserror::Error;
6
7#[derive(Debug, Error)]
8pub enum RepoError {
9    #[error("not a git repository (or any parent): {0}")]
10    NotFound(PathBuf),
11    #[error("repository has no working directory: {0}")]
12    Bare(PathBuf),
13    #[error(transparent)]
14    Git(#[from] git2::Error),
15}
16
17/// Discover the repository containing `path` and return its working directory root.
18pub fn discover(path: &Path) -> Result<PathBuf, RepoError> {
19    let repo = git2::Repository::discover(path).map_err(|err| {
20        if err.code() == git2::ErrorCode::NotFound {
21            RepoError::NotFound(path.to_path_buf())
22        } else {
23            RepoError::Git(err)
24        }
25    })?;
26    repo.workdir()
27        .map(Path::to_path_buf)
28        .ok_or_else(|| RepoError::Bare(path.to_path_buf()))
29}
30
31#[cfg(test)]
32mod tests {
33    use super::*;
34
35    #[test]
36    fn discovers_this_repository() {
37        let here = std::env::current_dir().expect("cwd");
38        let root = discover(&here).expect("repo root");
39        assert!(root.join(".git").exists());
40    }
41
42    #[test]
43    fn fails_outside_a_repository() {
44        // discover() walks all ancestors, so this test needs a dir whose
45        // ancestors are repo-free; tempdirs satisfy that on CI runners
46        let dir = tempfile::tempdir().expect("tempdir");
47        assert!(matches!(discover(dir.path()), Err(RepoError::NotFound(_))));
48    }
49
50    #[test]
51    fn bare_repository_is_reported() {
52        let dir = tempfile::tempdir().expect("tempdir");
53        git2::Repository::init_bare(dir.path()).expect("init bare");
54        assert!(matches!(discover(dir.path()), Err(RepoError::Bare(_))));
55    }
56}