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    #[must_use]
99    pub fn path(mut self, p: impl Into<PathBuf>) -> Self {
100        if let SubmoduleAction::Add { path, .. } = &mut self.action {
101            *path = Some(p.into());
102        }
103        self
104    }
105
106    /// Set `-b` branch (for `add`).
107    #[must_use]
108    pub fn branch(mut self, b: impl Into<String>) -> Self {
109        if let SubmoduleAction::Add { branch, .. } = &mut self.action {
110            *branch = Some(b.into());
111        }
112        self
113    }
114
115    /// Set `--force` (for `add` / `update` / `deinit`).
116    #[must_use]
117    pub fn force(mut self) -> Self {
118        match &mut self.action {
119            SubmoduleAction::Add { force, .. }
120            | SubmoduleAction::Update { force, .. }
121            | SubmoduleAction::Deinit { force, .. } => {
122                *force = true;
123            }
124            _ => {}
125        }
126        self
127    }
128
129    /// `submodule init`.
130    #[must_use]
131    pub fn init() -> Self {
132        Self {
133            executor: CommandExecutor::default(),
134            action: SubmoduleAction::Init { paths: vec![] },
135        }
136    }
137
138    /// `submodule update`.
139    #[must_use]
140    pub fn update() -> Self {
141        Self {
142            executor: CommandExecutor::default(),
143            action: SubmoduleAction::Update {
144                init: false,
145                recursive: false,
146                remote: false,
147                force: false,
148                paths: vec![],
149            },
150        }
151    }
152
153    /// `--init` (for `update`).
154    #[must_use]
155    pub fn with_init(mut self) -> Self {
156        if let SubmoduleAction::Update { init, .. } = &mut self.action {
157            *init = true;
158        }
159        self
160    }
161
162    /// `--recursive` (for `update` / `status` / `foreach` / `sync`).
163    #[must_use]
164    pub fn recursive(mut self) -> Self {
165        match &mut self.action {
166            SubmoduleAction::Update { recursive, .. }
167            | SubmoduleAction::Status { recursive, .. }
168            | SubmoduleAction::Foreach { recursive, .. }
169            | SubmoduleAction::Sync { recursive, .. } => {
170                *recursive = true;
171            }
172            _ => {}
173        }
174        self
175    }
176
177    /// `--remote` (for `update`).
178    #[must_use]
179    pub fn remote(mut self) -> Self {
180        if let SubmoduleAction::Update { remote, .. } = &mut self.action {
181            *remote = true;
182        }
183        self
184    }
185
186    /// Restrict to a given path.
187    #[must_use]
188    pub fn restrict_path(mut self, p: impl Into<PathBuf>) -> Self {
189        let p = p.into();
190        match &mut self.action {
191            SubmoduleAction::Init { paths }
192            | SubmoduleAction::Update { paths, .. }
193            | SubmoduleAction::Status { paths, .. }
194            | SubmoduleAction::Deinit { paths, .. }
195            | SubmoduleAction::Sync { paths, .. } => {
196                paths.push(p);
197            }
198            _ => {}
199        }
200        self
201    }
202
203    /// `submodule status`.
204    #[must_use]
205    pub fn status() -> Self {
206        Self {
207            executor: CommandExecutor::default(),
208            action: SubmoduleAction::Status {
209                cached: false,
210                recursive: false,
211                paths: vec![],
212            },
213        }
214    }
215
216    /// `--cached` (for `status`).
217    #[must_use]
218    pub fn cached(mut self) -> Self {
219        if let SubmoduleAction::Status { cached, .. } = &mut self.action {
220            *cached = true;
221        }
222        self
223    }
224
225    /// `submodule foreach`.
226    pub fn foreach(command: impl Into<String>) -> Self {
227        Self {
228            executor: CommandExecutor::default(),
229            action: SubmoduleAction::Foreach {
230                command: command.into(),
231                recursive: false,
232            },
233        }
234    }
235
236    /// `submodule deinit`.
237    #[must_use]
238    pub fn deinit() -> Self {
239        Self {
240            executor: CommandExecutor::default(),
241            action: SubmoduleAction::Deinit {
242                force: false,
243                all: false,
244                paths: vec![],
245            },
246        }
247    }
248
249    /// `--all` (for `deinit`).
250    #[must_use]
251    pub fn all(mut self) -> Self {
252        if let SubmoduleAction::Deinit { all, .. } = &mut self.action {
253            *all = true;
254        }
255        self
256    }
257
258    /// `submodule sync`.
259    #[must_use]
260    pub fn sync() -> Self {
261        Self {
262            executor: CommandExecutor::default(),
263            action: SubmoduleAction::Sync {
264                recursive: false,
265                paths: vec![],
266            },
267        }
268    }
269}
270
271#[async_trait]
272impl GitCommand for SubmoduleCommand {
273    type Output = CommandOutput;
274    fn get_executor(&self) -> &CommandExecutor {
275        &self.executor
276    }
277    fn get_executor_mut(&mut self) -> &mut CommandExecutor {
278        &mut self.executor
279    }
280    fn build_command_args(&self) -> Vec<String> {
281        let mut args = vec!["submodule".to_string()];
282        match &self.action {
283            SubmoduleAction::Add {
284                url,
285                path,
286                branch,
287                force,
288            } => {
289                args.push("add".into());
290                if *force {
291                    args.push("--force".into());
292                }
293                if let Some(b) = branch {
294                    args.push("-b".into());
295                    args.push(b.clone());
296                }
297                args.push(url.clone());
298                if let Some(p) = path {
299                    args.push(p.display().to_string());
300                }
301            }
302            SubmoduleAction::Init { paths } => {
303                args.push("init".into());
304                if !paths.is_empty() {
305                    args.push("--".into());
306                    args.extend(paths.iter().map(|p| p.display().to_string()));
307                }
308            }
309            SubmoduleAction::Update {
310                init,
311                recursive,
312                remote,
313                force,
314                paths,
315            } => {
316                args.push("update".into());
317                if *init {
318                    args.push("--init".into());
319                }
320                if *recursive {
321                    args.push("--recursive".into());
322                }
323                if *remote {
324                    args.push("--remote".into());
325                }
326                if *force {
327                    args.push("--force".into());
328                }
329                if !paths.is_empty() {
330                    args.push("--".into());
331                    args.extend(paths.iter().map(|p| p.display().to_string()));
332                }
333            }
334            SubmoduleAction::Status {
335                cached,
336                recursive,
337                paths,
338            } => {
339                args.push("status".into());
340                if *cached {
341                    args.push("--cached".into());
342                }
343                if *recursive {
344                    args.push("--recursive".into());
345                }
346                if !paths.is_empty() {
347                    args.push("--".into());
348                    args.extend(paths.iter().map(|p| p.display().to_string()));
349                }
350            }
351            SubmoduleAction::Foreach { command, recursive } => {
352                args.push("foreach".into());
353                if *recursive {
354                    args.push("--recursive".into());
355                }
356                args.push(command.clone());
357            }
358            SubmoduleAction::Deinit { force, all, paths } => {
359                args.push("deinit".into());
360                if *force {
361                    args.push("--force".into());
362                }
363                if *all {
364                    args.push("--all".into());
365                }
366                if !paths.is_empty() {
367                    args.push("--".into());
368                    args.extend(paths.iter().map(|p| p.display().to_string()));
369                }
370            }
371            SubmoduleAction::Sync { recursive, paths } => {
372                args.push("sync".into());
373                if *recursive {
374                    args.push("--recursive".into());
375                }
376                if !paths.is_empty() {
377                    args.push("--".into());
378                    args.extend(paths.iter().map(|p| p.display().to_string()));
379                }
380            }
381        }
382        args
383    }
384    async fn execute(&self) -> Result<CommandOutput> {
385        self.execute_raw().await
386    }
387}