1use std::path::Path;
2
3use git_url_parse::GitUrlParseError;
5use thiserror::Error;
6
7mod domain;
8mod repository;
9mod url;
10
11pub use domain::{actor::Actor, commit::Commit, mfile::ModifiedFile};
12pub use repository::{Local, Remote, Repository};
13pub use url::GitUrl;
14
15pub fn open_repository<P: AsRef<Path>>(p: P) -> Result<Repository<Local>, Error> {
17 Repository::<Local>::new(p)
18}
19
20pub fn clone_repository<P: AsRef<Path>>(
22 url: &str,
23 dest: Option<P>,
24) -> Result<Repository<Local>, Error> {
25 Repository::<Remote>::new(url, dest)
26}
27
28#[derive(Debug, Error, PartialEq)]
29pub enum Error {
30 #[error(transparent)]
32 Git(#[from] git2::Error),
33
34 #[error(transparent)]
36 Regex(#[from] regex::Error),
37
38 #[error(transparent)]
40 GitUrlError(#[from] GitUrlParseError),
41
42 #[error("URL scheme was {0}, cannot clone URL.")]
44 UrlScheme(String),
45
46 #[error("{0}")]
48 PathError(String),
49
50 #[error("Cache mutex has been poisoned")]
52 PoisonedCache,
53}
54
55#[cfg(test)]
58mod common {
59 use std::sync::LazyLock;
60 use std::{fs, path::Path};
61 use tempfile::TempDir;
62
63 pub const EXPECTED_MSG: &str = "commit msg\n\nCo-authored-by: John Doe <john@example.com>\nCo-authored-by: John Doe <john@example.com>\nCo-authored-by: Dave <dave@example.com>";
64 pub const EXPECTED_ACTOR_NAME: &str = "test";
65 pub const EXPECTED_ACTOR_EMAIL: &str = "test@example.com";
66
67 fn write_fp(root: &TempDir, path: &str, content: &str) {
69 let fp = root.path().join(path);
70 fs::write(&fp, content).expect("Failed to write to file");
71 }
72
73 fn write_to_index(index: &mut git2::Index, file: &str) {
75 index
76 .add_path(Path::new(file))
77 .expect("Failed to add file to index");
78 index.write().expect("Failed to write index");
79 }
80
81 fn write_tree<'a>(
83 repo: &'a git2::Repository,
84 index: &mut git2::Index,
85 file: &str,
86 ) -> git2::Tree<'a> {
87 write_to_index(index, file);
88
89 let tree_id = index.write_tree().expect("Failed to write tree");
90 repo.find_tree(tree_id).expect("Failed to find tree")
91 }
92
93 fn commit_file(
95 repo: &git2::Repository,
96 sig: &git2::Signature,
97 file: &str,
98 parent: Option<&git2::Commit<'_>>,
99 ) -> git2::Oid {
100 let mut index = repo.index().expect("Failed to get index");
102 let tree = write_tree(repo, &mut index, file);
103
104 let parents = match parent {
105 Some(v) => vec![v],
106 None => vec![],
107 };
108
109 repo.commit(Some("HEAD"), sig, sig, EXPECTED_MSG, &tree, &parents)
110 .expect("Failed to create commit")
111 }
112
113 fn make_repo(tmpdir: &TempDir) {
115 let repo = git2::Repository::init(tmpdir.path()).expect("Failed to init repo");
116 let sig = git2::Signature::now(EXPECTED_ACTOR_NAME, EXPECTED_ACTOR_EMAIL)
117 .expect("Failed to create actor signature");
118
119 let file = "file.txt";
120 write_fp(tmpdir, file, "Hello World\n");
121 let first_commit_id = commit_file(&repo, &sig, file, None);
122
123 write_fp(tmpdir, file, "Hello World\nFile Update\n");
124 let parent = repo
125 .find_commit(first_commit_id)
126 .expect("Failed to find first commit");
127 commit_file(&repo, &sig, file, Some(&parent));
128 }
129
130 static TEST_DATA_DIR: LazyLock<TempDir> = LazyLock::new(|| {
133 let dir = TempDir::new().expect("Create temp dir");
134 make_repo(&dir);
135 dir
136 });
137
138 fn test_data_dir() -> &'static Path {
140 TEST_DATA_DIR.path()
141 }
142
143 pub fn init_repo() -> git2::Repository {
166 git2::Repository::open(test_data_dir()).expect("Failed to open temp repo")
167 }
168}