summaryrefslogtreecommitdiff
path: root/makima/src/daemon/storage/patch.rs
diff options
context:
space:
mode:
authorsoryu <soryu@soryu.co>2026-02-09 21:21:10 +0000
committersoryu <soryu@soryu.co>2026-02-09 21:21:21 +0000
commit526edf672aae73c3670ab6141253bf92f1fbfe8c (patch)
treeceb32352f5f38be4662564126e135724850dbc31 /makima/src/daemon/storage/patch.rs
parent76bb9da745f6c12c8e7e587a9096677bbf98f395 (diff)
downloadsoryu-526edf672aae73c3670ab6141253bf92f1fbfe8c.tar.gz
soryu-526edf672aae73c3670ab6141253bf92f1fbfe8c.zip
Add auto-PR creation for remote repos in contracts
Diffstat (limited to 'makima/src/daemon/storage/patch.rs')
-rw-r--r--makima/src/daemon/storage/patch.rs179
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