diff options
| author | soryu <soryu@soryu.co> | 2026-01-15 18:25:10 +0000 |
|---|---|---|
| committer | soryu <soryu@soryu.co> | 2026-01-15 18:25:10 +0000 |
| commit | 908973b5c08a8b7b624880843c512e8bddf37896 (patch) | |
| tree | be9b44f8ec39164ba202fadd5cd52ee646a6c2de | |
| parent | 11c78ade600a2d74b8f033f18045a0c28fac4362 (diff) | |
| download | soryu-908973b5c08a8b7b624880843c512e8bddf37896.tar.gz soryu-908973b5c08a8b7b624880843c512e8bddf37896.zip | |
Implement git config inherit system
| -rw-r--r-- | makima/src/daemon/task/manager.rs | 195 | ||||
| -rw-r--r-- | makima/src/daemon/ws/protocol.rs | 21 | ||||
| -rw-r--r-- | makima/src/server/handlers/mesh_daemon.rs | 34 | ||||
| -rw-r--r-- | makima/src/server/state.rs | 7 |
4 files changed, 257 insertions, 0 deletions
diff --git a/makima/src/daemon/task/manager.rs b/makima/src/daemon/task/manager.rs index 427a9d1..5491934 100644 --- a/makima/src/daemon/task/manager.rs +++ b/makima/src/daemon/task/manager.rs @@ -1020,6 +1020,10 @@ pub struct TaskManager { merge_trackers: Arc<RwLock<HashMap<Uuid, MergeTracker>>>, /// Active process PIDs for graceful shutdown. active_pids: Arc<RwLock<HashMap<Uuid, u32>>>, + /// Inherited git user.email for worktrees. + git_user_email: Arc<RwLock<Option<String>>>, + /// Inherited git user.name for worktrees. + git_user_name: Arc<RwLock<Option<String>>>, } impl TaskManager { @@ -1049,6 +1053,8 @@ impl TaskManager { task_inputs: Arc::new(RwLock::new(HashMap::new())), merge_trackers: Arc::new(RwLock::new(HashMap::new())), active_pids: Arc::new(RwLock::new(HashMap::new())), + git_user_email: Arc::new(RwLock::new(None)), + git_user_name: Arc::new(RwLock::new(None)), } } @@ -1449,6 +1455,10 @@ impl TaskManager { ); self.handle_cleanup_worktree(task_id, delete_branch).await?; } + DaemonCommand::InheritGitConfig { source_dir } => { + tracing::info!(source_dir = ?source_dir, "Inheriting git config"); + self.handle_inherit_git_config(source_dir).await?; + } } Ok(()) } @@ -1571,6 +1581,8 @@ impl TaskManager { ws_tx: self.ws_tx.clone(), task_inputs: self.task_inputs.clone(), active_pids: self.active_pids.clone(), + git_user_email: self.git_user_email.clone(), + git_user_name: self.git_user_name.clone(), } } @@ -2737,6 +2749,121 @@ impl TaskManager { (total_added, total_removed, serde_json::json!(files)) } + + /// Handle InheritGitConfig command - read git config from a directory and store it. + async fn handle_inherit_git_config( + &self, + source_dir: Option<String>, + ) -> Result<(), DaemonError> { + // Use provided directory or current working directory + let dir = source_dir + .map(std::path::PathBuf::from) + .unwrap_or_else(|| std::env::current_dir().unwrap_or_else(|_| std::path::PathBuf::from("."))); + + tracing::info!(dir = ?dir, "Reading git config from directory"); + + // Read user.email + let email_output = tokio::process::Command::new("git") + .current_dir(&dir) + .args(["config", "user.email"]) + .output() + .await; + + let user_email = match email_output { + Ok(output) if output.status.success() => { + let email = String::from_utf8_lossy(&output.stdout).trim().to_string(); + if !email.is_empty() { + Some(email) + } else { + None + } + } + _ => None, + }; + + // Read user.name + let name_output = tokio::process::Command::new("git") + .current_dir(&dir) + .args(["config", "user.name"]) + .output() + .await; + + let user_name = match name_output { + Ok(output) if output.status.success() => { + let name = String::from_utf8_lossy(&output.stdout).trim().to_string(); + if !name.is_empty() { + Some(name) + } else { + None + } + } + _ => None, + }; + + // Check if we got at least one value + if user_email.is_none() && user_name.is_none() { + let msg = DaemonMessage::GitConfigInherited { + success: false, + user_email: None, + user_name: None, + error: Some("No git config found in the specified directory".to_string()), + }; + let _ = self.ws_tx.send(msg).await; + return Ok(()); + } + + // Store the config + if let Some(ref email) = user_email { + *self.git_user_email.write().await = Some(email.clone()); + tracing::info!(email = %email, "Inherited git user.email"); + } + if let Some(ref name) = user_name { + *self.git_user_name.write().await = Some(name.clone()); + tracing::info!(name = %name, "Inherited git user.name"); + } + + // Send success response + let msg = DaemonMessage::GitConfigInherited { + success: true, + user_email, + user_name, + error: None, + }; + let _ = self.ws_tx.send(msg).await; + Ok(()) + } + + /// Apply inherited git config to a worktree directory. + pub async fn apply_git_config(&self, worktree_path: &std::path::Path) -> Result<(), DaemonError> { + let email = self.git_user_email.read().await.clone(); + let name = self.git_user_name.read().await.clone(); + + if let Some(email) = email { + let result = tokio::process::Command::new("git") + .current_dir(worktree_path) + .args(["config", "user.email", &email]) + .output() + .await; + + if let Err(e) = result { + tracing::warn!(error = %e, "Failed to set git user.email in worktree"); + } + } + + if let Some(name) = name { + let result = tokio::process::Command::new("git") + .current_dir(worktree_path) + .args(["config", "user.name", &name]) + .output() + .await; + + if let Err(e) = result { + tracing::warn!(error = %e, "Failed to set git user.name in worktree"); + } + } + + Ok(()) + } } /// Inner state for spawned tasks (cloneable). @@ -2748,6 +2875,8 @@ struct TaskManagerInner { ws_tx: mpsc::Sender<DaemonMessage>, task_inputs: Arc<RwLock<HashMap<Uuid, mpsc::Sender<String>>>>, active_pids: Arc<RwLock<HashMap<Uuid, u32>>>, + git_user_email: Arc<RwLock<Option<String>>>, + git_user_name: Arc<RwLock<Option<String>>>, } impl TaskManagerInner { @@ -2800,6 +2929,9 @@ impl TaskManagerInner { "New repository created" ); + // Apply inherited git config to the new repo (overrides defaults) + self.apply_git_config(&worktree_info.path).await; + // Store worktree info { let mut tasks = self.tasks.write().await; @@ -2882,6 +3014,9 @@ impl TaskManagerInner { "Worktree created" ); + // Apply inherited git config to the worktree + self.apply_git_config(&worktree_info.path).await; + // Store worktree info { let mut tasks = self.tasks.write().await; @@ -3919,6 +4054,64 @@ impl TaskManagerInner { let msg = DaemonMessage::task_complete(task_id, false, Some(error.to_string())); let _ = self.ws_tx.send(msg).await; } + + /// Apply inherited git config to a worktree directory. + async fn apply_git_config(&self, worktree_path: &std::path::Path) { + let email = self.git_user_email.read().await.clone(); + let name = self.git_user_name.read().await.clone(); + + if email.is_none() && name.is_none() { + return; // No inherited config to apply + } + + if let Some(email) = email { + let result = tokio::process::Command::new("git") + .current_dir(worktree_path) + .args(["config", "user.email", &email]) + .output() + .await; + + match result { + Ok(output) if output.status.success() => { + tracing::debug!(email = %email, path = ?worktree_path, "Applied git user.email to worktree"); + } + Ok(output) => { + tracing::warn!( + path = ?worktree_path, + stderr = %String::from_utf8_lossy(&output.stderr), + "Failed to set git user.email in worktree" + ); + } + Err(e) => { + tracing::warn!(error = %e, "Failed to run git config user.email"); + } + } + } + + if let Some(name) = name { + let result = tokio::process::Command::new("git") + .current_dir(worktree_path) + .args(["config", "user.name", &name]) + .output() + .await; + + match result { + Ok(output) if output.status.success() => { + tracing::debug!(name = %name, path = ?worktree_path, "Applied git user.name to worktree"); + } + Ok(output) => { + tracing::warn!( + path = ?worktree_path, + stderr = %String::from_utf8_lossy(&output.stderr), + "Failed to set git user.name in worktree" + ); + } + Err(e) => { + tracing::warn!(error = %e, "Failed to run git config user.name"); + } + } + } + } } impl Clone for TaskManagerInner { @@ -3931,6 +4124,8 @@ impl Clone for TaskManagerInner { ws_tx: self.ws_tx.clone(), task_inputs: self.task_inputs.clone(), active_pids: self.active_pids.clone(), + git_user_email: self.git_user_email.clone(), + git_user_name: self.git_user_name.clone(), } } } diff --git a/makima/src/daemon/ws/protocol.rs b/makima/src/daemon/ws/protocol.rs index 339f5a4..5c9004b 100644 --- a/makima/src/daemon/ws/protocol.rs +++ b/makima/src/daemon/ws/protocol.rs @@ -287,6 +287,19 @@ pub enum DaemonMessage { success: bool, message: String, }, + + /// Response to InheritGitConfig command. + GitConfigInherited { + success: bool, + /// Git user.email that was inherited + #[serde(rename = "userEmail")] + user_email: Option<String>, + /// Git user.name that was inherited + #[serde(rename = "userName")] + user_name: Option<String>, + /// Error message if failed + error: Option<String>, + }, } /// Information about a branch (used in BranchList message). @@ -589,6 +602,14 @@ pub enum DaemonCommand { delete_branch: bool, }, + /// Inherit git config (user.email, user.name) from a directory. + /// This config will be applied to all future worktrees. + InheritGitConfig { + /// Directory to read git config from (defaults to daemon's working directory). + #[serde(rename = "sourceDir")] + source_dir: Option<String>, + }, + /// Error response. Error { code: String, diff --git a/makima/src/server/handlers/mesh_daemon.rs b/makima/src/server/handlers/mesh_daemon.rs index 0d00f5b..9833d51 100644 --- a/makima/src/server/handlers/mesh_daemon.rs +++ b/makima/src/server/handlers/mesh_daemon.rs @@ -410,6 +410,19 @@ pub enum DaemonMessage { /// User-provided checkpoint message message: String, }, + /// Notification that git config was inherited + GitConfigInherited { + /// Whether the operation succeeded + success: bool, + /// Git user.email that was inherited + #[serde(rename = "userEmail")] + user_email: Option<String>, + /// Git user.name that was inherited + #[serde(rename = "userName")] + user_name: Option<String>, + /// Error message if operation failed + error: Option<String>, + }, } /// Validated daemon authentication result. @@ -1239,6 +1252,27 @@ async fn handle_daemon_connection(socket: WebSocket, state: SharedState, auth_re }); } } + Ok(DaemonMessage::GitConfigInherited { + success, + user_email, + user_name, + error, + }) => { + if success { + tracing::info!( + daemon_id = %daemon_uuid, + user_email = ?user_email, + user_name = ?user_name, + "Daemon inherited git config" + ); + } else { + tracing::warn!( + daemon_id = %daemon_uuid, + error = ?error, + "Failed to inherit git config" + ); + } + } Err(e) => { tracing::warn!("Failed to parse daemon message: {}", e); } diff --git a/makima/src/server/state.rs b/makima/src/server/state.rs index 6a56f21..479eadf 100644 --- a/makima/src/server/state.rs +++ b/makima/src/server/state.rs @@ -413,6 +413,13 @@ pub enum DaemonCommand { delete_branch: bool, }, + /// Inherit git config (user.email, user.name) from a directory + InheritGitConfig { + /// Directory to read git config from (defaults to daemon's working directory) + #[serde(rename = "sourceDir")] + source_dir: Option<String>, + }, + /// Error response Error { code: String, message: String }, } |
