1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
mod cmd_clean;
mod cmd_explain;
mod cmd_log;
mod cmd_map;
mod cmd_push;
mod cmd_save;
mod cmd_stash;
mod cmd_status;
mod cmd_switch;
mod cmd_sync;
mod cmd_undo;
mod cmd_whoops;
mod git;
use clap::{Parser, Subcommand};
use colored::Colorize;
#[derive(Parser)]
#[command(name = "g", about = "Git that talks back.", version)]
struct Cli {
#[command(subcommand)]
command: Option<Commands>,
}
#[derive(Subcommand)]
enum Commands {
/// Show a friendly, actionable status of your repo
Status,
/// Stage everything and commit in one step
Save {
/// Commit message describing your changes
message: String,
},
/// Undo your last action safely
Undo {
/// Show interactive undo timeline
#[arg(long)]
list: bool,
},
/// Explain your recent actions in plain English
Explain,
/// Show a human-readable commit log
Log {
/// Tell the project story in plain English
#[arg(long)]
story: bool,
},
/// Switch to a branch (creates it if needed)
Switch {
/// Branch name to switch to
branch: String,
},
/// Fix the last commit message
Whoops {
/// The corrected commit message
message: String,
},
/// Push with a preview of what's being sent
Push,
/// Clean up stale and merged branches
Clean,
/// Pull latest changes with guided conflict resolution
Sync,
/// Stash your changes with a name
Stash {
#[command(subcommand)]
action: StashAction,
},
/// Export repo history as a Graphviz .dot file
Map {
/// Output file path (default: repo-map.dot)
#[arg(short, long)]
output: Option<String>,
/// Max commits to include
#[arg(long, default_value = "50")]
max_commits: usize,
},
}
#[derive(Subcommand)]
enum StashAction {
/// Save current changes to a named stash
Save {
/// Description of what you're stashing
message: String,
},
/// Restore a stash interactively
Pop,
/// List all stashes
List,
}
fn main() {
let cli = Cli::parse();
if !git::is_git_repo() {
eprintln!(
"\n {} Not a git repository. Run {} to get started.\n",
"✖".red(),
"git init".cyan()
);
std::process::exit(1);
}
match cli.command {
Some(Commands::Status) => cmd_status::run(),
Some(Commands::Save { message }) => cmd_save::run(&message),
Some(Commands::Undo { list }) => cmd_undo::run(list),
Some(Commands::Explain) => cmd_explain::run(),
Some(Commands::Log { story }) => cmd_log::run(story),
Some(Commands::Switch { branch }) => cmd_switch::run(&branch),
Some(Commands::Whoops { message }) => cmd_whoops::run(&message),
Some(Commands::Push) => cmd_push::run(),
Some(Commands::Clean) => cmd_clean::run(),
Some(Commands::Sync) => cmd_sync::run(),
Some(Commands::Stash { action }) => match action {
StashAction::Save { message } => cmd_stash::run_save(&message),
StashAction::Pop => cmd_stash::run_pop(),
StashAction::List => cmd_stash::run_list(),
},
Some(Commands::Map { output, max_commits }) => cmd_map::run(output, max_commits),
None => {
// Default to status when no command is given
cmd_status::run();
}
}
}