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    pub fn short(&mut self) -> &mut Self {
58        if let SymbolicRefAction::Read { short, .. } = &mut self.action {
59            *short = true;
60        }
61        self
62    }
63
64    /// Set `name` to point at `target`.
65    pub fn set(name: impl Into<String>, target: impl Into<String>) -> Self {
66        Self {
67            executor: CommandExecutor::default(),
68            action: SymbolicRefAction::Set {
69                name: name.into(),
70                target: target.into(),
71                reason: None,
72            },
73        }
74    }
75
76    /// Set the reflog reason (`-m`, only for [`set`](Self::set)).
77    pub fn reason(&mut self, r: impl Into<String>) -> &mut Self {
78        if let SymbolicRefAction::Set { reason, .. } = &mut self.action {
79            *reason = Some(r.into());
80        }
81        self
82    }
83
84    /// Delete the symbolic ref.
85    pub fn delete(name: impl Into<String>) -> Self {
86        Self {
87            executor: CommandExecutor::default(),
88            action: SymbolicRefAction::Delete {
89                name: name.into(),
90                quiet: false,
91            },
92        }
93    }
94
95    /// `-q` (only for [`delete`](Self::delete)).
96    pub fn quiet(&mut self) -> &mut Self {
97        if let SymbolicRefAction::Delete { quiet, .. } = &mut self.action {
98            *quiet = true;
99        }
100        self
101    }
102}
103
104#[async_trait]
105impl GitCommand for SymbolicRefCommand {
106    /// Trimmed stdout — the resolved target for `read`, empty for `set` / `delete`.
107    type Output = String;
108
109    fn get_executor(&self) -> &CommandExecutor {
110        &self.executor
111    }
112
113    fn get_executor_mut(&mut self) -> &mut CommandExecutor {
114        &mut self.executor
115    }
116
117    fn build_command_args(&self) -> Vec<String> {
118        let mut args = vec!["symbolic-ref".to_string()];
119        match &self.action {
120            SymbolicRefAction::Read { name, short } => {
121                if *short {
122                    args.push("--short".into());
123                }
124                args.push(name.clone());
125            }
126            SymbolicRefAction::Set {
127                name,
128                target,
129                reason,
130            } => {
131                if let Some(r) = reason {
132                    args.push("-m".into());
133                    args.push(r.clone());
134                }
135                args.push(name.clone());
136                args.push(target.clone());
137            }
138            SymbolicRefAction::Delete { name, quiet } => {
139                args.push("--delete".into());
140                if *quiet {
141                    args.push("-q".into());
142                }
143                args.push(name.clone());
144            }
145        }
146        args
147    }
148
149    async fn execute(&self) -> Result<String> {
150        if let SymbolicRefAction::Read { name, .. } | SymbolicRefAction::Set { name, .. } =
151            &self.action
152        {
153            if name.is_empty() {
154                return Err(Error::invalid_config("symbolic-ref requires a ref name"));
155            }
156        }
157        let out = self.execute_raw().await?;
158        Ok(out.stdout_trimmed())
159    }
160}