build.rs/src/dvcs.rs

270 lines
9.3 KiB
Rust

use std::env::{set_current_dir as cd, current_dir as pwd};
use std::path::{Path, PathBuf};
use std::process::Command;
#[derive(Debug, Clone)]
pub enum Backend {
Git,
Mercurial,
Unknown(String),
}
impl Backend {
pub fn from_setting(setting: Option<String>) -> Backend {
// Git is the default setting until further notice
setting.map_or(Backend::Git, |name| match name.as_ref() {
"git" => Backend::Git,
"mercurial" => Backend::Mercurial,
_ => Backend::Unknown(name.to_string()),
})
}
}
#[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) => {
// TODO: This should be a forgebuild error message
panic!("Uknown backend: {}. Cannot find branch", name);
}
}
}
pub fn clone(&self) -> bool {
let success = match &self.backend {
Backend::Git => {
let output = Command::new("git")
.arg("clone")
.arg("--recursive")
.arg(&self.source)
.arg(&self.dest)
.output() // To suppress (capture) output
.expect("PROCESS ERROR!");
output.status.success()
}
Backend::Mercurial => {
unreachable!("Unimplemented");
}
Backend::Unknown(name) => {
// TODO: This should be a forgebuild error message
eprintln!("Unknown DVCS: {}", name);
false
}
};
// If the clone was successful and subupdates is enabled,
// List submodules and check them for updates
if success && self.subupdates {
self.subupdate();
}
return success
}
/// Checks for updates from submodules. If any update was found, download them and return true.
pub fn subupdate(&self) -> bool {
let mut found_subupdates = false;
for subpath in self.submodules() {
// Move into the submodule
cd(self.dest.join(subpath)).unwrap();
// Generate a Repo instance for the submodule but don't enable subupdates
// Just in case someone would trigger an infinite loop by accident
let subrepo = Repo::new(self.backend.clone(), "irrelevant", &pwd().expect("Failed to read pwd"), false);
if subrepo.update() {
found_subupdates = true;
}
// Move back into main repo
cd(&self.dest).unwrap();
}
found_subupdates
}
pub fn checkout(&self, target: &str) -> bool {
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) => {
// TODO: This should be a forgebuild error message
eprintln!("Unknown DVCS: {}", name);
false
}
}
}
/// Parse the list of submodules in the current repository
/// When there is no submodules (eg. no .gitmodules file), return an empty Vec
pub fn submodules(&self) -> Vec<PathBuf> {
match &self.backend {
Backend::Git => {
let cmd = Command::new("git")
.arg("config")
.arg("--file")
.arg(&format!("{}/.gitmodules", self.dest.to_str().unwrap()))
.arg("--get-regexp")
.arg("path")
.output()
.expect("WTF");
if cmd.status.success() {
// Command succeded, split by the first space character to find the path to the submodule
let out = std::string::String::from_utf8(cmd.stdout).expect("Wrong unicode");
let mut results = Vec::new();
for line in out.lines() {
results.push(PathBuf::from(line.split_once(" ").expect("Misformed .gitmodules").1));
}
results
} else {
// No submodules found (even if .gitmodules exist but is empty)
Vec::new()
}
},
Backend::Mercurial => {
unimplemented!("Soon");
},
Backend::Unknown(name) => {
// TODO: This should be a forgebuild error message
eprintln!("Unknown DVCS:{}!", name);
Vec::new()
}
}
}
/// Check for updates on the main repository
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??
// TODO: This should be a forgebuild error message
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 => {
unimplemented!();
},
Backend::Unknown(name) => {
// TODO: This should be a forgebuild error message
eprintln!("Unknown DVCS:{}!", name);
false
}
}
}
/// Checks if the repository has updates, if so download them
/// Returns true if updates have been applied, false otherwise
/// Also applies submodules updates if self.subupdates is true
pub fn update(&self) -> bool {
match &self.backend {
Backend::Git => {
// First try to run submodules updates
let had_subupdates = if self.subupdates { self.subupdate() } else { false };
// Now run main repo updates
if self.has_updates() {
if Command::new("git")
.arg("pull")
.arg("--ff-only")
.arg("origin")
.arg(self.branch())
.status()
.expect("WTF")
.success()
{
// Main updates succeeded. If new submodules were added, initialize them
if !Command::new("git")
.arg("submodule")
.arg("update")
.arg("--init")
.arg("--recursive")
.status()
.expect("WTF")
.success()
{
// TODO: Should be forgebuild error message
eprintln!("Failed to initialize submodules which were added to the repo");
}
return true;
} else {
// Main updates failed, return true if there were some submodule updates
return had_subupdates;
}
}
// No main updates
return had_subupdates;
}
Backend::Mercurial => {
unreachable!("unimplemented");
}
Backend::Unknown(name) => {
// TODO: This should be a forgebuild error message
eprintln!("Unknown DVCS:{}!", name);
false
}
}
}
}