Skip to main content

mkit_cli/commands/
cat.rs

1//! `mkit cat <hash>` — decode and print an object by its hash.
2
3use std::io::Write;
4
5use clap::Parser;
6use mkit_core::hash::from_hex;
7use mkit_core::object::Object;
8use mkit_core::store::ObjectStore;
9use mkit_core::worktree;
10
11use crate::clap_shim;
12use crate::exit;
13use crate::format;
14
15#[derive(Debug, Parser)]
16#[command(name = "mkit cat", about = "Display an object by its hash.")]
17struct CatOpts {
18    /// 64-char hex object hash.
19    hash: String,
20}
21
22#[must_use]
23pub fn run(args: &[String]) -> u8 {
24    let opts = match clap_shim::parse::<CatOpts>("mkit cat", args) {
25        Ok(o) => o,
26        Err(code) => return code,
27    };
28    let hash_hex = &opts.hash;
29    let cwd = match std::env::current_dir() {
30        Ok(p) => p,
31        Err(e) => return emit_err(&format!("cwd: {e}"), exit::NOINPUT),
32    };
33    let layout = match super::resolve_layout(&cwd) {
34        Ok(layout) => layout,
35        Err(code) => return code,
36    };
37    let store = match ObjectStore::open(&layout) {
38        Ok(s) => s,
39        Err(e) => return emit_err(&format!("not a mkit repo: {e}"), exit::GENERAL_ERROR),
40    };
41    let h = match from_hex(hash_hex) {
42        Ok(h) => h,
43        Err(e) => return emit_err(&format!("bad hash: {e}"), exit::DATAERR),
44    };
45    let obj = match store.read_object(&h) {
46        Ok(o) => o,
47        Err(e) => return emit_err(&format!("read: {e}"), exit::NOINPUT),
48    };
49    let mut stdout = std::io::stdout().lock();
50    match obj {
51        Object::Blob(b) => {
52            let _ = stdout.write_all(&b.data);
53        }
54        // A ChunkedBlob is the canonical representation for files above
55        // the chunking threshold (#203). Reassemble its chunks and
56        // stream the full content, matching `mkit cat` on a plain Blob.
57        Object::ChunkedBlob(_) => match worktree::read_blob(&store, &h) {
58            Ok(data) => {
59                let _ = stdout.write_all(&data);
60            }
61            Err(e) => return emit_err(&format!("reassemble chunked blob: {e}"), exit::NOINPUT),
62        },
63        Object::Tree(t) => {
64            for e in t.entries {
65                let _ = writeln!(
66                    stdout,
67                    "{:02x} {} {}",
68                    e.mode as u8,
69                    format::hex_hash(&e.object_hash),
70                    String::from_utf8_lossy(&e.name)
71                );
72            }
73        }
74        Object::Commit(c) => {
75            let _ = writeln!(stdout, "tree {}", format::hex_hash(&c.tree_hash));
76            for p in &c.parents {
77                let _ = writeln!(stdout, "parent {}", format::hex_hash(p));
78            }
79            let _ = writeln!(stdout, "author {}", format::short_identity(&c.author));
80            let _ = writeln!(stdout, "timestamp {}", c.timestamp);
81            let _ = writeln!(stdout);
82            let _ = stdout.write_all(&c.message);
83            let _ = writeln!(stdout);
84        }
85        Object::Tag(t) => {
86            let _ = writeln!(stdout, "object {}", format::hex_hash(&t.target));
87            let _ = writeln!(stdout, "type {}", t.target_type.name());
88            let _ = writeln!(stdout, "tag {}", String::from_utf8_lossy(&t.name));
89            let _ = writeln!(stdout, "tagger {}", format::short_identity(&t.tagger));
90            let _ = writeln!(stdout, "timestamp {}", t.timestamp);
91            let signed = t.signature != [0u8; 64];
92            let _ = writeln!(stdout, "signed {signed}");
93            let _ = writeln!(stdout);
94            let _ = stdout.write_all(&t.message);
95            let _ = writeln!(stdout);
96        }
97        other => {
98            let _ = writeln!(stdout, "{other}");
99        }
100    }
101    exit::OK
102}
103
104use super::error as emit_err;