Skip to main content

Mountain/IPC/WindServiceHandlers/Git/
Shared.rs

1#![allow(non_snake_case, dead_code)]
2
3//! Shared helpers for the `Git/*` atomic handlers. Holds:
4//!
5//! - `RunningProcesses` static - the cancel-by-`OperationId` pid map. Survives
6//!   across `tauri::invoke` calls so a different invoke can cancel an in-flight
7//!   git op.
8//! - `RunGit` - spawn `git`, register the pid, await the output, clear the pid,
9//!   return `(exit, stdout, stderr)`.
10//! - `AsStringArray`, `Generated`, `ResolveCwd` - small parsers used by every
11//!   entry point.
12
13use std::{
14	collections::HashMap,
15	path::PathBuf,
16	sync::{Mutex, OnceLock},
17};
18
19use serde_json::Value;
20use tokio::process::Command;
21
22use crate::dev_log;
23
24pub fn RunningProcesses() -> &'static Mutex<HashMap<String, u32>> {
25	static SLOT:OnceLock<Mutex<HashMap<String, u32>>> = OnceLock::new();
26	SLOT.get_or_init(|| Mutex::new(HashMap::new()))
27}
28
29pub fn RegisterPid(OperationId:&str, Pid:u32) {
30	if OperationId.is_empty() {
31		return;
32	}
33	if let Ok(mut Map) = RunningProcesses().lock() {
34		Map.insert(OperationId.to_string(), Pid);
35	}
36}
37
38pub fn ClearPid(OperationId:&str) {
39	if OperationId.is_empty() {
40		return;
41	}
42	if let Ok(mut Map) = RunningProcesses().lock() {
43		Map.remove(OperationId);
44	}
45}
46
47pub fn TakePid(OperationId:&str) -> Option<u32> {
48	if OperationId.is_empty() {
49		return None;
50	}
51	RunningProcesses().lock().ok().and_then(|mut M| M.remove(OperationId))
52}
53
54pub fn ResolveCwd(Raw:&str) -> PathBuf {
55	if Raw.is_empty() {
56		std::env::current_dir().unwrap_or_default()
57	} else {
58		PathBuf::from(Raw)
59	}
60}
61
62pub async fn RunGit(OperationId:&str, Args:&[String], Cwd:Option<&str>) -> Result<(i32, String, String), String> {
63	dev_log!(
64		"git",
65		"[Git] exec-begin op={} cwd={} Arguments=[{}]",
66		OperationId,
67		Cwd.unwrap_or("<inherit>"),
68		Args.join(" ")
69	);
70
71	let WorkingDir = Cwd
72		.map(ResolveCwd)
73		.unwrap_or_else(|| std::env::current_dir().unwrap_or_default());
74
75	let mut Spawn = Command::new("git");
76	Spawn.args(Args).current_dir(&WorkingDir).kill_on_drop(true);
77
78	let Child = Spawn.spawn().map_err(|Error| {
79		dev_log!(
80			"git",
81			"[Git] exec-spawn-fail op={} Arguments=[{}] error={}",
82			OperationId,
83			Args.join(" "),
84			Error
85		);
86		format!("git spawn failed: {}", Error)
87	})?;
88
89	if let Some(Pid) = Child.id() {
90		RegisterPid(OperationId, Pid);
91	}
92
93	let Output = Child.wait_with_output().await.map_err(|Error| {
94		ClearPid(OperationId);
95		format!("git wait failed: {}", Error)
96	})?;
97
98	ClearPid(OperationId);
99
100	let ExitCode = Output.status.code().unwrap_or(-1);
101	let Stdout = String::from_utf8_lossy(&Output.stdout).into_owned();
102	let Stderr = String::from_utf8_lossy(&Output.stderr).into_owned();
103
104	dev_log!(
105		"git",
106		"[Git] exec-done op={} Arguments=[{}] exit={} stdout={}B stderr={}B",
107		OperationId,
108		Args.join(" "),
109		ExitCode,
110		Stdout.len(),
111		Stderr.len()
112	);
113
114	Ok((ExitCode, Stdout, Stderr))
115}
116
117pub fn AsStringArray(Value:&Value) -> Vec<String> {
118	Value
119		.as_array()
120		.map(|Arr| Arr.iter().filter_map(|V| V.as_str().map(str::to_string)).collect())
121		.unwrap_or_default()
122}
123
124pub fn Generated() -> String { uuid::Uuid::new_v4().to_string() }