rskit_git/testutil/
builder.rs1use std::fs;
2use std::path::{Path, PathBuf};
3
4use rskit_errors::{AppError, AppResult};
5use tempfile::TempDir;
6
7use crate::{CheckoutManager, Committer, ConfigReader, Differ, IndexManager, RefManager, Repo};
8
9pub struct RepoBuilder {
11 _tempdir: TempDir,
12 root: PathBuf,
13 repo: Repo,
14}
15
16impl RepoBuilder {
17 pub fn new() -> AppResult<Self> {
19 let tempdir = TempDir::new().map_err(AppError::internal)?;
20 let root = tempdir.path().to_path_buf();
21 let repo = crate::init(&root)?;
22 repo.config_set("user.name", "Test User")?;
23 repo.config_set("user.email", "test@example.com")?;
24 Ok(Self {
25 _tempdir: tempdir,
26 root,
27 repo,
28 })
29 }
30
31 #[must_use = "builder methods return the updated builder; chain or bind the result"]
33 pub fn with_file(self, path: &str, content: &str) -> AppResult<Self> {
34 let full_path = self.root.join(path);
35 if let Some(parent) = full_path.parent() {
36 fs::create_dir_all(parent).map_err(AppError::internal)?;
37 }
38 fs::write(full_path, content).map_err(AppError::internal)?;
39 Ok(self)
40 }
41
42 #[must_use = "builder methods return the updated builder; chain or bind the result"]
44 pub fn with_commit(self, message: &str) -> AppResult<Self> {
45 let paths = self
46 .repo
47 .status()?
48 .into_iter()
49 .map(|entry| entry.path)
50 .collect::<Vec<_>>();
51 let refs = paths.iter().map(String::as_str).collect::<Vec<_>>();
52 if !refs.is_empty() {
53 self.repo.stage(&refs)?;
54 }
55 self.repo.commit(message, None)?;
56 Ok(self)
57 }
58
59 #[must_use = "builder methods return the updated builder; chain or bind the result"]
61 pub fn with_branch(self, name: &str) -> AppResult<Self> {
62 self.repo.create_branch(name, "HEAD")?;
63 Ok(self)
64 }
65
66 #[must_use = "builder methods return the updated builder; chain or bind the result"]
68 pub fn with_checkout(self, branch: &str) -> AppResult<Self> {
69 self.repo.checkout(branch, None)?;
70 Ok(self)
71 }
72
73 #[must_use = "builder methods return the updated builder; chain or bind the result"]
76 pub fn with_tag(self, name: &str, message: Option<&str>) -> AppResult<Self> {
77 self.repo.create_tag(name, "HEAD", message)?;
78 Ok(self)
79 }
80
81 pub fn repo(&self) -> &Repo {
83 &self.repo
84 }
85
86 pub fn root(&self) -> &Path {
88 &self.root
89 }
90}
91
92#[cfg(test)]
93mod tests {
94 use crate::{BranchFilter, LogOptions, LogReader, RefManager, Repository};
95
96 use super::RepoBuilder;
97
98 #[test]
99 fn repo_builder_creates_commits_and_refs() {
100 let builder = RepoBuilder::new()
101 .unwrap()
102 .with_file("README.md", "# repo\n")
103 .unwrap()
104 .with_commit("initial commit")
105 .unwrap()
106 .with_branch("feature")
107 .unwrap()
108 .with_tag("v1.0.0", Some("release"))
109 .unwrap()
110 .with_checkout("feature")
111 .unwrap();
112
113 assert_eq!(
114 builder.repo().root().canonicalize().unwrap(),
115 builder.root().canonicalize().unwrap()
116 );
117
118 let commits = builder
119 .repo()
120 .log(Some(&LogOptions {
121 max_count: Some(1),
122 ..Default::default()
123 }))
124 .unwrap();
125 assert_eq!(commits[0].message, "initial commit");
126
127 let branches = builder.repo().list_branches(BranchFilter::Local).unwrap();
128 assert!(branches.iter().any(|branch| branch.name == "feature"));
129 let tags = builder.repo().list_tags().unwrap();
130 assert!(tags.iter().any(|tag| tag.name == "v1.0.0"));
131 }
132
133 #[test]
134 fn repo_builder_writes_files() {
135 let builder = RepoBuilder::new()
136 .unwrap()
137 .with_file("nested/file.txt", "hello\n")
138 .unwrap();
139
140 assert_eq!(
141 std::fs::read_to_string(builder.root().join("nested/file.txt")).unwrap(),
142 "hello\n"
143 );
144 }
145}