1use std::path::{Path, PathBuf};
4
5use async_trait::async_trait;
6use git2::Repository;
7use ironflow_core::error::OperationError;
8use ironflow_core::operation::{Operation, OperationContext, TypedOperation};
9use serde::{Deserialize, Serialize};
10use serde_json::Value;
11
12use crate::helpers::{blocking, to_value};
13
14#[derive(Debug, Clone, Serialize, Deserialize)]
15pub struct SubmoduleAddOutput {
16 pub url: String,
17 pub path: String,
18}
19
20#[derive(Debug, Clone, Serialize, Deserialize)]
21pub struct SubmoduleInitOutput {
22 pub name: String,
23 pub initialized: bool,
24}
25
26#[derive(Debug, Clone, Serialize, Deserialize)]
27pub struct SubmoduleUpdateOutput {
28 pub name: String,
29 pub updated: bool,
30}
31
32#[derive(Debug, Clone, Serialize, Deserialize)]
33pub struct SubmoduleLookupOutput {
34 pub name: String,
35 pub url: String,
36 pub path: String,
37 pub head_id: Option<String>,
38}
39
40#[derive(Debug, Clone, Serialize, Deserialize)]
42pub struct SubmoduleEntry {
43 pub name: String,
44 pub url: String,
45 pub path: String,
46}
47
48#[derive(Debug, Clone, Serialize, Deserialize)]
49pub struct SubmoduleListOutput {
50 pub submodules: Vec<SubmoduleEntry>,
51}
52
53pub struct SubmoduleAdd {
65 repo_path: PathBuf,
66 url: String,
67 path: String,
68}
69
70impl SubmoduleAdd {
71 pub fn new(
73 repo_path: impl Into<PathBuf>,
74 url: impl Into<String>,
75 path: impl Into<String>,
76 ) -> Self {
77 Self {
78 repo_path: repo_path.into(),
79 url: url.into(),
80 path: path.into(),
81 }
82 }
83
84 pub async fn run(&self, _ctx: &OperationContext) -> Result<SubmoduleAddOutput, OperationError> {
86 let repo_path = self.repo_path.clone();
87 let url = self.url.clone();
88 let path = self.path.clone();
89 blocking(move || {
90 let repo = Repository::open(&repo_path)?;
91 repo.submodule(&url, Path::new(&path), true)?;
92 Ok(SubmoduleAddOutput { url, path })
93 })
94 .await
95 }
96}
97
98#[async_trait]
99impl Operation for SubmoduleAdd {
100 fn kind(&self) -> &str {
101 "git"
102 }
103 async fn execute(&self, ctx: &OperationContext) -> Result<Value, OperationError> {
104 to_value(&self.run(ctx).await?)
105 }
106 fn input(&self) -> Option<Value> {
107 Some(serde_json::json!({ "repo_path": self.repo_path, "url": self.url, "path": self.path }))
108 }
109}
110
111impl TypedOperation for SubmoduleAdd {
112 type Output = SubmoduleAddOutput;
113}
114
115pub struct SubmoduleInit {
127 repo_path: PathBuf,
128 name: String,
129}
130
131impl SubmoduleInit {
132 pub fn new(repo_path: impl Into<PathBuf>, name: impl Into<String>) -> Self {
134 Self {
135 repo_path: repo_path.into(),
136 name: name.into(),
137 }
138 }
139
140 pub async fn run(
142 &self,
143 _ctx: &OperationContext,
144 ) -> Result<SubmoduleInitOutput, OperationError> {
145 let repo_path = self.repo_path.clone();
146 let name = self.name.clone();
147 blocking(move || {
148 let repo = Repository::open(&repo_path)?;
149 let mut sub = repo.find_submodule(&name)?;
150 sub.init(false)?;
151 Ok(SubmoduleInitOutput {
152 name,
153 initialized: true,
154 })
155 })
156 .await
157 }
158}
159
160#[async_trait]
161impl Operation for SubmoduleInit {
162 fn kind(&self) -> &str {
163 "git"
164 }
165 async fn execute(&self, ctx: &OperationContext) -> Result<Value, OperationError> {
166 to_value(&self.run(ctx).await?)
167 }
168 fn input(&self) -> Option<Value> {
169 Some(serde_json::json!({ "repo_path": self.repo_path, "name": self.name }))
170 }
171}
172
173impl TypedOperation for SubmoduleInit {
174 type Output = SubmoduleInitOutput;
175}
176
177pub struct SubmoduleUpdate {
189 repo_path: PathBuf,
190 name: String,
191}
192
193impl SubmoduleUpdate {
194 pub fn new(repo_path: impl Into<PathBuf>, name: impl Into<String>) -> Self {
196 Self {
197 repo_path: repo_path.into(),
198 name: name.into(),
199 }
200 }
201
202 pub async fn run(
204 &self,
205 _ctx: &OperationContext,
206 ) -> Result<SubmoduleUpdateOutput, OperationError> {
207 let repo_path = self.repo_path.clone();
208 let name = self.name.clone();
209 blocking(move || {
210 let repo = Repository::open(&repo_path)?;
211 let mut sub = repo.find_submodule(&name)?;
212 sub.update(true, None)?;
213 Ok(SubmoduleUpdateOutput {
214 name,
215 updated: true,
216 })
217 })
218 .await
219 }
220}
221
222#[async_trait]
223impl Operation for SubmoduleUpdate {
224 fn kind(&self) -> &str {
225 "git"
226 }
227 async fn execute(&self, ctx: &OperationContext) -> Result<Value, OperationError> {
228 to_value(&self.run(ctx).await?)
229 }
230 fn input(&self) -> Option<Value> {
231 Some(serde_json::json!({ "repo_path": self.repo_path, "name": self.name }))
232 }
233}
234
235impl TypedOperation for SubmoduleUpdate {
236 type Output = SubmoduleUpdateOutput;
237}
238
239pub struct SubmoduleLookup {
251 repo_path: PathBuf,
252 name: String,
253}
254
255impl SubmoduleLookup {
256 pub fn new(repo_path: impl Into<PathBuf>, name: impl Into<String>) -> Self {
258 Self {
259 repo_path: repo_path.into(),
260 name: name.into(),
261 }
262 }
263
264 pub async fn run(
266 &self,
267 _ctx: &OperationContext,
268 ) -> Result<SubmoduleLookupOutput, OperationError> {
269 let repo_path = self.repo_path.clone();
270 let name = self.name.clone();
271 blocking(move || {
272 let repo = Repository::open(&repo_path)?;
273 let sub = repo.find_submodule(&name)?;
274 Ok(SubmoduleLookupOutput {
275 name: sub.name().unwrap_or("").to_string(),
276 url: sub.url().unwrap_or("").to_string(),
277 path: sub.path().to_string_lossy().into_owned(),
278 head_id: sub.head_id().map(|o| o.to_string()),
279 })
280 })
281 .await
282 }
283}
284
285#[async_trait]
286impl Operation for SubmoduleLookup {
287 fn kind(&self) -> &str {
288 "git"
289 }
290 async fn execute(&self, ctx: &OperationContext) -> Result<Value, OperationError> {
291 to_value(&self.run(ctx).await?)
292 }
293 fn input(&self) -> Option<Value> {
294 Some(serde_json::json!({ "repo_path": self.repo_path, "name": self.name }))
295 }
296}
297
298impl TypedOperation for SubmoduleLookup {
299 type Output = SubmoduleLookupOutput;
300}
301
302pub struct SubmoduleList {
314 repo_path: PathBuf,
315}
316
317impl SubmoduleList {
318 pub fn new(repo_path: impl Into<PathBuf>) -> Self {
320 Self {
321 repo_path: repo_path.into(),
322 }
323 }
324
325 pub async fn run(
327 &self,
328 _ctx: &OperationContext,
329 ) -> Result<SubmoduleListOutput, OperationError> {
330 let repo_path = self.repo_path.clone();
331 blocking(move || {
332 let repo = Repository::open(&repo_path)?;
333 let subs = repo.submodules()?;
334 let list = subs
335 .iter()
336 .map(|s| SubmoduleEntry {
337 name: s.name().unwrap_or("").to_string(),
338 url: s.url().unwrap_or("").to_string(),
339 path: s.path().to_string_lossy().into_owned(),
340 })
341 .collect();
342 Ok(SubmoduleListOutput { submodules: list })
343 })
344 .await
345 }
346}
347
348#[async_trait]
349impl Operation for SubmoduleList {
350 fn kind(&self) -> &str {
351 "git"
352 }
353 async fn execute(&self, ctx: &OperationContext) -> Result<Value, OperationError> {
354 to_value(&self.run(ctx).await?)
355 }
356 fn input(&self) -> Option<Value> {
357 Some(serde_json::json!({ "repo_path": self.repo_path }))
358 }
359}
360
361impl TypedOperation for SubmoduleList {
362 type Output = SubmoduleListOutput;
363}