use gix::{
config::Source,
refs::transaction::{Change, LogChange},
};
use std::{
error::Error,
fs::{self, OpenOptions, read_dir},
path::Path,
};
pub fn init_bare_repository(directory: &Path) -> Result<gix::Repository, Box<dyn Error>> {
let repo = gix::init_bare(directory)?;
let repo_path = repo.path();
let config_path = repo_path.join("config");
let mut config = gix::config::File::from_path_no_includes(config_path, Source::Local)?;
config.set_raw_value_by("core", None, "sharedrepository", "2")?;
config.set_raw_value_by("receive", None, "denyNonFastforwards", "false")?;
config.set_raw_value_by("receive", None, "denyDeletes", "false")?;
config.set_raw_value_by("receive", None, "denyCurrentBranch", "false")?;
config.set_raw_value_by("http", None, "receivepack", "true")?;
config.set_raw_value_by("http", None, "uploadpack", "true")?;
let mut file = OpenOptions::new()
.write(true)
.truncate(true)
.open(repo_path.join("config"))?;
config.write_to(&mut file)?;
let dest_hook = repo_path.join("hooks/post-update");
fs::copy(repo_path.join("hooks/post-update.sample"), &dest_hook)?;
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
let mut perms = fs::metadata(&dest_hook)?.permissions();
perms.set_mode(0o755);
fs::set_permissions(&dest_hook, perms)?;
}
fs::File::create_new(repo_path.join("info/refs"))?;
Ok(repo)
}
pub fn list_bare_repositories(path: &Path) -> Result<Vec<String>, Box<dyn Error>> {
if !path.is_dir() {
return Ok(Vec::new());
}
let repos = read_dir(path)?
.filter_map(Result::ok)
.filter_map(|entry| {
let entry_path = entry.path();
let repo = gix::open(&entry_path).ok()?;
if repo.is_bare() {
entry_path.file_name()?.to_str().map(String::from)
} else {
None
}
})
.collect();
Ok(repos)
}
pub fn set_repository_head(repo_path: &Path, branch: &str) -> Result<(), Box<dyn Error>> {
let repository = gix::open(repo_path)?;
if !repository.is_bare() {
return Err(Box::<dyn Error>::from(format!(
"'{}' is not a bare repository",
repo_path.display()
)));
}
let branch_full_name = format!("refs/heads/{branch}");
let edit = gix::refs::transaction::RefEdit {
change: Change::Update {
log: LogChange::default(),
expected: gix::refs::transaction::PreviousValue::Any,
new: gix::refs::Target::Symbolic(branch_full_name.try_into()?),
},
name: "HEAD".try_into()?,
deref: false,
};
repository.edit_reference(edit)?;
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
use std::fs;
use tempfile::tempdir;
#[test]
fn test_init_bare_repository() {
let temp_dir = tempdir().unwrap();
let repo_path = temp_dir.path().join("test_repo.git");
let repo = init_bare_repository(&repo_path).unwrap();
assert!(repo.is_bare());
assert!(repo_path.exists());
assert!(repo_path.join("HEAD").exists());
assert!(repo_path.join("config").exists());
}
#[test]
fn test_list_bare_repositories() {
let temp_dir = tempdir().unwrap();
let base_path = temp_dir.path();
let bare_repo_path1 = base_path.join("repo1.git");
init_bare_repository(&bare_repo_path1).unwrap();
let bare_repo_path2 = base_path.join("repo2.git");
init_bare_repository(&bare_repo_path2).unwrap();
let non_bare_repo_path = base_path.join("non_bare");
gix::init(&non_bare_repo_path).unwrap();
let regular_dir = base_path.join("regular_dir");
fs::create_dir(®ular_dir).unwrap();
let mut repos = list_bare_repositories(base_path).unwrap();
repos.sort();
assert_eq!(repos.len(), 2);
assert_eq!(repos[0], "repo1.git");
assert_eq!(repos[1], "repo2.git");
}
}