A DVCS backend implements the Backend trait (allows 3rd party backends)
This commit is contained in:
parent
dbd65cdc27
commit
250e4372c4
|
@ -0,0 +1,149 @@
|
|||
use std::boxed::Box;
|
||||
use std::env::{set_current_dir as cd, current_dir as pwd};
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::process::Command;
|
||||
|
||||
use crate::backend::{Backend, Repo};
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct Git;
|
||||
|
||||
impl Backend for Git {
|
||||
fn download(&self, source: &str, dest: &Path) -> bool {
|
||||
Command::new("git")
|
||||
.arg("clone")
|
||||
.arg("--recursive")
|
||||
.arg(source)
|
||||
.arg(dest)
|
||||
.output() // To suppress (capture) output
|
||||
.expect("PROCESS ERROR!")
|
||||
.status
|
||||
.success()
|
||||
}
|
||||
|
||||
fn submodules(&self) -> Vec<PathBuf> {
|
||||
let cmd = Command::new("git")
|
||||
.arg("config")
|
||||
.arg("--file")
|
||||
.arg(".gitmodules")
|
||||
.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));
|
||||
}
|
||||
return results;
|
||||
} else {
|
||||
// No submodules found (even if .gitmodules exist but is empty)
|
||||
return Vec::new();
|
||||
}
|
||||
}
|
||||
|
||||
fn branch(&self) -> String {
|
||||
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()
|
||||
}
|
||||
|
||||
fn checkout(&self, target: &str) -> bool {
|
||||
let status = Command::new("git")
|
||||
.arg("checkout")
|
||||
.arg(target)
|
||||
.status()
|
||||
.expect("PROCESS ERROR!");
|
||||
status.success()
|
||||
}
|
||||
|
||||
fn has_updates(&self) -> bool {
|
||||
// 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;
|
||||
}
|
||||
|
||||
fn update(&self) -> bool {
|
||||
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 false;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Maybe this should be a generic implementation part of the trait?
|
||||
fn subupdate(&self) -> bool {
|
||||
let mut found_subupdates = false;
|
||||
let prev_dir = pwd().expect("failed to pwd");
|
||||
for subpath in self.submodules() {
|
||||
// Move into the submodule
|
||||
cd(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(Box::new(Git), "irrelevant", &pwd().expect("failed to pwd"), false);
|
||||
if subrepo.backend.update() {
|
||||
found_subupdates = true;
|
||||
}
|
||||
// Move back into main repo
|
||||
cd(&prev_dir).unwrap();
|
||||
}
|
||||
found_subupdates
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,46 @@
|
|||
use std::boxed::Box;
|
||||
use std::env::{set_current_dir as cd, current_dir as pwd};
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::process::Command;
|
||||
|
||||
use crate::backend::{Backend, Repo};
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct Mercurial;
|
||||
|
||||
impl Backend for Mercurial {
|
||||
#[allow(dead_code)]
|
||||
fn download(&self, _src: &str, _dest: &Path) -> bool {
|
||||
unimplemented!();
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
fn submodules(&self) -> Vec<PathBuf> {
|
||||
unimplemented!();
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
fn branch(&self) -> String {
|
||||
unimplemented!();
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
fn checkout(&self, _branch: &str) -> bool {
|
||||
unimplemented!();
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
fn has_updates(&self) -> bool {
|
||||
unimplemented!();
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
fn update(&self) -> bool {
|
||||
unimplemented!();
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
fn subupdate(&self) -> bool {
|
||||
unimplemented!();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,105 @@
|
|||
use std::boxed::Box;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
mod git;
|
||||
mod mercurial;
|
||||
|
||||
pub use git::Git;
|
||||
pub use mercurial::Mercurial;
|
||||
|
||||
/// Generates Some(Backend) from an optional String, or defaults to Git,
|
||||
/// or None when the backend is not recognized. If you want to implement your own
|
||||
/// backend outside of this crate, you should override this method.
|
||||
/// TODO: fallback backend should be customizable
|
||||
pub fn backend(setting: Option<String>) -> Option<Box<dyn Backend>> {
|
||||
// Git is the default setting until further notice
|
||||
setting.map_or(Some(Box::new(Git)), |name| match name.as_ref() {
|
||||
"git" => Some(Box::new(Git)),
|
||||
"mercurial" => Some(Box::new(Mercurial)),
|
||||
_ => None,
|
||||
})
|
||||
}
|
||||
|
||||
/// The trait implemented by DVCS backends (git, mercurial).
|
||||
/// NOTE: This interface may evolve quickly. Currently no state is stored inside
|
||||
/// the backend and it's assumed the current working dir is the repository we're operating on
|
||||
pub trait Backend: std::fmt::Debug {
|
||||
/// Clone a src repository and all related submodules to dest
|
||||
fn download(&self, src: &str, dest: &Path) -> bool;
|
||||
|
||||
/// List all submodules in repo, as a list of PathBuf's
|
||||
/// Returns an empty vector when no submodules are declared
|
||||
fn submodules(&self) -> Vec<PathBuf>;
|
||||
|
||||
/// Returns the current branch/commit/tag tracked by the repo
|
||||
fn branch(&self) -> String;
|
||||
|
||||
/// Checkouts out a specific branch/commit/tag to track on the repo
|
||||
fn checkout(&self, branch: &str) -> bool;
|
||||
|
||||
/// Checks for updates in repo. Does not account for submodules
|
||||
fn has_updates(&self) -> bool;
|
||||
|
||||
/// Applies updates on the main repo, and on submodules if subupdates is enabled. Returns true
|
||||
/// if some updates were applied, false otherwise.
|
||||
fn update(&self) -> bool;
|
||||
|
||||
/// Applies submodule updates. Returns true if some updates were applied, false otherwise.
|
||||
fn subupdate(&self) -> bool;
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Repo {
|
||||
pub backend: Box<dyn Backend>,
|
||||
pub source: String,
|
||||
pub dest: PathBuf,
|
||||
pub subupdates: bool,
|
||||
}
|
||||
|
||||
impl Repo {
|
||||
pub fn new(backend: Box<dyn Backend>, source: &str, dest: &Path, subupdates: bool) -> Repo {
|
||||
Repo {
|
||||
backend,
|
||||
source: source.to_string(),
|
||||
dest: dest.to_path_buf(),
|
||||
subupdates,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn download(&self) -> bool {
|
||||
let success = self.backend.download(&self.source, &self.dest);
|
||||
|
||||
// If the clone was successful and subupdates is enabled,
|
||||
// List submodules and check them for updates
|
||||
if success && self.subupdates {
|
||||
self.subupdate();
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
pub fn checkout(&self, target: &str) -> bool {
|
||||
self.backend.checkout(target)
|
||||
}
|
||||
|
||||
pub fn has_updates(&self) -> bool {
|
||||
self.backend.has_updates()
|
||||
}
|
||||
|
||||
pub fn update(&self) -> bool {
|
||||
// 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() {
|
||||
self.backend.update() || had_subupdates
|
||||
} else {
|
||||
had_subupdates
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
pub fn subupdate(&self) -> bool {
|
||||
self.backend.subupdate()
|
||||
}
|
||||
}
|
242
src/dvcs.rs
242
src/dvcs.rs
|
@ -1,242 +0,0 @@
|
|||
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,
|
||||
}
|
||||
|
||||
impl Backend {
|
||||
/// Generates Some(Backend) from an optional String, or defaults to Git,
|
||||
/// or None when the backend is not recognized
|
||||
/// TODO: fallback backend should be customizable
|
||||
pub fn from_setting(setting: Option<String>) -> Option<Backend> {
|
||||
// Git is the default setting until further notice
|
||||
setting.map_or(Some(Backend::Git), |name| match name.as_ref() {
|
||||
"git" => Some(Backend::Git),
|
||||
"mercurial" => Some(Backend::Mercurial),
|
||||
_ => None,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[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!");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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");
|
||||
}
|
||||
};
|
||||
|
||||
// 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");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 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");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 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!();
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// 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");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -2,7 +2,7 @@ use std::env::{set_current_dir as cd, set_var};
|
|||
|
||||
mod cli;
|
||||
mod db;
|
||||
mod dvcs;
|
||||
mod backend;
|
||||
mod log;
|
||||
mod task;
|
||||
|
||||
|
@ -63,7 +63,7 @@ fn main() -> Result<(), std::io::Error> {
|
|||
let source_dir = format!("{}/.{}", basedir_str, &task.name);
|
||||
if task.cloned == false {
|
||||
task.info("clone");
|
||||
if !repo.clone() {
|
||||
if !repo.download() {
|
||||
task.error("clone_failed");
|
||||
// Skip further processing
|
||||
continue;
|
||||
|
|
|
@ -3,7 +3,7 @@ use std::path::{Path, PathBuf};
|
|||
use std::process::Command;
|
||||
|
||||
use crate::db::{is_executable, read_extension, read_or_none};
|
||||
use crate::dvcs::{Backend, Repo};
|
||||
use crate::backend::{Repo, backend};
|
||||
use crate::log;
|
||||
|
||||
#[derive(Debug)]
|
||||
|
@ -76,7 +76,7 @@ impl Task {
|
|||
context.insert("$i18n_source".to_string(), source_url.clone());
|
||||
}
|
||||
|
||||
let dvcs = if let Some(dvcs) = Backend::from_setting(read_extension(path, "dvcs")) { dvcs } else {
|
||||
let dvcs = if let Some(dvcs) = backend(read_extension(path, "dvcs")) { dvcs } else {
|
||||
// TODO: forgebuild error message
|
||||
eprintln!("Unrecognized dvcs for task {}, possible options are 'git' or 'mercurial'. Skipped", &name);
|
||||
return None;
|
||||
|
|
Loading…
Reference in New Issue