Skip to content

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

Merged
silverwind merged 16 commits intogo-gitea:mainfrom
krjakbrjak:VNI-fix-signing-merge
Apr 24, 2026
Merged

Allow fast-forward-only merge when signed commits are required#37335
silverwind merged 16 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
@lunny lunny added this to the 1.27.0 milestone Apr 22, 2026
@wxiaoguang
Copy link
Copy Markdown
Contributor

wxiaoguang commented Apr 23, 2026

The template is a mess and unmaintainable.

Need to refactor and clean up the related code first

-> Refactor pull request view #37380

@wxiaoguang wxiaoguang marked this pull request as draft April 23, 2026 07:13
@krjakbrjak
Copy link
Copy Markdown
Contributor Author

The template is a mess and unmaintainable.

Need to refactor and clean up the related code first

-> Refactor pull request view #37380

Sounds good. Maybe it would make sense to drop the template changes from this PR and keep only the backend parts (the shared AllHeadCommitsVerified helper, the signing precheck consolidation, ErrHeadCommitsNotAllVerified with its API mapping, and the integration tests)? That way this one could move forward independently of your refactor, and I'd revisit the merge-box UI bits once the refactor lands. What do you think @wxiaoguang ?

@wxiaoguang
Copy link
Copy Markdown
Contributor

Maybe it would make sense to drop the template changes from this PR and keep only the backend parts (the shared AllHeadCommitsVerified helper, the signing precheck consolidation, ErrHeadCommitsNotAllVerified with its API mapping, and the integration tests)

It sounds good, thank you!

silverwind and others added 2 commits April 23, 2026 10:11
Signed-off-by: Nikita Vakula <programmistov.programmist@gmail.com>
@krjakbrjak
Copy link
Copy Markdown
Contributor Author

krjakbrjak commented Apr 23, 2026

Maybe it would make sense to drop the template changes from this PR and keep only the backend parts (the shared AllHeadCommitsVerified helper, the signing precheck consolidation, ErrHeadCommitsNotAllVerified with its API mapping, and the integration tests)

It sounds good, thank you!

Reverted the templates. only the backend part is kept. Should anything else be done here in your opinion?

Comment thread tests/integration/pull_merge_test.go Outdated
Comment thread tests/integration/pull_merge_test.go Outdated
Signed-off-by: Nikita Vakula <programmistov.programmist@gmail.com>
Signed-off-by: Nikita Vakula <programmistov.programmist@gmail.com>
Comment thread tests/integration/pull_merge_test.go Outdated
Signed-off-by: Nikita Vakula <programmistov.programmist@gmail.com>
@krjakbrjak krjakbrjak requested a review from wxiaoguang April 23, 2026 12:47
@GiteaBot GiteaBot added lgtm/done This PR has enough approvals to get merged. There are no important open reservations anymore. and removed lgtm/need 1 This PR needs approval from one additional maintainer to be merged. labels Apr 23, 2026
@wxiaoguang wxiaoguang marked this pull request as ready for review April 23, 2026 12:48
Comment thread tests/integration/pull_merge_test.go Outdated
Signed-off-by: wxiaoguang <wxiaoguang@gmail.com>
@lunny lunny added the reviewed/wait-merge This pull request is part of the merge queue. It will be merged soon. label Apr 23, 2026
@silverwind silverwind enabled auto-merge (squash) April 23, 2026 22:50
@silverwind silverwind merged commit 3b2fd97 into go-gitea:main Apr 24, 2026
26 checks passed
silverwind added a commit to silverwind/gitea that referenced this pull request Apr 24, 2026
* origin/main:
  Allow fast-forward-only merge when signed commits are required (go-gitea#37335)
  Introduce `ActionRunAttempt` to represent each execution of a run (go-gitea#37119)
  Move review request functions to a standalone file (go-gitea#37358)
  Fix repo init README EOL (go-gitea#37388)
  Fix org team assignee/reviewer lookups for team member permissions (go-gitea#37365)
  Remove external service dependencies in migration tests (go-gitea#36866)
  Extend issue context popup beyond markdown content (go-gitea#36908)

# Conflicts:
#	routers/api/v1/repo/action.go
#	web_src/js/components/RepoActionView.vue
@GiteaBot GiteaBot removed the reviewed/wait-merge This pull request is part of the merge queue. It will be merged soon. label Apr 24, 2026
silverwind added a commit to mohammad-rj/gitea that referenced this pull request Apr 24, 2026
* origin/main: (127 commits)
  Refactor pull request view (1) (go-gitea#37380)
  Improve AGENTS.md (go-gitea#37382)
  Remove dead CSS (go-gitea#37376)
  Add pr-review e2e test and speed up e2e tests (go-gitea#37345)
  Drop Fomantic tab, checkbox and form patches (go-gitea#37377)
  fix: dump with default zip type produces uncompressed zip (go-gitea#37401)
  Allow fast-forward-only merge when signed commits are required (go-gitea#37335)
  Introduce `ActionRunAttempt` to represent each execution of a run (go-gitea#37119)
  Move review request functions to a standalone file (go-gitea#37358)
  Fix repo init README EOL (go-gitea#37388)
  Fix org team assignee/reviewer lookups for team member permissions (go-gitea#37365)
  Remove external service dependencies in migration tests (go-gitea#36866)
  Extend issue context popup beyond markdown content (go-gitea#36908)
  fix: commit status reporting (go-gitea#37372)
  Support for Custom URI Schemes in OAuth2 Redirect URIs (go-gitea#37356)
  Fix cmd tests by mocking builtin paths (go-gitea#37369)
  chore: upgrade Go version in devcontainer image to 1.26 (go-gitea#37374)
  Fix button layout shift when collapsing file tree in editor (go-gitea#37363)
  Update `Block a user` form (go-gitea#37359)
  Remove IsValidExternalURL/IsAPIURL and use IsValidURL at call sites (go-gitea#37364)
  ...

# Conflicts:
#	modules/eventsource/event.go
#	tests/e2e/events.test.ts
silverwind added a commit to silverwind/gitea that referenced this pull request Apr 25, 2026
* origin/main: (51 commits)
  Fix color regressions, add `priority` color (go-gitea#37417)
  [skip ci] Updated translations via Crowdin
  Stabilize e2e logout propagation test (go-gitea#37403)
  refactor: serve site manifest via `/assets/site-manifest.json` endpoint (go-gitea#37405)
  feat(security): set X-Content-Type-Options: nosniff by default (go-gitea#37354)
  Refactor pull request view (1) (go-gitea#37380)
  Improve AGENTS.md (go-gitea#37382)
  Remove dead CSS (go-gitea#37376)
  Add pr-review e2e test and speed up e2e tests (go-gitea#37345)
  Drop Fomantic tab, checkbox and form patches (go-gitea#37377)
  fix: dump with default zip type produces uncompressed zip (go-gitea#37401)
  Allow fast-forward-only merge when signed commits are required (go-gitea#37335)
  Introduce `ActionRunAttempt` to represent each execution of a run (go-gitea#37119)
  Move review request functions to a standalone file (go-gitea#37358)
  Fix repo init README EOL (go-gitea#37388)
  Fix org team assignee/reviewer lookups for team member permissions (go-gitea#37365)
  Remove external service dependencies in migration tests (go-gitea#36866)
  Extend issue context popup beyond markdown content (go-gitea#36908)
  fix: commit status reporting (go-gitea#37372)
  Support for Custom URI Schemes in OAuth2 Redirect URIs (go-gitea#37356)
  ...
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

lgtm/done This PR has enough approvals to get merged. There are no important open reservations anymore. 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.

5 participants