use std::io::Write;
use clap::{Parser, Subcommand};
use mkit_core::hash::Hash;
use mkit_core::layout::RepoLayout;
use mkit_core::refs::{self, RefError};
use crate::clap_shim;
use crate::exit;
use crate::format;
#[derive(Debug, Parser)]
#[command(name = "mkit ref", about = "Inspect refs: list them, or resolve one.")]
struct RefOpts {
#[command(subcommand)]
sub: RefCmd,
}
#[derive(Debug, Subcommand)]
enum RefCmd {
List {
#[arg(long)]
pattern: Option<String>,
},
Cat {
name: String,
},
}
#[must_use]
pub fn run(args: &[String]) -> u8 {
let opts = match clap_shim::parse::<RefOpts>("mkit ref", args) {
Ok(o) => o,
Err(code) => return code,
};
let cwd = match std::env::current_dir() {
Ok(p) => p,
Err(e) => return emit_err(&format!("cwd: {e}"), exit::NOINPUT),
};
let layout = match super::resolve_layout(&cwd) {
Ok(layout) => layout,
Err(code) => return code,
};
match opts.sub {
RefCmd::List { pattern } => run_list(&layout, pattern.as_deref()),
RefCmd::Cat { name } => run_cat(&layout, &name),
}
}
struct Row {
name: String,
hash: Hash,
}
fn run_list(layout: &RepoLayout, pattern: Option<&str>) -> u8 {
let mut rows: Vec<Row> = Vec::new();
match refs::list_refs(layout) {
Ok(rs) => push_rows(&mut rows, &rs, "refs/heads/"),
Err(e) => return emit_err(&format!("list refs: {e}"), exit::GENERAL_ERROR),
}
match refs::list_tags(layout) {
Ok(rs) => push_rows(&mut rows, &rs, "refs/tags/"),
Err(e) => return emit_err(&format!("list tags: {e}"), exit::GENERAL_ERROR),
}
match refs::list_remote_names(layout) {
Ok(remotes) => {
for remote in remotes {
match refs::list_remote_refs(layout, &remote) {
Ok(rs) => push_rows(&mut rows, &rs, &format!("refs/remotes/{remote}/")),
Err(e) => {
return emit_err(&format!("list remote refs: {e}"), exit::GENERAL_ERROR);
}
}
}
}
Err(e) => return emit_err(&format!("list remotes: {e}"), exit::GENERAL_ERROR),
}
rows.sort_by(|a, b| a.name.cmp(&b.name));
if let Some(pat) = pattern {
rows.retain(|r| super::branch::glob_match(pat, &r.name));
}
let mut stdout = std::io::stdout().lock();
for r in &rows {
let _ = writeln!(stdout, "{} {}", r.name, format::hex_hash(&r.hash));
}
exit::OK
}
fn push_rows(out: &mut Vec<Row>, rs: &[refs::Ref], prefix: &str) {
for r in rs {
if let Some(h) = r.hash {
out.push(Row {
name: format!("{prefix}{}", r.name),
hash: h,
});
}
}
}
fn run_cat(layout: &RepoLayout, name: &str) -> u8 {
let resolved: Result<Option<Hash>, RefError> = if name == "HEAD" {
refs::resolve_head(layout)
} else if let Some(short) = name.strip_prefix("refs/heads/") {
refs::read_ref(layout, short)
} else if let Some(short) = name.strip_prefix("refs/tags/") {
refs::read_tag(layout, short)
} else if let Some(rest) = name.strip_prefix("refs/remotes/") {
match rest.split_once('/') {
Some((remote, branch)) => refs::read_remote_ref(layout, remote, branch),
None => {
return emit_err(
&format!(
"invalid remote ref '{name}': expected refs/remotes/<remote>/<branch>"
),
exit::USAGE,
);
}
}
} else {
return emit_err(
&format!(
"unsupported ref '{name}': ref cat handles HEAD, refs/heads/<b>, refs/tags/<t>, \
and refs/remotes/<r>/<b>"
),
exit::USAGE,
);
};
match resolved {
Ok(Some(h)) => {
let mut stdout = std::io::stdout().lock();
let _ = writeln!(stdout, "{}", format::hex_hash(&h));
exit::OK
}
Ok(None) => emit_err(&format!("ref '{name}' not found"), exit::GENERAL_ERROR),
Err(e) => emit_err(&format!("ref cat {name}: {e}"), exit::GENERAL_ERROR),
}
}
use super::error as emit_err;