From eb8e6d5169dcb62b2cf126432b27264e349d599b Mon Sep 17 00:00:00 2001 From: Mathias Date: Sat, 10 Oct 2026 10:48:55 +0200 Subject: [PATCH] fix(pr_merge): send Gitea's merge_title_field; merge_message_title was silently ignored (gitea-mcp#77) Gitea 1.27's MergePullRequestOption has merge_title_field, not merge_message_title; an unknown field is dropped without an error, so a custom merge title never took effect. The tool's argument keeps its name. The new test checks the field name on the wire (the existing fake never did). Co-Authored-By: Claude Sonnet 5.5 Claude-Session: https://claude.ai/code/session_01SZkhi49hfeSqBp5aZJNXsM --- internal/gitea/pulls.go | 2 +- internal/tools/pr_merge_test.go | 16 ++++++++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/internal/gitea/pulls.go b/internal/gitea/pulls.go index ca76163..98af187 100644 --- a/internal/gitea/pulls.go +++ b/internal/gitea/pulls.go @@ -107,7 +107,7 @@ func (c *Client) GetPullRequestDiff(ctx context.Context, owner, repo string, ind type MergePRArgs struct { Do string `json:"Do"` - Title string `json:"merge_message_title,omitempty"` + Title string `json:"merge_title_field,omitempty"` Body string `json:"merge_message_field,omitempty"` // HeadCommitID binds the merge to the commit whose CI was checked: if the PR branch moved, Gitea refuses. HeadCommitID string `json:"head_commit_id,omitempty"` diff --git a/internal/tools/pr_merge_test.go b/internal/tools/pr_merge_test.go index 41ca4ad..1cff506 100644 --- a/internal/tools/pr_merge_test.go +++ b/internal/tools/pr_merge_test.go @@ -166,6 +166,22 @@ func TestPRMergeDefaultsToMergeStyleAndPassesStyleThrough(t *testing.T) { assert.Equal(t, "squash", payload["Do"]) } +// Gitea 1.27's MergePullRequestOption has merge_title_field and merge_message_field. A field it does not know is +// ignored without an error, so a custom merge title silently did nothing while this tool sent merge_message_title +// (gitea-mcp#77). The tool's own argument keeps its name; the wire field must be Gitea's. +func TestPRMerge_ACustomMergeTitleTravelsAsGiteasMergeTitleField(t *testing.T) { + f := newPRMergeFake(t, prMergeFakeOpts{}) + + _, err := callMerge(t, f, `{"owner":"owner","repo":"repo","number":7,"issue":42,"merge_message_title":"feat: one line","merge_message_field":"body text"}`) + + require.NoError(t, err) + var payload map[string]any + require.NoError(t, json.Unmarshal(f.mergeBody, &payload)) + assert.Equal(t, "feat: one line", payload["merge_title_field"]) + assert.Equal(t, "body text", payload["merge_message_field"]) + assert.NotContains(t, payload, "merge_message_title", "not a Gitea field: it is ignored without an error") +} + // TOCTOU: the gate read CI for ONE head commit. The merge must be bound to that commit, so a push to the PR branch // after the check makes Gitea refuse the merge instead of merging code nobody checked. func TestPRMerge_TheMergeIsBoundToTheCommitTheGateChecked(t *testing.T) {