mod alias;
mod config;
mod shell;
mod utils;
use colored::Colorize;
use std::collections::HashMap;
use std::fs;
use std::path::PathBuf;
#[cfg(windows)]
fn enable_ansi_support() {
unsafe {
use windows::Win32::System::Console::{
CONSOLE_MODE, GetConsoleMode, GetStdHandle, STD_OUTPUT_HANDLE,
};
use windows::Win32::System::LibraryLoader::{GetModuleHandleW, GetProcAddress};
use windows::core::{s, w};
let kernel32 = GetModuleHandleW(w!("kernel32")).unwrap();
let set_console_mode_ptr = GetProcAddress(kernel32, s!("SetConsoleMode")).unwrap();
let set_console_mode: extern "system" fn(
windows::Win32::Foundation::HANDLE,
CONSOLE_MODE,
) -> i32 = std::mem::transmute(set_console_mode_ptr);
let console_handle = GetStdHandle(STD_OUTPUT_HANDLE).unwrap();
let mut mode = CONSOLE_MODE(0);
GetConsoleMode(console_handle, &mut mode).ok();
mode |= CONSOLE_MODE(0x0004); set_console_mode(console_handle, mode);
}
}
fn main() {
#[cfg(windows)]
enable_ansi_support();
colored::control::set_override(true);
let args: Vec<String> = std::env::args().collect();
let exe_name = get_exe_name();
let aliases = load_aliases();
if !exe_name.starts_with('g') && aliases.contains_key(&exe_name) {
eprintln!(
"{}: '{}' is not a valid command",
"Error".red().bold(),
exe_name
);
eprintln!();
eprintln!("Use 'g {}' instead", exe_name.green());
std::process::exit(1);
}
if exe_name == "g" || exe_name == "git-alias" {
handle_subcommand(&args, &aliases);
} else {
execute_alias(&exe_name, &args[1..], &aliases);
}
}
fn get_exe_name() -> String {
std::env::args()
.next()
.and_then(|p| {
std::path::Path::new(&p)
.file_stem()
.map(|s| s.to_string_lossy().to_string())
})
.unwrap_or_else(|| "g".to_string())
}
fn load_aliases() -> HashMap<String, Vec<String>> {
let mut aliases = alias::get_builtin_aliases();
let config = config::load_config();
aliases.extend(config.aliases);
aliases
}
fn execute_alias(name: &str, args: &[String], aliases: &HashMap<String, Vec<String>>) {
let config = config::load_config();
if let Some(git_args) = aliases.get(name) {
let mut full_args = git_args.clone();
full_args.extend_from_slice(args);
if config.verbose {
eprintln!("{} git {}", "Executing:".cyan(), full_args.join(" "));
}
let exit_code = utils::execute_git_command(&full_args);
std::process::exit(exit_code);
} else {
let mut full_args = vec![name.to_string()];
full_args.extend_from_slice(args);
if config.verbose {
eprintln!("{} git {}", "Executing:".cyan(), full_args.join(" "));
}
let exit_code = utils::execute_git_command(&full_args);
std::process::exit(exit_code);
}
}
fn handle_subcommand(args: &[String], aliases: &HashMap<String, Vec<String>>) {
if args.len() < 2 {
print_help();
return;
}
match args[1].as_str() {
"list" => {
let filter = args.get(2);
alias::list_aliases(aliases, filter.map(|s| s.as_str()));
}
"completions" => {
if let Some(shell) = args.get(2) {
shell::print_completions(shell, aliases);
} else {
print_completions_help();
}
}
"init" => {
if let Err(e) = config::create_default_config() {
eprintln!("{}: {}", "Error".red().bold(), e);
std::process::exit(1);
}
}
"install" => {
if let Err(e) = install_all() {
eprintln!("{}: {}", "Error".red().bold(), e);
std::process::exit(1);
}
}
"--help" | "-h" => {
print_help();
}
"--version" | "-V" => {
println!("git-alias 1.0.0");
}
alias_name => {
if alias_name.starts_with('g') && aliases.contains_key(alias_name) {
eprintln!(
"{}: 'g {}' is not needed",
"Hint".yellow().bold(),
alias_name
);
eprintln!();
eprintln!("Use '{}' directly instead", alias_name.green());
std::process::exit(1);
}
if aliases.contains_key(alias_name) {
let remaining_args = &args[2..];
let git_args = aliases.get(alias_name).unwrap();
let mut full_args = git_args.clone();
full_args.extend_from_slice(remaining_args);
let exit_code = utils::execute_git_command(&full_args);
std::process::exit(exit_code);
} else {
let mut full_args = vec![alias_name.to_string()];
full_args.extend_from_slice(&args[2..]);
let exit_code = utils::execute_git_command(&full_args);
std::process::exit(exit_code);
}
}
}
}
fn install_all() -> Result<(), String> {
let install_dir = get_install_dir()?;
fs::create_dir_all(&install_dir).map_err(|e| {
format!(
"Failed to create directory {}: {}",
install_dir.display(),
e
)
})?;
let current_exe = std::env::current_exe()
.map_err(|e| format!("Failed to get current executable path: {}", e))?;
let aliases = load_aliases();
let mut installed = vec![];
for alias_name in aliases.keys() {
if !alias_name.starts_with('g') {
continue;
}
let target_path = install_dir.join(alias_name);
#[cfg(windows)]
{
fs::copy(¤t_exe, &target_path).map_err(|e| {
format!("Failed to copy binary to {}: {}", target_path.display(), e)
})?;
}
#[cfg(unix)]
{
if target_path.exists() || target_path.symlink_metadata().is_ok() {
fs::remove_file(&target_path)
.map_err(|e| format!("Failed to remove existing file: {}", e))?;
}
std::os::unix::fs::symlink(¤t_exe, &target_path)
.map_err(|e| format!("Failed to create symlink: {}", e))?;
}
installed.push(alias_name.clone());
}
let g_path = install_dir.join("g");
fs::copy(¤t_exe, &g_path)
.map_err(|e| format!("Failed to copy binary to {}: {}", g_path.display(), e))?;
installed.push("g".to_string());
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
let permissions = fs::Permissions::from_mode(0o755);
fs::set_permissions(&g_path, permissions)
.map_err(|e| format!("Failed to set permissions: {}", e))?;
}
println!("{}", "Installation successful!".green().bold());
println!();
println!("Installed to: {}", install_dir.display());
println!("Total: {} commands installed", installed.len());
println!();
println!("Usage:");
println!(" g s # git status");
println!(" gs # git status");
println!(" g ls # git log --no-merges");
println!(" gls # git log --no-merges");
println!();
println!("Run: {} list", "g".green());
Ok(())
}
fn get_install_dir() -> Result<PathBuf, String> {
let home = dirs::home_dir().ok_or_else(|| "Failed to get home directory".to_string())?;
let install_dir = home.join(".local").join("bin");
Ok(install_dir)
}
fn print_help() {
println!("{}", "Git-Alias - Fast Git Alias Tool".cyan().bold());
println!();
println!("{}", "USAGE:".yellow().bold());
println!(" {} [args...] Run git command directly", "gs".green());
println!(" g {} [args...] Run git command via g prefix", "s".green());
println!();
println!("{}", "SUBCOMMANDS:".yellow().bold());
println!(" g list [filter] List all aliases");
println!(" g completions <sh> Generate shell completions (bash/zsh/fish)");
println!(" g init Create config file at ~/.git-alias.toml");
println!(" g install Install all alias commands to ~/.local/bin");
println!();
println!("{}", "NOTES:".yellow().bold());
println!(" - Direct call must start with 'g' (e.g., gs, gls)");
println!(" - g prefix works with any alias (e.g., g s, g ls)");
println!(" - g gs is not supported, use gs instead");
println!(" - Run 'g list' to see all available aliases");
}
fn print_completions_help() {
println!("{}", "Shell Completions".cyan().bold());
println!();
println!("{}", "Usage:".yellow().bold());
println!(" {} <shell>", "g completions".green());
println!();
println!("{}", "Supported shells:".yellow().bold());
println!(" {} Bash shell", "bash".cyan());
println!(" {} Zsh shell", "zsh".cyan());
println!(" {} Fish shell", "fish".cyan());
println!();
println!("{}", "Examples:".yellow().bold());
println!(" {} {}", "#".dimmed(), "Bash - 临时启用".white());
println!(" {}", "source <(g completions bash)".green());
println!();
println!(" {} {}", "#".dimmed(), "Bash - 持久化 (添加到 ~/.bashrc)".white());
println!(" {}", "echo 'source <(g completions bash)' >> ~/.bashrc".green());
println!();
println!(" {} {}", "#".dimmed(), "Zsh - 临时启用".white());
println!(" {}", "source <(g completions zsh)".green());
println!();
println!(" {} {}", "#".dimmed(), "Zsh - 持久化 (添加到 ~/.zshrc)".white());
println!(" {}", "echo 'source <(g completions zsh)' >> ~/.zshrc".green());
println!();
println!(" {} {}", "#".dimmed(), "Fish - 持久化".white());
println!(" {}", "g completions fish > ~/.config/fish/completions/g.fish".green());
}