use std::io::Write;
use std::path::{Path, PathBuf};
use clap::Parser;
use mkit_core::layout::RepoLayout;
use mkit_core::refs::{self, Head};
use mkit_core::store::ObjectStore;
use super::revspec;
use crate::clap_shim;
use crate::exit;
use crate::format;
const DEFAULT_ABBREV: usize = 7;
#[derive(Debug, Parser)]
#[command(name = "mkit rev-parse", about = "Resolve revisions to object ids.")]
struct RevParseOpts {
#[arg(long)]
verify: bool,
#[arg(long, num_args = 0..=1, require_equals = true, default_missing_value = "7")]
short: Option<usize>,
#[arg(long = "abbrev-ref")]
abbrev_ref: bool,
#[arg(long = "show-toplevel")]
show_toplevel: bool,
args: Vec<String>,
}
#[must_use]
pub fn run(args: &[String]) -> u8 {
let opts = match clap_shim::parse::<RevParseOpts>("mkit rev-parse", 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 mut stdout = std::io::stdout().lock();
if opts.show_toplevel {
let Some(root) = find_repo_root(&cwd) else {
return emit_err("not inside a mkit repository", exit::GENERAL_ERROR);
};
let _ = writeln!(stdout, "{}", root.display());
return exit::OK;
}
let layout = match super::resolve_layout(&cwd) {
Ok(layout) => layout,
Err(code) => return code,
};
let store = match ObjectStore::open(&layout) {
Ok(s) => s,
Err(e) => return emit_err(&format!("not a mkit repo: {e}"), exit::GENERAL_ERROR),
};
if opts.args.is_empty() {
return super::usage_error("usage: mkit rev-parse [opts] <rev>...");
}
for spec in &opts.args {
if opts.abbrev_ref {
match abbrev_ref(&layout, spec) {
Ok(name) => {
let _ = writeln!(stdout, "{name}");
}
Err(code) => return code,
}
continue;
}
let hash = match revspec::resolve_revision(&store, &layout, spec) {
Ok(h) => h,
Err(e) => {
let _ = opts.verify;
return emit_err(&format!("bad revision '{spec}': {e}"), exit::GENERAL_ERROR);
}
};
let rendered = match opts.short {
Some(n) => format::short_hash(&hash, if n == 0 { DEFAULT_ABBREV } else { n }),
None => format::hex_hash(&hash),
};
let _ = writeln!(stdout, "{rendered}");
}
exit::OK
}
fn abbrev_ref(layout: &RepoLayout, spec: &str) -> Result<String, u8> {
if spec == "HEAD" {
return match refs::read_head(layout) {
Ok(Head::Branch(name)) => Ok(name),
Ok(Head::Detached(_)) => Ok("HEAD".to_string()),
Err(e) => Err(emit_err(&format!("read HEAD: {e}"), exit::DATAERR)),
};
}
let short = spec
.strip_prefix("refs/heads/")
.or_else(|| spec.strip_prefix("refs/tags/"))
.or_else(|| spec.strip_prefix("refs/remotes/"))
.unwrap_or(spec);
Ok(short.to_string())
}
fn find_repo_root(start: &Path) -> Option<PathBuf> {
let mut cur = start;
loop {
if cur.join(mkit_core::MKIT_DIR).is_dir() {
return Some(cur.to_path_buf());
}
cur = cur.parent()?;
}
}
use super::error as emit_err;