2024-02-14 20:06:49 +01:00
|
|
|
use std::io::Read;
|
|
|
|
|
2024-02-15 03:12:00 +01:00
|
|
|
use crate::{cfg::{TciConfig, TciScriptConfig}, error::{TciErr, TciResult}, git::{RefUpdateVecHelper, TciRepo}};
|
2024-02-14 20:06:49 +01:00
|
|
|
|
2024-02-15 03:04:53 +01:00
|
|
|
pub struct Tci {
|
|
|
|
cfg: TciConfig,
|
|
|
|
repo: TciRepo,
|
2024-02-15 03:12:00 +01:00
|
|
|
// stdin: String,
|
2024-02-14 20:06:49 +01:00
|
|
|
}
|
|
|
|
|
2024-02-15 03:04:53 +01:00
|
|
|
impl Tci {
|
|
|
|
pub fn new() -> TciResult<Self> {
|
|
|
|
let cfg = TciConfig::load()
|
|
|
|
.unwrap_or_else(|e| {
|
|
|
|
eprintln!("[!] invalid config: {e}");
|
|
|
|
TciConfig::default()
|
|
|
|
});
|
2024-02-14 20:06:49 +01:00
|
|
|
|
2024-02-15 03:04:53 +01:00
|
|
|
// check which branches are being updated
|
|
|
|
let mut stdin = String::new();
|
|
|
|
std::io::stdin().read_to_string(&mut stdin)
|
|
|
|
.unwrap_or_else(|e| {
|
|
|
|
eprintln!("[!] could not read refs from stdin: {e}");
|
|
|
|
0
|
|
|
|
});
|
2024-02-14 20:06:49 +01:00
|
|
|
|
2024-02-15 03:12:00 +01:00
|
|
|
let repo = TciRepo::new(&stdin)?;
|
2024-02-14 20:06:49 +01:00
|
|
|
|
2024-02-15 03:12:00 +01:00
|
|
|
Ok(Tci{cfg, repo})
|
2024-02-14 20:06:49 +01:00
|
|
|
}
|
|
|
|
|
2024-02-15 03:04:53 +01:00
|
|
|
/// check if tci is allowed to run for this repository
|
|
|
|
pub fn allowed(&self) -> bool {
|
|
|
|
if !self.cfg.allow_all && !self.repo.cfg.get_bool("tci.allow").unwrap_or(false) {
|
|
|
|
return false; // we are in whitelist mode and this repo is not whitelisted
|
|
|
|
}
|
|
|
|
|
|
|
|
if let Some(ref branch) = self.cfg.branch {
|
|
|
|
if !self.repo.updates.contains_branch(branch) {
|
|
|
|
return false; // tci is not allowed to run on changes for this branch
|
|
|
|
}
|
|
|
|
}
|
2024-02-14 20:06:49 +01:00
|
|
|
|
2024-02-15 03:04:53 +01:00
|
|
|
true
|
2024-02-14 20:06:49 +01:00
|
|
|
}
|
|
|
|
|
2024-02-15 03:04:53 +01:00
|
|
|
pub fn run(&self) -> TciResult<()> {
|
|
|
|
// run hooks
|
|
|
|
for hook in &self.cfg.hooks {
|
|
|
|
println!("[*] running hook ({hook})");
|
|
|
|
Tci::exec(&self.repo.path, hook)?;
|
|
|
|
}
|
|
|
|
|
|
|
|
if !self.allowed() { return Ok(()) }
|
2024-02-14 20:06:49 +01:00
|
|
|
|
2024-02-15 03:04:53 +01:00
|
|
|
let env = self.prepare_env()?;
|
2024-02-14 20:06:49 +01:00
|
|
|
|
2024-02-15 03:12:00 +01:00
|
|
|
let tci_script = self.repo.cfg.get_string("tci.path")
|
|
|
|
.unwrap_or_else(|_| ".tci".into());
|
|
|
|
let tci_path = env.path().join(tci_script);
|
2024-02-14 20:06:49 +01:00
|
|
|
|
2024-02-15 03:04:53 +01:00
|
|
|
if !tci_path.is_file() && !tci_path.is_dir() {
|
2024-02-15 03:12:00 +01:00
|
|
|
return Err(TciErr::Missing);
|
2024-02-15 03:04:53 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
for setup in &self.cfg.setup {
|
|
|
|
println!("[+] setting up ({setup})");
|
|
|
|
Tci::exec(&self.repo.path, setup)?;
|
|
|
|
}
|
|
|
|
|
|
|
|
let res = if tci_path.is_file() {
|
|
|
|
println!("[=] running tci script for {}", self.repo.name);
|
|
|
|
Tci::exec(env.path(), tci_path)
|
|
|
|
} else if tci_path.is_dir() {
|
2024-02-15 03:12:00 +01:00
|
|
|
self.run_tci_hook(env.path(), &tci_path)
|
2024-02-15 03:04:53 +01:00
|
|
|
} else { Ok(()) }; // will never happen but rustc doesn't know
|
|
|
|
|
|
|
|
for cleanup in &self.cfg.cleanup {
|
|
|
|
println!("[-] cleaning up ({cleanup}) ");
|
|
|
|
Tci::exec(&self.repo.path, cleanup)?;
|
|
|
|
}
|
|
|
|
|
|
|
|
res?; // if there was an error, return it now
|
|
|
|
|
|
|
|
println!("[o] tci complete");
|
|
|
|
|
|
|
|
Ok(())
|
2024-02-14 20:06:49 +01:00
|
|
|
}
|
2024-02-15 03:04:53 +01:00
|
|
|
|
|
|
|
fn prepare_env(&self) -> TciResult<tempdir::TempDir> {
|
|
|
|
let tmp = tempdir::TempDir::new(
|
|
|
|
&format!("tci-{}", self.repo.name.replace('/', "_"))
|
|
|
|
)?;
|
|
|
|
|
|
|
|
// TODO allow customizing clone? just clone recursive? just let hook setup submodules?
|
|
|
|
git2::Repository::clone(
|
|
|
|
self.repo.path.to_str()
|
2024-02-15 03:12:00 +01:00
|
|
|
.ok_or(TciErr::FsError("repo path is not a valid string"))?,
|
2024-02-15 03:04:53 +01:00
|
|
|
tmp.path(),
|
|
|
|
)?;
|
|
|
|
|
|
|
|
Ok(tmp)
|
2024-02-14 20:06:49 +01:00
|
|
|
}
|
|
|
|
|
2024-02-15 03:12:00 +01:00
|
|
|
fn run_tci_hook(&self, cwd: &std::path::Path, dir: &std::path::Path) -> TciResult<()> {
|
2024-02-15 03:04:53 +01:00
|
|
|
let cfg = TciScriptConfig::load(&dir.join("config.toml"))?;
|
2024-02-14 20:06:49 +01:00
|
|
|
|
2024-02-15 03:04:53 +01:00
|
|
|
if let Some(branch) = cfg.branch {
|
|
|
|
if !self.repo.updates.contains_branch(&branch) {
|
|
|
|
return Ok(()); // tci is not configured to run on this branch
|
|
|
|
}
|
|
|
|
}
|
2024-02-14 20:06:49 +01:00
|
|
|
|
2024-02-15 03:04:53 +01:00
|
|
|
// TODO calculate which files changed and decide if we should
|
|
|
|
// run depending on cfg.filter (regex match idk?)
|
2024-02-14 20:06:49 +01:00
|
|
|
|
2024-02-15 03:04:53 +01:00
|
|
|
for script in cfg.scripts {
|
|
|
|
println!("[=] running tci script '{script}' for {}", self.repo.name);
|
|
|
|
Tci::exec(cwd, dir.join(script))?;
|
2024-02-14 20:06:49 +01:00
|
|
|
}
|
2024-02-15 03:04:53 +01:00
|
|
|
|
2024-02-14 20:06:49 +01:00
|
|
|
Ok(())
|
2024-02-15 03:04:53 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
fn exec<S : AsRef<std::ffi::OsStr>>(cwd: &std::path::Path, s: S) -> TciResult<()> {
|
|
|
|
match std::process::Command::new(s)
|
|
|
|
.current_dir(cwd)
|
|
|
|
// .stdin(self.stdin.clone()) // TODO not this easy
|
|
|
|
.status()?
|
|
|
|
.code()
|
|
|
|
{
|
|
|
|
Some(0) => Ok(()),
|
2024-02-15 03:12:00 +01:00
|
|
|
Some(x) => Err(crate::error::TciErr::SubprocessError(x)),
|
|
|
|
None => Err(crate::error::TciErr::SubprocessTerminated),
|
2024-02-15 03:04:53 +01:00
|
|
|
}
|
2024-02-14 20:06:49 +01:00
|
|
|
}
|
|
|
|
}
|
2024-02-15 03:04:53 +01:00
|
|
|
|