summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorsoryu <soryu@soryu.co>2026-01-15 18:25:10 +0000
committersoryu <soryu@soryu.co>2026-01-15 18:25:10 +0000
commit908973b5c08a8b7b624880843c512e8bddf37896 (patch)
treebe9b44f8ec39164ba202fadd5cd52ee646a6c2de
parent11c78ade600a2d74b8f033f18045a0c28fac4362 (diff)
downloadsoryu-908973b5c08a8b7b624880843c512e8bddf37896.tar.gz
soryu-908973b5c08a8b7b624880843c512e8bddf37896.zip
Implement git config inherit system
-rw-r--r--makima/src/daemon/task/manager.rs195
-rw-r--r--makima/src/daemon/ws/protocol.rs21
-rw-r--r--makima/src/server/handlers/mesh_daemon.rs34
-rw-r--r--makima/src/server/state.rs7
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 },
}