Skip to content

Allow fast-forward-only merge when signed commits are required#37335

Open
krjakbrjak wants to merge 7 commits intogo-gitea:mainfrom
krjakbrjak:VNI-fix-signing-merge
Open

Allow fast-forward-only merge when signed commits are required#37335
krjakbrjak wants to merge 7 commits intogo-gitea:mainfrom
krjakbrjak:VNI-fix-signing-merge

Conversation

@krjakbrjak
Copy link
Copy Markdown
Contributor

@krjakbrjak krjakbrjak commented Apr 21, 2026

Fast-forward-only creates no Gitea commit, so skip the "can Gitea sign" precheck for it. Pre-check head-commit verification for styles that preserve user commits on the target (merge, fast-forward-only) so a PR with unsigned commits surfaces a localized error instead of a 500 at the pre-receive hook. The dropdown still shows every configured style; the avatar and signing warning toggle per selection via data-pull-merge-style.

Fixes #12272

Note: Admin force-merge does not bypass the new head-commits check. This matches the existing isSignedIfRequired behavior.

test-merge.webm

handleFetchActionError captured resp.text() into respText and then called
await resp.text() a second time inside JSON.parse(...). The second call
throws "Failed to execute 'text' on 'Response': body stream already read",
so every JSON 4xx error response surfaced that generic browser error
instead of the translated server message. Reuse the already-captured
text.
@GiteaBot GiteaBot added the lgtm/need 2 This PR needs two approvals by maintainers to be considered for merging. label Apr 21, 2026
@silverwind
Copy link
Copy Markdown
Member

silverwind commented Apr 21, 2026

Some suggestions from a review pass:

  1. Extract a shared helper for the commit-walk — checkHeadCommitsVerifiedIfRequired in services/pull/check.go duplicates the commitsSigned logic in services/asymkey/sign.go:340-359 (same base/head/merge-base/CommitsBeforeUntil/ParseCommitWithSignature pattern). Factor both call sites to one helper to prevent drift.
  2. Split the unrelated fix in web_src/js/features/common-fetch-action.ts (JSON.parse(respText) instead of a second await resp.text()) into its own PR — it's a real bug but unrelated to the feature, and bisectability suffers from bundling.
  3. Reduce template repetition in templates/repo/issue/view_content/pull_merge_box.tmpl{{if \$signingBlocksMerge}}data-pull-merge-style=\"fast-forward-only\"{{end}} is injected on multiple items. Compute it once into a variable (e.g. \$ffOnlyAttr) and reuse.
  4. The avatar cascade now relies on \$avatarBaseClass + \$avatarColorFromSigning and a conditional double-render. It's correct but a future branch added to the cascade is easy to forget — a short comment on the cascade explaining the coupling would help.
  5. Parse data-pull-merge-style once in web_src/js/components/PullRequestMergeForm.vue instead of split(',').map(s => s.trim()) on every style change. The attribute is static; parse at mount or normalize at template emission so plain split(',') suffices.
  6. Add a one-line comment in the template explaining why manually-merged appears in the red-avatar list but not in the green fast-forward-only case — the intent isn't obvious at a glance.
  7. In tests/integration/pull_merge_test.go, the inner t.Run closures shadow the outer req. Distinct names (ffReq, apiReq, …) would read clearer and avoid the shadow.
  8. Confirm the intent that admin force-merge does not bypass ErrHeadCommitsNotAllVerified. It's consistent with today's behavior around isSignedIfRequired, but worth being explicit about in the PR description so reviewers don't have to reason about it.
  9. Consider whether the API should return 400 (like the web router does) rather than 405 for ErrHeadCommitsNotAllVerified — 405 "Method Not Allowed" is a stretch semantically for a precondition failure, though it matches the neighboring ErrWontSign mapping.

Review written with the help of Claude Opus 4.7.

Fast-forward-only creates no Gitea commit, so skip the "can Gitea sign"
precheck for it. Pre-check head-commit verification for styles that
preserve user commits on the target (merge, fast-forward-only) so a PR
with unsigned commits surfaces a localized error instead of a 500 at
the pre-receive hook. The dropdown still shows every configured style;
the avatar and signing warning toggle per selection via
data-pull-merge-style.

Admin force-merge does not bypass the new check, matching the existing
isSignedIfRequired behavior.

Signed-off-by: Nikita Vakula <programmistov.programmist@gmail.com>
@krjakbrjak krjakbrjak force-pushed the VNI-fix-signing-merge branch from 50a4623 to d38d9fa Compare April 21, 2026 18:41
@krjakbrjak
Copy link
Copy Markdown
Contributor Author

Some suggestions from a review pass:

  1. Extract a shared helper for the commit-walk — checkHeadCommitsVerifiedIfRequired in services/pull/check.go duplicates the commitsSigned logic in services/asymkey/sign.go:340-359 (same base/head/merge-base/CommitsBeforeUntil/ParseCommitWithSignature pattern). Factor both call sites to one helper to prevent drift.
  2. Split the unrelated fix in web_src/js/features/common-fetch-action.ts (JSON.parse(respText) instead of a second await resp.text()) into its own PR — it's a real bug but unrelated to the feature, and bisectability suffers from bundling.
  3. Reduce template repetition in templates/repo/issue/view_content/pull_merge_box.tmpl{{if \$signingBlocksMerge}}data-pull-merge-style=\"fast-forward-only\"{{end}} is injected on multiple items. Compute it once into a variable (e.g. \$ffOnlyAttr) and reuse.
  4. The avatar cascade now relies on \$avatarBaseClass + \$avatarColorFromSigning and a conditional double-render. It's correct but a future branch added to the cascade is easy to forget — a short comment on the cascade explaining the coupling would help.
  5. Parse data-pull-merge-style once in web_src/js/components/PullRequestMergeForm.vue instead of split(',').map(s => s.trim()) on every style change. The attribute is static; parse at mount or normalize at template emission so plain split(',') suffices.
  6. Add a one-line comment in the template explaining why manually-merged appears in the red-avatar list but not in the green fast-forward-only case — the intent isn't obvious at a glance.
  7. In tests/integration/pull_merge_test.go, the inner t.Run closures shadow the outer req. Distinct names (ffReq, apiReq, …) would read clearer and avoid the shadow.
  8. Confirm the intent that admin force-merge does not bypass ErrHeadCommitsNotAllVerified. It's consistent with today's behavior around isSignedIfRequired, but worth being explicit about in the PR description so reviewers don't have to reason about it.
  9. Consider whether the API should return 400 (like the web router does) rather than 405 for ErrHeadCommitsNotAllVerified — 405 "Method Not Allowed" is a stretch semantically for a precondition failure, though it matches the neighboring ErrWontSign mapping.

Review written with the help of Claude Opus 4.7.

Thanks for the review @silverwind . Went through the list:

  1. Pulled the commit walk out into AllHeadCommitsVerified.
  2. Moved the ff-only attribute into one variable, $ffOnlyStyleAttr.
  3. Added a note on the cascade: $avatarColorFromSigning should stay
    false for any new branch.
  4. Dropped the .map(s => s.trim()). The template emits the attribute
    without spaces, so plain split(',') does the job.
  5. Added a short comment on why manually-merged is in the red list
    but not the green one.
  6. Renamed the req variables in the test so they don't shadow each
    other: createPRReq, mergeReq, apiReq.
  7. Mentioned in the PR description: admin force-merge does not skip
    the new check, same as isSignedIfRequired.

A couple I'd like to push back on:

  1. I kept the fetch-action fix in this PR. The "body stream already
    read" error was exactly what hid the new signing message in the UI,
    so shipping them together makes sense to me. Happy to split if you
    still want me to — it's one line.

  2. Left the API status as 405. The other merge errors in the same
    handler already return 405, and changing only this one to 400
    would make the API less predictable. A full cleanup feels like a
    separate PR.

@silverwind
Copy link
Copy Markdown
Member

I'll push a few cleanups.

silverwind and others added 4 commits April 21, 2026 23:32
AllHeadCommitsVerified walks commits via CommitsBeforeUntil, which
returns the range (merge-base..head] and therefore already checks
headCommit. The explicit ParseCommitWithSignature(headCommit) before
the helper call is a duplicate.

Co-Authored-By: Claude (Opus 4.7) <noreply@anthropic.com>
checkHeadCommitsVerifiedIfRequired and isSignedIfRequired ran back-to-back
for the merge style, each re-running GetFirstMatchProtectedBranchRule and
opening the git repository. Fold both into checkSigningRequirements so a
single protected-branch lookup and gitRepo open cover both checks.

Co-Authored-By: Claude (Opus 4.7) <noreply@anthropic.com>
Remove comments that restated what variable names and surrounding code
already convey. Kept the two non-obvious invariants (fast-forward-only
rescue rationale, \$avatarColorFromSigning cascade constraint).

Co-Authored-By: Claude (Opus 4.7) <noreply@anthropic.com>
Add rebase-merge to the won't-sign style list so all three Gitea-signed
styles are exercised, and assert that admin force-merge does not bypass
ErrHeadCommitsNotAllVerified (the pre-receive hook would reject the push
regardless).

Co-Authored-By: Claude (Opus 4.7) <noreply@anthropic.com>
@GiteaBot GiteaBot added lgtm/need 1 This PR needs approval from one additional maintainer to be merged. and removed lgtm/need 2 This PR needs two approvals by maintainers to be considered for merging. labels Apr 21, 2026
The attribute now holds a comma-separated list of styles on most
occurrences (for the signing-blocked rescue rows). Plural matches the
semantics. Also inline the split result since it is only used once.

Co-Authored-By: Claude (Opus 4.7) <noreply@anthropic.com>
@silverwind silverwind added the type/enhancement An improvement of existing functionality label Apr 21, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

lgtm/need 1 This PR needs approval from one additional maintainer to be merged. type/enhancement An improvement of existing functionality

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Can't merge pull requests when commit signing is required.

3 participants