diff options
Diffstat (limited to 'makima/src/daemon/storage/patch.rs')
| -rw-r--r-- | makima/src/daemon/storage/patch.rs | 179 |
1 files changed, 94 insertions, 85 deletions
diff --git a/makima/src/daemon/storage/patch.rs b/makima/src/daemon/storage/patch.rs index b374d15..05c45a3 100644 --- a/makima/src/daemon/storage/patch.rs +++ b/makima/src/daemon/storage/patch.rs @@ -161,6 +161,99 @@ pub async fn get_head_sha(worktree_path: &Path) -> Result<String, PatchError> { Ok(String::from_utf8_lossy(&output.stdout).trim().to_string()) } +/// Resolve the merge-base SHA for diffing against the main/master branch. +/// +/// Tries in order: +/// 1. Upstream tracking branch merge-base +/// 2. Common branches: origin/main, origin/master, main, master +/// 3. Fallback: HEAD~1 +/// +/// Returns `Err(PatchError::EmptyPatch)` if the merge-base equals HEAD (no diff). +pub async fn get_merge_base_sha(worktree_path: &Path) -> Result<String, PatchError> { + // Try to get the upstream tracking branch + let upstream_result = Command::new("git") + .current_dir(worktree_path) + .args(["rev-parse", "--abbrev-ref", "--symbolic-full-name", "@{u}"]) + .output() + .await; + + let base = if let Ok(output) = upstream_result { + if output.status.success() { + let upstream = String::from_utf8_lossy(&output.stdout).trim().to_string(); + // Get merge-base with upstream + let merge_base = Command::new("git") + .current_dir(worktree_path) + .args(["merge-base", "HEAD", &upstream]) + .output() + .await; + + if let Ok(mb_output) = merge_base { + if mb_output.status.success() { + Some( + String::from_utf8_lossy(&mb_output.stdout) + .trim() + .to_string(), + ) + } else { + None + } + } else { + None + } + } else { + None + } + } else { + None + }; + + // Get current HEAD SHA for comparison + let head_sha = Command::new("git") + .current_dir(worktree_path) + .args(["rev-parse", "HEAD"]) + .output() + .await + .ok() + .filter(|o| o.status.success()) + .map(|o| String::from_utf8_lossy(&o.stdout).trim().to_string()); + + // If we couldn't find upstream, try common default branches + let base = if base.is_none() { + let default_branches = ["origin/main", "origin/master", "main", "master"]; + let mut found_base = None; + + for branch in default_branches { + let merge_base = Command::new("git") + .current_dir(worktree_path) + .args(["merge-base", "HEAD", branch]) + .output() + .await; + + if let Ok(output) = merge_base { + if output.status.success() { + let mb_sha = String::from_utf8_lossy(&output.stdout).trim().to_string(); + // Skip if merge-base equals HEAD (would result in empty diff) + if head_sha.as_ref() != Some(&mb_sha) { + found_base = Some(mb_sha); + break; + } + } + } + } + found_base + } else { + // Also check upstream base + if base.as_ref() == head_sha.as_ref() { + None + } else { + base + } + }; + + // If still nothing, fall back to HEAD~1 + Ok(base.unwrap_or_else(|| "HEAD~1".to_string())) +} + /// Result of creating an export patch. #[derive(Debug, Clone)] pub struct ExportPatchResult { @@ -192,91 +285,7 @@ pub async fn create_export_patch( // Determine the base SHA to diff against let resolved_base_sha = match base_sha { Some(sha) => sha.to_string(), - None => { - // Try to find the merge-base with the default branch - // First, try to get the upstream tracking branch - let upstream_result = Command::new("git") - .current_dir(worktree_path) - .args(["rev-parse", "--abbrev-ref", "--symbolic-full-name", "@{u}"]) - .output() - .await; - - let base = if let Ok(output) = upstream_result { - if output.status.success() { - let upstream = String::from_utf8_lossy(&output.stdout).trim().to_string(); - // Get merge-base with upstream - let merge_base = Command::new("git") - .current_dir(worktree_path) - .args(["merge-base", "HEAD", &upstream]) - .output() - .await; - - if let Ok(mb_output) = merge_base { - if mb_output.status.success() { - Some(String::from_utf8_lossy(&mb_output.stdout).trim().to_string()) - } else { - None - } - } else { - None - } - } else { - None - } - } else { - None - }; - - // Get current HEAD SHA for comparison - let head_sha = Command::new("git") - .current_dir(worktree_path) - .args(["rev-parse", "HEAD"]) - .output() - .await - .ok() - .filter(|o| o.status.success()) - .map(|o| String::from_utf8_lossy(&o.stdout).trim().to_string()); - - // If we couldn't find upstream, try common default branches - let base = if base.is_none() { - let default_branches = ["origin/main", "origin/master", "main", "master"]; - let mut found_base = None; - - for branch in default_branches { - let merge_base = Command::new("git") - .current_dir(worktree_path) - .args(["merge-base", "HEAD", branch]) - .output() - .await; - - if let Ok(output) = merge_base { - if output.status.success() { - let mb_sha = String::from_utf8_lossy(&output.stdout).trim().to_string(); - // Skip if merge-base equals HEAD (would result in empty diff) - if head_sha.as_ref() != Some(&mb_sha) { - found_base = Some(mb_sha); - break; - } - } - } - } - found_base - } else { - // Also check upstream base - if base.as_ref() == head_sha.as_ref() { - None - } else { - base - } - }; - - // If still nothing, get the first commit or use HEAD~1 - base.unwrap_or_else(|| { - // This will be used, but if HEAD~1 doesn't exist (only one commit), - // git diff will handle it gracefully - "HEAD~1".to_string() - }) - } + None => get_merge_base_sha(worktree_path).await?, }; // Get diff stats using --stat |
