use clap::{Args, Parser, Subcommand};
#[derive(Parser)]
#[command(
name = "git-meta",
version,
about = "Structured metadata for Git data",
disable_help_subcommand = true
)]
pub struct Cli {
#[command(subcommand)]
pub command: Commands,
}
#[derive(Subcommand)]
pub enum Commands {
#[command(display_order = 10)]
Set {
#[arg(short = 'F', long = "file")]
file: Option<String>,
#[arg(long)]
json: bool,
#[arg(long)]
timestamp: Option<i64>,
target: String,
key: String,
value: Option<String>,
},
#[command(display_order = 11)]
Get {
#[arg(long)]
json: bool,
#[arg(long = "with-authorship")]
with_authorship: bool,
target: String,
key: Option<String>,
},
#[command(display_order = 12)]
Rm {
target: String,
key: String,
},
#[command(name = "list:push", display_order = 13)]
ListPush {
target: String,
key: String,
value: String,
},
#[command(name = "list:pop", display_order = 14)]
ListPop {
target: String,
key: String,
value: String,
},
#[command(name = "list:rm", display_order = 15)]
ListRm {
target: String,
key: String,
index: Option<usize>,
},
#[command(name = "set:add", display_order = 16)]
SetAdd {
#[arg(long)]
json: bool,
#[arg(long)]
timestamp: Option<i64>,
target: String,
key: String,
value: String,
},
#[command(name = "set:rm", display_order = 17)]
SetRm {
#[arg(long)]
json: bool,
#[arg(long)]
timestamp: Option<i64>,
target: String,
key: String,
value: String,
},
#[command(display_order = 20)]
Show {
#[arg(value_name = "COMMIT")]
commit: String,
},
#[command(display_order = 21)]
Inspect {
target_type: Option<String>,
term: Option<String>,
#[arg(long)]
timeline: bool,
#[arg(long)]
promisor: bool,
},
#[command(display_order = 22)]
Stats,
#[command(display_order = 23)]
Log {
#[arg(value_name = "REF")]
start_ref: Option<String>,
#[arg(short = 'n', default_value = "20")]
count: usize,
#[arg(long = "mo")]
metadata_only: bool,
},
#[command(display_order = 30)]
Serialize {
#[arg(short = 'v', long)]
verbose: bool,
},
#[command(display_order = 31)]
Materialize {
remote: Option<String>,
#[arg(long = "dry-run")]
dry_run: bool,
#[arg(short = 'v', long)]
verbose: bool,
},
#[command(display_order = 32, hide = true)]
Import {
#[arg(long)]
format: String,
#[arg(long = "dry-run")]
dry_run: bool,
#[arg(long)]
since: Option<String>,
},
#[command(display_order = 33)]
Setup,
#[command(display_order = 34)]
Remote(RemoteArgs),
#[command(display_order = 35)]
Push {
remote: Option<String>,
#[arg(short = 'v', long)]
verbose: bool,
#[arg(long)]
readme: bool,
},
#[command(display_order = 36)]
Pull {
remote: Option<String>,
#[arg(short = 'v', long)]
verbose: bool,
},
#[command(display_order = 37, hide = true)]
Promisor,
#[command(display_order = 33, hide = true)]
Watch {
#[arg(long, default_value = "claude")]
agent: String,
#[arg(long, default_value = "30")]
debounce: u64,
},
#[command(display_order = 40)]
Config {
#[arg(long)]
list: bool,
#[arg(long)]
unset: bool,
key: Option<String>,
value: Option<String>,
},
#[command(name = "config:prune", display_order = 41, hide = true)]
ConfigPrune,
#[command(display_order = 42, hide = true)]
Prune {
#[arg(long = "dry-run")]
dry_run: bool,
},
#[command(name = "local-prune", display_order = 43, hide = true)]
LocalPrune {
#[arg(long = "dry-run")]
dry_run: bool,
#[arg(long = "skip-date")]
skip_date: bool,
},
#[command(display_order = 44)]
Teardown,
#[cfg(feature = "bench")]
Bench,
#[cfg(feature = "bench")]
FanoutBench {
#[arg(long, default_value = "1000000")]
objects: usize,
},
#[cfg(feature = "bench")]
HistoryWalker {
#[arg(long, default_value = "500")]
commits: usize,
},
#[cfg(feature = "bench")]
SerializeBench {
#[arg(long, default_value = "10")]
rounds: usize,
},
}
#[derive(Args)]
pub struct RemoteArgs {
#[command(subcommand)]
pub action: RemoteAction,
}
#[derive(Subcommand)]
pub enum RemoteAction {
Add {
url: String,
#[arg(long, default_value = "meta")]
name: String,
#[arg(long)]
namespace: Option<String>,
#[arg(long)]
init: bool,
},
Remove {
name: String,
},
List,
}
struct HelpGroup {
heading: &'static str,
sections: &'static [HelpSection],
}
struct HelpSection {
label: Option<&'static str>,
commands: &'static [&'static str],
}
const HELP_GROUPS: &[HelpGroup] = &[
HelpGroup {
heading: "read and write data",
sections: &[
HelpSection {
label: Some("(strings)"),
commands: &["set", "get", "rm"],
},
HelpSection {
label: Some("(lists)"),
commands: &["list:push", "list:pop", "list:rm"],
},
HelpSection {
label: Some("(sets)"),
commands: &["set:add", "set:rm"],
},
],
},
HelpGroup {
heading: "browse and exchange (porcelain)",
sections: &[
HelpSection {
label: None,
commands: &["push", "pull"],
},
HelpSection {
label: None,
commands: &["show", "inspect", "log", "stats"],
},
],
},
HelpGroup {
heading: "low-level git ref operations (plumbing)",
sections: &[HelpSection {
label: None,
commands: &["serialize", "materialize"],
}],
},
HelpGroup {
heading: "setup and configuration",
sections: &[HelpSection {
label: None,
commands: &["setup", "remote", "config", "teardown"],
}],
},
];
fn use_color() -> bool {
crate::style::use_color_stdout()
}
struct Palette {
bold: &'static str,
dim: &'static str,
yellow: &'static str,
green: &'static str,
reset: &'static str,
}
impl Palette {
fn detect() -> Self {
if use_color() {
Self {
bold: "\x1b[1m",
dim: "\x1b[2m",
yellow: "\x1b[33m",
green: "\x1b[32m",
reset: "\x1b[0m",
}
} else {
Self {
bold: "",
dim: "",
yellow: "",
green: "",
reset: "",
}
}
}
}
pub fn print_help() {
use clap::CommandFactory;
let cmd = Cli::command();
let p = Palette::detect();
let pad = HELP_GROUPS
.iter()
.flat_map(|g| g.sections.iter())
.flat_map(|s| s.commands.iter())
.map(|n| n.len())
.max()
.unwrap_or(0)
+ 4;
println!("{}usage:{} git meta <command> [options]", p.bold, p.reset);
println!();
println!("Structured metadata for Git data — attach values to commits, branches,");
println!("paths, and projects, and exchange them over normal git transport.");
println!();
println!("These are the most commonly used git meta commands:");
for group in HELP_GROUPS {
println!();
println!("{}{}{}{}", p.bold, p.yellow, group.heading, p.reset);
for (idx, section) in group.sections.iter().enumerate() {
if idx > 0 {
println!();
}
if let Some(label) = section.label {
println!(" {}{}{}", p.dim, label, p.reset);
}
for name in section.commands {
let about = cmd
.find_subcommand(name)
.and_then(|c| c.get_about())
.map(std::string::ToString::to_string)
.unwrap_or_default();
println!(
" {green}{name:<pad$}{reset}{about}",
green = p.green,
reset = p.reset,
);
}
}
}
println!();
println!(
"{dim}Run 'git meta <command> --help' for command-specific options.{reset}",
dim = p.dim,
reset = p.reset,
);
println!(
"{dim}See https://git-meta.com for the spec and full docs.{reset}",
dim = p.dim,
reset = p.reset,
);
}