1use tracing::{debug, info};
4use uuid::Uuid;
5
6use crate::{client::DaytonaClient, error::Result, models::git::*};
7
8pub struct GitManager<'a> {
10 client: &'a DaytonaClient,
11}
12
13impl<'a> GitManager<'a> {
14 pub fn new(client: &'a DaytonaClient) -> Self {
16 Self { client }
17 }
18
19 pub async fn clone(&self, sandbox_id: &Uuid, request: GitCloneRequest) -> Result<()> {
21 info!(
22 "Cloning repository {} into sandbox {}",
23 request.url, sandbox_id
24 );
25
26 let response = self
27 .client
28 .build_request(
29 reqwest::Method::POST,
30 &format!("/toolbox/{}/toolbox/git/clone", sandbox_id),
31 )
32 .json(&request)
33 .send()
34 .await?;
35
36 if response.status().is_success() {
37 Ok(())
38 } else {
39 let error_text = response.text().await?;
40 Err(crate::error::DaytonaError::RequestFailed(format!(
41 "Failed to clone repository: {}",
42 error_text
43 )))
44 }
45 }
46
47 pub async fn status(&self, sandbox_id: &Uuid, path: &str) -> Result<GitStatus> {
49 debug!("Getting Git status for {} in sandbox {}", path, sandbox_id);
50
51 let response = self
52 .client
53 .build_request(
54 reqwest::Method::GET,
55 &format!(
56 "/toolbox/{}/toolbox/git/status?path={}",
57 sandbox_id,
58 urlencoding::encode(path)
59 ),
60 )
61 .send()
62 .await?;
63
64 self.client.handle_response(response).await
65 }
66
67 pub async fn commit(&self, sandbox_id: &Uuid, request: GitCommitRequest) -> Result<String> {
69 info!(
70 "Committing changes in {} for sandbox {}",
71 request.path, sandbox_id
72 );
73
74 let response = self
75 .client
76 .build_request(
77 reqwest::Method::POST,
78 &format!("/toolbox/{}/toolbox/git/commit", sandbox_id),
79 )
80 .json(&request)
81 .send()
82 .await?;
83
84 if response.status().is_success() {
85 let result: serde_json::Value = self.client.handle_response(response).await?;
86 Ok(result["commit_hash"].as_str().unwrap_or("").to_string())
87 } else {
88 let error_text = response.text().await?;
89 Err(crate::error::DaytonaError::RequestFailed(format!(
90 "Failed to commit changes: {}",
91 error_text
92 )))
93 }
94 }
95
96 pub async fn push(&self, sandbox_id: &Uuid, path: &str, request: GitPushRequest) -> Result<()> {
98 info!("Pushing changes from {} in sandbox {}", path, sandbox_id);
99
100 let response = self
101 .client
102 .build_request(
103 reqwest::Method::POST,
104 &format!(
105 "/toolbox/{}/toolbox/git/push?path={}",
106 sandbox_id,
107 urlencoding::encode(path)
108 ),
109 )
110 .json(&request)
111 .send()
112 .await?;
113
114 if response.status().is_success() {
115 Ok(())
116 } else {
117 let error_text = response.text().await?;
118 Err(crate::error::DaytonaError::RequestFailed(format!(
119 "Failed to push changes: {}",
120 error_text
121 )))
122 }
123 }
124
125 pub async fn pull(&self, sandbox_id: &Uuid, path: &str, request: GitPullRequest) -> Result<()> {
127 info!("Pulling changes to {} in sandbox {}", path, sandbox_id);
128
129 let response = self
130 .client
131 .build_request(
132 reqwest::Method::POST,
133 &format!(
134 "/toolbox/{}/toolbox/git/pull?path={}",
135 sandbox_id,
136 urlencoding::encode(path)
137 ),
138 )
139 .json(&request)
140 .send()
141 .await?;
142
143 if response.status().is_success() {
144 Ok(())
145 } else {
146 let error_text = response.text().await?;
147 Err(crate::error::DaytonaError::RequestFailed(format!(
148 "Failed to pull changes: {}",
149 error_text
150 )))
151 }
152 }
153
154 pub async fn history(&self, sandbox_id: &Uuid, path: &str) -> Result<Vec<GitCommitInfo>> {
156 debug!("Getting Git history for {} in sandbox {}", path, sandbox_id);
157
158 let url = format!(
159 "/toolbox/{}/toolbox/git/history?path={}",
160 sandbox_id,
161 urlencoding::encode(path)
162 );
163
164 let response = self
165 .client
166 .build_request(reqwest::Method::GET, &url)
167 .send()
168 .await?;
169
170 self.client.handle_response(response).await
171 }
172
173 pub async fn log(&self, sandbox_id: &Uuid, path: &str) -> Result<Vec<GitCommitInfo>> {
175 self.history(sandbox_id, path).await
176 }
177
178 pub async fn list_branches(&self, sandbox_id: &Uuid, path: &str) -> Result<Vec<String>> {
180 debug!(
181 "Listing Git branches for {} in sandbox {}",
182 path, sandbox_id
183 );
184
185 let response = self
186 .client
187 .build_request(
188 reqwest::Method::GET,
189 &format!(
190 "/toolbox/{}/toolbox/git/branches?path={}",
191 sandbox_id,
192 urlencoding::encode(path)
193 ),
194 )
195 .send()
196 .await?;
197
198 let result: ListBranchResponse = self.client.handle_response(response).await?;
199 Ok(result.branches)
200 }
201
202 pub async fn create_branch(
204 &self,
205 sandbox_id: &Uuid,
206 path: &str,
207 branch_name: &str,
208 ) -> Result<()> {
209 info!(
210 "Creating branch {} in {} for sandbox {}",
211 branch_name, path, sandbox_id
212 );
213
214 let body = serde_json::json!({
215 "path": path,
216 "name": branch_name
217 });
218
219 let response = self
220 .client
221 .build_request(
222 reqwest::Method::POST,
223 &format!("/toolbox/{}/toolbox/git/branches", sandbox_id),
224 )
225 .json(&body)
226 .send()
227 .await?;
228
229 if response.status().is_success() {
230 Ok(())
231 } else {
232 let error_text = response.text().await?;
233 Err(crate::error::DaytonaError::RequestFailed(format!(
234 "Failed to create branch: {}",
235 error_text
236 )))
237 }
238 }
239
240 pub async fn switch_branch(
242 &self,
243 sandbox_id: &Uuid,
244 path: &str,
245 branch_name: &str,
246 ) -> Result<()> {
247 info!(
248 "Switching to branch {} in {} for sandbox {}",
249 branch_name, path, sandbox_id
250 );
251
252 let body = serde_json::json!({
253 "path": path,
254 "branch": branch_name
255 });
256
257 let response = self
258 .client
259 .build_request(
260 reqwest::Method::POST,
261 &format!("/toolbox/{}/toolbox/git/checkout", sandbox_id),
262 )
263 .json(&body)
264 .send()
265 .await?;
266
267 if response.status().is_success() {
268 Ok(())
269 } else {
270 let error_text = response.text().await?;
271 Err(crate::error::DaytonaError::RequestFailed(format!(
272 "Failed to switch branch: {}",
273 error_text
274 )))
275 }
276 }
277
278 pub async fn delete_branch(
280 &self,
281 sandbox_id: &Uuid,
282 path: &str,
283 branch_name: &str,
284 ) -> Result<()> {
285 info!(
286 "Deleting branch {} in {} for sandbox {}",
287 branch_name, path, sandbox_id
288 );
289
290 let body = serde_json::json!({
291 "path": path,
292 "name": branch_name
293 });
294
295 let response = self
296 .client
297 .build_request(
298 reqwest::Method::DELETE,
299 &format!("/toolbox/{}/toolbox/git/branches", sandbox_id),
300 )
301 .json(&body)
302 .send()
303 .await?;
304
305 if response.status().is_success() {
306 Ok(())
307 } else {
308 let error_text = response.text().await?;
309 Err(crate::error::DaytonaError::RequestFailed(format!(
310 "Failed to delete branch: {}",
311 error_text
312 )))
313 }
314 }
315
316 pub async fn stage(&self, sandbox_id: &Uuid, path: &str, files: Vec<String>) -> Result<()> {
318 info!(
319 "Staging {} files in {} for sandbox {}",
320 files.len(),
321 path,
322 sandbox_id
323 );
324
325 let body = serde_json::json!({
326 "path": path,
327 "files": files
328 });
329
330 let response = self
331 .client
332 .build_request(
333 reqwest::Method::POST,
334 &format!("/toolbox/{}/toolbox/git/add", sandbox_id),
335 )
336 .json(&body)
337 .send()
338 .await?;
339
340 if response.status().is_success() {
341 Ok(())
342 } else {
343 let error_text = response.text().await?;
344 Err(crate::error::DaytonaError::RequestFailed(format!(
345 "Failed to stage files: {}",
346 error_text
347 )))
348 }
349 }
350}
351
352impl DaytonaClient {
353 pub fn git(&self) -> GitManager<'_> {
355 GitManager::new(self)
356 }
357}