Skip to main content

Mountain/Track/Effect/CreateEffectForRequest/
Git.rs

1#![allow(non_snake_case, unused_variables, dead_code, unused_imports)]
2
3//! # Git Effect (CreateEffectForRequest)
4//!
5//! Effect constructor for the `$gitExec` command. Executes `git` as a
6//! subprocess with the provided arguments and working directory, returning
7//! the exit code, stdout, and stderr as a JSON object.
8//!
9//! ## Execution model
10//!
11//! - Command is spawned via `tokio::process::Command::new("git")`.
12//! - 30-second timeout prevents hung git operations from blocking.
13//! - Accepts both object-style params `{ args, repository/cwd }` and
14//!   array-style params `[args, cwd]` for backward compatibility with different
15//!   Cocoon shim versions.
16
17use std::{future::Future, pin::Pin, sync::Arc, time::Duration};
18
19use serde_json::{Value, json};
20use tauri::Runtime;
21
22use crate::{RunTime::ApplicationRunTime::ApplicationRunTime, Track::Effect::MappedEffectType::MappedEffect, dev_log};
23
24pub fn CreateEffect<R:Runtime>(MethodName:&str, Parameters:Value) -> Option<Result<MappedEffect, String>> {
25	match MethodName {
26		"$gitExec" => {
27			let effect =
28				move |_run_time:Arc<ApplicationRunTime>| -> Pin<Box<dyn Future<Output = Result<Value, String>> + Send>> {
29
30					Box::pin(async move {
31						let (Args, WorkingDir) = if let Some(Object) = Parameters.as_object() {
32							let ArgsVec:Vec<String> = Object
33								.get("args")
34								.and_then(Value::as_array)
35								.map(|Array| {
36									Array
37										.iter()
38										.filter_map(|V| V.as_str().map(str::to_string))
39										.collect()
40								})
41								.unwrap_or_default();
42							let RepoPath = Object
43								.get("repository")
44								.or_else(|| Object.get("cwd"))
45								.and_then(Value::as_str)
46								.map(str::to_string)
47								.unwrap_or_default();
48							(ArgsVec, RepoPath)
49						} else {
50							let ArgsVec:Vec<String> = Parameters
51								.get(0)
52								.and_then(Value::as_array)
53								.map(|Array| {
54									Array
55										.iter()
56										.filter_map(|V| V.as_str().map(str::to_string))
57										.collect()
58								})
59								.unwrap_or_default();
60							let RepoPath = Parameters
61								.get(1)
62								.and_then(Value::as_str)
63								.map(str::to_string)
64								.unwrap_or_default();
65							(ArgsVec, RepoPath)
66						};
67						let Cwd = if WorkingDir.is_empty() {
68							std::env::current_dir().unwrap_or_default()
69						} else {
70							std::path::PathBuf::from(&WorkingDir)
71						};
72						dev_log!(
73							"grpc",
74
75							"[$gitExec] Received gRPC Request: Method='$gitExec' args={:?} cwd={}",
76
77							Args,
78
79							Cwd.display()
80						);
81						let StartAt = std::time::Instant::now();
82						let OutputResult = tokio::time::timeout(
83							Duration::from_secs(30),
84							tokio::process::Command::new("git")
85								.args(&Args)
86								.current_dir(&Cwd)
87								.output(),
88						)
89						.await
90						.map_err(|_| {
91							format!(
92								"$gitExec timed out after 30s: args={:?} cwd={}",
93								Args,
94								Cwd.display()
95							)
96						})?
97						.map_err(|Error| format!("$gitExec failed to spawn git: {}", Error))?;
98						let ExitCode = OutputResult.status.code().unwrap_or(-1);
99						let Stdout = String::from_utf8_lossy(&OutputResult.stdout).to_string();
100						let Stderr = String::from_utf8_lossy(&OutputResult.stderr).to_string();
101						dev_log!(
102							"grpc",
103
104							"[$gitExec] exit={} elapsed={}ms stdout={}B stderr={}B",
105
106							ExitCode,
107
108							StartAt.elapsed().as_millis(),
109
110							Stdout.len(),
111
112							Stderr.len()
113						);
114						Ok(json!({
115							"exitCode": ExitCode,
116							"stdout": Stdout,
117							"stderr": Stderr,
118						}))
119					})
120				};
121
122			Some(Ok(Box::new(effect)))
123		},
124
125		_ => None,
126	}
127}