Skip to main content

Mountain/Track/Effect/CreateEffectForRequest/
Git.rs

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