1use 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
17pub 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 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}