Complete proper cloning
This commit is contained in:
parent
2f2292342a
commit
ea2e4574b7
188
src/main.rs
188
src/main.rs
|
@ -1,4 +1,3 @@
|
|||
use std::env;
|
||||
use std::fs;
|
||||
use std::io;
|
||||
use std::io::prelude::*;
|
||||
|
@ -14,7 +13,7 @@ fn main() {
|
|||
Ok(config) => config,
|
||||
Err(e) => { eprintln!("{:?}", e); process::exit(1) }
|
||||
};
|
||||
process_root_config(&root_config);
|
||||
process::exit(if process_root_config(&root_config) { 0 } else { 1 });
|
||||
}
|
||||
|
||||
fn load_file(path: &path::Path) -> Result<String, io::Error> {
|
||||
|
@ -24,88 +23,175 @@ fn load_file(path: &path::Path) -> Result<String, io::Error> {
|
|||
Ok(contents)
|
||||
}
|
||||
|
||||
/// Configuration file.
|
||||
#[derive(Debug, serde::Deserialize)]
|
||||
struct RootConfig {
|
||||
workdir: Option<String>,
|
||||
workspace: String,
|
||||
auth: serde_json::Value,
|
||||
configurations: Vec<Configuration>,
|
||||
}
|
||||
|
||||
// #[derive(Debug, serde::Deserialize)]
|
||||
// struct Authentication {
|
||||
// key: Option<String>,
|
||||
// }
|
||||
|
||||
fn process_root_config(root_config: &RootConfig) {
|
||||
// Ensure working directory exists and move to it.
|
||||
if let Some(workdir) = &root_config.workdir {
|
||||
let workdir = path::Path::new(&workdir);
|
||||
if !workdir.is_dir() {
|
||||
if let Err(e) = fs::create_dir_all(&workdir) {
|
||||
eprintln!("Failed to create working directory: {}.", e);
|
||||
return
|
||||
}
|
||||
}
|
||||
if let Err(e) = env::set_current_dir(&workdir) {
|
||||
eprintln!("Failed to move to working directory: {}.", e);
|
||||
return
|
||||
}
|
||||
}
|
||||
// Process each configuration.
|
||||
for config in &root_config.configurations {
|
||||
process_config(config);
|
||||
}
|
||||
/// Authentication options, unused at the moment.
|
||||
#[derive(Debug, serde::Deserialize)]
|
||||
struct Authentication {
|
||||
key: Option<String>,
|
||||
}
|
||||
|
||||
/// Server configuration.
|
||||
#[derive(Debug, serde::Deserialize)]
|
||||
struct Configuration {
|
||||
name: String,
|
||||
mirrors: Vec<Mirror>,
|
||||
}
|
||||
|
||||
/// Mirror configuration.
|
||||
#[derive(Debug, serde::Deserialize)]
|
||||
struct Mirror {
|
||||
name: String,
|
||||
src: String,
|
||||
dest: String,
|
||||
}
|
||||
|
||||
fn process_config(config: &Configuration) {
|
||||
println!("Processing config {}.", config.name);
|
||||
// Move into the configuration directory.
|
||||
let mut config_path = match env::current_dir() {
|
||||
Ok(pb) => pb,
|
||||
Err(e) => { eprintln!("Current directory is not available: {}.", e); return }
|
||||
};
|
||||
config_path.push(&config.name);
|
||||
if !config_path.is_dir() {
|
||||
if let Err(e) = fs::create_dir_all(&config_path) {
|
||||
eprintln!("Failed to create working directory: {}.", e);
|
||||
return
|
||||
/// Process the Mira configuration file, return true on complete success.
|
||||
fn process_root_config(root_config: &RootConfig) -> bool {
|
||||
// Ensure working directory exists and move to it.
|
||||
let workspace = path::Path::new(&root_config.workspace);
|
||||
if !workspace.is_dir() {
|
||||
if let Err(e) = fs::create_dir_all(&workspace) {
|
||||
eprintln!("Failed to create workspace directory: {}.", e);
|
||||
return false
|
||||
}
|
||||
}
|
||||
if let Err(e) = env::set_current_dir(&config_path) {
|
||||
eprintln!("Failed to move to working directory: {}.", e);
|
||||
return
|
||||
// Process each configuration, even if some of them fail.
|
||||
let mut complete_success = true;
|
||||
for config in &root_config.configurations {
|
||||
if let Err(e) = process_config(config, workspace) {
|
||||
eprintln!("An error occured with configuration {}: {}", config.name, e);
|
||||
complete_success = false;
|
||||
}
|
||||
}
|
||||
complete_success
|
||||
}
|
||||
|
||||
/// Result of a mirror operation.
|
||||
enum MirrorResult {
|
||||
Success,
|
||||
CloneFailed,
|
||||
RemotesError,
|
||||
PushFailed,
|
||||
}
|
||||
|
||||
/// Process mirrors of this server configuration.
|
||||
///
|
||||
/// If an IO error is met when preparing for the mirroring, this function returns early with this
|
||||
/// error. After that, all mirrors in `config` are processed, and the function returns true only if
|
||||
/// every mirror completes succesfully.
|
||||
fn process_config(config: &Configuration, workspace: &path::Path) -> Result<bool, io::Error> {
|
||||
println!("Processing config {}.", config.name);
|
||||
// Move into the configuration directory.
|
||||
let mut config_path = workspace.to_path_buf();
|
||||
config_path.push(&config.name);
|
||||
if !config_path.is_dir() {
|
||||
fs::create_dir_all(&config_path)?;
|
||||
}
|
||||
// Mirror each repository in the configuration.
|
||||
let mut complete_success = true;
|
||||
for mirror in &config.mirrors {
|
||||
mirror_repo(&mirror.src, &mirror.dest);
|
||||
match mirror_repo(&mirror.name, &mirror.src, &mirror.dest, &config_path) {
|
||||
Ok(MirrorResult::Success) => { println!("{} mirrored successfully.", mirror.name); },
|
||||
Ok(MirrorResult::CloneFailed) => {
|
||||
println!("Failed to clone {}.", mirror.name);
|
||||
complete_success = false;
|
||||
},
|
||||
Ok(MirrorResult::RemotesError) => {
|
||||
println!("Failed to process remotes for {}.", mirror.name);
|
||||
complete_success = false;
|
||||
},
|
||||
Ok(MirrorResult::PushFailed) => {
|
||||
println!("Failed to push {}.", mirror.name);
|
||||
complete_success = false;
|
||||
},
|
||||
Err(e) => {
|
||||
eprintln!("An error occured during {} mirroring: {}", mirror.name, e);
|
||||
complete_success = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(complete_success)
|
||||
}
|
||||
|
||||
fn mirror_repo(src_url: &str, dest_url: &str) -> bool {
|
||||
clone(src_url)
|
||||
/// Mirror a repository from `src_url` to `dest_url`.
|
||||
///
|
||||
/// This function assumes that the current work directory is the workspace,
|
||||
/// so that a directory named `name` can be used to clone and/or push from.
|
||||
fn mirror_repo(
|
||||
name: &str,
|
||||
src_url: &str,
|
||||
dest_url: &str,
|
||||
path: &path::Path
|
||||
) -> Result<MirrorResult, io::Error> {
|
||||
let mut repo_path = path.to_path_buf();
|
||||
repo_path.push(name);
|
||||
// Ensure the repository is cloned.
|
||||
if !repo_path.exists() {
|
||||
if !clone(src_url, path, name) {
|
||||
return Ok(MirrorResult::CloneFailed)
|
||||
}
|
||||
}
|
||||
// Ensure the mirror remote is available.
|
||||
let remotes = match get_remotes(&repo_path) {
|
||||
Some(remotes) => remotes,
|
||||
None => return Ok(MirrorResult::RemotesError)
|
||||
};
|
||||
// Push to the mirror repo.
|
||||
if !push(&repo_path, dest_url) {
|
||||
return Ok(MirrorResult::PushFailed)
|
||||
}
|
||||
Ok(MirrorResult::Success)
|
||||
}
|
||||
|
||||
fn run_git_command(args: Vec<&str>) -> bool {
|
||||
/// Run a git mirror clone command.
|
||||
fn clone(url: &str, path: &path::Path, name: &str) -> bool {
|
||||
let (success, _) = run_git_command_in(vec!("clone", "--mirror", url, name), path);
|
||||
success
|
||||
}
|
||||
|
||||
/// Return a vector of remote names on success.
|
||||
fn get_remotes(path: &path::Path) -> Option<Vec<String>> {
|
||||
let (success, stdout) = run_git_command_in(vec!("remote"), path);
|
||||
if !success || stdout.is_none() {
|
||||
return None
|
||||
}
|
||||
Some(stdout.unwrap().split_whitespace().map(|s| s.to_string()).collect())
|
||||
}
|
||||
|
||||
/// Run a git mirror push command.
|
||||
fn push(path: &path::Path, url: &str) -> bool {
|
||||
let (success, _) = run_git_command_in(vec!("push", "--mirror", url), path);
|
||||
success
|
||||
}
|
||||
|
||||
/// Run a git command with supplied arguments, return true on successful completion.
|
||||
fn run_git_command(args: Vec<&str>) -> (bool, Option<String>) {
|
||||
let mut command = process::Command::new("git");
|
||||
command.args(&args);
|
||||
match command.status() {
|
||||
Ok(status) => status.success(),
|
||||
Err(e) => { eprintln!("Failed to run Git: {}", e); false }
|
||||
match command.output() {
|
||||
Ok(output) => {
|
||||
let success = output.status.success();
|
||||
let stdout = String::from_utf8(output.stdout).ok();
|
||||
(success, stdout)
|
||||
}
|
||||
Err(e) => { eprintln!("Failed to run Git: {}", e); (false, None) }
|
||||
}
|
||||
}
|
||||
|
||||
fn clone(url: &str) -> bool {
|
||||
run_git_command(vec!("clone", "--mirror", url))
|
||||
/// Call `run_git_command` but with a work directory specified.
|
||||
fn run_git_command_in(args: Vec<&str>, path: &path::Path) -> (bool, Option<String>) {
|
||||
let path = match path.to_str() {
|
||||
Some(path) => path,
|
||||
None => { eprintln!("Invalid path: {:?}", path); return (false, None) }
|
||||
};
|
||||
let mut full_args = vec!("-C", path);
|
||||
full_args.extend(args.clone());
|
||||
run_git_command(full_args)
|
||||
}
|
||||
|
|
Reference in a new issue