use std::io::Write;
use proc_exit::prelude::*;
#[derive(clap::Args)]
pub struct AliasArgs {
#[arg(long)]
register: bool,
#[arg(long)]
unregister: bool,
}
impl AliasArgs {
pub fn exec(&self, colored_stdout: bool, colored_stderr: bool) -> proc_exit::ExitResult {
if self.register {
register(colored_stdout, colored_stderr)?;
} else if self.unregister {
unregister(colored_stdout, colored_stderr)?;
} else {
status(colored_stdout, colored_stderr)?;
}
Ok(())
}
}
fn register(_colored_stdout: bool, colored_stderr: bool) -> proc_exit::ExitResult {
let config = if let Ok(config) = open_repo_config() {
config
} else {
git2::Config::open_default().with_code(proc_exit::Code::FAILURE)?
}
.snapshot()
.with_code(proc_exit::Code::FAILURE)?;
let mut user_config = git2::Config::open_default()
.with_code(proc_exit::Code::FAILURE)?
.open_global()
.with_code(proc_exit::Code::FAILURE)?;
let stderr_palette = if colored_stderr {
crate::ops::Palette::colored()
} else {
crate::ops::Palette::plain()
};
let mut stderr = std::io::stderr().lock();
let mut success = true;
for alias in ALIASES {
let key = format!("alias.{}", alias.alias);
match config.get_string(&key) {
Ok(value) => {
if value == alias.action {
log::debug!("{}=\"{}\" is registered", alias.alias, value);
} else if value.starts_with(alias.action_base) {
log::debug!(
"{}=\"{}\" is registered but diverged from \"{}\"",
alias.alias,
value,
alias.action
);
} else {
let _ = writeln!(
stderr,
"{}: {}=\"{}\" is registered, not overwriting with \"{}\"",
stderr_palette.error.paint("error"),
alias.alias,
value,
alias.action_base
);
success = false;
}
}
Err(_) => {
let _ = writeln!(
stderr,
"{}: {}=\"{}\"",
stderr_palette.good.paint("Registering"),
alias.alias,
alias.action
);
user_config
.set_str(&key, alias.action)
.with_code(proc_exit::Code::FAILURE)?;
}
}
}
if success {
Ok(())
} else {
Err(proc_exit::Code::FAILURE.as_exit())
}
}
fn unregister(_colored_stdout: bool, colored_stderr: bool) -> proc_exit::ExitResult {
let config = if let Ok(config) = open_repo_config() {
config
} else {
git2::Config::open_default().with_code(proc_exit::Code::FAILURE)?
}
.snapshot()
.with_code(proc_exit::Code::FAILURE)?;
let mut user_config = git2::Config::open_default()
.with_code(proc_exit::Code::FAILURE)?
.open_global()
.with_code(proc_exit::Code::FAILURE)?;
let stderr_palette = if colored_stderr {
crate::ops::Palette::colored()
} else {
crate::ops::Palette::plain()
};
let mut stderr = std::io::stderr().lock();
let mut entries = config
.entries(Some("alias.*"))
.with_code(proc_exit::Code::FAILURE)?;
while let Some(entry) = entries.next() {
let entry = entry.with_code(proc_exit::Code::FAILURE)?;
let Some(key) = entry.name() else {continue};
let name = key.split_once('.').map(|n| n.1).unwrap_or(key);
let Some(value) = entry.value() else {continue};
let mut unregister = false;
if let Some(alias) = ALIASES.iter().find(|a| a.alias == name) {
if value == alias.action {
unregister = true;
} else if value.starts_with(alias.action_base) {
unregister = true;
}
} else if let Some(_alias) = ALIASES.iter().find(|a| value.starts_with(a.action_base)) {
unregister = true;
}
if unregister {
let _ = writeln!(
stderr,
"{}: {}=\"{}\"",
stderr_palette.good.paint("Unregistering"),
name,
value
);
user_config
.remove(key)
.with_code(proc_exit::Code::FAILURE)?;
}
}
Ok(())
}
fn status(colored_stdout: bool, colored_stderr: bool) -> proc_exit::ExitResult {
let config = if let Ok(config) = open_repo_config() {
config
} else {
git2::Config::open_default().with_code(proc_exit::sysexits::USAGE_ERR)?
};
let stdout_palette = if colored_stdout {
crate::ops::Palette::colored()
} else {
crate::ops::Palette::plain()
};
let stderr_palette = if colored_stderr {
crate::ops::Palette::colored()
} else {
crate::ops::Palette::plain()
};
let mut stdout = std::io::stdout().lock();
let mut stderr = std::io::stderr().lock();
let _ = writeln!(stdout, "[alias]");
let mut registered = false;
let mut covered = std::collections::HashSet::new();
let mut entries = config
.entries(Some("alias.*"))
.with_code(proc_exit::Code::FAILURE)?;
while let Some(entry) = entries.next() {
let entry = entry.with_code(proc_exit::Code::FAILURE)?;
let Some(name) = entry.name() else {continue};
let name = name.split_once('.').map(|n| n.1).unwrap_or(name);
let Some(value) = entry.value() else {continue};
if let Some(alias) = ALIASES.iter().find(|a| a.alias == name) {
if value == alias.action {
let _ = writeln!(
stdout,
"{}{}",
stdout_palette
.good
.paint(format_args!(" {} = {}", name, value)),
stdout_palette.hint.paint(" # registered")
);
registered = true;
} else if value.starts_with(alias.action_base) {
let _ = writeln!(
stdout,
"{}{}",
stdout_palette
.warn
.paint(format_args!(" {} = {}", name, value)),
stdout_palette
.hint
.paint(format_args!(" # diverged from \"{}\"", alias.action))
);
registered = true;
} else {
let _ = writeln!(
stdout,
"{}{}",
stdout_palette
.error
.paint(format_args!(" {} = {}", name, value)),
stdout_palette
.hint
.paint(format_args!(" # instead of `{}`", alias.action))
);
}
covered.insert(name.to_owned());
} else if let Some(_alias) = ALIASES.iter().find(|a| value.starts_with(a.action_base)) {
let _ = writeln!(stdout, " {} = {}", name, value);
registered = true;
}
}
let mut unregistered = false;
for alias in ALIASES {
if covered.contains(alias.alias) {
continue;
}
let _ = writeln!(
stdout,
"{}{}",
stdout_palette
.error
.paint(format_args!("# {} = {}", alias.alias, alias.action)),
stdout_palette.hint.paint(" # unregistered")
);
unregistered = true;
}
if registered {
let _ = writeln!(
stderr,
"{}: To unregister, pass {}",
stderr_palette.info.paint("note"),
stderr_palette.error.paint("`--unregister`")
);
}
if unregistered {
let _ = writeln!(
stderr,
"{}: To register, pass {}",
stderr_palette.info.paint("note"),
stderr_palette.good.paint("`--register`")
);
}
Ok(())
}
pub struct Alias {
pub alias: &'static str,
pub action: &'static str,
pub action_base: &'static str,
}
const ALIASES: &[Alias] = &[
crate::next::NextArgs::alias(),
crate::prev::PrevArgs::alias(),
crate::reword::RewordArgs::alias(),
crate::amend::AmendArgs::alias(),
crate::sync::SyncArgs::alias(),
crate::run::RunArgs::alias(),
];
fn open_repo_config() -> Result<git2::Config, eyre::Error> {
let cwd = std::env::current_dir()?;
let repo = git2::Repository::discover(cwd)?;
let config = repo.config()?;
Ok(config)
}