gclone/src/git.rs

149 lines
4.0 KiB
Rust

//! This module contains the utility functions to manage git repository.
use std::env;
use std::fs::{create_dir_all, remove_dir_all};
use std::path::Path;
use std::process::{exit, Command, Stdio};
use lazy_static::lazy_static;
use crate::{Error, Result};
lazy_static! {
/// The directory in which the repositories will be cloned and searched.
pub static ref GCLONE_PATH: String = {
match env::var("GCLONE_PATH") {
Ok(d) => d,
Err(e) => {
let e: Error = e.into();
error!("couldn't read environnement variable GCLONE_PATH: {}", e);
exit(1);
},
}
};
}
macro_rules! unwrap {
($e: expr) => {
match $e {
Some(e) => e,
None => return Err(Error::GitUrlParseError),
}
};
}
/// Transforms a git url to a (server, owner, repository) tuple.
///
/// Returns none if it failed.
pub fn parse_url(input: &str) -> Result<(String, String, String)> {
if input.starts_with("http") {
parse_http_url(input)
} else if input.starts_with("git@") {
parse_ssh_url(input)
} else {
parse_github_url(input)
}
}
/// Parses an HTTP url for a git repository.
fn parse_http_url(input: &str) -> Result<(String, String, String)> {
let split = input.split("/").collect::<Vec<_>>();
if split.len() < 3 {
return Err(Error::GitUrlParseError);
}
let repo = String::from(split[split.len() - 1]);
let owner = String::from(split[split.len() - 2]);
let server = split[2..split.len() - 2].join("/");
Ok((server, owner, repo))
}
/// Parses an SSH url for a git repository.
fn parse_ssh_url(input: &str) -> Result<(String, String, String)> {
let url = unwrap!(input.split("@").nth(1));
let mut split = url.split(":");
let server = String::from(unwrap!(split.next()));
let rest = unwrap!(split.next());
let mut resplit = rest.split("/");
let owner = String::from(unwrap!(resplit.next()));
let repo = String::from(unwrap!(resplit.next()));
Ok((server, owner, repo))
}
/// Parses a github url in the format owner/repo.
fn parse_github_url(input: &str) -> Result<(String, String, String)> {
let split = input.split("/").collect::<Vec<_>>();
if split.len() != 2 {
return Err(Error::GitUrlParseError);
}
let server = String::from("github.com");
let owner = String::from(split[0]);
let repo = String::from(split[1]);
Ok((server, owner, repo))
}
/// Converts the parse result of a repo into an url.
pub fn parse_to_url(server: &str, owner: &str, repo: &str) -> String {
format!("git@{}:{}/{}", server, owner, repo)
}
/// Clones a git repository in the right place.
///
/// If an error happens, it deletes the directory created.
pub fn clone<P: AsRef<Path>>(url: &str, place: P) -> Result<()> {
if place.as_ref().exists() {
return Err(Error::PathAlreadyExists);
}
match clone_dirty(url, &place) {
Ok(o) => Ok(o),
Err(e) => {
remove_dir_all(&place).ok();
Err(e)
}
}
}
/// Rewrites an url to a SSH url.
pub fn rewrite_url(url: &str) -> Result<String> {
let (server, owner, repo) = parse_url(url)?;
Ok(parse_to_url(&server, &owner, &repo))
}
/// Clones a git repository in the right place.
fn clone_dirty<P: AsRef<Path>>(url: &str, place: P) -> Result<()> {
let place = place.as_ref();
// Need to create the parent dir only if it exists
if let Some(parent) = place.parent() {
create_dir_all(parent)?;
}
let url = if env::var("GCLONE_FORCE_SSH") == Ok("true".to_owned()) {
rewrite_url(url)?
} else {
url.to_owned()
};
let command = Command::new("git")
.args(&[
"clone",
"--recurse-submodules",
&url,
&place.display().to_string(),
])
.stderr(Stdio::null())
.status();
match command {
Err(e) => Err(Error::GitCloneError(Some(e))),
Ok(o) if !o.success() => Err(Error::GitCloneError(None)),
Ok(_) => Ok(()),
}
}