Merge requests show live mergeability and gain a signed merge-commit mode.
Mergeability: a background check per repository (merge-tree) reports checking, up to date, behind N or conflict with paths. It is stored, exposed in the API (feature merge_requests.mergeability), published as a Watch event for every settled result on new branch tips, and shown live in the CLI and the web merge box. Failed checks retry per repository with backoff (1 s to 1 min, 5 attempts).
Merge modes: fast-forward (requires up to date), squash (uses the merge-tree tree, so a behind but clean branch can be squashed) and merge commit (two parents, signed by the instance key, see 'ermite signing-key show'). Every mode is allowed by default; existing repos are upgraded once. Restrict with 'ermite repo edit --merge-modes'.
Merge answer: a refused merge re-renders the page with an inline error (409), never an error page. Post-merge side effects (source branch deletion, events) run after the response.
Requires bnema.dev/ermite-api v0.10.0.
Checks: build, vet, hexcheck -fail-on warn, go test -count=1 ./..., race on usecase and app, JS tests.
ermite mr create <repo> --source <branch> --title … | list <repo> | show <repo> <n> [--diff]
57
+
ermite mr create <repo> --source <branch> --title … | list <repo> | show <repo> <n> [--diff] # show prints the mergeability: up to date, behind, conflict
ermite signing-key show # public key of squash commits
81
+
ermite signing-key show # public key of squash and merge commits (server-signed)
82
82
ermite oidc rotate-key
83
83
ermite watch [repo] [--json] # follow live events
84
84
ermite version # client and server versions
@@ -90,7 +90,8 @@ Issues need a server with the `issues` feature and `ermite repo edit <repo> --is
90
90
## Output
91
91
92
92
-`--json` prints stable field names (listed in each `--help`) on `repo list|show|create|edit`, `issue create|list|show|comment|close|reopen`, `token create|list`, `run list|view|cancel|rerun`, `runner list` and `version`.
93
-
-`watch --json` prints one object per line.
93
+
-`watch --json` prints one object per line. `merge_request_mergeability` events carry `mergeability` (`state`, `behind`, `conflict_paths`…) and print as `repo!n mergeability: behind by 2`.
94
+
-`mr merge --mode squash|merge` creates a commit signed by the server's own key (generated on first start); the output names its fingerprint, `ermite signing-key show` prints the key. Every mode is allowed by default; `repo edit --merge-modes fast-forward,squash` disables `merge`.
94
95
- Errors are one line on stderr (`ermite: …`) with exit status 1.
95
96
- Shell completion completes repository names from the server.
ermite mr create <repo> --source <branch> --title … | list <repo> | show <repo> <n> [--diff]
57
+
ermite mr create <repo> --source <branch> --title … | list <repo> | show <repo> <n> [--diff] # show prints the mergeability: up to date, behind, conflict
ermite signing-key show # public key of squash commits
81
+
ermite signing-key show # public key of squash and merge commits (server-signed)
82
ermite oidc rotate-key
82
ermite oidc rotate-key
83
ermite watch [repo] [--json] # follow live events
83
ermite watch [repo] [--json] # follow live events
84
ermite version # client and server versions
84
ermite version # client and server versions
@@ -90,7 +90,8 @@ Issues need a server with the `issues` feature and `ermite repo edit <repo> --is
90
## Output
90
## Output
91
91
92
-`--json` prints stable field names (listed in each `--help`) on `repo list|show|create|edit`, `issue create|list|show|comment|close|reopen`, `token create|list`, `run list|view|cancel|rerun`, `runner list` and `version`.
92
-`--json` prints stable field names (listed in each `--help`) on `repo list|show|create|edit`, `issue create|list|show|comment|close|reopen`, `token create|list`, `run list|view|cancel|rerun`, `runner list` and `version`.
93
-
-`watch --json` prints one object per line.
93
+
-`watch --json` prints one object per line. `merge_request_mergeability` events carry `mergeability` (`state`, `behind`, `conflict_paths`…) and print as `repo!n mergeability: behind by 2`.
94
+
-`mr merge --mode squash|merge` creates a commit signed by the server's own key (generated on first start); the output names its fingerprint, `ermite signing-key show` prints the key. Every mode is allowed by default; `repo edit --merge-modes fast-forward,squash` disables `merge`.
94
- Errors are one line on stderr (`ermite: …`) with exit status 1.
95
- Errors are one line on stderr (`ermite: …`) with exit status 1.
95
- Shell completion completes repository names from the server.
96
- Shell completion completes repository names from the server.
- A key may sign for 1 to 10 emails; up to 100 keys.
38
38
- The id is the fingerprint: `SHA256:…` for SSH, the primary key fingerprint for OpenPGP.
39
-
- The server's own key (`ermite signing-key show`), which signs squash merges, is always trusted for the committer email and cannot be removed.
39
+
- The server's own key (`ermite signing-key show`), which signs squash and merge commits (generated automatically on first start), is always trusted for the committer email and cannot be removed.
- A key may sign for 1 to 10 emails; up to 100 keys.
37
- A key may sign for 1 to 10 emails; up to 100 keys.
38
- The id is the fingerprint: `SHA256:…` for SSH, the primary key fingerprint for OpenPGP.
38
- The id is the fingerprint: `SHA256:…` for SSH, the primary key fingerprint for OpenPGP.
39
-
- The server's own key (`ermite signing-key show`), which signs squash merges, is always trusted for the committer email and cannot be removed.
39
+
- The server's own key (`ermite signing-key show`), which signs squash and merge commits (generated automatically on first start), is always trusted for the committer email and cannot be removed.
40
- Private keys are refused.
40
- Private keys are refused.
Mdocs/configuration.md+2-2
@@ -34,8 +34,8 @@ ermite serve
34
34
|---|---|---|
35
35
| `ERMITE_GIT` | `git` | git 2.45 or newer |
36
36
| `ERMITE_GIT_READER` | `ermite-git` | `ermite-git` reads with the [ermite-git](https://github.com/bnema/ermite-git) library where it is faster, and falls back to `git`. `cli` uses `git` for every read: set it and restart if a read looks wrong |
37
-
| `ERMITE_COMMITTER_NAME` | `ermite` | committer of squash merges |
| `ERMITE_COMMITTER_NAME` | `ermite` | committer of squash and merge commits |
38
+
| `ERMITE_COMMITTER_EMAIL` | `ermite@<public host>` | committer email of squash and merge commits |
39
39
40
40
## CI
41
41
@@ -34,8 +34,8 @@ ermite serve
34
|---|---|---|
34
|---|---|---|
35
| `ERMITE_GIT` | `git` | git 2.45 or newer |
35
| `ERMITE_GIT` | `git` | git 2.45 or newer |
36
| `ERMITE_GIT_READER` | `ermite-git` | `ermite-git` reads with the [ermite-git](https://github.com/bnema/ermite-git) library where it is faster, and falls back to `git`. `cli` uses `git` for every read: set it and restart if a read looks wrong |
36
| `ERMITE_GIT_READER` | `ermite-git` | `ermite-git` reads with the [ermite-git](https://github.com/bnema/ermite-git) library where it is faster, and falls back to `git`. `cli` uses `git` for every read: set it and restart if a read looks wrong |
37
-
| `ERMITE_COMMITTER_NAME` | `ermite` | committer of squash merges |
37
+
| `ERMITE_COMMITTER_NAME` | `ermite` | committer of squash and merge commits |
`ermite mr create` and `ermite mr close` print a confirmation followed by the full
12
12
merge request web URL on its own line. With `--json`, merge request objects
13
13
(create, show, list, close and merge) include `url` alongside the other fields.
14
14
15
-
- **Fast-forward** moves the target. **Squash** writes one commit signed by the server's SSH key.
16
-
- The merge is refused when the branch moved since the reviewed `--head`, or when the target is not an ancestor of it.
15
+
## Merge modes
16
+
17
+
| Mode | Result | Needs |
18
+
|---|---|---|
19
+
| `fast-forward` (default) | the target moves to the head, no commit | the target is an ancestor of the head |
20
+
| `squash` | one new commit with the merged tree, the target tip as only parent | the branch merges without conflicts |
21
+
| `merge` | a merge commit with two parents, the target tip and the head | the branch merges without conflicts |
22
+
23
+
- The server never rebases. A branch behind its target merges with `squash` or `merge`, or is rebased and pushed again for a fast-forward.
24
+
- The server checks the mode again when it merges, whatever the badge showed, and refuses with the reason (the target moved, a conflict with the paths).
25
+
- The merge is also refused when the branch moved since the reviewed `--head`.
17
26
- With `failing_checks_block` on (default), the merge is refused while a check of the head failed or is still running.
18
-
-`--delete-branch` deletes the source branch, unless it is the default branch or another open request uses it.
19
-
-`ermite repo edit --merge-modes` limits the modes per repository.
27
+
- The merge answers as soon as the target has moved. `--delete-branch` then deletes the source branch right after, in the background (unless it is the default branch or another open request uses it); if the branch moved meanwhile, it is kept.
28
+
- Every mode is allowed by default, including in repositories that had only `fast-forward` and `squash` before `merge` existed: those gain `merge`. Disable a mode per repository with `ermite repo edit <repo> --merge-modes fast-forward,squash` (list the modes to keep).
20
29
- Scopes: `mr:write` to open and comment, `mr:review` to give a verdict, `mr:merge` to merge.
21
30
22
-
Verify squash commits with the `allowed_signers` line from `ermite signing-key show`:
31
+
## Signed commits
32
+
33
+
Squash and merge commits are signed by ermite's own key. The key is generated automatically the first time the server starts, belongs to the server, and its committer identity is `ERMITE_COMMITTER_NAME` / `ERMITE_COMMITTER_EMAIL`. `ermite signing-key show` prints its fingerprint and the `allowed_signers` line; use it to verify locally, or register the public key on GitHub or Forgejo.
34
+
35
+
```sh
36
+
ermite signing-key show --allowed-signers > allowed_signers
The merge box of the web page and the output of `mr merge --mode squash|merge` repeat this with the fingerprint.
41
+
42
+
## Mergeability
43
+
44
+
The server checks in the background whether each open request merges with its target, and again whenever the source or the target moves. `mr show` prints it; the web merge box shows it as a live badge; `ermite watch` reports each change.
45
+
46
+
| State | Meaning | Modes possible |
47
+
|---|---|---|
48
+
| `checking` | no result yet for the current branches | none (wait) |
49
+
| `up_to_date` | the target is an ancestor of the head | all |
50
+
| `behind` | the head lacks `behind` commits of the target (`behind_capped`: at least 1000) but merges without conflicts | `squash`, `merge` |
51
+
| `conflict` | the merge conflicts; up to 20 `conflict_paths` are listed (`conflict_paths_truncated` when there are more); no paths means no common history | none (rebase) |
23
52
24
53
```sh
25
-
git verify-commit <commit>
54
+
ermite mr show myrepo 1
55
+
# mergeability behind by 2, merges without conflicts (squash or merge commit; fast-forward needs a rebase)
26
56
```
27
57
58
+
In `mr show --json`, `mergeability` holds `state`, `behind`, `behind_capped`, `conflict_paths`, `conflict_paths_truncated`, `target_id` and `head_id`; it is `null` unless the request is open. `ermite watch` prints `merge_request_mergeability` events when the state changes (not on every push):
59
+
60
+
```text
61
+
2026-10-19T14:00:00Z merge_request_mergeability myrepo!1 mergeability: behind by 2
62
+
```
63
+
64
+
In the web merge box a mode that is not possible is disabled with its reason, and the Merge button too when no mode is possible. A merge the server refuses (target moved, conflict, branch moved) shows the page again with the reason in the box.
65
+
28
66
## Notes
29
67
30
68
A note is the only review object. It is published as soon as it is written: there is no draft and no submit step.
@@ -5,26 +5,64 @@ A merge request proposes a branch for a target (the default branch unless set).
`ermite mr create` and `ermite mr close` print a confirmation followed by the full
11
`ermite mr create` and `ermite mr close` print a confirmation followed by the full
12
merge request web URL on its own line. With `--json`, merge request objects
12
merge request web URL on its own line. With `--json`, merge request objects
13
(create, show, list, close and merge) include `url` alongside the other fields.
13
(create, show, list, close and merge) include `url` alongside the other fields.
14
14
15
-
- **Fast-forward** moves the target. **Squash** writes one commit signed by the server's SSH key.
15
+
## Merge modes
16
-
- The merge is refused when the branch moved since the reviewed `--head`, or when the target is not an ancestor of it.
16
+
17
+
| Mode | Result | Needs |
18
+
|---|---|---|
19
+
| `fast-forward` (default) | the target moves to the head, no commit | the target is an ancestor of the head |
20
+
| `squash` | one new commit with the merged tree, the target tip as only parent | the branch merges without conflicts |
21
+
| `merge` | a merge commit with two parents, the target tip and the head | the branch merges without conflicts |
22
+
23
+
- The server never rebases. A branch behind its target merges with `squash` or `merge`, or is rebased and pushed again for a fast-forward.
24
+
- The server checks the mode again when it merges, whatever the badge showed, and refuses with the reason (the target moved, a conflict with the paths).
25
+
- The merge is also refused when the branch moved since the reviewed `--head`.
17
- With `failing_checks_block` on (default), the merge is refused while a check of the head failed or is still running.
26
- With `failing_checks_block` on (default), the merge is refused while a check of the head failed or is still running.
18
-
-`--delete-branch` deletes the source branch, unless it is the default branch or another open request uses it.
27
+
- The merge answers as soon as the target has moved. `--delete-branch` then deletes the source branch right after, in the background (unless it is the default branch or another open request uses it); if the branch moved meanwhile, it is kept.
19
-
-`ermite repo edit --merge-modes` limits the modes per repository.
28
+
- Every mode is allowed by default, including in repositories that had only `fast-forward` and `squash` before `merge` existed: those gain `merge`. Disable a mode per repository with `ermite repo edit <repo> --merge-modes fast-forward,squash` (list the modes to keep).
20
- Scopes: `mr:write` to open and comment, `mr:review` to give a verdict, `mr:merge` to merge.
29
- Scopes: `mr:write` to open and comment, `mr:review` to give a verdict, `mr:merge` to merge.
21
30
22
-
Verify squash commits with the `allowed_signers` line from `ermite signing-key show`:
31
+
## Signed commits
32
+
33
+
Squash and merge commits are signed by ermite's own key. The key is generated automatically the first time the server starts, belongs to the server, and its committer identity is `ERMITE_COMMITTER_NAME` / `ERMITE_COMMITTER_EMAIL`. `ermite signing-key show` prints its fingerprint and the `allowed_signers` line; use it to verify locally, or register the public key on GitHub or Forgejo.
34
+
35
+
```sh
36
+
ermite signing-key show --allowed-signers > allowed_signers
The merge box of the web page and the output of `mr merge --mode squash|merge` repeat this with the fingerprint.
41
+
42
+
## Mergeability
43
+
44
+
The server checks in the background whether each open request merges with its target, and again whenever the source or the target moves. `mr show` prints it; the web merge box shows it as a live badge; `ermite watch` reports each change.
45
+
46
+
| State | Meaning | Modes possible |
47
+
|---|---|---|
48
+
| `checking` | no result yet for the current branches | none (wait) |
49
+
| `up_to_date` | the target is an ancestor of the head | all |
50
+
| `behind` | the head lacks `behind` commits of the target (`behind_capped`: at least 1000) but merges without conflicts | `squash`, `merge` |
51
+
| `conflict` | the merge conflicts; up to 20 `conflict_paths` are listed (`conflict_paths_truncated` when there are more); no paths means no common history | none (rebase) |
23
52
24
```sh
53
```sh
25
-
git verify-commit <commit>
54
+
ermite mr show myrepo 1
55
+
# mergeability behind by 2, merges without conflicts (squash or merge commit; fast-forward needs a rebase)
26
```
56
```
27
57
58
+
In `mr show --json`, `mergeability` holds `state`, `behind`, `behind_capped`, `conflict_paths`, `conflict_paths_truncated`, `target_id` and `head_id`; it is `null` unless the request is open. `ermite watch` prints `merge_request_mergeability` events when the state changes (not on every push):
59
+
60
+
```text
61
+
2026-10-19T14:00:00Z merge_request_mergeability myrepo!1 mergeability: behind by 2
62
+
```
63
+
64
+
In the web merge box a mode that is not possible is disabled with its reason, and the Merge button too when no mode is possible. A merge the server refuses (target moved, conflict, branch moved) shows the page again with the reason in the box.
65
+
28
## Notes
66
## Notes
29
67
30
A note is the only review object. It is published as soon as it is written: there is no draft and no submit step.
68
A note is the only review object. It is published as soon as it is written: there is no draft and no submit step.