use crate::{path2url, project, Project, ProjectBuilder, SymlinkBuilder};
use std::fs;
use std::path::{Path, PathBuf};
use std::sync::Once;
use url::Url;
#[must_use]
pub struct RepoBuilder {
repo: git2::Repository,
files: Vec<PathBuf>,
}
pub struct Repository(git2::Repository);
pub fn repo(p: &Path) -> RepoBuilder {
RepoBuilder::init(p)
}
impl RepoBuilder {
pub fn init(p: &Path) -> RepoBuilder {
t!(fs::create_dir_all(p.parent().unwrap()));
let repo = init(p);
RepoBuilder {
repo,
files: Vec::new(),
}
}
pub fn file(self, path: &str, contents: &str) -> RepoBuilder {
let mut me = self.nocommit_file(path, contents);
me.files.push(PathBuf::from(path));
me
}
pub fn nocommit_symlink_dir<T: AsRef<Path>>(self, dst: T, src: T) -> Self {
let workdir = self.repo.workdir().unwrap();
SymlinkBuilder::new_dir(workdir.join(dst), workdir.join(src)).mk();
self
}
pub fn nocommit_file(self, path: &str, contents: &str) -> RepoBuilder {
let dst = self.repo.workdir().unwrap().join(path);
t!(fs::create_dir_all(dst.parent().unwrap()));
t!(fs::write(&dst, contents));
self
}
pub fn build(self) -> Repository {
{
let mut index = t!(self.repo.index());
for file in self.files.iter() {
t!(index.add_path(file));
}
t!(index.write());
let id = t!(index.write_tree());
let tree = t!(self.repo.find_tree(id));
let sig = t!(self.repo.signature());
t!(self
.repo
.commit(Some("HEAD"), &sig, &sig, "Initial commit", &tree, &[]));
}
let RepoBuilder { repo, .. } = self;
Repository(repo)
}
}
impl Repository {
pub fn root(&self) -> &Path {
self.0.workdir().unwrap()
}
pub fn url(&self) -> Url {
path2url(self.0.workdir().unwrap().to_path_buf())
}
pub fn revparse_head(&self) -> String {
self.0
.revparse_single("HEAD")
.expect("revparse HEAD")
.id()
.to_string()
}
}
pub fn init(path: &Path) -> git2::Repository {
default_search_path();
let repo = t!(git2::Repository::init(path));
default_repo_cfg(&repo);
repo
}
fn default_search_path() {
use crate::paths::global_root;
use git2::{opts::set_search_path, ConfigLevel};
static INIT: Once = Once::new();
INIT.call_once(|| unsafe {
let path = global_root().join("blank_git_search_path");
t!(set_search_path(ConfigLevel::System, &path));
t!(set_search_path(ConfigLevel::Global, &path));
t!(set_search_path(ConfigLevel::XDG, &path));
t!(set_search_path(ConfigLevel::ProgramData, &path));
})
}
fn default_repo_cfg(repo: &git2::Repository) {
let mut cfg = t!(repo.config());
t!(cfg.set_str("user.email", "foo@bar.com"));
t!(cfg.set_str("user.name", "Foo Bar"));
}
pub fn new<F>(name: &str, callback: F) -> Project
where
F: FnOnce(ProjectBuilder) -> ProjectBuilder,
{
new_repo(name, callback).0
}
pub fn new_repo<F>(name: &str, callback: F) -> (Project, git2::Repository)
where
F: FnOnce(ProjectBuilder) -> ProjectBuilder,
{
let mut git_project = project().at(name);
git_project = callback(git_project);
let git_project = git_project.build();
let repo = init(&git_project.root());
add(&repo);
commit(&repo);
(git_project, repo)
}
pub fn add(repo: &git2::Repository) {
let mut index = t!(repo.index());
t!(index.add_all(["*"].iter(), git2::IndexAddOption::DEFAULT, None));
t!(index.write());
}
pub fn add_submodule<'a>(
repo: &'a git2::Repository,
url: &str,
path: &Path,
) -> git2::Submodule<'a> {
let path = path.to_str().unwrap().replace(r"\", "/");
let mut s = t!(repo.submodule(url, Path::new(&path), false));
let subrepo = t!(s.open());
default_repo_cfg(&subrepo);
t!(subrepo.remote_add_fetch("origin", "refs/heads/*:refs/heads/*"));
let mut origin = t!(subrepo.find_remote("origin"));
t!(origin.fetch(&Vec::<String>::new(), None, None));
t!(subrepo.checkout_head(None));
t!(s.add_finalize());
s
}
pub fn commit(repo: &git2::Repository) -> git2::Oid {
let tree_id = t!(t!(repo.index()).write_tree());
let sig = t!(repo.signature());
let mut parents = Vec::new();
if let Some(parent) = repo.head().ok().map(|h| h.target().unwrap()) {
parents.push(t!(repo.find_commit(parent)))
}
let parents = parents.iter().collect::<Vec<_>>();
t!(repo.commit(
Some("HEAD"),
&sig,
&sig,
"test",
&t!(repo.find_tree(tree_id)),
&parents
))
}
pub fn tag(repo: &git2::Repository, name: &str) {
let head = repo.head().unwrap().target().unwrap();
t!(repo.tag(
name,
&t!(repo.find_object(head, None)),
&t!(repo.signature()),
"make a new tag",
false
));
}
pub fn cargo_uses_gitoxide() -> bool {
std::env::var_os("__CARGO_USE_GITOXIDE_INSTEAD_OF_GIT2").map_or(false, |value| value == "1")
}