Skip to main content

ironflow_ops_git/
fetch.rs

1//! Fetch and push operations.
2
3use std::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 FetchPushOutput {
16    pub remote: String,
17    pub refspecs: Vec<String>,
18}
19
20#[derive(Debug, Clone, Serialize, Deserialize)]
21pub struct RemotePruneOutput {
22    pub remote: String,
23    pub pruned: bool,
24}
25
26#[derive(Debug, Clone, Serialize, Deserialize)]
27pub struct RemoteDefaultBranchOutput {
28    pub remote: String,
29    pub default_branch: Option<String>,
30}
31
32/// Fetch from a remote.
33///
34/// # Examples
35///
36/// ```no_run
37/// use ironflow_ops_git::fetch::FetchRemote;
38/// use ironflow_core::operation::Operation;
39///
40/// let op = FetchRemote::new("/path/to/repo", "origin", vec!["main"]);
41/// assert_eq!(op.kind(), "git");
42/// ```
43pub struct FetchRemote {
44    repo_path: PathBuf,
45    remote_name: String,
46    refspecs: Vec<String>,
47}
48
49impl FetchRemote {
50    /// Create a new fetch operation.
51    pub fn new(
52        repo_path: impl Into<PathBuf>,
53        remote_name: impl Into<String>,
54        refspecs: Vec<impl Into<String>>,
55    ) -> Self {
56        Self {
57            repo_path: repo_path.into(),
58            remote_name: remote_name.into(),
59            refspecs: refspecs.into_iter().map(Into::into).collect(),
60        }
61    }
62
63    /// Execute and return a typed result.
64    pub async fn run(&self, _ctx: &OperationContext) -> Result<FetchPushOutput, OperationError> {
65        let repo_path = self.repo_path.clone();
66        let remote_name = self.remote_name.clone();
67        let refspecs = self.refspecs.clone();
68        blocking(move || {
69            let repo = Repository::open(&repo_path)?;
70            let mut remote = repo.find_remote(&remote_name)?;
71            let refs: Vec<&str> = refspecs.iter().map(String::as_str).collect();
72            remote.fetch(&refs, None, None)?;
73            Ok(FetchPushOutput {
74                remote: remote_name,
75                refspecs,
76            })
77        })
78        .await
79    }
80}
81
82#[async_trait]
83impl Operation for FetchRemote {
84    fn kind(&self) -> &str {
85        "git"
86    }
87    async fn execute(&self, ctx: &OperationContext) -> Result<Value, OperationError> {
88        to_value(&self.run(ctx).await?)
89    }
90    fn input(&self) -> Option<Value> {
91        Some(serde_json::json!({ "repo_path": self.repo_path, "remote": self.remote_name }))
92    }
93}
94
95impl TypedOperation for FetchRemote {
96    type Output = FetchPushOutput;
97}
98
99/// Push to a remote.
100///
101/// # Examples
102///
103/// ```no_run
104/// use ironflow_ops_git::fetch::PushRemote;
105/// use ironflow_core::operation::Operation;
106///
107/// let op = PushRemote::new("/path/to/repo", "origin", vec!["refs/heads/main"]);
108/// assert_eq!(op.kind(), "git");
109/// ```
110pub struct PushRemote {
111    repo_path: PathBuf,
112    remote_name: String,
113    refspecs: Vec<String>,
114}
115
116impl PushRemote {
117    /// Create a new push operation.
118    pub fn new(
119        repo_path: impl Into<PathBuf>,
120        remote_name: impl Into<String>,
121        refspecs: Vec<impl Into<String>>,
122    ) -> Self {
123        Self {
124            repo_path: repo_path.into(),
125            remote_name: remote_name.into(),
126            refspecs: refspecs.into_iter().map(Into::into).collect(),
127        }
128    }
129
130    /// Execute and return a typed result.
131    pub async fn run(&self, _ctx: &OperationContext) -> Result<FetchPushOutput, OperationError> {
132        let repo_path = self.repo_path.clone();
133        let remote_name = self.remote_name.clone();
134        let refspecs = self.refspecs.clone();
135        blocking(move || {
136            let repo = Repository::open(&repo_path)?;
137            let mut remote = repo.find_remote(&remote_name)?;
138            let refs: Vec<&str> = refspecs.iter().map(String::as_str).collect();
139            remote.push(&refs, None)?;
140            Ok(FetchPushOutput {
141                remote: remote_name,
142                refspecs,
143            })
144        })
145        .await
146    }
147}
148
149#[async_trait]
150impl Operation for PushRemote {
151    fn kind(&self) -> &str {
152        "git"
153    }
154    async fn execute(&self, ctx: &OperationContext) -> Result<Value, OperationError> {
155        to_value(&self.run(ctx).await?)
156    }
157    fn input(&self) -> Option<Value> {
158        Some(serde_json::json!({ "repo_path": self.repo_path, "remote": self.remote_name }))
159    }
160}
161
162impl TypedOperation for PushRemote {
163    type Output = FetchPushOutput;
164}
165
166/// Prune stale remote-tracking branches.
167///
168/// # Examples
169///
170/// ```no_run
171/// use ironflow_ops_git::fetch::RemotePrune;
172/// use ironflow_core::operation::Operation;
173///
174/// let op = RemotePrune::new("/path/to/repo", "origin");
175/// assert_eq!(op.kind(), "git");
176/// ```
177pub struct RemotePrune {
178    repo_path: PathBuf,
179    remote_name: String,
180}
181
182impl RemotePrune {
183    /// Create a new prune operation.
184    pub fn new(repo_path: impl Into<PathBuf>, remote_name: impl Into<String>) -> Self {
185        Self {
186            repo_path: repo_path.into(),
187            remote_name: remote_name.into(),
188        }
189    }
190
191    /// Execute and return a typed result.
192    pub async fn run(&self, _ctx: &OperationContext) -> Result<RemotePruneOutput, OperationError> {
193        let repo_path = self.repo_path.clone();
194        let remote_name = self.remote_name.clone();
195        blocking(move || {
196            let repo = Repository::open(&repo_path)?;
197            let mut remote = repo.find_remote(&remote_name)?;
198            remote.prune(None)?;
199            Ok(RemotePruneOutput {
200                remote: remote_name,
201                pruned: true,
202            })
203        })
204        .await
205    }
206}
207
208#[async_trait]
209impl Operation for RemotePrune {
210    fn kind(&self) -> &str {
211        "git"
212    }
213    async fn execute(&self, ctx: &OperationContext) -> Result<Value, OperationError> {
214        to_value(&self.run(ctx).await?)
215    }
216    fn input(&self) -> Option<Value> {
217        Some(serde_json::json!({ "repo_path": self.repo_path, "remote": self.remote_name }))
218    }
219}
220
221impl TypedOperation for RemotePrune {
222    type Output = RemotePruneOutput;
223}
224
225/// Get the default branch of a remote.
226///
227/// # Examples
228///
229/// ```no_run
230/// use ironflow_ops_git::fetch::RemoteDefaultBranch;
231/// use ironflow_core::operation::Operation;
232///
233/// let op = RemoteDefaultBranch::new("/path/to/repo", "origin");
234/// assert_eq!(op.kind(), "git");
235/// ```
236pub struct RemoteDefaultBranch {
237    repo_path: PathBuf,
238    remote_name: String,
239}
240
241impl RemoteDefaultBranch {
242    /// Create a new default-branch query operation.
243    pub fn new(repo_path: impl Into<PathBuf>, remote_name: impl Into<String>) -> Self {
244        Self {
245            repo_path: repo_path.into(),
246            remote_name: remote_name.into(),
247        }
248    }
249
250    /// Execute and return a typed result.
251    pub async fn run(
252        &self,
253        _ctx: &OperationContext,
254    ) -> Result<RemoteDefaultBranchOutput, OperationError> {
255        let repo_path = self.repo_path.clone();
256        let remote_name = self.remote_name.clone();
257        blocking(move || {
258            let repo = Repository::open(&repo_path)?;
259            let remote = repo.find_remote(&remote_name)?;
260            let default = remote.default_branch()?;
261            let name = default.as_str().map(String::from);
262            Ok(RemoteDefaultBranchOutput {
263                remote: remote_name,
264                default_branch: name,
265            })
266        })
267        .await
268    }
269}
270
271#[async_trait]
272impl Operation for RemoteDefaultBranch {
273    fn kind(&self) -> &str {
274        "git"
275    }
276    async fn execute(&self, ctx: &OperationContext) -> Result<Value, OperationError> {
277        to_value(&self.run(ctx).await?)
278    }
279    fn input(&self) -> Option<Value> {
280        Some(serde_json::json!({ "repo_path": self.repo_path, "remote": self.remote_name }))
281    }
282}
283
284impl TypedOperation for RemoteDefaultBranch {
285    type Output = RemoteDefaultBranchOutput;
286}