detached 303f037

feat(api): add merge requests, web sessions and signing key

Add MergeRequestService, SessionService, ServerService.GetSigningKey,
RepoSettings (merge modes, failing checks) on Repo and UpdateRepo, the
MergeMode enum and the merge request event kinds. Compatible additions only.
commit
303f037eb5ed30b94157b8eecadcea11ec97509b
author
brice · · 2026-10-10 15:50 UTC
parent
6c7e2b3
signature
GPG VerifiedCD88F9E564E0102646FB6077A1A07C7C7C3E349D · arch-gpg Verified for the committer email
19 files · +3961 -140
M README.md +6 -4
@@ -2,7 +2,7 @@
22
33 The public API contract of [ermite](https://git.bnema.dev/bnema/ermite), a single-user Git forge: protobuf definitions for its ConnectRPC services and the Go code generated from them.
44
5-Status: early development. The contract defines five services (below); the server that implements them is being built.
5+Status: early development. The contract defines seven services (below); the server that implements them is being built.
66
77 ```go
88 import "bnema.dev/ermite-api/gen/ermite"
@@ -16,17 +16,19 @@ Handlers are mounted under `/-/api/` by the server. Errors are Connect codes.
1616
1717 | Service | RPCs |
1818 |---|---|
19-| `RepoService` | `ListRepos`, `GetRepo`, `CreateRepo`, `UpdateRepo` (field mask), `DeleteRepo` |
19+| `RepoService` | `ListRepos`, `GetRepo`, `CreateRepo`, `UpdateRepo` (field mask, merge settings), `DeleteRepo` |
2020 | `GitService` | `ListRefs`, `GetTree`, `GetBlob` (streamed), `ListCommits`, `GetCommit` (structured diff), `ResolveRevision` |
2121 | `TokenService` | `CreateToken` (secret shown once), `ListTokens`, `RevokeToken` |
22+| `MergeRequestService` | `CreateMergeRequest`, `ListMergeRequests`, `GetMergeRequest` (commits, diff, comments), `AddComment`, `ResolveComment`, `CloseMergeRequest`, `MergeMergeRequest`, `MarkRead` |
23+| `SessionService` | `CreateLoginLink`, `SignOutEverywhere` (owner only) |
2224 | `EventService` | `Watch` (streamed, resumable) |
23-| `ServerService` | `GetVersion` (server version and feature list) |
25+| `ServerService` | `GetVersion` (server version and feature list), `GetSigningKey` (commit signing key) |
2426
2527 Tokens carry scopes only; presets such as `cli` or `agent` are a client concern.
2628
2729 ## Layout
2830
29-- `proto/ermite/`: `.proto` files of the `ermite` package, one per service (`repo`, `git`, `token`, `event`, `server`) plus shared `types`. Checked by `buf lint` and `buf breaking`.
31+- `proto/ermite/`: `.proto` files of the `ermite` package, one per service (`repo`, `git`, `merge_request`, `token`, `session`, `event`, `server`) plus shared `types`. Checked by `buf lint` and `buf breaking`.
3032 - `gen/ermite/`: generated Go messages. `gen/ermite/ermiteconnect/`: generated Connect clients and handlers. Committed, never edited by hand.
3133
3234 ## Generate
@@ -2,7 +2,7 @@
2 2
3 The public API contract of [ermite](https://git.bnema.dev/bnema/ermite), a single-user Git forge: protobuf definitions for its ConnectRPC services and the Go code generated from them.3 The public API contract of [ermite](https://git.bnema.dev/bnema/ermite), a single-user Git forge: protobuf definitions for its ConnectRPC services and the Go code generated from them.
4 4
5-Status: early development. The contract defines five services (below); the server that implements them is being built.5+Status: early development. The contract defines seven services (below); the server that implements them is being built.
6 6
7 ```go7 ```go
8 import "bnema.dev/ermite-api/gen/ermite"8 import "bnema.dev/ermite-api/gen/ermite"
@@ -16,17 +16,19 @@ Handlers are mounted under `/-/api/` by the server. Errors are Connect codes.
16 16
17 | Service | RPCs |17 | Service | RPCs |
18 |---|---|18 |---|---|
19-| `RepoService` | `ListRepos`, `GetRepo`, `CreateRepo`, `UpdateRepo` (field mask), `DeleteRepo` |19+| `RepoService` | `ListRepos`, `GetRepo`, `CreateRepo`, `UpdateRepo` (field mask, merge settings), `DeleteRepo` |
20 | `GitService` | `ListRefs`, `GetTree`, `GetBlob` (streamed), `ListCommits`, `GetCommit` (structured diff), `ResolveRevision` |20 | `GitService` | `ListRefs`, `GetTree`, `GetBlob` (streamed), `ListCommits`, `GetCommit` (structured diff), `ResolveRevision` |
21 | `TokenService` | `CreateToken` (secret shown once), `ListTokens`, `RevokeToken` |21 | `TokenService` | `CreateToken` (secret shown once), `ListTokens`, `RevokeToken` |
22+| `MergeRequestService` | `CreateMergeRequest`, `ListMergeRequests`, `GetMergeRequest` (commits, diff, comments), `AddComment`, `ResolveComment`, `CloseMergeRequest`, `MergeMergeRequest`, `MarkRead` |
23+| `SessionService` | `CreateLoginLink`, `SignOutEverywhere` (owner only) |
22 | `EventService` | `Watch` (streamed, resumable) |24 | `EventService` | `Watch` (streamed, resumable) |
23-| `ServerService` | `GetVersion` (server version and feature list) |25+| `ServerService` | `GetVersion` (server version and feature list), `GetSigningKey` (commit signing key) |
24 26
25 Tokens carry scopes only; presets such as `cli` or `agent` are a client concern.27 Tokens carry scopes only; presets such as `cli` or `agent` are a client concern.
26 28
27 ## Layout29 ## Layout
28 30
29-- `proto/ermite/`: `.proto` files of the `ermite` package, one per service (`repo`, `git`, `token`, `event`, `server`) plus shared `types`. Checked by `buf lint` and `buf breaking`.31+- `proto/ermite/`: `.proto` files of the `ermite` package, one per service (`repo`, `git`, `merge_request`, `token`, `session`, `event`, `server`) plus shared `types`. Checked by `buf lint` and `buf breaking`.
30 - `gen/ermite/`: generated Go messages. `gen/ermite/ermiteconnect/`: generated Connect clients and handlers. Committed, never edited by hand.32 - `gen/ermite/`: generated Go messages. `gen/ermite/ermiteconnect/`: generated Connect clients and handlers. Committed, never edited by hand.
31 33
32 ## Generate34 ## Generate
M gen/ermite/ermiteconnect/event.connect.go +18 -0
@@ -45,6 +45,15 @@ type EventServiceClient interface {
4545 // down. Events are filtered by the caller's access: events of a private
4646 // repository are only sent to callers that may read it.
4747 //
48+ // The events are repo_created, repo_updated, repo_deleted, pushed, gap and,
49+ // for merge requests, merge_request_opened, merge_request_merged,
50+ // merge_request_closed and comment_added. Each one is delivered on the
51+ // "repo:<name>" topic of its repository and on "home", subject to the
52+ // visibility of the repository. There is no event for a change of title or
53+ // description, nor for a resolved comment: after a gap, or to see such
54+ // changes, clients read the merge request again. A merge publishes
55+ // merge_request_merged, then pushed for the target branch.
56+ //
4857 // The server keeps a bounded buffer of recent events. With resume_after set,
4958 // buffered events after that id are replayed first, then live events follow.
5059 // When resume_after is no longer buffered (or unknown), the stream begins
@@ -98,6 +107,15 @@ type EventServiceHandler interface {
98107 // down. Events are filtered by the caller's access: events of a private
99108 // repository are only sent to callers that may read it.
100109 //
110+ // The events are repo_created, repo_updated, repo_deleted, pushed, gap and,
111+ // for merge requests, merge_request_opened, merge_request_merged,
112+ // merge_request_closed and comment_added. Each one is delivered on the
113+ // "repo:<name>" topic of its repository and on "home", subject to the
114+ // visibility of the repository. There is no event for a change of title or
115+ // description, nor for a resolved comment: after a gap, or to see such
116+ // changes, clients read the merge request again. A merge publishes
117+ // merge_request_merged, then pushed for the target branch.
118+ //
101119 // The server keeps a bounded buffer of recent events. With resume_after set,
102120 // buffered events after that id are replayed first, then live events follow.
103121 // When resume_after is no longer buffered (or unknown), the stream begins
@@ -45,6 +45,15 @@ type EventServiceClient interface {
45 // down. Events are filtered by the caller's access: events of a private45 // down. Events are filtered by the caller's access: events of a private
46 // repository are only sent to callers that may read it.46 // repository are only sent to callers that may read it.
47 //47 //
48+ // The events are repo_created, repo_updated, repo_deleted, pushed, gap and,
49+ // for merge requests, merge_request_opened, merge_request_merged,
50+ // merge_request_closed and comment_added. Each one is delivered on the
51+ // "repo:<name>" topic of its repository and on "home", subject to the
52+ // visibility of the repository. There is no event for a change of title or
53+ // description, nor for a resolved comment: after a gap, or to see such
54+ // changes, clients read the merge request again. A merge publishes
55+ // merge_request_merged, then pushed for the target branch.
56+ //
48 // The server keeps a bounded buffer of recent events. With resume_after set,57 // The server keeps a bounded buffer of recent events. With resume_after set,
49 // buffered events after that id are replayed first, then live events follow.58 // buffered events after that id are replayed first, then live events follow.
50 // When resume_after is no longer buffered (or unknown), the stream begins59 // When resume_after is no longer buffered (or unknown), the stream begins
@@ -98,6 +107,15 @@ type EventServiceHandler interface {
98 // down. Events are filtered by the caller's access: events of a private107 // down. Events are filtered by the caller's access: events of a private
99 // repository are only sent to callers that may read it.108 // repository are only sent to callers that may read it.
100 //109 //
110+ // The events are repo_created, repo_updated, repo_deleted, pushed, gap and,
111+ // for merge requests, merge_request_opened, merge_request_merged,
112+ // merge_request_closed and comment_added. Each one is delivered on the
113+ // "repo:<name>" topic of its repository and on "home", subject to the
114+ // visibility of the repository. There is no event for a change of title or
115+ // description, nor for a resolved comment: after a gap, or to see such
116+ // changes, clients read the merge request again. A merge publishes
117+ // merge_request_merged, then pushed for the target branch.
118+ //
101 // The server keeps a bounded buffer of recent events. With resume_after set,119 // The server keeps a bounded buffer of recent events. With resume_after set,
102 // buffered events after that id are replayed first, then live events follow.120 // buffered events after that id are replayed first, then live events follow.
103 // When resume_after is no longer buffered (or unknown), the stream begins121 // When resume_after is no longer buffered (or unknown), the stream begins
A gen/ermite/ermiteconnect/merge_request.connect.go +396 -0
@@ -0,0 +1,396 @@
1+// SPDX-License-Identifier: MIT
2+
3+// Code generated by protoc-gen-connect-go. DO NOT EDIT.
4+//
5+// Source: ermite/merge_request.proto
6+
7+package ermiteconnect
8+
9+import (
10+ ermite "bnema.dev/ermite-api/gen/ermite"
11+ connect "connectrpc.com/connect"
12+ context "context"
13+ errors "errors"
14+ http "net/http"
15+ strings "strings"
16+)
17+
18+// This is a compile-time assertion to ensure that this generated file and the connect package are
19+// compatible. If you get a compiler error that this constant is not defined, this code was
20+// generated with a version of connect newer than the one compiled into your binary. You can fix the
21+// problem by either regenerating this code with an older version of connect or updating the connect
22+// version compiled into your binary.
23+const _ = connect.IsAtLeastVersion1_13_0
24+
25+const (
26+ // MergeRequestServiceName is the fully-qualified name of the MergeRequestService service.
27+ MergeRequestServiceName = "ermite.MergeRequestService"
28+)
29+
30+// These constants are the fully-qualified names of the RPCs defined in this package. They're
31+// exposed at runtime as Spec.Procedure and as the final two segments of the HTTP route.
32+//
33+// Note that these are different from the fully-qualified method names used by
34+// google.golang.org/protobuf/reflect/protoreflect. To convert from these constants to
35+// reflection-formatted method names, remove the leading slash and convert the remaining slash to a
36+// period.
37+const (
38+ // MergeRequestServiceCreateMergeRequestProcedure is the fully-qualified name of the
39+ // MergeRequestService's CreateMergeRequest RPC.
40+ MergeRequestServiceCreateMergeRequestProcedure = "/ermite.MergeRequestService/CreateMergeRequest"
41+ // MergeRequestServiceListMergeRequestsProcedure is the fully-qualified name of the
42+ // MergeRequestService's ListMergeRequests RPC.
43+ MergeRequestServiceListMergeRequestsProcedure = "/ermite.MergeRequestService/ListMergeRequests"
44+ // MergeRequestServiceGetMergeRequestProcedure is the fully-qualified name of the
45+ // MergeRequestService's GetMergeRequest RPC.
46+ MergeRequestServiceGetMergeRequestProcedure = "/ermite.MergeRequestService/GetMergeRequest"
47+ // MergeRequestServiceAddCommentProcedure is the fully-qualified name of the MergeRequestService's
48+ // AddComment RPC.
49+ MergeRequestServiceAddCommentProcedure = "/ermite.MergeRequestService/AddComment"
50+ // MergeRequestServiceResolveCommentProcedure is the fully-qualified name of the
51+ // MergeRequestService's ResolveComment RPC.
52+ MergeRequestServiceResolveCommentProcedure = "/ermite.MergeRequestService/ResolveComment"
53+ // MergeRequestServiceCloseMergeRequestProcedure is the fully-qualified name of the
54+ // MergeRequestService's CloseMergeRequest RPC.
55+ MergeRequestServiceCloseMergeRequestProcedure = "/ermite.MergeRequestService/CloseMergeRequest"
56+ // MergeRequestServiceMergeMergeRequestProcedure is the fully-qualified name of the
57+ // MergeRequestService's MergeMergeRequest RPC.
58+ MergeRequestServiceMergeMergeRequestProcedure = "/ermite.MergeRequestService/MergeMergeRequest"
59+ // MergeRequestServiceMarkReadProcedure is the fully-qualified name of the MergeRequestService's
60+ // MarkRead RPC.
61+ MergeRequestServiceMarkReadProcedure = "/ermite.MergeRequestService/MarkRead"
62+)
63+
64+// MergeRequestServiceClient is a client for the ermite.MergeRequestService service.
65+type MergeRequestServiceClient interface {
66+ // CreateMergeRequest opens a merge request. Needs the mr:write scope. Fails
67+ // with invalid_argument if a text rule is broken, if a branch does not exist
68+ // or if source and target are the same branch, and with already_exists if an
69+ // open merge request already has the same source and target.
70+ CreateMergeRequest(context.Context, *connect.Request[ermite.CreateMergeRequestRequest]) (*connect.Response[ermite.CreateMergeRequestResponse], error)
71+ // ListMergeRequests lists the merge requests of a repository, highest
72+ // number (newest) first, optionally restricted to one state. The listed
73+ // merge requests carry no diff or comments; head_id is set. Fails with
74+ // not_found if the repository does not exist or is not readable.
75+ ListMergeRequests(context.Context, *connect.Request[ermite.ListMergeRequestsRequest]) (*connect.Response[ermite.ListMergeRequestsResponse], error)
76+ // GetMergeRequest returns one merge request with its commits, its diff and
77+ // its comments. The diff compares the target with the head (what the merge
78+ // would bring: "target...head"), or, with since_last_read, the head the
79+ // caller last marked read with the current head. Fails with not_found if
80+ // the repository or the number is unknown.
81+ GetMergeRequest(context.Context, *connect.Request[ermite.GetMergeRequestRequest]) (*connect.Response[ermite.GetMergeRequestResponse], error)
82+ // AddComment adds a general comment or a comment on a line of the diff.
83+ // Needs the mr:write scope. Fails with invalid_argument if the text breaks
84+ // a rule or if path, line and side do not designate a line of the current
85+ // diff of the merge request, and with failed_precondition if the merge
86+ // request is not open.
87+ AddComment(context.Context, *connect.Request[ermite.AddCommentRequest]) (*connect.Response[ermite.AddCommentResponse], error)
88+ // ResolveComment marks a comment resolved or unresolved. Needs the mr:write
89+ // scope. Fails with not_found if the comment is unknown and with
90+ // failed_precondition if the merge request is not open. Setting the state
91+ // the comment already has succeeds and changes nothing.
92+ ResolveComment(context.Context, *connect.Request[ermite.ResolveCommentRequest]) (*connect.Response[ermite.ResolveCommentResponse], error)
93+ // CloseMergeRequest closes an open merge request without merging it. Needs
94+ // the mr:write scope. Fails with failed_precondition if the merge request
95+ // is not open. A closed merge request cannot be reopened: open a new one.
96+ CloseMergeRequest(context.Context, *connect.Request[ermite.CloseMergeRequestRequest]) (*connect.Response[ermite.CloseMergeRequestResponse], error)
97+ // MergeMergeRequest merges an open merge request. Needs the mr:merge scope.
98+ // The caller passes the head it reviewed: the server merges only if the
99+ // source branch still points to it, so a push between review and merge is
100+ // never merged unseen. Fails with invalid_argument if head_id is empty or
101+ // not a commit id, and with failed_precondition if the merge request is not
102+ // open, if the source branch no longer points to head_id ("branch moved,
103+ // read again"), if the target is not an ancestor of the head (rebase and
104+ // push again), if the mode is not allowed by RepoSettings, or if a check
105+ // fails and RepoSettings.failing_checks_block is set. Merges of one
106+ // repository run one at a time. Repeating a call that succeeded fails with
107+ // failed_precondition (not open). On success the server publishes
108+ // MergeRequestMerged then Pushed for the target branch.
109+ MergeMergeRequest(context.Context, *connect.Request[ermite.MergeMergeRequestRequest]) (*connect.Response[ermite.MergeMergeRequestResponse], error)
110+ // MarkRead records the head the caller has reviewed, for the since_last_read
111+ // option of GetMergeRequest. There is one read position per merge request,
112+ // the reviewer's. Needs the mr:write scope. Marking a commit that does not
113+ // exist in the repository is invalid_argument.
114+ MarkRead(context.Context, *connect.Request[ermite.MarkReadRequest]) (*connect.Response[ermite.MarkReadResponse], error)
115+}
116+
117+// NewMergeRequestServiceClient constructs a client for the ermite.MergeRequestService service. By
118+// default, it uses the Connect protocol with the binary Protobuf Codec, asks for gzipped responses,
119+// and sends uncompressed requests. To use the gRPC or gRPC-Web protocols, supply the
120+// connect.WithGRPC() or connect.WithGRPCWeb() options.
121+//
122+// The URL supplied here should be the base URL for the Connect or gRPC server (for example,
123+// http://api.acme.com or https://acme.com/grpc).
124+func NewMergeRequestServiceClient(httpClient connect.HTTPClient, baseURL string, opts ...connect.ClientOption) MergeRequestServiceClient {
125+ baseURL = strings.TrimRight(baseURL, "/")
126+ mergeRequestServiceMethods := ermite.File_ermite_merge_request_proto.Services().ByName("MergeRequestService").Methods()
127+ return &mergeRequestServiceClient{
128+ createMergeRequest: connect.NewClient[ermite.CreateMergeRequestRequest, ermite.CreateMergeRequestResponse](
129+ httpClient,
130+ baseURL+MergeRequestServiceCreateMergeRequestProcedure,
131+ connect.WithSchema(mergeRequestServiceMethods.ByName("CreateMergeRequest")),
132+ connect.WithClientOptions(opts...),
133+ ),
134+ listMergeRequests: connect.NewClient[ermite.ListMergeRequestsRequest, ermite.ListMergeRequestsResponse](
135+ httpClient,
136+ baseURL+MergeRequestServiceListMergeRequestsProcedure,
137+ connect.WithSchema(mergeRequestServiceMethods.ByName("ListMergeRequests")),
138+ connect.WithClientOptions(opts...),
139+ ),
140+ getMergeRequest: connect.NewClient[ermite.GetMergeRequestRequest, ermite.GetMergeRequestResponse](
141+ httpClient,
142+ baseURL+MergeRequestServiceGetMergeRequestProcedure,
143+ connect.WithSchema(mergeRequestServiceMethods.ByName("GetMergeRequest")),
144+ connect.WithClientOptions(opts...),
145+ ),
146+ addComment: connect.NewClient[ermite.AddCommentRequest, ermite.AddCommentResponse](
147+ httpClient,
148+ baseURL+MergeRequestServiceAddCommentProcedure,
149+ connect.WithSchema(mergeRequestServiceMethods.ByName("AddComment")),
150+ connect.WithClientOptions(opts...),
151+ ),
152+ resolveComment: connect.NewClient[ermite.ResolveCommentRequest, ermite.ResolveCommentResponse](
153+ httpClient,
154+ baseURL+MergeRequestServiceResolveCommentProcedure,
155+ connect.WithSchema(mergeRequestServiceMethods.ByName("ResolveComment")),
156+ connect.WithClientOptions(opts...),
157+ ),
158+ closeMergeRequest: connect.NewClient[ermite.CloseMergeRequestRequest, ermite.CloseMergeRequestResponse](
159+ httpClient,
160+ baseURL+MergeRequestServiceCloseMergeRequestProcedure,
161+ connect.WithSchema(mergeRequestServiceMethods.ByName("CloseMergeRequest")),
162+ connect.WithClientOptions(opts...),
163+ ),
164+ mergeMergeRequest: connect.NewClient[ermite.MergeMergeRequestRequest, ermite.MergeMergeRequestResponse](
165+ httpClient,
166+ baseURL+MergeRequestServiceMergeMergeRequestProcedure,
167+ connect.WithSchema(mergeRequestServiceMethods.ByName("MergeMergeRequest")),
168+ connect.WithClientOptions(opts...),
169+ ),
170+ markRead: connect.NewClient[ermite.MarkReadRequest, ermite.MarkReadResponse](
171+ httpClient,
172+ baseURL+MergeRequestServiceMarkReadProcedure,
173+ connect.WithSchema(mergeRequestServiceMethods.ByName("MarkRead")),
174+ connect.WithClientOptions(opts...),
175+ ),
176+ }
177+}
178+
179+// mergeRequestServiceClient implements MergeRequestServiceClient.
180+type mergeRequestServiceClient struct {
181+ createMergeRequest *connect.Client[ermite.CreateMergeRequestRequest, ermite.CreateMergeRequestResponse]
182+ listMergeRequests *connect.Client[ermite.ListMergeRequestsRequest, ermite.ListMergeRequestsResponse]
183+ getMergeRequest *connect.Client[ermite.GetMergeRequestRequest, ermite.GetMergeRequestResponse]
184+ addComment *connect.Client[ermite.AddCommentRequest, ermite.AddCommentResponse]
185+ resolveComment *connect.Client[ermite.ResolveCommentRequest, ermite.ResolveCommentResponse]
186+ closeMergeRequest *connect.Client[ermite.CloseMergeRequestRequest, ermite.CloseMergeRequestResponse]
187+ mergeMergeRequest *connect.Client[ermite.MergeMergeRequestRequest, ermite.MergeMergeRequestResponse]
188+ markRead *connect.Client[ermite.MarkReadRequest, ermite.MarkReadResponse]
189+}
190+
191+// CreateMergeRequest calls ermite.MergeRequestService.CreateMergeRequest.
192+func (c *mergeRequestServiceClient) CreateMergeRequest(ctx context.Context, req *connect.Request[ermite.CreateMergeRequestRequest]) (*connect.Response[ermite.CreateMergeRequestResponse], error) {
193+ return c.createMergeRequest.CallUnary(ctx, req)
194+}
195+
196+// ListMergeRequests calls ermite.MergeRequestService.ListMergeRequests.
197+func (c *mergeRequestServiceClient) ListMergeRequests(ctx context.Context, req *connect.Request[ermite.ListMergeRequestsRequest]) (*connect.Response[ermite.ListMergeRequestsResponse], error) {
198+ return c.listMergeRequests.CallUnary(ctx, req)
199+}
200+
201+// GetMergeRequest calls ermite.MergeRequestService.GetMergeRequest.
202+func (c *mergeRequestServiceClient) GetMergeRequest(ctx context.Context, req *connect.Request[ermite.GetMergeRequestRequest]) (*connect.Response[ermite.GetMergeRequestResponse], error) {
203+ return c.getMergeRequest.CallUnary(ctx, req)
204+}
205+
206+// AddComment calls ermite.MergeRequestService.AddComment.
207+func (c *mergeRequestServiceClient) AddComment(ctx context.Context, req *connect.Request[ermite.AddCommentRequest]) (*connect.Response[ermite.AddCommentResponse], error) {
208+ return c.addComment.CallUnary(ctx, req)
209+}
210+
211+// ResolveComment calls ermite.MergeRequestService.ResolveComment.
212+func (c *mergeRequestServiceClient) ResolveComment(ctx context.Context, req *connect.Request[ermite.ResolveCommentRequest]) (*connect.Response[ermite.ResolveCommentResponse], error) {
213+ return c.resolveComment.CallUnary(ctx, req)
214+}
215+
216+// CloseMergeRequest calls ermite.MergeRequestService.CloseMergeRequest.
217+func (c *mergeRequestServiceClient) CloseMergeRequest(ctx context.Context, req *connect.Request[ermite.CloseMergeRequestRequest]) (*connect.Response[ermite.CloseMergeRequestResponse], error) {
218+ return c.closeMergeRequest.CallUnary(ctx, req)
219+}
220+
221+// MergeMergeRequest calls ermite.MergeRequestService.MergeMergeRequest.
222+func (c *mergeRequestServiceClient) MergeMergeRequest(ctx context.Context, req *connect.Request[ermite.MergeMergeRequestRequest]) (*connect.Response[ermite.MergeMergeRequestResponse], error) {
223+ return c.mergeMergeRequest.CallUnary(ctx, req)
224+}
225+
226+// MarkRead calls ermite.MergeRequestService.MarkRead.
227+func (c *mergeRequestServiceClient) MarkRead(ctx context.Context, req *connect.Request[ermite.MarkReadRequest]) (*connect.Response[ermite.MarkReadResponse], error) {
228+ return c.markRead.CallUnary(ctx, req)
229+}
230+
231+// MergeRequestServiceHandler is an implementation of the ermite.MergeRequestService service.
232+type MergeRequestServiceHandler interface {
233+ // CreateMergeRequest opens a merge request. Needs the mr:write scope. Fails
234+ // with invalid_argument if a text rule is broken, if a branch does not exist
235+ // or if source and target are the same branch, and with already_exists if an
236+ // open merge request already has the same source and target.
237+ CreateMergeRequest(context.Context, *connect.Request[ermite.CreateMergeRequestRequest]) (*connect.Response[ermite.CreateMergeRequestResponse], error)
238+ // ListMergeRequests lists the merge requests of a repository, highest
239+ // number (newest) first, optionally restricted to one state. The listed
240+ // merge requests carry no diff or comments; head_id is set. Fails with
241+ // not_found if the repository does not exist or is not readable.
242+ ListMergeRequests(context.Context, *connect.Request[ermite.ListMergeRequestsRequest]) (*connect.Response[ermite.ListMergeRequestsResponse], error)
243+ // GetMergeRequest returns one merge request with its commits, its diff and
244+ // its comments. The diff compares the target with the head (what the merge
245+ // would bring: "target...head"), or, with since_last_read, the head the
246+ // caller last marked read with the current head. Fails with not_found if
247+ // the repository or the number is unknown.
248+ GetMergeRequest(context.Context, *connect.Request[ermite.GetMergeRequestRequest]) (*connect.Response[ermite.GetMergeRequestResponse], error)
249+ // AddComment adds a general comment or a comment on a line of the diff.
250+ // Needs the mr:write scope. Fails with invalid_argument if the text breaks
251+ // a rule or if path, line and side do not designate a line of the current
252+ // diff of the merge request, and with failed_precondition if the merge
253+ // request is not open.
254+ AddComment(context.Context, *connect.Request[ermite.AddCommentRequest]) (*connect.Response[ermite.AddCommentResponse], error)
255+ // ResolveComment marks a comment resolved or unresolved. Needs the mr:write
256+ // scope. Fails with not_found if the comment is unknown and with
257+ // failed_precondition if the merge request is not open. Setting the state
258+ // the comment already has succeeds and changes nothing.
259+ ResolveComment(context.Context, *connect.Request[ermite.ResolveCommentRequest]) (*connect.Response[ermite.ResolveCommentResponse], error)
260+ // CloseMergeRequest closes an open merge request without merging it. Needs
261+ // the mr:write scope. Fails with failed_precondition if the merge request
262+ // is not open. A closed merge request cannot be reopened: open a new one.
263+ CloseMergeRequest(context.Context, *connect.Request[ermite.CloseMergeRequestRequest]) (*connect.Response[ermite.CloseMergeRequestResponse], error)
264+ // MergeMergeRequest merges an open merge request. Needs the mr:merge scope.
265+ // The caller passes the head it reviewed: the server merges only if the
266+ // source branch still points to it, so a push between review and merge is
267+ // never merged unseen. Fails with invalid_argument if head_id is empty or
268+ // not a commit id, and with failed_precondition if the merge request is not
269+ // open, if the source branch no longer points to head_id ("branch moved,
270+ // read again"), if the target is not an ancestor of the head (rebase and
271+ // push again), if the mode is not allowed by RepoSettings, or if a check
272+ // fails and RepoSettings.failing_checks_block is set. Merges of one
273+ // repository run one at a time. Repeating a call that succeeded fails with
274+ // failed_precondition (not open). On success the server publishes
275+ // MergeRequestMerged then Pushed for the target branch.
276+ MergeMergeRequest(context.Context, *connect.Request[ermite.MergeMergeRequestRequest]) (*connect.Response[ermite.MergeMergeRequestResponse], error)
277+ // MarkRead records the head the caller has reviewed, for the since_last_read
278+ // option of GetMergeRequest. There is one read position per merge request,
279+ // the reviewer's. Needs the mr:write scope. Marking a commit that does not
280+ // exist in the repository is invalid_argument.
281+ MarkRead(context.Context, *connect.Request[ermite.MarkReadRequest]) (*connect.Response[ermite.MarkReadResponse], error)
282+}
283+
284+// NewMergeRequestServiceHandler builds an HTTP handler from the service implementation. It returns
285+// the path on which to mount the handler and the handler itself.
286+//
287+// By default, handlers support the Connect, gRPC, and gRPC-Web protocols with the binary Protobuf
288+// and JSON codecs. They also support gzip compression.
289+func NewMergeRequestServiceHandler(svc MergeRequestServiceHandler, opts ...connect.HandlerOption) (string, http.Handler) {
290+ mergeRequestServiceMethods := ermite.File_ermite_merge_request_proto.Services().ByName("MergeRequestService").Methods()
291+ mergeRequestServiceCreateMergeRequestHandler := connect.NewUnaryHandler(
292+ MergeRequestServiceCreateMergeRequestProcedure,
293+ svc.CreateMergeRequest,
294+ connect.WithSchema(mergeRequestServiceMethods.ByName("CreateMergeRequest")),
295+ connect.WithHandlerOptions(opts...),
296+ )
297+ mergeRequestServiceListMergeRequestsHandler := connect.NewUnaryHandler(
298+ MergeRequestServiceListMergeRequestsProcedure,
299+ svc.ListMergeRequests,
300+ connect.WithSchema(mergeRequestServiceMethods.ByName("ListMergeRequests")),
301+ connect.WithHandlerOptions(opts...),
302+ )
303+ mergeRequestServiceGetMergeRequestHandler := connect.NewUnaryHandler(
304+ MergeRequestServiceGetMergeRequestProcedure,
305+ svc.GetMergeRequest,
306+ connect.WithSchema(mergeRequestServiceMethods.ByName("GetMergeRequest")),
307+ connect.WithHandlerOptions(opts...),
308+ )
309+ mergeRequestServiceAddCommentHandler := connect.NewUnaryHandler(
310+ MergeRequestServiceAddCommentProcedure,
311+ svc.AddComment,
312+ connect.WithSchema(mergeRequestServiceMethods.ByName("AddComment")),
313+ connect.WithHandlerOptions(opts...),
314+ )
315+ mergeRequestServiceResolveCommentHandler := connect.NewUnaryHandler(
316+ MergeRequestServiceResolveCommentProcedure,
317+ svc.ResolveComment,
318+ connect.WithSchema(mergeRequestServiceMethods.ByName("ResolveComment")),
319+ connect.WithHandlerOptions(opts...),
320+ )
321+ mergeRequestServiceCloseMergeRequestHandler := connect.NewUnaryHandler(
322+ MergeRequestServiceCloseMergeRequestProcedure,
323+ svc.CloseMergeRequest,
324+ connect.WithSchema(mergeRequestServiceMethods.ByName("CloseMergeRequest")),
325+ connect.WithHandlerOptions(opts...),
326+ )
327+ mergeRequestServiceMergeMergeRequestHandler := connect.NewUnaryHandler(
328+ MergeRequestServiceMergeMergeRequestProcedure,
329+ svc.MergeMergeRequest,
330+ connect.WithSchema(mergeRequestServiceMethods.ByName("MergeMergeRequest")),
331+ connect.WithHandlerOptions(opts...),
332+ )
333+ mergeRequestServiceMarkReadHandler := connect.NewUnaryHandler(
334+ MergeRequestServiceMarkReadProcedure,
335+ svc.MarkRead,
336+ connect.WithSchema(mergeRequestServiceMethods.ByName("MarkRead")),
337+ connect.WithHandlerOptions(opts...),
338+ )
339+ return "/ermite.MergeRequestService/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
340+ switch r.URL.Path {
341+ case MergeRequestServiceCreateMergeRequestProcedure:
342+ mergeRequestServiceCreateMergeRequestHandler.ServeHTTP(w, r)
343+ case MergeRequestServiceListMergeRequestsProcedure:
344+ mergeRequestServiceListMergeRequestsHandler.ServeHTTP(w, r)
345+ case MergeRequestServiceGetMergeRequestProcedure:
346+ mergeRequestServiceGetMergeRequestHandler.ServeHTTP(w, r)
347+ case MergeRequestServiceAddCommentProcedure:
348+ mergeRequestServiceAddCommentHandler.ServeHTTP(w, r)
349+ case MergeRequestServiceResolveCommentProcedure:
350+ mergeRequestServiceResolveCommentHandler.ServeHTTP(w, r)
351+ case MergeRequestServiceCloseMergeRequestProcedure:
352+ mergeRequestServiceCloseMergeRequestHandler.ServeHTTP(w, r)
353+ case MergeRequestServiceMergeMergeRequestProcedure:
354+ mergeRequestServiceMergeMergeRequestHandler.ServeHTTP(w, r)
355+ case MergeRequestServiceMarkReadProcedure:
356+ mergeRequestServiceMarkReadHandler.ServeHTTP(w, r)
357+ default:
358+ http.NotFound(w, r)
359+ }
360+ })
361+}
362+
363+// UnimplementedMergeRequestServiceHandler returns CodeUnimplemented from all methods.
364+type UnimplementedMergeRequestServiceHandler struct{}
365+
366+func (UnimplementedMergeRequestServiceHandler) CreateMergeRequest(context.Context, *connect.Request[ermite.CreateMergeRequestRequest]) (*connect.Response[ermite.CreateMergeRequestResponse], error) {
367+ return nil, connect.NewError(connect.CodeUnimplemented, errors.New("ermite.MergeRequestService.CreateMergeRequest is not implemented"))
368+}
369+
370+func (UnimplementedMergeRequestServiceHandler) ListMergeRequests(context.Context, *connect.Request[ermite.ListMergeRequestsRequest]) (*connect.Response[ermite.ListMergeRequestsResponse], error) {
371+ return nil, connect.NewError(connect.CodeUnimplemented, errors.New("ermite.MergeRequestService.ListMergeRequests is not implemented"))
372+}
373+
374+func (UnimplementedMergeRequestServiceHandler) GetMergeRequest(context.Context, *connect.Request[ermite.GetMergeRequestRequest]) (*connect.Response[ermite.GetMergeRequestResponse], error) {
375+ return nil, connect.NewError(connect.CodeUnimplemented, errors.New("ermite.MergeRequestService.GetMergeRequest is not implemented"))
376+}
377+
378+func (UnimplementedMergeRequestServiceHandler) AddComment(context.Context, *connect.Request[ermite.AddCommentRequest]) (*connect.Response[ermite.AddCommentResponse], error) {
379+ return nil, connect.NewError(connect.CodeUnimplemented, errors.New("ermite.MergeRequestService.AddComment is not implemented"))
380+}
381+
382+func (UnimplementedMergeRequestServiceHandler) ResolveComment(context.Context, *connect.Request[ermite.ResolveCommentRequest]) (*connect.Response[ermite.ResolveCommentResponse], error) {
383+ return nil, connect.NewError(connect.CodeUnimplemented, errors.New("ermite.MergeRequestService.ResolveComment is not implemented"))
384+}
385+
386+func (UnimplementedMergeRequestServiceHandler) CloseMergeRequest(context.Context, *connect.Request[ermite.CloseMergeRequestRequest]) (*connect.Response[ermite.CloseMergeRequestResponse], error) {
387+ return nil, connect.NewError(connect.CodeUnimplemented, errors.New("ermite.MergeRequestService.CloseMergeRequest is not implemented"))
388+}
389+
390+func (UnimplementedMergeRequestServiceHandler) MergeMergeRequest(context.Context, *connect.Request[ermite.MergeMergeRequestRequest]) (*connect.Response[ermite.MergeMergeRequestResponse], error) {
391+ return nil, connect.NewError(connect.CodeUnimplemented, errors.New("ermite.MergeRequestService.MergeMergeRequest is not implemented"))
392+}
393+
394+func (UnimplementedMergeRequestServiceHandler) MarkRead(context.Context, *connect.Request[ermite.MarkReadRequest]) (*connect.Response[ermite.MarkReadResponse], error) {
395+ return nil, connect.NewError(connect.CodeUnimplemented, errors.New("ermite.MergeRequestService.MarkRead is not implemented"))
396+}
@@ -0,0 +1,396 @@
1+// SPDX-License-Identifier: MIT
2+
3+// Code generated by protoc-gen-connect-go. DO NOT EDIT.
4+//
5+// Source: ermite/merge_request.proto
6+
7+package ermiteconnect
8+
9+import (
10+ ermite "bnema.dev/ermite-api/gen/ermite"
11+ connect "connectrpc.com/connect"
12+ context "context"
13+ errors "errors"
14+ http "net/http"
15+ strings "strings"
16+)
17+
18+// This is a compile-time assertion to ensure that this generated file and the connect package are
19+// compatible. If you get a compiler error that this constant is not defined, this code was
20+// generated with a version of connect newer than the one compiled into your binary. You can fix the
21+// problem by either regenerating this code with an older version of connect or updating the connect
22+// version compiled into your binary.
23+const _ = connect.IsAtLeastVersion1_13_0
24+
25+const (
26+ // MergeRequestServiceName is the fully-qualified name of the MergeRequestService service.
27+ MergeRequestServiceName = "ermite.MergeRequestService"
28+)
29+
30+// These constants are the fully-qualified names of the RPCs defined in this package. They're
31+// exposed at runtime as Spec.Procedure and as the final two segments of the HTTP route.
32+//
33+// Note that these are different from the fully-qualified method names used by
34+// google.golang.org/protobuf/reflect/protoreflect. To convert from these constants to
35+// reflection-formatted method names, remove the leading slash and convert the remaining slash to a
36+// period.
37+const (
38+ // MergeRequestServiceCreateMergeRequestProcedure is the fully-qualified name of the
39+ // MergeRequestService's CreateMergeRequest RPC.
40+ MergeRequestServiceCreateMergeRequestProcedure = "/ermite.MergeRequestService/CreateMergeRequest"
41+ // MergeRequestServiceListMergeRequestsProcedure is the fully-qualified name of the
42+ // MergeRequestService's ListMergeRequests RPC.
43+ MergeRequestServiceListMergeRequestsProcedure = "/ermite.MergeRequestService/ListMergeRequests"
44+ // MergeRequestServiceGetMergeRequestProcedure is the fully-qualified name of the
45+ // MergeRequestService's GetMergeRequest RPC.
46+ MergeRequestServiceGetMergeRequestProcedure = "/ermite.MergeRequestService/GetMergeRequest"
47+ // MergeRequestServiceAddCommentProcedure is the fully-qualified name of the MergeRequestService's
48+ // AddComment RPC.
49+ MergeRequestServiceAddCommentProcedure = "/ermite.MergeRequestService/AddComment"
50+ // MergeRequestServiceResolveCommentProcedure is the fully-qualified name of the
51+ // MergeRequestService's ResolveComment RPC.
52+ MergeRequestServiceResolveCommentProcedure = "/ermite.MergeRequestService/ResolveComment"
53+ // MergeRequestServiceCloseMergeRequestProcedure is the fully-qualified name of the
54+ // MergeRequestService's CloseMergeRequest RPC.
55+ MergeRequestServiceCloseMergeRequestProcedure = "/ermite.MergeRequestService/CloseMergeRequest"
56+ // MergeRequestServiceMergeMergeRequestProcedure is the fully-qualified name of the
57+ // MergeRequestService's MergeMergeRequest RPC.
58+ MergeRequestServiceMergeMergeRequestProcedure = "/ermite.MergeRequestService/MergeMergeRequest"
59+ // MergeRequestServiceMarkReadProcedure is the fully-qualified name of the MergeRequestService's
60+ // MarkRead RPC.
61+ MergeRequestServiceMarkReadProcedure = "/ermite.MergeRequestService/MarkRead"
62+)
63+
64+// MergeRequestServiceClient is a client for the ermite.MergeRequestService service.
65+type MergeRequestServiceClient interface {
66+ // CreateMergeRequest opens a merge request. Needs the mr:write scope. Fails
67+ // with invalid_argument if a text rule is broken, if a branch does not exist
68+ // or if source and target are the same branch, and with already_exists if an
69+ // open merge request already has the same source and target.
70+ CreateMergeRequest(context.Context, *connect.Request[ermite.CreateMergeRequestRequest]) (*connect.Response[ermite.CreateMergeRequestResponse], error)
71+ // ListMergeRequests lists the merge requests of a repository, highest
72+ // number (newest) first, optionally restricted to one state. The listed
73+ // merge requests carry no diff or comments; head_id is set. Fails with
74+ // not_found if the repository does not exist or is not readable.
75+ ListMergeRequests(context.Context, *connect.Request[ermite.ListMergeRequestsRequest]) (*connect.Response[ermite.ListMergeRequestsResponse], error)
76+ // GetMergeRequest returns one merge request with its commits, its diff and
77+ // its comments. The diff compares the target with the head (what the merge
78+ // would bring: "target...head"), or, with since_last_read, the head the
79+ // caller last marked read with the current head. Fails with not_found if
80+ // the repository or the number is unknown.
81+ GetMergeRequest(context.Context, *connect.Request[ermite.GetMergeRequestRequest]) (*connect.Response[ermite.GetMergeRequestResponse], error)
82+ // AddComment adds a general comment or a comment on a line of the diff.
83+ // Needs the mr:write scope. Fails with invalid_argument if the text breaks
84+ // a rule or if path, line and side do not designate a line of the current
85+ // diff of the merge request, and with failed_precondition if the merge
86+ // request is not open.
87+ AddComment(context.Context, *connect.Request[ermite.AddCommentRequest]) (*connect.Response[ermite.AddCommentResponse], error)
88+ // ResolveComment marks a comment resolved or unresolved. Needs the mr:write
89+ // scope. Fails with not_found if the comment is unknown and with
90+ // failed_precondition if the merge request is not open. Setting the state
91+ // the comment already has succeeds and changes nothing.
92+ ResolveComment(context.Context, *connect.Request[ermite.ResolveCommentRequest]) (*connect.Response[ermite.ResolveCommentResponse], error)
93+ // CloseMergeRequest closes an open merge request without merging it. Needs
94+ // the mr:write scope. Fails with failed_precondition if the merge request
95+ // is not open. A closed merge request cannot be reopened: open a new one.
96+ CloseMergeRequest(context.Context, *connect.Request[ermite.CloseMergeRequestRequest]) (*connect.Response[ermite.CloseMergeRequestResponse], error)
97+ // MergeMergeRequest merges an open merge request. Needs the mr:merge scope.
98+ // The caller passes the head it reviewed: the server merges only if the
99+ // source branch still points to it, so a push between review and merge is
100+ // never merged unseen. Fails with invalid_argument if head_id is empty or
101+ // not a commit id, and with failed_precondition if the merge request is not
102+ // open, if the source branch no longer points to head_id ("branch moved,
103+ // read again"), if the target is not an ancestor of the head (rebase and
104+ // push again), if the mode is not allowed by RepoSettings, or if a check
105+ // fails and RepoSettings.failing_checks_block is set. Merges of one
106+ // repository run one at a time. Repeating a call that succeeded fails with
107+ // failed_precondition (not open). On success the server publishes
108+ // MergeRequestMerged then Pushed for the target branch.
109+ MergeMergeRequest(context.Context, *connect.Request[ermite.MergeMergeRequestRequest]) (*connect.Response[ermite.MergeMergeRequestResponse], error)
110+ // MarkRead records the head the caller has reviewed, for the since_last_read
111+ // option of GetMergeRequest. There is one read position per merge request,
112+ // the reviewer's. Needs the mr:write scope. Marking a commit that does not
113+ // exist in the repository is invalid_argument.
114+ MarkRead(context.Context, *connect.Request[ermite.MarkReadRequest]) (*connect.Response[ermite.MarkReadResponse], error)
115+}
116+
117+// NewMergeRequestServiceClient constructs a client for the ermite.MergeRequestService service. By
118+// default, it uses the Connect protocol with the binary Protobuf Codec, asks for gzipped responses,
119+// and sends uncompressed requests. To use the gRPC or gRPC-Web protocols, supply the
120+// connect.WithGRPC() or connect.WithGRPCWeb() options.
121+//
122+// The URL supplied here should be the base URL for the Connect or gRPC server (for example,
123+// http://api.acme.com or https://acme.com/grpc).
124+func NewMergeRequestServiceClient(httpClient connect.HTTPClient, baseURL string, opts ...connect.ClientOption) MergeRequestServiceClient {
125+ baseURL = strings.TrimRight(baseURL, "/")
126+ mergeRequestServiceMethods := ermite.File_ermite_merge_request_proto.Services().ByName("MergeRequestService").Methods()
127+ return &mergeRequestServiceClient{
128+ createMergeRequest: connect.NewClient[ermite.CreateMergeRequestRequest, ermite.CreateMergeRequestResponse](
129+ httpClient,
130+ baseURL+MergeRequestServiceCreateMergeRequestProcedure,
131+ connect.WithSchema(mergeRequestServiceMethods.ByName("CreateMergeRequest")),
132+ connect.WithClientOptions(opts...),
133+ ),
134+ listMergeRequests: connect.NewClient[ermite.ListMergeRequestsRequest, ermite.ListMergeRequestsResponse](
135+ httpClient,
136+ baseURL+MergeRequestServiceListMergeRequestsProcedure,
137+ connect.WithSchema(mergeRequestServiceMethods.ByName("ListMergeRequests")),
138+ connect.WithClientOptions(opts...),
139+ ),
140+ getMergeRequest: connect.NewClient[ermite.GetMergeRequestRequest, ermite.GetMergeRequestResponse](
141+ httpClient,
142+ baseURL+MergeRequestServiceGetMergeRequestProcedure,
143+ connect.WithSchema(mergeRequestServiceMethods.ByName("GetMergeRequest")),
144+ connect.WithClientOptions(opts...),
145+ ),
146+ addComment: connect.NewClient[ermite.AddCommentRequest, ermite.AddCommentResponse](
147+ httpClient,
148+ baseURL+MergeRequestServiceAddCommentProcedure,
149+ connect.WithSchema(mergeRequestServiceMethods.ByName("AddComment")),
150+ connect.WithClientOptions(opts...),
151+ ),
152+ resolveComment: connect.NewClient[ermite.ResolveCommentRequest, ermite.ResolveCommentResponse](
153+ httpClient,
154+ baseURL+MergeRequestServiceResolveCommentProcedure,
155+ connect.WithSchema(mergeRequestServiceMethods.ByName("ResolveComment")),
156+ connect.WithClientOptions(opts...),
157+ ),
158+ closeMergeRequest: connect.NewClient[ermite.CloseMergeRequestRequest, ermite.CloseMergeRequestResponse](
159+ httpClient,
160+ baseURL+MergeRequestServiceCloseMergeRequestProcedure,
161+ connect.WithSchema(mergeRequestServiceMethods.ByName("CloseMergeRequest")),
162+ connect.WithClientOptions(opts...),
163+ ),
164+ mergeMergeRequest: connect.NewClient[ermite.MergeMergeRequestRequest, ermite.MergeMergeRequestResponse](
165+ httpClient,
166+ baseURL+MergeRequestServiceMergeMergeRequestProcedure,
167+ connect.WithSchema(mergeRequestServiceMethods.ByName("MergeMergeRequest")),
168+ connect.WithClientOptions(opts...),
169+ ),
170+ markRead: connect.NewClient[ermite.MarkReadRequest, ermite.MarkReadResponse](
171+ httpClient,
172+ baseURL+MergeRequestServiceMarkReadProcedure,
173+ connect.WithSchema(mergeRequestServiceMethods.ByName("MarkRead")),
174+ connect.WithClientOptions(opts...),
175+ ),
176+ }
177+}
178+
179+// mergeRequestServiceClient implements MergeRequestServiceClient.
180+type mergeRequestServiceClient struct {
181+ createMergeRequest *connect.Client[ermite.CreateMergeRequestRequest, ermite.CreateMergeRequestResponse]
182+ listMergeRequests *connect.Client[ermite.ListMergeRequestsRequest, ermite.ListMergeRequestsResponse]
183+ getMergeRequest *connect.Client[ermite.GetMergeRequestRequest, ermite.GetMergeRequestResponse]
184+ addComment *connect.Client[ermite.AddCommentRequest, ermite.AddCommentResponse]
185+ resolveComment *connect.Client[ermite.ResolveCommentRequest, ermite.ResolveCommentResponse]
186+ closeMergeRequest *connect.Client[ermite.CloseMergeRequestRequest, ermite.CloseMergeRequestResponse]
187+ mergeMergeRequest *connect.Client[ermite.MergeMergeRequestRequest, ermite.MergeMergeRequestResponse]
188+ markRead *connect.Client[ermite.MarkReadRequest, ermite.MarkReadResponse]
189+}
190+
191+// CreateMergeRequest calls ermite.MergeRequestService.CreateMergeRequest.
192+func (c *mergeRequestServiceClient) CreateMergeRequest(ctx context.Context, req *connect.Request[ermite.CreateMergeRequestRequest]) (*connect.Response[ermite.CreateMergeRequestResponse], error) {
193+ return c.createMergeRequest.CallUnary(ctx, req)
194+}
195+
196+// ListMergeRequests calls ermite.MergeRequestService.ListMergeRequests.
197+func (c *mergeRequestServiceClient) ListMergeRequests(ctx context.Context, req *connect.Request[ermite.ListMergeRequestsRequest]) (*connect.Response[ermite.ListMergeRequestsResponse], error) {
198+ return c.listMergeRequests.CallUnary(ctx, req)
199+}
200+
201+// GetMergeRequest calls ermite.MergeRequestService.GetMergeRequest.
202+func (c *mergeRequestServiceClient) GetMergeRequest(ctx context.Context, req *connect.Request[ermite.GetMergeRequestRequest]) (*connect.Response[ermite.GetMergeRequestResponse], error) {
203+ return c.getMergeRequest.CallUnary(ctx, req)
204+}
205+
206+// AddComment calls ermite.MergeRequestService.AddComment.
207+func (c *mergeRequestServiceClient) AddComment(ctx context.Context, req *connect.Request[ermite.AddCommentRequest]) (*connect.Response[ermite.AddCommentResponse], error) {
208+ return c.addComment.CallUnary(ctx, req)
209+}
210+
211+// ResolveComment calls ermite.MergeRequestService.ResolveComment.
212+func (c *mergeRequestServiceClient) ResolveComment(ctx context.Context, req *connect.Request[ermite.ResolveCommentRequest]) (*connect.Response[ermite.ResolveCommentResponse], error) {
213+ return c.resolveComment.CallUnary(ctx, req)
214+}
215+
216+// CloseMergeRequest calls ermite.MergeRequestService.CloseMergeRequest.
217+func (c *mergeRequestServiceClient) CloseMergeRequest(ctx context.Context, req *connect.Request[ermite.CloseMergeRequestRequest]) (*connect.Response[ermite.CloseMergeRequestResponse], error) {
218+ return c.closeMergeRequest.CallUnary(ctx, req)
219+}
220+
221+// MergeMergeRequest calls ermite.MergeRequestService.MergeMergeRequest.
222+func (c *mergeRequestServiceClient) MergeMergeRequest(ctx context.Context, req *connect.Request[ermite.MergeMergeRequestRequest]) (*connect.Response[ermite.MergeMergeRequestResponse], error) {
223+ return c.mergeMergeRequest.CallUnary(ctx, req)
224+}
225+
226+// MarkRead calls ermite.MergeRequestService.MarkRead.
227+func (c *mergeRequestServiceClient) MarkRead(ctx context.Context, req *connect.Request[ermite.MarkReadRequest]) (*connect.Response[ermite.MarkReadResponse], error) {
228+ return c.markRead.CallUnary(ctx, req)
229+}
230+
231+// MergeRequestServiceHandler is an implementation of the ermite.MergeRequestService service.
232+type MergeRequestServiceHandler interface {
233+ // CreateMergeRequest opens a merge request. Needs the mr:write scope. Fails
234+ // with invalid_argument if a text rule is broken, if a branch does not exist
235+ // or if source and target are the same branch, and with already_exists if an
236+ // open merge request already has the same source and target.
237+ CreateMergeRequest(context.Context, *connect.Request[ermite.CreateMergeRequestRequest]) (*connect.Response[ermite.CreateMergeRequestResponse], error)
238+ // ListMergeRequests lists the merge requests of a repository, highest
239+ // number (newest) first, optionally restricted to one state. The listed
240+ // merge requests carry no diff or comments; head_id is set. Fails with
241+ // not_found if the repository does not exist or is not readable.
242+ ListMergeRequests(context.Context, *connect.Request[ermite.ListMergeRequestsRequest]) (*connect.Response[ermite.ListMergeRequestsResponse], error)
243+ // GetMergeRequest returns one merge request with its commits, its diff and
244+ // its comments. The diff compares the target with the head (what the merge
245+ // would bring: "target...head"), or, with since_last_read, the head the
246+ // caller last marked read with the current head. Fails with not_found if
247+ // the repository or the number is unknown.
248+ GetMergeRequest(context.Context, *connect.Request[ermite.GetMergeRequestRequest]) (*connect.Response[ermite.GetMergeRequestResponse], error)
249+ // AddComment adds a general comment or a comment on a line of the diff.
250+ // Needs the mr:write scope. Fails with invalid_argument if the text breaks
251+ // a rule or if path, line and side do not designate a line of the current
252+ // diff of the merge request, and with failed_precondition if the merge
253+ // request is not open.
254+ AddComment(context.Context, *connect.Request[ermite.AddCommentRequest]) (*connect.Response[ermite.AddCommentResponse], error)
255+ // ResolveComment marks a comment resolved or unresolved. Needs the mr:write
256+ // scope. Fails with not_found if the comment is unknown and with
257+ // failed_precondition if the merge request is not open. Setting the state
258+ // the comment already has succeeds and changes nothing.
259+ ResolveComment(context.Context, *connect.Request[ermite.ResolveCommentRequest]) (*connect.Response[ermite.ResolveCommentResponse], error)
260+ // CloseMergeRequest closes an open merge request without merging it. Needs
261+ // the mr:write scope. Fails with failed_precondition if the merge request
262+ // is not open. A closed merge request cannot be reopened: open a new one.
263+ CloseMergeRequest(context.Context, *connect.Request[ermite.CloseMergeRequestRequest]) (*connect.Response[ermite.CloseMergeRequestResponse], error)
264+ // MergeMergeRequest merges an open merge request. Needs the mr:merge scope.
265+ // The caller passes the head it reviewed: the server merges only if the
266+ // source branch still points to it, so a push between review and merge is
267+ // never merged unseen. Fails with invalid_argument if head_id is empty or
268+ // not a commit id, and with failed_precondition if the merge request is not
269+ // open, if the source branch no longer points to head_id ("branch moved,
270+ // read again"), if the target is not an ancestor of the head (rebase and
271+ // push again), if the mode is not allowed by RepoSettings, or if a check
272+ // fails and RepoSettings.failing_checks_block is set. Merges of one
273+ // repository run one at a time. Repeating a call that succeeded fails with
274+ // failed_precondition (not open). On success the server publishes
275+ // MergeRequestMerged then Pushed for the target branch.
276+ MergeMergeRequest(context.Context, *connect.Request[ermite.MergeMergeRequestRequest]) (*connect.Response[ermite.MergeMergeRequestResponse], error)
277+ // MarkRead records the head the caller has reviewed, for the since_last_read
278+ // option of GetMergeRequest. There is one read position per merge request,
279+ // the reviewer's. Needs the mr:write scope. Marking a commit that does not
280+ // exist in the repository is invalid_argument.
281+ MarkRead(context.Context, *connect.Request[ermite.MarkReadRequest]) (*connect.Response[ermite.MarkReadResponse], error)
282+}
283+
284+// NewMergeRequestServiceHandler builds an HTTP handler from the service implementation. It returns
285+// the path on which to mount the handler and the handler itself.
286+//
287+// By default, handlers support the Connect, gRPC, and gRPC-Web protocols with the binary Protobuf
288+// and JSON codecs. They also support gzip compression.
289+func NewMergeRequestServiceHandler(svc MergeRequestServiceHandler, opts ...connect.HandlerOption) (string, http.Handler) {
290+ mergeRequestServiceMethods := ermite.File_ermite_merge_request_proto.Services().ByName("MergeRequestService").Methods()
291+ mergeRequestServiceCreateMergeRequestHandler := connect.NewUnaryHandler(
292+ MergeRequestServiceCreateMergeRequestProcedure,
293+ svc.CreateMergeRequest,
294+ connect.WithSchema(mergeRequestServiceMethods.ByName("CreateMergeRequest")),
295+ connect.WithHandlerOptions(opts...),
296+ )
297+ mergeRequestServiceListMergeRequestsHandler := connect.NewUnaryHandler(
298+ MergeRequestServiceListMergeRequestsProcedure,
299+ svc.ListMergeRequests,
300+ connect.WithSchema(mergeRequestServiceMethods.ByName("ListMergeRequests")),
301+ connect.WithHandlerOptions(opts...),
302+ )
303+ mergeRequestServiceGetMergeRequestHandler := connect.NewUnaryHandler(
304+ MergeRequestServiceGetMergeRequestProcedure,
305+ svc.GetMergeRequest,
306+ connect.WithSchema(mergeRequestServiceMethods.ByName("GetMergeRequest")),
307+ connect.WithHandlerOptions(opts...),
308+ )
309+ mergeRequestServiceAddCommentHandler := connect.NewUnaryHandler(
310+ MergeRequestServiceAddCommentProcedure,
311+ svc.AddComment,
312+ connect.WithSchema(mergeRequestServiceMethods.ByName("AddComment")),
313+ connect.WithHandlerOptions(opts...),
314+ )
315+ mergeRequestServiceResolveCommentHandler := connect.NewUnaryHandler(
316+ MergeRequestServiceResolveCommentProcedure,
317+ svc.ResolveComment,
318+ connect.WithSchema(mergeRequestServiceMethods.ByName("ResolveComment")),
319+ connect.WithHandlerOptions(opts...),
320+ )
321+ mergeRequestServiceCloseMergeRequestHandler := connect.NewUnaryHandler(
322+ MergeRequestServiceCloseMergeRequestProcedure,
323+ svc.CloseMergeRequest,
324+ connect.WithSchema(mergeRequestServiceMethods.ByName("CloseMergeRequest")),
325+ connect.WithHandlerOptions(opts...),
326+ )
327+ mergeRequestServiceMergeMergeRequestHandler := connect.NewUnaryHandler(
328+ MergeRequestServiceMergeMergeRequestProcedure,
329+ svc.MergeMergeRequest,
330+ connect.WithSchema(mergeRequestServiceMethods.ByName("MergeMergeRequest")),
331+ connect.WithHandlerOptions(opts...),
332+ )
333+ mergeRequestServiceMarkReadHandler := connect.NewUnaryHandler(
334+ MergeRequestServiceMarkReadProcedure,
335+ svc.MarkRead,
336+ connect.WithSchema(mergeRequestServiceMethods.ByName("MarkRead")),
337+ connect.WithHandlerOptions(opts...),
338+ )
339+ return "/ermite.MergeRequestService/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
340+ switch r.URL.Path {
341+ case MergeRequestServiceCreateMergeRequestProcedure:
342+ mergeRequestServiceCreateMergeRequestHandler.ServeHTTP(w, r)
343+ case MergeRequestServiceListMergeRequestsProcedure:
344+ mergeRequestServiceListMergeRequestsHandler.ServeHTTP(w, r)
345+ case MergeRequestServiceGetMergeRequestProcedure:
346+ mergeRequestServiceGetMergeRequestHandler.ServeHTTP(w, r)
347+ case MergeRequestServiceAddCommentProcedure:
348+ mergeRequestServiceAddCommentHandler.ServeHTTP(w, r)
349+ case MergeRequestServiceResolveCommentProcedure:
350+ mergeRequestServiceResolveCommentHandler.ServeHTTP(w, r)
351+ case MergeRequestServiceCloseMergeRequestProcedure:
352+ mergeRequestServiceCloseMergeRequestHandler.ServeHTTP(w, r)
353+ case MergeRequestServiceMergeMergeRequestProcedure:
354+ mergeRequestServiceMergeMergeRequestHandler.ServeHTTP(w, r)
355+ case MergeRequestServiceMarkReadProcedure:
356+ mergeRequestServiceMarkReadHandler.ServeHTTP(w, r)
357+ default:
358+ http.NotFound(w, r)
359+ }
360+ })
361+}
362+
363+// UnimplementedMergeRequestServiceHandler returns CodeUnimplemented from all methods.
364+type UnimplementedMergeRequestServiceHandler struct{}
365+
366+func (UnimplementedMergeRequestServiceHandler) CreateMergeRequest(context.Context, *connect.Request[ermite.CreateMergeRequestRequest]) (*connect.Response[ermite.CreateMergeRequestResponse], error) {
367+ return nil, connect.NewError(connect.CodeUnimplemented, errors.New("ermite.MergeRequestService.CreateMergeRequest is not implemented"))
368+}
369+
370+func (UnimplementedMergeRequestServiceHandler) ListMergeRequests(context.Context, *connect.Request[ermite.ListMergeRequestsRequest]) (*connect.Response[ermite.ListMergeRequestsResponse], error) {
371+ return nil, connect.NewError(connect.CodeUnimplemented, errors.New("ermite.MergeRequestService.ListMergeRequests is not implemented"))
372+}
373+
374+func (UnimplementedMergeRequestServiceHandler) GetMergeRequest(context.Context, *connect.Request[ermite.GetMergeRequestRequest]) (*connect.Response[ermite.GetMergeRequestResponse], error) {
375+ return nil, connect.NewError(connect.CodeUnimplemented, errors.New("ermite.MergeRequestService.GetMergeRequest is not implemented"))
376+}
377+
378+func (UnimplementedMergeRequestServiceHandler) AddComment(context.Context, *connect.Request[ermite.AddCommentRequest]) (*connect.Response[ermite.AddCommentResponse], error) {
379+ return nil, connect.NewError(connect.CodeUnimplemented, errors.New("ermite.MergeRequestService.AddComment is not implemented"))
380+}
381+
382+func (UnimplementedMergeRequestServiceHandler) ResolveComment(context.Context, *connect.Request[ermite.ResolveCommentRequest]) (*connect.Response[ermite.ResolveCommentResponse], error) {
383+ return nil, connect.NewError(connect.CodeUnimplemented, errors.New("ermite.MergeRequestService.ResolveComment is not implemented"))
384+}
385+
386+func (UnimplementedMergeRequestServiceHandler) CloseMergeRequest(context.Context, *connect.Request[ermite.CloseMergeRequestRequest]) (*connect.Response[ermite.CloseMergeRequestResponse], error) {
387+ return nil, connect.NewError(connect.CodeUnimplemented, errors.New("ermite.MergeRequestService.CloseMergeRequest is not implemented"))
388+}
389+
390+func (UnimplementedMergeRequestServiceHandler) MergeMergeRequest(context.Context, *connect.Request[ermite.MergeMergeRequestRequest]) (*connect.Response[ermite.MergeMergeRequestResponse], error) {
391+ return nil, connect.NewError(connect.CodeUnimplemented, errors.New("ermite.MergeRequestService.MergeMergeRequest is not implemented"))
392+}
393+
394+func (UnimplementedMergeRequestServiceHandler) MarkRead(context.Context, *connect.Request[ermite.MarkReadRequest]) (*connect.Response[ermite.MarkReadResponse], error) {
395+ return nil, connect.NewError(connect.CodeUnimplemented, errors.New("ermite.MergeRequestService.MarkRead is not implemented"))
396+}
M gen/ermite/ermiteconnect/repo.connect.go +8 -6
@@ -54,12 +54,13 @@ type RepoServiceClient interface {
5454 // GetRepo returns one repository. Fails with not_found if it does not exist
5555 // or is private and not readable by the caller.
5656 GetRepo(context.Context, *connect.Request[ermite.GetRepoRequest]) (*connect.Response[ermite.GetRepoResponse], error)
57- // CreateRepo creates an empty repository. Needs the repo:write scope. Fails
57+ // CreateRepo creates an empty repository with the default settings. Needs
58+ // the repo:write scope. Fails
5859 // with already_exists if the name is taken and invalid_argument if the name
5960 // is not valid.
6061 CreateRepo(context.Context, *connect.Request[ermite.CreateRepoRequest]) (*connect.Response[ermite.CreateRepoResponse], error)
61- // UpdateRepo changes the fields named in update_mask. Needs the repo:admin
62- // scope.
62+ // UpdateRepo changes the fields named in update_mask, including the merge
63+ // settings. Needs the repo:admin scope.
6364 UpdateRepo(context.Context, *connect.Request[ermite.UpdateRepoRequest]) (*connect.Response[ermite.UpdateRepoResponse], error)
6465 // DeleteRepo deletes a repository. Needs the repo:admin scope. The server
6566 // moves the data aside rather than destroying it; there is no undelete RPC.
@@ -151,12 +152,13 @@ type RepoServiceHandler interface {
151152 // GetRepo returns one repository. Fails with not_found if it does not exist
152153 // or is private and not readable by the caller.
153154 GetRepo(context.Context, *connect.Request[ermite.GetRepoRequest]) (*connect.Response[ermite.GetRepoResponse], error)
154- // CreateRepo creates an empty repository. Needs the repo:write scope. Fails
155+ // CreateRepo creates an empty repository with the default settings. Needs
156+ // the repo:write scope. Fails
155157 // with already_exists if the name is taken and invalid_argument if the name
156158 // is not valid.
157159 CreateRepo(context.Context, *connect.Request[ermite.CreateRepoRequest]) (*connect.Response[ermite.CreateRepoResponse], error)
158- // UpdateRepo changes the fields named in update_mask. Needs the repo:admin
159- // scope.
160+ // UpdateRepo changes the fields named in update_mask, including the merge
161+ // settings. Needs the repo:admin scope.
160162 UpdateRepo(context.Context, *connect.Request[ermite.UpdateRepoRequest]) (*connect.Response[ermite.UpdateRepoResponse], error)
161163 // DeleteRepo deletes a repository. Needs the repo:admin scope. The server
162164 // moves the data aside rather than destroying it; there is no undelete RPC.
@@ -54,12 +54,13 @@ type RepoServiceClient interface {
54 // GetRepo returns one repository. Fails with not_found if it does not exist54 // GetRepo returns one repository. Fails with not_found if it does not exist
55 // or is private and not readable by the caller.55 // or is private and not readable by the caller.
56 GetRepo(context.Context, *connect.Request[ermite.GetRepoRequest]) (*connect.Response[ermite.GetRepoResponse], error)56 GetRepo(context.Context, *connect.Request[ermite.GetRepoRequest]) (*connect.Response[ermite.GetRepoResponse], error)
57- // CreateRepo creates an empty repository. Needs the repo:write scope. Fails57+ // CreateRepo creates an empty repository with the default settings. Needs
58+ // the repo:write scope. Fails
58 // with already_exists if the name is taken and invalid_argument if the name59 // with already_exists if the name is taken and invalid_argument if the name
59 // is not valid.60 // is not valid.
60 CreateRepo(context.Context, *connect.Request[ermite.CreateRepoRequest]) (*connect.Response[ermite.CreateRepoResponse], error)61 CreateRepo(context.Context, *connect.Request[ermite.CreateRepoRequest]) (*connect.Response[ermite.CreateRepoResponse], error)
61- // UpdateRepo changes the fields named in update_mask. Needs the repo:admin62+ // UpdateRepo changes the fields named in update_mask, including the merge
62- // scope.63+ // settings. Needs the repo:admin scope.
63 UpdateRepo(context.Context, *connect.Request[ermite.UpdateRepoRequest]) (*connect.Response[ermite.UpdateRepoResponse], error)64 UpdateRepo(context.Context, *connect.Request[ermite.UpdateRepoRequest]) (*connect.Response[ermite.UpdateRepoResponse], error)
64 // DeleteRepo deletes a repository. Needs the repo:admin scope. The server65 // DeleteRepo deletes a repository. Needs the repo:admin scope. The server
65 // moves the data aside rather than destroying it; there is no undelete RPC.66 // moves the data aside rather than destroying it; there is no undelete RPC.
@@ -151,12 +152,13 @@ type RepoServiceHandler interface {
151 // GetRepo returns one repository. Fails with not_found if it does not exist152 // GetRepo returns one repository. Fails with not_found if it does not exist
152 // or is private and not readable by the caller.153 // or is private and not readable by the caller.
153 GetRepo(context.Context, *connect.Request[ermite.GetRepoRequest]) (*connect.Response[ermite.GetRepoResponse], error)154 GetRepo(context.Context, *connect.Request[ermite.GetRepoRequest]) (*connect.Response[ermite.GetRepoResponse], error)
154- // CreateRepo creates an empty repository. Needs the repo:write scope. Fails155+ // CreateRepo creates an empty repository with the default settings. Needs
156+ // the repo:write scope. Fails
155 // with already_exists if the name is taken and invalid_argument if the name157 // with already_exists if the name is taken and invalid_argument if the name
156 // is not valid.158 // is not valid.
157 CreateRepo(context.Context, *connect.Request[ermite.CreateRepoRequest]) (*connect.Response[ermite.CreateRepoResponse], error)159 CreateRepo(context.Context, *connect.Request[ermite.CreateRepoRequest]) (*connect.Response[ermite.CreateRepoResponse], error)
158- // UpdateRepo changes the fields named in update_mask. Needs the repo:admin160+ // UpdateRepo changes the fields named in update_mask, including the merge
159- // scope.161+ // settings. Needs the repo:admin scope.
160 UpdateRepo(context.Context, *connect.Request[ermite.UpdateRepoRequest]) (*connect.Response[ermite.UpdateRepoResponse], error)162 UpdateRepo(context.Context, *connect.Request[ermite.UpdateRepoRequest]) (*connect.Response[ermite.UpdateRepoResponse], error)
161 // DeleteRepo deletes a repository. Needs the repo:admin scope. The server163 // DeleteRepo deletes a repository. Needs the repo:admin scope. The server
162 // moves the data aside rather than destroying it; there is no undelete RPC.164 // moves the data aside rather than destroying it; there is no undelete RPC.
M gen/ermite/ermiteconnect/server.connect.go +40 -1
@@ -38,6 +38,9 @@ const (
3838 // ServerServiceGetVersionProcedure is the fully-qualified name of the ServerService's GetVersion
3939 // RPC.
4040 ServerServiceGetVersionProcedure = "/ermite.ServerService/GetVersion"
41+ // ServerServiceGetSigningKeyProcedure is the fully-qualified name of the ServerService's
42+ // GetSigningKey RPC.
43+ ServerServiceGetSigningKeyProcedure = "/ermite.ServerService/GetSigningKey"
4144 )
4245
4346 // ServerServiceClient is a client for the ermite.ServerService service.
@@ -46,6 +49,12 @@ type ServerServiceClient interface {
4649 // contract it implements. It needs no authentication, so a client can call
4750 // it first to check compatibility.
4851 GetVersion(context.Context, *connect.Request[ermite.GetVersionRequest]) (*connect.Response[ermite.GetVersionResponse], error)
52+ // GetSigningKey returns the public key with which the server signs the
53+ // commits it creates (squash merges, see MergeMode). It needs no
54+ // authentication: the key is public, and anyone verifying a commit needs it.
55+ // Servers that implement it list "signing_key" in
56+ // GetVersionResponse.features.
57+ GetSigningKey(context.Context, *connect.Request[ermite.GetSigningKeyRequest]) (*connect.Response[ermite.GetSigningKeyResponse], error)
4958 }
5059
5160 // NewServerServiceClient constructs a client for the ermite.ServerService service. By default, it
@@ -65,12 +74,19 @@ func NewServerServiceClient(httpClient connect.HTTPClient, baseURL string, opts
6574 connect.WithSchema(serverServiceMethods.ByName("GetVersion")),
6675 connect.WithClientOptions(opts...),
6776 ),
77+ getSigningKey: connect.NewClient[ermite.GetSigningKeyRequest, ermite.GetSigningKeyResponse](
78+ httpClient,
79+ baseURL+ServerServiceGetSigningKeyProcedure,
80+ connect.WithSchema(serverServiceMethods.ByName("GetSigningKey")),
81+ connect.WithClientOptions(opts...),
82+ ),
6883 }
6984 }
7085
7186 // serverServiceClient implements ServerServiceClient.
7287 type serverServiceClient struct {
73- getVersion *connect.Client[ermite.GetVersionRequest, ermite.GetVersionResponse]
88+ getVersion *connect.Client[ermite.GetVersionRequest, ermite.GetVersionResponse]
89+ getSigningKey *connect.Client[ermite.GetSigningKeyRequest, ermite.GetSigningKeyResponse]
7490 }
7591
7692 // GetVersion calls ermite.ServerService.GetVersion.
@@ -78,12 +94,23 @@ func (c *serverServiceClient) GetVersion(ctx context.Context, req *connect.Reque
7894 return c.getVersion.CallUnary(ctx, req)
7995 }
8096
97+// GetSigningKey calls ermite.ServerService.GetSigningKey.
98+func (c *serverServiceClient) GetSigningKey(ctx context.Context, req *connect.Request[ermite.GetSigningKeyRequest]) (*connect.Response[ermite.GetSigningKeyResponse], error) {
99+ return c.getSigningKey.CallUnary(ctx, req)
100+}
101+
81102 // ServerServiceHandler is an implementation of the ermite.ServerService service.
82103 type ServerServiceHandler interface {
83104 // GetVersion returns the server version and the optional capabilities of the
84105 // contract it implements. It needs no authentication, so a client can call
85106 // it first to check compatibility.
86107 GetVersion(context.Context, *connect.Request[ermite.GetVersionRequest]) (*connect.Response[ermite.GetVersionResponse], error)
108+ // GetSigningKey returns the public key with which the server signs the
109+ // commits it creates (squash merges, see MergeMode). It needs no
110+ // authentication: the key is public, and anyone verifying a commit needs it.
111+ // Servers that implement it list "signing_key" in
112+ // GetVersionResponse.features.
113+ GetSigningKey(context.Context, *connect.Request[ermite.GetSigningKeyRequest]) (*connect.Response[ermite.GetSigningKeyResponse], error)
87114 }
88115
89116 // NewServerServiceHandler builds an HTTP handler from the service implementation. It returns the
@@ -99,10 +126,18 @@ func NewServerServiceHandler(svc ServerServiceHandler, opts ...connect.HandlerOp
99126 connect.WithSchema(serverServiceMethods.ByName("GetVersion")),
100127 connect.WithHandlerOptions(opts...),
101128 )
129+ serverServiceGetSigningKeyHandler := connect.NewUnaryHandler(
130+ ServerServiceGetSigningKeyProcedure,
131+ svc.GetSigningKey,
132+ connect.WithSchema(serverServiceMethods.ByName("GetSigningKey")),
133+ connect.WithHandlerOptions(opts...),
134+ )
102135 return "/ermite.ServerService/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
103136 switch r.URL.Path {
104137 case ServerServiceGetVersionProcedure:
105138 serverServiceGetVersionHandler.ServeHTTP(w, r)
139+ case ServerServiceGetSigningKeyProcedure:
140+ serverServiceGetSigningKeyHandler.ServeHTTP(w, r)
106141 default:
107142 http.NotFound(w, r)
108143 }
@@ -115,3 +150,7 @@ type UnimplementedServerServiceHandler struct{}
115150 func (UnimplementedServerServiceHandler) GetVersion(context.Context, *connect.Request[ermite.GetVersionRequest]) (*connect.Response[ermite.GetVersionResponse], error) {
116151 return nil, connect.NewError(connect.CodeUnimplemented, errors.New("ermite.ServerService.GetVersion is not implemented"))
117152 }
153+
154+func (UnimplementedServerServiceHandler) GetSigningKey(context.Context, *connect.Request[ermite.GetSigningKeyRequest]) (*connect.Response[ermite.GetSigningKeyResponse], error) {
155+ return nil, connect.NewError(connect.CodeUnimplemented, errors.New("ermite.ServerService.GetSigningKey is not implemented"))
156+}
@@ -38,6 +38,9 @@ const (
38 // ServerServiceGetVersionProcedure is the fully-qualified name of the ServerService's GetVersion38 // ServerServiceGetVersionProcedure is the fully-qualified name of the ServerService's GetVersion
39 // RPC.39 // RPC.
40 ServerServiceGetVersionProcedure = "/ermite.ServerService/GetVersion"40 ServerServiceGetVersionProcedure = "/ermite.ServerService/GetVersion"
41+ // ServerServiceGetSigningKeyProcedure is the fully-qualified name of the ServerService's
42+ // GetSigningKey RPC.
43+ ServerServiceGetSigningKeyProcedure = "/ermite.ServerService/GetSigningKey"
41 )44 )
42 45
43 // ServerServiceClient is a client for the ermite.ServerService service.46 // ServerServiceClient is a client for the ermite.ServerService service.
@@ -46,6 +49,12 @@ type ServerServiceClient interface {
46 // contract it implements. It needs no authentication, so a client can call49 // contract it implements. It needs no authentication, so a client can call
47 // it first to check compatibility.50 // it first to check compatibility.
48 GetVersion(context.Context, *connect.Request[ermite.GetVersionRequest]) (*connect.Response[ermite.GetVersionResponse], error)51 GetVersion(context.Context, *connect.Request[ermite.GetVersionRequest]) (*connect.Response[ermite.GetVersionResponse], error)
52+ // GetSigningKey returns the public key with which the server signs the
53+ // commits it creates (squash merges, see MergeMode). It needs no
54+ // authentication: the key is public, and anyone verifying a commit needs it.
55+ // Servers that implement it list "signing_key" in
56+ // GetVersionResponse.features.
57+ GetSigningKey(context.Context, *connect.Request[ermite.GetSigningKeyRequest]) (*connect.Response[ermite.GetSigningKeyResponse], error)
49 }58 }
50 59
51 // NewServerServiceClient constructs a client for the ermite.ServerService service. By default, it60 // NewServerServiceClient constructs a client for the ermite.ServerService service. By default, it
@@ -65,12 +74,19 @@ func NewServerServiceClient(httpClient connect.HTTPClient, baseURL string, opts
65 connect.WithSchema(serverServiceMethods.ByName("GetVersion")),74 connect.WithSchema(serverServiceMethods.ByName("GetVersion")),
66 connect.WithClientOptions(opts...),75 connect.WithClientOptions(opts...),
67 ),76 ),
77+ getSigningKey: connect.NewClient[ermite.GetSigningKeyRequest, ermite.GetSigningKeyResponse](
78+ httpClient,
79+ baseURL+ServerServiceGetSigningKeyProcedure,
80+ connect.WithSchema(serverServiceMethods.ByName("GetSigningKey")),
81+ connect.WithClientOptions(opts...),
82+ ),
68 }83 }
69 }84 }
70 85
71 // serverServiceClient implements ServerServiceClient.86 // serverServiceClient implements ServerServiceClient.
72 type serverServiceClient struct {87 type serverServiceClient struct {
73- getVersion *connect.Client[ermite.GetVersionRequest, ermite.GetVersionResponse]88+ getVersion *connect.Client[ermite.GetVersionRequest, ermite.GetVersionResponse]
89+ getSigningKey *connect.Client[ermite.GetSigningKeyRequest, ermite.GetSigningKeyResponse]
74 }90 }
75 91
76 // GetVersion calls ermite.ServerService.GetVersion.92 // GetVersion calls ermite.ServerService.GetVersion.
@@ -78,12 +94,23 @@ func (c *serverServiceClient) GetVersion(ctx context.Context, req *connect.Reque
78 return c.getVersion.CallUnary(ctx, req)94 return c.getVersion.CallUnary(ctx, req)
79 }95 }
80 96
97+// GetSigningKey calls ermite.ServerService.GetSigningKey.
98+func (c *serverServiceClient) GetSigningKey(ctx context.Context, req *connect.Request[ermite.GetSigningKeyRequest]) (*connect.Response[ermite.GetSigningKeyResponse], error) {
99+ return c.getSigningKey.CallUnary(ctx, req)
100+}
101+
81 // ServerServiceHandler is an implementation of the ermite.ServerService service.102 // ServerServiceHandler is an implementation of the ermite.ServerService service.
82 type ServerServiceHandler interface {103 type ServerServiceHandler interface {
83 // GetVersion returns the server version and the optional capabilities of the104 // GetVersion returns the server version and the optional capabilities of the
84 // contract it implements. It needs no authentication, so a client can call105 // contract it implements. It needs no authentication, so a client can call
85 // it first to check compatibility.106 // it first to check compatibility.
86 GetVersion(context.Context, *connect.Request[ermite.GetVersionRequest]) (*connect.Response[ermite.GetVersionResponse], error)107 GetVersion(context.Context, *connect.Request[ermite.GetVersionRequest]) (*connect.Response[ermite.GetVersionResponse], error)
108+ // GetSigningKey returns the public key with which the server signs the
109+ // commits it creates (squash merges, see MergeMode). It needs no
110+ // authentication: the key is public, and anyone verifying a commit needs it.
111+ // Servers that implement it list "signing_key" in
112+ // GetVersionResponse.features.
113+ GetSigningKey(context.Context, *connect.Request[ermite.GetSigningKeyRequest]) (*connect.Response[ermite.GetSigningKeyResponse], error)
87 }114 }
88 115
89 // NewServerServiceHandler builds an HTTP handler from the service implementation. It returns the116 // NewServerServiceHandler builds an HTTP handler from the service implementation. It returns the
@@ -99,10 +126,18 @@ func NewServerServiceHandler(svc ServerServiceHandler, opts ...connect.HandlerOp
99 connect.WithSchema(serverServiceMethods.ByName("GetVersion")),126 connect.WithSchema(serverServiceMethods.ByName("GetVersion")),
100 connect.WithHandlerOptions(opts...),127 connect.WithHandlerOptions(opts...),
101 )128 )
129+ serverServiceGetSigningKeyHandler := connect.NewUnaryHandler(
130+ ServerServiceGetSigningKeyProcedure,
131+ svc.GetSigningKey,
132+ connect.WithSchema(serverServiceMethods.ByName("GetSigningKey")),
133+ connect.WithHandlerOptions(opts...),
134+ )
102 return "/ermite.ServerService/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {135 return "/ermite.ServerService/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
103 switch r.URL.Path {136 switch r.URL.Path {
104 case ServerServiceGetVersionProcedure:137 case ServerServiceGetVersionProcedure:
105 serverServiceGetVersionHandler.ServeHTTP(w, r)138 serverServiceGetVersionHandler.ServeHTTP(w, r)
139+ case ServerServiceGetSigningKeyProcedure:
140+ serverServiceGetSigningKeyHandler.ServeHTTP(w, r)
106 default:141 default:
107 http.NotFound(w, r)142 http.NotFound(w, r)
108 }143 }
@@ -115,3 +150,7 @@ type UnimplementedServerServiceHandler struct{}
115 func (UnimplementedServerServiceHandler) GetVersion(context.Context, *connect.Request[ermite.GetVersionRequest]) (*connect.Response[ermite.GetVersionResponse], error) {150 func (UnimplementedServerServiceHandler) GetVersion(context.Context, *connect.Request[ermite.GetVersionRequest]) (*connect.Response[ermite.GetVersionResponse], error) {
116 return nil, connect.NewError(connect.CodeUnimplemented, errors.New("ermite.ServerService.GetVersion is not implemented"))151 return nil, connect.NewError(connect.CodeUnimplemented, errors.New("ermite.ServerService.GetVersion is not implemented"))
117 }152 }
153+
154+func (UnimplementedServerServiceHandler) GetSigningKey(context.Context, *connect.Request[ermite.GetSigningKeyRequest]) (*connect.Response[ermite.GetSigningKeyResponse], error) {
155+ return nil, connect.NewError(connect.CodeUnimplemented, errors.New("ermite.ServerService.GetSigningKey is not implemented"))
156+}
A gen/ermite/ermiteconnect/session.connect.go +158 -0
@@ -0,0 +1,158 @@
1+// SPDX-License-Identifier: MIT
2+
3+// Code generated by protoc-gen-connect-go. DO NOT EDIT.
4+//
5+// Source: ermite/session.proto
6+
7+package ermiteconnect
8+
9+import (
10+ ermite "bnema.dev/ermite-api/gen/ermite"
11+ connect "connectrpc.com/connect"
12+ context "context"
13+ errors "errors"
14+ http "net/http"
15+ strings "strings"
16+)
17+
18+// This is a compile-time assertion to ensure that this generated file and the connect package are
19+// compatible. If you get a compiler error that this constant is not defined, this code was
20+// generated with a version of connect newer than the one compiled into your binary. You can fix the
21+// problem by either regenerating this code with an older version of connect or updating the connect
22+// version compiled into your binary.
23+const _ = connect.IsAtLeastVersion1_13_0
24+
25+const (
26+ // SessionServiceName is the fully-qualified name of the SessionService service.
27+ SessionServiceName = "ermite.SessionService"
28+)
29+
30+// These constants are the fully-qualified names of the RPCs defined in this package. They're
31+// exposed at runtime as Spec.Procedure and as the final two segments of the HTTP route.
32+//
33+// Note that these are different from the fully-qualified method names used by
34+// google.golang.org/protobuf/reflect/protoreflect. To convert from these constants to
35+// reflection-formatted method names, remove the leading slash and convert the remaining slash to a
36+// period.
37+const (
38+ // SessionServiceCreateLoginLinkProcedure is the fully-qualified name of the SessionService's
39+ // CreateLoginLink RPC.
40+ SessionServiceCreateLoginLinkProcedure = "/ermite.SessionService/CreateLoginLink"
41+ // SessionServiceSignOutEverywhereProcedure is the fully-qualified name of the SessionService's
42+ // SignOutEverywhere RPC.
43+ SessionServiceSignOutEverywhereProcedure = "/ermite.SessionService/SignOutEverywhere"
44+)
45+
46+// SessionServiceClient is a client for the ermite.SessionService service.
47+type SessionServiceClient interface {
48+ // CreateLoginLink creates a one-time link that signs the owner's browser
49+ // in. The link is valid for a short time (about a minute), can be used
50+ // once, and is kept in memory only: a server restart voids it. Opening it
51+ // shows a confirmation page; only confirming it consumes the link and sets
52+ // the session. Creating a new link does not void an earlier one. Fails with
53+ // resource_exhausted when too many links are pending.
54+ CreateLoginLink(context.Context, *connect.Request[ermite.CreateLoginLinkRequest]) (*connect.Response[ermite.CreateLoginLinkResponse], error)
55+ // SignOutEverywhere ends every browser session at once, including those on
56+ // other devices. Pending login links are not affected. Tokens are not
57+ // affected either: revoke them with TokenService.
58+ SignOutEverywhere(context.Context, *connect.Request[ermite.SignOutEverywhereRequest]) (*connect.Response[ermite.SignOutEverywhereResponse], error)
59+}
60+
61+// NewSessionServiceClient constructs a client for the ermite.SessionService service. By default, it
62+// uses the Connect protocol with the binary Protobuf Codec, asks for gzipped responses, and sends
63+// uncompressed requests. To use the gRPC or gRPC-Web protocols, supply the connect.WithGRPC() or
64+// connect.WithGRPCWeb() options.
65+//
66+// The URL supplied here should be the base URL for the Connect or gRPC server (for example,
67+// http://api.acme.com or https://acme.com/grpc).
68+func NewSessionServiceClient(httpClient connect.HTTPClient, baseURL string, opts ...connect.ClientOption) SessionServiceClient {
69+ baseURL = strings.TrimRight(baseURL, "/")
70+ sessionServiceMethods := ermite.File_ermite_session_proto.Services().ByName("SessionService").Methods()
71+ return &sessionServiceClient{
72+ createLoginLink: connect.NewClient[ermite.CreateLoginLinkRequest, ermite.CreateLoginLinkResponse](
73+ httpClient,
74+ baseURL+SessionServiceCreateLoginLinkProcedure,
75+ connect.WithSchema(sessionServiceMethods.ByName("CreateLoginLink")),
76+ connect.WithClientOptions(opts...),
77+ ),
78+ signOutEverywhere: connect.NewClient[ermite.SignOutEverywhereRequest, ermite.SignOutEverywhereResponse](
79+ httpClient,
80+ baseURL+SessionServiceSignOutEverywhereProcedure,
81+ connect.WithSchema(sessionServiceMethods.ByName("SignOutEverywhere")),
82+ connect.WithClientOptions(opts...),
83+ ),
84+ }
85+}
86+
87+// sessionServiceClient implements SessionServiceClient.
88+type sessionServiceClient struct {
89+ createLoginLink *connect.Client[ermite.CreateLoginLinkRequest, ermite.CreateLoginLinkResponse]
90+ signOutEverywhere *connect.Client[ermite.SignOutEverywhereRequest, ermite.SignOutEverywhereResponse]
91+}
92+
93+// CreateLoginLink calls ermite.SessionService.CreateLoginLink.
94+func (c *sessionServiceClient) CreateLoginLink(ctx context.Context, req *connect.Request[ermite.CreateLoginLinkRequest]) (*connect.Response[ermite.CreateLoginLinkResponse], error) {
95+ return c.createLoginLink.CallUnary(ctx, req)
96+}
97+
98+// SignOutEverywhere calls ermite.SessionService.SignOutEverywhere.
99+func (c *sessionServiceClient) SignOutEverywhere(ctx context.Context, req *connect.Request[ermite.SignOutEverywhereRequest]) (*connect.Response[ermite.SignOutEverywhereResponse], error) {
100+ return c.signOutEverywhere.CallUnary(ctx, req)
101+}
102+
103+// SessionServiceHandler is an implementation of the ermite.SessionService service.
104+type SessionServiceHandler interface {
105+ // CreateLoginLink creates a one-time link that signs the owner's browser
106+ // in. The link is valid for a short time (about a minute), can be used
107+ // once, and is kept in memory only: a server restart voids it. Opening it
108+ // shows a confirmation page; only confirming it consumes the link and sets
109+ // the session. Creating a new link does not void an earlier one. Fails with
110+ // resource_exhausted when too many links are pending.
111+ CreateLoginLink(context.Context, *connect.Request[ermite.CreateLoginLinkRequest]) (*connect.Response[ermite.CreateLoginLinkResponse], error)
112+ // SignOutEverywhere ends every browser session at once, including those on
113+ // other devices. Pending login links are not affected. Tokens are not
114+ // affected either: revoke them with TokenService.
115+ SignOutEverywhere(context.Context, *connect.Request[ermite.SignOutEverywhereRequest]) (*connect.Response[ermite.SignOutEverywhereResponse], error)
116+}
117+
118+// NewSessionServiceHandler builds an HTTP handler from the service implementation. It returns the
119+// path on which to mount the handler and the handler itself.
120+//
121+// By default, handlers support the Connect, gRPC, and gRPC-Web protocols with the binary Protobuf
122+// and JSON codecs. They also support gzip compression.
123+func NewSessionServiceHandler(svc SessionServiceHandler, opts ...connect.HandlerOption) (string, http.Handler) {
124+ sessionServiceMethods := ermite.File_ermite_session_proto.Services().ByName("SessionService").Methods()
125+ sessionServiceCreateLoginLinkHandler := connect.NewUnaryHandler(
126+ SessionServiceCreateLoginLinkProcedure,
127+ svc.CreateLoginLink,
128+ connect.WithSchema(sessionServiceMethods.ByName("CreateLoginLink")),
129+ connect.WithHandlerOptions(opts...),
130+ )
131+ sessionServiceSignOutEverywhereHandler := connect.NewUnaryHandler(
132+ SessionServiceSignOutEverywhereProcedure,
133+ svc.SignOutEverywhere,
134+ connect.WithSchema(sessionServiceMethods.ByName("SignOutEverywhere")),
135+ connect.WithHandlerOptions(opts...),
136+ )
137+ return "/ermite.SessionService/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
138+ switch r.URL.Path {
139+ case SessionServiceCreateLoginLinkProcedure:
140+ sessionServiceCreateLoginLinkHandler.ServeHTTP(w, r)
141+ case SessionServiceSignOutEverywhereProcedure:
142+ sessionServiceSignOutEverywhereHandler.ServeHTTP(w, r)
143+ default:
144+ http.NotFound(w, r)
145+ }
146+ })
147+}
148+
149+// UnimplementedSessionServiceHandler returns CodeUnimplemented from all methods.
150+type UnimplementedSessionServiceHandler struct{}
151+
152+func (UnimplementedSessionServiceHandler) CreateLoginLink(context.Context, *connect.Request[ermite.CreateLoginLinkRequest]) (*connect.Response[ermite.CreateLoginLinkResponse], error) {
153+ return nil, connect.NewError(connect.CodeUnimplemented, errors.New("ermite.SessionService.CreateLoginLink is not implemented"))
154+}
155+
156+func (UnimplementedSessionServiceHandler) SignOutEverywhere(context.Context, *connect.Request[ermite.SignOutEverywhereRequest]) (*connect.Response[ermite.SignOutEverywhereResponse], error) {
157+ return nil, connect.NewError(connect.CodeUnimplemented, errors.New("ermite.SessionService.SignOutEverywhere is not implemented"))
158+}
@@ -0,0 +1,158 @@
1+// SPDX-License-Identifier: MIT
2+
3+// Code generated by protoc-gen-connect-go. DO NOT EDIT.
4+//
5+// Source: ermite/session.proto
6+
7+package ermiteconnect
8+
9+import (
10+ ermite "bnema.dev/ermite-api/gen/ermite"
11+ connect "connectrpc.com/connect"
12+ context "context"
13+ errors "errors"
14+ http "net/http"
15+ strings "strings"
16+)
17+
18+// This is a compile-time assertion to ensure that this generated file and the connect package are
19+// compatible. If you get a compiler error that this constant is not defined, this code was
20+// generated with a version of connect newer than the one compiled into your binary. You can fix the
21+// problem by either regenerating this code with an older version of connect or updating the connect
22+// version compiled into your binary.
23+const _ = connect.IsAtLeastVersion1_13_0
24+
25+const (
26+ // SessionServiceName is the fully-qualified name of the SessionService service.
27+ SessionServiceName = "ermite.SessionService"
28+)
29+
30+// These constants are the fully-qualified names of the RPCs defined in this package. They're
31+// exposed at runtime as Spec.Procedure and as the final two segments of the HTTP route.
32+//
33+// Note that these are different from the fully-qualified method names used by
34+// google.golang.org/protobuf/reflect/protoreflect. To convert from these constants to
35+// reflection-formatted method names, remove the leading slash and convert the remaining slash to a
36+// period.
37+const (
38+ // SessionServiceCreateLoginLinkProcedure is the fully-qualified name of the SessionService's
39+ // CreateLoginLink RPC.
40+ SessionServiceCreateLoginLinkProcedure = "/ermite.SessionService/CreateLoginLink"
41+ // SessionServiceSignOutEverywhereProcedure is the fully-qualified name of the SessionService's
42+ // SignOutEverywhere RPC.
43+ SessionServiceSignOutEverywhereProcedure = "/ermite.SessionService/SignOutEverywhere"
44+)
45+
46+// SessionServiceClient is a client for the ermite.SessionService service.
47+type SessionServiceClient interface {
48+ // CreateLoginLink creates a one-time link that signs the owner's browser
49+ // in. The link is valid for a short time (about a minute), can be used
50+ // once, and is kept in memory only: a server restart voids it. Opening it
51+ // shows a confirmation page; only confirming it consumes the link and sets
52+ // the session. Creating a new link does not void an earlier one. Fails with
53+ // resource_exhausted when too many links are pending.
54+ CreateLoginLink(context.Context, *connect.Request[ermite.CreateLoginLinkRequest]) (*connect.Response[ermite.CreateLoginLinkResponse], error)
55+ // SignOutEverywhere ends every browser session at once, including those on
56+ // other devices. Pending login links are not affected. Tokens are not
57+ // affected either: revoke them with TokenService.
58+ SignOutEverywhere(context.Context, *connect.Request[ermite.SignOutEverywhereRequest]) (*connect.Response[ermite.SignOutEverywhereResponse], error)
59+}
60+
61+// NewSessionServiceClient constructs a client for the ermite.SessionService service. By default, it
62+// uses the Connect protocol with the binary Protobuf Codec, asks for gzipped responses, and sends
63+// uncompressed requests. To use the gRPC or gRPC-Web protocols, supply the connect.WithGRPC() or
64+// connect.WithGRPCWeb() options.
65+//
66+// The URL supplied here should be the base URL for the Connect or gRPC server (for example,
67+// http://api.acme.com or https://acme.com/grpc).
68+func NewSessionServiceClient(httpClient connect.HTTPClient, baseURL string, opts ...connect.ClientOption) SessionServiceClient {
69+ baseURL = strings.TrimRight(baseURL, "/")
70+ sessionServiceMethods := ermite.File_ermite_session_proto.Services().ByName("SessionService").Methods()
71+ return &sessionServiceClient{
72+ createLoginLink: connect.NewClient[ermite.CreateLoginLinkRequest, ermite.CreateLoginLinkResponse](
73+ httpClient,
74+ baseURL+SessionServiceCreateLoginLinkProcedure,
75+ connect.WithSchema(sessionServiceMethods.ByName("CreateLoginLink")),
76+ connect.WithClientOptions(opts...),
77+ ),
78+ signOutEverywhere: connect.NewClient[ermite.SignOutEverywhereRequest, ermite.SignOutEverywhereResponse](
79+ httpClient,
80+ baseURL+SessionServiceSignOutEverywhereProcedure,
81+ connect.WithSchema(sessionServiceMethods.ByName("SignOutEverywhere")),
82+ connect.WithClientOptions(opts...),
83+ ),
84+ }
85+}
86+
87+// sessionServiceClient implements SessionServiceClient.
88+type sessionServiceClient struct {
89+ createLoginLink *connect.Client[ermite.CreateLoginLinkRequest, ermite.CreateLoginLinkResponse]
90+ signOutEverywhere *connect.Client[ermite.SignOutEverywhereRequest, ermite.SignOutEverywhereResponse]
91+}
92+
93+// CreateLoginLink calls ermite.SessionService.CreateLoginLink.
94+func (c *sessionServiceClient) CreateLoginLink(ctx context.Context, req *connect.Request[ermite.CreateLoginLinkRequest]) (*connect.Response[ermite.CreateLoginLinkResponse], error) {
95+ return c.createLoginLink.CallUnary(ctx, req)
96+}
97+
98+// SignOutEverywhere calls ermite.SessionService.SignOutEverywhere.
99+func (c *sessionServiceClient) SignOutEverywhere(ctx context.Context, req *connect.Request[ermite.SignOutEverywhereRequest]) (*connect.Response[ermite.SignOutEverywhereResponse], error) {
100+ return c.signOutEverywhere.CallUnary(ctx, req)
101+}
102+
103+// SessionServiceHandler is an implementation of the ermite.SessionService service.
104+type SessionServiceHandler interface {
105+ // CreateLoginLink creates a one-time link that signs the owner's browser
106+ // in. The link is valid for a short time (about a minute), can be used
107+ // once, and is kept in memory only: a server restart voids it. Opening it
108+ // shows a confirmation page; only confirming it consumes the link and sets
109+ // the session. Creating a new link does not void an earlier one. Fails with
110+ // resource_exhausted when too many links are pending.
111+ CreateLoginLink(context.Context, *connect.Request[ermite.CreateLoginLinkRequest]) (*connect.Response[ermite.CreateLoginLinkResponse], error)
112+ // SignOutEverywhere ends every browser session at once, including those on
113+ // other devices. Pending login links are not affected. Tokens are not
114+ // affected either: revoke them with TokenService.
115+ SignOutEverywhere(context.Context, *connect.Request[ermite.SignOutEverywhereRequest]) (*connect.Response[ermite.SignOutEverywhereResponse], error)
116+}
117+
118+// NewSessionServiceHandler builds an HTTP handler from the service implementation. It returns the
119+// path on which to mount the handler and the handler itself.
120+//
121+// By default, handlers support the Connect, gRPC, and gRPC-Web protocols with the binary Protobuf
122+// and JSON codecs. They also support gzip compression.
123+func NewSessionServiceHandler(svc SessionServiceHandler, opts ...connect.HandlerOption) (string, http.Handler) {
124+ sessionServiceMethods := ermite.File_ermite_session_proto.Services().ByName("SessionService").Methods()
125+ sessionServiceCreateLoginLinkHandler := connect.NewUnaryHandler(
126+ SessionServiceCreateLoginLinkProcedure,
127+ svc.CreateLoginLink,
128+ connect.WithSchema(sessionServiceMethods.ByName("CreateLoginLink")),
129+ connect.WithHandlerOptions(opts...),
130+ )
131+ sessionServiceSignOutEverywhereHandler := connect.NewUnaryHandler(
132+ SessionServiceSignOutEverywhereProcedure,
133+ svc.SignOutEverywhere,
134+ connect.WithSchema(sessionServiceMethods.ByName("SignOutEverywhere")),
135+ connect.WithHandlerOptions(opts...),
136+ )
137+ return "/ermite.SessionService/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
138+ switch r.URL.Path {
139+ case SessionServiceCreateLoginLinkProcedure:
140+ sessionServiceCreateLoginLinkHandler.ServeHTTP(w, r)
141+ case SessionServiceSignOutEverywhereProcedure:
142+ sessionServiceSignOutEverywhereHandler.ServeHTTP(w, r)
143+ default:
144+ http.NotFound(w, r)
145+ }
146+ })
147+}
148+
149+// UnimplementedSessionServiceHandler returns CodeUnimplemented from all methods.
150+type UnimplementedSessionServiceHandler struct{}
151+
152+func (UnimplementedSessionServiceHandler) CreateLoginLink(context.Context, *connect.Request[ermite.CreateLoginLinkRequest]) (*connect.Response[ermite.CreateLoginLinkResponse], error) {
153+ return nil, connect.NewError(connect.CodeUnimplemented, errors.New("ermite.SessionService.CreateLoginLink is not implemented"))
154+}
155+
156+func (UnimplementedSessionServiceHandler) SignOutEverywhere(context.Context, *connect.Request[ermite.SignOutEverywhereRequest]) (*connect.Response[ermite.SignOutEverywhereResponse], error) {
157+ return nil, connect.NewError(connect.CodeUnimplemented, errors.New("ermite.SessionService.SignOutEverywhere is not implemented"))
158+}
M gen/ermite/event.pb.go +331 -25
@@ -148,6 +148,10 @@ type Event struct {
148148 // *Event_RepoDeleted
149149 // *Event_Pushed
150150 // *Event_Gap
151+ // *Event_MergeRequestOpened
152+ // *Event_MergeRequestMerged
153+ // *Event_MergeRequestClosed
154+ // *Event_CommentAdded
151155 Kind isEvent_Kind `protobuf_oneof:"kind"`
152156 unknownFields protoimpl.UnknownFields
153157 sizeCache protoimpl.SizeCache
@@ -249,6 +253,42 @@ func (x *Event) GetGap() *Gap {
249253 return nil
250254 }
251255
256+func (x *Event) GetMergeRequestOpened() *MergeRequestOpened {
257+ if x != nil {
258+ if x, ok := x.Kind.(*Event_MergeRequestOpened); ok {
259+ return x.MergeRequestOpened
260+ }
261+ }
262+ return nil
263+}
264+
265+func (x *Event) GetMergeRequestMerged() *MergeRequestMerged {
266+ if x != nil {
267+ if x, ok := x.Kind.(*Event_MergeRequestMerged); ok {
268+ return x.MergeRequestMerged
269+ }
270+ }
271+ return nil
272+}
273+
274+func (x *Event) GetMergeRequestClosed() *MergeRequestClosed {
275+ if x != nil {
276+ if x, ok := x.Kind.(*Event_MergeRequestClosed); ok {
277+ return x.MergeRequestClosed
278+ }
279+ }
280+ return nil
281+}
282+
283+func (x *Event) GetCommentAdded() *CommentAdded {
284+ if x != nil {
285+ if x, ok := x.Kind.(*Event_CommentAdded); ok {
286+ return x.CommentAdded
287+ }
288+ }
289+ return nil
290+}
291+
252292 type isEvent_Kind interface {
253293 isEvent_Kind()
254294 }
@@ -278,6 +318,26 @@ type Event_Gap struct {
278318 Gap *Gap `protobuf:"bytes,7,opt,name=gap,proto3,oneof"`
279319 }
280320
321+type Event_MergeRequestOpened struct {
322+ // MergeRequestOpened: a merge request was opened.
323+ MergeRequestOpened *MergeRequestOpened `protobuf:"bytes,8,opt,name=merge_request_opened,json=mergeRequestOpened,proto3,oneof"`
324+}
325+
326+type Event_MergeRequestMerged struct {
327+ // MergeRequestMerged: a merge request was merged.
328+ MergeRequestMerged *MergeRequestMerged `protobuf:"bytes,9,opt,name=merge_request_merged,json=mergeRequestMerged,proto3,oneof"`
329+}
330+
331+type Event_MergeRequestClosed struct {
332+ // MergeRequestClosed: a merge request was closed without merging.
333+ MergeRequestClosed *MergeRequestClosed `protobuf:"bytes,10,opt,name=merge_request_closed,json=mergeRequestClosed,proto3,oneof"`
334+}
335+
336+type Event_CommentAdded struct {
337+ // CommentAdded: a comment was added to a merge request.
338+ CommentAdded *CommentAdded `protobuf:"bytes,11,opt,name=comment_added,json=commentAdded,proto3,oneof"`
339+}
340+
281341 func (*Event_RepoCreated) isEvent_Kind() {}
282342
283343 func (*Event_RepoUpdated) isEvent_Kind() {}
@@ -288,6 +348,14 @@ func (*Event_Pushed) isEvent_Kind() {}
288348
289349 func (*Event_Gap) isEvent_Kind() {}
290350
351+func (*Event_MergeRequestOpened) isEvent_Kind() {}
352+
353+func (*Event_MergeRequestMerged) isEvent_Kind() {}
354+
355+func (*Event_MergeRequestClosed) isEvent_Kind() {}
356+
357+func (*Event_CommentAdded) isEvent_Kind() {}
358+
291359 // RepoCreated reports a new repository.
292360 type RepoCreated struct {
293361 state protoimpl.MessageState `protogen:"open.v1"`
@@ -545,9 +613,213 @@ func (x *RefUpdate) GetNewId() string {
545613 return ""
546614 }
547615
616+// MergeRequestOpened reports a new merge request.
617+type MergeRequestOpened struct {
618+ state protoimpl.MessageState `protogen:"open.v1"`
619+ // MergeRequest is the new merge request.
620+ MergeRequest *MergeRequest `protobuf:"bytes,1,opt,name=merge_request,json=mergeRequest,proto3" json:"merge_request,omitempty"`
621+ unknownFields protoimpl.UnknownFields
622+ sizeCache protoimpl.SizeCache
623+}
624+
625+func (x *MergeRequestOpened) Reset() {
626+ *x = MergeRequestOpened{}
627+ mi := &file_ermite_event_proto_msgTypes[8]
628+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
629+ ms.StoreMessageInfo(mi)
630+}
631+
632+func (x *MergeRequestOpened) String() string {
633+ return protoimpl.X.MessageStringOf(x)
634+}
635+
636+func (*MergeRequestOpened) ProtoMessage() {}
637+
638+func (x *MergeRequestOpened) ProtoReflect() protoreflect.Message {
639+ mi := &file_ermite_event_proto_msgTypes[8]
640+ if x != nil {
641+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
642+ if ms.LoadMessageInfo() == nil {
643+ ms.StoreMessageInfo(mi)
644+ }
645+ return ms
646+ }
647+ return mi.MessageOf(x)
648+}
649+
650+// Deprecated: Use MergeRequestOpened.ProtoReflect.Descriptor instead.
651+func (*MergeRequestOpened) Descriptor() ([]byte, []int) {
652+ return file_ermite_event_proto_rawDescGZIP(), []int{8}
653+}
654+
655+func (x *MergeRequestOpened) GetMergeRequest() *MergeRequest {
656+ if x != nil {
657+ return x.MergeRequest
658+ }
659+ return nil
660+}
661+
662+// MergeRequestMerged reports a merged merge request. It is published before
663+// the Pushed event of the target branch.
664+type MergeRequestMerged struct {
665+ state protoimpl.MessageState `protogen:"open.v1"`
666+ // MergeRequest is the merge request after the merge.
667+ MergeRequest *MergeRequest `protobuf:"bytes,1,opt,name=merge_request,json=mergeRequest,proto3" json:"merge_request,omitempty"`
668+ unknownFields protoimpl.UnknownFields
669+ sizeCache protoimpl.SizeCache
670+}
671+
672+func (x *MergeRequestMerged) Reset() {
673+ *x = MergeRequestMerged{}
674+ mi := &file_ermite_event_proto_msgTypes[9]
675+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
676+ ms.StoreMessageInfo(mi)
677+}
678+
679+func (x *MergeRequestMerged) String() string {
680+ return protoimpl.X.MessageStringOf(x)
681+}
682+
683+func (*MergeRequestMerged) ProtoMessage() {}
684+
685+func (x *MergeRequestMerged) ProtoReflect() protoreflect.Message {
686+ mi := &file_ermite_event_proto_msgTypes[9]
687+ if x != nil {
688+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
689+ if ms.LoadMessageInfo() == nil {
690+ ms.StoreMessageInfo(mi)
691+ }
692+ return ms
693+ }
694+ return mi.MessageOf(x)
695+}
696+
697+// Deprecated: Use MergeRequestMerged.ProtoReflect.Descriptor instead.
698+func (*MergeRequestMerged) Descriptor() ([]byte, []int) {
699+ return file_ermite_event_proto_rawDescGZIP(), []int{9}
700+}
701+
702+func (x *MergeRequestMerged) GetMergeRequest() *MergeRequest {
703+ if x != nil {
704+ return x.MergeRequest
705+ }
706+ return nil
707+}
708+
709+// MergeRequestClosed reports a merge request closed without merging.
710+type MergeRequestClosed struct {
711+ state protoimpl.MessageState `protogen:"open.v1"`
712+ // MergeRequest is the merge request after closing.
713+ MergeRequest *MergeRequest `protobuf:"bytes,1,opt,name=merge_request,json=mergeRequest,proto3" json:"merge_request,omitempty"`
714+ unknownFields protoimpl.UnknownFields
715+ sizeCache protoimpl.SizeCache
716+}
717+
718+func (x *MergeRequestClosed) Reset() {
719+ *x = MergeRequestClosed{}
720+ mi := &file_ermite_event_proto_msgTypes[10]
721+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
722+ ms.StoreMessageInfo(mi)
723+}
724+
725+func (x *MergeRequestClosed) String() string {
726+ return protoimpl.X.MessageStringOf(x)
727+}
728+
729+func (*MergeRequestClosed) ProtoMessage() {}
730+
731+func (x *MergeRequestClosed) ProtoReflect() protoreflect.Message {
732+ mi := &file_ermite_event_proto_msgTypes[10]
733+ if x != nil {
734+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
735+ if ms.LoadMessageInfo() == nil {
736+ ms.StoreMessageInfo(mi)
737+ }
738+ return ms
739+ }
740+ return mi.MessageOf(x)
741+}
742+
743+// Deprecated: Use MergeRequestClosed.ProtoReflect.Descriptor instead.
744+func (*MergeRequestClosed) Descriptor() ([]byte, []int) {
745+ return file_ermite_event_proto_rawDescGZIP(), []int{10}
746+}
747+
748+func (x *MergeRequestClosed) GetMergeRequest() *MergeRequest {
749+ if x != nil {
750+ return x.MergeRequest
751+ }
752+ return nil
753+}
754+
755+// CommentAdded reports a new comment on a merge request.
756+type CommentAdded struct {
757+ state protoimpl.MessageState `protogen:"open.v1"`
758+ // Repo is the name of the repository.
759+ Repo string `protobuf:"bytes,1,opt,name=repo,proto3" json:"repo,omitempty"`
760+ // Number is the number of the merge request.
761+ Number int64 `protobuf:"varint,2,opt,name=number,proto3" json:"number,omitempty"`
762+ // Comment is the new comment.
763+ Comment *Comment `protobuf:"bytes,3,opt,name=comment,proto3" json:"comment,omitempty"`
764+ unknownFields protoimpl.UnknownFields
765+ sizeCache protoimpl.SizeCache
766+}
767+
768+func (x *CommentAdded) Reset() {
769+ *x = CommentAdded{}
770+ mi := &file_ermite_event_proto_msgTypes[11]
771+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
772+ ms.StoreMessageInfo(mi)
773+}
774+
775+func (x *CommentAdded) String() string {
776+ return protoimpl.X.MessageStringOf(x)
777+}
778+
779+func (*CommentAdded) ProtoMessage() {}
780+
781+func (x *CommentAdded) ProtoReflect() protoreflect.Message {
782+ mi := &file_ermite_event_proto_msgTypes[11]
783+ if x != nil {
784+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
785+ if ms.LoadMessageInfo() == nil {
786+ ms.StoreMessageInfo(mi)
787+ }
788+ return ms
789+ }
790+ return mi.MessageOf(x)
791+}
792+
793+// Deprecated: Use CommentAdded.ProtoReflect.Descriptor instead.
794+func (*CommentAdded) Descriptor() ([]byte, []int) {
795+ return file_ermite_event_proto_rawDescGZIP(), []int{11}
796+}
797+
798+func (x *CommentAdded) GetRepo() string {
799+ if x != nil {
800+ return x.Repo
801+ }
802+ return ""
803+}
804+
805+func (x *CommentAdded) GetNumber() int64 {
806+ if x != nil {
807+ return x.Number
808+ }
809+ return 0
810+}
811+
812+func (x *CommentAdded) GetComment() *Comment {
813+ if x != nil {
814+ return x.Comment
815+ }
816+ return nil
817+}
818+
548819 // Gap tells the client that events were lost: the requested resume point is
549820 // older than the server's buffer. It has no fields. The client should reload
550-// the state it displays (repositories, refs) and carry on with the next events.
821+// the state it displays (repositories, refs, merge requests and their
822+// comments) and carry on with the next events.
551823 type Gap struct {
552824 state protoimpl.MessageState `protogen:"open.v1"`
553825 unknownFields protoimpl.UnknownFields
@@ -556,7 +828,7 @@ type Gap struct {
556828
557829 func (x *Gap) Reset() {
558830 *x = Gap{}
559- mi := &file_ermite_event_proto_msgTypes[8]
831+ mi := &file_ermite_event_proto_msgTypes[12]
560832 ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
561833 ms.StoreMessageInfo(mi)
562834 }
@@ -568,7 +840,7 @@ func (x *Gap) String() string {
568840 func (*Gap) ProtoMessage() {}
569841
570842 func (x *Gap) ProtoReflect() protoreflect.Message {
571- mi := &file_ermite_event_proto_msgTypes[8]
843+ mi := &file_ermite_event_proto_msgTypes[12]
572844 if x != nil {
573845 ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
574846 if ms.LoadMessageInfo() == nil {
@@ -581,19 +853,19 @@ func (x *Gap) ProtoReflect() protoreflect.Message {
581853
582854 // Deprecated: Use Gap.ProtoReflect.Descriptor instead.
583855 func (*Gap) Descriptor() ([]byte, []int) {
584- return file_ermite_event_proto_rawDescGZIP(), []int{8}
856+ return file_ermite_event_proto_rawDescGZIP(), []int{12}
585857 }
586858
587859 var File_ermite_event_proto protoreflect.FileDescriptor
588860
589861 const file_ermite_event_proto_rawDesc = "" +
590862 "\n" +
591- "\x12ermite/event.proto\x12\x06ermite\x1a\x11ermite/repo.proto\x1a\x1fgoogle/protobuf/timestamp.proto\"I\n" +
863+ "\x12ermite/event.proto\x12\x06ermite\x1a\x1aermite/merge_request.proto\x1a\x11ermite/repo.proto\x1a\x1fgoogle/protobuf/timestamp.proto\"I\n" +
592864 "\fWatchRequest\x12\x16\n" +
593865 "\x06topics\x18\x01 \x03(\tR\x06topics\x12!\n" +
594866 "\fresume_after\x18\x02 \x01(\tR\vresumeAfter\"4\n" +
595867 "\rWatchResponse\x12#\n" +
596- "\x05event\x18\x01 \x01(\v2\r.ermite.EventR\x05event\"\xc8\x02\n" +
868+ "\x05event\x18\x01 \x01(\v2\r.ermite.EventR\x05event\"\xf5\x04\n" +
597869 "\x05Event\x12\x0e\n" +
598870 "\x02id\x18\x01 \x01(\tR\x02id\x12.\n" +
599871 "\x04time\x18\x02 \x01(\v2\x1a.google.protobuf.TimestampR\x04time\x128\n" +
@@ -601,7 +873,12 @@ const file_ermite_event_proto_rawDesc = "" +
601873 "\frepo_updated\x18\x04 \x01(\v2\x13.ermite.RepoUpdatedH\x00R\vrepoUpdated\x128\n" +
602874 "\frepo_deleted\x18\x05 \x01(\v2\x13.ermite.RepoDeletedH\x00R\vrepoDeleted\x12(\n" +
603875 "\x06pushed\x18\x06 \x01(\v2\x0e.ermite.PushedH\x00R\x06pushed\x12\x1f\n" +
604- "\x03gap\x18\a \x01(\v2\v.ermite.GapH\x00R\x03gapB\x06\n" +
876+ "\x03gap\x18\a \x01(\v2\v.ermite.GapH\x00R\x03gap\x12N\n" +
877+ "\x14merge_request_opened\x18\b \x01(\v2\x1a.ermite.MergeRequestOpenedH\x00R\x12mergeRequestOpened\x12N\n" +
878+ "\x14merge_request_merged\x18\t \x01(\v2\x1a.ermite.MergeRequestMergedH\x00R\x12mergeRequestMerged\x12N\n" +
879+ "\x14merge_request_closed\x18\n" +
880+ " \x01(\v2\x1a.ermite.MergeRequestClosedH\x00R\x12mergeRequestClosed\x12;\n" +
881+ "\rcomment_added\x18\v \x01(\v2\x14.ermite.CommentAddedH\x00R\fcommentAddedB\x06\n" +
605882 "\x04kind\"/\n" +
606883 "\vRepoCreated\x12 \n" +
607884 "\x04repo\x18\x01 \x01(\v2\f.ermite.RepoR\x04repo\"/\n" +
@@ -616,7 +893,17 @@ const file_ermite_event_proto_rawDesc = "" +
616893 "\tRefUpdate\x12\x10\n" +
617894 "\x03ref\x18\x01 \x01(\tR\x03ref\x12\x15\n" +
618895 "\x06old_id\x18\x02 \x01(\tR\x05oldId\x12\x15\n" +
619- "\x06new_id\x18\x03 \x01(\tR\x05newId\"\x05\n" +
896+ "\x06new_id\x18\x03 \x01(\tR\x05newId\"O\n" +
897+ "\x12MergeRequestOpened\x129\n" +
898+ "\rmerge_request\x18\x01 \x01(\v2\x14.ermite.MergeRequestR\fmergeRequest\"O\n" +
899+ "\x12MergeRequestMerged\x129\n" +
900+ "\rmerge_request\x18\x01 \x01(\v2\x14.ermite.MergeRequestR\fmergeRequest\"O\n" +
901+ "\x12MergeRequestClosed\x129\n" +
902+ "\rmerge_request\x18\x01 \x01(\v2\x14.ermite.MergeRequestR\fmergeRequest\"e\n" +
903+ "\fCommentAdded\x12\x12\n" +
904+ "\x04repo\x18\x01 \x01(\tR\x04repo\x12\x16\n" +
905+ "\x06number\x18\x02 \x01(\x03R\x06number\x12)\n" +
906+ "\acomment\x18\x03 \x01(\v2\x0f.ermite.CommentR\acomment\"\x05\n" +
620907 "\x03Gap2F\n" +
621908 "\fEventService\x126\n" +
622909 "\x05Watch\x12\x14.ermite.WatchRequest\x1a\x15.ermite.WatchResponse0\x01Bq\n" +
@@ -636,7 +923,7 @@ func file_ermite_event_proto_rawDescGZIP() []byte {
636923 return file_ermite_event_proto_rawDescData
637924 }
638925
639-var file_ermite_event_proto_msgTypes = make([]protoimpl.MessageInfo, 9)
926+var file_ermite_event_proto_msgTypes = make([]protoimpl.MessageInfo, 13)
640927 var file_ermite_event_proto_goTypes = []any{
641928 (*WatchRequest)(nil), // 0: ermite.WatchRequest
642929 (*WatchResponse)(nil), // 1: ermite.WatchResponse
@@ -646,28 +933,42 @@ var file_ermite_event_proto_goTypes = []any{
646933 (*RepoDeleted)(nil), // 5: ermite.RepoDeleted
647934 (*Pushed)(nil), // 6: ermite.Pushed
648935 (*RefUpdate)(nil), // 7: ermite.RefUpdate
649- (*Gap)(nil), // 8: ermite.Gap
650- (*timestamppb.Timestamp)(nil), // 9: google.protobuf.Timestamp
651- (*Repo)(nil), // 10: ermite.Repo
936+ (*MergeRequestOpened)(nil), // 8: ermite.MergeRequestOpened
937+ (*MergeRequestMerged)(nil), // 9: ermite.MergeRequestMerged
938+ (*MergeRequestClosed)(nil), // 10: ermite.MergeRequestClosed
939+ (*CommentAdded)(nil), // 11: ermite.CommentAdded
940+ (*Gap)(nil), // 12: ermite.Gap
941+ (*timestamppb.Timestamp)(nil), // 13: google.protobuf.Timestamp
942+ (*Repo)(nil), // 14: ermite.Repo
943+ (*MergeRequest)(nil), // 15: ermite.MergeRequest
944+ (*Comment)(nil), // 16: ermite.Comment
652945 }
653946 var file_ermite_event_proto_depIdxs = []int32{
654947 2, // 0: ermite.WatchResponse.event:type_name -> ermite.Event
655- 9, // 1: ermite.Event.time:type_name -> google.protobuf.Timestamp
948+ 13, // 1: ermite.Event.time:type_name -> google.protobuf.Timestamp
656949 3, // 2: ermite.Event.repo_created:type_name -> ermite.RepoCreated
657950 4, // 3: ermite.Event.repo_updated:type_name -> ermite.RepoUpdated
658951 5, // 4: ermite.Event.repo_deleted:type_name -> ermite.RepoDeleted
659952 6, // 5: ermite.Event.pushed:type_name -> ermite.Pushed
660- 8, // 6: ermite.Event.gap:type_name -> ermite.Gap
661- 10, // 7: ermite.RepoCreated.repo:type_name -> ermite.Repo
662- 10, // 8: ermite.RepoUpdated.repo:type_name -> ermite.Repo
663- 7, // 9: ermite.Pushed.ref_updates:type_name -> ermite.RefUpdate
664- 0, // 10: ermite.EventService.Watch:input_type -> ermite.WatchRequest
665- 1, // 11: ermite.EventService.Watch:output_type -> ermite.WatchResponse
666- 11, // [11:12] is the sub-list for method output_type
667- 10, // [10:11] is the sub-list for method input_type
668- 10, // [10:10] is the sub-list for extension type_name
669- 10, // [10:10] is the sub-list for extension extendee
670- 0, // [0:10] is the sub-list for field type_name
953+ 12, // 6: ermite.Event.gap:type_name -> ermite.Gap
954+ 8, // 7: ermite.Event.merge_request_opened:type_name -> ermite.MergeRequestOpened
955+ 9, // 8: ermite.Event.merge_request_merged:type_name -> ermite.MergeRequestMerged
956+ 10, // 9: ermite.Event.merge_request_closed:type_name -> ermite.MergeRequestClosed
957+ 11, // 10: ermite.Event.comment_added:type_name -> ermite.CommentAdded
958+ 14, // 11: ermite.RepoCreated.repo:type_name -> ermite.Repo
959+ 14, // 12: ermite.RepoUpdated.repo:type_name -> ermite.Repo
960+ 7, // 13: ermite.Pushed.ref_updates:type_name -> ermite.RefUpdate
961+ 15, // 14: ermite.MergeRequestOpened.merge_request:type_name -> ermite.MergeRequest
962+ 15, // 15: ermite.MergeRequestMerged.merge_request:type_name -> ermite.MergeRequest
963+ 15, // 16: ermite.MergeRequestClosed.merge_request:type_name -> ermite.MergeRequest
964+ 16, // 17: ermite.CommentAdded.comment:type_name -> ermite.Comment
965+ 0, // 18: ermite.EventService.Watch:input_type -> ermite.WatchRequest
966+ 1, // 19: ermite.EventService.Watch:output_type -> ermite.WatchResponse
967+ 19, // [19:20] is the sub-list for method output_type
968+ 18, // [18:19] is the sub-list for method input_type
969+ 18, // [18:18] is the sub-list for extension type_name
970+ 18, // [18:18] is the sub-list for extension extendee
971+ 0, // [0:18] is the sub-list for field type_name
671972 }
672973
673974 func init() { file_ermite_event_proto_init() }
@@ -675,6 +976,7 @@ func file_ermite_event_proto_init() {
675976 if File_ermite_event_proto != nil {
676977 return
677978 }
979+ file_ermite_merge_request_proto_init()
678980 file_ermite_repo_proto_init()
679981 file_ermite_event_proto_msgTypes[2].OneofWrappers = []any{
680982 (*Event_RepoCreated)(nil),
@@ -682,6 +984,10 @@ func file_ermite_event_proto_init() {
682984 (*Event_RepoDeleted)(nil),
683985 (*Event_Pushed)(nil),
684986 (*Event_Gap)(nil),
987+ (*Event_MergeRequestOpened)(nil),
988+ (*Event_MergeRequestMerged)(nil),
989+ (*Event_MergeRequestClosed)(nil),
990+ (*Event_CommentAdded)(nil),
685991 }
686992 type x struct{}
687993 out := protoimpl.TypeBuilder{
@@ -689,7 +995,7 @@ func file_ermite_event_proto_init() {
689995 GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
690996 RawDescriptor: unsafe.Slice(unsafe.StringData(file_ermite_event_proto_rawDesc), len(file_ermite_event_proto_rawDesc)),
691997 NumEnums: 0,
692- NumMessages: 9,
998+ NumMessages: 13,
693999 NumExtensions: 0,
6941000 NumServices: 1,
6951001 },
@@ -148,6 +148,10 @@ type Event struct {
148 // *Event_RepoDeleted148 // *Event_RepoDeleted
149 // *Event_Pushed149 // *Event_Pushed
150 // *Event_Gap150 // *Event_Gap
151+ // *Event_MergeRequestOpened
152+ // *Event_MergeRequestMerged
153+ // *Event_MergeRequestClosed
154+ // *Event_CommentAdded
151 Kind isEvent_Kind `protobuf_oneof:"kind"`155 Kind isEvent_Kind `protobuf_oneof:"kind"`
152 unknownFields protoimpl.UnknownFields156 unknownFields protoimpl.UnknownFields
153 sizeCache protoimpl.SizeCache157 sizeCache protoimpl.SizeCache
@@ -249,6 +253,42 @@ func (x *Event) GetGap() *Gap {
249 return nil253 return nil
250 }254 }
251 255
256+func (x *Event) GetMergeRequestOpened() *MergeRequestOpened {
257+ if x != nil {
258+ if x, ok := x.Kind.(*Event_MergeRequestOpened); ok {
259+ return x.MergeRequestOpened
260+ }
261+ }
262+ return nil
263+}
264+
265+func (x *Event) GetMergeRequestMerged() *MergeRequestMerged {
266+ if x != nil {
267+ if x, ok := x.Kind.(*Event_MergeRequestMerged); ok {
268+ return x.MergeRequestMerged
269+ }
270+ }
271+ return nil
272+}
273+
274+func (x *Event) GetMergeRequestClosed() *MergeRequestClosed {
275+ if x != nil {
276+ if x, ok := x.Kind.(*Event_MergeRequestClosed); ok {
277+ return x.MergeRequestClosed
278+ }
279+ }
280+ return nil
281+}
282+
283+func (x *Event) GetCommentAdded() *CommentAdded {
284+ if x != nil {
285+ if x, ok := x.Kind.(*Event_CommentAdded); ok {
286+ return x.CommentAdded
287+ }
288+ }
289+ return nil
290+}
291+
252 type isEvent_Kind interface {292 type isEvent_Kind interface {
253 isEvent_Kind()293 isEvent_Kind()
254 }294 }
@@ -278,6 +318,26 @@ type Event_Gap struct {
278 Gap *Gap `protobuf:"bytes,7,opt,name=gap,proto3,oneof"`318 Gap *Gap `protobuf:"bytes,7,opt,name=gap,proto3,oneof"`
279 }319 }
280 320
321+type Event_MergeRequestOpened struct {
322+ // MergeRequestOpened: a merge request was opened.
323+ MergeRequestOpened *MergeRequestOpened `protobuf:"bytes,8,opt,name=merge_request_opened,json=mergeRequestOpened,proto3,oneof"`
324+}
325+
326+type Event_MergeRequestMerged struct {
327+ // MergeRequestMerged: a merge request was merged.
328+ MergeRequestMerged *MergeRequestMerged `protobuf:"bytes,9,opt,name=merge_request_merged,json=mergeRequestMerged,proto3,oneof"`
329+}
330+
331+type Event_MergeRequestClosed struct {
332+ // MergeRequestClosed: a merge request was closed without merging.
333+ MergeRequestClosed *MergeRequestClosed `protobuf:"bytes,10,opt,name=merge_request_closed,json=mergeRequestClosed,proto3,oneof"`
334+}
335+
336+type Event_CommentAdded struct {
337+ // CommentAdded: a comment was added to a merge request.
338+ CommentAdded *CommentAdded `protobuf:"bytes,11,opt,name=comment_added,json=commentAdded,proto3,oneof"`
339+}
340+
281 func (*Event_RepoCreated) isEvent_Kind() {}341 func (*Event_RepoCreated) isEvent_Kind() {}
282 342
283 func (*Event_RepoUpdated) isEvent_Kind() {}343 func (*Event_RepoUpdated) isEvent_Kind() {}
@@ -288,6 +348,14 @@ func (*Event_Pushed) isEvent_Kind() {}
288 348
289 func (*Event_Gap) isEvent_Kind() {}349 func (*Event_Gap) isEvent_Kind() {}
290 350
351+func (*Event_MergeRequestOpened) isEvent_Kind() {}
352+
353+func (*Event_MergeRequestMerged) isEvent_Kind() {}
354+
355+func (*Event_MergeRequestClosed) isEvent_Kind() {}
356+
357+func (*Event_CommentAdded) isEvent_Kind() {}
358+
291 // RepoCreated reports a new repository.359 // RepoCreated reports a new repository.
292 type RepoCreated struct {360 type RepoCreated struct {
293 state protoimpl.MessageState `protogen:"open.v1"`361 state protoimpl.MessageState `protogen:"open.v1"`
@@ -545,9 +613,213 @@ func (x *RefUpdate) GetNewId() string {
545 return ""613 return ""
546 }614 }
547 615
616+// MergeRequestOpened reports a new merge request.
617+type MergeRequestOpened struct {
618+ state protoimpl.MessageState `protogen:"open.v1"`
619+ // MergeRequest is the new merge request.
620+ MergeRequest *MergeRequest `protobuf:"bytes,1,opt,name=merge_request,json=mergeRequest,proto3" json:"merge_request,omitempty"`
621+ unknownFields protoimpl.UnknownFields
622+ sizeCache protoimpl.SizeCache
623+}
624+
625+func (x *MergeRequestOpened) Reset() {
626+ *x = MergeRequestOpened{}
627+ mi := &file_ermite_event_proto_msgTypes[8]
628+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
629+ ms.StoreMessageInfo(mi)
630+}
631+
632+func (x *MergeRequestOpened) String() string {
633+ return protoimpl.X.MessageStringOf(x)
634+}
635+
636+func (*MergeRequestOpened) ProtoMessage() {}
637+
638+func (x *MergeRequestOpened) ProtoReflect() protoreflect.Message {
639+ mi := &file_ermite_event_proto_msgTypes[8]
640+ if x != nil {
641+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
642+ if ms.LoadMessageInfo() == nil {
643+ ms.StoreMessageInfo(mi)
644+ }
645+ return ms
646+ }
647+ return mi.MessageOf(x)
648+}
649+
650+// Deprecated: Use MergeRequestOpened.ProtoReflect.Descriptor instead.
651+func (*MergeRequestOpened) Descriptor() ([]byte, []int) {
652+ return file_ermite_event_proto_rawDescGZIP(), []int{8}
653+}
654+
655+func (x *MergeRequestOpened) GetMergeRequest() *MergeRequest {
656+ if x != nil {
657+ return x.MergeRequest
658+ }
659+ return nil
660+}
661+
662+// MergeRequestMerged reports a merged merge request. It is published before
663+// the Pushed event of the target branch.
664+type MergeRequestMerged struct {
665+ state protoimpl.MessageState `protogen:"open.v1"`
666+ // MergeRequest is the merge request after the merge.
667+ MergeRequest *MergeRequest `protobuf:"bytes,1,opt,name=merge_request,json=mergeRequest,proto3" json:"merge_request,omitempty"`
668+ unknownFields protoimpl.UnknownFields
669+ sizeCache protoimpl.SizeCache
670+}
671+
672+func (x *MergeRequestMerged) Reset() {
673+ *x = MergeRequestMerged{}
674+ mi := &file_ermite_event_proto_msgTypes[9]
675+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
676+ ms.StoreMessageInfo(mi)
677+}
678+
679+func (x *MergeRequestMerged) String() string {
680+ return protoimpl.X.MessageStringOf(x)
681+}
682+
683+func (*MergeRequestMerged) ProtoMessage() {}
684+
685+func (x *MergeRequestMerged) ProtoReflect() protoreflect.Message {
686+ mi := &file_ermite_event_proto_msgTypes[9]
687+ if x != nil {
688+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
689+ if ms.LoadMessageInfo() == nil {
690+ ms.StoreMessageInfo(mi)
691+ }
692+ return ms
693+ }
694+ return mi.MessageOf(x)
695+}
696+
697+// Deprecated: Use MergeRequestMerged.ProtoReflect.Descriptor instead.
698+func (*MergeRequestMerged) Descriptor() ([]byte, []int) {
699+ return file_ermite_event_proto_rawDescGZIP(), []int{9}
700+}
701+
702+func (x *MergeRequestMerged) GetMergeRequest() *MergeRequest {
703+ if x != nil {
704+ return x.MergeRequest
705+ }
706+ return nil
707+}
708+
709+// MergeRequestClosed reports a merge request closed without merging.
710+type MergeRequestClosed struct {
711+ state protoimpl.MessageState `protogen:"open.v1"`
712+ // MergeRequest is the merge request after closing.
713+ MergeRequest *MergeRequest `protobuf:"bytes,1,opt,name=merge_request,json=mergeRequest,proto3" json:"merge_request,omitempty"`
714+ unknownFields protoimpl.UnknownFields
715+ sizeCache protoimpl.SizeCache
716+}
717+
718+func (x *MergeRequestClosed) Reset() {
719+ *x = MergeRequestClosed{}
720+ mi := &file_ermite_event_proto_msgTypes[10]
721+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
722+ ms.StoreMessageInfo(mi)
723+}
724+
725+func (x *MergeRequestClosed) String() string {
726+ return protoimpl.X.MessageStringOf(x)
727+}
728+
729+func (*MergeRequestClosed) ProtoMessage() {}
730+
731+func (x *MergeRequestClosed) ProtoReflect() protoreflect.Message {
732+ mi := &file_ermite_event_proto_msgTypes[10]
733+ if x != nil {
734+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
735+ if ms.LoadMessageInfo() == nil {
736+ ms.StoreMessageInfo(mi)
737+ }
738+ return ms
739+ }
740+ return mi.MessageOf(x)
741+}
742+
743+// Deprecated: Use MergeRequestClosed.ProtoReflect.Descriptor instead.
744+func (*MergeRequestClosed) Descriptor() ([]byte, []int) {
745+ return file_ermite_event_proto_rawDescGZIP(), []int{10}
746+}
747+
748+func (x *MergeRequestClosed) GetMergeRequest() *MergeRequest {
749+ if x != nil {
750+ return x.MergeRequest
751+ }
752+ return nil
753+}
754+
755+// CommentAdded reports a new comment on a merge request.
756+type CommentAdded struct {
757+ state protoimpl.MessageState `protogen:"open.v1"`
758+ // Repo is the name of the repository.
759+ Repo string `protobuf:"bytes,1,opt,name=repo,proto3" json:"repo,omitempty"`
760+ // Number is the number of the merge request.
761+ Number int64 `protobuf:"varint,2,opt,name=number,proto3" json:"number,omitempty"`
762+ // Comment is the new comment.
763+ Comment *Comment `protobuf:"bytes,3,opt,name=comment,proto3" json:"comment,omitempty"`
764+ unknownFields protoimpl.UnknownFields
765+ sizeCache protoimpl.SizeCache
766+}
767+
768+func (x *CommentAdded) Reset() {
769+ *x = CommentAdded{}
770+ mi := &file_ermite_event_proto_msgTypes[11]
771+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
772+ ms.StoreMessageInfo(mi)
773+}
774+
775+func (x *CommentAdded) String() string {
776+ return protoimpl.X.MessageStringOf(x)
777+}
778+
779+func (*CommentAdded) ProtoMessage() {}
780+
781+func (x *CommentAdded) ProtoReflect() protoreflect.Message {
782+ mi := &file_ermite_event_proto_msgTypes[11]
783+ if x != nil {
784+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
785+ if ms.LoadMessageInfo() == nil {
786+ ms.StoreMessageInfo(mi)
787+ }
788+ return ms
789+ }
790+ return mi.MessageOf(x)
791+}
792+
793+// Deprecated: Use CommentAdded.ProtoReflect.Descriptor instead.
794+func (*CommentAdded) Descriptor() ([]byte, []int) {
795+ return file_ermite_event_proto_rawDescGZIP(), []int{11}
796+}
797+
798+func (x *CommentAdded) GetRepo() string {
799+ if x != nil {
800+ return x.Repo
801+ }
802+ return ""
803+}
804+
805+func (x *CommentAdded) GetNumber() int64 {
806+ if x != nil {
807+ return x.Number
808+ }
809+ return 0
810+}
811+
812+func (x *CommentAdded) GetComment() *Comment {
813+ if x != nil {
814+ return x.Comment
815+ }
816+ return nil
817+}
818+
548 // Gap tells the client that events were lost: the requested resume point is819 // Gap tells the client that events were lost: the requested resume point is
549 // older than the server's buffer. It has no fields. The client should reload820 // older than the server's buffer. It has no fields. The client should reload
550-// the state it displays (repositories, refs) and carry on with the next events.821+// the state it displays (repositories, refs, merge requests and their
822+// comments) and carry on with the next events.
551 type Gap struct {823 type Gap struct {
552 state protoimpl.MessageState `protogen:"open.v1"`824 state protoimpl.MessageState `protogen:"open.v1"`
553 unknownFields protoimpl.UnknownFields825 unknownFields protoimpl.UnknownFields
@@ -556,7 +828,7 @@ type Gap struct {
556 828
557 func (x *Gap) Reset() {829 func (x *Gap) Reset() {
558 *x = Gap{}830 *x = Gap{}
559- mi := &file_ermite_event_proto_msgTypes[8]831+ mi := &file_ermite_event_proto_msgTypes[12]
560 ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))832 ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
561 ms.StoreMessageInfo(mi)833 ms.StoreMessageInfo(mi)
562 }834 }
@@ -568,7 +840,7 @@ func (x *Gap) String() string {
568 func (*Gap) ProtoMessage() {}840 func (*Gap) ProtoMessage() {}
569 841
570 func (x *Gap) ProtoReflect() protoreflect.Message {842 func (x *Gap) ProtoReflect() protoreflect.Message {
571- mi := &file_ermite_event_proto_msgTypes[8]843+ mi := &file_ermite_event_proto_msgTypes[12]
572 if x != nil {844 if x != nil {
573 ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))845 ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
574 if ms.LoadMessageInfo() == nil {846 if ms.LoadMessageInfo() == nil {
@@ -581,19 +853,19 @@ func (x *Gap) ProtoReflect() protoreflect.Message {
581 853
582 // Deprecated: Use Gap.ProtoReflect.Descriptor instead.854 // Deprecated: Use Gap.ProtoReflect.Descriptor instead.
583 func (*Gap) Descriptor() ([]byte, []int) {855 func (*Gap) Descriptor() ([]byte, []int) {
584- return file_ermite_event_proto_rawDescGZIP(), []int{8}856+ return file_ermite_event_proto_rawDescGZIP(), []int{12}
585 }857 }
586 858
587 var File_ermite_event_proto protoreflect.FileDescriptor859 var File_ermite_event_proto protoreflect.FileDescriptor
588 860
589 const file_ermite_event_proto_rawDesc = "" +861 const file_ermite_event_proto_rawDesc = "" +
590 "\n" +862 "\n" +
591- "\x12ermite/event.proto\x12\x06ermite\x1a\x11ermite/repo.proto\x1a\x1fgoogle/protobuf/timestamp.proto\"I\n" +863+ "\x12ermite/event.proto\x12\x06ermite\x1a\x1aermite/merge_request.proto\x1a\x11ermite/repo.proto\x1a\x1fgoogle/protobuf/timestamp.proto\"I\n" +
592 "\fWatchRequest\x12\x16\n" +864 "\fWatchRequest\x12\x16\n" +
593 "\x06topics\x18\x01 \x03(\tR\x06topics\x12!\n" +865 "\x06topics\x18\x01 \x03(\tR\x06topics\x12!\n" +
594 "\fresume_after\x18\x02 \x01(\tR\vresumeAfter\"4\n" +866 "\fresume_after\x18\x02 \x01(\tR\vresumeAfter\"4\n" +
595 "\rWatchResponse\x12#\n" +867 "\rWatchResponse\x12#\n" +
596- "\x05event\x18\x01 \x01(\v2\r.ermite.EventR\x05event\"\xc8\x02\n" +868+ "\x05event\x18\x01 \x01(\v2\r.ermite.EventR\x05event\"\xf5\x04\n" +
597 "\x05Event\x12\x0e\n" +869 "\x05Event\x12\x0e\n" +
598 "\x02id\x18\x01 \x01(\tR\x02id\x12.\n" +870 "\x02id\x18\x01 \x01(\tR\x02id\x12.\n" +
599 "\x04time\x18\x02 \x01(\v2\x1a.google.protobuf.TimestampR\x04time\x128\n" +871 "\x04time\x18\x02 \x01(\v2\x1a.google.protobuf.TimestampR\x04time\x128\n" +
@@ -601,7 +873,12 @@ const file_ermite_event_proto_rawDesc = "" +
601 "\frepo_updated\x18\x04 \x01(\v2\x13.ermite.RepoUpdatedH\x00R\vrepoUpdated\x128\n" +873 "\frepo_updated\x18\x04 \x01(\v2\x13.ermite.RepoUpdatedH\x00R\vrepoUpdated\x128\n" +
602 "\frepo_deleted\x18\x05 \x01(\v2\x13.ermite.RepoDeletedH\x00R\vrepoDeleted\x12(\n" +874 "\frepo_deleted\x18\x05 \x01(\v2\x13.ermite.RepoDeletedH\x00R\vrepoDeleted\x12(\n" +
603 "\x06pushed\x18\x06 \x01(\v2\x0e.ermite.PushedH\x00R\x06pushed\x12\x1f\n" +875 "\x06pushed\x18\x06 \x01(\v2\x0e.ermite.PushedH\x00R\x06pushed\x12\x1f\n" +
604- "\x03gap\x18\a \x01(\v2\v.ermite.GapH\x00R\x03gapB\x06\n" +876+ "\x03gap\x18\a \x01(\v2\v.ermite.GapH\x00R\x03gap\x12N\n" +
877+ "\x14merge_request_opened\x18\b \x01(\v2\x1a.ermite.MergeRequestOpenedH\x00R\x12mergeRequestOpened\x12N\n" +
878+ "\x14merge_request_merged\x18\t \x01(\v2\x1a.ermite.MergeRequestMergedH\x00R\x12mergeRequestMerged\x12N\n" +
879+ "\x14merge_request_closed\x18\n" +
880+ " \x01(\v2\x1a.ermite.MergeRequestClosedH\x00R\x12mergeRequestClosed\x12;\n" +
881+ "\rcomment_added\x18\v \x01(\v2\x14.ermite.CommentAddedH\x00R\fcommentAddedB\x06\n" +
605 "\x04kind\"/\n" +882 "\x04kind\"/\n" +
606 "\vRepoCreated\x12 \n" +883 "\vRepoCreated\x12 \n" +
607 "\x04repo\x18\x01 \x01(\v2\f.ermite.RepoR\x04repo\"/\n" +884 "\x04repo\x18\x01 \x01(\v2\f.ermite.RepoR\x04repo\"/\n" +
@@ -616,7 +893,17 @@ const file_ermite_event_proto_rawDesc = "" +
616 "\tRefUpdate\x12\x10\n" +893 "\tRefUpdate\x12\x10\n" +
617 "\x03ref\x18\x01 \x01(\tR\x03ref\x12\x15\n" +894 "\x03ref\x18\x01 \x01(\tR\x03ref\x12\x15\n" +
618 "\x06old_id\x18\x02 \x01(\tR\x05oldId\x12\x15\n" +895 "\x06old_id\x18\x02 \x01(\tR\x05oldId\x12\x15\n" +
619- "\x06new_id\x18\x03 \x01(\tR\x05newId\"\x05\n" +896+ "\x06new_id\x18\x03 \x01(\tR\x05newId\"O\n" +
897+ "\x12MergeRequestOpened\x129\n" +
898+ "\rmerge_request\x18\x01 \x01(\v2\x14.ermite.MergeRequestR\fmergeRequest\"O\n" +
899+ "\x12MergeRequestMerged\x129\n" +
900+ "\rmerge_request\x18\x01 \x01(\v2\x14.ermite.MergeRequestR\fmergeRequest\"O\n" +
901+ "\x12MergeRequestClosed\x129\n" +
902+ "\rmerge_request\x18\x01 \x01(\v2\x14.ermite.MergeRequestR\fmergeRequest\"e\n" +
903+ "\fCommentAdded\x12\x12\n" +
904+ "\x04repo\x18\x01 \x01(\tR\x04repo\x12\x16\n" +
905+ "\x06number\x18\x02 \x01(\x03R\x06number\x12)\n" +
906+ "\acomment\x18\x03 \x01(\v2\x0f.ermite.CommentR\acomment\"\x05\n" +
620 "\x03Gap2F\n" +907 "\x03Gap2F\n" +
621 "\fEventService\x126\n" +908 "\fEventService\x126\n" +
622 "\x05Watch\x12\x14.ermite.WatchRequest\x1a\x15.ermite.WatchResponse0\x01Bq\n" +909 "\x05Watch\x12\x14.ermite.WatchRequest\x1a\x15.ermite.WatchResponse0\x01Bq\n" +
@@ -636,7 +923,7 @@ func file_ermite_event_proto_rawDescGZIP() []byte {
636 return file_ermite_event_proto_rawDescData923 return file_ermite_event_proto_rawDescData
637 }924 }
638 925
639-var file_ermite_event_proto_msgTypes = make([]protoimpl.MessageInfo, 9)926+var file_ermite_event_proto_msgTypes = make([]protoimpl.MessageInfo, 13)
640 var file_ermite_event_proto_goTypes = []any{927 var file_ermite_event_proto_goTypes = []any{
641 (*WatchRequest)(nil), // 0: ermite.WatchRequest928 (*WatchRequest)(nil), // 0: ermite.WatchRequest
642 (*WatchResponse)(nil), // 1: ermite.WatchResponse929 (*WatchResponse)(nil), // 1: ermite.WatchResponse
@@ -646,28 +933,42 @@ var file_ermite_event_proto_goTypes = []any{
646 (*RepoDeleted)(nil), // 5: ermite.RepoDeleted933 (*RepoDeleted)(nil), // 5: ermite.RepoDeleted
647 (*Pushed)(nil), // 6: ermite.Pushed934 (*Pushed)(nil), // 6: ermite.Pushed
648 (*RefUpdate)(nil), // 7: ermite.RefUpdate935 (*RefUpdate)(nil), // 7: ermite.RefUpdate
649- (*Gap)(nil), // 8: ermite.Gap936+ (*MergeRequestOpened)(nil), // 8: ermite.MergeRequestOpened
650- (*timestamppb.Timestamp)(nil), // 9: google.protobuf.Timestamp937+ (*MergeRequestMerged)(nil), // 9: ermite.MergeRequestMerged
651- (*Repo)(nil), // 10: ermite.Repo938+ (*MergeRequestClosed)(nil), // 10: ermite.MergeRequestClosed
939+ (*CommentAdded)(nil), // 11: ermite.CommentAdded
940+ (*Gap)(nil), // 12: ermite.Gap
941+ (*timestamppb.Timestamp)(nil), // 13: google.protobuf.Timestamp
942+ (*Repo)(nil), // 14: ermite.Repo
943+ (*MergeRequest)(nil), // 15: ermite.MergeRequest
944+ (*Comment)(nil), // 16: ermite.Comment
652 }945 }
653 var file_ermite_event_proto_depIdxs = []int32{946 var file_ermite_event_proto_depIdxs = []int32{
654 2, // 0: ermite.WatchResponse.event:type_name -> ermite.Event947 2, // 0: ermite.WatchResponse.event:type_name -> ermite.Event
655- 9, // 1: ermite.Event.time:type_name -> google.protobuf.Timestamp948+ 13, // 1: ermite.Event.time:type_name -> google.protobuf.Timestamp
656 3, // 2: ermite.Event.repo_created:type_name -> ermite.RepoCreated949 3, // 2: ermite.Event.repo_created:type_name -> ermite.RepoCreated
657 4, // 3: ermite.Event.repo_updated:type_name -> ermite.RepoUpdated950 4, // 3: ermite.Event.repo_updated:type_name -> ermite.RepoUpdated
658 5, // 4: ermite.Event.repo_deleted:type_name -> ermite.RepoDeleted951 5, // 4: ermite.Event.repo_deleted:type_name -> ermite.RepoDeleted
659 6, // 5: ermite.Event.pushed:type_name -> ermite.Pushed952 6, // 5: ermite.Event.pushed:type_name -> ermite.Pushed
660- 8, // 6: ermite.Event.gap:type_name -> ermite.Gap953+ 12, // 6: ermite.Event.gap:type_name -> ermite.Gap
661- 10, // 7: ermite.RepoCreated.repo:type_name -> ermite.Repo954+ 8, // 7: ermite.Event.merge_request_opened:type_name -> ermite.MergeRequestOpened
662- 10, // 8: ermite.RepoUpdated.repo:type_name -> ermite.Repo955+ 9, // 8: ermite.Event.merge_request_merged:type_name -> ermite.MergeRequestMerged
663- 7, // 9: ermite.Pushed.ref_updates:type_name -> ermite.RefUpdate956+ 10, // 9: ermite.Event.merge_request_closed:type_name -> ermite.MergeRequestClosed
664- 0, // 10: ermite.EventService.Watch:input_type -> ermite.WatchRequest957+ 11, // 10: ermite.Event.comment_added:type_name -> ermite.CommentAdded
665- 1, // 11: ermite.EventService.Watch:output_type -> ermite.WatchResponse958+ 14, // 11: ermite.RepoCreated.repo:type_name -> ermite.Repo
666- 11, // [11:12] is the sub-list for method output_type959+ 14, // 12: ermite.RepoUpdated.repo:type_name -> ermite.Repo
667- 10, // [10:11] is the sub-list for method input_type960+ 7, // 13: ermite.Pushed.ref_updates:type_name -> ermite.RefUpdate
668- 10, // [10:10] is the sub-list for extension type_name961+ 15, // 14: ermite.MergeRequestOpened.merge_request:type_name -> ermite.MergeRequest
669- 10, // [10:10] is the sub-list for extension extendee962+ 15, // 15: ermite.MergeRequestMerged.merge_request:type_name -> ermite.MergeRequest
670- 0, // [0:10] is the sub-list for field type_name963+ 15, // 16: ermite.MergeRequestClosed.merge_request:type_name -> ermite.MergeRequest
964+ 16, // 17: ermite.CommentAdded.comment:type_name -> ermite.Comment
965+ 0, // 18: ermite.EventService.Watch:input_type -> ermite.WatchRequest
966+ 1, // 19: ermite.EventService.Watch:output_type -> ermite.WatchResponse
967+ 19, // [19:20] is the sub-list for method output_type
968+ 18, // [18:19] is the sub-list for method input_type
969+ 18, // [18:18] is the sub-list for extension type_name
970+ 18, // [18:18] is the sub-list for extension extendee
971+ 0, // [0:18] is the sub-list for field type_name
671 }972 }
672 973
673 func init() { file_ermite_event_proto_init() }974 func init() { file_ermite_event_proto_init() }
@@ -675,6 +976,7 @@ func file_ermite_event_proto_init() {
675 if File_ermite_event_proto != nil {976 if File_ermite_event_proto != nil {
676 return977 return
677 }978 }
979+ file_ermite_merge_request_proto_init()
678 file_ermite_repo_proto_init()980 file_ermite_repo_proto_init()
679 file_ermite_event_proto_msgTypes[2].OneofWrappers = []any{981 file_ermite_event_proto_msgTypes[2].OneofWrappers = []any{
680 (*Event_RepoCreated)(nil),982 (*Event_RepoCreated)(nil),
@@ -682,6 +984,10 @@ func file_ermite_event_proto_init() {
682 (*Event_RepoDeleted)(nil),984 (*Event_RepoDeleted)(nil),
683 (*Event_Pushed)(nil),985 (*Event_Pushed)(nil),
684 (*Event_Gap)(nil),986 (*Event_Gap)(nil),
987+ (*Event_MergeRequestOpened)(nil),
988+ (*Event_MergeRequestMerged)(nil),
989+ (*Event_MergeRequestClosed)(nil),
990+ (*Event_CommentAdded)(nil),
685 }991 }
686 type x struct{}992 type x struct{}
687 out := protoimpl.TypeBuilder{993 out := protoimpl.TypeBuilder{
@@ -689,7 +995,7 @@ func file_ermite_event_proto_init() {
689 GoPackagePath: reflect.TypeOf(x{}).PkgPath(),995 GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
690 RawDescriptor: unsafe.Slice(unsafe.StringData(file_ermite_event_proto_rawDesc), len(file_ermite_event_proto_rawDesc)),996 RawDescriptor: unsafe.Slice(unsafe.StringData(file_ermite_event_proto_rawDesc), len(file_ermite_event_proto_rawDesc)),
691 NumEnums: 0,997 NumEnums: 0,
692- NumMessages: 9,998+ NumMessages: 13,
693 NumExtensions: 0,999 NumExtensions: 0,
694 NumServices: 1,1000 NumServices: 1,
695 },1001 },
A gen/ermite/merge_request.pb.go +1716 -0 generated file: collapsed
M gen/ermite/repo.pb.go +179 -81 generated file: collapsed
M gen/ermite/server.pb.go +148 -9 generated file: collapsed
A gen/ermite/session.pb.go +268 -0 generated file: collapsed
M gen/ermite/types.pb.go +78 -8 generated file: collapsed
M gen_test.go +61 -0
@@ -30,7 +30,18 @@ func TestProcedures(t *testing.T) {
3030 {ermiteconnect.TokenServiceListTokensProcedure, "/ermite.TokenService/ListTokens"},
3131 {ermiteconnect.TokenServiceRevokeTokenProcedure, "/ermite.TokenService/RevokeToken"},
3232 {ermiteconnect.EventServiceWatchProcedure, "/ermite.EventService/Watch"},
33+ {ermiteconnect.MergeRequestServiceCreateMergeRequestProcedure, "/ermite.MergeRequestService/CreateMergeRequest"},
34+ {ermiteconnect.MergeRequestServiceListMergeRequestsProcedure, "/ermite.MergeRequestService/ListMergeRequests"},
35+ {ermiteconnect.MergeRequestServiceGetMergeRequestProcedure, "/ermite.MergeRequestService/GetMergeRequest"},
36+ {ermiteconnect.MergeRequestServiceAddCommentProcedure, "/ermite.MergeRequestService/AddComment"},
37+ {ermiteconnect.MergeRequestServiceResolveCommentProcedure, "/ermite.MergeRequestService/ResolveComment"},
38+ {ermiteconnect.MergeRequestServiceCloseMergeRequestProcedure, "/ermite.MergeRequestService/CloseMergeRequest"},
39+ {ermiteconnect.MergeRequestServiceMergeMergeRequestProcedure, "/ermite.MergeRequestService/MergeMergeRequest"},
40+ {ermiteconnect.MergeRequestServiceMarkReadProcedure, "/ermite.MergeRequestService/MarkRead"},
41+ {ermiteconnect.SessionServiceCreateLoginLinkProcedure, "/ermite.SessionService/CreateLoginLink"},
42+ {ermiteconnect.SessionServiceSignOutEverywhereProcedure, "/ermite.SessionService/SignOutEverywhere"},
3343 {ermiteconnect.ServerServiceGetVersionProcedure, "/ermite.ServerService/GetVersion"},
44+ {ermiteconnect.ServerServiceGetSigningKeyProcedure, "/ermite.ServerService/GetSigningKey"},
3445 }
3546 for _, tt := range tests {
3647 if tt.got != tt.want {
@@ -50,6 +61,15 @@ func TestEnumNumbers(t *testing.T) {
5061 if got := ermite.Visibility_VISIBILITY_PRIVATE; got != 2 {
5162 t.Errorf("VISIBILITY_PRIVATE = %d, want 2", got)
5263 }
64+ if got := ermite.MergeMode_MERGE_MODE_SQUASH; got != 2 {
65+ t.Errorf("MERGE_MODE_SQUASH = %d, want 2", got)
66+ }
67+ if got := ermite.MergeRequestState_MERGE_REQUEST_STATE_CLOSED; got != 3 {
68+ t.Errorf("MERGE_REQUEST_STATE_CLOSED = %d, want 3", got)
69+ }
70+ if got := ermite.DiffSide_DIFF_SIDE_NEW; got != 2 {
71+ t.Errorf("DIFF_SIDE_NEW = %d, want 2", got)
72+ }
5373 }
5474
5575 // TestEventRoundTrip checks that an event with a oneof kind survives the wire.
@@ -90,3 +110,44 @@ func TestUpdateRepoMask(t *testing.T) {
90110 t.Errorf("paths = %v", got)
91111 }
92112 }
113+
114+// TestMergeRequestEventRoundTrip checks the merge request event kinds and the
115+// repository settings survive the wire.
116+func TestMergeRequestEventRoundTrip(t *testing.T) {
117+ in := &ermite.WatchResponse{Event: &ermite.Event{
118+ Id: "43",
119+ Kind: &ermite.Event_CommentAdded{CommentAdded: &ermite.CommentAdded{
120+ Repo: "ermite",
121+ Number: 7,
122+ Comment: &ermite.Comment{
123+ Id: "c1", Path: "a.go", Line: 3, Side: ermite.DiffSide_DIFF_SIDE_OLD, Text: "hi",
124+ },
125+ }},
126+ }}
127+ b, err := proto.Marshal(in)
128+ if err != nil {
129+ t.Fatal(err)
130+ }
131+ var out ermite.WatchResponse
132+ if err := proto.Unmarshal(b, &out); err != nil {
133+ t.Fatal(err)
134+ }
135+ if !proto.Equal(in, &out) {
136+ t.Errorf("round trip mismatch: %v != %v", &out, in)
137+ }
138+ if got := out.GetEvent().GetCommentAdded().GetNumber(); got != 7 {
139+ t.Errorf("number = %d, want 7", got)
140+ }
141+
142+ req := &ermite.UpdateRepoRequest{
143+ Repo: "ermite",
144+ Settings: &ermite.RepoSettings{
145+ AllowedMergeModes: []ermite.MergeMode{ermite.MergeMode_MERGE_MODE_SQUASH},
146+ FailingChecksBlock: true,
147+ },
148+ UpdateMask: &fieldmaskpb.FieldMask{Paths: []string{"settings.allowed_merge_modes", "settings.failing_checks_block"}},
149+ }
150+ if got := req.GetSettings().GetAllowedMergeModes(); len(got) != 1 || got[0] != ermite.MergeMode_MERGE_MODE_SQUASH {
151+ t.Errorf("modes = %v", got)
152+ }
153+}
@@ -30,7 +30,18 @@ func TestProcedures(t *testing.T) {
30 {ermiteconnect.TokenServiceListTokensProcedure, "/ermite.TokenService/ListTokens"},30 {ermiteconnect.TokenServiceListTokensProcedure, "/ermite.TokenService/ListTokens"},
31 {ermiteconnect.TokenServiceRevokeTokenProcedure, "/ermite.TokenService/RevokeToken"},31 {ermiteconnect.TokenServiceRevokeTokenProcedure, "/ermite.TokenService/RevokeToken"},
32 {ermiteconnect.EventServiceWatchProcedure, "/ermite.EventService/Watch"},32 {ermiteconnect.EventServiceWatchProcedure, "/ermite.EventService/Watch"},
33+ {ermiteconnect.MergeRequestServiceCreateMergeRequestProcedure, "/ermite.MergeRequestService/CreateMergeRequest"},
34+ {ermiteconnect.MergeRequestServiceListMergeRequestsProcedure, "/ermite.MergeRequestService/ListMergeRequests"},
35+ {ermiteconnect.MergeRequestServiceGetMergeRequestProcedure, "/ermite.MergeRequestService/GetMergeRequest"},
36+ {ermiteconnect.MergeRequestServiceAddCommentProcedure, "/ermite.MergeRequestService/AddComment"},
37+ {ermiteconnect.MergeRequestServiceResolveCommentProcedure, "/ermite.MergeRequestService/ResolveComment"},
38+ {ermiteconnect.MergeRequestServiceCloseMergeRequestProcedure, "/ermite.MergeRequestService/CloseMergeRequest"},
39+ {ermiteconnect.MergeRequestServiceMergeMergeRequestProcedure, "/ermite.MergeRequestService/MergeMergeRequest"},
40+ {ermiteconnect.MergeRequestServiceMarkReadProcedure, "/ermite.MergeRequestService/MarkRead"},
41+ {ermiteconnect.SessionServiceCreateLoginLinkProcedure, "/ermite.SessionService/CreateLoginLink"},
42+ {ermiteconnect.SessionServiceSignOutEverywhereProcedure, "/ermite.SessionService/SignOutEverywhere"},
33 {ermiteconnect.ServerServiceGetVersionProcedure, "/ermite.ServerService/GetVersion"},43 {ermiteconnect.ServerServiceGetVersionProcedure, "/ermite.ServerService/GetVersion"},
44+ {ermiteconnect.ServerServiceGetSigningKeyProcedure, "/ermite.ServerService/GetSigningKey"},
34 }45 }
35 for _, tt := range tests {46 for _, tt := range tests {
36 if tt.got != tt.want {47 if tt.got != tt.want {
@@ -50,6 +61,15 @@ func TestEnumNumbers(t *testing.T) {
50 if got := ermite.Visibility_VISIBILITY_PRIVATE; got != 2 {61 if got := ermite.Visibility_VISIBILITY_PRIVATE; got != 2 {
51 t.Errorf("VISIBILITY_PRIVATE = %d, want 2", got)62 t.Errorf("VISIBILITY_PRIVATE = %d, want 2", got)
52 }63 }
64+ if got := ermite.MergeMode_MERGE_MODE_SQUASH; got != 2 {
65+ t.Errorf("MERGE_MODE_SQUASH = %d, want 2", got)
66+ }
67+ if got := ermite.MergeRequestState_MERGE_REQUEST_STATE_CLOSED; got != 3 {
68+ t.Errorf("MERGE_REQUEST_STATE_CLOSED = %d, want 3", got)
69+ }
70+ if got := ermite.DiffSide_DIFF_SIDE_NEW; got != 2 {
71+ t.Errorf("DIFF_SIDE_NEW = %d, want 2", got)
72+ }
53 }73 }
54 74
55 // TestEventRoundTrip checks that an event with a oneof kind survives the wire.75 // TestEventRoundTrip checks that an event with a oneof kind survives the wire.
@@ -90,3 +110,44 @@ func TestUpdateRepoMask(t *testing.T) {
90 t.Errorf("paths = %v", got)110 t.Errorf("paths = %v", got)
91 }111 }
92 }112 }
113+
114+// TestMergeRequestEventRoundTrip checks the merge request event kinds and the
115+// repository settings survive the wire.
116+func TestMergeRequestEventRoundTrip(t *testing.T) {
117+ in := &ermite.WatchResponse{Event: &ermite.Event{
118+ Id: "43",
119+ Kind: &ermite.Event_CommentAdded{CommentAdded: &ermite.CommentAdded{
120+ Repo: "ermite",
121+ Number: 7,
122+ Comment: &ermite.Comment{
123+ Id: "c1", Path: "a.go", Line: 3, Side: ermite.DiffSide_DIFF_SIDE_OLD, Text: "hi",
124+ },
125+ }},
126+ }}
127+ b, err := proto.Marshal(in)
128+ if err != nil {
129+ t.Fatal(err)
130+ }
131+ var out ermite.WatchResponse
132+ if err := proto.Unmarshal(b, &out); err != nil {
133+ t.Fatal(err)
134+ }
135+ if !proto.Equal(in, &out) {
136+ t.Errorf("round trip mismatch: %v != %v", &out, in)
137+ }
138+ if got := out.GetEvent().GetCommentAdded().GetNumber(); got != 7 {
139+ t.Errorf("number = %d, want 7", got)
140+ }
141+
142+ req := &ermite.UpdateRepoRequest{
143+ Repo: "ermite",
144+ Settings: &ermite.RepoSettings{
145+ AllowedMergeModes: []ermite.MergeMode{ermite.MergeMode_MERGE_MODE_SQUASH},
146+ FailingChecksBlock: true,
147+ },
148+ UpdateMask: &fieldmaskpb.FieldMask{Paths: []string{"settings.allowed_merge_modes", "settings.failing_checks_block"}},
149+ }
150+ if got := req.GetSettings().GetAllowedMergeModes(); len(got) != 1 || got[0] != ermite.MergeMode_MERGE_MODE_SQUASH {
151+ t.Errorf("modes = %v", got)
152+ }
153+}
M proto/ermite/event.proto +49 -1
@@ -4,6 +4,7 @@ syntax = "proto3";
44
55 package ermite;
66
7+import "ermite/merge_request.proto";
78 import "ermite/repo.proto";
89 import "google/protobuf/timestamp.proto";
910
@@ -13,6 +14,15 @@ service EventService {
1314 // down. Events are filtered by the caller's access: events of a private
1415 // repository are only sent to callers that may read it.
1516 //
17+ // The events are repo_created, repo_updated, repo_deleted, pushed, gap and,
18+ // for merge requests, merge_request_opened, merge_request_merged,
19+ // merge_request_closed and comment_added. Each one is delivered on the
20+ // "repo:<name>" topic of its repository and on "home", subject to the
21+ // visibility of the repository. There is no event for a change of title or
22+ // description, nor for a resolved comment: after a gap, or to see such
23+ // changes, clients read the merge request again. A merge publishes
24+ // merge_request_merged, then pushed for the target branch.
25+ //
1626 // The server keeps a bounded buffer of recent events. With resume_after set,
1727 // buffered events after that id are replayed first, then live events follow.
1828 // When resume_after is no longer buffered (or unknown), the stream begins
@@ -69,6 +79,14 @@ message Event {
6979 Pushed pushed = 6;
7080 // Gap: events were lost, reload state.
7181 Gap gap = 7;
82+ // MergeRequestOpened: a merge request was opened.
83+ MergeRequestOpened merge_request_opened = 8;
84+ // MergeRequestMerged: a merge request was merged.
85+ MergeRequestMerged merge_request_merged = 9;
86+ // MergeRequestClosed: a merge request was closed without merging.
87+ MergeRequestClosed merge_request_closed = 10;
88+ // CommentAdded: a comment was added to a merge request.
89+ CommentAdded comment_added = 11;
7290 }
7391 }
7492
@@ -108,7 +126,37 @@ message RefUpdate {
108126 string new_id = 3;
109127 }
110128
129+// MergeRequestOpened reports a new merge request.
130+message MergeRequestOpened {
131+ // MergeRequest is the new merge request.
132+ MergeRequest merge_request = 1;
133+}
134+
135+// MergeRequestMerged reports a merged merge request. It is published before
136+// the Pushed event of the target branch.
137+message MergeRequestMerged {
138+ // MergeRequest is the merge request after the merge.
139+ MergeRequest merge_request = 1;
140+}
141+
142+// MergeRequestClosed reports a merge request closed without merging.
143+message MergeRequestClosed {
144+ // MergeRequest is the merge request after closing.
145+ MergeRequest merge_request = 1;
146+}
147+
148+// CommentAdded reports a new comment on a merge request.
149+message CommentAdded {
150+ // Repo is the name of the repository.
151+ string repo = 1;
152+ // Number is the number of the merge request.
153+ int64 number = 2;
154+ // Comment is the new comment.
155+ Comment comment = 3;
156+}
157+
111158 // Gap tells the client that events were lost: the requested resume point is
112159 // older than the server's buffer. It has no fields. The client should reload
113-// the state it displays (repositories, refs) and carry on with the next events.
160+// the state it displays (repositories, refs, merge requests and their
161+// comments) and carry on with the next events.
114162 message Gap {}
@@ -4,6 +4,7 @@ syntax = "proto3";
4 4
5 package ermite;5 package ermite;
6 6
7+import "ermite/merge_request.proto";
7 import "ermite/repo.proto";8 import "ermite/repo.proto";
8 import "google/protobuf/timestamp.proto";9 import "google/protobuf/timestamp.proto";
9 10
@@ -13,6 +14,15 @@ service EventService {
13 // down. Events are filtered by the caller's access: events of a private14 // down. Events are filtered by the caller's access: events of a private
14 // repository are only sent to callers that may read it.15 // repository are only sent to callers that may read it.
15 //16 //
17+ // The events are repo_created, repo_updated, repo_deleted, pushed, gap and,
18+ // for merge requests, merge_request_opened, merge_request_merged,
19+ // merge_request_closed and comment_added. Each one is delivered on the
20+ // "repo:<name>" topic of its repository and on "home", subject to the
21+ // visibility of the repository. There is no event for a change of title or
22+ // description, nor for a resolved comment: after a gap, or to see such
23+ // changes, clients read the merge request again. A merge publishes
24+ // merge_request_merged, then pushed for the target branch.
25+ //
16 // The server keeps a bounded buffer of recent events. With resume_after set,26 // The server keeps a bounded buffer of recent events. With resume_after set,
17 // buffered events after that id are replayed first, then live events follow.27 // buffered events after that id are replayed first, then live events follow.
18 // When resume_after is no longer buffered (or unknown), the stream begins28 // When resume_after is no longer buffered (or unknown), the stream begins
@@ -69,6 +79,14 @@ message Event {
69 Pushed pushed = 6;79 Pushed pushed = 6;
70 // Gap: events were lost, reload state.80 // Gap: events were lost, reload state.
71 Gap gap = 7;81 Gap gap = 7;
82+ // MergeRequestOpened: a merge request was opened.
83+ MergeRequestOpened merge_request_opened = 8;
84+ // MergeRequestMerged: a merge request was merged.
85+ MergeRequestMerged merge_request_merged = 9;
86+ // MergeRequestClosed: a merge request was closed without merging.
87+ MergeRequestClosed merge_request_closed = 10;
88+ // CommentAdded: a comment was added to a merge request.
89+ CommentAdded comment_added = 11;
72 }90 }
73 }91 }
74 92
@@ -108,7 +126,37 @@ message RefUpdate {
108 string new_id = 3;126 string new_id = 3;
109 }127 }
110 128
129+// MergeRequestOpened reports a new merge request.
130+message MergeRequestOpened {
131+ // MergeRequest is the new merge request.
132+ MergeRequest merge_request = 1;
133+}
134+
135+// MergeRequestMerged reports a merged merge request. It is published before
136+// the Pushed event of the target branch.
137+message MergeRequestMerged {
138+ // MergeRequest is the merge request after the merge.
139+ MergeRequest merge_request = 1;
140+}
141+
142+// MergeRequestClosed reports a merge request closed without merging.
143+message MergeRequestClosed {
144+ // MergeRequest is the merge request after closing.
145+ MergeRequest merge_request = 1;
146+}
147+
148+// CommentAdded reports a new comment on a merge request.
149+message CommentAdded {
150+ // Repo is the name of the repository.
151+ string repo = 1;
152+ // Number is the number of the merge request.
153+ int64 number = 2;
154+ // Comment is the new comment.
155+ Comment comment = 3;
156+}
157+
111 // Gap tells the client that events were lost: the requested resume point is158 // Gap tells the client that events were lost: the requested resume point is
112 // older than the server's buffer. It has no fields. The client should reload159 // older than the server's buffer. It has no fields. The client should reload
113-// the state it displays (repositories, refs) and carry on with the next events.160+// the state it displays (repositories, refs, merge requests and their
161+// comments) and carry on with the next events.
114 message Gap {}162 message Gap {}
A proto/ermite/merge_request.proto +371 -0
@@ -0,0 +1,371 @@
1+// SPDX-License-Identifier: MIT
2+
3+syntax = "proto3";
4+
5+package ermite;
6+
7+import "ermite/git.proto";
8+import "ermite/types.proto";
9+import "google/protobuf/timestamp.proto";
10+
11+// MergeRequestState is where a merge request is in its life.
12+enum MergeRequestState {
13+ // Not set. In ListMergeRequestsRequest.state it means every state. Never
14+ // returned by the server.
15+ MERGE_REQUEST_STATE_UNSPECIFIED = 0;
16+ // The merge request awaits review; it can be commented on, merged or closed.
17+ MERGE_REQUEST_STATE_OPEN = 1;
18+ // The source branch was merged into the target. Final.
19+ MERGE_REQUEST_STATE_MERGED = 2;
20+ // The merge request was closed without merging. Final.
21+ MERGE_REQUEST_STATE_CLOSED = 3;
22+}
23+
24+// DiffSide is the side of a diff a line comment is attached to.
25+enum DiffSide {
26+ // Not set. On a Comment it means the comment is general, not attached to a
27+ // line. In AddCommentRequest it is valid only for a general comment.
28+ DIFF_SIDE_UNSPECIFIED = 0;
29+ // The old side: the line is a Line.old_number (a deletion or a context line).
30+ DIFF_SIDE_OLD = 1;
31+ // The new side: the line is a Line.new_number (an addition or a context
32+ // line).
33+ DIFF_SIDE_NEW = 2;
34+}
35+
36+// MergeRequest asks to merge one branch of a repository into another branch
37+// of the same repository. It is about a branch, not about who wrote it.
38+message MergeRequest {
39+ // Repo is the name of the repository.
40+ string repo = 1;
41+ // Number identifies the merge request within its repository, starting at 1
42+ // and never reused. It is written "!<number>" in messages and URLs.
43+ int64 number = 2;
44+ // Title is a one-line plain-text title, at most 120 characters.
45+ string title = 3;
46+ // Description is a plain-text description, at most 8,000 characters. It may
47+ // be empty. Clients show it escaped, with line breaks kept.
48+ string description = 4;
49+ // SourceBranch is the short name of the branch to merge, for example
50+ // "feat/x".
51+ string source_branch = 5;
52+ // TargetBranch is the short name of the branch to merge into, for example
53+ // "main".
54+ string target_branch = 6;
55+ // State is where the merge request is in its life.
56+ MergeRequestState state = 7;
57+ // Author names who created the merge request: the label of the token that
58+ // was used, "root" for the root token, or "owner" for a web session. It is
59+ // informational: labels are not unique and the token may be gone.
60+ string author = 8;
61+ // CreatedAt is when the merge request was created.
62+ google.protobuf.Timestamp created_at = 9;
63+ // UpdatedAt is when the merge request last changed: title, description,
64+ // state, or a comment added or resolved. Pushes to the source branch do not
65+ // change it.
66+ google.protobuf.Timestamp updated_at = 10;
67+ // HeadId is the id of the commit the source branch points to. While the
68+ // merge request is open it is read from Git at every call, so it changes
69+ // when the branch is pushed; it is empty if the branch was deleted. Once
70+ // merged or closed it is the head at that moment.
71+ string head_id = 11;
72+ // MergedCommitId is the id of the commit the target branch points to after
73+ // the merge: the head for a fast-forward, the new commit for a squash.
74+ // Empty unless state is MERGED.
75+ string merged_commit_id = 12;
76+ // MergeMode is how the merge request was merged. MERGE_MODE_UNSPECIFIED
77+ // unless state is MERGED.
78+ MergeMode merge_mode = 13;
79+}
80+
81+// Comment is a note on a merge request, either general or attached to a line
82+// of its diff.
83+message Comment {
84+ // Id identifies the comment within its merge request. Opaque.
85+ string id = 1;
86+ // Author names who wrote the comment, like MergeRequest.author.
87+ string author = 2;
88+ // Path is the path of the commented file in the diff (the new path, or the
89+ // old path of a deleted file). Empty for a general comment.
90+ string path = 3;
91+ // Line is the 1-based line number on side. 0 for a general comment.
92+ int32 line = 4;
93+ // Side tells whether line counts on the old or the new side of the diff.
94+ // DIFF_SIDE_UNSPECIFIED for a general comment.
95+ DiffSide side = 5;
96+ // CommitId is the head of the merge request when the comment was written.
97+ // When it differs from the current head the line may have moved; clients
98+ // may show the comment as outdated.
99+ string commit_id = 6;
100+ // Text is the plain-text content, at most 8,000 characters. Clients show it
101+ // escaped, with line breaks kept.
102+ string text = 7;
103+ // Resolved is true once the comment was marked resolved.
104+ bool resolved = 8;
105+ // CreatedAt is when the comment was written.
106+ google.protobuf.Timestamp created_at = 9;
107+}
108+
109+// MergeRequestService reviews and merges branches.
110+//
111+// Every RPC takes the repository name and, except for creation and listing,
112+// the merge request number. A repository that does not exist or that the
113+// caller cannot read gives not_found; so does an unknown number. Reading
114+// (ListMergeRequests, GetMergeRequest) needs the same access as reading the
115+// repository: nothing for a public one, the repo:read scope for a private
116+// one. Writing needs a scope on the repository, given per RPC; a caller
117+// without it gets permission_denied.
118+//
119+// Text fields follow the same rules everywhere: valid UTF-8, no control
120+// characters (except the line break in description and comment text) and no
121+// Unicode bidirectional override or isolate characters; title is one line of
122+// at most 120 characters, description and comment text at most 8,000
123+// characters. A request that breaks a rule is rejected whole with
124+// invalid_argument, and the error carries a BadRequest detail naming the
125+// fields.
126+//
127+// Servers that implement this service list "merge_requests" in
128+// GetVersionResponse.features.
129+service MergeRequestService {
130+ // CreateMergeRequest opens a merge request. Needs the mr:write scope. Fails
131+ // with invalid_argument if a text rule is broken, if a branch does not exist
132+ // or if source and target are the same branch, and with already_exists if an
133+ // open merge request already has the same source and target.
134+ rpc CreateMergeRequest(CreateMergeRequestRequest) returns (CreateMergeRequestResponse);
135+ // ListMergeRequests lists the merge requests of a repository, highest
136+ // number (newest) first, optionally restricted to one state. The listed
137+ // merge requests carry no diff or comments; head_id is set. Fails with
138+ // not_found if the repository does not exist or is not readable.
139+ rpc ListMergeRequests(ListMergeRequestsRequest) returns (ListMergeRequestsResponse);
140+ // GetMergeRequest returns one merge request with its commits, its diff and
141+ // its comments. The diff compares the target with the head (what the merge
142+ // would bring: "target...head"), or, with since_last_read, the head the
143+ // caller last marked read with the current head. Fails with not_found if
144+ // the repository or the number is unknown.
145+ rpc GetMergeRequest(GetMergeRequestRequest) returns (GetMergeRequestResponse);
146+ // AddComment adds a general comment or a comment on a line of the diff.
147+ // Needs the mr:write scope. Fails with invalid_argument if the text breaks
148+ // a rule or if path, line and side do not designate a line of the current
149+ // diff of the merge request, and with failed_precondition if the merge
150+ // request is not open.
151+ rpc AddComment(AddCommentRequest) returns (AddCommentResponse);
152+ // ResolveComment marks a comment resolved or unresolved. Needs the mr:write
153+ // scope. Fails with not_found if the comment is unknown and with
154+ // failed_precondition if the merge request is not open. Setting the state
155+ // the comment already has succeeds and changes nothing.
156+ rpc ResolveComment(ResolveCommentRequest) returns (ResolveCommentResponse);
157+ // CloseMergeRequest closes an open merge request without merging it. Needs
158+ // the mr:write scope. Fails with failed_precondition if the merge request
159+ // is not open. A closed merge request cannot be reopened: open a new one.
160+ rpc CloseMergeRequest(CloseMergeRequestRequest) returns (CloseMergeRequestResponse);
161+ // MergeMergeRequest merges an open merge request. Needs the mr:merge scope.
162+ // The caller passes the head it reviewed: the server merges only if the
163+ // source branch still points to it, so a push between review and merge is
164+ // never merged unseen. Fails with invalid_argument if head_id is empty or
165+ // not a commit id, and with failed_precondition if the merge request is not
166+ // open, if the source branch no longer points to head_id ("branch moved,
167+ // read again"), if the target is not an ancestor of the head (rebase and
168+ // push again), if the mode is not allowed by RepoSettings, or if a check
169+ // fails and RepoSettings.failing_checks_block is set. Merges of one
170+ // repository run one at a time. Repeating a call that succeeded fails with
171+ // failed_precondition (not open). On success the server publishes
172+ // MergeRequestMerged then Pushed for the target branch.
173+ rpc MergeMergeRequest(MergeMergeRequestRequest) returns (MergeMergeRequestResponse);
174+ // MarkRead records the head the caller has reviewed, for the since_last_read
175+ // option of GetMergeRequest. There is one read position per merge request,
176+ // the reviewer's. Needs the mr:write scope. Marking a commit that does not
177+ // exist in the repository is invalid_argument.
178+ rpc MarkRead(MarkReadRequest) returns (MarkReadResponse);
179+}
180+
181+// CreateMergeRequestRequest describes the merge request to open.
182+message CreateMergeRequestRequest {
183+ // Repo is the repository name.
184+ string repo = 1;
185+ // Title is the one-line title. Required.
186+ string title = 2;
187+ // Description is the optional description.
188+ string description = 3;
189+ // SourceBranch is the short name of the branch to merge. Required; the
190+ // branch must exist.
191+ string source_branch = 4;
192+ // TargetBranch is the short name of the branch to merge into. Empty means
193+ // the default branch of the repository. The branch must exist and differ
194+ // from source_branch.
195+ string target_branch = 5;
196+}
197+
198+// CreateMergeRequestResponse holds the new merge request.
199+message CreateMergeRequestResponse {
200+ // MergeRequest is the created merge request.
201+ MergeRequest merge_request = 1;
202+}
203+
204+// ListMergeRequestsRequest selects a page of merge requests.
205+message ListMergeRequestsRequest {
206+ // Repo is the repository name.
207+ string repo = 1;
208+ // State restricts the result to one state. MERGE_REQUEST_STATE_UNSPECIFIED
209+ // means every state.
210+ MergeRequestState state = 2;
211+ // PageSize is the maximum number of merge requests to return. 0 means the
212+ // server default; values above the server maximum are lowered to it.
213+ int32 page_size = 3;
214+ // PageToken is the next_page_token of a previous response, to continue the
215+ // same listing. Empty for the first page. Other fields must be unchanged.
216+ string page_token = 4;
217+}
218+
219+// ListMergeRequestsResponse is one page of merge requests.
220+message ListMergeRequestsResponse {
221+ // MergeRequests are the merge requests of this page, highest number first.
222+ repeated MergeRequest merge_requests = 1;
223+ // NextPageToken is passed as page_token to get the next page. Empty when
224+ // there is no next page.
225+ string next_page_token = 2;
226+}
227+
228+// GetMergeRequestRequest selects a merge request.
229+message GetMergeRequestRequest {
230+ // Repo is the repository name.
231+ string repo = 1;
232+ // Number is the merge request number.
233+ int64 number = 2;
234+ // SinceLastRead asks for the diff between the head recorded by MarkRead and
235+ // the current head instead of the diff against the target. It works after a
236+ // force-push as long as the recorded commit still exists in the repository.
237+ // When nothing was marked read, or the recorded commit is gone, the response
238+ // holds the diff against the target and says so in last_read_id and
239+ // last_read_lost.
240+ bool since_last_read = 3;
241+}
242+
243+// GetMergeRequestResponse holds a merge request with its review data.
244+message GetMergeRequestResponse {
245+ // MergeRequest is the merge request.
246+ MergeRequest merge_request = 1;
247+ // Commits are the commits of the head that are not in the target
248+ // ("target..head"), newest first. At most 100 are returned (a server limit
249+ // that may change; rely on commits_truncated, not on the number). Empty when
250+ // the source branch was deleted.
251+ repeated Commit commits = 2;
252+ // CommitsTruncated is true when the branch has more commits than commits
253+ // holds.
254+ bool commits_truncated = 3;
255+ // DiffBaseId is the id of the commit the diff compares from: the merge base
256+ // of the target and the head, or the last read head when since_last_read
257+ // applied. Empty when the source branch was deleted; diff is then empty.
258+ string diff_base_id = 4;
259+ // Stats are the totals of the diff, including files that it omits or
260+ // truncates.
261+ DiffStats stats = 5;
262+ // Diff is the structured diff from diff_base_id to the head, with the same
263+ // limits and truncated flags as GetCommitResponse.diff.
264+ Diff diff = 6;
265+ // Comments are every comment of the merge request, oldest first.
266+ repeated Comment comments = 7;
267+ // LastReadId is the head recorded by MarkRead, whether or not
268+ // since_last_read was set. Empty when nothing was marked read.
269+ string last_read_id = 8;
270+ // LastReadLost is true when since_last_read was set, a head was recorded,
271+ // but that commit no longer exists in the repository: the diff is then the
272+ // diff against the target. A client should tell the reader to review the
273+ // whole change again.
274+ bool last_read_lost = 9;
275+}
276+
277+// AddCommentRequest describes the comment to add.
278+message AddCommentRequest {
279+ // Repo is the repository name.
280+ string repo = 1;
281+ // Number is the merge request number.
282+ int64 number = 2;
283+ // Path is the file the comment is attached to, as given by the diff of the
284+ // merge request: FileDiff.new_path, or old_path for a deleted file. Empty
285+ // for a general comment; then line must be 0 and side unspecified.
286+ string path = 3;
287+ // Line is the 1-based line number on side. It must be the old_number or
288+ // new_number of a Line of a hunk of the file; a line outside the hunks is
289+ // invalid_argument. 0 for a general comment.
290+ int32 line = 4;
291+ // Side is the side line counts on. Required when path is set.
292+ DiffSide side = 5;
293+ // Text is the plain-text content. Required.
294+ string text = 6;
295+}
296+
297+// AddCommentResponse holds the new comment.
298+message AddCommentResponse {
299+ // Comment is the created comment.
300+ Comment comment = 1;
301+}
302+
303+// ResolveCommentRequest changes the resolved state of a comment.
304+message ResolveCommentRequest {
305+ // Repo is the repository name.
306+ string repo = 1;
307+ // Number is the merge request number.
308+ int64 number = 2;
309+ // CommentId is the Comment.id to change.
310+ string comment_id = 3;
311+ // Resolved is the new state: true to resolve, false to unresolve.
312+ bool resolved = 4;
313+}
314+
315+// ResolveCommentResponse holds the comment after the change.
316+message ResolveCommentResponse {
317+ // Comment is the updated comment.
318+ Comment comment = 1;
319+}
320+
321+// CloseMergeRequestRequest names the merge request to close.
322+message CloseMergeRequestRequest {
323+ // Repo is the repository name.
324+ string repo = 1;
325+ // Number is the merge request number.
326+ int64 number = 2;
327+}
328+
329+// CloseMergeRequestResponse holds the merge request after closing.
330+message CloseMergeRequestResponse {
331+ // MergeRequest is the closed merge request.
332+ MergeRequest merge_request = 1;
333+}
334+
335+// MergeMergeRequestRequest describes the merge to perform.
336+message MergeMergeRequestRequest {
337+ // Repo is the repository name.
338+ string repo = 1;
339+ // Number is the merge request number.
340+ int64 number = 2;
341+ // Mode is how to merge. MERGE_MODE_UNSPECIFIED means fast-forward. The mode
342+ // must be in RepoSettings.allowed_merge_modes of the repository.
343+ MergeMode mode = 3;
344+ // HeadId is the full id of the commit the caller reviewed, that is the
345+ // MergeRequest.head_id it was shown. Required. The merge happens only if the
346+ // source branch still points to it.
347+ string head_id = 4;
348+}
349+
350+// MergeMergeRequestResponse holds the merged merge request.
351+message MergeMergeRequestResponse {
352+ // MergeRequest is the merge request after the merge: state MERGED,
353+ // merged_commit_id set to the new tip of the target branch and merge_mode
354+ // set to the mode used.
355+ MergeRequest merge_request = 1;
356+}
357+
358+// MarkReadRequest records a read position.
359+message MarkReadRequest {
360+ // Repo is the repository name.
361+ string repo = 1;
362+ // Number is the merge request number.
363+ int64 number = 2;
364+ // HeadId is the full id of the commit the caller has reviewed, usually the
365+ // MergeRequest.head_id it was shown. Empty means the current head of the
366+ // source branch.
367+ string head_id = 3;
368+}
369+
370+// MarkReadResponse is empty.
371+message MarkReadResponse {}
@@ -0,0 +1,371 @@
1+// SPDX-License-Identifier: MIT
2+
3+syntax = "proto3";
4+
5+package ermite;
6+
7+import "ermite/git.proto";
8+import "ermite/types.proto";
9+import "google/protobuf/timestamp.proto";
10+
11+// MergeRequestState is where a merge request is in its life.
12+enum MergeRequestState {
13+ // Not set. In ListMergeRequestsRequest.state it means every state. Never
14+ // returned by the server.
15+ MERGE_REQUEST_STATE_UNSPECIFIED = 0;
16+ // The merge request awaits review; it can be commented on, merged or closed.
17+ MERGE_REQUEST_STATE_OPEN = 1;
18+ // The source branch was merged into the target. Final.
19+ MERGE_REQUEST_STATE_MERGED = 2;
20+ // The merge request was closed without merging. Final.
21+ MERGE_REQUEST_STATE_CLOSED = 3;
22+}
23+
24+// DiffSide is the side of a diff a line comment is attached to.
25+enum DiffSide {
26+ // Not set. On a Comment it means the comment is general, not attached to a
27+ // line. In AddCommentRequest it is valid only for a general comment.
28+ DIFF_SIDE_UNSPECIFIED = 0;
29+ // The old side: the line is a Line.old_number (a deletion or a context line).
30+ DIFF_SIDE_OLD = 1;
31+ // The new side: the line is a Line.new_number (an addition or a context
32+ // line).
33+ DIFF_SIDE_NEW = 2;
34+}
35+
36+// MergeRequest asks to merge one branch of a repository into another branch
37+// of the same repository. It is about a branch, not about who wrote it.
38+message MergeRequest {
39+ // Repo is the name of the repository.
40+ string repo = 1;
41+ // Number identifies the merge request within its repository, starting at 1
42+ // and never reused. It is written "!<number>" in messages and URLs.
43+ int64 number = 2;
44+ // Title is a one-line plain-text title, at most 120 characters.
45+ string title = 3;
46+ // Description is a plain-text description, at most 8,000 characters. It may
47+ // be empty. Clients show it escaped, with line breaks kept.
48+ string description = 4;
49+ // SourceBranch is the short name of the branch to merge, for example
50+ // "feat/x".
51+ string source_branch = 5;
52+ // TargetBranch is the short name of the branch to merge into, for example
53+ // "main".
54+ string target_branch = 6;
55+ // State is where the merge request is in its life.
56+ MergeRequestState state = 7;
57+ // Author names who created the merge request: the label of the token that
58+ // was used, "root" for the root token, or "owner" for a web session. It is
59+ // informational: labels are not unique and the token may be gone.
60+ string author = 8;
61+ // CreatedAt is when the merge request was created.
62+ google.protobuf.Timestamp created_at = 9;
63+ // UpdatedAt is when the merge request last changed: title, description,
64+ // state, or a comment added or resolved. Pushes to the source branch do not
65+ // change it.
66+ google.protobuf.Timestamp updated_at = 10;
67+ // HeadId is the id of the commit the source branch points to. While the
68+ // merge request is open it is read from Git at every call, so it changes
69+ // when the branch is pushed; it is empty if the branch was deleted. Once
70+ // merged or closed it is the head at that moment.
71+ string head_id = 11;
72+ // MergedCommitId is the id of the commit the target branch points to after
73+ // the merge: the head for a fast-forward, the new commit for a squash.
74+ // Empty unless state is MERGED.
75+ string merged_commit_id = 12;
76+ // MergeMode is how the merge request was merged. MERGE_MODE_UNSPECIFIED
77+ // unless state is MERGED.
78+ MergeMode merge_mode = 13;
79+}
80+
81+// Comment is a note on a merge request, either general or attached to a line
82+// of its diff.
83+message Comment {
84+ // Id identifies the comment within its merge request. Opaque.
85+ string id = 1;
86+ // Author names who wrote the comment, like MergeRequest.author.
87+ string author = 2;
88+ // Path is the path of the commented file in the diff (the new path, or the
89+ // old path of a deleted file). Empty for a general comment.
90+ string path = 3;
91+ // Line is the 1-based line number on side. 0 for a general comment.
92+ int32 line = 4;
93+ // Side tells whether line counts on the old or the new side of the diff.
94+ // DIFF_SIDE_UNSPECIFIED for a general comment.
95+ DiffSide side = 5;
96+ // CommitId is the head of the merge request when the comment was written.
97+ // When it differs from the current head the line may have moved; clients
98+ // may show the comment as outdated.
99+ string commit_id = 6;
100+ // Text is the plain-text content, at most 8,000 characters. Clients show it
101+ // escaped, with line breaks kept.
102+ string text = 7;
103+ // Resolved is true once the comment was marked resolved.
104+ bool resolved = 8;
105+ // CreatedAt is when the comment was written.
106+ google.protobuf.Timestamp created_at = 9;
107+}
108+
109+// MergeRequestService reviews and merges branches.
110+//
111+// Every RPC takes the repository name and, except for creation and listing,
112+// the merge request number. A repository that does not exist or that the
113+// caller cannot read gives not_found; so does an unknown number. Reading
114+// (ListMergeRequests, GetMergeRequest) needs the same access as reading the
115+// repository: nothing for a public one, the repo:read scope for a private
116+// one. Writing needs a scope on the repository, given per RPC; a caller
117+// without it gets permission_denied.
118+//
119+// Text fields follow the same rules everywhere: valid UTF-8, no control
120+// characters (except the line break in description and comment text) and no
121+// Unicode bidirectional override or isolate characters; title is one line of
122+// at most 120 characters, description and comment text at most 8,000
123+// characters. A request that breaks a rule is rejected whole with
124+// invalid_argument, and the error carries a BadRequest detail naming the
125+// fields.
126+//
127+// Servers that implement this service list "merge_requests" in
128+// GetVersionResponse.features.
129+service MergeRequestService {
130+ // CreateMergeRequest opens a merge request. Needs the mr:write scope. Fails
131+ // with invalid_argument if a text rule is broken, if a branch does not exist
132+ // or if source and target are the same branch, and with already_exists if an
133+ // open merge request already has the same source and target.
134+ rpc CreateMergeRequest(CreateMergeRequestRequest) returns (CreateMergeRequestResponse);
135+ // ListMergeRequests lists the merge requests of a repository, highest
136+ // number (newest) first, optionally restricted to one state. The listed
137+ // merge requests carry no diff or comments; head_id is set. Fails with
138+ // not_found if the repository does not exist or is not readable.
139+ rpc ListMergeRequests(ListMergeRequestsRequest) returns (ListMergeRequestsResponse);
140+ // GetMergeRequest returns one merge request with its commits, its diff and
141+ // its comments. The diff compares the target with the head (what the merge
142+ // would bring: "target...head"), or, with since_last_read, the head the
143+ // caller last marked read with the current head. Fails with not_found if
144+ // the repository or the number is unknown.
145+ rpc GetMergeRequest(GetMergeRequestRequest) returns (GetMergeRequestResponse);
146+ // AddComment adds a general comment or a comment on a line of the diff.
147+ // Needs the mr:write scope. Fails with invalid_argument if the text breaks
148+ // a rule or if path, line and side do not designate a line of the current
149+ // diff of the merge request, and with failed_precondition if the merge
150+ // request is not open.
151+ rpc AddComment(AddCommentRequest) returns (AddCommentResponse);
152+ // ResolveComment marks a comment resolved or unresolved. Needs the mr:write
153+ // scope. Fails with not_found if the comment is unknown and with
154+ // failed_precondition if the merge request is not open. Setting the state
155+ // the comment already has succeeds and changes nothing.
156+ rpc ResolveComment(ResolveCommentRequest) returns (ResolveCommentResponse);
157+ // CloseMergeRequest closes an open merge request without merging it. Needs
158+ // the mr:write scope. Fails with failed_precondition if the merge request
159+ // is not open. A closed merge request cannot be reopened: open a new one.
160+ rpc CloseMergeRequest(CloseMergeRequestRequest) returns (CloseMergeRequestResponse);
161+ // MergeMergeRequest merges an open merge request. Needs the mr:merge scope.
162+ // The caller passes the head it reviewed: the server merges only if the
163+ // source branch still points to it, so a push between review and merge is
164+ // never merged unseen. Fails with invalid_argument if head_id is empty or
165+ // not a commit id, and with failed_precondition if the merge request is not
166+ // open, if the source branch no longer points to head_id ("branch moved,
167+ // read again"), if the target is not an ancestor of the head (rebase and
168+ // push again), if the mode is not allowed by RepoSettings, or if a check
169+ // fails and RepoSettings.failing_checks_block is set. Merges of one
170+ // repository run one at a time. Repeating a call that succeeded fails with
171+ // failed_precondition (not open). On success the server publishes
172+ // MergeRequestMerged then Pushed for the target branch.
173+ rpc MergeMergeRequest(MergeMergeRequestRequest) returns (MergeMergeRequestResponse);
174+ // MarkRead records the head the caller has reviewed, for the since_last_read
175+ // option of GetMergeRequest. There is one read position per merge request,
176+ // the reviewer's. Needs the mr:write scope. Marking a commit that does not
177+ // exist in the repository is invalid_argument.
178+ rpc MarkRead(MarkReadRequest) returns (MarkReadResponse);
179+}
180+
181+// CreateMergeRequestRequest describes the merge request to open.
182+message CreateMergeRequestRequest {
183+ // Repo is the repository name.
184+ string repo = 1;
185+ // Title is the one-line title. Required.
186+ string title = 2;
187+ // Description is the optional description.
188+ string description = 3;
189+ // SourceBranch is the short name of the branch to merge. Required; the
190+ // branch must exist.
191+ string source_branch = 4;
192+ // TargetBranch is the short name of the branch to merge into. Empty means
193+ // the default branch of the repository. The branch must exist and differ
194+ // from source_branch.
195+ string target_branch = 5;
196+}
197+
198+// CreateMergeRequestResponse holds the new merge request.
199+message CreateMergeRequestResponse {
200+ // MergeRequest is the created merge request.
201+ MergeRequest merge_request = 1;
202+}
203+
204+// ListMergeRequestsRequest selects a page of merge requests.
205+message ListMergeRequestsRequest {
206+ // Repo is the repository name.
207+ string repo = 1;
208+ // State restricts the result to one state. MERGE_REQUEST_STATE_UNSPECIFIED
209+ // means every state.
210+ MergeRequestState state = 2;
211+ // PageSize is the maximum number of merge requests to return. 0 means the
212+ // server default; values above the server maximum are lowered to it.
213+ int32 page_size = 3;
214+ // PageToken is the next_page_token of a previous response, to continue the
215+ // same listing. Empty for the first page. Other fields must be unchanged.
216+ string page_token = 4;
217+}
218+
219+// ListMergeRequestsResponse is one page of merge requests.
220+message ListMergeRequestsResponse {
221+ // MergeRequests are the merge requests of this page, highest number first.
222+ repeated MergeRequest merge_requests = 1;
223+ // NextPageToken is passed as page_token to get the next page. Empty when
224+ // there is no next page.
225+ string next_page_token = 2;
226+}
227+
228+// GetMergeRequestRequest selects a merge request.
229+message GetMergeRequestRequest {
230+ // Repo is the repository name.
231+ string repo = 1;
232+ // Number is the merge request number.
233+ int64 number = 2;
234+ // SinceLastRead asks for the diff between the head recorded by MarkRead and
235+ // the current head instead of the diff against the target. It works after a
236+ // force-push as long as the recorded commit still exists in the repository.
237+ // When nothing was marked read, or the recorded commit is gone, the response
238+ // holds the diff against the target and says so in last_read_id and
239+ // last_read_lost.
240+ bool since_last_read = 3;
241+}
242+
243+// GetMergeRequestResponse holds a merge request with its review data.
244+message GetMergeRequestResponse {
245+ // MergeRequest is the merge request.
246+ MergeRequest merge_request = 1;
247+ // Commits are the commits of the head that are not in the target
248+ // ("target..head"), newest first. At most 100 are returned (a server limit
249+ // that may change; rely on commits_truncated, not on the number). Empty when
250+ // the source branch was deleted.
251+ repeated Commit commits = 2;
252+ // CommitsTruncated is true when the branch has more commits than commits
253+ // holds.
254+ bool commits_truncated = 3;
255+ // DiffBaseId is the id of the commit the diff compares from: the merge base
256+ // of the target and the head, or the last read head when since_last_read
257+ // applied. Empty when the source branch was deleted; diff is then empty.
258+ string diff_base_id = 4;
259+ // Stats are the totals of the diff, including files that it omits or
260+ // truncates.
261+ DiffStats stats = 5;
262+ // Diff is the structured diff from diff_base_id to the head, with the same
263+ // limits and truncated flags as GetCommitResponse.diff.
264+ Diff diff = 6;
265+ // Comments are every comment of the merge request, oldest first.
266+ repeated Comment comments = 7;
267+ // LastReadId is the head recorded by MarkRead, whether or not
268+ // since_last_read was set. Empty when nothing was marked read.
269+ string last_read_id = 8;
270+ // LastReadLost is true when since_last_read was set, a head was recorded,
271+ // but that commit no longer exists in the repository: the diff is then the
272+ // diff against the target. A client should tell the reader to review the
273+ // whole change again.
274+ bool last_read_lost = 9;
275+}
276+
277+// AddCommentRequest describes the comment to add.
278+message AddCommentRequest {
279+ // Repo is the repository name.
280+ string repo = 1;
281+ // Number is the merge request number.
282+ int64 number = 2;
283+ // Path is the file the comment is attached to, as given by the diff of the
284+ // merge request: FileDiff.new_path, or old_path for a deleted file. Empty
285+ // for a general comment; then line must be 0 and side unspecified.
286+ string path = 3;
287+ // Line is the 1-based line number on side. It must be the old_number or
288+ // new_number of a Line of a hunk of the file; a line outside the hunks is
289+ // invalid_argument. 0 for a general comment.
290+ int32 line = 4;
291+ // Side is the side line counts on. Required when path is set.
292+ DiffSide side = 5;
293+ // Text is the plain-text content. Required.
294+ string text = 6;
295+}
296+
297+// AddCommentResponse holds the new comment.
298+message AddCommentResponse {
299+ // Comment is the created comment.
300+ Comment comment = 1;
301+}
302+
303+// ResolveCommentRequest changes the resolved state of a comment.
304+message ResolveCommentRequest {
305+ // Repo is the repository name.
306+ string repo = 1;
307+ // Number is the merge request number.
308+ int64 number = 2;
309+ // CommentId is the Comment.id to change.
310+ string comment_id = 3;
311+ // Resolved is the new state: true to resolve, false to unresolve.
312+ bool resolved = 4;
313+}
314+
315+// ResolveCommentResponse holds the comment after the change.
316+message ResolveCommentResponse {
317+ // Comment is the updated comment.
318+ Comment comment = 1;
319+}
320+
321+// CloseMergeRequestRequest names the merge request to close.
322+message CloseMergeRequestRequest {
323+ // Repo is the repository name.
324+ string repo = 1;
325+ // Number is the merge request number.
326+ int64 number = 2;
327+}
328+
329+// CloseMergeRequestResponse holds the merge request after closing.
330+message CloseMergeRequestResponse {
331+ // MergeRequest is the closed merge request.
332+ MergeRequest merge_request = 1;
333+}
334+
335+// MergeMergeRequestRequest describes the merge to perform.
336+message MergeMergeRequestRequest {
337+ // Repo is the repository name.
338+ string repo = 1;
339+ // Number is the merge request number.
340+ int64 number = 2;
341+ // Mode is how to merge. MERGE_MODE_UNSPECIFIED means fast-forward. The mode
342+ // must be in RepoSettings.allowed_merge_modes of the repository.
343+ MergeMode mode = 3;
344+ // HeadId is the full id of the commit the caller reviewed, that is the
345+ // MergeRequest.head_id it was shown. Required. The merge happens only if the
346+ // source branch still points to it.
347+ string head_id = 4;
348+}
349+
350+// MergeMergeRequestResponse holds the merged merge request.
351+message MergeMergeRequestResponse {
352+ // MergeRequest is the merge request after the merge: state MERGED,
353+ // merged_commit_id set to the new tip of the target branch and merge_mode
354+ // set to the mode used.
355+ MergeRequest merge_request = 1;
356+}
357+
358+// MarkReadRequest records a read position.
359+message MarkReadRequest {
360+ // Repo is the repository name.
361+ string repo = 1;
362+ // Number is the merge request number.
363+ int64 number = 2;
364+ // HeadId is the full id of the commit the caller has reviewed, usually the
365+ // MergeRequest.head_id it was shown. Empty means the current head of the
366+ // source branch.
367+ string head_id = 3;
368+}
369+
370+// MarkReadResponse is empty.
371+message MarkReadResponse {}
M proto/ermite/repo.proto +33 -5
@@ -5,6 +5,7 @@ syntax = "proto3";
55 package ermite;
66
77 import "google/protobuf/field_mask.proto";
8+import "ermite/types.proto";
89 import "google/protobuf/timestamp.proto";
910
1011 // Visibility says who may read a repository.
@@ -19,6 +20,21 @@ enum Visibility {
1920 VISIBILITY_PRIVATE = 2;
2021 }
2122
23+// RepoSettings are the settings of a repository that govern its merge
24+// requests.
25+message RepoSettings {
26+ // AllowedMergeModes are the modes with which a merge request of the
27+ // repository may be merged. It is never empty in a response and has no
28+ // duplicate. Both MERGE_MODE_FAST_FORWARD and MERGE_MODE_SQUASH are allowed
29+ // by default. MERGE_MODE_UNSPECIFIED is rejected.
30+ repeated MergeMode allowed_merge_modes = 1;
31+ // FailingChecksBlock says whether a merge request whose checks fail cannot be
32+ // merged. It is true at creation of the repository and always stored
33+ // explicitly, so a later change of the server default never alters an
34+ // existing repository.
35+ bool failing_checks_block = 2;
36+}
37+
2238 // Repo is the metadata of a repository. Git-owned state (refs, trees, history)
2339 // is read through GitService.
2440 message Repo {
@@ -37,6 +53,9 @@ message Repo {
3753 google.protobuf.Timestamp created_at = 5;
3854 // PushedAt is the time of the last push. It is unset until the first push.
3955 google.protobuf.Timestamp pushed_at = 6;
56+ // Settings are the merge settings of the repository. Always set in a
57+ // response, with the defaults for a repository that never changed them.
58+ RepoSettings settings = 7;
4059 }
4160
4261 // RepoService manages repositories and their metadata.
@@ -49,12 +68,13 @@ service RepoService {
4968 // GetRepo returns one repository. Fails with not_found if it does not exist
5069 // or is private and not readable by the caller.
5170 rpc GetRepo(GetRepoRequest) returns (GetRepoResponse);
52- // CreateRepo creates an empty repository. Needs the repo:write scope. Fails
71+ // CreateRepo creates an empty repository with the default settings. Needs
72+ // the repo:write scope. Fails
5373 // with already_exists if the name is taken and invalid_argument if the name
5474 // is not valid.
5575 rpc CreateRepo(CreateRepoRequest) returns (CreateRepoResponse);
56- // UpdateRepo changes the fields named in update_mask. Needs the repo:admin
57- // scope.
76+ // UpdateRepo changes the fields named in update_mask, including the merge
77+ // settings. Needs the repo:admin scope.
5878 rpc UpdateRepo(UpdateRepoRequest) returns (UpdateRepoResponse);
5979 // DeleteRepo deletes a repository. Needs the repo:admin scope. The server
6080 // moves the data aside rather than destroying it; there is no undelete RPC.
@@ -124,9 +144,17 @@ message UpdateRepoRequest {
124144 // update_mask contains "default_branch". The branch must exist, otherwise
125145 // the call fails with failed_precondition.
126146 string default_branch = 4;
127- // UpdateMask lists the fields to change: "description", "visibility" and/or
128- // "default_branch". It must not be empty. Other paths are invalid_argument.
147+ // UpdateMask lists the fields to change: "description", "visibility",
148+ // "default_branch", "settings.allowed_merge_modes" and/or
149+ // "settings.failing_checks_block". "settings" stands for both settings
150+ // paths. It must not be empty. Other paths are invalid_argument.
129151 google.protobuf.FieldMask update_mask = 5;
152+ // Settings holds the new merge settings. Only the fields named in
153+ // update_mask are used. allowed_merge_modes must then not be empty nor
154+ // contain MERGE_MODE_UNSPECIFIED, otherwise the call fails with
155+ // invalid_argument. Changing settings needs the repo:admin scope, like any
156+ // other field.
157+ RepoSettings settings = 6;
130158 }
131159
132160 // UpdateRepoResponse holds the repository after the update.
@@ -5,6 +5,7 @@ syntax = "proto3";
5 package ermite;5 package ermite;
6 6
7 import "google/protobuf/field_mask.proto";7 import "google/protobuf/field_mask.proto";
8+import "ermite/types.proto";
8 import "google/protobuf/timestamp.proto";9 import "google/protobuf/timestamp.proto";
9 10
10 // Visibility says who may read a repository.11 // Visibility says who may read a repository.
@@ -19,6 +20,21 @@ enum Visibility {
19 VISIBILITY_PRIVATE = 2;20 VISIBILITY_PRIVATE = 2;
20 }21 }
21 22
23+// RepoSettings are the settings of a repository that govern its merge
24+// requests.
25+message RepoSettings {
26+ // AllowedMergeModes are the modes with which a merge request of the
27+ // repository may be merged. It is never empty in a response and has no
28+ // duplicate. Both MERGE_MODE_FAST_FORWARD and MERGE_MODE_SQUASH are allowed
29+ // by default. MERGE_MODE_UNSPECIFIED is rejected.
30+ repeated MergeMode allowed_merge_modes = 1;
31+ // FailingChecksBlock says whether a merge request whose checks fail cannot be
32+ // merged. It is true at creation of the repository and always stored
33+ // explicitly, so a later change of the server default never alters an
34+ // existing repository.
35+ bool failing_checks_block = 2;
36+}
37+
22 // Repo is the metadata of a repository. Git-owned state (refs, trees, history)38 // Repo is the metadata of a repository. Git-owned state (refs, trees, history)
23 // is read through GitService.39 // is read through GitService.
24 message Repo {40 message Repo {
@@ -37,6 +53,9 @@ message Repo {
37 google.protobuf.Timestamp created_at = 5;53 google.protobuf.Timestamp created_at = 5;
38 // PushedAt is the time of the last push. It is unset until the first push.54 // PushedAt is the time of the last push. It is unset until the first push.
39 google.protobuf.Timestamp pushed_at = 6;55 google.protobuf.Timestamp pushed_at = 6;
56+ // Settings are the merge settings of the repository. Always set in a
57+ // response, with the defaults for a repository that never changed them.
58+ RepoSettings settings = 7;
40 }59 }
41 60
42 // RepoService manages repositories and their metadata.61 // RepoService manages repositories and their metadata.
@@ -49,12 +68,13 @@ service RepoService {
49 // GetRepo returns one repository. Fails with not_found if it does not exist68 // GetRepo returns one repository. Fails with not_found if it does not exist
50 // or is private and not readable by the caller.69 // or is private and not readable by the caller.
51 rpc GetRepo(GetRepoRequest) returns (GetRepoResponse);70 rpc GetRepo(GetRepoRequest) returns (GetRepoResponse);
52- // CreateRepo creates an empty repository. Needs the repo:write scope. Fails71+ // CreateRepo creates an empty repository with the default settings. Needs
72+ // the repo:write scope. Fails
53 // with already_exists if the name is taken and invalid_argument if the name73 // with already_exists if the name is taken and invalid_argument if the name
54 // is not valid.74 // is not valid.
55 rpc CreateRepo(CreateRepoRequest) returns (CreateRepoResponse);75 rpc CreateRepo(CreateRepoRequest) returns (CreateRepoResponse);
56- // UpdateRepo changes the fields named in update_mask. Needs the repo:admin76+ // UpdateRepo changes the fields named in update_mask, including the merge
57- // scope.77+ // settings. Needs the repo:admin scope.
58 rpc UpdateRepo(UpdateRepoRequest) returns (UpdateRepoResponse);78 rpc UpdateRepo(UpdateRepoRequest) returns (UpdateRepoResponse);
59 // DeleteRepo deletes a repository. Needs the repo:admin scope. The server79 // DeleteRepo deletes a repository. Needs the repo:admin scope. The server
60 // moves the data aside rather than destroying it; there is no undelete RPC.80 // moves the data aside rather than destroying it; there is no undelete RPC.
@@ -124,9 +144,17 @@ message UpdateRepoRequest {
124 // update_mask contains "default_branch". The branch must exist, otherwise144 // update_mask contains "default_branch". The branch must exist, otherwise
125 // the call fails with failed_precondition.145 // the call fails with failed_precondition.
126 string default_branch = 4;146 string default_branch = 4;
127- // UpdateMask lists the fields to change: "description", "visibility" and/or147+ // UpdateMask lists the fields to change: "description", "visibility",
128- // "default_branch". It must not be empty. Other paths are invalid_argument.148+ // "default_branch", "settings.allowed_merge_modes" and/or
149+ // "settings.failing_checks_block". "settings" stands for both settings
150+ // paths. It must not be empty. Other paths are invalid_argument.
129 google.protobuf.FieldMask update_mask = 5;151 google.protobuf.FieldMask update_mask = 5;
152+ // Settings holds the new merge settings. Only the fields named in
153+ // update_mask are used. allowed_merge_modes must then not be empty nor
154+ // contain MERGE_MODE_UNSPECIFIED, otherwise the call fails with
155+ // invalid_argument. Changing settings needs the repo:admin scope, like any
156+ // other field.
157+ RepoSettings settings = 6;
130 }158 }
131 159
132 // UpdateRepoResponse holds the repository after the update.160 // UpdateRepoResponse holds the repository after the update.
M proto/ermite/server.proto +30 -0
@@ -10,6 +10,12 @@ service ServerService {
1010 // contract it implements. It needs no authentication, so a client can call
1111 // it first to check compatibility.
1212 rpc GetVersion(GetVersionRequest) returns (GetVersionResponse);
13+ // GetSigningKey returns the public key with which the server signs the
14+ // commits it creates (squash merges, see MergeMode). It needs no
15+ // authentication: the key is public, and anyone verifying a commit needs it.
16+ // Servers that implement it list "signing_key" in
17+ // GetVersionResponse.features.
18+ rpc GetSigningKey(GetSigningKeyRequest) returns (GetSigningKeyResponse);
1319 }
1420
1521 // GetVersionRequest is empty.
@@ -28,3 +34,27 @@ message GetVersionResponse {
2834 // Identifiers are lowercase, dot-separated and never reused.
2935 repeated string features = 2;
3036 }
37+
38+// GetSigningKeyRequest is empty.
39+message GetSigningKeyRequest {}
40+
41+// GetSigningKeyResponse describes the commit signing key of the server.
42+message GetSigningKeyResponse {
43+ // PublicKey is the public key as one OpenSSH authorized_keys line, for
44+ // example "ssh-ed25519 AAAA...". Commits are signed in the SSH signature
45+ // format (SSHSIG).
46+ string public_key = 1;
47+ // Fingerprint is the SHA-256 fingerprint of the key, in the form
48+ // "SHA256:<base64>" that ssh-keygen prints.
49+ string fingerprint = 2;
50+ // CommitterName is the name the server records as the committer of the
51+ // commits it creates.
52+ string committer_name = 3;
53+ // CommitterEmail is the email address the server records as the committer of
54+ // the commits it creates. It is the principal of allowed_signers.
55+ string committer_email = 4;
56+ // AllowedSigners is a ready-to-use line for an allowed signers file
57+ // ("<committer_email> <public_key>"), to verify these commits with
58+ // "git config gpg.ssh.allowedSignersFile".
59+ string allowed_signers = 5;
60+}
@@ -10,6 +10,12 @@ service ServerService {
10 // contract it implements. It needs no authentication, so a client can call10 // contract it implements. It needs no authentication, so a client can call
11 // it first to check compatibility.11 // it first to check compatibility.
12 rpc GetVersion(GetVersionRequest) returns (GetVersionResponse);12 rpc GetVersion(GetVersionRequest) returns (GetVersionResponse);
13+ // GetSigningKey returns the public key with which the server signs the
14+ // commits it creates (squash merges, see MergeMode). It needs no
15+ // authentication: the key is public, and anyone verifying a commit needs it.
16+ // Servers that implement it list "signing_key" in
17+ // GetVersionResponse.features.
18+ rpc GetSigningKey(GetSigningKeyRequest) returns (GetSigningKeyResponse);
13 }19 }
14 20
15 // GetVersionRequest is empty.21 // GetVersionRequest is empty.
@@ -28,3 +34,27 @@ message GetVersionResponse {
28 // Identifiers are lowercase, dot-separated and never reused.34 // Identifiers are lowercase, dot-separated and never reused.
29 repeated string features = 2;35 repeated string features = 2;
30 }36 }
37+
38+// GetSigningKeyRequest is empty.
39+message GetSigningKeyRequest {}
40+
41+// GetSigningKeyResponse describes the commit signing key of the server.
42+message GetSigningKeyResponse {
43+ // PublicKey is the public key as one OpenSSH authorized_keys line, for
44+ // example "ssh-ed25519 AAAA...". Commits are signed in the SSH signature
45+ // format (SSHSIG).
46+ string public_key = 1;
47+ // Fingerprint is the SHA-256 fingerprint of the key, in the form
48+ // "SHA256:<base64>" that ssh-keygen prints.
49+ string fingerprint = 2;
50+ // CommitterName is the name the server records as the committer of the
51+ // commits it creates.
52+ string committer_name = 3;
53+ // CommitterEmail is the email address the server records as the committer of
54+ // the commits it creates. It is the principal of allowed_signers.
55+ string committer_email = 4;
56+ // AllowedSigners is a ready-to-use line for an allowed signers file
57+ // ("<committer_email> <public_key>"), to verify these commits with
58+ // "git config gpg.ssh.allowedSignersFile".
59+ string allowed_signers = 5;
60+}
A proto/ermite/session.proto +51 -0
@@ -0,0 +1,51 @@
1+// SPDX-License-Identifier: MIT
2+
3+syntax = "proto3";
4+
5+package ermite;
6+
7+import "google/protobuf/timestamp.proto";
8+
9+// SessionService manages the owner's browser sessions. The browser itself
10+// never calls it: it signs in by opening the link that CreateLoginLink
11+// returns, and signs out by posting to the web's logout route.
12+//
13+// Every RPC is owner-only: it needs the root token, or a stored token that
14+// holds every scope and is not restricted to repositories. Any other caller
15+// gets permission_denied. Sessions do not go through this API: the Connect
16+// API accepts bearer tokens only and ignores cookies.
17+//
18+// Servers that implement this service list "session" in
19+// GetVersionResponse.features.
20+service SessionService {
21+ // CreateLoginLink creates a one-time link that signs the owner's browser
22+ // in. The link is valid for a short time (about a minute), can be used
23+ // once, and is kept in memory only: a server restart voids it. Opening it
24+ // shows a confirmation page; only confirming it consumes the link and sets
25+ // the session. Creating a new link does not void an earlier one. Fails with
26+ // resource_exhausted when too many links are pending.
27+ rpc CreateLoginLink(CreateLoginLinkRequest) returns (CreateLoginLinkResponse);
28+ // SignOutEverywhere ends every browser session at once, including those on
29+ // other devices. Pending login links are not affected. Tokens are not
30+ // affected either: revoke them with TokenService.
31+ rpc SignOutEverywhere(SignOutEverywhereRequest) returns (SignOutEverywhereResponse);
32+}
33+
34+// CreateLoginLinkRequest is empty.
35+message CreateLoginLinkRequest {}
36+
37+// CreateLoginLinkResponse holds the link.
38+message CreateLoginLinkResponse {
39+ // Url is the absolute link to open in a browser, on the public URL of the
40+ // server. It holds a secret: clients print it to the terminal and nowhere
41+ // else, and never log it.
42+ string url = 1;
43+ // ExpiresAt is when the link stops working if it is not used.
44+ google.protobuf.Timestamp expires_at = 2;
45+}
46+
47+// SignOutEverywhereRequest is empty.
48+message SignOutEverywhereRequest {}
49+
50+// SignOutEverywhereResponse is empty.
51+message SignOutEverywhereResponse {}
@@ -0,0 +1,51 @@
1+// SPDX-License-Identifier: MIT
2+
3+syntax = "proto3";
4+
5+package ermite;
6+
7+import "google/protobuf/timestamp.proto";
8+
9+// SessionService manages the owner's browser sessions. The browser itself
10+// never calls it: it signs in by opening the link that CreateLoginLink
11+// returns, and signs out by posting to the web's logout route.
12+//
13+// Every RPC is owner-only: it needs the root token, or a stored token that
14+// holds every scope and is not restricted to repositories. Any other caller
15+// gets permission_denied. Sessions do not go through this API: the Connect
16+// API accepts bearer tokens only and ignores cookies.
17+//
18+// Servers that implement this service list "session" in
19+// GetVersionResponse.features.
20+service SessionService {
21+ // CreateLoginLink creates a one-time link that signs the owner's browser
22+ // in. The link is valid for a short time (about a minute), can be used
23+ // once, and is kept in memory only: a server restart voids it. Opening it
24+ // shows a confirmation page; only confirming it consumes the link and sets
25+ // the session. Creating a new link does not void an earlier one. Fails with
26+ // resource_exhausted when too many links are pending.
27+ rpc CreateLoginLink(CreateLoginLinkRequest) returns (CreateLoginLinkResponse);
28+ // SignOutEverywhere ends every browser session at once, including those on
29+ // other devices. Pending login links are not affected. Tokens are not
30+ // affected either: revoke them with TokenService.
31+ rpc SignOutEverywhere(SignOutEverywhereRequest) returns (SignOutEverywhereResponse);
32+}
33+
34+// CreateLoginLinkRequest is empty.
35+message CreateLoginLinkRequest {}
36+
37+// CreateLoginLinkResponse holds the link.
38+message CreateLoginLinkResponse {
39+ // Url is the absolute link to open in a browser, on the public URL of the
40+ // server. It holds a secret: clients print it to the terminal and nowhere
41+ // else, and never log it.
42+ string url = 1;
43+ // ExpiresAt is when the link stops working if it is not used.
44+ google.protobuf.Timestamp expires_at = 2;
45+}
46+
47+// SignOutEverywhereRequest is empty.
48+message SignOutEverywhereRequest {}
49+
50+// SignOutEverywhereResponse is empty.
51+message SignOutEverywhereResponse {}
M proto/ermite/types.proto +20 -0
@@ -35,3 +35,23 @@ message Commit {
3535 // not valid UTF-8 is converted with the replacement character.
3636 string message = 6;
3737 }
38+
39+// MergeMode is how a merge request is merged. Both modes need the target to
40+// be an ancestor of the head: a branch behind its target is rebased by its
41+// author and pushed again, the server never rebases nor creates merge commits.
42+enum MergeMode {
43+ // Not set. In MergeMergeRequestRequest.mode it means fast-forward. Rejected
44+ // in RepoSettings.allowed_merge_modes. Returned on a merge request that was
45+ // not merged.
46+ MERGE_MODE_UNSPECIFIED = 0;
47+ // The target branch moves to the head. No commit is created and the
48+ // signatures of the branch's commits are kept.
49+ MERGE_MODE_FAST_FORWARD = 1;
50+ // One new commit is created with the tree of the head and the target as
51+ // only parent, signed by the server's key (see ServerService.GetSigningKey).
52+ // Its author is the author of the first commit of the branch, the other
53+ // authors are added as "Co-authored-by:" trailers, and its message is the
54+ // title, " (!<number>)", a blank line and the list of the squashed commits'
55+ // summaries.
56+ MERGE_MODE_SQUASH = 2;
57+}
@@ -35,3 +35,23 @@ message Commit {
35 // not valid UTF-8 is converted with the replacement character.35 // not valid UTF-8 is converted with the replacement character.
36 string message = 6;36 string message = 6;
37 }37 }
38+
39+// MergeMode is how a merge request is merged. Both modes need the target to
40+// be an ancestor of the head: a branch behind its target is rebased by its
41+// author and pushed again, the server never rebases nor creates merge commits.
42+enum MergeMode {
43+ // Not set. In MergeMergeRequestRequest.mode it means fast-forward. Rejected
44+ // in RepoSettings.allowed_merge_modes. Returned on a merge request that was
45+ // not merged.
46+ MERGE_MODE_UNSPECIFIED = 0;
47+ // The target branch moves to the head. No commit is created and the
48+ // signatures of the branch's commits are kept.
49+ MERGE_MODE_FAST_FORWARD = 1;
50+ // One new commit is created with the tree of the head and the target as
51+ // only parent, signed by the server's key (see ServerService.GetSigningKey).
52+ // Its author is the author of the first commit of the branch, the other
53+ // authors are added as "Co-authored-by:" trailers, and its message is the
54+ // title, " (!<number>)", a blank line and the list of the squashed commits'
55+ // summaries.
56+ MERGE_MODE_SQUASH = 2;
57+}

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