feat(refinery): use squash merge to eliminate redundant merge commits (#856)
Replace MergeNoFF with MergeSquash in the Refinery to preserve the original conventional commit message (feat:/fix:) from polecat branches instead of creating "Merge polecat/... into main" commits. Changes: - Add MergeSquash function to internal/git/git.go - Add GetBranchCommitMessage helper to retrieve branch commit messages - Update engineer.go doMerge to use squash merge with original message Fixes #855 Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -570,6 +570,27 @@ func (g *Git) MergeNoFF(branch, message string) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MergeSquash performs a squash merge of the given branch and commits with the provided message.
|
||||||
|
// This stages all changes from the branch without creating a merge commit, then commits them
|
||||||
|
// as a single commit with the given message. This eliminates redundant merge commits while
|
||||||
|
// preserving the original commit message from the source branch.
|
||||||
|
func (g *Git) MergeSquash(branch, message string) error {
|
||||||
|
// Stage all changes from the branch without committing
|
||||||
|
if _, err := g.run("merge", "--squash", branch); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// Commit the staged changes with the provided message
|
||||||
|
_, err := g.run("commit", "-m", message)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetBranchCommitMessage returns the commit message of the HEAD commit on the given branch.
|
||||||
|
// This is useful for preserving the original conventional commit message (feat:/fix:) when
|
||||||
|
// performing squash merges.
|
||||||
|
func (g *Git) GetBranchCommitMessage(branch string) (string, error) {
|
||||||
|
return g.run("log", "-1", "--format=%B", branch)
|
||||||
|
}
|
||||||
|
|
||||||
// DeleteRemoteBranch deletes a branch on the remote.
|
// DeleteRemoteBranch deletes a branch on the remote.
|
||||||
func (g *Git) DeleteRemoteBranch(remote, branch string) error {
|
func (g *Git) DeleteRemoteBranch(remote, branch string) error {
|
||||||
_, err := g.run("push", remote, "--delete", branch)
|
_, err := g.run("push", remote, "--delete", branch)
|
||||||
|
|||||||
@@ -319,13 +319,20 @@ func (e *Engineer) doMerge(ctx context.Context, branch, target, sourceIssue stri
|
|||||||
_, _ = fmt.Fprintln(e.output, "[Engineer] Tests passed")
|
_, _ = fmt.Fprintln(e.output, "[Engineer] Tests passed")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Step 5: Perform the actual merge
|
// Step 5: Perform the actual merge using squash merge
|
||||||
mergeMsg := fmt.Sprintf("Merge %s into %s", branch, target)
|
// Get the original commit message from the polecat branch to preserve the
|
||||||
if sourceIssue != "" {
|
// conventional commit format (feat:/fix:) instead of creating redundant merge commits
|
||||||
mergeMsg = fmt.Sprintf("Merge %s into %s (%s)", branch, target, sourceIssue)
|
originalMsg, err := e.git.GetBranchCommitMessage(branch)
|
||||||
|
if err != nil {
|
||||||
|
// Fallback to a descriptive message if we can't get the original
|
||||||
|
originalMsg = fmt.Sprintf("Squash merge %s into %s", branch, target)
|
||||||
|
if sourceIssue != "" {
|
||||||
|
originalMsg = fmt.Sprintf("Squash merge %s into %s (%s)", branch, target, sourceIssue)
|
||||||
|
}
|
||||||
|
_, _ = fmt.Fprintf(e.output, "[Engineer] Warning: could not get original commit message: %v\n", err)
|
||||||
}
|
}
|
||||||
_, _ = fmt.Fprintf(e.output, "[Engineer] Merging with message: %s\n", mergeMsg)
|
_, _ = fmt.Fprintf(e.output, "[Engineer] Squash merging with message: %s\n", strings.TrimSpace(originalMsg))
|
||||||
if err := e.git.MergeNoFF(branch, mergeMsg); err != nil {
|
if err := e.git.MergeSquash(branch, originalMsg); err != nil {
|
||||||
// ZFC: Use git's porcelain output to detect conflicts instead of parsing stderr.
|
// ZFC: Use git's porcelain output to detect conflicts instead of parsing stderr.
|
||||||
// GetConflictingFiles() uses `git diff --diff-filter=U` which is proper.
|
// GetConflictingFiles() uses `git diff --diff-filter=U` which is proper.
|
||||||
conflicts, conflictErr := e.git.GetConflictingFiles()
|
conflicts, conflictErr := e.git.GetConflictingFiles()
|
||||||
|
|||||||
Reference in New Issue
Block a user