1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
//
use super::RepoDetails;
use crate::repository::test::TestRepository;
use crate::validation::remotes::validate_default_remotes;
pub use factory::RepositoryFactory;
use git_next_config::{GitDir, RemoteUrl};
pub use open::otest::OnFetch;
pub use open::otest::OnPush;
pub use open::OpenRepository;
pub use open::OpenRepositoryLike;
pub use open::RealOpenRepository;
use tracing::info;

pub mod factory;
pub mod open;
mod test;

#[cfg(test)]
mod tests;

#[derive(Clone, Debug)]
#[allow(clippy::large_enum_variant)]
pub enum Repository {
    Real,
    Test(TestRepository),
}

pub const fn test(fs: kxio::fs::FileSystem) -> TestRepository {
    TestRepository::new(fs, vec![], vec![])
}

/// Opens a repository, cloning if necessary
#[tracing::instrument(skip_all)]
#[cfg(not(tarpaulin_include))] // requires network access to either clone new and/or fetch.
pub fn open(
    repository_factory: &dyn factory::RepositoryFactory,
    repo_details: &RepoDetails,
) -> Result<Box<dyn OpenRepositoryLike>> {
    let open_repository = if !repo_details.gitdir.exists() {
        info!("Local copy not found - cloning...");
        repository_factory.git_clone(repo_details)?
    } else {
        info!("Local copy found - opening...");
        repository_factory.open(repo_details)?
    };
    info!("Validating...");
    validate_default_remotes(&*open_repository, repo_details)
        .map_err(|e| Error::Validation(e.to_string()))?;
    Ok(open_repository)
}

pub trait RepositoryLike {
    fn open(&self, gitdir: &GitDir) -> Result<OpenRepository>;
    fn git_clone(&self, repo_details: &RepoDetails) -> Result<OpenRepository>;
}
// impl std::ops::Deref for Repository {
//     type Target = dyn RepositoryLike;
//
//     fn deref(&self) -> &Self::Target {
//         match self {
//             Self::Real => &real::RealRepository,
//             Self::Test(test_repository) => test_repository,
//         }
//     }
// }

#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)]
pub enum Direction {
    /// Push local changes to the remote.
    Push,
    /// Fetch changes from the remote to the local repository.
    Fetch,
}
impl From<Direction> for gix::remote::Direction {
    fn from(value: Direction) -> Self {
        match value {
            Direction::Push => Self::Push,
            Direction::Fetch => Self::Fetch,
        }
    }
}

pub type Result<T> = core::result::Result<T, Error>;

#[derive(Debug, thiserror::Error)]
pub enum Error {
    #[error("invalid git dir: {0}")]
    InvalidGitDir(git_next_config::GitDir),

    #[error("kxiofs: {0}")]
    KxioFs(#[from] kxio::fs::Error),

    #[error("io: {0}")]
    Io(std::io::Error),

    #[error("git exec wait: {0}")]
    Wait(std::io::Error),

    #[error("git exec spawn: {0}")]
    Spawn(std::io::Error),

    #[error("validation: {0}")]
    Validation(String),

    #[error("git clone: {0}")]
    Clone(String),

    #[error("open: {0}")]
    Open(String),

    #[error("git fetch: {0}")]
    Fetch(String),

    #[error("fake repository lock")]
    FakeLock,

    #[error("MismatchDefaultFetchRemote(found: {found:?}, expected: {expected:?})")]
    MismatchDefaultFetchRemote {
        found: Box<RemoteUrl>,
        expected: Box<RemoteUrl>,
    },
}

mod gix_errors {
    #![cfg(not(tarpaulin_include))] // third-party library errors
    use super::Error;
    impl From<gix::clone::Error> for Error {
        fn from(value: gix::clone::Error) -> Self {
            Self::Clone(value.to_string())
        }
    }
    impl From<gix::open::Error> for Error {
        fn from(value: gix::open::Error) -> Self {
            Self::Open(value.to_string())
        }
    }
    impl From<gix::clone::fetch::Error> for Error {
        fn from(value: gix::clone::fetch::Error) -> Self {
            Self::Fetch(value.to_string())
        }
    }
}