use clap::{Args, Command as ClapCommand, CommandFactory, Parser, ValueEnum};
use lib::git::NonZeroOid;
use std::fmt::Display;
use std::path::{Path, PathBuf};
use std::str::FromStr;
#[derive(Clone, Debug)]
pub struct Revset(pub String);
impl Revset {
pub fn default_smartlog_revset() -> Self {
Self("((draft() | branches() | @) % main()) | branches() | @".to_string())
}
}
impl FromStr for Revset {
type Err = std::convert::Infallible;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(Self(s.to_string()))
}
}
impl Display for Revset {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.0)
}
}
#[derive(Parser)]
pub enum WrappedCommand {
#[clap(external_subcommand)]
WrappedCommand(Vec<String>),
}
#[derive(Args, Debug, Default)]
pub struct ResolveRevsetOptions {
#[clap(action, long = "hidden")]
pub show_hidden_commits: bool,
}
#[derive(Args, Debug)]
pub struct MoveOptions {
#[clap(action, short = 'f', long = "force-rewrite", visible_alias = "fr")]
pub force_rewrite_public_commits: bool,
#[clap(action, long = "in-memory", conflicts_with_all(&["force_on_disk", "merge"]))]
pub force_in_memory: bool,
#[clap(action, long = "on-disk")]
pub force_on_disk: bool,
#[clap(action(clap::ArgAction::SetFalse), long = "no-deduplicate-commits")]
pub detect_duplicate_commits_via_patch_id: bool,
#[clap(action, name = "merge", short = 'm', long = "merge")]
pub resolve_merge_conflicts: bool,
#[clap(action, long = "debug-dump-rebase-constraints")]
pub dump_rebase_constraints: bool,
#[clap(action, long = "debug-dump-rebase-plan")]
pub dump_rebase_plan: bool,
}
#[derive(Args, Debug)]
pub struct TraverseCommitsOptions {
#[clap(value_parser)]
pub num_commits: Option<usize>,
#[clap(action, short = 'a', long = "all")]
pub all_the_way: bool,
#[clap(action, short = 'b', long = "branch")]
pub move_by_branches: bool,
#[clap(action, short = 'o', long = "oldest")]
pub oldest: bool,
#[clap(action, short = 'n', long = "newest", conflicts_with("oldest"))]
pub newest: bool,
#[clap(
action,
short = 'i',
long = "interactive",
conflicts_with("newest"),
conflicts_with("oldest")
)]
pub interactive: bool,
#[clap(action, short = 'm', long = "merge")]
pub merge: bool,
#[clap(action, short = 'f', long = "force", conflicts_with("merge"))]
pub force: bool,
}
#[derive(Args, Debug)]
pub struct SwitchOptions {
#[clap(action, short = 'i', long = "interactive")]
pub interactive: bool,
#[clap(value_parser, short = 'c', long = "create")]
pub branch_name: Option<String>,
#[clap(action, short = 'f', long = "force")]
pub force: bool,
#[clap(action, short = 'm', long = "merge", conflicts_with("force"))]
pub merge: bool,
#[clap(value_parser)]
pub target: Option<String>,
}
#[derive(Parser)]
pub enum Command {
Amend {
#[clap(flatten)]
move_options: MoveOptions,
},
BugReport,
Gc,
Hide {
#[clap(value_parser)]
revsets: Vec<Revset>,
#[clap(flatten)]
resolve_revset_options: ResolveRevsetOptions,
#[clap(action, short = 'D', long = "delete-branches")]
delete_branches: bool,
#[clap(action, short = 'r', long = "recursive")]
recursive: bool,
},
#[clap(hide = true)]
HookDetectEmptyCommit {
#[clap(value_parser)]
old_commit_oid: String,
},
#[clap(hide = true)]
HookPreAutoGc,
#[clap(hide = true)]
HookPostCheckout {
#[clap(value_parser)]
previous_commit: String,
#[clap(value_parser)]
current_commit: String,
#[clap(value_parser)]
is_branch_checkout: isize,
},
#[clap(hide = true)]
HookPostCommit,
#[clap(hide = true)]
HookPostMerge {
#[clap(value_parser)]
is_squash_merge: isize,
},
#[clap(hide = true)]
HookPostRewrite {
#[clap(value_parser)]
rewrite_type: String,
},
#[clap(hide = true)]
HookReferenceTransaction {
#[clap(value_parser)]
transaction_state: String,
},
#[clap(hide = true)]
HookRegisterExtraPostRewriteHook,
#[clap(hide = true)]
HookSkipUpstreamAppliedCommit {
#[clap(value_parser)]
commit_oid: String,
},
Init {
#[clap(action, long = "uninstall")]
uninstall: bool,
#[clap(value_parser, long = "main-branch", conflicts_with = "uninstall")]
main_branch_name: Option<String>,
},
Move {
#[clap(action(clap::ArgAction::Append), short = 's', long = "source")]
source: Vec<Revset>,
#[clap(
action(clap::ArgAction::Append),
short = 'b',
long = "base",
conflicts_with = "source"
)]
base: Vec<Revset>,
#[clap(
action(clap::ArgAction::Append),
short = 'x',
long = "exact",
conflicts_with_all(&["source", "base"])
)]
exact: Vec<Revset>,
#[clap(value_parser, short = 'd', long = "dest")]
dest: Option<Revset>,
#[clap(flatten)]
resolve_revset_options: ResolveRevsetOptions,
#[clap(flatten)]
move_options: MoveOptions,
#[clap(action, short = 'I', long = "insert")]
insert: bool,
},
Next {
#[clap(flatten)]
traverse_commits_options: TraverseCommitsOptions,
},
Prev {
#[clap(flatten)]
traverse_commits_options: TraverseCommitsOptions,
},
Query {
#[clap(value_parser)]
revset: Revset,
#[clap(flatten)]
resolve_revset_options: ResolveRevsetOptions,
#[clap(action, short = 'b', long = "branches")]
show_branches: bool,
#[clap(action, short = 'r', long = "raw", conflicts_with("show_branches"))]
raw: bool,
},
Repair {
#[clap(action(clap::ArgAction::SetFalse), long = "no-dry-run")]
dry_run: bool,
},
Restack {
#[clap(value_parser)]
revsets: Vec<Revset>,
#[clap(flatten)]
resolve_revset_options: ResolveRevsetOptions,
#[clap(flatten)]
move_options: MoveOptions,
},
Record {
#[clap(value_parser, short = 'm', long = "message")]
message: Option<String>,
#[clap(action, short = 'i', long = "interactive")]
interactive: bool,
#[clap(action, short = 'd', long = "detach")]
detach: bool,
},
Reword {
#[clap(value_parser)]
revsets: Vec<Revset>,
#[clap(flatten)]
resolve_revset_options: ResolveRevsetOptions,
#[clap(action, short = 'f', long = "force-rewrite", visible_alias = "fr")]
force_rewrite_public_commits: bool,
#[clap(value_parser, short = 'm', long = "message")]
messages: Vec<String>,
#[clap(action, short = 'd', long = "discard", conflicts_with("messages"))]
discard: bool,
#[clap(value_parser, long = "fixup", conflicts_with_all(&["messages", "discard"]))]
commit_to_fixup: Option<Revset>,
},
Smartlog {
#[clap(value_parser, long = "event-id")]
event_id: Option<isize>,
#[clap(value_parser)]
revset: Option<Revset>,
#[clap(flatten)]
resolve_revset_options: ResolveRevsetOptions,
},
#[clap(hide = true)]
Snapshot {
#[clap(subcommand)]
subcommand: SnapshotSubcommand,
},
Submit {
#[clap(action, short = 'c', long = "create")]
create: bool,
#[clap(value_parser, default_value = "stack()")]
revset: Revset,
#[clap(flatten)]
resolve_revset_options: ResolveRevsetOptions,
},
Switch {
#[clap(flatten)]
switch_options: SwitchOptions,
},
Sync {
#[clap(
action,
short = 'p',
long = "pull",
visible_short_alias = 'u',
visible_alias = "--update"
)]
pull: bool,
#[clap(flatten)]
move_options: MoveOptions,
#[clap(value_parser)]
revsets: Vec<Revset>,
#[clap(flatten)]
resolve_revset_options: ResolveRevsetOptions,
},
Test {
#[clap(subcommand)]
subcommand: TestSubcommand,
},
Undo {
#[clap(action, short = 'i', long = "interactive")]
interactive: bool,
#[clap(action, short = 'y', long = "yes")]
yes: bool,
},
Unhide {
#[clap(value_parser)]
revsets: Vec<Revset>,
#[clap(flatten)]
resolve_revset_options: ResolveRevsetOptions,
#[clap(action, short = 'r', long = "recursive")]
recursive: bool,
},
Wrap {
#[clap(value_parser, long = "git-executable")]
git_executable: Option<PathBuf>,
#[clap(subcommand)]
command: WrappedCommand,
},
}
#[derive(ValueEnum, Clone)]
pub enum ColorSetting {
Auto,
Always,
Never,
}
#[derive(Clone, Copy, Debug, ValueEnum)]
pub enum TestExecutionStrategy {
WorkingCopy,
Worktree,
}
#[derive(Parser)]
#[clap(version = env!("CARGO_PKG_VERSION"), author = "Waleed Khan <me@waleedkhan.name>")]
pub struct Opts {
#[clap(value_parser, short = 'C', global = true)]
pub working_directory: Option<PathBuf>,
#[clap(value_parser, long = "color", value_enum, global = true)]
pub color: Option<ColorSetting>,
#[clap(subcommand)]
pub command: Command,
}
#[derive(Parser)]
pub enum SnapshotSubcommand {
Create,
Restore {
#[clap(value_parser)]
snapshot_oid: NonZeroOid,
},
}
#[derive(Parser)]
pub enum TestSubcommand {
Run {
#[clap(value_parser, short = 'x', long = "exec")]
exec: Option<String>,
#[clap(value_parser, short = 'c', long = "command", conflicts_with("exec"))]
command: Option<String>,
#[clap(value_parser, default_value = "stack()")]
revset: Revset,
#[clap(flatten)]
resolve_revset_options: ResolveRevsetOptions,
#[clap(short = 'v', long = "verbose", action = clap::ArgAction::Count)]
verbosity: u8,
#[clap(short = 's', long = "strategy")]
strategy: Option<TestExecutionStrategy>,
#[clap(short = 'j', long = "jobs")]
jobs: Option<usize>,
},
Show {
#[clap(value_parser, short = 'x', long = "exec")]
exec: Option<String>,
#[clap(value_parser, short = 'c', long = "command", conflicts_with("exec"))]
command: Option<String>,
#[clap(value_parser, default_value = "stack()")]
revset: Revset,
#[clap(flatten)]
resolve_revset_options: ResolveRevsetOptions,
#[clap(short = 'v', long = "verbose", action = clap::ArgAction::Count)]
verbosity: u8,
},
Clean {
#[clap(value_parser, default_value = "stack()")]
revset: Revset,
#[clap(flatten)]
resolve_revset_options: ResolveRevsetOptions,
},
}
pub fn write_man_pages(man_dir: &Path) -> std::io::Result<()> {
let man1_dir = man_dir.join("man1");
std::fs::create_dir_all(&man1_dir)?;
let app = Opts::command();
generate_man_page(&man1_dir, "git-branchless", &app)?;
for subcommand in app.get_subcommands() {
let subcommand_exe_name = format!("git-branchless-{}", subcommand.get_name());
generate_man_page(&man1_dir, &subcommand_exe_name, subcommand)?;
}
Ok(())
}
fn generate_man_page(man1_dir: &Path, name: &str, command: &ClapCommand) -> std::io::Result<()> {
let rendered_man_page = {
let mut buffer = Vec::new();
clap_mangen::Man::new(command.clone()).render(&mut buffer)?;
buffer
};
let output_path = man1_dir.join(format!("{}.1", name));
std::fs::write(output_path, rendered_man_page)?;
Ok(())
}