Skip to main content

mkit_cli/commands/
show_ref.rs

1//! `mkit show-ref [--heads] [--tags]` — list refs as `<hash> <refname>`,
2//! like `git show-ref`. Output is sorted by full ref name; the hash is a
3//! 64-hex BLAKE3 id (vs git's 40-hex SHA-1).
4
5use std::io::Write;
6
7use clap::Parser;
8use mkit_core::refs;
9
10use crate::clap_shim;
11use crate::exit;
12use crate::format;
13
14#[derive(Debug, Parser)]
15#[command(name = "mkit show-ref", about = "List refs and their object ids.")]
16struct ShowRefOpts {
17    /// Limit to `refs/heads/*` (branches).
18    #[arg(long)]
19    heads: bool,
20    /// Limit to `refs/tags/*`.
21    #[arg(long)]
22    tags: bool,
23}
24
25#[must_use]
26pub fn run(args: &[String]) -> u8 {
27    let opts = match clap_shim::parse::<ShowRefOpts>("mkit show-ref", args) {
28        Ok(o) => o,
29        Err(code) => return code,
30    };
31    let cwd = match std::env::current_dir() {
32        Ok(p) => p,
33        Err(e) => return emit_err(&format!("cwd: {e}"), exit::NOINPUT),
34    };
35    let mkit_dir = cwd.join(mkit_core::MKIT_DIR);
36
37    // Neither flag → show both heads and tags; either flag selects only
38    // that namespace (both flags → the union, matching git).
39    let want_heads = opts.heads || !opts.tags;
40    let want_tags = opts.tags || !opts.heads;
41
42    let mut lines: Vec<(String, String)> = Vec::new(); // (full refname, hash hex)
43    if want_heads {
44        match refs::list_refs(&mkit_dir) {
45            Ok(rs) => collect(&mut lines, &rs, "refs/heads/"),
46            Err(e) => return emit_err(&format!("list refs: {e}"), exit::GENERAL_ERROR),
47        }
48    }
49    if want_tags {
50        match refs::list_tags(&mkit_dir) {
51            Ok(rs) => collect(&mut lines, &rs, "refs/tags/"),
52            Err(e) => return emit_err(&format!("list tags: {e}"), exit::GENERAL_ERROR),
53        }
54    }
55    // Remote-tracking refs are listed in the unfiltered view (like
56    // git show-ref); --heads/--tags keep their narrow meaning.
57    if !opts.heads && !opts.tags {
58        match refs::list_remote_names(&mkit_dir) {
59            Ok(remotes) => {
60                for remote in remotes {
61                    match refs::list_remote_refs(&mkit_dir, &remote) {
62                        Ok(rs) => {
63                            collect(&mut lines, &rs, &format!("refs/remotes/{remote}/"));
64                        }
65                        Err(e) => {
66                            return emit_err(
67                                &format!("list remote refs: {e}"),
68                                exit::GENERAL_ERROR,
69                            );
70                        }
71                    }
72                }
73            }
74            Err(e) => return emit_err(&format!("list remotes: {e}"), exit::GENERAL_ERROR),
75        }
76    }
77    lines.sort_by(|a, b| a.0.cmp(&b.0));
78
79    let mut stdout = std::io::stdout().lock();
80    for (name, hash) in &lines {
81        let _ = writeln!(stdout, "{hash} {name}");
82    }
83    // Like git, exit non-zero (no diagnostic) when nothing matched, so a
84    // script can test for the existence of any head/tag.
85    if lines.is_empty() {
86        exit::GENERAL_ERROR
87    } else {
88        exit::OK
89    }
90}
91
92/// Push `(<prefix><name>, hex hash)` for every ref with a readable hash.
93fn collect(out: &mut Vec<(String, String)>, rs: &[refs::Ref], prefix: &str) {
94    for r in rs {
95        if let Some(h) = &r.hash {
96            out.push((format!("{prefix}{}", r.name), format::hex_hash(h)));
97        }
98    }
99}
100
101fn emit_err(msg: &str, code: u8) -> u8 {
102    let mut stderr = std::io::stderr().lock();
103    let _ = writeln!(stderr, "error: {msg}");
104    code
105}