bug(merge): all merge paths ignore the repo's default_merge_style — hardcoded "merge" fallback creates forge merge commits despite fast-forward-only setting #73

Closed
opened 2026-07-14 22:28:17 +00:00 by hexajon · 0 comments
hexajon commented 2026-07-14 22:28:17 +00:00 (Migrated from codeberg.org)

Symptom

Main history on a repo whose default_merge_style is fast-forward-only (set deliberately, several times) still accumulates one forge-authored merge commit per PR (Merge pull request … by <user>@noreply.codeberg.org). Those commits are unsigned, so a repo with commit.gpgsign discipline shows a permanent mix of signed work commits and unsigned merge commits — this repo's own main is the live evidence (e.g. 3b6bd54c, 2c623dee vs the signed G commits between them).

Root cause

No merge path ever consults the repo's default_merge_style:

  • internal/gitea/client_pr.go MergePR: empty Strategy falls back to a hardcoded strategy = "merge" — the API call always carries an explicit do, so the forge-side default is never in play.
  • internal/service/workflow/finish.go:1089 mergeStep: passes gitea.MergePROptions{} → hits that fallback. Every madt_finish therefore requests a merge commit, unconditionally.
  • internal/cmd/pr/merge.go: no strategy flag → hardcoded strategy := "merge" default.
  • internal/mcp/tools_prs_crud.go prsMerge: empty strategy param → same client fallback.

Setting default_merge_style on the repo (the documented forge-side lever) is silently overridden by every madtea surface.

Fix

When the caller does not choose a strategy explicitly, resolve the target repo's default_merge_style and send that as do. Explicit flags/params keep overriding. Behavior on repos whose default is merge is unchanged.

Points to cover:

  • finish mergeStep uses the resolved repo default; the behind-base retry path (MergeWithRetry rebase handling) must still work under fast-forward-only, where a behind-base branch cannot ff — needs a test.
  • CLI pr merge with no strategy flag: repo default instead of hardcoded merge (flags unchanged).
  • MCP madt_prs action=merge with no strategy: same.
  • Keep ADR 0004 clean-merge/terminal-closure semantics in finish honest under ff (no merge commit exists after an ff merge).
  • Resolve the default once per operation (the finish flow already fetches repo metadata — reuse, don't add a second round-trip per merge).
  • Docs: COMPARISON.md terse mention (default merge style now repo-driven); COMPAT.md if any flavor caveat; regenerate reference docs.

Acceptance

  • On a repo with default_merge_style: fast-forward-only, madt_finish / pr merge / madt_prs merge with no explicit strategy performs a fast-forward merge (no forge merge commit).
  • Explicit strategy flags/params still override the repo default.
  • Behind-base branch under ff-only default: finish still completes (update/rebase then ff), covered by a test.
  • Full gate green.
## Symptom Main history on a repo whose `default_merge_style` is `fast-forward-only` (set deliberately, several times) still accumulates one forge-authored merge commit per PR (`Merge pull request …` by `<user>@noreply.codeberg.org`). Those commits are unsigned, so a repo with `commit.gpgsign` discipline shows a permanent mix of signed work commits and unsigned merge commits — this repo's own main is the live evidence (e.g. 3b6bd54c, 2c623dee vs the signed `G` commits between them). ## Root cause No merge path ever consults the repo's `default_merge_style`: - `internal/gitea/client_pr.go` `MergePR`: empty `Strategy` falls back to a hardcoded `strategy = "merge"` — the API call always carries an explicit `do`, so the forge-side default is never in play. - `internal/service/workflow/finish.go:1089` `mergeStep`: passes `gitea.MergePROptions{}` → hits that fallback. Every `madt_finish` therefore requests a merge commit, unconditionally. - `internal/cmd/pr/merge.go`: no strategy flag → hardcoded `strategy := "merge"` default. - `internal/mcp/tools_prs_crud.go` `prsMerge`: empty `strategy` param → same client fallback. Setting `default_merge_style` on the repo (the documented forge-side lever) is silently overridden by every madtea surface. ## Fix When the caller does not choose a strategy explicitly, resolve the target repo's `default_merge_style` and send that as `do`. Explicit flags/params keep overriding. Behavior on repos whose default is `merge` is unchanged. Points to cover: - `finish` mergeStep uses the resolved repo default; the behind-base retry path (`MergeWithRetry` rebase handling) must still work under `fast-forward-only`, where a behind-base branch cannot ff — needs a test. - CLI `pr merge` with no strategy flag: repo default instead of hardcoded `merge` (flags unchanged). - MCP `madt_prs action=merge` with no `strategy`: same. - Keep ADR 0004 clean-merge/terminal-closure semantics in finish honest under ff (no merge commit exists after an ff merge). - Resolve the default once per operation (the finish flow already fetches repo metadata — reuse, don't add a second round-trip per merge). - Docs: COMPARISON.md terse mention (default merge style now repo-driven); COMPAT.md if any flavor caveat; regenerate reference docs. ## Acceptance - On a repo with `default_merge_style: fast-forward-only`, `madt_finish` / `pr merge` / `madt_prs merge` with no explicit strategy performs a fast-forward merge (no forge merge commit). - Explicit strategy flags/params still override the repo default. - Behind-base branch under ff-only default: finish still completes (update/rebase then ff), covered by a test. - Full gate green.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
sixfold-space/madtea#73
No description provided.