1use std::path::{Path, PathBuf};
4
5use thiserror::Error;
6
7use crate::diffalgo::DiffSettings;
8use crate::git::GitVcs;
9use crate::jj::JjVcs;
10use crate::vcs::{Vcs, VcsError};
11
12#[derive(Debug, Error)]
13pub enum RepoError {
14 #[error("not a git repository (or any parent): {0}")]
15 NotFound(PathBuf),
16 #[error("repository has no working directory: {0}")]
17 Bare(PathBuf),
18 #[error(
19 "jj repo at {0} is not colocated with git; run `jj git colocation enable` there to use diffler"
20 )]
21 JjNotColocated(PathBuf),
22 #[error(transparent)]
23 Git(#[from] git2::Error),
24}
25
26pub fn discover(path: &Path) -> Result<PathBuf, RepoError> {
30 let repo = match git2::Repository::discover(path) {
31 Ok(repo) => repo,
32 Err(err) if err.code() == git2::ErrorCode::NotFound => {
33 return Err(find_uncolocated_jj(path).map_or_else(
34 || RepoError::NotFound(path.to_path_buf()),
35 RepoError::JjNotColocated,
36 ));
37 }
38 Err(err) => return Err(RepoError::Git(err)),
39 };
40 repo.workdir()
41 .map(Path::to_path_buf)
42 .ok_or_else(|| RepoError::Bare(path.to_path_buf()))
43}
44
45fn find_uncolocated_jj(path: &Path) -> Option<PathBuf> {
48 let mut dir = if path.is_dir() {
49 Some(path)
50 } else {
51 path.parent()
52 };
53 while let Some(candidate) = dir {
54 if candidate.join(".jj").is_dir() {
55 return Some(candidate.to_path_buf());
56 }
57 dir = candidate.parent();
58 }
59 None
60}
61
62pub fn open(root: &Path) -> Result<Box<dyn Vcs>, VcsError> {
66 open_with_settings(root, &DiffSettings::default())
67}
68
69pub fn open_with_settings(root: &Path, settings: &DiffSettings) -> Result<Box<dyn Vcs>, VcsError> {
72 if root.join(".jj").is_dir() {
73 Ok(Box::new(JjVcs::open_with_settings(root, settings)?))
74 } else {
75 Ok(Box::new(GitVcs::open_with_settings(root, settings)?))
76 }
77}
78
79#[cfg(test)]
80mod tests {
81 use super::*;
82
83 #[test]
84 fn discovers_this_repository() {
85 let here = std::env::current_dir().expect("cwd");
86 let root = discover(&here).expect("repo root");
87 assert!(root.join(".git").exists());
88 }
89
90 #[test]
91 fn fails_outside_a_repository() {
92 let dir = tempfile::tempdir().expect("tempdir");
95 assert!(matches!(discover(dir.path()), Err(RepoError::NotFound(_))));
96 }
97
98 #[test]
99 fn bare_repository_is_reported() {
100 let dir = tempfile::tempdir().expect("tempdir");
101 git2::Repository::init_bare(dir.path()).expect("init bare");
102 assert!(matches!(discover(dir.path()), Err(RepoError::Bare(_))));
103 }
104}