use std::{fs::File, io, process::Command};
use anyhow::Result;
use clap::Parser;
use serde::Deserialize;
#[derive(Debug, Parser)]
#[command(
version,
name = "pullcode",
about = "Pull code from remote repositories through a YAML file."
)]
struct Args {
path: String,
}
#[derive(Debug, Deserialize)]
struct Projects {
git_name: String,
git_email: String,
repos: Vec<Repository>,
}
#[derive(Debug, Deserialize)]
struct Repository {
name: String,
url: String,
}
fn main() -> Result<()> {
let args = Args::parse();
let file = File::open(args.path)?;
let projects: Projects = serde_yaml_bw::from_reader(file)?;
println!(
"start to git clone projects, count: {}",
projects.repos.len()
);
for repo in projects.repos {
if let Err(e) = git_clone(
&projects.git_name,
&projects.git_email,
&repo.name,
&repo.url,
) {
println!("git clone {} fail: {}", repo.url, e);
}
}
println!("pull code finished");
Ok(())
}
fn git_clone(git_name: &str, git_email: &str, name: &str, url: &str) -> Result<()> {
let mut git = Command::new("git");
git.arg("clone");
if !git_name.is_empty() {
git.args(["-c", &format!("user.name={}", git_name)]);
}
if !git_email.is_empty() {
git.args(["-c", &format!("user.email={}", git_email)]);
}
git.arg(url);
if name.ends_with('/') {
let repo_name = get_repo_name(url).ok_or(anyhow::anyhow!("Get repo name fail"))?;
git.arg(format!("{}{}", name, repo_name));
} else if !name.is_empty() {
git.arg(name);
}
git.stdout(io::stdout()).status()?;
println!("git clone {} finished", url);
Ok(())
}
fn get_repo_name(url: &str) -> Option<&str> {
let end = url.rfind('.')?;
let start = url.rfind('/')?;
url.get(start + 1..end)
}
#[test]
fn test_get_repo_name() {
println!(
"{:?}",
get_repo_name("https://github.com/something/some_project.git")
);
}