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"]
75 pub fn with_tag(self, name: &str, message: &str) -> AppResult<Self> {
76 self.repo.create_tag(name, "HEAD", message)?;
77 Ok(self)
78 }
79
80 pub fn repo(&self) -> &Repo {
82 &self.repo
83 }
84
85 pub fn root(&self) -> &Path {
87 &self.root
88 }
89}
90
91#[cfg(test)]
92mod tests {
93 use crate::{BranchFilter, LogOptions, LogReader, RefManager, Repository};
94
95 use super::RepoBuilder;
96
97 #[test]
98 fn repo_builder_creates_commits_and_refs() {
99 let builder = RepoBuilder::new()
100 .unwrap()
101 .with_file("README.md", "# repo\n")
102 .unwrap()
103 .with_commit("initial commit")
104 .unwrap()
105 .with_branch("feature")
106 .unwrap()
107 .with_tag("v1.0.0", "release")
108 .unwrap()
109 .with_checkout("feature")
110 .unwrap();
111
112 assert_eq!(
113 builder.repo().root().canonicalize().unwrap(),
114 builder.root().canonicalize().unwrap()
115 );
116
117 let commits = builder
118 .repo()
119 .log(Some(&LogOptions {
120 max_count: Some(1),
121 ..Default::default()
122 }))
123 .unwrap();
124 assert_eq!(commits[0].message, "initial commit");
125
126 let branches = builder.repo().list_branches(BranchFilter::Local).unwrap();
127 assert!(branches.iter().any(|branch| branch.name == "feature"));
128 let tags = builder.repo().list_tags().unwrap();
129 assert!(tags.iter().any(|tag| tag.name == "v1.0.0"));
130 }
131
132 #[test]
133 fn repo_builder_writes_files() {
134 let builder = RepoBuilder::new()
135 .unwrap()
136 .with_file("nested/file.txt", "hello\n")
137 .unwrap();
138
139 assert_eq!(
140 std::fs::read_to_string(builder.root().join("nested/file.txt")).unwrap(),
141 "hello\n"
142 );
143 }
144}