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 generated file: collapsed
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
@@ -121,6 +121,132 @@ func (x *GetVersionResponse) GetFeatures() []string {
121121 return nil
122122 }
123123
124+// GetSigningKeyRequest is empty.
125+type GetSigningKeyRequest struct {
126+ state protoimpl.MessageState `protogen:"open.v1"`
127+ unknownFields protoimpl.UnknownFields
128+ sizeCache protoimpl.SizeCache
129+}
130+
131+func (x *GetSigningKeyRequest) Reset() {
132+ *x = GetSigningKeyRequest{}
133+ mi := &file_ermite_server_proto_msgTypes[2]
134+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
135+ ms.StoreMessageInfo(mi)
136+}
137+
138+func (x *GetSigningKeyRequest) String() string {
139+ return protoimpl.X.MessageStringOf(x)
140+}
141+
142+func (*GetSigningKeyRequest) ProtoMessage() {}
143+
144+func (x *GetSigningKeyRequest) ProtoReflect() protoreflect.Message {
145+ mi := &file_ermite_server_proto_msgTypes[2]
146+ if x != nil {
147+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
148+ if ms.LoadMessageInfo() == nil {
149+ ms.StoreMessageInfo(mi)
150+ }
151+ return ms
152+ }
153+ return mi.MessageOf(x)
154+}
155+
156+// Deprecated: Use GetSigningKeyRequest.ProtoReflect.Descriptor instead.
157+func (*GetSigningKeyRequest) Descriptor() ([]byte, []int) {
158+ return file_ermite_server_proto_rawDescGZIP(), []int{2}
159+}
160+
161+// GetSigningKeyResponse describes the commit signing key of the server.
162+type GetSigningKeyResponse struct {
163+ state protoimpl.MessageState `protogen:"open.v1"`
164+ // PublicKey is the public key as one OpenSSH authorized_keys line, for
165+ // example "ssh-ed25519 AAAA...". Commits are signed in the SSH signature
166+ // format (SSHSIG).
167+ PublicKey string `protobuf:"bytes,1,opt,name=public_key,json=publicKey,proto3" json:"public_key,omitempty"`
168+ // Fingerprint is the SHA-256 fingerprint of the key, in the form
169+ // "SHA256:<base64>" that ssh-keygen prints.
170+ Fingerprint string `protobuf:"bytes,2,opt,name=fingerprint,proto3" json:"fingerprint,omitempty"`
171+ // CommitterName is the name the server records as the committer of the
172+ // commits it creates.
173+ CommitterName string `protobuf:"bytes,3,opt,name=committer_name,json=committerName,proto3" json:"committer_name,omitempty"`
174+ // CommitterEmail is the email address the server records as the committer of
175+ // the commits it creates. It is the principal of allowed_signers.
176+ CommitterEmail string `protobuf:"bytes,4,opt,name=committer_email,json=committerEmail,proto3" json:"committer_email,omitempty"`
177+ // AllowedSigners is a ready-to-use line for an allowed signers file
178+ // ("<committer_email> <public_key>"), to verify these commits with
179+ // "git config gpg.ssh.allowedSignersFile".
180+ AllowedSigners string `protobuf:"bytes,5,opt,name=allowed_signers,json=allowedSigners,proto3" json:"allowed_signers,omitempty"`
181+ unknownFields protoimpl.UnknownFields
182+ sizeCache protoimpl.SizeCache
183+}
184+
185+func (x *GetSigningKeyResponse) Reset() {
186+ *x = GetSigningKeyResponse{}
187+ mi := &file_ermite_server_proto_msgTypes[3]
188+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
189+ ms.StoreMessageInfo(mi)
190+}
191+
192+func (x *GetSigningKeyResponse) String() string {
193+ return protoimpl.X.MessageStringOf(x)
194+}
195+
196+func (*GetSigningKeyResponse) ProtoMessage() {}
197+
198+func (x *GetSigningKeyResponse) ProtoReflect() protoreflect.Message {
199+ mi := &file_ermite_server_proto_msgTypes[3]
200+ if x != nil {
201+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
202+ if ms.LoadMessageInfo() == nil {
203+ ms.StoreMessageInfo(mi)
204+ }
205+ return ms
206+ }
207+ return mi.MessageOf(x)
208+}
209+
210+// Deprecated: Use GetSigningKeyResponse.ProtoReflect.Descriptor instead.
211+func (*GetSigningKeyResponse) Descriptor() ([]byte, []int) {
212+ return file_ermite_server_proto_rawDescGZIP(), []int{3}
213+}
214+
215+func (x *GetSigningKeyResponse) GetPublicKey() string {
216+ if x != nil {
217+ return x.PublicKey
218+ }
219+ return ""
220+}
221+
222+func (x *GetSigningKeyResponse) GetFingerprint() string {
223+ if x != nil {
224+ return x.Fingerprint
225+ }
226+ return ""
227+}
228+
229+func (x *GetSigningKeyResponse) GetCommitterName() string {
230+ if x != nil {
231+ return x.CommitterName
232+ }
233+ return ""
234+}
235+
236+func (x *GetSigningKeyResponse) GetCommitterEmail() string {
237+ if x != nil {
238+ return x.CommitterEmail
239+ }
240+ return ""
241+}
242+
243+func (x *GetSigningKeyResponse) GetAllowedSigners() string {
244+ if x != nil {
245+ return x.AllowedSigners
246+ }
247+ return ""
248+}
249+
124250 var File_ermite_server_proto protoreflect.FileDescriptor
125251
126252 const file_ermite_server_proto_rawDesc = "" +
@@ -129,10 +255,19 @@ const file_ermite_server_proto_rawDesc = "" +
129255 "\x11GetVersionRequest\"J\n" +
130256 "\x12GetVersionResponse\x12\x18\n" +
131257 "\aversion\x18\x01 \x01(\tR\aversion\x12\x1a\n" +
132- "\bfeatures\x18\x02 \x03(\tR\bfeatures2T\n" +
258+ "\bfeatures\x18\x02 \x03(\tR\bfeatures\"\x16\n" +
259+ "\x14GetSigningKeyRequest\"\xd1\x01\n" +
260+ "\x15GetSigningKeyResponse\x12\x1d\n" +
261+ "\n" +
262+ "public_key\x18\x01 \x01(\tR\tpublicKey\x12 \n" +
263+ "\vfingerprint\x18\x02 \x01(\tR\vfingerprint\x12%\n" +
264+ "\x0ecommitter_name\x18\x03 \x01(\tR\rcommitterName\x12'\n" +
265+ "\x0fcommitter_email\x18\x04 \x01(\tR\x0ecommitterEmail\x12'\n" +
266+ "\x0fallowed_signers\x18\x05 \x01(\tR\x0eallowedSigners2\xa2\x01\n" +
133267 "\rServerService\x12C\n" +
134268 "\n" +
135- "GetVersion\x12\x19.ermite.GetVersionRequest\x1a\x1a.ermite.GetVersionResponseBr\n" +
269+ "GetVersion\x12\x19.ermite.GetVersionRequest\x1a\x1a.ermite.GetVersionResponse\x12L\n" +
270+ "\rGetSigningKey\x12\x1c.ermite.GetSigningKeyRequest\x1a\x1d.ermite.GetSigningKeyResponseBr\n" +
136271 "\n" +
137272 "com.ermiteB\vServerProtoP\x01Z\x1fbnema.dev/ermite-api/gen/ermite\xa2\x02\x03EXX\xaa\x02\x06Ermite\xca\x02\x06Ermite\xe2\x02\x12Ermite\\GPBMetadata\xea\x02\x06Ermiteb\x06proto3"
138273
@@ -148,16 +283,20 @@ func file_ermite_server_proto_rawDescGZIP() []byte {
148283 return file_ermite_server_proto_rawDescData
149284 }
150285
151-var file_ermite_server_proto_msgTypes = make([]protoimpl.MessageInfo, 2)
286+var file_ermite_server_proto_msgTypes = make([]protoimpl.MessageInfo, 4)
152287 var file_ermite_server_proto_goTypes = []any{
153- (*GetVersionRequest)(nil), // 0: ermite.GetVersionRequest
154- (*GetVersionResponse)(nil), // 1: ermite.GetVersionResponse
288+ (*GetVersionRequest)(nil), // 0: ermite.GetVersionRequest
289+ (*GetVersionResponse)(nil), // 1: ermite.GetVersionResponse
290+ (*GetSigningKeyRequest)(nil), // 2: ermite.GetSigningKeyRequest
291+ (*GetSigningKeyResponse)(nil), // 3: ermite.GetSigningKeyResponse
155292 }
156293 var file_ermite_server_proto_depIdxs = []int32{
157294 0, // 0: ermite.ServerService.GetVersion:input_type -> ermite.GetVersionRequest
158- 1, // 1: ermite.ServerService.GetVersion:output_type -> ermite.GetVersionResponse
159- 1, // [1:2] is the sub-list for method output_type
160- 0, // [0:1] is the sub-list for method input_type
295+ 2, // 1: ermite.ServerService.GetSigningKey:input_type -> ermite.GetSigningKeyRequest
296+ 1, // 2: ermite.ServerService.GetVersion:output_type -> ermite.GetVersionResponse
297+ 3, // 3: ermite.ServerService.GetSigningKey:output_type -> ermite.GetSigningKeyResponse
298+ 2, // [2:4] is the sub-list for method output_type
299+ 0, // [0:2] is the sub-list for method input_type
161300 0, // [0:0] is the sub-list for extension type_name
162301 0, // [0:0] is the sub-list for extension extendee
163302 0, // [0:0] is the sub-list for field type_name
@@ -174,7 +313,7 @@ func file_ermite_server_proto_init() {
174313 GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
175314 RawDescriptor: unsafe.Slice(unsafe.StringData(file_ermite_server_proto_rawDesc), len(file_ermite_server_proto_rawDesc)),
176315 NumEnums: 0,
177- NumMessages: 2,
316+ NumMessages: 4,
178317 NumExtensions: 0,
179318 NumServices: 1,
180319 },
@@ -121,6 +121,132 @@ func (x *GetVersionResponse) GetFeatures() []string {
121 return nil121 return nil
122 }122 }
123 123
124+// GetSigningKeyRequest is empty.
125+type GetSigningKeyRequest struct {
126+ state protoimpl.MessageState `protogen:"open.v1"`
127+ unknownFields protoimpl.UnknownFields
128+ sizeCache protoimpl.SizeCache
129+}
130+
131+func (x *GetSigningKeyRequest) Reset() {
132+ *x = GetSigningKeyRequest{}
133+ mi := &file_ermite_server_proto_msgTypes[2]
134+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
135+ ms.StoreMessageInfo(mi)
136+}
137+
138+func (x *GetSigningKeyRequest) String() string {
139+ return protoimpl.X.MessageStringOf(x)
140+}
141+
142+func (*GetSigningKeyRequest) ProtoMessage() {}
143+
144+func (x *GetSigningKeyRequest) ProtoReflect() protoreflect.Message {
145+ mi := &file_ermite_server_proto_msgTypes[2]
146+ if x != nil {
147+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
148+ if ms.LoadMessageInfo() == nil {
149+ ms.StoreMessageInfo(mi)
150+ }
151+ return ms
152+ }
153+ return mi.MessageOf(x)
154+}
155+
156+// Deprecated: Use GetSigningKeyRequest.ProtoReflect.Descriptor instead.
157+func (*GetSigningKeyRequest) Descriptor() ([]byte, []int) {
158+ return file_ermite_server_proto_rawDescGZIP(), []int{2}
159+}
160+
161+// GetSigningKeyResponse describes the commit signing key of the server.
162+type GetSigningKeyResponse struct {
163+ state protoimpl.MessageState `protogen:"open.v1"`
164+ // PublicKey is the public key as one OpenSSH authorized_keys line, for
165+ // example "ssh-ed25519 AAAA...". Commits are signed in the SSH signature
166+ // format (SSHSIG).
167+ PublicKey string `protobuf:"bytes,1,opt,name=public_key,json=publicKey,proto3" json:"public_key,omitempty"`
168+ // Fingerprint is the SHA-256 fingerprint of the key, in the form
169+ // "SHA256:<base64>" that ssh-keygen prints.
170+ Fingerprint string `protobuf:"bytes,2,opt,name=fingerprint,proto3" json:"fingerprint,omitempty"`
171+ // CommitterName is the name the server records as the committer of the
172+ // commits it creates.
173+ CommitterName string `protobuf:"bytes,3,opt,name=committer_name,json=committerName,proto3" json:"committer_name,omitempty"`
174+ // CommitterEmail is the email address the server records as the committer of
175+ // the commits it creates. It is the principal of allowed_signers.
176+ CommitterEmail string `protobuf:"bytes,4,opt,name=committer_email,json=committerEmail,proto3" json:"committer_email,omitempty"`
177+ // AllowedSigners is a ready-to-use line for an allowed signers file
178+ // ("<committer_email> <public_key>"), to verify these commits with
179+ // "git config gpg.ssh.allowedSignersFile".
180+ AllowedSigners string `protobuf:"bytes,5,opt,name=allowed_signers,json=allowedSigners,proto3" json:"allowed_signers,omitempty"`
181+ unknownFields protoimpl.UnknownFields
182+ sizeCache protoimpl.SizeCache
183+}
184+
185+func (x *GetSigningKeyResponse) Reset() {
186+ *x = GetSigningKeyResponse{}
187+ mi := &file_ermite_server_proto_msgTypes[3]
188+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
189+ ms.StoreMessageInfo(mi)
190+}
191+
192+func (x *GetSigningKeyResponse) String() string {
193+ return protoimpl.X.MessageStringOf(x)
194+}
195+
196+func (*GetSigningKeyResponse) ProtoMessage() {}
197+
198+func (x *GetSigningKeyResponse) ProtoReflect() protoreflect.Message {
199+ mi := &file_ermite_server_proto_msgTypes[3]
200+ if x != nil {
201+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
202+ if ms.LoadMessageInfo() == nil {
203+ ms.StoreMessageInfo(mi)
204+ }
205+ return ms
206+ }
207+ return mi.MessageOf(x)
208+}
209+
210+// Deprecated: Use GetSigningKeyResponse.ProtoReflect.Descriptor instead.
211+func (*GetSigningKeyResponse) Descriptor() ([]byte, []int) {
212+ return file_ermite_server_proto_rawDescGZIP(), []int{3}
213+}
214+
215+func (x *GetSigningKeyResponse) GetPublicKey() string {
216+ if x != nil {
217+ return x.PublicKey
218+ }
219+ return ""
220+}
221+
222+func (x *GetSigningKeyResponse) GetFingerprint() string {
223+ if x != nil {
224+ return x.Fingerprint
225+ }
226+ return ""
227+}
228+
229+func (x *GetSigningKeyResponse) GetCommitterName() string {
230+ if x != nil {
231+ return x.CommitterName
232+ }
233+ return ""
234+}
235+
236+func (x *GetSigningKeyResponse) GetCommitterEmail() string {
237+ if x != nil {
238+ return x.CommitterEmail
239+ }
240+ return ""
241+}
242+
243+func (x *GetSigningKeyResponse) GetAllowedSigners() string {
244+ if x != nil {
245+ return x.AllowedSigners
246+ }
247+ return ""
248+}
249+
124 var File_ermite_server_proto protoreflect.FileDescriptor250 var File_ermite_server_proto protoreflect.FileDescriptor
125 251
126 const file_ermite_server_proto_rawDesc = "" +252 const file_ermite_server_proto_rawDesc = "" +
@@ -129,10 +255,19 @@ const file_ermite_server_proto_rawDesc = "" +
129 "\x11GetVersionRequest\"J\n" +255 "\x11GetVersionRequest\"J\n" +
130 "\x12GetVersionResponse\x12\x18\n" +256 "\x12GetVersionResponse\x12\x18\n" +
131 "\aversion\x18\x01 \x01(\tR\aversion\x12\x1a\n" +257 "\aversion\x18\x01 \x01(\tR\aversion\x12\x1a\n" +
132- "\bfeatures\x18\x02 \x03(\tR\bfeatures2T\n" +258+ "\bfeatures\x18\x02 \x03(\tR\bfeatures\"\x16\n" +
259+ "\x14GetSigningKeyRequest\"\xd1\x01\n" +
260+ "\x15GetSigningKeyResponse\x12\x1d\n" +
261+ "\n" +
262+ "public_key\x18\x01 \x01(\tR\tpublicKey\x12 \n" +
263+ "\vfingerprint\x18\x02 \x01(\tR\vfingerprint\x12%\n" +
264+ "\x0ecommitter_name\x18\x03 \x01(\tR\rcommitterName\x12'\n" +
265+ "\x0fcommitter_email\x18\x04 \x01(\tR\x0ecommitterEmail\x12'\n" +
266+ "\x0fallowed_signers\x18\x05 \x01(\tR\x0eallowedSigners2\xa2\x01\n" +
133 "\rServerService\x12C\n" +267 "\rServerService\x12C\n" +
134 "\n" +268 "\n" +
135- "GetVersion\x12\x19.ermite.GetVersionRequest\x1a\x1a.ermite.GetVersionResponseBr\n" +269+ "GetVersion\x12\x19.ermite.GetVersionRequest\x1a\x1a.ermite.GetVersionResponse\x12L\n" +
270+ "\rGetSigningKey\x12\x1c.ermite.GetSigningKeyRequest\x1a\x1d.ermite.GetSigningKeyResponseBr\n" +
136 "\n" +271 "\n" +
137 "com.ermiteB\vServerProtoP\x01Z\x1fbnema.dev/ermite-api/gen/ermite\xa2\x02\x03EXX\xaa\x02\x06Ermite\xca\x02\x06Ermite\xe2\x02\x12Ermite\\GPBMetadata\xea\x02\x06Ermiteb\x06proto3"272 "com.ermiteB\vServerProtoP\x01Z\x1fbnema.dev/ermite-api/gen/ermite\xa2\x02\x03EXX\xaa\x02\x06Ermite\xca\x02\x06Ermite\xe2\x02\x12Ermite\\GPBMetadata\xea\x02\x06Ermiteb\x06proto3"
138 273
@@ -148,16 +283,20 @@ func file_ermite_server_proto_rawDescGZIP() []byte {
148 return file_ermite_server_proto_rawDescData283 return file_ermite_server_proto_rawDescData
149 }284 }
150 285
151-var file_ermite_server_proto_msgTypes = make([]protoimpl.MessageInfo, 2)286+var file_ermite_server_proto_msgTypes = make([]protoimpl.MessageInfo, 4)
152 var file_ermite_server_proto_goTypes = []any{287 var file_ermite_server_proto_goTypes = []any{
153- (*GetVersionRequest)(nil), // 0: ermite.GetVersionRequest288+ (*GetVersionRequest)(nil), // 0: ermite.GetVersionRequest
154- (*GetVersionResponse)(nil), // 1: ermite.GetVersionResponse289+ (*GetVersionResponse)(nil), // 1: ermite.GetVersionResponse
290+ (*GetSigningKeyRequest)(nil), // 2: ermite.GetSigningKeyRequest
291+ (*GetSigningKeyResponse)(nil), // 3: ermite.GetSigningKeyResponse
155 }292 }
156 var file_ermite_server_proto_depIdxs = []int32{293 var file_ermite_server_proto_depIdxs = []int32{
157 0, // 0: ermite.ServerService.GetVersion:input_type -> ermite.GetVersionRequest294 0, // 0: ermite.ServerService.GetVersion:input_type -> ermite.GetVersionRequest
158- 1, // 1: ermite.ServerService.GetVersion:output_type -> ermite.GetVersionResponse295+ 2, // 1: ermite.ServerService.GetSigningKey:input_type -> ermite.GetSigningKeyRequest
159- 1, // [1:2] is the sub-list for method output_type296+ 1, // 2: ermite.ServerService.GetVersion:output_type -> ermite.GetVersionResponse
160- 0, // [0:1] is the sub-list for method input_type297+ 3, // 3: ermite.ServerService.GetSigningKey:output_type -> ermite.GetSigningKeyResponse
298+ 2, // [2:4] is the sub-list for method output_type
299+ 0, // [0:2] is the sub-list for method input_type
161 0, // [0:0] is the sub-list for extension type_name300 0, // [0:0] is the sub-list for extension type_name
162 0, // [0:0] is the sub-list for extension extendee301 0, // [0:0] is the sub-list for extension extendee
163 0, // [0:0] is the sub-list for field type_name302 0, // [0:0] is the sub-list for field type_name
@@ -174,7 +313,7 @@ func file_ermite_server_proto_init() {
174 GoPackagePath: reflect.TypeOf(x{}).PkgPath(),313 GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
175 RawDescriptor: unsafe.Slice(unsafe.StringData(file_ermite_server_proto_rawDesc), len(file_ermite_server_proto_rawDesc)),314 RawDescriptor: unsafe.Slice(unsafe.StringData(file_ermite_server_proto_rawDesc), len(file_ermite_server_proto_rawDesc)),
176 NumEnums: 0,315 NumEnums: 0,
177- NumMessages: 2,316+ NumMessages: 4,
178 NumExtensions: 0,317 NumExtensions: 0,
179 NumServices: 1,318 NumServices: 1,
180 },319 },
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