tci/src/tci.rs

112 lines
3.4 KiB
Rust
Raw Normal View History

2024-02-14 20:06:49 +01:00
use std::io::Read;
use crate::{config::{TciServerConfig, TciScriptConfig}, error::TciError, git::{shell_out, RefUpdate, RefUpdateVecHelper}};
lazy_static::lazy_static!{
static ref HOME : String = std::env::var("HOME").unwrap_or_default();
}
pub fn tci(cfg: TciServerConfig) -> Result<(), Box<dyn std::error::Error>> {
let repo_path = std::path::PathBuf::from(&std::env::var("GIT_DIR")?);
// check which branches are being updated
let mut input = String::new();
std::io::stdin().read_to_string(&mut input)?;
let updates : Vec<RefUpdate> = input.split('\n')
.filter_map(|l| {
let split : Vec<&str> = l.split(' ').collect();
Some(RefUpdate {
from: (*split.get(0)?).to_string(),
to: (*split.get(1)?).to_string(),
branch: (*split.get(2)?).to_string(),
})
})
.collect();
if let Some(branch) = cfg.branch {
if !updates.contains_branch(&branch) {
return Ok(()); // tci is not allowed to run on changes for this branch
}
}
// check config before even creating temp dir and cloning repo
let git_config = git2::Config::open(&repo_path.join("config"))?;
let tci_script = git_config.get_str("tci.script").unwrap_or(".tci").to_string();
// check if CI is allowed
if !cfg.allow_all
&& !cfg.run_setup_even_if_not_allowed
&& !git_config.get_bool("tci.allow").unwrap_or(false)
{
return Ok(()); // we are in whitelist mode and this repo is not whitelisted
}
// run hooks
for setup in cfg.setup {
println!("[+] setting up ({setup})");
shell_out(setup)?;
}
if !cfg.allow_all && !git_config.get_bool("tci.allow").unwrap_or(false) {
return Ok(()); // ewwww!!! ugly fix to allow running cgit update-agefile on every repo anyway
}
let res = tci_hook(&repo_path, &updates, tci_script);
for cleanup in cfg.cleanup {
println!("[-] cleaning up ({cleanup}) ");
shell_out(cleanup)?;
}
res?; // only check error AFTER running cleanup hooks
println!("[o] script run successfully");
Ok(())
}
pub fn tci_hook(repo_path: &std::path::Path, updates: &Vec<RefUpdate>, tci_script: String) -> Result<(), TciError> {
// TODO kind of ew but ehh should do its job
let mut name = repo_path.to_string_lossy()
.replace(HOME.as_str(), "");
if name.starts_with('/') {
name.remove(0);
}
if name.ends_with('/') {
name.remove(name.chars().count());
}
let tmp = tempdir::TempDir::new(&format!("tci-{}", name.replace('/', "_")))?;
// TODO allow customizing clone? just clone recursive? just let hook setup submodules?
git2::Repository::clone(
repo_path.to_str()
.ok_or(TciError::FsError("repo path is not a valid string"))?,
tmp.path(),
)?;
let tci_path = tmp.path().join(&tci_script);
let tci_config_path = tmp.path().join(tci_script + ".toml");
if tci_config_path.is_file() { // if .tci.toml is a config file, parse it for advanced CI options
let tci_config_raw = std::fs::read_to_string(tci_config_path)?;
let tci_config : TciScriptConfig = toml::from_str(&tci_config_raw)?;
if let Some(branch) = tci_config.branch {
if !updates.contains_branch(&branch) {
return Ok(()); // tci script should not be invoked for this branch
}
}
println!("[=] running tci pipeline for repo '{name}'");
Ok(())
} else if tci_path.is_file() { // if .tci is just one script, simply run it
println!("[=] running tci script for repo '{name}'");
std::env::set_current_dir(tmp.path())?;
let res = shell_out(tci_path);
std::env::set_current_dir(repo_path)?;
res
} else {
return Err(TciError::MissingScript);
}
}