Skip to main content

git_spawn/command/
cat_file.rs

1//! `git cat-file` — provide content or type/size information for repository objects.
2
3use crate::command::{CommandExecutor, GitCommand};
4use crate::error::{Error, Result};
5use async_trait::async_trait;
6
7/// Mode of operation for `cat-file`.
8#[derive(Debug, Clone, Copy)]
9pub enum CatFileMode {
10    /// `-t`: print the object's type.
11    Type,
12    /// `-s`: print the object's size.
13    Size,
14    /// `-e`: exit 0 if object exists, non-zero otherwise.
15    Exists,
16    /// `-p`: pretty-print the object's contents.
17    PrettyPrint,
18}
19
20/// Builder for `git cat-file`.
21#[derive(Debug, Clone)]
22pub struct CatFileCommand {
23    /// Shared executor.
24    pub executor: CommandExecutor,
25    /// Operation mode.
26    pub mode: CatFileMode,
27    /// Object to inspect.
28    pub object: String,
29}
30
31impl CatFileCommand {
32    /// Create a `cat-file -p <object>` command.
33    pub fn pretty_print(object: impl Into<String>) -> Self {
34        Self {
35            executor: CommandExecutor::default(),
36            mode: CatFileMode::PrettyPrint,
37            object: object.into(),
38        }
39    }
40
41    /// Create a `cat-file -t <object>` command.
42    pub fn object_type(object: impl Into<String>) -> Self {
43        Self {
44            executor: CommandExecutor::default(),
45            mode: CatFileMode::Type,
46            object: object.into(),
47        }
48    }
49
50    /// Create a `cat-file -s <object>` command.
51    pub fn size(object: impl Into<String>) -> Self {
52        Self {
53            executor: CommandExecutor::default(),
54            mode: CatFileMode::Size,
55            object: object.into(),
56        }
57    }
58
59    /// Create a `cat-file -e <object>` command.
60    pub fn exists(object: impl Into<String>) -> Self {
61        Self {
62            executor: CommandExecutor::default(),
63            mode: CatFileMode::Exists,
64            object: object.into(),
65        }
66    }
67
68    /// Run the command and return stdout as raw, untrimmed bytes.
69    ///
70    /// Prefer this over [`execute`](GitCommand::execute) when the object may be
71    /// binary (typically `pretty_print` on a blob): `execute` decodes stdout
72    /// lossily as UTF-8 and trims trailing whitespace, either of which corrupts
73    /// binary content.
74    pub async fn execute_bytes(&self) -> Result<Vec<u8>> {
75        if self.object.is_empty() {
76            return Err(Error::invalid_config(
77                "cat-file requires a non-empty object",
78            ));
79        }
80        let out = self.execute_raw().await?;
81        Ok(out.stdout)
82    }
83}
84
85#[async_trait]
86impl GitCommand for CatFileCommand {
87    /// Trimmed, lossily-decoded stdout. For `Exists` mode, success is reported
88    /// via `Ok(String::new())`; a missing object returns
89    /// [`Error::CommandFailed`]. For binary blobs use
90    /// [`execute_bytes`](CatFileCommand::execute_bytes) instead.
91    type Output = String;
92    fn get_executor(&self) -> &CommandExecutor {
93        &self.executor
94    }
95    fn get_executor_mut(&mut self) -> &mut CommandExecutor {
96        &mut self.executor
97    }
98    fn build_command_args(&self) -> Vec<String> {
99        let flag = match self.mode {
100            CatFileMode::Type => "-t",
101            CatFileMode::Size => "-s",
102            CatFileMode::Exists => "-e",
103            CatFileMode::PrettyPrint => "-p",
104        };
105        vec!["cat-file".into(), flag.into(), self.object.clone()]
106    }
107    async fn execute(&self) -> Result<String> {
108        if self.object.is_empty() {
109            return Err(Error::invalid_config(
110                "cat-file requires a non-empty object",
111            ));
112        }
113        let out = self.execute_raw().await?;
114        Ok(out.stdout_trimmed())
115    }
116}