diff --git a/src/dvcs.rs b/src/dvcs.rs index 79cbee9..4a91e18 100644 --- a/src/dvcs.rs +++ b/src/dvcs.rs @@ -1,4 +1,5 @@ use std::process::Command; +use std::path::{Path,PathBuf}; pub fn from_setting(setting: Option) -> Backend { // Git is the default setting until further notice @@ -16,15 +17,54 @@ pub enum Backend { Unknown(String), } -impl Backend { - pub fn clone(&self, source: &str, dest: &str) -> bool { - match self { +#[derive(Debug)] +pub struct Repo { + pub backend: Backend, + pub source: String, + pub dest: PathBuf, + pub subupdates: bool, +} + +impl Repo { + pub fn new(backend: Backend, source: &str, dest: &Path, subupdates: bool) -> Repo { + Repo { + backend, + source: source.to_string(), + dest: dest.to_path_buf(), + subupdates + } + } + + pub fn branch(&self) -> String { + match &self.backend { + Backend::Git => { + let output = Command::new("git") + .arg("rev-parse") + .arg("--abbrev-ref") + .arg("HEAD") + .output().expect("WTF"); + if !output.status.success() { + panic!("Corrupted git repository???"); + } + String::from_utf8(output.stdout).unwrap().trim().to_string() + }, + Backend::Mercurial => { + unreachable!("UNIMPLEMENTED!"); + }, + Backend::Unknown(name) => { + panic!("Uknown backend: {}. Cannot find ranch", name); + }, + } + } + + pub fn clone(&self) -> bool { + match &self.backend { Backend::Git => { let status = Command::new("git") .arg("clone") .arg("--recursive") - .arg(source) - .arg(dest) + .arg(&self.source) + .arg(&self.dest) .status().expect("PROCESS ERROR!"); status.success() }, @@ -38,6 +78,60 @@ impl Backend { } } + pub fn checkout(&self, target: &str) -> bool { + println!("CHECKOUT TO {}", target); + match &self.backend { + Backend::Git => { + let status = Command::new("git") + .arg("checkout") + .arg(target) + .status().expect("PROCESS ERROR!"); + status.success() + }, + Backend::Mercurial => { + unreachable!("UNIMPLEMENTED"); + }, + Backend::Unknown(name) => { + eprintln!("Unknown DVCS: {}", name); + false + } + } + } + + pub fn has_updates(&self) -> bool { + match &self.backend { + Backend::Git => { + // Refresh remote + if !Command::new("git") + .arg("fetch") + .arg("--quiet") + .arg("origin") + .status().expect("WTF").success() { + // FAILED, no internet?? + eprintln!("Fetching updates failed"); + return false; + } + let branch = self.branch(); + if Command::new("git") + .arg("diff") + .arg("--quiet") + .arg(format!("remotes/origin/{}", &branch)) + .status().expect("WTF").success() { + // Command succeeeded, no updates + return false; + } + // Updates + return true; + }, + Backend::Mercurial => { + unreachable!("unimplemented"); + }, + Backend::Unknown(name) => { + eprintln!("Unknown DVCS:{}!", name); + false + } + } + } /* /// Runs an update on the repository in dest. Returns true /// when an update was performed, false otherwise diff --git a/src/main.rs b/src/main.rs index eb22638..1cfd943 100644 --- a/src/main.rs +++ b/src/main.rs @@ -15,7 +15,7 @@ fn main() -> Result<(), std::io::Error> { std::env::set_var("GITBUILDDIR", &base_dir); - let tasks = if cmd.tasks.is_empty() { + let mut tasks = if cmd.tasks.is_empty() { task::from_dir(&base_dir).expect("Could not load DB") } else { task::from_dir_and_list(&base_dir, cmd.tasks).expect("Could not load given tasks") @@ -25,41 +25,52 @@ fn main() -> Result<(), std::io::Error> { std::env::set_var("GITBUILDCONF", &config_folder); - for (task_name, task) in tasks.iter() { - if ignored_tasks.contains(&task_name) { + // Reorder tasks alphanumerically + tasks.sort_unstable_by_key(|t| t.name.clone()); + // Remove duplicates, in case a task was called along + // the corresponding source URL (so we'd be tempted to call the task twice) + tasks.dedup_by_key(|t| t.name.clone()); + for task in &tasks { + if ignored_tasks.contains(&task.name) { // Skip task which has CONFIG/task.ignore continue; } - println!("TASK: {:?}", task_name); + println!("TASK: {:?}", task.name); let mut context = HashMap::new(); - context.insert("$i18n_task", task_name.as_str()); + context.insert("$i18n_task", task.name.as_str()); log::debug("found_task", Some(&context)); // Maybe the task has a source we should clone? - if let Some(source) = &task.source { - let source_dir = format!("{}/.{}", base_dir, task_name.as_str()); + if let Some(repo) = &task.repo { + let source_dir = format!("{}/.{}", base_dir, &task.name); if task.cloned == false { - if !task.dvcs.clone(source, &source_dir) { - context.insert("$i18n_source", &source); + if !repo.clone() { + context.insert("$i18n_source", &repo.source); log::error("clone_failed", Some(&context)); // Skip further processing continue } // New repo just cloned // TODO: submodule and submodule updates - println!("Downloaded source for {}", task_name); + println!("Downloaded source for {}", task.name); std::env::set_current_dir(&source_dir); + + // Checkout specific branch? + task.checkout(); task.run(); + } else { + // So the cloned repo is already here maybe update? + // Let's say there was an update and run + println!("Task {} already exists, run i t only if updates", task.name); + std::env::set_current_dir(&source_dir); + task.checkout(); + task.update_and_run(&cmd.force); + //task.run(); } - // So the cloned repo is already here maybe update? - // Let's say there was an update and run - println!("Task {} already exists, run it", task_name); - std::env::set_current_dir(&source_dir); - task.run(); } else { // No source, chaneg working dir to basedir std::env::set_current_dir(&base_dir); - println!("Taks {} doesn't have a source, run it", task_name); + println!("Taks {} doesn't have a source, run it", task.name); task.run_once(); } } diff --git a/src/task.rs b/src/task.rs index 3614498..55e12c6 100644 --- a/src/task.rs +++ b/src/task.rs @@ -1,23 +1,24 @@ use std::path::{Path, PathBuf}; -use std::ffi::OsString; use std::collections::HashMap; use std::process::Command; -//use crate::log; use crate::dvcs; +use crate::dvcs::{Backend,Repo}; use crate::db; use crate::db::{Entry,is_executable}; +use crate::log; #[derive(Debug)] pub struct Task { - pub name: OsString, + pub name: String, pub bin: PathBuf, pub source: Option, - pub dvcs: dvcs::Backend, + pub repo: Option, pub config: HashMap, pub branch: Option, pub hosts: Vec, pub cloned: bool, + pub subupdates: bool, } /// config returns an option of (settings directory, ignored tasks) as @@ -30,7 +31,7 @@ pub fn config(basedir: &Path) -> (PathBuf, Vec) { if path.is_dir() { let ignored = path.read_dir().unwrap().filter_map(|x| { if x.is_err() { return None; } - let mut name = x.unwrap().file_name().into_string().unwrap(); + let name = x.unwrap().file_name().into_string().unwrap(); if name.ends_with(".ignore") { return Some(name.trim_end_matches(".ignore").to_string()); } @@ -46,24 +47,42 @@ pub fn config(basedir: &Path) -> (PathBuf, Vec) { impl Task { pub fn from_entry(entry: &Entry) -> Task { let source = entry.read_setting("source"); + let dest = entry.base_dir.join(&format!(".{}", entry.name.to_str().expect("WTF"))); let cloned = source.clone().map_or(false, |_| { - let mut path = entry.base_dir.clone(); - path.push(format!(".{}", entry.name.to_str().expect("WTF"))); - path.is_dir() + dest.is_dir() }); + let subupdates = entry.read_setting("subupdates").is_some(); + let dvcs = dvcs::from_setting(entry.read_setting("dvcs")); + Task { - name: entry.name.clone(), + name: entry.name.to_str().unwrap().to_string(), bin: entry.path.clone(), + // None source = None repo + repo: source.as_ref().map(|s| + Repo::new(dvcs, s, &dest, subupdates) + ), source, - dvcs: dvcs::from_setting(entry.read_setting("dvcs")), config: HashMap::new(), - branch: entry.read_setting("branch"), + branch: entry.read_setting("checkout"), hosts: entry.read_setting("hosts").map_or(Vec::new(), |c| c.split("\n").map(|line| line.to_string()).collect()), cloned, + subupdates: entry.read_setting("subupdates").is_some(), } } + pub fn checkout(&self) { + if let Some(branch) = &self.branch { + println!("requesting branch"); + if let Some(repo) = &self.repo { + println!("has repo"); + repo.checkout(branch); + } + } else { + println!("no ranch requested"); + } + } + pub fn run_on_host(&self) -> bool { println!("{:#?}", self.hosts); if self.hosts.len() == 0 { @@ -81,9 +100,28 @@ impl Task { return false; } + pub fn update_and_run(&self, force: &bool) { + let mut context = HashMap::new(); + context.insert("$i18n_task", self.name.as_str()); + + if let Some(repo) = &self.repo { + if repo.has_updates() { + self.run(); + } else if *force { + log::debug("forcing", Some(&context)); + self.run(); + } else { + log::debug("no_update", Some(&context)); + } + } else { + unreachable!("this function should never be called on a task whcih doesnt have a repo"); + } + } + pub fn run(&self) { if !self.run_on_host() { return; } - let cmd_out = Command::new("bash") + + let cmd_out = Command::new("bash") // TODO: no need to call bash? .arg(&self.bin) .arg(&self.name) .output() @@ -105,41 +143,30 @@ impl Task { } // Takes an already instanced database (ie Vec) -// to turn into a dictionary of Tasks -pub fn from_entries(db: Vec) -> HashMap { - let mut res: HashMap = HashMap::new(); - for entry in db { - let task = Task::from_entry(&entry); - res.insert( - task.name.clone().into_string().expect("Failed to convert"), - task - ); - } - return res; +// to turn into a list of Tasks +pub fn from_entries(db: Vec) -> Vec { + db.iter().map(|x| Task::from_entry(&x)).collect() } -/// Returns a hashmap of tasks, or std::io::Error +/// Returns a list of tasks, or std::io::Error /// Reads all entries in a directory -pub fn from_dir(base_dir: &str) -> Result, std::io::Error> { +pub fn from_dir(base_dir: &str) -> Result, std::io::Error> { Ok(from_entries( db::from(base_dir, is_executable)? )) } -/// Returns a hashmap of tasks, or std::io::Error +/// Returns a list of tasks, or std::io::Error /// Reads entries in a given list from a directory, fails if a requested entry doesn't exist /// (does not load the whole folder) -pub fn from_dir_and_list(basedir: &str, list: Vec) -> Result, db::Error> { - let mut entries: HashMap = HashMap::new(); - for item in list { - if let Some(entry) = db::entry(&basedir, is_executable, &item) { - entries.insert(item.clone(), Task::from_entry(&entry)); +pub fn from_dir_and_list(basedir: &str, list: Vec) -> Result, db::Error> { + let mut tasks = Vec::new(); + for task in list { + if let Some(entry) = db::entry(&basedir, is_executable, &task) { + tasks.push(Task::from_entry(&entry)); } else { - return Err(db::Error::EntryNotFound(item.clone())) + return Err(db::Error::EntryNotFound(task.clone())); } } - - Ok( - entries - ) + Ok(tasks) }