cargo_test_support/
git.rs
1use crate::{paths::CargoPathExt, project, Project, ProjectBuilder, SymlinkBuilder};
44use std::fs;
45use std::path::{Path, PathBuf};
46use std::sync::Once;
47use url::Url;
48
49#[must_use]
53pub struct RepoBuilder {
54 repo: git2::Repository,
55 files: Vec<PathBuf>,
56}
57
58pub struct Repository(git2::Repository);
60
61pub fn repo(p: &Path) -> RepoBuilder {
65 RepoBuilder::init(p)
66}
67
68impl RepoBuilder {
69 pub fn init(p: &Path) -> RepoBuilder {
70 t!(fs::create_dir_all(p.parent().unwrap()));
71 let repo = init(p);
72 RepoBuilder {
73 repo,
74 files: Vec::new(),
75 }
76 }
77
78 pub fn file(self, path: &str, contents: &str) -> RepoBuilder {
80 let mut me = self.nocommit_file(path, contents);
81 me.files.push(PathBuf::from(path));
82 me
83 }
84
85 pub fn nocommit_symlink_dir<T: AsRef<Path>>(self, dst: T, src: T) -> Self {
87 let workdir = self.repo.workdir().unwrap();
88 SymlinkBuilder::new_dir(workdir.join(dst), workdir.join(src)).mk();
89 self
90 }
91
92 pub fn nocommit_file(self, path: &str, contents: &str) -> RepoBuilder {
95 let dst = self.repo.workdir().unwrap().join(path);
96 t!(fs::create_dir_all(dst.parent().unwrap()));
97 t!(fs::write(&dst, contents));
98 self
99 }
100
101 pub fn build(self) -> Repository {
103 {
104 let mut index = t!(self.repo.index());
105 for file in self.files.iter() {
106 t!(index.add_path(file));
107 }
108 t!(index.write());
109 let id = t!(index.write_tree());
110 let tree = t!(self.repo.find_tree(id));
111 let sig = t!(self.repo.signature());
112 t!(self
113 .repo
114 .commit(Some("HEAD"), &sig, &sig, "Initial commit", &tree, &[]));
115 }
116 let RepoBuilder { repo, .. } = self;
117 Repository(repo)
118 }
119}
120
121impl Repository {
122 pub fn root(&self) -> &Path {
123 self.0.workdir().unwrap()
124 }
125
126 pub fn url(&self) -> Url {
127 self.0.workdir().unwrap().to_url()
128 }
129
130 pub fn revparse_head(&self) -> String {
131 self.0
132 .revparse_single("HEAD")
133 .expect("revparse HEAD")
134 .id()
135 .to_string()
136 }
137}
138
139pub fn init(path: &Path) -> git2::Repository {
141 default_search_path();
142 let repo = t!(git2::Repository::init(path));
143 default_repo_cfg(&repo);
144 repo
145}
146
147fn default_search_path() {
148 use crate::paths::global_root;
149 use git2::{opts::set_search_path, ConfigLevel};
150
151 static INIT: Once = Once::new();
152 INIT.call_once(|| unsafe {
153 let path = global_root().join("blank_git_search_path");
154 t!(set_search_path(ConfigLevel::System, &path));
155 t!(set_search_path(ConfigLevel::Global, &path));
156 t!(set_search_path(ConfigLevel::XDG, &path));
157 t!(set_search_path(ConfigLevel::ProgramData, &path));
158 })
159}
160
161fn default_repo_cfg(repo: &git2::Repository) {
162 let mut cfg = t!(repo.config());
163 t!(cfg.set_str("user.email", "foo@bar.com"));
164 t!(cfg.set_str("user.name", "Foo Bar"));
165}
166
167pub fn new<F>(name: &str, callback: F) -> Project
169where
170 F: FnOnce(ProjectBuilder) -> ProjectBuilder,
171{
172 new_repo(name, callback).0
173}
174
175pub fn new_repo<F>(name: &str, callback: F) -> (Project, git2::Repository)
177where
178 F: FnOnce(ProjectBuilder) -> ProjectBuilder,
179{
180 let mut git_project = project().at(name);
181 git_project = callback(git_project);
182 let git_project = git_project.build();
183
184 let repo = init(&git_project.root());
185 add(&repo);
186 commit(&repo);
187 (git_project, repo)
188}
189
190pub fn add(repo: &git2::Repository) {
192 let mut index = t!(repo.index());
193 t!(index.add_all(["*"].iter(), git2::IndexAddOption::DEFAULT, None));
194 t!(index.write());
195}
196
197pub fn add_submodule<'a>(
199 repo: &'a git2::Repository,
200 url: &str,
201 path: &Path,
202) -> git2::Submodule<'a> {
203 let path = path.to_str().unwrap().replace(r"\", "/");
204 let mut s = t!(repo.submodule(url, Path::new(&path), false));
205 let subrepo = t!(s.open());
206 default_repo_cfg(&subrepo);
207 t!(subrepo.remote_add_fetch("origin", "refs/heads/*:refs/heads/*"));
208 let mut origin = t!(subrepo.find_remote("origin"));
209 t!(origin.fetch(&Vec::<String>::new(), None, None));
210 t!(subrepo.checkout_head(None));
211 t!(s.add_finalize());
212 s
213}
214
215pub fn commit(repo: &git2::Repository) -> git2::Oid {
217 let tree_id = t!(t!(repo.index()).write_tree());
218 let sig = t!(repo.signature());
219 let mut parents = Vec::new();
220 if let Some(parent) = repo.head().ok().map(|h| h.target().unwrap()) {
221 parents.push(t!(repo.find_commit(parent)))
222 }
223 let parents = parents.iter().collect::<Vec<_>>();
224 t!(repo.commit(
225 Some("HEAD"),
226 &sig,
227 &sig,
228 "test",
229 &t!(repo.find_tree(tree_id)),
230 &parents
231 ))
232}
233
234pub fn tag(repo: &git2::Repository, name: &str) {
236 let head = repo.head().unwrap().target().unwrap();
237 t!(repo.tag(
238 name,
239 &t!(repo.find_object(head, None)),
240 &t!(repo.signature()),
241 "make a new tag",
242 false
243 ));
244}
245
246pub fn cargo_uses_gitoxide() -> bool {
250 std::env::var_os("__CARGO_USE_GITOXIDE_INSTEAD_OF_GIT2").map_or(false, |value| value == "1")
251}