New Sign in

fierj

Public

Tiny personal git forge

9fc2523d4cde5e05199725b2293df13dd08f3899
diff --git a/patch.go b/patch.go
index 4d4956c..254ada4 100644
--- a/patch.go
+++ b/patch.go
@@ -186,13 +186,10 @@ func mergePatch(repoPath string, p *Patch) error {
 		return fmt.Errorf("patch is not open")
 	}
 	if current.Branch != "" {
-		cmd := exec.Command("git", "merge", "--ff-only", current.Branch)
-		cmd.Dir = repoPath
-		var stderr bytes.Buffer
-		cmd.Stderr = &stderr
-		if err := cmd.Run(); err != nil {
-			slog.Error("patch merge failed", "repo", filepath.Base(repoPath), "branch", current.Branch, "error", err, "stderr", stderr.String())
-			return fmt.Errorf("cannot fast-forward merge %q: %s", current.Branch, strings.TrimSpace(stderr.String()))
+		target := defaultBranch(repoPath)
+		if err := fastForwardMerge(repoPath, target, current.Branch); err != nil {
+			slog.Error("patch merge failed", "repo", filepath.Base(repoPath), "branch", current.Branch, "error", err)
+			return err
 		}
 	} else if current.PatchFile != "" {
 		patchFilePath := filepath.Join(patchDataDir(repoPath), current.PatchFile)
@@ -238,6 +235,26 @@ func writePatch(repoPath string, p *Patch) error {
 	return os.Rename(tmp, target)
 }
 
+// fastForwardMerge updates target to point to branch if target is an ancestor
+// of branch (pure fast-forward). Works in bare repositories.
+func fastForwardMerge(repoPath, target, branch string) error {
+	// Check that target is an ancestor of branch.
+	cmd := exec.Command("git", "merge-base", "--is-ancestor", "refs/heads/"+target, "refs/heads/"+branch)
+	cmd.Dir = repoPath
+	if err := cmd.Run(); err != nil {
+		return fmt.Errorf("%q is not ahead of %q — rebase the branch first", branch, target)
+	}
+	// Fast-forward: update the target ref to point to the branch.
+	cmd = exec.Command("git", "update-ref", "refs/heads/"+target, "refs/heads/"+branch)
+	cmd.Dir = repoPath
+	var stderr bytes.Buffer
+	cmd.Stderr = &stderr
+	if err := cmd.Run(); err != nil {
+		return fmt.Errorf("update-ref failed: %s", strings.TrimSpace(stderr.String()))
+	}
+	return nil
+}
+
 // nonDefaultBranches returns branches that aren't the default (potential patches).
 func nonDefaultBranches(repoPath string) []string {
 	def := defaultBranch(repoPath)