mod.rs
raw
//! Fixtures: a temporary store of bare repositories built with git2, and
//! an App configured to serve it.
use std::{fs, path::Path, sync::Arc};
use clap::Parser;
use git2::{Repository, RepositoryInitOptions, Signature};
use mugit::{App, Config};
use tempfile::TempDir;
pub const PUBLIC_URL: &str = "http://git.test";
pub struct Fixture {
pub dir: TempDir,
pub app: Arc<App>,
}
impl Fixture {
pub fn new() -> Self {
Self::with_page_size(50)
}
pub fn with_page_size(page_size: u32) -> Self {
Self::with_args(&["--page-size", &page_size.to_string()])
}
/// A fixture whose app is configured with further command line
/// arguments on top of the usual ones.
pub fn with_args(args: &[&str]) -> Self {
let dir = tempfile::tempdir().expect("temp dir");
let usual = [
"mugit",
"--repos",
dir.path().to_str().unwrap(),
"--listen",
"127.0.0.1:0",
"--public-url",
PUBLIC_URL,
"--site-name",
"Test Site",
];
let config = Config::parse_from(usual.iter().chain(args));
let app = App::new(config).expect("app");
Fixture { dir, app }
}
/// Creates a bare repository with one commit containing `files`,
/// on branch `main`. File names may contain `/` to build directories.
pub fn repo(&self, name: &str, public: bool, description: &str, files: &[(&str, &str)]) {
let byte_files: Vec<(&str, &[u8])> =
files.iter().map(|(f, c)| (*f, c.as_bytes())).collect();
self.repo_bytes(name, public, description, &byte_files);
}
/// As `repo`, with arbitrary file contents.
pub fn repo_bytes(&self, name: &str, public: bool, description: &str, files: &[(&str, &[u8])]) {
let path = self.dir.path().join(format!("{name}.git"));
let mut options = RepositoryInitOptions::new();
options.bare(true).initial_head("main");
let repo = Repository::init_opts(&path, &options).expect("init bare repo");
if !files.is_empty() {
let tree = repo.find_tree(build_tree(&repo, files)).expect("find tree");
let sig = Signature::now("Test", "test@example.com").expect("signature");
repo.commit(Some("HEAD"), &sig, &sig, "initial", &tree, &[])
.expect("commit");
}
fs::write(path.join("description"), format!("{description}\n")).expect("description");
set_public(&path, public);
}
}
// Writes a tree for files whose names may contain directory separators.
fn build_tree(repo: &Repository, files: &[(&str, &[u8])]) -> git2::Oid {
let mut builder = repo.treebuilder(None).expect("tree builder");
let mut subdirs: Vec<(&str, Vec<(&str, &[u8])>)> = Vec::new();
for (file, content) in files {
match file.split_once('/') {
None => {
let oid = repo.blob(content).expect("blob");
builder.insert(file, oid, 0o100644).expect("tree entry");
}
Some((dir, rest)) => match subdirs.iter_mut().find(|(d, _)| *d == dir) {
Some((_, entries)) => entries.push((rest, content)),
None => subdirs.push((dir, vec![(rest, content)])),
},
}
}
for (dir, entries) in subdirs {
let oid = build_tree(repo, &entries);
builder.insert(dir, oid, 0o040000).expect("subtree entry");
}
builder.write().expect("tree")
}
pub fn set_public(repo_path: &Path, public: bool) {
let marker = repo_path.join(mugit::repos::EXPORT_MARKER);
if public {
fs::write(&marker, "").expect("export marker");
} else if marker.exists() {
fs::remove_file(&marker).expect("remove export marker");
}
}