Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 22 additions & 1 deletion src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,28 @@ use clap::{Parser, Subcommand};

#[derive(Parser)]
#[command(name = "gw")]
#[command(about = "Git workflow CLI - type-safe worktree-aware git operations")]
#[command(about = "Worktree-aware git workflow: branch -> PR -> cleanup, safely.")]
#[command(long_about = "\
Worktree-aware git workflow: branch -> PR -> cleanup, safely.

For developers using a PR-per-branch workflow. gw drives each change from a
fresh branch through review to a cleaned-up merge, and keeps parallel worktrees
in sync so day-to-day work never touches `main` directly.

\"home branch\": the branch a worktree returns to (the main worktree's home is
`main`). `gw home` switches to it and syncs with origin/main.

TYPICAL FLOW:
gw new feature/login # branch off a fresh origin/main
# ...edit, then: git commit -> git push -u origin <branch> -> gh pr create
gw await <pr> --open # wait for CI, open the PR, watch to merge, then clean up

COMMANDS BY SITUATION:
Everyday: new, status, open, sync, cleanup, home
Recovery: pause, abandon, undo
Worktrees: worktree pool (give parallel agents isolated worktrees)

Lost? Run `gw status` -- it prints the single next command for where you are.")]
#[command(version)]
pub struct Cli {
#[command(subcommand)]
Expand Down
32 changes: 31 additions & 1 deletion src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,18 @@
use std::process::ExitCode;

use clap::Parser;
use clap::error::ErrorKind;

use git_workflow::cli::{Cli, Commands, PoolCommands, WorktreeCommands};
use git_workflow::commands;
use git_workflow::error::GwError;
use git_workflow::output;

fn main() -> ExitCode {
let cli = Cli::parse();
let cli = match Cli::try_parse() {
Ok(cli) => cli,
Err(e) => return handle_parse_error(e),
};

let result = match cli.command {
Commands::Home => commands::home::run(cli.verbose),
Expand Down Expand Up @@ -65,3 +69,29 @@ fn main() -> ExitCode {
}
}
}

/// Render clap parse failures, then guide the user to a runnable next command.
///
/// `--help`/`--version` aren't failures: print them as-is and exit 0. Real usage
/// errors (bad subcommand, missing argument) get clap's message plus a `Try:`
/// footer pointing at `gw status` -- the command that names the next step -- so
/// the user never has to reconstruct the syntax themselves.
fn handle_parse_error(e: clap::Error) -> ExitCode {
let kind = e.kind();
// clap writes help to stdout and errors to stderr; let it pick the stream.
let _ = e.print();

match kind {
ErrorKind::DisplayHelp
| ErrorKind::DisplayVersion
| ErrorKind::DisplayHelpOnMissingArgumentOrSubcommand => ExitCode::SUCCESS,
_ => {
output::error_footer(&[
"gw status # show where you are and the next command to run",
"gw --help # full command reference",
]);
// Clap uses exit code 2 for usage errors; mirror that.
ExitCode::from(2)
}
}
}
9 changes: 9 additions & 0 deletions src/output/style.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,3 +58,12 @@ pub fn hints(lines: &[&str]) {
println!(" {line}");
}
}

/// Print a `Try:` footer (to stderr) listing runnable next commands after an error.
pub fn error_footer(lines: &[&str]) {
eprintln!();
eprintln!("{BOLD}Try:{RESET}");
for line in lines {
eprintln!(" {line}");
}
}
Loading