detached 49667f3

Merge 6d3fb04738056fb79e0d5474ac3bdf2f90fbb138 into 2aff1f72c9fe3e30cef3a2f5509e92ca1143118f

commit
49667f3b8eab9d533ede35cb3238aa06c12b9f58
author
ermite · · 2026-10-19 14:54 UTC
merge of
2aff1f7 + 6d3fb04 · diff against the first parent
signature
unsigned
14 files · +735 -310
M docs/services.md +2 -2
@@ -5,10 +5,10 @@
55 | `RepoService` | `ListRepos`, `GetRepo`, `CreateRepo`, `UpdateRepo` (field mask, merge settings), `DeleteRepo` |
66 | `GitService` | `ListRefs`, `GetTree`, `ListPaths`, `GetBlob` (streamed), `ListCommits`, `GetCommit` (structured diff), `ResolveRevision` |
77 | `TokenService` | `CreateToken` (secret shown once), `ListTokens`, `RevokeToken` |
8-| `MergeRequestService` | `CreateMergeRequest`, `ListMergeRequests`, `GetMergeRequest` (commits, diff, comments), `AddComment`, `EditComment`, `DeleteComment`, `ResolveComment`, `CloseMergeRequest`, `MergeMergeRequest`, `MarkRead` |
8+| `MergeRequestService` | `CreateMergeRequest`, `ListMergeRequests`, `GetMergeRequest` (commits, diff, comments), `AddComment`, `EditComment`, `DeleteComment`, `ResolveComment`, `CloseMergeRequest`, `MergeMergeRequest` (modes fast-forward, squash, merge commit), `MarkRead`. Open merge requests carry `mergeability` (up to date, behind, conflict, or checking) and `Watch` publishes `MergeRequestMergeability` when it changes (feature `merge_requests.mergeability`) |
99 | `IssueService` | `CreateIssue`, `ListIssues`, `GetIssue` (comments), `AddIssueComment`, `CloseIssue`, `ReopenIssue`; owner only: `ListPendingIssues`, `ApproveIssue`, `RejectIssue`. Feature `issues`. Issues share numbers with merge requests |
1010 | `SessionService` | `CreateLoginLink`, `SignOutEverywhere` (owner only) |
11-| `EventService` | `Watch` (streamed, resumable). Topics `home`, `repo:<name>` and `owner` (owner rights only). Events 17–21: `issue_opened`, `issue_closed`, `issue_reopened`, `issue_commented`, and `issue_submitted` (only on `owner`, no content) |
11+| `EventService` | `Watch` (streamed, resumable). Topics `home`, `repo:<name>` and `owner` (owner rights only). Events 17–21: `issue_opened`, `issue_closed`, `issue_reopened`, `issue_commented`, and `issue_submitted` (only on `owner`, no content). Event 22: `merge_request_mergeability` |
1212 | `ServerService` | `GetVersion` (version and features), `GetSigningKey`, `RotateOIDCKey` (owner only) |
1313 | `TrustedKeyService` | `ListTrustedKeys`, `AddTrustedKey` and `RemoveTrustedKey` (owner only). `Commit.verification` carries the result of the check (feature `commit_signatures`) |
1414 | `RunService` | `ListRuns`, `GetRun`, `GetJob`, `GetJobLog` (streamed, follow), `ListAnnotations`, `GetStepSummaries`, `CancelRun`, `RerunRun`, `DispatchWorkflow`, `ListWorkflows`, `DisableWorkflow`, `EnableWorkflow`, `ListChecks`, `ListRunners`, `RemoveRunner` |
@@ -5,10 +5,10 @@
5 | `RepoService` | `ListRepos`, `GetRepo`, `CreateRepo`, `UpdateRepo` (field mask, merge settings), `DeleteRepo` |5 | `RepoService` | `ListRepos`, `GetRepo`, `CreateRepo`, `UpdateRepo` (field mask, merge settings), `DeleteRepo` |
6 | `GitService` | `ListRefs`, `GetTree`, `ListPaths`, `GetBlob` (streamed), `ListCommits`, `GetCommit` (structured diff), `ResolveRevision` |6 | `GitService` | `ListRefs`, `GetTree`, `ListPaths`, `GetBlob` (streamed), `ListCommits`, `GetCommit` (structured diff), `ResolveRevision` |
7 | `TokenService` | `CreateToken` (secret shown once), `ListTokens`, `RevokeToken` |7 | `TokenService` | `CreateToken` (secret shown once), `ListTokens`, `RevokeToken` |
8-| `MergeRequestService` | `CreateMergeRequest`, `ListMergeRequests`, `GetMergeRequest` (commits, diff, comments), `AddComment`, `EditComment`, `DeleteComment`, `ResolveComment`, `CloseMergeRequest`, `MergeMergeRequest`, `MarkRead` |8+| `MergeRequestService` | `CreateMergeRequest`, `ListMergeRequests`, `GetMergeRequest` (commits, diff, comments), `AddComment`, `EditComment`, `DeleteComment`, `ResolveComment`, `CloseMergeRequest`, `MergeMergeRequest` (modes fast-forward, squash, merge commit), `MarkRead`. Open merge requests carry `mergeability` (up to date, behind, conflict, or checking) and `Watch` publishes `MergeRequestMergeability` when it changes (feature `merge_requests.mergeability`) |
9 | `IssueService` | `CreateIssue`, `ListIssues`, `GetIssue` (comments), `AddIssueComment`, `CloseIssue`, `ReopenIssue`; owner only: `ListPendingIssues`, `ApproveIssue`, `RejectIssue`. Feature `issues`. Issues share numbers with merge requests |9 | `IssueService` | `CreateIssue`, `ListIssues`, `GetIssue` (comments), `AddIssueComment`, `CloseIssue`, `ReopenIssue`; owner only: `ListPendingIssues`, `ApproveIssue`, `RejectIssue`. Feature `issues`. Issues share numbers with merge requests |
10 | `SessionService` | `CreateLoginLink`, `SignOutEverywhere` (owner only) |10 | `SessionService` | `CreateLoginLink`, `SignOutEverywhere` (owner only) |
11-| `EventService` | `Watch` (streamed, resumable). Topics `home`, `repo:<name>` and `owner` (owner rights only). Events 17–21: `issue_opened`, `issue_closed`, `issue_reopened`, `issue_commented`, and `issue_submitted` (only on `owner`, no content) |11+| `EventService` | `Watch` (streamed, resumable). Topics `home`, `repo:<name>` and `owner` (owner rights only). Events 17–21: `issue_opened`, `issue_closed`, `issue_reopened`, `issue_commented`, and `issue_submitted` (only on `owner`, no content). Event 22: `merge_request_mergeability` |
12 | `ServerService` | `GetVersion` (version and features), `GetSigningKey`, `RotateOIDCKey` (owner only) |12 | `ServerService` | `GetVersion` (version and features), `GetSigningKey`, `RotateOIDCKey` (owner only) |
13 | `TrustedKeyService` | `ListTrustedKeys`, `AddTrustedKey` and `RemoveTrustedKey` (owner only). `Commit.verification` carries the result of the check (feature `commit_signatures`) |13 | `TrustedKeyService` | `ListTrustedKeys`, `AddTrustedKey` and `RemoveTrustedKey` (owner only). `Commit.verification` carries the result of the check (feature `commit_signatures`) |
14 | `RunService` | `ListRuns`, `GetRun`, `GetJob`, `GetJobLog` (streamed, follow), `ListAnnotations`, `GetStepSummaries`, `CancelRun`, `RerunRun`, `DispatchWorkflow`, `ListWorkflows`, `DisableWorkflow`, `EnableWorkflow`, `ListChecks`, `ListRunners`, `RemoveRunner` |14 | `RunService` | `ListRuns`, `GetRun`, `GetJob`, `GetJobLog` (streamed, follow), `ListAnnotations`, `GetStepSummaries`, `CancelRun`, `RerunRun`, `DispatchWorkflow`, `ListWorkflows`, `DisableWorkflow`, `EnableWorkflow`, `ListChecks`, `ListRunners`, `RemoveRunner` |
M gen/ermite/ermiteconnect/merge_request.connect.go +12 -6
@@ -127,9 +127,12 @@ type MergeRequestServiceClient interface {
127127 // never merged unseen. Fails with invalid_argument if head_id is empty or
128128 // not a commit id, and with failed_precondition if the merge request is not
129129 // open, if the source branch no longer points to head_id ("branch moved,
130- // read again"), if the target is not an ancestor of the head (rebase and
131- // push again), if the mode is not allowed by RepoSettings, or if a check
132- // fails and RepoSettings.failing_checks_block is set. Merges of one
130+ // read again"), if the mode is fast-forward and the target is not an
131+ // ancestor of the head (rebase and push again, or merge with squash or
132+ // merge commit), if the mode is squash or merge commit and the head
133+ // conflicts with the target or shares no history with it (rebase and push
134+ // again), if the mode is not allowed by RepoSettings, or if a check fails
135+ // and RepoSettings.failing_checks_block is set. Merges of one
133136 // repository run one at a time. Repeating a call that succeeded fails with
134137 // failed_precondition (not open). On success the server publishes
135138 // MergeRequestMerged then Pushed for the target branch.
@@ -339,9 +342,12 @@ type MergeRequestServiceHandler interface {
339342 // never merged unseen. Fails with invalid_argument if head_id is empty or
340343 // not a commit id, and with failed_precondition if the merge request is not
341344 // open, if the source branch no longer points to head_id ("branch moved,
342- // read again"), if the target is not an ancestor of the head (rebase and
343- // push again), if the mode is not allowed by RepoSettings, or if a check
344- // fails and RepoSettings.failing_checks_block is set. Merges of one
345+ // read again"), if the mode is fast-forward and the target is not an
346+ // ancestor of the head (rebase and push again, or merge with squash or
347+ // merge commit), if the mode is squash or merge commit and the head
348+ // conflicts with the target or shares no history with it (rebase and push
349+ // again), if the mode is not allowed by RepoSettings, or if a check fails
350+ // and RepoSettings.failing_checks_block is set. Merges of one
345351 // repository run one at a time. Repeating a call that succeeded fails with
346352 // failed_precondition (not open). On success the server publishes
347353 // MergeRequestMerged then Pushed for the target branch.
@@ -127,9 +127,12 @@ type MergeRequestServiceClient interface {
127 // never merged unseen. Fails with invalid_argument if head_id is empty or127 // never merged unseen. Fails with invalid_argument if head_id is empty or
128 // not a commit id, and with failed_precondition if the merge request is not128 // not a commit id, and with failed_precondition if the merge request is not
129 // open, if the source branch no longer points to head_id ("branch moved,129 // open, if the source branch no longer points to head_id ("branch moved,
130- // read again"), if the target is not an ancestor of the head (rebase and130+ // read again"), if the mode is fast-forward and the target is not an
131- // push again), if the mode is not allowed by RepoSettings, or if a check131+ // ancestor of the head (rebase and push again, or merge with squash or
132- // fails and RepoSettings.failing_checks_block is set. Merges of one132+ // merge commit), if the mode is squash or merge commit and the head
133+ // conflicts with the target or shares no history with it (rebase and push
134+ // again), if the mode is not allowed by RepoSettings, or if a check fails
135+ // and RepoSettings.failing_checks_block is set. Merges of one
133 // repository run one at a time. Repeating a call that succeeded fails with136 // repository run one at a time. Repeating a call that succeeded fails with
134 // failed_precondition (not open). On success the server publishes137 // failed_precondition (not open). On success the server publishes
135 // MergeRequestMerged then Pushed for the target branch.138 // MergeRequestMerged then Pushed for the target branch.
@@ -339,9 +342,12 @@ type MergeRequestServiceHandler interface {
339 // never merged unseen. Fails with invalid_argument if head_id is empty or342 // never merged unseen. Fails with invalid_argument if head_id is empty or
340 // not a commit id, and with failed_precondition if the merge request is not343 // not a commit id, and with failed_precondition if the merge request is not
341 // open, if the source branch no longer points to head_id ("branch moved,344 // open, if the source branch no longer points to head_id ("branch moved,
342- // read again"), if the target is not an ancestor of the head (rebase and345+ // read again"), if the mode is fast-forward and the target is not an
343- // push again), if the mode is not allowed by RepoSettings, or if a check346+ // ancestor of the head (rebase and push again, or merge with squash or
344- // fails and RepoSettings.failing_checks_block is set. Merges of one347+ // merge commit), if the mode is squash or merge commit and the head
348+ // conflicts with the target or shares no history with it (rebase and push
349+ // again), if the mode is not allowed by RepoSettings, or if a check fails
350+ // and RepoSettings.failing_checks_block is set. Merges of one
345 // repository run one at a time. Repeating a call that succeeded fails with351 // repository run one at a time. Repeating a call that succeeded fails with
346 // failed_precondition (not open). On success the server publishes352 // failed_precondition (not open). On success the server publishes
347 // MergeRequestMerged then Pushed for the target branch.353 // MergeRequestMerged then Pushed for the target branch.
M gen/ermite/ermiteconnect/server.connect.go +2 -2
@@ -53,7 +53,7 @@ type ServerServiceClient interface {
5353 // it first to check compatibility.
5454 GetVersion(context.Context, *connect.Request[ermite.GetVersionRequest]) (*connect.Response[ermite.GetVersionResponse], error)
5555 // GetSigningKey returns the public key with which the server signs the
56- // commits it creates (squash merges, see MergeMode). It needs no
56+ // commits it creates (squash and merge commits, see MergeMode). It needs no
5757 // authentication: the key is public, and anyone verifying a commit needs it.
5858 // Servers that implement it list "signing_key" in
5959 // GetVersionResponse.features.
@@ -132,7 +132,7 @@ type ServerServiceHandler interface {
132132 // it first to check compatibility.
133133 GetVersion(context.Context, *connect.Request[ermite.GetVersionRequest]) (*connect.Response[ermite.GetVersionResponse], error)
134134 // GetSigningKey returns the public key with which the server signs the
135- // commits it creates (squash merges, see MergeMode). It needs no
135+ // commits it creates (squash and merge commits, see MergeMode). It needs no
136136 // authentication: the key is public, and anyone verifying a commit needs it.
137137 // Servers that implement it list "signing_key" in
138138 // GetVersionResponse.features.
@@ -53,7 +53,7 @@ type ServerServiceClient interface {
53 // it first to check compatibility.53 // it first to check compatibility.
54 GetVersion(context.Context, *connect.Request[ermite.GetVersionRequest]) (*connect.Response[ermite.GetVersionResponse], error)54 GetVersion(context.Context, *connect.Request[ermite.GetVersionRequest]) (*connect.Response[ermite.GetVersionResponse], error)
55 // GetSigningKey returns the public key with which the server signs the55 // GetSigningKey returns the public key with which the server signs the
56- // commits it creates (squash merges, see MergeMode). It needs no56+ // commits it creates (squash and merge commits, see MergeMode). It needs no
57 // authentication: the key is public, and anyone verifying a commit needs it.57 // authentication: the key is public, and anyone verifying a commit needs it.
58 // Servers that implement it list "signing_key" in58 // Servers that implement it list "signing_key" in
59 // GetVersionResponse.features.59 // GetVersionResponse.features.
@@ -132,7 +132,7 @@ type ServerServiceHandler interface {
132 // it first to check compatibility.132 // it first to check compatibility.
133 GetVersion(context.Context, *connect.Request[ermite.GetVersionRequest]) (*connect.Response[ermite.GetVersionResponse], error)133 GetVersion(context.Context, *connect.Request[ermite.GetVersionRequest]) (*connect.Response[ermite.GetVersionResponse], error)
134 // GetSigningKey returns the public key with which the server signs the134 // GetSigningKey returns the public key with which the server signs the
135- // commits it creates (squash merges, see MergeMode). It needs no135+ // commits it creates (squash and merge commits, see MergeMode). It needs no
136 // authentication: the key is public, and anyone verifying a commit needs it.136 // authentication: the key is public, and anyone verifying a commit needs it.
137 // Servers that implement it list "signing_key" in137 // Servers that implement it list "signing_key" in
138 // GetVersionResponse.features.138 // GetVersionResponse.features.
M gen/ermite/event.pb.go +207 -112 generated file: collapsed
M gen/ermite/merge_request.pb.go +380 -162 generated file: collapsed
M gen/ermite/repo.pb.go +2 -2 generated file: collapsed
M gen/ermite/server.pb.go +4 -1
@@ -85,7 +85,10 @@ type GetVersionResponse struct {
8585 // secrets, vars, environment, permissions and trusted fields and Run.trusted),
8686 // "ci.dispatch" (RunService.DispatchWorkflow), "ci.concurrency"
8787 // (CI_STATUS_PENDING and RunJob.concurrency_group), "ci.cache" (Job.cache)
88- // and "ci.artifacts" (JobArtifactService and ArtifactService).
88+ // and "ci.artifacts" (JobArtifactService and ArtifactService). The merge
89+ // request identifier "merge_requests.mergeability" covers
90+ // MergeRequest.mergeability, the MergeRequestMergeability event and
91+ // MERGE_MODE_MERGE_COMMIT.
8992 Features []string `protobuf:"bytes,2,rep,name=features,proto3" json:"features,omitempty"`
9093 unknownFields protoimpl.UnknownFields
9194 sizeCache protoimpl.SizeCache
@@ -85,7 +85,10 @@ type GetVersionResponse struct {
85 // secrets, vars, environment, permissions and trusted fields and Run.trusted),85 // secrets, vars, environment, permissions and trusted fields and Run.trusted),
86 // "ci.dispatch" (RunService.DispatchWorkflow), "ci.concurrency"86 // "ci.dispatch" (RunService.DispatchWorkflow), "ci.concurrency"
87 // (CI_STATUS_PENDING and RunJob.concurrency_group), "ci.cache" (Job.cache)87 // (CI_STATUS_PENDING and RunJob.concurrency_group), "ci.cache" (Job.cache)
88- // and "ci.artifacts" (JobArtifactService and ArtifactService).88+ // and "ci.artifacts" (JobArtifactService and ArtifactService). The merge
89+ // request identifier "merge_requests.mergeability" covers
90+ // MergeRequest.mergeability, the MergeRequestMergeability event and
91+ // MERGE_MODE_MERGE_COMMIT.
89 Features []string `protobuf:"bytes,2,rep,name=features,proto3" json:"features,omitempty"`92 Features []string `protobuf:"bytes,2,rep,name=features,proto3" json:"features,omitempty"`
90 unknownFields protoimpl.UnknownFields93 unknownFields protoimpl.UnknownFields
91 sizeCache protoimpl.SizeCache94 sizeCache protoimpl.SizeCache
M gen/ermite/types.pb.go +20 -7 generated file: collapsed
M gen_test.go +6 -0
@@ -114,6 +114,12 @@ func TestEnumNumbers(t *testing.T) {
114114 if got := ermite.MergeMode_MERGE_MODE_SQUASH; got != 2 {
115115 t.Errorf("MERGE_MODE_SQUASH = %d, want 2", got)
116116 }
117+ if got := ermite.MergeMode_MERGE_MODE_MERGE_COMMIT; got != 3 {
118+ t.Errorf("MERGE_MODE_MERGE_COMMIT = %d, want 3", got)
119+ }
120+ if got := ermite.MergeabilityState_MERGEABILITY_STATE_CONFLICT; got != 4 {
121+ t.Errorf("MERGEABILITY_STATE_CONFLICT = %d, want 4", got)
122+ }
117123 if got := ermite.MergeRequestState_MERGE_REQUEST_STATE_CLOSED; got != 3 {
118124 t.Errorf("MERGE_REQUEST_STATE_CLOSED = %d, want 3", got)
119125 }
@@ -114,6 +114,12 @@ func TestEnumNumbers(t *testing.T) {
114 if got := ermite.MergeMode_MERGE_MODE_SQUASH; got != 2 {114 if got := ermite.MergeMode_MERGE_MODE_SQUASH; got != 2 {
115 t.Errorf("MERGE_MODE_SQUASH = %d, want 2", got)115 t.Errorf("MERGE_MODE_SQUASH = %d, want 2", got)
116 }116 }
117+ if got := ermite.MergeMode_MERGE_MODE_MERGE_COMMIT; got != 3 {
118+ t.Errorf("MERGE_MODE_MERGE_COMMIT = %d, want 3", got)
119+ }
120+ if got := ermite.MergeabilityState_MERGEABILITY_STATE_CONFLICT; got != 4 {
121+ t.Errorf("MERGEABILITY_STATE_CONFLICT = %d, want 4", got)
122+ }
117 if got := ermite.MergeRequestState_MERGE_REQUEST_STATE_CLOSED; got != 3 {123 if got := ermite.MergeRequestState_MERGE_REQUEST_STATE_CLOSED; got != 3 {
118 t.Errorf("MERGE_REQUEST_STATE_CLOSED = %d, want 3", got)124 t.Errorf("MERGE_REQUEST_STATE_CLOSED = %d, want 3", got)
119 }125 }
M proto/ermite/event.proto +15 -0
@@ -132,6 +132,9 @@ message Event {
132132 // IssueSubmitted: an issue is awaiting moderation. Delivered only on the
133133 // "owner" topic, never on "home" or "repo:<name>", and carries no content.
134134 IssueSubmitted issue_submitted = 21;
135+ // MergeRequestMergeability: the mergeability of an open merge request
136+ // changed.
137+ MergeRequestMergeability merge_request_mergeability = 22;
135138 }
136139 }
137140
@@ -184,6 +187,18 @@ message MergeRequestMerged {
184187 MergeRequest merge_request = 1;
185188 }
186189
190+// MergeRequestMergeability reports the mergeability of an open merge request.
191+// It is published when the state changes, or when a check settles a result
192+// that readers had not seen yet (they saw CHECKING), not on every push.
193+message MergeRequestMergeability {
194+ // Repo is the name of the repository.
195+ string repo = 1;
196+ // Number is the number of the merge request.
197+ int64 number = 2;
198+ // Mergeability is the new result.
199+ Mergeability mergeability = 3;
200+}
201+
187202 // MergeRequestClosed reports a merge request closed without merging.
188203 message MergeRequestClosed {
189204 // MergeRequest is the merge request after closing.
@@ -132,6 +132,9 @@ message Event {
132 // IssueSubmitted: an issue is awaiting moderation. Delivered only on the132 // IssueSubmitted: an issue is awaiting moderation. Delivered only on the
133 // "owner" topic, never on "home" or "repo:<name>", and carries no content.133 // "owner" topic, never on "home" or "repo:<name>", and carries no content.
134 IssueSubmitted issue_submitted = 21;134 IssueSubmitted issue_submitted = 21;
135+ // MergeRequestMergeability: the mergeability of an open merge request
136+ // changed.
137+ MergeRequestMergeability merge_request_mergeability = 22;
135 }138 }
136 }139 }
137 140
@@ -184,6 +187,18 @@ message MergeRequestMerged {
184 MergeRequest merge_request = 1;187 MergeRequest merge_request = 1;
185 }188 }
186 189
190+// MergeRequestMergeability reports the mergeability of an open merge request.
191+// It is published when the state changes, or when a check settles a result
192+// that readers had not seen yet (they saw CHECKING), not on every push.
193+message MergeRequestMergeability {
194+ // Repo is the name of the repository.
195+ string repo = 1;
196+ // Number is the number of the merge request.
197+ int64 number = 2;
198+ // Mergeability is the new result.
199+ Mergeability mergeability = 3;
200+}
201+
187 // MergeRequestClosed reports a merge request closed without merging.202 // MergeRequestClosed reports a merge request closed without merging.
188 message MergeRequestClosed {203 message MergeRequestClosed {
189 // MergeRequest is the merge request after closing.204 // MergeRequest is the merge request after closing.
M proto/ermite/merge_request.proto +63 -7
@@ -44,6 +44,50 @@ enum Verdict {
4444 VERDICT_CHANGES_REQUESTED = 2;
4545 }
4646
47+// MergeabilityState tells whether the head of an open merge request can be
48+// merged into its target.
49+enum MergeabilityState {
50+ // Not set.
51+ MERGEABILITY_STATE_UNSPECIFIED = 0;
52+ // The server has no result yet for the current tips of both branches. It
53+ // is replaced by another state when the check completes.
54+ MERGEABILITY_STATE_CHECKING = 1;
55+ // The target is an ancestor of the head: every mode can merge.
56+ MERGEABILITY_STATE_UP_TO_DATE = 2;
57+ // The target has commits the head lacks, but the head merges cleanly:
58+ // squash and merge commit can merge, fast-forward cannot.
59+ MERGEABILITY_STATE_BEHIND = 3;
60+ // The head does not merge cleanly into the target, or the branches share no
61+ // history: no mode can merge.
62+ MERGEABILITY_STATE_CONFLICT = 4;
63+}
64+
65+// Mergeability is the result of checking an open merge request against the
66+// tips of its branches.
67+message Mergeability {
68+ // State is the result.
69+ MergeabilityState state = 1;
70+ // TargetId is the commit the target branch pointed to when checked. Empty
71+ // while CHECKING.
72+ string target_id = 2;
73+ // HeadId is the commit the source branch pointed to when checked. Empty
74+ // while CHECKING.
75+ string head_id = 3;
76+ // Behind is the number of commits of the target that the head lacks. Set
77+ // only when state is BEHIND.
78+ int32 behind = 4;
79+ // BehindCapped says that Behind is a lower bound: the server stops counting
80+ // at 1,000.
81+ bool behind_capped = 5;
82+ // ConflictPaths are the paths that conflict, at most 20. Set only when
83+ // state is CONFLICT; empty when the branches share no history.
84+ repeated string conflict_paths = 6;
85+ // ConflictPathsTruncated says that more paths conflict than listed.
86+ bool conflict_paths_truncated = 7;
87+ // CheckedAt is when the server computed the result. Unset while CHECKING.
88+ google.protobuf.Timestamp checked_at = 8;
89+}
90+
4791 // MergeRequest asks to merge one branch of a repository into another branch
4892 // of the same repository. It is about a branch, not about who wrote it.
4993 message MergeRequest {
@@ -81,12 +125,19 @@ message MergeRequest {
81125 // merged or closed it is the head at that moment.
82126 string head_id = 11;
83127 // MergedCommitId is the id of the commit the target branch points to after
84- // the merge: the head for a fast-forward, the new commit for a squash.
128+ // the merge: the head for a fast-forward, the new commit for a squash or a
129+ // merge commit.
85130 // Empty unless state is MERGED.
86131 string merged_commit_id = 12;
87132 // MergeMode is how the merge request was merged. MERGE_MODE_UNSPECIFIED
88133 // unless state is MERGED.
89134 MergeMode merge_mode = 13;
135+ // Mergeability tells whether the head can be merged into the target. Set
136+ // while the merge request is OPEN, with state CHECKING until the server has
137+ // a result for the current tips; never set once merged or closed. It is a
138+ // hint: MergeMergeRequest checks again. Set only by servers with the
139+ // "merge_requests.mergeability" feature.
140+ Mergeability mergeability = 14;
90141 }
91142
92143 // Comment is a note on a merge request, either general or attached to a line
@@ -233,9 +284,12 @@ service MergeRequestService {
233284 // never merged unseen. Fails with invalid_argument if head_id is empty or
234285 // not a commit id, and with failed_precondition if the merge request is not
235286 // open, if the source branch no longer points to head_id ("branch moved,
236- // read again"), if the target is not an ancestor of the head (rebase and
237- // push again), if the mode is not allowed by RepoSettings, or if a check
238- // fails and RepoSettings.failing_checks_block is set. Merges of one
287+ // read again"), if the mode is fast-forward and the target is not an
288+ // ancestor of the head (rebase and push again, or merge with squash or
289+ // merge commit), if the mode is squash or merge commit and the head
290+ // conflicts with the target or shares no history with it (rebase and push
291+ // again), if the mode is not allowed by RepoSettings, or if a check fails
292+ // and RepoSettings.failing_checks_block is set. Merges of one
239293 // repository run one at a time. Repeating a call that succeeded fails with
240294 // failed_precondition (not open). On success the server publishes
241295 // MergeRequestMerged then Pushed for the target branch.
@@ -477,9 +531,11 @@ message MergeMergeRequestResponse {
477531 // merged_commit_id set to the new tip of the target branch and merge_mode
478532 // set to the mode used.
479533 MergeRequest merge_request = 1;
480- // SourceBranchDeleted is true when delete_source_branch was set and the
481- // branch is gone. It is false when the deletion failed after the merge (the
482- // branch moved in between, say): the merge stands and the branch is kept.
534+ // SourceBranchDeleted is true when delete_source_branch was set: the server
535+ // deletes the branch after answering. The deletion is skipped, and the
536+ // branch kept, if the branch moved or another open merge request uses it
537+ // by then; the merge stands either way. Watch reports the deletion with a
538+ // Pushed event for the source branch.
483539 bool source_branch_deleted = 2;
484540 }
485541
@@ -44,6 +44,50 @@ enum Verdict {
44 VERDICT_CHANGES_REQUESTED = 2;44 VERDICT_CHANGES_REQUESTED = 2;
45 }45 }
46 46
47+// MergeabilityState tells whether the head of an open merge request can be
48+// merged into its target.
49+enum MergeabilityState {
50+ // Not set.
51+ MERGEABILITY_STATE_UNSPECIFIED = 0;
52+ // The server has no result yet for the current tips of both branches. It
53+ // is replaced by another state when the check completes.
54+ MERGEABILITY_STATE_CHECKING = 1;
55+ // The target is an ancestor of the head: every mode can merge.
56+ MERGEABILITY_STATE_UP_TO_DATE = 2;
57+ // The target has commits the head lacks, but the head merges cleanly:
58+ // squash and merge commit can merge, fast-forward cannot.
59+ MERGEABILITY_STATE_BEHIND = 3;
60+ // The head does not merge cleanly into the target, or the branches share no
61+ // history: no mode can merge.
62+ MERGEABILITY_STATE_CONFLICT = 4;
63+}
64+
65+// Mergeability is the result of checking an open merge request against the
66+// tips of its branches.
67+message Mergeability {
68+ // State is the result.
69+ MergeabilityState state = 1;
70+ // TargetId is the commit the target branch pointed to when checked. Empty
71+ // while CHECKING.
72+ string target_id = 2;
73+ // HeadId is the commit the source branch pointed to when checked. Empty
74+ // while CHECKING.
75+ string head_id = 3;
76+ // Behind is the number of commits of the target that the head lacks. Set
77+ // only when state is BEHIND.
78+ int32 behind = 4;
79+ // BehindCapped says that Behind is a lower bound: the server stops counting
80+ // at 1,000.
81+ bool behind_capped = 5;
82+ // ConflictPaths are the paths that conflict, at most 20. Set only when
83+ // state is CONFLICT; empty when the branches share no history.
84+ repeated string conflict_paths = 6;
85+ // ConflictPathsTruncated says that more paths conflict than listed.
86+ bool conflict_paths_truncated = 7;
87+ // CheckedAt is when the server computed the result. Unset while CHECKING.
88+ google.protobuf.Timestamp checked_at = 8;
89+}
90+
47 // MergeRequest asks to merge one branch of a repository into another branch91 // MergeRequest asks to merge one branch of a repository into another branch
48 // of the same repository. It is about a branch, not about who wrote it.92 // of the same repository. It is about a branch, not about who wrote it.
49 message MergeRequest {93 message MergeRequest {
@@ -81,12 +125,19 @@ message MergeRequest {
81 // merged or closed it is the head at that moment.125 // merged or closed it is the head at that moment.
82 string head_id = 11;126 string head_id = 11;
83 // MergedCommitId is the id of the commit the target branch points to after127 // MergedCommitId is the id of the commit the target branch points to after
84- // the merge: the head for a fast-forward, the new commit for a squash.128+ // the merge: the head for a fast-forward, the new commit for a squash or a
129+ // merge commit.
85 // Empty unless state is MERGED.130 // Empty unless state is MERGED.
86 string merged_commit_id = 12;131 string merged_commit_id = 12;
87 // MergeMode is how the merge request was merged. MERGE_MODE_UNSPECIFIED132 // MergeMode is how the merge request was merged. MERGE_MODE_UNSPECIFIED
88 // unless state is MERGED.133 // unless state is MERGED.
89 MergeMode merge_mode = 13;134 MergeMode merge_mode = 13;
135+ // Mergeability tells whether the head can be merged into the target. Set
136+ // while the merge request is OPEN, with state CHECKING until the server has
137+ // a result for the current tips; never set once merged or closed. It is a
138+ // hint: MergeMergeRequest checks again. Set only by servers with the
139+ // "merge_requests.mergeability" feature.
140+ Mergeability mergeability = 14;
90 }141 }
91 142
92 // Comment is a note on a merge request, either general or attached to a line143 // Comment is a note on a merge request, either general or attached to a line
@@ -233,9 +284,12 @@ service MergeRequestService {
233 // never merged unseen. Fails with invalid_argument if head_id is empty or284 // never merged unseen. Fails with invalid_argument if head_id is empty or
234 // not a commit id, and with failed_precondition if the merge request is not285 // not a commit id, and with failed_precondition if the merge request is not
235 // open, if the source branch no longer points to head_id ("branch moved,286 // open, if the source branch no longer points to head_id ("branch moved,
236- // read again"), if the target is not an ancestor of the head (rebase and287+ // read again"), if the mode is fast-forward and the target is not an
237- // push again), if the mode is not allowed by RepoSettings, or if a check288+ // ancestor of the head (rebase and push again, or merge with squash or
238- // fails and RepoSettings.failing_checks_block is set. Merges of one289+ // merge commit), if the mode is squash or merge commit and the head
290+ // conflicts with the target or shares no history with it (rebase and push
291+ // again), if the mode is not allowed by RepoSettings, or if a check fails
292+ // and RepoSettings.failing_checks_block is set. Merges of one
239 // repository run one at a time. Repeating a call that succeeded fails with293 // repository run one at a time. Repeating a call that succeeded fails with
240 // failed_precondition (not open). On success the server publishes294 // failed_precondition (not open). On success the server publishes
241 // MergeRequestMerged then Pushed for the target branch.295 // MergeRequestMerged then Pushed for the target branch.
@@ -477,9 +531,11 @@ message MergeMergeRequestResponse {
477 // merged_commit_id set to the new tip of the target branch and merge_mode531 // merged_commit_id set to the new tip of the target branch and merge_mode
478 // set to the mode used.532 // set to the mode used.
479 MergeRequest merge_request = 1;533 MergeRequest merge_request = 1;
480- // SourceBranchDeleted is true when delete_source_branch was set and the534+ // SourceBranchDeleted is true when delete_source_branch was set: the server
481- // branch is gone. It is false when the deletion failed after the merge (the535+ // deletes the branch after answering. The deletion is skipped, and the
482- // branch moved in between, say): the merge stands and the branch is kept.536+ // branch kept, if the branch moved or another open merge request uses it
537+ // by then; the merge stands either way. Watch reports the deletion with a
538+ // Pushed event for the source branch.
483 bool source_branch_deleted = 2;539 bool source_branch_deleted = 2;
484 }540 }
485 541
M proto/ermite/repo.proto +2 -2
@@ -37,8 +37,8 @@ enum IssuesMode {
3737 message RepoSettings {
3838 // AllowedMergeModes are the modes with which a merge request of the
3939 // repository may be merged. It is never empty in a response and has no
40- // duplicate. Both MERGE_MODE_FAST_FORWARD and MERGE_MODE_SQUASH are allowed
41- // by default. MERGE_MODE_UNSPECIFIED is rejected.
40+ // duplicate. Every mode is allowed by default. MERGE_MODE_UNSPECIFIED is
41+ // rejected.
4242 repeated MergeMode allowed_merge_modes = 1;
4343 // FailingChecksBlock says whether a merge request whose checks fail cannot be
4444 // merged. It is true at creation of the repository and always stored
@@ -37,8 +37,8 @@ enum IssuesMode {
37 message RepoSettings {37 message RepoSettings {
38 // AllowedMergeModes are the modes with which a merge request of the38 // AllowedMergeModes are the modes with which a merge request of the
39 // repository may be merged. It is never empty in a response and has no39 // repository may be merged. It is never empty in a response and has no
40- // duplicate. Both MERGE_MODE_FAST_FORWARD and MERGE_MODE_SQUASH are allowed40+ // duplicate. Every mode is allowed by default. MERGE_MODE_UNSPECIFIED is
41- // by default. MERGE_MODE_UNSPECIFIED is rejected.41+ // rejected.
42 repeated MergeMode allowed_merge_modes = 1;42 repeated MergeMode allowed_merge_modes = 1;
43 // FailingChecksBlock says whether a merge request whose checks fail cannot be43 // FailingChecksBlock says whether a merge request whose checks fail cannot be
44 // merged. It is true at creation of the repository and always stored44 // merged. It is true at creation of the repository and always stored
M proto/ermite/server.proto +5 -2
@@ -13,7 +13,7 @@ service ServerService {
1313 // it first to check compatibility.
1414 rpc GetVersion(GetVersionRequest) returns (GetVersionResponse);
1515 // GetSigningKey returns the public key with which the server signs the
16- // commits it creates (squash merges, see MergeMode). It needs no
16+ // commits it creates (squash and merge commits, see MergeMode). It needs no
1717 // authentication: the key is public, and anyone verifying a commit needs it.
1818 // Servers that implement it list "signing_key" in
1919 // GetVersionResponse.features.
@@ -57,7 +57,10 @@ message GetVersionResponse {
5757 // secrets, vars, environment, permissions and trusted fields and Run.trusted),
5858 // "ci.dispatch" (RunService.DispatchWorkflow), "ci.concurrency"
5959 // (CI_STATUS_PENDING and RunJob.concurrency_group), "ci.cache" (Job.cache)
60- // and "ci.artifacts" (JobArtifactService and ArtifactService).
60+ // and "ci.artifacts" (JobArtifactService and ArtifactService). The merge
61+ // request identifier "merge_requests.mergeability" covers
62+ // MergeRequest.mergeability, the MergeRequestMergeability event and
63+ // MERGE_MODE_MERGE_COMMIT.
6164 repeated string features = 2;
6265 }
6366
@@ -13,7 +13,7 @@ service ServerService {
13 // it first to check compatibility.13 // it first to check compatibility.
14 rpc GetVersion(GetVersionRequest) returns (GetVersionResponse);14 rpc GetVersion(GetVersionRequest) returns (GetVersionResponse);
15 // GetSigningKey returns the public key with which the server signs the15 // GetSigningKey returns the public key with which the server signs the
16- // commits it creates (squash merges, see MergeMode). It needs no16+ // commits it creates (squash and merge commits, see MergeMode). It needs no
17 // authentication: the key is public, and anyone verifying a commit needs it.17 // authentication: the key is public, and anyone verifying a commit needs it.
18 // Servers that implement it list "signing_key" in18 // Servers that implement it list "signing_key" in
19 // GetVersionResponse.features.19 // GetVersionResponse.features.
@@ -57,7 +57,10 @@ message GetVersionResponse {
57 // secrets, vars, environment, permissions and trusted fields and Run.trusted),57 // secrets, vars, environment, permissions and trusted fields and Run.trusted),
58 // "ci.dispatch" (RunService.DispatchWorkflow), "ci.concurrency"58 // "ci.dispatch" (RunService.DispatchWorkflow), "ci.concurrency"
59 // (CI_STATUS_PENDING and RunJob.concurrency_group), "ci.cache" (Job.cache)59 // (CI_STATUS_PENDING and RunJob.concurrency_group), "ci.cache" (Job.cache)
60- // and "ci.artifacts" (JobArtifactService and ArtifactService).60+ // and "ci.artifacts" (JobArtifactService and ArtifactService). The merge
61+ // request identifier "merge_requests.mergeability" covers
62+ // MergeRequest.mergeability, the MergeRequestMergeability event and
63+ // MERGE_MODE_MERGE_COMMIT.
61 repeated string features = 2;64 repeated string features = 2;
62 }65 }
63 66
M proto/ermite/types.proto +15 -5
@@ -40,9 +40,11 @@ message Commit {
4040 CommitVerification verification = 7;
4141 }
4242
43-// MergeMode is how a merge request is merged. Both modes need the target to
44-// be an ancestor of the head: a branch behind its target is rebased by its
45-// author and pushed again, the server never rebases nor creates merge commits.
43+// MergeMode is how a merge request is merged. Fast-forward needs the target
44+// to be an ancestor of the head. Squash and merge commit need the head to
45+// merge cleanly into the target, which holds when the branch is behind its
46+// target without conflict. The server never rebases: a branch that conflicts
47+// is rebased by its author and pushed again.
4648 enum MergeMode {
4749 // Not set. In MergeMergeRequestRequest.mode it means fast-forward. Rejected
4850 // in RepoSettings.allowed_merge_modes. Returned on a merge request that was
@@ -51,11 +53,19 @@ enum MergeMode {
5153 // The target branch moves to the head. No commit is created and the
5254 // signatures of the branch's commits are kept.
5355 MERGE_MODE_FAST_FORWARD = 1;
54- // One new commit is created with the tree of the head and the target as
55- // only parent, signed by the server's key (see ServerService.GetSigningKey).
56+ // One new commit is created with the tree of the merge of the head into the
57+ // target (the tree of the head when the target is its ancestor) and the
58+ // target tip as only parent, signed by the server's key (see ServerService.GetSigningKey).
5659 // Its author is the author of the first commit of the branch, the other
5760 // authors are added as "Co-authored-by:" trailers, and its message is the
5861 // title, " (!<number>)", a blank line and the list of the squashed commits'
5962 // summaries.
6063 MERGE_MODE_SQUASH = 2;
64+ // One new commit is created with two parents, the target tip then the head,
65+ // and the tree of the merge. It is signed by the server's key (see
66+ // ServerService.GetSigningKey); its author and committer are the server
67+ // identity. Its message is "Merge branch '<source>' into <target> (!<n>)",
68+ // a blank line and the title. The signatures of the branch's commits are
69+ // kept. Needs the "merge_requests.mergeability" feature.
70+ MERGE_MODE_MERGE_COMMIT = 3;
6171 }
@@ -40,9 +40,11 @@ message Commit {
40 CommitVerification verification = 7;40 CommitVerification verification = 7;
41 }41 }
42 42
43-// MergeMode is how a merge request is merged. Both modes need the target to43+// MergeMode is how a merge request is merged. Fast-forward needs the target
44-// be an ancestor of the head: a branch behind its target is rebased by its44+// to be an ancestor of the head. Squash and merge commit need the head to
45-// author and pushed again, the server never rebases nor creates merge commits.45+// merge cleanly into the target, which holds when the branch is behind its
46+// target without conflict. The server never rebases: a branch that conflicts
47+// is rebased by its author and pushed again.
46 enum MergeMode {48 enum MergeMode {
47 // Not set. In MergeMergeRequestRequest.mode it means fast-forward. Rejected49 // Not set. In MergeMergeRequestRequest.mode it means fast-forward. Rejected
48 // in RepoSettings.allowed_merge_modes. Returned on a merge request that was50 // in RepoSettings.allowed_merge_modes. Returned on a merge request that was
@@ -51,11 +53,19 @@ enum MergeMode {
51 // The target branch moves to the head. No commit is created and the53 // The target branch moves to the head. No commit is created and the
52 // signatures of the branch's commits are kept.54 // signatures of the branch's commits are kept.
53 MERGE_MODE_FAST_FORWARD = 1;55 MERGE_MODE_FAST_FORWARD = 1;
54- // One new commit is created with the tree of the head and the target as56+ // One new commit is created with the tree of the merge of the head into the
55- // only parent, signed by the server's key (see ServerService.GetSigningKey).57+ // target (the tree of the head when the target is its ancestor) and the
58+ // target tip as only parent, signed by the server's key (see ServerService.GetSigningKey).
56 // Its author is the author of the first commit of the branch, the other59 // Its author is the author of the first commit of the branch, the other
57 // authors are added as "Co-authored-by:" trailers, and its message is the60 // authors are added as "Co-authored-by:" trailers, and its message is the
58 // title, " (!<number>)", a blank line and the list of the squashed commits'61 // title, " (!<number>)", a blank line and the list of the squashed commits'
59 // summaries.62 // summaries.
60 MERGE_MODE_SQUASH = 2;63 MERGE_MODE_SQUASH = 2;
64+ // One new commit is created with two parents, the target tip then the head,
65+ // and the tree of the merge. It is signed by the server's key (see
66+ // ServerService.GetSigningKey); its author and committer are the server
67+ // identity. Its message is "Merge branch '<source>' into <target> (!<n>)",
68+ // a blank line and the title. The signatures of the branch's commits are
69+ // kept. Needs the "merge_requests.mergeability" feature.
70+ MERGE_MODE_MERGE_COMMIT = 3;
61 }71 }

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 2

Tags 18

↑↓ moveenter openesc close