mkit_cli/commands/
ref_cmd.rs1use std::io::Write;
34
35use clap::{Parser, Subcommand};
36use mkit_core::hash::Hash;
37use mkit_core::layout::RepoLayout;
38use mkit_core::refs::{self, RefError};
39
40use crate::clap_shim;
41use crate::exit;
42use crate::format;
43
44#[derive(Debug, Parser)]
45#[command(name = "mkit ref", about = "Inspect refs: list them, or resolve one.")]
46struct RefOpts {
47 #[command(subcommand)]
48 sub: RefCmd,
49}
50
51#[derive(Debug, Subcommand)]
52enum RefCmd {
53 List {
55 #[arg(long)]
57 pattern: Option<String>,
58 },
59 Cat {
62 name: String,
65 },
66}
67
68#[must_use]
69pub fn run(args: &[String]) -> u8 {
70 let opts = match clap_shim::parse::<RefOpts>("mkit ref", args) {
71 Ok(o) => o,
72 Err(code) => return code,
73 };
74 let cwd = match std::env::current_dir() {
75 Ok(p) => p,
76 Err(e) => return emit_err(&format!("cwd: {e}"), exit::NOINPUT),
77 };
78 let layout = match super::resolve_layout(&cwd) {
79 Ok(layout) => layout,
80 Err(code) => return code,
81 };
82
83 match opts.sub {
84 RefCmd::List { pattern } => run_list(&layout, pattern.as_deref()),
85 RefCmd::Cat { name } => run_cat(&layout, &name),
86 }
87}
88
89struct Row {
91 name: String,
92 hash: Hash,
93}
94
95fn run_list(layout: &RepoLayout, pattern: Option<&str>) -> u8 {
96 let mut rows: Vec<Row> = Vec::new();
97 match refs::list_refs(layout) {
98 Ok(rs) => push_rows(&mut rows, &rs, "refs/heads/"),
99 Err(e) => return emit_err(&format!("list refs: {e}"), exit::GENERAL_ERROR),
100 }
101 match refs::list_tags(layout) {
102 Ok(rs) => push_rows(&mut rows, &rs, "refs/tags/"),
103 Err(e) => return emit_err(&format!("list tags: {e}"), exit::GENERAL_ERROR),
104 }
105 match refs::list_remote_names(layout) {
106 Ok(remotes) => {
107 for remote in remotes {
108 match refs::list_remote_refs(layout, &remote) {
109 Ok(rs) => push_rows(&mut rows, &rs, &format!("refs/remotes/{remote}/")),
110 Err(e) => {
111 return emit_err(&format!("list remote refs: {e}"), exit::GENERAL_ERROR);
112 }
113 }
114 }
115 }
116 Err(e) => return emit_err(&format!("list remotes: {e}"), exit::GENERAL_ERROR),
117 }
118 rows.sort_by(|a, b| a.name.cmp(&b.name));
119
120 if let Some(pat) = pattern {
121 rows.retain(|r| super::branch::glob_match(pat, &r.name));
122 }
123
124 let mut stdout = std::io::stdout().lock();
125 for r in &rows {
126 let _ = writeln!(stdout, "{} {}", r.name, format::hex_hash(&r.hash));
127 }
128 exit::OK
129}
130
131fn push_rows(out: &mut Vec<Row>, rs: &[refs::Ref], prefix: &str) {
134 for r in rs {
135 if let Some(h) = r.hash {
136 out.push(Row {
137 name: format!("{prefix}{}", r.name),
138 hash: h,
139 });
140 }
141 }
142}
143
144fn run_cat(layout: &RepoLayout, name: &str) -> u8 {
145 let resolved: Result<Option<Hash>, RefError> = if name == "HEAD" {
146 refs::resolve_head(layout)
147 } else if let Some(short) = name.strip_prefix("refs/heads/") {
148 refs::read_ref(layout, short)
149 } else if let Some(short) = name.strip_prefix("refs/tags/") {
150 refs::read_tag(layout, short)
151 } else if let Some(rest) = name.strip_prefix("refs/remotes/") {
152 match rest.split_once('/') {
153 Some((remote, branch)) => refs::read_remote_ref(layout, remote, branch),
154 None => {
155 return emit_err(
156 &format!(
157 "invalid remote ref '{name}': expected refs/remotes/<remote>/<branch>"
158 ),
159 exit::USAGE,
160 );
161 }
162 }
163 } else {
164 return emit_err(
165 &format!(
166 "unsupported ref '{name}': ref cat handles HEAD, refs/heads/<b>, refs/tags/<t>, \
167 and refs/remotes/<r>/<b>"
168 ),
169 exit::USAGE,
170 );
171 };
172
173 match resolved {
174 Ok(Some(h)) => {
175 let mut stdout = std::io::stdout().lock();
176 let _ = writeln!(stdout, "{}", format::hex_hash(&h));
177 exit::OK
178 }
179 Ok(None) => emit_err(&format!("ref '{name}' not found"), exit::GENERAL_ERROR),
180 Err(e) => emit_err(&format!("ref cat {name}: {e}"), exit::GENERAL_ERROR),
181 }
182}
183
184use super::error as emit_err;