use std::ffi::OsString;
use std::path::{Path, PathBuf};
use clap::{Args, CommandFactory, FromArgMatches, Parser, Subcommand};
use outpost_core::ops;
use outpost_core::{BranchName, OutpostResult, RemoteName, SourceRemoteRef};
const ROOT_AFTER_HELP: &str = "Command-specific long flags: --remote-name, --reason, --verbose, --force, --no-branch-cleanup, --dry-run";
#[derive(Debug, Parser)]
#[command(
version,
about = "Manage self-contained Git outposts.",
disable_help_subcommand = true,
subcommand_required = true,
after_help = ROOT_AFTER_HELP
)]
pub struct Cli {
#[command(subcommand)]
pub command: Command,
#[arg(short = 'C', global = true, value_name = "PATH")]
pub cd: Option<PathBuf>,
#[arg(long, global = true)]
pub no_color: bool,
}
impl Cli {
pub fn try_parse_from_with_bin<I, T>(args: I, bin: &str) -> Result<Self, clap::Error>
where
I: IntoIterator<Item = T>,
T: Into<OsString> + Clone,
{
let matches = Self::command()
.bin_name(bin.to_owned())
.try_get_matches_from(args)?;
Self::from_arg_matches(&matches)
}
pub fn validate_refs(&self) -> OutpostResult<()> {
self.command.validate_refs()
}
}
#[derive(Debug, Subcommand)]
pub enum Command {
Add(AddArgs),
Pull(PullArgs),
Source(SourceArgs),
Merge(MergeArgs),
Rebase(RebaseArgs),
Push(PushArgs),
List(ListArgs),
Lock(LockArgs),
Unlock(UnlockArgs),
Move(MoveArgs),
Remove(RemoveArgs),
Prune(PruneArgs),
Status(StatusArgs),
Analyze(AnalyzeArgs),
}
impl Command {
fn validate_refs(&self) -> OutpostResult<()> {
match self {
Command::Add(args) => args.validate_refs(),
Command::Source(args) => args.validate_refs(),
Command::Merge(args) => args.validate_refs(),
Command::Rebase(args) => args.validate_refs(),
Command::Pull(_)
| Command::Push(_)
| Command::List(_)
| Command::Lock(_)
| Command::Unlock(_)
| Command::Move(_)
| Command::Remove(_)
| Command::Prune(_)
| Command::Status(_)
| Command::Analyze(_) => Ok(()),
}
}
}
#[derive(Debug, Args)]
pub struct AddArgs {
#[arg(value_name = "PATH")]
pub path: PathBuf,
#[arg(value_name = "TARGET-BRANCH")]
pub target_branch: Option<String>,
#[arg(short = 'b', value_name = "NEW-BRANCH")]
pub new_branch: Option<String>,
#[arg(long, default_value = "local", value_name = "NAME")]
pub remote_name: String,
}
impl AddArgs {
fn validate_refs(&self) -> OutpostResult<()> {
if let Some(new_branch) = &self.new_branch {
BranchName::parse(new_branch.clone())?;
}
if let Some(target_branch) = &self.target_branch {
BranchName::parse(target_branch.clone())?;
}
RemoteName::parse(self.remote_name.clone())?;
Ok(())
}
pub fn to_options(&self, cwd: &Path) -> OutpostResult<ops::add::AddOptions> {
let target_branch = self
.target_branch
.clone()
.map(BranchName::parse)
.transpose()?;
let checkout = match &self.new_branch {
Some(new_branch) => ops::add::AddCheckout::NewBranch {
name: BranchName::parse(new_branch.clone())?,
target_branch,
},
None => ops::add::AddCheckout::CheckoutExisting { target_branch },
};
Ok(ops::add::AddOptions {
destination: resolve_path_arg(cwd, self.path.clone()),
checkout,
remote_name: RemoteName::parse(self.remote_name.clone())?,
})
}
}
fn resolve_path_arg(cwd: &Path, path: PathBuf) -> PathBuf {
if path.is_absolute() {
path
} else {
cwd.join(path)
}
}
#[derive(Debug, Args)]
pub struct ListArgs {
#[arg(short = 'v', long)]
pub verbose: bool,
}
#[derive(Debug, Args)]
pub struct LockArgs {
#[arg(long, value_name = "STRING")]
pub reason: Option<String>,
#[arg(value_name = "OUTPOST")]
pub outpost_path: Option<PathBuf>,
}
#[derive(Debug, Args)]
pub struct UnlockArgs {
#[arg(value_name = "OUTPOST")]
pub outpost_path: Option<PathBuf>,
}
#[derive(Debug, Args)]
pub struct MoveArgs {
#[arg(value_name = "OUTPOST")]
pub outpost_path: PathBuf,
#[arg(value_name = "NEW-PATH")]
pub new_path: PathBuf,
#[arg(short = 'f', long)]
pub force: bool,
}
#[derive(Debug, Args)]
pub struct RemoveArgs {
#[arg(value_name = "OUTPOST")]
pub outpost_path: PathBuf,
#[arg(short = 'f', long)]
pub force: bool,
#[arg(long)]
pub no_branch_cleanup: bool,
}
#[derive(Debug, Args)]
pub struct PruneArgs {
#[arg(short = 'n', long)]
pub dry_run: bool,
#[arg(short = 'v', long)]
pub verbose: bool,
}
#[derive(Debug, Args)]
pub struct StatusArgs;
#[derive(Debug, Args)]
pub struct AnalyzeArgs {
#[arg(value_name = "OUTPOST")]
pub outpost_path: Option<PathBuf>,
}
#[derive(Debug, Args)]
pub struct PullArgs;
#[derive(Debug, Args)]
pub struct PushArgs;
#[derive(Debug, Args)]
pub struct MergeArgs {
#[arg(value_name = "SOURCE-REF")]
pub source_ref: String,
}
impl MergeArgs {
fn validate_refs(&self) -> OutpostResult<()> {
SourceRemoteRef::parse(self.source_ref.clone()).map(|_| ())
}
pub fn to_options(&self) -> OutpostResult<ops::merge::MergeOptions> {
Ok(ops::merge::MergeOptions {
source_ref: SourceRemoteRef::parse(self.source_ref.clone())?,
})
}
}
#[derive(Debug, Args)]
pub struct RebaseArgs {
#[arg(value_name = "SOURCE-REF")]
pub source_ref: String,
}
impl RebaseArgs {
fn validate_refs(&self) -> OutpostResult<()> {
SourceRemoteRef::parse(self.source_ref.clone()).map(|_| ())
}
pub fn to_options(&self) -> OutpostResult<ops::rebase::RebaseOptions> {
Ok(ops::rebase::RebaseOptions {
source_ref: SourceRemoteRef::parse(self.source_ref.clone())?,
})
}
}
#[derive(Debug, Args)]
pub struct SourceArgs {
#[command(subcommand)]
pub command: SourceCommand,
}
impl SourceArgs {
fn validate_refs(&self) -> OutpostResult<()> {
self.command.validate_refs()
}
}
#[derive(Debug, Subcommand)]
pub enum SourceCommand {
Pull(SourcePullArgs),
}
impl SourceCommand {
fn validate_refs(&self) -> OutpostResult<()> {
match self {
SourceCommand::Pull(args) => args.validate_refs(),
}
}
}
#[derive(Debug, Args)]
pub struct SourcePullArgs {
#[arg(value_name = "SOURCE-BRANCH")]
pub source_branch: String,
}
impl SourcePullArgs {
fn validate_refs(&self) -> OutpostResult<()> {
BranchName::parse(self.source_branch.clone()).map(|_| ())
}
pub fn to_options(&self) -> OutpostResult<ops::source::SourcePullOptions> {
Ok(ops::source::SourcePullOptions {
branch: BranchName::parse(self.source_branch.clone())?,
})
}
}