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> {
29 let repo = match git2::Repository::discover(path) {
30 Ok(repo) => repo,
31 Err(err) if err.code() == git2::ErrorCode::NotFound => {
32 return Err(find_uncolocated_jj(path).map_or_else(
33 || RepoError::NotFound(path.to_path_buf()),
34 RepoError::JjNotColocated,
35 ));
36 }
37 Err(err) => return Err(RepoError::Git(err)),
38 };
39 repo.workdir()
40 .map(Path::to_path_buf)
41 .ok_or_else(|| RepoError::Bare(path.to_path_buf()))
42}
43
44fn find_uncolocated_jj(path: &Path) -> Option<PathBuf> {
45 let mut dir = if path.is_dir() {
46 Some(path)
47 } else {
48 path.parent()
49 };
50 while let Some(candidate) = dir {
51 if candidate.join(".jj").is_dir() {
52 return Some(candidate.to_path_buf());
53 }
54 dir = candidate.parent();
55 }
56 None
57}
58
59pub fn open(root: &Path) -> Result<Box<dyn Vcs>, VcsError> {
61 open_with_settings(root, &DiffSettings::default())
62}
63
64pub fn open_with_settings(root: &Path, settings: &DiffSettings) -> Result<Box<dyn Vcs>, VcsError> {
65 if root.join(".jj").is_dir() {
66 Ok(Box::new(JjVcs::open_with_settings(root, settings)?))
67 } else {
68 Ok(Box::new(GitVcs::open_with_settings(root, settings)?))
69 }
70}
71
72#[cfg(test)]
73mod tests {
74 use super::*;
75
76 #[test]
77 fn discovers_this_repository() {
78 let here = std::env::current_dir().expect("cwd");
79 let root = discover(&here).expect("repo root");
80 assert!(root.join(".git").exists());
81 }
82
83 #[test]
84 fn fails_outside_a_repository() {
85 let dir = tempfile::tempdir().expect("tempdir");
88 assert!(matches!(discover(dir.path()), Err(RepoError::NotFound(_))));
89 }
90
91 #[test]
92 fn bare_repository_is_reported() {
93 let dir = tempfile::tempdir().expect("tempdir");
94 git2::Repository::init_bare(dir.path()).expect("init bare");
95 assert!(matches!(discover(dir.path()), Err(RepoError::Bare(_))));
96 }
97}