fix(merge gate): bind pr_merge and tbd_ship merges to the head commit the gate checked (TOCTOU)
The gate evaluates CI for one head sha; a push to the PR branch between that check and the merge would have merged unchecked code. MergePRArgs now carries head_commit_id (documented in Gitea 1.27.1's MergePullRequestOption) and both tools send the sha they evaluated. Tests for both tools; each removal flip-tested red. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SZkhi49hfeSqBp5aZJNXsM
This commit is contained in:
@@ -145,6 +145,19 @@ func TestPRMergeDefaultsToMergeStyleAndPassesStyleThrough(t *testing.T) {
|
||||
assert.Equal(t, "squash", payload["Do"])
|
||||
}
|
||||
|
||||
// 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) {
|
||||
f := newPRMergeFake(t, prMergeFakeOpts{})
|
||||
|
||||
_, err := callMerge(t, f, mergeArgs)
|
||||
|
||||
require.NoError(t, err)
|
||||
var payload map[string]any
|
||||
require.NoError(t, json.Unmarshal(f.mergeBody, &payload))
|
||||
assert.Equal(t, "abc", payload["head_commit_id"], "the head sha the CI check used")
|
||||
}
|
||||
|
||||
// A merge conflict is still an error, as before the gate existed.
|
||||
func TestPRMergeConflictReturnsError(t *testing.T) {
|
||||
f := newPRMergeFake(t, prMergeFakeOpts{mergeStatus: http.StatusConflict})
|
||||
|
||||
Reference in New Issue
Block a user