mod cmd;
mod format;
use std::path::PathBuf;
use clap::{Parser, Subcommand};
use miette::Result;
#[derive(Parser, Debug)]
#[command(
name = "schema",
version,
about = "Schematic version control — schema migration toolkit based on generalized algebraic theories"
)]
struct Cli {
#[arg(short, long, global = true)]
verbose: bool,
#[command(subcommand)]
command: Command,
}
#[derive(Subcommand, Debug)]
enum Command {
Validate {
#[arg(long)]
protocol: String,
schema: PathBuf,
},
Check {
#[arg(long)]
src: PathBuf,
#[arg(long)]
tgt: PathBuf,
#[arg(long)]
mapping: PathBuf,
#[arg(long)]
typecheck: bool,
},
Scaffold {
#[arg(long)]
protocol: String,
schema: PathBuf,
#[arg(long, default_value = "3")]
depth: usize,
#[arg(long, default_value = "1000")]
max_terms: usize,
#[arg(long)]
json: bool,
},
Normalize {
#[arg(long)]
protocol: String,
schema: PathBuf,
#[arg(long = "identify", value_delimiter = ',')]
identifications: Vec<String>,
#[arg(long)]
json: bool,
},
Typecheck {
#[arg(long)]
src: PathBuf,
#[arg(long)]
tgt: PathBuf,
#[arg(long)]
migration: PathBuf,
},
Verify {
#[arg(long)]
protocol: String,
schema: PathBuf,
#[arg(long, default_value = "10000")]
max_assignments: usize,
},
Init {
#[arg(default_value = ".")]
path: PathBuf,
#[arg(short = 'b', long = "initial-branch")]
initial_branch: Option<String>,
},
Add {
schema: PathBuf,
#[arg(short = 'n', long)]
dry_run: bool,
#[arg(short = 'f', long)]
force: bool,
#[arg(long)]
data: Option<PathBuf>,
},
Commit {
#[arg(short, long)]
message: String,
#[arg(long, default_value = "anonymous")]
author: String,
#[arg(long)]
amend: bool,
#[arg(long)]
allow_empty: bool,
#[arg(long)]
skip_verify: bool,
},
Status {
#[arg(short = 's', long)]
short: bool,
#[arg(long)]
porcelain: bool,
#[arg(short = 'b', long)]
branch: bool,
#[arg(long)]
data: Option<PathBuf>,
},
Log {
#[arg(short = 'n', long)]
limit: Option<usize>,
#[arg(long)]
oneline: bool,
#[arg(long)]
graph: bool,
#[arg(long)]
all: bool,
#[arg(long)]
format: Option<String>,
#[arg(long)]
author: Option<String>,
#[arg(long)]
grep: Option<String>,
#[arg(long)]
data: bool,
},
Diff {
old: Option<PathBuf>,
new: Option<PathBuf>,
#[arg(long)]
stat: bool,
#[arg(long)]
name_only: bool,
#[arg(long)]
name_status: bool,
#[arg(long, alias = "cached")]
staged: bool,
#[arg(long)]
detect_renames: bool,
#[arg(long)]
theory: bool,
#[arg(long)]
lens: bool,
#[arg(long)]
save: Option<PathBuf>,
#[arg(long)]
optic_kind: bool,
},
Show {
target: String,
#[arg(long)]
format: Option<String>,
#[arg(long)]
stat: bool,
},
Branch {
name: Option<String>,
#[arg(short, long)]
delete: bool,
#[arg(short = 'D')]
force_delete: bool,
#[arg(short = 'f', long)]
force: bool,
#[arg(short = 'm', long = "move")]
rename: Option<String>,
#[arg(short = 'v', long)]
verbose: bool,
#[arg(short = 'a', long)]
all: bool,
},
Tag {
name: Option<String>,
#[arg(short, long)]
delete: bool,
#[arg(short = 'a', long)]
annotate: bool,
#[arg(short = 'm', long)]
message: Option<String>,
#[arg(short = 'l', long)]
list: bool,
#[arg(short = 'f', long)]
force: bool,
},
Checkout {
target: String,
#[arg(short = 'b')]
create: bool,
#[arg(long)]
detach: bool,
#[arg(long)]
migrate: Option<PathBuf>,
},
Merge {
branch: Option<String>,
#[arg(long, default_value = "anonymous")]
author: String,
#[arg(long)]
no_commit: bool,
#[arg(long)]
ff_only: bool,
#[arg(long)]
no_ff: bool,
#[arg(long)]
squash: bool,
#[arg(long)]
abort: bool,
#[arg(short = 'm', long)]
message: Option<String>,
#[arg(short = 'v', long)]
verbose: bool,
#[arg(long)]
migrate: Option<PathBuf>,
},
Rebase {
onto: Option<String>,
#[arg(long, default_value = "anonymous")]
author: String,
#[arg(long)]
abort: bool,
#[arg(long, alias = "continue")]
cont: bool,
},
CherryPick {
commit: Option<String>,
#[arg(long, default_value = "anonymous")]
author: String,
#[arg(short = 'n', long)]
no_commit: bool,
#[arg(short = 'x')]
record_origin: bool,
#[arg(long)]
abort: bool,
},
Reset {
target: String,
#[arg(long)]
soft: bool,
#[arg(long)]
hard: bool,
#[arg(long, hide = true)]
mode: Option<String>,
#[arg(long, default_value = "anonymous")]
author: String,
},
Stash {
#[command(subcommand)]
action: StashAction,
},
Reflog {
#[arg(default_value = "HEAD")]
ref_name: String,
#[arg(short = 'n', long)]
limit: Option<usize>,
#[arg(long)]
all: bool,
},
Bisect {
good: String,
bad: String,
},
Blame {
#[arg(long)]
element_type: String,
element_id: String,
#[arg(long)]
reverse: bool,
},
Lift {
#[arg(long)]
migration: PathBuf,
#[arg(long)]
src_schema: PathBuf,
#[arg(long)]
tgt_schema: PathBuf,
record: PathBuf,
#[arg(long, default_value = "restrict")]
direction: String,
#[arg(long, default_value = "wtype")]
instance_type: String,
},
Integrate {
left: PathBuf,
right: PathBuf,
#[arg(long)]
auto_overlap: bool,
#[arg(long)]
json: bool,
},
AutoMigrate {
old: PathBuf,
new: PathBuf,
#[arg(long)]
monic: bool,
#[arg(long)]
json: bool,
},
Gc {
#[arg(long)]
dry_run: bool,
},
Expr {
#[command(subcommand)]
action: ExprAction,
},
Enrich {
#[command(subcommand)]
action: EnrichAction,
},
Remote {
#[command(subcommand)]
action: RemoteAction,
},
Push {
remote: Option<String>,
branch: Option<String>,
},
Pull {
remote: Option<String>,
branch: Option<String>,
},
Fetch {
remote: Option<String>,
},
Clone {
url: String,
path: Option<PathBuf>,
},
Data {
#[command(subcommand)]
action: DataAction,
},
Theory {
#[command(subcommand)]
action: TheoryAction,
},
Lens {
#[command(subcommand)]
action: LensAction,
},
Parse {
#[command(subcommand)]
action: ParseAction,
},
Git {
#[command(subcommand)]
action: GitAction,
},
}
#[derive(Subcommand, Debug)]
enum RemoteAction {
Add {
name: String,
url: String,
},
Remove {
name: String,
},
List,
}
#[derive(Subcommand, Debug)]
enum StashAction {
Push {
#[arg(short, long)]
message: Option<String>,
#[arg(long, default_value = "anonymous")]
author: String,
},
Pop,
List,
Drop,
Apply {
#[arg(default_value = "0")]
index: usize,
},
Show {
#[arg(default_value = "0")]
index: usize,
},
Clear,
}
#[derive(Subcommand, Debug)]
enum ExprAction {
GatEval {
file: PathBuf,
#[arg(long)]
env: Option<PathBuf>,
},
GatCheck {
file: PathBuf,
},
Repl,
Parse {
source: String,
},
Eval {
source: String,
},
Fmt {
source: String,
},
Check {
source: String,
},
}
#[derive(Subcommand, Debug)]
enum EnrichAction {
AddDefault {
vertex: String,
#[arg(long)]
expr: String,
},
AddCoercion {
from: String,
to: String,
#[arg(long)]
expr: String,
},
AddMerger {
vertex: String,
#[arg(long)]
expr: String,
},
AddPolicy {
vertex: String,
#[arg(long)]
strategy: String,
},
List,
Remove {
name: String,
},
}
#[derive(clap::Subcommand, Debug)]
enum TheoryAction {
Validate {
file: PathBuf,
},
Compile {
file: PathBuf,
#[arg(long)]
json: bool,
},
CompileDir {
dir: PathBuf,
},
CheckMorphism {
file: PathBuf,
},
Recompose {
file: PathBuf,
},
}
#[derive(Subcommand, Debug)]
enum LensAction {
Generate {
old: PathBuf,
new: PathBuf,
#[arg(long)]
protocol: String,
#[arg(long)]
json: bool,
#[arg(long)]
chain: bool,
#[arg(long)]
try_overlap: bool,
#[arg(long)]
save: Option<PathBuf>,
#[arg(long, value_delimiter = ',')]
defaults: Vec<String>,
#[arg(long)]
fuse: bool,
#[arg(long)]
requirements: bool,
#[arg(long)]
hints: Option<PathBuf>,
},
Apply {
chain: PathBuf,
data: PathBuf,
#[arg(long)]
protocol: String,
#[arg(long, default_value = "forward")]
direction: String,
#[arg(long)]
complement: Option<PathBuf>,
#[arg(long)]
schema: Option<PathBuf>,
},
Compose {
chain1: PathBuf,
chain2: PathBuf,
#[arg(long)]
protocol: String,
#[arg(long)]
json: bool,
#[arg(long)]
chain: bool,
},
Verify {
data: PathBuf,
#[arg(long)]
protocol: String,
schema: Option<PathBuf>,
},
Inspect {
chain: PathBuf,
#[arg(long)]
protocol: String,
},
Check {
chain: PathBuf,
schemas_dir: PathBuf,
#[arg(long)]
protocol: String,
#[arg(long)]
dry_run: bool,
},
Lift {
chain: PathBuf,
morphism: PathBuf,
#[arg(long)]
json: bool,
},
}
#[derive(Subcommand, Debug)]
enum DataAction {
Migrate {
data: PathBuf,
#[arg(long)]
protocol: Option<String>,
#[arg(long)]
range: Option<String>,
#[arg(long)]
dry_run: bool,
#[arg(short, long)]
output: Option<PathBuf>,
#[arg(long)]
backward: bool,
#[arg(long)]
coverage: bool,
},
Convert {
data: PathBuf,
#[arg(long)]
from: Option<PathBuf>,
#[arg(long)]
to: Option<PathBuf>,
#[arg(long)]
protocol: String,
#[arg(long)]
chain: Option<PathBuf>,
#[arg(short, long)]
output: Option<PathBuf>,
#[arg(long, default_value = "forward")]
direction: String,
#[arg(long, value_delimiter = ',')]
defaults: Vec<String>,
},
Sync {
data_dir: PathBuf,
#[arg(long)]
edits: bool,
#[arg(long)]
target: Option<String>,
},
Status {
data_dir: PathBuf,
},
}
#[derive(Subcommand, Debug)]
enum ParseAction {
File {
path: PathBuf,
},
Project {
#[arg(default_value = ".")]
path: PathBuf,
},
Emit {
path: PathBuf,
},
}
#[derive(Subcommand, Debug)]
enum GitAction {
Import {
repo: PathBuf,
#[arg(default_value = "HEAD")]
revspec: String,
},
Export {
#[arg(long, default_value = ".")]
repo: PathBuf,
dest: PathBuf,
},
}
fn main() -> Result<()> {
let cli = Cli::parse();
dispatch(cli.command, cli.verbose)
}
#[allow(clippy::too_many_lines)]
fn dispatch(command: Command, verbose: bool) -> Result<()> {
match command {
command @ (Command::Validate { .. }
| Command::Check { .. }
| Command::Lift { .. }
| Command::Integrate { .. }
| Command::AutoMigrate { .. }
| Command::Scaffold { .. }
| Command::Normalize { .. }
| Command::Typecheck { .. }
| Command::Verify { .. }) => dispatch_schema_commands(command, verbose),
Command::Theory { action } => dispatch_theory_commands(action, verbose),
Command::Lens { action } => dispatch_lens_commands(action, verbose),
Command::Data { action } => dispatch_data_commands(action, verbose),
Command::Init {
path,
initial_branch,
} => cmd::vcs::cmd_init(&path, initial_branch.as_deref()),
Command::Add {
schema,
dry_run,
force,
data,
} => cmd::vcs::cmd_add(&schema, dry_run, force, data.as_deref(), verbose),
Command::Commit {
message,
author,
amend,
allow_empty,
skip_verify,
} => cmd::vcs::cmd_commit(&message, &author, amend, allow_empty, skip_verify),
Command::Status {
short,
porcelain,
branch,
data,
} => cmd::vcs::cmd_status(short, porcelain, branch, data.as_deref()),
Command::Log {
limit,
oneline,
graph: _graph,
all: _all,
format,
author,
grep,
data,
} => cmd::vcs::cmd_log(&cmd::vcs::LogCmdOptions {
limit,
oneline,
fmt: format.as_deref(),
filter_author: author.as_deref(),
filter_grep: grep.as_deref(),
show_data: data,
}),
Command::Diff {
old,
new,
stat,
name_only,
name_status,
staged,
detect_renames,
theory,
lens,
save,
optic_kind,
} => {
let result = cmd::schema::cmd_diff(
old.as_deref(),
new.as_deref(),
&cmd::schema::DiffOptions {
stat,
name_only,
name_status,
staged,
verbose,
detect_renames,
theory,
optic_kind,
},
);
if lens {
if let (Some(old_path), Some(new_path)) = (old.as_deref(), new.as_deref()) {
let range = format!(
"{old}..{new}",
old = old_path.display(),
new = new_path.display(),
);
cmd::lens::cmd_lens_diff(&range, true, save.as_deref(), verbose)?;
}
}
result
}
Command::Show {
target,
format,
stat,
} => cmd::schema::cmd_show(&target, format.as_deref(), stat),
Command::Expr { action } => dispatch_expr_commands(action, verbose),
Command::Enrich { action } => dispatch_enrich_commands(action, verbose),
command @ (Command::Branch { .. }
| Command::Tag { .. }
| Command::Checkout { .. }
| Command::Merge { .. }) => dispatch_branch_commands(command),
command @ (Command::Rebase { .. }
| Command::CherryPick { .. }
| Command::Reset { .. }
| Command::Stash { .. }
| Command::Reflog { .. }
| Command::Bisect { .. }
| Command::Blame { .. }
| Command::Gc { .. }
| Command::Remote { .. }
| Command::Push { .. }
| Command::Pull { .. }
| Command::Fetch { .. }
| Command::Clone { .. }) => dispatch_history_commands(command),
Command::Parse { action } => dispatch_parse_commands(action, verbose),
Command::Git { action } => dispatch_git_commands(action, verbose),
}
}
#[allow(clippy::too_many_lines)]
fn dispatch_schema_commands(command: Command, verbose: bool) -> Result<()> {
match command {
Command::Validate { protocol, schema } => {
cmd::schema::cmd_validate(&protocol, &schema, verbose)
}
Command::Check {
src,
tgt,
mapping,
typecheck,
} => cmd::schema::cmd_check(&src, &tgt, &mapping, verbose, typecheck),
Command::Scaffold {
protocol,
schema,
depth,
max_terms,
json,
} => cmd::schema::cmd_scaffold(&protocol, &schema, depth, max_terms, json, verbose),
Command::Normalize {
protocol,
schema,
identifications,
json,
} => cmd::schema::cmd_normalize(&protocol, &schema, &identifications, json, verbose),
Command::Typecheck {
src,
tgt,
migration,
} => cmd::schema::cmd_typecheck(&src, &tgt, &migration, verbose),
Command::Verify {
protocol,
schema,
max_assignments,
} => cmd::schema::cmd_verify(&protocol, &schema, max_assignments, verbose),
Command::Lift {
migration,
src_schema,
tgt_schema,
record,
direction,
instance_type,
} => cmd::schema::cmd_lift(
&migration,
&src_schema,
&tgt_schema,
&record,
&direction,
&instance_type,
verbose,
),
Command::Integrate {
left,
right,
auto_overlap,
json,
} => cmd::schema::cmd_integrate(&left, &right, auto_overlap, json, verbose),
Command::AutoMigrate {
old,
new,
monic,
json,
} => cmd::schema::cmd_auto_migrate(&old, &new, monic, json, verbose),
_ => unreachable!(),
}
}
fn dispatch_branch_commands(command: Command) -> Result<()> {
match command {
Command::Branch {
name,
delete,
force_delete,
force,
rename,
verbose,
all,
} => cmd::branch::cmd_branch(&cmd::branch::BranchCmdOptions {
name: name.as_deref(),
delete,
force_delete,
force,
rename: rename.as_deref(),
verbose,
all,
}),
Command::Tag {
name,
delete,
annotate,
message,
list,
force,
} => cmd::branch::cmd_tag(&cmd::branch::TagCmdOptions {
name: name.as_deref(),
delete,
annotate,
message: message.as_deref(),
list,
force,
}),
Command::Checkout {
target,
create,
detach,
migrate,
} => cmd::branch::cmd_checkout(&target, create, detach, migrate.as_deref()),
Command::Merge {
branch,
author,
no_commit,
ff_only,
no_ff,
squash,
abort,
message,
verbose,
migrate,
} => cmd::branch::cmd_merge(
&cmd::branch::MergeCmdOptions {
branch: branch.as_deref(),
author: &author,
no_commit,
ff_only,
no_ff,
squash,
abort,
message: message.as_deref(),
verbose,
},
migrate.as_deref(),
),
_ => unreachable!(),
}
}
fn dispatch_history_commands(command: Command) -> Result<()> {
match command {
Command::Rebase {
onto,
author,
abort,
cont,
} => cmd::history::cmd_rebase(onto.as_deref(), &author, abort, cont),
Command::CherryPick {
commit,
author,
no_commit,
record_origin,
abort,
} => cmd::history::cmd_cherry_pick(
commit.as_deref(),
&author,
no_commit,
record_origin,
abort,
),
Command::Reset {
target,
soft,
hard,
mode,
author,
} => cmd::history::cmd_reset(&target, soft, hard, mode.as_deref(), &author),
Command::Stash { action } => cmd::history::cmd_stash(action),
Command::Reflog {
ref_name,
limit,
all,
} => cmd::history::cmd_reflog(&ref_name, limit, all),
Command::Bisect { good, bad } => cmd::history::cmd_bisect(&good, &bad),
Command::Blame {
element_type,
element_id,
reverse,
} => cmd::history::cmd_blame(&element_type, &element_id, reverse),
Command::Gc { dry_run } => cmd::history::cmd_gc(dry_run),
Command::Remote { action } => cmd::history::cmd_remote(action),
Command::Push { remote, branch } => {
cmd::history::cmd_push(remote.as_deref(), branch.as_deref())
}
Command::Pull { remote, branch } => {
cmd::history::cmd_pull(remote.as_deref(), branch.as_deref())
}
Command::Fetch { remote } => cmd::history::cmd_fetch(remote.as_deref()),
Command::Clone { url, path } => cmd::history::cmd_clone(&url, path.as_deref()),
_ => unreachable!(),
}
}
fn dispatch_expr_commands(action: ExprAction, verbose: bool) -> Result<()> {
match action {
ExprAction::GatEval { file, env } => {
cmd::expr::cmd_expr_gat_eval(&file, env.as_deref(), verbose)
}
ExprAction::GatCheck { file } => cmd::expr::cmd_expr_gat_check(&file, verbose),
ExprAction::Repl => cmd::expr::cmd_expr_repl(),
ExprAction::Parse { source } => cmd::expr::cmd_expr_parse(&source, verbose),
ExprAction::Eval { source } => cmd::expr::cmd_expr_eval_source(&source, verbose),
ExprAction::Fmt { source } => cmd::expr::cmd_expr_fmt(&source, verbose),
ExprAction::Check { source } => cmd::expr::cmd_expr_check_source(&source, verbose),
}
}
fn dispatch_enrich_commands(action: EnrichAction, verbose: bool) -> Result<()> {
match action {
EnrichAction::AddDefault { vertex, expr } => {
cmd::enrich::cmd_enrich_add_default(&vertex, &expr, verbose)
}
EnrichAction::AddCoercion { from, to, expr } => {
cmd::enrich::cmd_enrich_add_coercion(&from, &to, &expr, verbose)
}
EnrichAction::AddMerger { vertex, expr } => {
cmd::enrich::cmd_enrich_add_merger(&vertex, &expr, verbose)
}
EnrichAction::AddPolicy { vertex, strategy } => {
cmd::enrich::cmd_enrich_add_policy(&vertex, &strategy, verbose)
}
EnrichAction::List => cmd::enrich::cmd_enrich_list(verbose),
EnrichAction::Remove { name } => cmd::enrich::cmd_enrich_remove(&name, verbose),
}
}
fn dispatch_theory_commands(action: TheoryAction, verbose: bool) -> Result<()> {
match action {
TheoryAction::Validate { file } => cmd::theory::cmd_theory_validate(&file, verbose),
TheoryAction::Compile { file, json } => {
cmd::theory::cmd_theory_compile(&file, json, verbose)
}
TheoryAction::CompileDir { dir } => cmd::theory::cmd_theory_compile_dir(&dir, verbose),
TheoryAction::CheckMorphism { file } => {
cmd::theory::cmd_theory_check_morphism(&file, verbose)
}
TheoryAction::Recompose { file } => cmd::theory::cmd_theory_recompose(&file, verbose),
}
}
fn dispatch_lens_commands(action: LensAction, verbose: bool) -> Result<()> {
match action {
LensAction::Generate {
old,
new,
protocol,
json,
chain,
try_overlap,
save,
defaults,
fuse,
requirements,
hints,
} => cmd::lens::cmd_lens_generate(
&old,
&new,
&protocol,
json,
chain,
try_overlap,
save.as_deref(),
&defaults,
fuse,
requirements,
verbose,
hints.as_deref(),
),
LensAction::Apply {
chain,
data,
protocol,
direction,
complement,
schema,
} => cmd::lens::cmd_lens_apply(
&chain,
&data,
&protocol,
schema.as_deref(),
&direction,
complement.as_deref(),
verbose,
),
LensAction::Compose {
chain1,
chain2,
protocol,
json,
chain,
} => cmd::lens::cmd_lens_compose(&chain1, &chain2, &protocol, json, chain, verbose),
LensAction::Verify {
data,
protocol,
schema,
} => cmd::lens::cmd_lens_verify(&data, schema.as_deref(), &protocol, None, false, verbose),
LensAction::Inspect { chain, protocol } => {
cmd::lens::cmd_lens_inspect(&chain, &protocol, verbose)
}
LensAction::Check {
chain,
schemas_dir,
protocol,
dry_run,
} => cmd::lens::cmd_lens_fleet(&chain, &schemas_dir, &protocol, dry_run, verbose),
LensAction::Lift {
chain,
morphism,
json,
} => cmd::lens::cmd_lens_lift(&chain, &morphism, json, verbose),
}
}
fn dispatch_data_commands(action: DataAction, verbose: bool) -> Result<()> {
match action {
DataAction::Migrate {
data,
protocol,
range,
dry_run,
output,
backward,
coverage,
} => {
cmd::migrate::cmd_migrate(
&data,
protocol.as_deref(),
range.as_deref(),
dry_run,
output.as_deref(),
backward,
verbose,
)?;
if coverage {
cmd::migrate::cmd_migrate_coverage(
&data,
protocol.as_deref(),
range.as_deref(),
verbose,
)?;
}
Ok(())
}
DataAction::Convert {
data,
from,
to,
protocol,
chain,
output,
direction,
defaults,
} => cmd::convert::cmd_convert(
&data,
from.as_deref(),
to.as_deref(),
&protocol,
chain.as_deref(),
output.as_deref(),
&direction,
&defaults,
verbose,
),
DataAction::Sync {
data_dir,
edits,
target,
} => cmd::data::cmd_data_sync(&data_dir, edits, target.as_deref(), verbose),
DataAction::Status { data_dir } => cmd::data::cmd_data_status(&data_dir, verbose),
}
}
fn dispatch_parse_commands(action: ParseAction, verbose: bool) -> Result<()> {
match action {
ParseAction::File { path } => cmd::parse::cmd_parse_file(&path, verbose),
ParseAction::Project { path } => cmd::parse::cmd_parse_project(&path, verbose),
ParseAction::Emit { path } => cmd::parse::cmd_emit(&path, verbose),
}
}
fn dispatch_git_commands(action: GitAction, verbose: bool) -> Result<()> {
match action {
GitAction::Import { repo, revspec } => {
cmd::git_bridge::cmd_git_import(&repo, &revspec, verbose)
}
GitAction::Export { repo, dest } => cmd::git_bridge::cmd_git_export(&repo, &dest, verbose),
}
}