Skip to main content

git_spawn/command/
submodule.rs

1//! `git submodule` — initialize, update, or inspect submodules.
2
3use crate::command::{CommandExecutor, CommandOutput, GitCommand};
4use crate::error::Result;
5use async_trait::async_trait;
6use std::path::PathBuf;
7
8/// Actions supported by `git submodule`.
9#[derive(Debug, Clone)]
10pub enum SubmoduleAction {
11    /// `git submodule add <url> [<path>]`.
12    Add {
13        /// Submodule URL.
14        url: String,
15        /// Optional path where the submodule is placed.
16        path: Option<PathBuf>,
17        /// `-b <branch>`.
18        branch: Option<String>,
19        /// `--force`.
20        force: bool,
21    },
22    /// `git submodule init [<paths>…]`.
23    Init {
24        /// Restrict to these paths.
25        paths: Vec<PathBuf>,
26    },
27    /// `git submodule update [<options>] [<paths>…]`.
28    Update {
29        /// `--init`.
30        init: bool,
31        /// `--recursive`.
32        recursive: bool,
33        /// `--remote`.
34        remote: bool,
35        /// `--force`.
36        force: bool,
37        /// Restrict to these paths.
38        paths: Vec<PathBuf>,
39    },
40    /// `git submodule status [<paths>…]`.
41    Status {
42        /// `--cached`.
43        cached: bool,
44        /// `--recursive`.
45        recursive: bool,
46        /// Restrict to these paths.
47        paths: Vec<PathBuf>,
48    },
49    /// `git submodule foreach <cmd>`.
50    Foreach {
51        /// Command to run inside each submodule.
52        command: String,
53        /// `--recursive`.
54        recursive: bool,
55    },
56    /// `git submodule deinit <paths>`.
57    Deinit {
58        /// `--force`.
59        force: bool,
60        /// `--all`.
61        all: bool,
62        /// Paths to deinit.
63        paths: Vec<PathBuf>,
64    },
65    /// `git submodule sync [<paths>…]`.
66    Sync {
67        /// `--recursive`.
68        recursive: bool,
69        /// Restrict to these paths.
70        paths: Vec<PathBuf>,
71    },
72}
73
74/// Builder for `git submodule`.
75#[derive(Debug, Clone)]
76pub struct SubmoduleCommand {
77    /// Shared executor.
78    pub executor: CommandExecutor,
79    /// Action.
80    pub action: SubmoduleAction,
81}
82
83impl SubmoduleCommand {
84    /// `submodule add`.
85    pub fn add(url: impl Into<String>) -> Self {
86        Self {
87            executor: CommandExecutor::default(),
88            action: SubmoduleAction::Add {
89                url: url.into(),
90                path: None,
91                branch: None,
92                force: false,
93            },
94        }
95    }
96
97    /// Set the submodule path (for `add`).
98    pub fn path(&mut self, p: impl Into<PathBuf>) -> &mut Self {
99        if let SubmoduleAction::Add { path, .. } = &mut self.action {
100            *path = Some(p.into());
101        }
102        self
103    }
104
105    /// Set `-b` branch (for `add`).
106    pub fn branch(&mut self, b: impl Into<String>) -> &mut Self {
107        if let SubmoduleAction::Add { branch, .. } = &mut self.action {
108            *branch = Some(b.into());
109        }
110        self
111    }
112
113    /// Set `--force` (for `add` / `update` / `deinit`).
114    pub fn force(&mut self) -> &mut Self {
115        match &mut self.action {
116            SubmoduleAction::Add { force, .. }
117            | SubmoduleAction::Update { force, .. }
118            | SubmoduleAction::Deinit { force, .. } => {
119                *force = true;
120            }
121            _ => {}
122        }
123        self
124    }
125
126    /// `submodule init`.
127    #[must_use]
128    pub fn init() -> Self {
129        Self {
130            executor: CommandExecutor::default(),
131            action: SubmoduleAction::Init { paths: vec![] },
132        }
133    }
134
135    /// `submodule update`.
136    #[must_use]
137    pub fn update() -> Self {
138        Self {
139            executor: CommandExecutor::default(),
140            action: SubmoduleAction::Update {
141                init: false,
142                recursive: false,
143                remote: false,
144                force: false,
145                paths: vec![],
146            },
147        }
148    }
149
150    /// `--init` (for `update`).
151    pub fn with_init(&mut self) -> &mut Self {
152        if let SubmoduleAction::Update { init, .. } = &mut self.action {
153            *init = true;
154        }
155        self
156    }
157
158    /// `--recursive` (for `update` / `status` / `foreach` / `sync`).
159    pub fn recursive(&mut self) -> &mut Self {
160        match &mut self.action {
161            SubmoduleAction::Update { recursive, .. }
162            | SubmoduleAction::Status { recursive, .. }
163            | SubmoduleAction::Foreach { recursive, .. }
164            | SubmoduleAction::Sync { recursive, .. } => {
165                *recursive = true;
166            }
167            _ => {}
168        }
169        self
170    }
171
172    /// `--remote` (for `update`).
173    pub fn remote(&mut self) -> &mut Self {
174        if let SubmoduleAction::Update { remote, .. } = &mut self.action {
175            *remote = true;
176        }
177        self
178    }
179
180    /// Restrict to a given path.
181    pub fn restrict_path(&mut self, p: impl Into<PathBuf>) -> &mut Self {
182        let p = p.into();
183        match &mut self.action {
184            SubmoduleAction::Init { paths }
185            | SubmoduleAction::Update { paths, .. }
186            | SubmoduleAction::Status { paths, .. }
187            | SubmoduleAction::Deinit { paths, .. }
188            | SubmoduleAction::Sync { paths, .. } => {
189                paths.push(p);
190            }
191            _ => {}
192        }
193        self
194    }
195
196    /// `submodule status`.
197    #[must_use]
198    pub fn status() -> Self {
199        Self {
200            executor: CommandExecutor::default(),
201            action: SubmoduleAction::Status {
202                cached: false,
203                recursive: false,
204                paths: vec![],
205            },
206        }
207    }
208
209    /// `--cached` (for `status`).
210    pub fn cached(&mut self) -> &mut Self {
211        if let SubmoduleAction::Status { cached, .. } = &mut self.action {
212            *cached = true;
213        }
214        self
215    }
216
217    /// `submodule foreach`.
218    pub fn foreach(command: impl Into<String>) -> Self {
219        Self {
220            executor: CommandExecutor::default(),
221            action: SubmoduleAction::Foreach {
222                command: command.into(),
223                recursive: false,
224            },
225        }
226    }
227
228    /// `submodule deinit`.
229    #[must_use]
230    pub fn deinit() -> Self {
231        Self {
232            executor: CommandExecutor::default(),
233            action: SubmoduleAction::Deinit {
234                force: false,
235                all: false,
236                paths: vec![],
237            },
238        }
239    }
240
241    /// `--all` (for `deinit`).
242    pub fn all(&mut self) -> &mut Self {
243        if let SubmoduleAction::Deinit { all, .. } = &mut self.action {
244            *all = true;
245        }
246        self
247    }
248
249    /// `submodule sync`.
250    #[must_use]
251    pub fn sync() -> Self {
252        Self {
253            executor: CommandExecutor::default(),
254            action: SubmoduleAction::Sync {
255                recursive: false,
256                paths: vec![],
257            },
258        }
259    }
260}
261
262#[async_trait]
263impl GitCommand for SubmoduleCommand {
264    type Output = CommandOutput;
265    fn get_executor(&self) -> &CommandExecutor {
266        &self.executor
267    }
268    fn get_executor_mut(&mut self) -> &mut CommandExecutor {
269        &mut self.executor
270    }
271    fn build_command_args(&self) -> Vec<String> {
272        let mut args = vec!["submodule".to_string()];
273        match &self.action {
274            SubmoduleAction::Add {
275                url,
276                path,
277                branch,
278                force,
279            } => {
280                args.push("add".into());
281                if *force {
282                    args.push("--force".into());
283                }
284                if let Some(b) = branch {
285                    args.push("-b".into());
286                    args.push(b.clone());
287                }
288                args.push(url.clone());
289                if let Some(p) = path {
290                    args.push(p.display().to_string());
291                }
292            }
293            SubmoduleAction::Init { paths } => {
294                args.push("init".into());
295                if !paths.is_empty() {
296                    args.push("--".into());
297                    args.extend(paths.iter().map(|p| p.display().to_string()));
298                }
299            }
300            SubmoduleAction::Update {
301                init,
302                recursive,
303                remote,
304                force,
305                paths,
306            } => {
307                args.push("update".into());
308                if *init {
309                    args.push("--init".into());
310                }
311                if *recursive {
312                    args.push("--recursive".into());
313                }
314                if *remote {
315                    args.push("--remote".into());
316                }
317                if *force {
318                    args.push("--force".into());
319                }
320                if !paths.is_empty() {
321                    args.push("--".into());
322                    args.extend(paths.iter().map(|p| p.display().to_string()));
323                }
324            }
325            SubmoduleAction::Status {
326                cached,
327                recursive,
328                paths,
329            } => {
330                args.push("status".into());
331                if *cached {
332                    args.push("--cached".into());
333                }
334                if *recursive {
335                    args.push("--recursive".into());
336                }
337                if !paths.is_empty() {
338                    args.push("--".into());
339                    args.extend(paths.iter().map(|p| p.display().to_string()));
340                }
341            }
342            SubmoduleAction::Foreach { command, recursive } => {
343                args.push("foreach".into());
344                if *recursive {
345                    args.push("--recursive".into());
346                }
347                args.push(command.clone());
348            }
349            SubmoduleAction::Deinit { force, all, paths } => {
350                args.push("deinit".into());
351                if *force {
352                    args.push("--force".into());
353                }
354                if *all {
355                    args.push("--all".into());
356                }
357                if !paths.is_empty() {
358                    args.push("--".into());
359                    args.extend(paths.iter().map(|p| p.display().to_string()));
360                }
361            }
362            SubmoduleAction::Sync { recursive, paths } => {
363                args.push("sync".into());
364                if *recursive {
365                    args.push("--recursive".into());
366                }
367                if !paths.is_empty() {
368                    args.push("--".into());
369                    args.extend(paths.iter().map(|p| p.display().to_string()));
370                }
371            }
372        }
373        args
374    }
375    async fn execute(&self) -> Result<CommandOutput> {
376        self.execute_raw().await
377    }
378}