fj pr merge ignores the repo's default_merge_style and forces style=merge #240

Closed
opened 2026-08-08 23:40:18 +00:00 by stephen · 0 comments
Owner

fj pr merge sends style=merge when --style is omitted, instead of the
repo's configured default_merge_style. On a repo that disallows merge commits
it fails outright; on a repo that allows both it silently produces the wrong
kind of history.

Reproduction

rasterstate/fleet does not allow merge commits:

$ fj pr merge --repo rasterstate/fleet 271
error: merge style "merge" is not enabled for rasterstate/fleet; allowed: rebase, rebase-merge, squash.

fj picked merge on its own; nothing on the command line asked for it.

rasterstate/paragon allows both, and its configured default is squash:

$ fj api repos/rasterstate/paragon | grep -E 'allow_merge_commits|allow_squash_merge|default_merge_style'
allow_merge_commits = True
allow_squash_merge  = True
default_merge_style = squash

Merging there without --style still builds a merge commit.

Why it is worth more than a style nit

paragon has require_signed_commits on develop. A merge commit brings the PR
branch's original commits onto the target, so branch protection inspects each of
them; a squash builds one new commit signed by the Forgejo instance key and the
contributor's own signature is never on that path. So the two styles differ in
whether a merge is possible, not just in how history looks:

$ fj pr merge --repo rasterstate/paragon 1167
error: conflict: PushRejected with remote message: Forgejo: branch develop is protected from unverified commit fac2d77790925fff39927a55301ef89c92476cc4

$ fj pr merge --repo rasterstate/paragon 1167 --style squash
✓ Merged PR #1167

Same PR, same commit, seconds apart. The only difference is the flag fj
supplied for me.

That error message points at the contributor's signature, so the natural reading
is "this contributor's key is not trusted". It cost us real work tonight: I read
it that way, concluded four agent accounts needed their signing keys fixed, and
briefed all four to re-sign and force-push. One of them refused and produced the
counter-example. Another had already complied and discarded a green CI run plus
its review verdict, roughly 25 minutes of runner time, for a problem that did not
exist. The defaulted flag was the whole cause.

Expected

Omitting --style uses the repo's default_merge_style. gh pr merge behaves
this way, and it is what the field is for.

Suggested fix

Read default_merge_style from the repo object and use it when --style is
absent. If that field is somehow unavailable, prefer failing with "no --style
given and the repo default could not be read" over silently choosing one, since
the wrong choice is not always visible as an error.

Secondary, worth fixing either way: when the server rejects a push because a
commit is unverified, the message reads as a statement about the contributor. It
would be much less misleading if it named the merge style in play, e.g. "style
'merge' places the branch's own commits on develop, which requires each to be
signed by a verified key; 'squash' does not."

`fj pr merge` sends `style=merge` when `--style` is omitted, instead of the repo's configured `default_merge_style`. On a repo that disallows merge commits it fails outright; on a repo that allows both it silently produces the wrong kind of history. ## Reproduction `rasterstate/fleet` does not allow merge commits: ``` $ fj pr merge --repo rasterstate/fleet 271 error: merge style "merge" is not enabled for rasterstate/fleet; allowed: rebase, rebase-merge, squash. ``` `fj` picked `merge` on its own; nothing on the command line asked for it. `rasterstate/paragon` allows both, and its configured default is squash: ``` $ fj api repos/rasterstate/paragon | grep -E 'allow_merge_commits|allow_squash_merge|default_merge_style' allow_merge_commits = True allow_squash_merge = True default_merge_style = squash ``` Merging there without `--style` still builds a merge commit. ## Why it is worth more than a style nit paragon has `require_signed_commits` on `develop`. A merge commit brings the PR branch's original commits onto the target, so branch protection inspects each of them; a squash builds one new commit signed by the Forgejo instance key and the contributor's own signature is never on that path. So the two styles differ in whether a merge is *possible*, not just in how history looks: ``` $ fj pr merge --repo rasterstate/paragon 1167 error: conflict: PushRejected with remote message: Forgejo: branch develop is protected from unverified commit fac2d77790925fff39927a55301ef89c92476cc4 $ fj pr merge --repo rasterstate/paragon 1167 --style squash ✓ Merged PR #1167 ``` Same PR, same commit, seconds apart. The only difference is the flag `fj` supplied for me. That error message points at the contributor's signature, so the natural reading is "this contributor's key is not trusted". It cost us real work tonight: I read it that way, concluded four agent accounts needed their signing keys fixed, and briefed all four to re-sign and force-push. One of them refused and produced the counter-example. Another had already complied and discarded a green CI run plus its review verdict, roughly 25 minutes of runner time, for a problem that did not exist. The defaulted flag was the whole cause. ## Expected Omitting `--style` uses the repo's `default_merge_style`. `gh pr merge` behaves this way, and it is what the field is for. ## Suggested fix Read `default_merge_style` from the repo object and use it when `--style` is absent. If that field is somehow unavailable, prefer failing with "no --style given and the repo default could not be read" over silently choosing one, since the wrong choice is not always visible as an error. Secondary, worth fixing either way: when the server rejects a push because a commit is unverified, the message reads as a statement about the contributor. It would be much less misleading if it named the merge style in play, e.g. "style 'merge' places the branch's own commits on develop, which requires each to be signed by a verified key; 'squash' does not."
Sign in to join this conversation.
No milestone
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
rasterstate/fj#240
No description provided.