147 lines
3.8 KiB
Rust
147 lines
3.8 KiB
Rust
|
use std::path::{Path, PathBuf};
|
||
|
|
||
|
#[derive(clap::Parser)]
|
||
|
struct Options {
|
||
|
/// Show more verbose statement.
|
||
|
#[clap(long, short)]
|
||
|
#[clap(global = true)]
|
||
|
#[clap(action = clap::ArgAction::Count)]
|
||
|
verbose: u8,
|
||
|
|
||
|
/// The subcommand.
|
||
|
#[clap(subcommand)]
|
||
|
command: Command,
|
||
|
}
|
||
|
|
||
|
#[derive(clap::Subcommand)]
|
||
|
enum Command {
|
||
|
Clone(CloneCommand),
|
||
|
Fetch(FetchCommand),
|
||
|
Push(PushCommand),
|
||
|
}
|
||
|
|
||
|
/// Clone a repository.
|
||
|
#[derive(clap::Parser)]
|
||
|
struct CloneCommand {
|
||
|
/// The URL of the repository to clone.
|
||
|
#[clap(value_name = "URL")]
|
||
|
repo: String,
|
||
|
|
||
|
/// The path where to clone the repository.
|
||
|
#[clap(value_name = "PATH")]
|
||
|
local_path: Option<PathBuf>,
|
||
|
}
|
||
|
|
||
|
/// Fetch from a remote.
|
||
|
#[derive(clap::Parser)]
|
||
|
struct FetchCommand {
|
||
|
/// The repository to operate on.
|
||
|
#[clap(value_name = "PATH")]
|
||
|
#[clap(short = 'C', long)]
|
||
|
repo: PathBuf,
|
||
|
|
||
|
/// The repository to operate on.
|
||
|
#[clap(value_name = "REMOTE")]
|
||
|
remote: String,
|
||
|
|
||
|
/// The refs to fetch.
|
||
|
#[clap(trailing_var_arg = true)]
|
||
|
#[clap(required = true)]
|
||
|
refspec: Vec<String>,
|
||
|
}
|
||
|
|
||
|
/// Push to a remote.
|
||
|
#[derive(clap::Parser)]
|
||
|
struct PushCommand {
|
||
|
/// The repository to operate on.
|
||
|
#[clap(value_name = "PATH")]
|
||
|
#[clap(short = 'C', long)]
|
||
|
#[clap(default_value = ".")]
|
||
|
repo: PathBuf,
|
||
|
|
||
|
/// The repository to operate on.
|
||
|
#[clap(value_name = "REMOTE")]
|
||
|
remote: String,
|
||
|
|
||
|
/// The refs to fetch.
|
||
|
#[clap(trailing_var_arg = true)]
|
||
|
#[clap(required = true)]
|
||
|
refspec: Vec<String>,
|
||
|
}
|
||
|
|
||
|
fn main() {
|
||
|
if let Err(()) = do_main(clap::Parser::parse()) {
|
||
|
std::process::exit(1);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
fn log_level(verbose: u8) -> log::LevelFilter {
|
||
|
match verbose {
|
||
|
0 => log::LevelFilter::Info,
|
||
|
1 => log::LevelFilter::Debug,
|
||
|
2.. => log::LevelFilter::Trace,
|
||
|
}
|
||
|
}
|
||
|
|
||
|
fn do_main(options: Options) -> Result<(), ()> {
|
||
|
let log_level = log_level(options.verbose);
|
||
|
env_logger::builder()
|
||
|
.parse_default_env()
|
||
|
.filter_module(module_path!(), log_level)
|
||
|
.filter_module("auth_git2", log_level)
|
||
|
.init();
|
||
|
|
||
|
match options.command {
|
||
|
Command::Clone(command) => clone(command),
|
||
|
Command::Fetch(command) => fetch(command),
|
||
|
Command::Push(command) => push(command),
|
||
|
}
|
||
|
}
|
||
|
|
||
|
fn clone(command: CloneCommand) -> Result<(), ()> {
|
||
|
let local_path = command.local_path.as_deref()
|
||
|
.unwrap_or_else(|| Path::new(repo_name_from_url(&command.repo)));
|
||
|
|
||
|
log::info!("Cloning {} into {}", command.repo, local_path.display());
|
||
|
|
||
|
let auth = auth_git2::GitAuthenticator::default();
|
||
|
auth.clone_repo(&command.repo, local_path)
|
||
|
.map_err(|e| log::error!("Failed to clone {}: {}", command.repo, e))?;
|
||
|
Ok(())
|
||
|
}
|
||
|
|
||
|
fn fetch(command: FetchCommand) -> Result<(), ()> {
|
||
|
let repo = git2::Repository::open(&command.repo)
|
||
|
.map_err(|e| log::error!("Failed to open git repo at {}: {e}", command.repo.display()))?;
|
||
|
|
||
|
let refspecs: Vec<_> = command.refspec.iter().map(|x| x.as_str()).collect();
|
||
|
|
||
|
let auth = auth_git2::GitAuthenticator::default();
|
||
|
let mut remote = repo.find_remote(&command.remote)
|
||
|
.map_err(|e| log::error!("Failed to find remote {:?}: {e}", command.remote))?;
|
||
|
auth.fetch(&repo, &mut remote, &refspecs, None)
|
||
|
.map_err(|e| log::error!("Failed to fetch from remote {:?}: {e}", command.remote))?;
|
||
|
Ok(())
|
||
|
}
|
||
|
|
||
|
fn push(command: PushCommand) -> Result<(), ()> {
|
||
|
let repo = git2::Repository::open(&command.repo)
|
||
|
.map_err(|e| log::error!("Failed to open git repo at {}: {e}", command.repo.display()))?;
|
||
|
|
||
|
log::info!("Fetching {:?} from remote {:?}", command.refspec, command.remote);
|
||
|
let refspecs: Vec<_> = command.refspec.iter().map(|x| x.as_str()).collect();
|
||
|
|
||
|
let auth = auth_git2::GitAuthenticator::default();
|
||
|
let mut remote = repo.find_remote(&command.remote)
|
||
|
.map_err(|e| log::error!("Failed to find remote {:?}: {e}", command.remote))?;
|
||
|
auth.push(&repo, &mut remote, &refspecs,)
|
||
|
.map_err(|e| log::error!("Failed to push to remote {:?}: {e}", command.remote))?;
|
||
|
Ok(())
|
||
|
}
|
||
|
|
||
|
fn repo_name_from_url(url: &str) -> &str {
|
||
|
url.rsplit_once('/')
|
||
|
.map(|(_head, tail)| tail)
|
||
|
.unwrap_or(url)
|
||
|
}
|