use std::collections::BTreeMap;
use std::fs;
use std::io::Write;
use std::os::unix::fs::PermissionsExt;
use std::path::Path;
use serde::Deserialize;
use walkdir::WalkDir;
use crate::git_util;
const HOOK_FILE: &str = "hook.json";
const HOOK_DIR: &str = "hooks";
const HOOK_REPO: &str = "https://github.com/codebase-rs/hooks.git";
const HOOK_SCRIPT: &str = "script";
#[derive(Deserialize, Clone)]
pub struct Hook {
pub description: String,
pub author: String,
}
pub fn find<P: AsRef<Path>>(name: &str, config_dir: P) -> anyhow::Result<Option<Hook>> {
let hooks = hooks(&config_dir)?;
Ok(hooks
.iter()
.find(|(hook_name, _)| *hook_name == &name.to_string())
.map(|(_, hook)| hook.clone()))
}
pub fn hooks<P: AsRef<Path>>(config_dir: P) -> anyhow::Result<BTreeMap<String, Hook>> {
let hooks_dir = config_dir.as_ref().join(HOOK_DIR);
if !hooks_dir.exists() {
git2::Repository::clone(HOOK_REPO, &hooks_dir)?;
} else {
let repo = git2::Repository::open(&hooks_dir)?;
git_util::pull(&repo, "origin", "master")?;
}
let mut hooks: BTreeMap<String, Hook> = BTreeMap::new();
for entry in WalkDir::new(&hooks_dir)
.into_iter()
.filter_map(Result::ok)
.filter(|e| e.file_name().to_str().unwrap() == HOOK_FILE)
{
let hooks_dir = hooks_dir.to_str().unwrap();
let local_path: String = entry
.path()
.to_str()
.unwrap()
.replace(&format!("{}/", hooks_dir), "")
.replace(&format!("/{}", HOOK_FILE), "");
let hook_id = local_path;
let json = fs::read_to_string(&entry.path()).unwrap();
let json: Hook = serde_json::from_str(&json).unwrap();
hooks.insert(hook_id, json);
}
Ok(hooks)
}
pub fn install<A: AsRef<Path>, B: AsRef<Path>>(
hook_id: &str,
repo_path: A,
config_dir: B,
) -> anyhow::Result<()> {
hooks(&config_dir)?;
let src_path = config_dir
.as_ref()
.join(HOOK_DIR)
.join(hook_id)
.join(HOOK_SCRIPT);
if !src_path.exists() {
return Err(anyhow::anyhow!(format!(
"No script found for hook `{}`",
hook_id
)));
}
let content = fs::read_to_string(src_path)?;
let dst_path = repo_path.as_ref().join("hooks").join("pre-commit");
let mut file = fs::OpenOptions::new()
.write(true)
.create(true)
.truncate(true)
.open(&dst_path)?;
file.write_all(content.as_bytes())?;
let mut permissions = file.metadata()?.permissions();
permissions.set_mode(0o750);
file.set_permissions(permissions)?;
Ok(())
}