use std::path::PathBuf; use clap::{Parser, Subcommand}; use vcs_cli::app::App; use vcs_cli::out::{Fail, Out}; use vcs_cli::{access, cmd}; /// Version control where nobody waits and nothing gets lost. #[derive(Parser)] #[command(name = "vcs", version, about, disable_help_subcommand = true)] struct Cli { /// Print one JSON object instead of text. #[arg(long, global = true)] json: bool, /// Run as if started in this folder. #[arg(short = 'C', global = true, value_name = "FOLDER")] dir: Option, #[command(subcommand)] command: Command, } #[derive(Subcommand)] enum Command { /// Make a workspace for a project. Get { /// host/owner/name, or the folder of a local project. project: String, folder: Option, /// Start on an existing change instead of a new one. #[arg(long)] change: Option, /// Get only these folders (repeatable). #[arg(long)] only: Vec, }, /// Start a new project that lives only on this machine. Init { folder: Option }, /// Make a project from a git repository, with its history. Import { url: String, /// Put it on a host as host/owner/name instead of this machine. #[arg(long)] to: Option, folder: Option, }, /// Put a local project on a host. Publish { project: String }, /// Sign in to a host. Login { host: Option, /// Use this token instead of signing in through the browser. #[arg(long)] token: Option, }, /// Where you are and what to do next. Status { change: Option }, /// What a change changes. Diff { paths: Vec, #[arg(long)] change: Option, #[arg(long)] from: Option, #[arg(long)] to: Option, #[arg(long)] stat: bool, }, /// History of main, open changes, saves or operations. Log { /// A version number or a change. what: Option, #[arg(long)] changes: bool, #[arg(long)] saves: bool, #[arg(long)] ops: bool, #[arg(short = 'n', default_value_t = 20)] limit: i64, }, /// Start another change. The current one is kept. New { title: Option, /// Build on a change that hasn't landed yet. #[arg(long)] on: Option, }, /// Set the current change's description. Describe { #[arg(required = true)] parts: Vec, }, /// Put this workspace on another change. Switch { change: String }, /// Abandon a change. Drop { change: Option }, /// Bring the change up to date with main. Sync, /// Put a change on main. Land { change: Option, /// Description: the first is the title, the rest the body. #[arg(short = 'm')] message: Vec, #[arg(long)] no_wait: bool, #[arg(long)] allow_secrets: bool, }, /// Undo the last vcs command here, or take a landed version back out. Undo { version: Option }, /// Bring files back from an earlier point. Restore { #[arg(required = true)] paths: Vec, /// From your own earlier save: "10 min ago", "2h ago", 15:30. #[arg(long)] at: Option, /// From a version of main, a change, or change:save. #[arg(long)] from: Option, }, /// See or set who can see a change or a path. Access { target: String, who: Vec, /// Also let them work on the change. #[arg(long)] edit: bool, }, /// Print the guide for coding agents. Guide, #[command(hide = true)] Saver, } fn run(app: &App, command: Command) -> Out { match command { Command::Get { project, folder, change, only } => cmd::get(app, &project, folder, change, only), Command::Init { folder } => cmd::init(app, folder), Command::Import { url, to, folder } => vcs_cli::remote::import(app, &url, to, folder), Command::Publish { project } => vcs_cli::remote::publish(app, &project), Command::Login { host, token } => vcs_cli::remote::login(app, host, token), Command::Status { change } => cmd::status(app, change), Command::Diff { paths, change, from, to, stat } => cmd::diff_cmd(app, paths, change, from, to, stat), Command::Log { what, changes, saves, ops, limit } => cmd::log(app, what, changes, saves, ops, limit), Command::New { title, on } => cmd::new(app, title, on), Command::Describe { parts } => cmd::describe(app, parts), Command::Switch { change } => cmd::switch(app, &change), Command::Drop { change } => cmd::drop(app, change), Command::Sync => cmd::sync(app), Command::Land { change, message, no_wait, allow_secrets } => cmd::land(app, change, message, no_wait, allow_secrets), Command::Undo { version } => cmd::undo(app, version), Command::Restore { paths, at, from } => cmd::restore(app, paths, at, from), Command::Access { target, who, edit } => access::run(app, &target, who, edit), Command::Guide => cmd::guide(), Command::Saver => vcs_cli::remote::saver(app), } } fn main() { let cli = match Cli::try_parse() { Ok(cli) => cli, Err(e) => { let code = if e.use_stderr() { 2 } else { 0 }; let _ = e.print(); std::process::exit(code); } }; let json = cli.json || std::env::var("VCS_JSON").is_ok_and(|v| v == "1"); let cwd = match cli.dir { Some(d) => std::env::current_dir().map(|c| c.join(d)).unwrap_or_default(), None => std::env::current_dir().unwrap_or_default(), }; let app = match App::open(json, cwd) { Ok(app) => app, Err(e) => { Fail::from(e).print(json); std::process::exit(1); } }; let starts_saver = !matches!(cli.command, Command::Saver | Command::Guide | Command::Login { .. }); let result = run(&app, cli.command); if starts_saver { vcs_cli::saver::ensure_running(); } let notes = app.notes.borrow().clone(); match result { Ok(mut report) => { if !notes.is_empty() { report.set("notes", notes.clone()); } report.print(json); if !json { for n in ¬es { eprintln!("note: {n}"); } } std::process::exit(report.exit); } Err(mut fail) => { if !notes.is_empty() { fail = fail.set("notes", notes.clone()); } fail.print(json); if !json { for n in ¬es { eprintln!("note: {n}"); } } std::process::exit(fail.exit); } } }