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> { 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 = 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, 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); } }