use clap::{Parser, Subcommand};
use spr::{
commands,
error::{Error, Result},
output::output,
};
#[derive(Parser, Debug)]
#[clap(
name = "spr",
version,
about = "Submit pull requests for individual, amendable, rebaseable commits to GitHub"
)]
pub struct Cli {
#[clap(long, value_name = "DIR")]
cd: Option<String>,
#[clap(long)]
github_auth_token: Option<String>,
#[clap(long)]
github_repository: Option<String>,
#[clap(long)]
github_master_branch: Option<String>,
#[clap(long)]
branch_prefix: Option<String>,
#[clap(subcommand)]
command: Commands,
}
#[derive(Subcommand, Debug)]
enum Commands {
Init,
Diff(commands::diff::DiffOptions),
Format(commands::format::FormatOptions),
Land(commands::land::LandOptions),
Amend(commands::amend::AmendOptions),
List,
Patch(commands::patch::PatchOptions),
Close(commands::close::CloseOptions),
}
#[derive(Debug, thiserror::Error)]
pub enum OptionsError {
#[error(
"GitHub repository must be given as 'OWNER/REPO', but given value was '{0}'"
)]
InvalidRepository(String),
}
pub async fn spr() -> Result<()> {
let cli = Cli::parse();
if let Some(path) = &cli.cd {
if let Err(err) = std::env::set_current_dir(path) {
eprintln!("Could not change directory to {:?}", &path);
return Err(err.into());
}
}
if let Commands::Init = cli.command {
return commands::init::init().await;
}
let repo = git2::Repository::discover(std::env::current_dir()?)?;
let git_config = repo.config()?;
let github_repository = match cli.github_repository {
Some(v) => Ok(v),
None => git_config.get_string("spr.githubRepository"),
}?;
let github_master_branch = match cli.github_master_branch {
Some(v) => Ok::<String, git2::Error>(v),
None => git_config
.get_string("spr.githubMasterBranch")
.or_else(|_| Ok("master".to_string())),
}?;
let branch_prefix = match cli.branch_prefix {
Some(v) => Ok(v),
None => git_config.get_string("spr.branchPrefix"),
}?;
let (github_owner, github_repo) = {
let captures = lazy_regex::regex!(r#"^([\w\-\.]+)/([\w\-\.]+)$"#)
.captures(&github_repository)
.ok_or_else(|| {
OptionsError::InvalidRepository(github_repository.clone())
})?;
(
captures.get(1).unwrap().as_str().to_string(),
captures.get(2).unwrap().as_str().to_string(),
)
};
let github_remote_name = git_config
.get_string("spr.githubRemoteName")
.unwrap_or_else(|_| "origin".to_string());
let require_approval = git_config
.get_bool("spr.requireApproval")
.ok()
.unwrap_or(false);
let require_test_plan = git_config
.get_bool("spr.requireTestPlan")
.ok()
.unwrap_or(true);
let config = spr::config::Config::new(
github_owner,
github_repo,
github_remote_name,
github_master_branch,
branch_prefix,
require_approval,
require_test_plan,
);
let git = spr::git::Git::new(repo);
if let Commands::Format(opts) = cli.command {
return commands::format::format(opts, &git, &config).await;
}
let github_auth_token = match cli.github_auth_token {
Some(v) => Ok(v),
None => git_config.get_string("spr.githubAuthToken"),
}?;
octocrab::initialise(
octocrab::Octocrab::builder()
.personal_token(github_auth_token.clone())
.build()?,
);
let mut gh = spr::github::GitHub::new(config.clone(), git.clone());
match cli.command {
Commands::Diff(opts) => {
commands::diff::diff(opts, &git, &mut gh, &config).await?
}
Commands::Land(opts) => {
commands::land::land(opts, &git, &mut gh, &config).await?
}
Commands::Amend(opts) => {
commands::amend::amend(opts, &git, &mut gh, &config).await?
}
Commands::List => commands::list::list(&config).await?,
Commands::Patch(opts) => {
commands::patch::patch(opts, &git, &mut gh, &config).await?
}
Commands::Close(opts) => {
commands::close::close(opts, &git, &mut gh, &config).await?
}
Commands::Init | Commands::Format(_) => (),
};
Ok::<_, Error>(())
}
#[tokio::main]
async fn main() -> Result<()> {
if let Err(error) = spr().await {
for message in error.messages() {
output("🛑", message)?;
}
for message in error.cause_messages() {
output("caused by:", message)?;
}
std::process::exit(1);
}
Ok(())
}