summaryrefslogtreecommitdiff
path: root/makima/src/daemon/worktree
diff options
context:
space:
mode:
authorsoryu <soryu@soryu.co>2026-01-22 22:32:46 +0000
committersoryu <soryu@soryu.co>2026-01-23 01:03:04 +0000
commit1ed362424dafec690f919154f5116471951cda9c (patch)
tree19c7ca9231887394a791223fe32a8ad335a687a8 /makima/src/daemon/worktree
parent265f8cf14fec9d7116d09af49e4b48b357faceda (diff)
downloadsoryu-1ed362424dafec690f919154f5116471951cda9c.tar.gz
soryu-1ed362424dafec690f919154f5116471951cda9c.zip
Add patch checkpointing
Diffstat (limited to 'makima/src/daemon/worktree')
-rw-r--r--makima/src/daemon/worktree/manager.rs135
1 files changed, 135 insertions, 0 deletions
diff --git a/makima/src/daemon/worktree/manager.rs b/makima/src/daemon/worktree/manager.rs
index 5edd7b1..04cb307 100644
--- a/makima/src/daemon/worktree/manager.rs
+++ b/makima/src/daemon/worktree/manager.rs
@@ -1697,6 +1697,141 @@ impl WorktreeManager {
pub async fn target_directory_exists(&self, target_dir: &Path) -> bool {
target_dir.exists()
}
+
+ /// Restore a worktree from a stored patch.
+ ///
+ /// This is used for task recovery when the local worktree has been lost.
+ /// 1. Clone/fetch the source repo to get the base commit
+ /// 2. Create a new worktree at the base commit
+ /// 3. Apply the patch to restore the task's state
+ pub async fn restore_from_patch(
+ &self,
+ source_repo: &str,
+ task_id: Uuid,
+ task_name: &str,
+ base_commit_sha: &str,
+ patch_data: &[u8],
+ ) -> Result<WorktreeInfo, WorktreeError> {
+ use crate::daemon::storage;
+
+ // Generate directory and branch names
+ let dir_name = format!("{}-{}", short_uuid(task_id), sanitize_name(task_name));
+ let worktree_path = self.base_dir.join(&dir_name);
+ let branch_name = format!(
+ "{}{}-{}",
+ self.branch_prefix,
+ sanitize_name(task_name),
+ short_uuid(task_id)
+ );
+
+ // Ensure base directory exists
+ tokio::fs::create_dir_all(&self.base_dir).await?;
+
+ // Remove existing worktree if present (we're restoring from scratch)
+ if worktree_path.exists() {
+ tracing::info!(
+ task_id = %task_id,
+ worktree_path = %worktree_path.display(),
+ "Removing existing worktree before restore"
+ );
+ tokio::fs::remove_dir_all(&worktree_path).await?;
+ }
+
+ // Clone the source repo if needed
+ let repo_path = self.ensure_repo(source_repo).await?;
+
+ // Create worktree at the base commit
+ // First, we need to make sure the base commit is available
+ let fetch_output = Command::new("git")
+ .args(["fetch", "--all"])
+ .current_dir(&repo_path)
+ .output()
+ .await?;
+
+ if !fetch_output.status.success() {
+ tracing::warn!(
+ task_id = %task_id,
+ stderr = %String::from_utf8_lossy(&fetch_output.stderr),
+ "git fetch failed, continuing anyway"
+ );
+ }
+
+ // Create the worktree from the base commit
+ let output = Command::new("git")
+ .args([
+ "worktree",
+ "add",
+ "-b",
+ &branch_name,
+ worktree_path.to_str().ok_or_else(|| {
+ WorktreeError::InvalidPath("Invalid worktree path".to_string())
+ })?,
+ base_commit_sha,
+ ])
+ .current_dir(&repo_path)
+ .output()
+ .await?;
+
+ if !output.status.success() {
+ let stderr = String::from_utf8_lossy(&output.stderr);
+ // If branch already exists, try without -b flag
+ if stderr.contains("already exists") {
+ // Remove the branch and try again
+ let _ = Command::new("git")
+ .args(["branch", "-D", &branch_name])
+ .current_dir(&repo_path)
+ .output()
+ .await;
+
+ let retry_output = Command::new("git")
+ .args([
+ "worktree",
+ "add",
+ "-b",
+ &branch_name,
+ worktree_path.to_str().unwrap(),
+ base_commit_sha,
+ ])
+ .current_dir(&repo_path)
+ .output()
+ .await?;
+
+ if !retry_output.status.success() {
+ return Err(WorktreeError::GitCommand(format!(
+ "Failed to create worktree after retry: {}",
+ String::from_utf8_lossy(&retry_output.stderr)
+ )));
+ }
+ } else {
+ return Err(WorktreeError::GitCommand(format!(
+ "Failed to create worktree: {}",
+ stderr
+ )));
+ }
+ }
+
+ // Apply the patch to restore the task's state
+ if let Err(e) = storage::apply_patch(&worktree_path, patch_data).await {
+ tracing::error!(
+ task_id = %task_id,
+ error = %e,
+ "Failed to apply patch, worktree is at base commit"
+ );
+ // Don't fail - the worktree is usable, just at the base commit
+ } else {
+ tracing::info!(
+ task_id = %task_id,
+ worktree_path = %worktree_path.display(),
+ "Successfully restored worktree from patch"
+ );
+ }
+
+ Ok(WorktreeInfo {
+ path: worktree_path,
+ branch: branch_name,
+ source_repo: repo_path,
+ })
+ }
}
/// Check if repo_source is a "new repo" request.