Skip to main content

git_spawn/command/
symbolic_ref.rs

1//! `git symbolic-ref` — read or modify a symbolic ref (most commonly `HEAD`).
2
3use crate::command::{CommandExecutor, GitCommand};
4use crate::error::{Error, Result};
5use async_trait::async_trait;
6
7/// Actions supported by `git symbolic-ref`.
8#[derive(Debug, Clone)]
9pub enum SymbolicRefAction {
10    /// Read the target of `ref` (e.g. `HEAD` -> `refs/heads/main`).
11    Read {
12        /// Ref name to read.
13        name: String,
14        /// `--short` shows the short form (`main`).
15        short: bool,
16    },
17    /// Set `name` to point at `target`.
18    Set {
19        /// Ref name.
20        name: String,
21        /// Target ref.
22        target: String,
23        /// `-m <reason>` reflog message.
24        reason: Option<String>,
25    },
26    /// Delete the symbolic ref.
27    Delete {
28        /// Ref name.
29        name: String,
30        /// `-q` suppress errors.
31        quiet: bool,
32    },
33}
34
35/// Builder for `git symbolic-ref`.
36#[derive(Debug, Clone)]
37pub struct SymbolicRefCommand {
38    /// Shared executor.
39    pub executor: CommandExecutor,
40    /// Action.
41    pub action: SymbolicRefAction,
42}
43
44impl SymbolicRefCommand {
45    /// Read the target of `name` (e.g. `read("HEAD")`).
46    pub fn read(name: impl Into<String>) -> Self {
47        Self {
48            executor: CommandExecutor::default(),
49            action: SymbolicRefAction::Read {
50                name: name.into(),
51                short: false,
52            },
53        }
54    }
55
56    /// `--short` for a read (only applies when the action is [`read`](Self::read)).
57    #[must_use]
58    pub fn short(mut self) -> Self {
59        if let SymbolicRefAction::Read { short, .. } = &mut self.action {
60            *short = true;
61        }
62        self
63    }
64
65    /// Set `name` to point at `target`.
66    pub fn set(name: impl Into<String>, target: impl Into<String>) -> Self {
67        Self {
68            executor: CommandExecutor::default(),
69            action: SymbolicRefAction::Set {
70                name: name.into(),
71                target: target.into(),
72                reason: None,
73            },
74        }
75    }
76
77    /// Set the reflog reason (`-m`, only for [`set`](Self::set)).
78    #[must_use]
79    pub fn reason(mut self, r: impl Into<String>) -> Self {
80        if let SymbolicRefAction::Set { reason, .. } = &mut self.action {
81            *reason = Some(r.into());
82        }
83        self
84    }
85
86    /// Delete the symbolic ref.
87    pub fn delete(name: impl Into<String>) -> Self {
88        Self {
89            executor: CommandExecutor::default(),
90            action: SymbolicRefAction::Delete {
91                name: name.into(),
92                quiet: false,
93            },
94        }
95    }
96
97    /// `-q` (only for [`delete`](Self::delete)).
98    #[must_use]
99    pub fn quiet(mut self) -> Self {
100        if let SymbolicRefAction::Delete { quiet, .. } = &mut self.action {
101            *quiet = true;
102        }
103        self
104    }
105}
106
107#[async_trait]
108impl GitCommand for SymbolicRefCommand {
109    /// Trimmed stdout — the resolved target for `read`, empty for `set` / `delete`.
110    type Output = String;
111
112    fn get_executor(&self) -> &CommandExecutor {
113        &self.executor
114    }
115
116    fn get_executor_mut(&mut self) -> &mut CommandExecutor {
117        &mut self.executor
118    }
119
120    fn build_command_args(&self) -> Vec<String> {
121        let mut args = vec!["symbolic-ref".to_string()];
122        match &self.action {
123            SymbolicRefAction::Read { name, short } => {
124                if *short {
125                    args.push("--short".into());
126                }
127                args.push(name.clone());
128            }
129            SymbolicRefAction::Set {
130                name,
131                target,
132                reason,
133            } => {
134                if let Some(r) = reason {
135                    args.push("-m".into());
136                    args.push(r.clone());
137                }
138                args.push(name.clone());
139                args.push(target.clone());
140            }
141            SymbolicRefAction::Delete { name, quiet } => {
142                args.push("--delete".into());
143                if *quiet {
144                    args.push("-q".into());
145                }
146                args.push(name.clone());
147            }
148        }
149        args
150    }
151
152    async fn execute(&self) -> Result<String> {
153        if let SymbolicRefAction::Read { name, .. } | SymbolicRefAction::Set { name, .. } =
154            &self.action
155        {
156            if name.is_empty() {
157                return Err(Error::invalid_config("symbolic-ref requires a ref name"));
158            }
159        }
160        let out = self.execute_raw().await?;
161        Ok(out.stdout_trimmed().to_string())
162    }
163}