detached 0be2bc9

feat(proto): merge request note threads, ranges, edits and verdicts

commit
0be2bc9659a87db8a05ab55ffcaf21eb574e5a44
author
brice · · 2026-10-19 05:20 UTC
parent
d244a03
signature
GPG VerifiedCD88F9E564E0102646FB6077A1A07C7C7C3E349D · arch-gpg Verified for the committer email
8 files · +1274 -206
M docs/services.md +1 -1
@@ -5,7 +5,7 @@
55 | `RepoService` | `ListRepos`, `GetRepo`, `CreateRepo`, `UpdateRepo` (field mask, merge settings), `DeleteRepo` |
66 | `GitService` | `ListRefs`, `GetTree`, `ListPaths`, `GetBlob` (streamed), `ListCommits`, `GetCommit` (structured diff), `ResolveRevision` |
77 | `TokenService` | `CreateToken` (secret shown once), `ListTokens`, `RevokeToken` |
8-| `MergeRequestService` | `CreateMergeRequest`, `ListMergeRequests`, `GetMergeRequest` (commits, diff, comments), `AddComment`, `ResolveComment`, `CloseMergeRequest`, `MergeMergeRequest`, `MarkRead` |
8+| `MergeRequestService` | `CreateMergeRequest`, `ListMergeRequests`, `GetMergeRequest` (commits, diff, comments), `AddComment`, `EditComment`, `DeleteComment`, `ResolveComment`, `CloseMergeRequest`, `MergeMergeRequest`, `MarkRead` |
99 | `SessionService` | `CreateLoginLink`, `SignOutEverywhere` (owner only) |
1010 | `EventService` | `Watch` (streamed, resumable) |
1111 | `ServerService` | `GetVersion` (version and features), `GetSigningKey`, `RotateOIDCKey` (owner only) |
@@ -5,7 +5,7 @@
5 | `RepoService` | `ListRepos`, `GetRepo`, `CreateRepo`, `UpdateRepo` (field mask, merge settings), `DeleteRepo` |5 | `RepoService` | `ListRepos`, `GetRepo`, `CreateRepo`, `UpdateRepo` (field mask, merge settings), `DeleteRepo` |
6 | `GitService` | `ListRefs`, `GetTree`, `ListPaths`, `GetBlob` (streamed), `ListCommits`, `GetCommit` (structured diff), `ResolveRevision` |6 | `GitService` | `ListRefs`, `GetTree`, `ListPaths`, `GetBlob` (streamed), `ListCommits`, `GetCommit` (structured diff), `ResolveRevision` |
7 | `TokenService` | `CreateToken` (secret shown once), `ListTokens`, `RevokeToken` |7 | `TokenService` | `CreateToken` (secret shown once), `ListTokens`, `RevokeToken` |
8-| `MergeRequestService` | `CreateMergeRequest`, `ListMergeRequests`, `GetMergeRequest` (commits, diff, comments), `AddComment`, `ResolveComment`, `CloseMergeRequest`, `MergeMergeRequest`, `MarkRead` |8+| `MergeRequestService` | `CreateMergeRequest`, `ListMergeRequests`, `GetMergeRequest` (commits, diff, comments), `AddComment`, `EditComment`, `DeleteComment`, `ResolveComment`, `CloseMergeRequest`, `MergeMergeRequest`, `MarkRead` |
9 | `SessionService` | `CreateLoginLink`, `SignOutEverywhere` (owner only) |9 | `SessionService` | `CreateLoginLink`, `SignOutEverywhere` (owner only) |
10 | `EventService` | `Watch` (streamed, resumable) |10 | `EventService` | `Watch` (streamed, resumable) |
11 | `ServerService` | `GetVersion` (version and features), `GetSigningKey`, `RotateOIDCKey` (owner only) |11 | `ServerService` | `GetVersion` (version and features), `GetSigningKey`, `RotateOIDCKey` (owner only) |
M gen/ermite/ermiteconnect/merge_request.connect.go +110 -14
@@ -47,6 +47,12 @@ const (
4747 // MergeRequestServiceAddCommentProcedure is the fully-qualified name of the MergeRequestService's
4848 // AddComment RPC.
4949 MergeRequestServiceAddCommentProcedure = "/ermite.MergeRequestService/AddComment"
50+ // MergeRequestServiceEditCommentProcedure is the fully-qualified name of the MergeRequestService's
51+ // EditComment RPC.
52+ MergeRequestServiceEditCommentProcedure = "/ermite.MergeRequestService/EditComment"
53+ // MergeRequestServiceDeleteCommentProcedure is the fully-qualified name of the
54+ // MergeRequestService's DeleteComment RPC.
55+ MergeRequestServiceDeleteCommentProcedure = "/ermite.MergeRequestService/DeleteComment"
5056 // MergeRequestServiceResolveCommentProcedure is the fully-qualified name of the
5157 // MergeRequestService's ResolveComment RPC.
5258 MergeRequestServiceResolveCommentProcedure = "/ermite.MergeRequestService/ResolveComment"
@@ -79,14 +85,35 @@ type MergeRequestServiceClient interface {
7985 // caller last marked read with the current head. Fails with not_found if
8086 // the repository or the number is unknown.
8187 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.
88+ // AddComment adds a general comment, a comment on a line or a range of
89+ // lines of the diff, or a reply to a root comment. Needs the mr:write
90+ // scope; setting a verdict also needs the mr:review scope (permission_denied
91+ // without it). Fails with invalid_argument if the text breaks a rule (it may
92+ // be empty only with a verdict), if path, line, start_line and side do not
93+ // designate lines of the current diff of the merge request, if reply_to is
94+ // not the id of a root comment that is not deleted, if a reply sets path,
95+ // line, start_line, side or verdict, or if a verdict is set on a reply; and
96+ // with failed_precondition if the merge request is not open. A comment with
97+ // VERDICT_APPROVE is created resolved.
8798 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
99+ // EditComment replaces the text of a comment. Needs the mr:write scope, and
100+ // only the author of the comment can edit it (permission_denied
101+ // otherwise). Fails with invalid_argument if the text breaks a rule (it may
102+ // be empty only for a comment with a verdict), with not_found if the comment
103+ // is unknown, and with failed_precondition if the merge request is not open
104+ // or the comment was deleted. Sending the current text succeeds and changes
105+ // nothing.
106+ EditComment(context.Context, *connect.Request[ermite.EditCommentRequest]) (*connect.Response[ermite.EditCommentResponse], error)
107+ // DeleteComment deletes a comment: its text is emptied and deleted is set,
108+ // while its id, anchor, replies and verdict remain. Needs the mr:write
109+ // scope, and only the author of the comment can delete it
110+ // (permission_denied otherwise). Fails with not_found if the comment is
111+ // unknown and with failed_precondition if the merge request is not open.
112+ // Deleting a comment already deleted succeeds and changes nothing.
113+ DeleteComment(context.Context, *connect.Request[ermite.DeleteCommentRequest]) (*connect.Response[ermite.DeleteCommentResponse], error)
114+ // ResolveComment marks a thread resolved or unresolved. Needs the mr:write
115+ // scope. Resolution belongs to the root comment: on a reply it fails with
116+ // invalid_argument. Fails with not_found if the comment is unknown and with
90117 // failed_precondition if the merge request is not open. Setting the state
91118 // the comment already has succeeds and changes nothing.
92119 ResolveComment(context.Context, *connect.Request[ermite.ResolveCommentRequest]) (*connect.Response[ermite.ResolveCommentResponse], error)
@@ -149,6 +176,18 @@ func NewMergeRequestServiceClient(httpClient connect.HTTPClient, baseURL string,
149176 connect.WithSchema(mergeRequestServiceMethods.ByName("AddComment")),
150177 connect.WithClientOptions(opts...),
151178 ),
179+ editComment: connect.NewClient[ermite.EditCommentRequest, ermite.EditCommentResponse](
180+ httpClient,
181+ baseURL+MergeRequestServiceEditCommentProcedure,
182+ connect.WithSchema(mergeRequestServiceMethods.ByName("EditComment")),
183+ connect.WithClientOptions(opts...),
184+ ),
185+ deleteComment: connect.NewClient[ermite.DeleteCommentRequest, ermite.DeleteCommentResponse](
186+ httpClient,
187+ baseURL+MergeRequestServiceDeleteCommentProcedure,
188+ connect.WithSchema(mergeRequestServiceMethods.ByName("DeleteComment")),
189+ connect.WithClientOptions(opts...),
190+ ),
152191 resolveComment: connect.NewClient[ermite.ResolveCommentRequest, ermite.ResolveCommentResponse](
153192 httpClient,
154193 baseURL+MergeRequestServiceResolveCommentProcedure,
@@ -182,6 +221,8 @@ type mergeRequestServiceClient struct {
182221 listMergeRequests *connect.Client[ermite.ListMergeRequestsRequest, ermite.ListMergeRequestsResponse]
183222 getMergeRequest *connect.Client[ermite.GetMergeRequestRequest, ermite.GetMergeRequestResponse]
184223 addComment *connect.Client[ermite.AddCommentRequest, ermite.AddCommentResponse]
224+ editComment *connect.Client[ermite.EditCommentRequest, ermite.EditCommentResponse]
225+ deleteComment *connect.Client[ermite.DeleteCommentRequest, ermite.DeleteCommentResponse]
185226 resolveComment *connect.Client[ermite.ResolveCommentRequest, ermite.ResolveCommentResponse]
186227 closeMergeRequest *connect.Client[ermite.CloseMergeRequestRequest, ermite.CloseMergeRequestResponse]
187228 mergeMergeRequest *connect.Client[ermite.MergeMergeRequestRequest, ermite.MergeMergeRequestResponse]
@@ -208,6 +249,16 @@ func (c *mergeRequestServiceClient) AddComment(ctx context.Context, req *connect
208249 return c.addComment.CallUnary(ctx, req)
209250 }
210251
252+// EditComment calls ermite.MergeRequestService.EditComment.
253+func (c *mergeRequestServiceClient) EditComment(ctx context.Context, req *connect.Request[ermite.EditCommentRequest]) (*connect.Response[ermite.EditCommentResponse], error) {
254+ return c.editComment.CallUnary(ctx, req)
255+}
256+
257+// DeleteComment calls ermite.MergeRequestService.DeleteComment.
258+func (c *mergeRequestServiceClient) DeleteComment(ctx context.Context, req *connect.Request[ermite.DeleteCommentRequest]) (*connect.Response[ermite.DeleteCommentResponse], error) {
259+ return c.deleteComment.CallUnary(ctx, req)
260+}
261+
211262 // ResolveComment calls ermite.MergeRequestService.ResolveComment.
212263 func (c *mergeRequestServiceClient) ResolveComment(ctx context.Context, req *connect.Request[ermite.ResolveCommentRequest]) (*connect.Response[ermite.ResolveCommentResponse], error) {
213264 return c.resolveComment.CallUnary(ctx, req)
@@ -246,14 +297,35 @@ type MergeRequestServiceHandler interface {
246297 // caller last marked read with the current head. Fails with not_found if
247298 // the repository or the number is unknown.
248299 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.
300+ // AddComment adds a general comment, a comment on a line or a range of
301+ // lines of the diff, or a reply to a root comment. Needs the mr:write
302+ // scope; setting a verdict also needs the mr:review scope (permission_denied
303+ // without it). Fails with invalid_argument if the text breaks a rule (it may
304+ // be empty only with a verdict), if path, line, start_line and side do not
305+ // designate lines of the current diff of the merge request, if reply_to is
306+ // not the id of a root comment that is not deleted, if a reply sets path,
307+ // line, start_line, side or verdict, or if a verdict is set on a reply; and
308+ // with failed_precondition if the merge request is not open. A comment with
309+ // VERDICT_APPROVE is created resolved.
254310 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
311+ // EditComment replaces the text of a comment. Needs the mr:write scope, and
312+ // only the author of the comment can edit it (permission_denied
313+ // otherwise). Fails with invalid_argument if the text breaks a rule (it may
314+ // be empty only for a comment with a verdict), with not_found if the comment
315+ // is unknown, and with failed_precondition if the merge request is not open
316+ // or the comment was deleted. Sending the current text succeeds and changes
317+ // nothing.
318+ EditComment(context.Context, *connect.Request[ermite.EditCommentRequest]) (*connect.Response[ermite.EditCommentResponse], error)
319+ // DeleteComment deletes a comment: its text is emptied and deleted is set,
320+ // while its id, anchor, replies and verdict remain. Needs the mr:write
321+ // scope, and only the author of the comment can delete it
322+ // (permission_denied otherwise). Fails with not_found if the comment is
323+ // unknown and with failed_precondition if the merge request is not open.
324+ // Deleting a comment already deleted succeeds and changes nothing.
325+ DeleteComment(context.Context, *connect.Request[ermite.DeleteCommentRequest]) (*connect.Response[ermite.DeleteCommentResponse], error)
326+ // ResolveComment marks a thread resolved or unresolved. Needs the mr:write
327+ // scope. Resolution belongs to the root comment: on a reply it fails with
328+ // invalid_argument. Fails with not_found if the comment is unknown and with
257329 // failed_precondition if the merge request is not open. Setting the state
258330 // the comment already has succeeds and changes nothing.
259331 ResolveComment(context.Context, *connect.Request[ermite.ResolveCommentRequest]) (*connect.Response[ermite.ResolveCommentResponse], error)
@@ -312,6 +384,18 @@ func NewMergeRequestServiceHandler(svc MergeRequestServiceHandler, opts ...conne
312384 connect.WithSchema(mergeRequestServiceMethods.ByName("AddComment")),
313385 connect.WithHandlerOptions(opts...),
314386 )
387+ mergeRequestServiceEditCommentHandler := connect.NewUnaryHandler(
388+ MergeRequestServiceEditCommentProcedure,
389+ svc.EditComment,
390+ connect.WithSchema(mergeRequestServiceMethods.ByName("EditComment")),
391+ connect.WithHandlerOptions(opts...),
392+ )
393+ mergeRequestServiceDeleteCommentHandler := connect.NewUnaryHandler(
394+ MergeRequestServiceDeleteCommentProcedure,
395+ svc.DeleteComment,
396+ connect.WithSchema(mergeRequestServiceMethods.ByName("DeleteComment")),
397+ connect.WithHandlerOptions(opts...),
398+ )
315399 mergeRequestServiceResolveCommentHandler := connect.NewUnaryHandler(
316400 MergeRequestServiceResolveCommentProcedure,
317401 svc.ResolveComment,
@@ -346,6 +430,10 @@ func NewMergeRequestServiceHandler(svc MergeRequestServiceHandler, opts ...conne
346430 mergeRequestServiceGetMergeRequestHandler.ServeHTTP(w, r)
347431 case MergeRequestServiceAddCommentProcedure:
348432 mergeRequestServiceAddCommentHandler.ServeHTTP(w, r)
433+ case MergeRequestServiceEditCommentProcedure:
434+ mergeRequestServiceEditCommentHandler.ServeHTTP(w, r)
435+ case MergeRequestServiceDeleteCommentProcedure:
436+ mergeRequestServiceDeleteCommentHandler.ServeHTTP(w, r)
349437 case MergeRequestServiceResolveCommentProcedure:
350438 mergeRequestServiceResolveCommentHandler.ServeHTTP(w, r)
351439 case MergeRequestServiceCloseMergeRequestProcedure:
@@ -379,6 +467,14 @@ func (UnimplementedMergeRequestServiceHandler) AddComment(context.Context, *conn
379467 return nil, connect.NewError(connect.CodeUnimplemented, errors.New("ermite.MergeRequestService.AddComment is not implemented"))
380468 }
381469
470+func (UnimplementedMergeRequestServiceHandler) EditComment(context.Context, *connect.Request[ermite.EditCommentRequest]) (*connect.Response[ermite.EditCommentResponse], error) {
471+ return nil, connect.NewError(connect.CodeUnimplemented, errors.New("ermite.MergeRequestService.EditComment is not implemented"))
472+}
473+
474+func (UnimplementedMergeRequestServiceHandler) DeleteComment(context.Context, *connect.Request[ermite.DeleteCommentRequest]) (*connect.Response[ermite.DeleteCommentResponse], error) {
475+ return nil, connect.NewError(connect.CodeUnimplemented, errors.New("ermite.MergeRequestService.DeleteComment is not implemented"))
476+}
477+
382478 func (UnimplementedMergeRequestServiceHandler) ResolveComment(context.Context, *connect.Request[ermite.ResolveCommentRequest]) (*connect.Response[ermite.ResolveCommentResponse], error) {
383479 return nil, connect.NewError(connect.CodeUnimplemented, errors.New("ermite.MergeRequestService.ResolveComment is not implemented"))
384480 }
@@ -47,6 +47,12 @@ const (
47 // MergeRequestServiceAddCommentProcedure is the fully-qualified name of the MergeRequestService's47 // MergeRequestServiceAddCommentProcedure is the fully-qualified name of the MergeRequestService's
48 // AddComment RPC.48 // AddComment RPC.
49 MergeRequestServiceAddCommentProcedure = "/ermite.MergeRequestService/AddComment"49 MergeRequestServiceAddCommentProcedure = "/ermite.MergeRequestService/AddComment"
50+ // MergeRequestServiceEditCommentProcedure is the fully-qualified name of the MergeRequestService's
51+ // EditComment RPC.
52+ MergeRequestServiceEditCommentProcedure = "/ermite.MergeRequestService/EditComment"
53+ // MergeRequestServiceDeleteCommentProcedure is the fully-qualified name of the
54+ // MergeRequestService's DeleteComment RPC.
55+ MergeRequestServiceDeleteCommentProcedure = "/ermite.MergeRequestService/DeleteComment"
50 // MergeRequestServiceResolveCommentProcedure is the fully-qualified name of the56 // MergeRequestServiceResolveCommentProcedure is the fully-qualified name of the
51 // MergeRequestService's ResolveComment RPC.57 // MergeRequestService's ResolveComment RPC.
52 MergeRequestServiceResolveCommentProcedure = "/ermite.MergeRequestService/ResolveComment"58 MergeRequestServiceResolveCommentProcedure = "/ermite.MergeRequestService/ResolveComment"
@@ -79,14 +85,35 @@ type MergeRequestServiceClient interface {
79 // caller last marked read with the current head. Fails with not_found if85 // caller last marked read with the current head. Fails with not_found if
80 // the repository or the number is unknown.86 // the repository or the number is unknown.
81 GetMergeRequest(context.Context, *connect.Request[ermite.GetMergeRequestRequest]) (*connect.Response[ermite.GetMergeRequestResponse], error)87 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.88+ // AddComment adds a general comment, a comment on a line or a range of
83- // Needs the mr:write scope. Fails with invalid_argument if the text breaks89+ // lines of the diff, or a reply to a root comment. Needs the mr:write
84- // a rule or if path, line and side do not designate a line of the current90+ // scope; setting a verdict also needs the mr:review scope (permission_denied
85- // diff of the merge request, and with failed_precondition if the merge91+ // without it). Fails with invalid_argument if the text breaks a rule (it may
86- // request is not open.92+ // be empty only with a verdict), if path, line, start_line and side do not
93+ // designate lines of the current diff of the merge request, if reply_to is
94+ // not the id of a root comment that is not deleted, if a reply sets path,
95+ // line, start_line, side or verdict, or if a verdict is set on a reply; and
96+ // with failed_precondition if the merge request is not open. A comment with
97+ // VERDICT_APPROVE is created resolved.
87 AddComment(context.Context, *connect.Request[ermite.AddCommentRequest]) (*connect.Response[ermite.AddCommentResponse], error)98 AddComment(context.Context, *connect.Request[ermite.AddCommentRequest]) (*connect.Response[ermite.AddCommentResponse], error)
88- // ResolveComment marks a comment resolved or unresolved. Needs the mr:write99+ // EditComment replaces the text of a comment. Needs the mr:write scope, and
89- // scope. Fails with not_found if the comment is unknown and with100+ // only the author of the comment can edit it (permission_denied
101+ // otherwise). Fails with invalid_argument if the text breaks a rule (it may
102+ // be empty only for a comment with a verdict), with not_found if the comment
103+ // is unknown, and with failed_precondition if the merge request is not open
104+ // or the comment was deleted. Sending the current text succeeds and changes
105+ // nothing.
106+ EditComment(context.Context, *connect.Request[ermite.EditCommentRequest]) (*connect.Response[ermite.EditCommentResponse], error)
107+ // DeleteComment deletes a comment: its text is emptied and deleted is set,
108+ // while its id, anchor, replies and verdict remain. Needs the mr:write
109+ // scope, and only the author of the comment can delete it
110+ // (permission_denied otherwise). Fails with not_found if the comment is
111+ // unknown and with failed_precondition if the merge request is not open.
112+ // Deleting a comment already deleted succeeds and changes nothing.
113+ DeleteComment(context.Context, *connect.Request[ermite.DeleteCommentRequest]) (*connect.Response[ermite.DeleteCommentResponse], error)
114+ // ResolveComment marks a thread resolved or unresolved. Needs the mr:write
115+ // scope. Resolution belongs to the root comment: on a reply it fails with
116+ // invalid_argument. Fails with not_found if the comment is unknown and with
90 // failed_precondition if the merge request is not open. Setting the state117 // failed_precondition if the merge request is not open. Setting the state
91 // the comment already has succeeds and changes nothing.118 // the comment already has succeeds and changes nothing.
92 ResolveComment(context.Context, *connect.Request[ermite.ResolveCommentRequest]) (*connect.Response[ermite.ResolveCommentResponse], error)119 ResolveComment(context.Context, *connect.Request[ermite.ResolveCommentRequest]) (*connect.Response[ermite.ResolveCommentResponse], error)
@@ -149,6 +176,18 @@ func NewMergeRequestServiceClient(httpClient connect.HTTPClient, baseURL string,
149 connect.WithSchema(mergeRequestServiceMethods.ByName("AddComment")),176 connect.WithSchema(mergeRequestServiceMethods.ByName("AddComment")),
150 connect.WithClientOptions(opts...),177 connect.WithClientOptions(opts...),
151 ),178 ),
179+ editComment: connect.NewClient[ermite.EditCommentRequest, ermite.EditCommentResponse](
180+ httpClient,
181+ baseURL+MergeRequestServiceEditCommentProcedure,
182+ connect.WithSchema(mergeRequestServiceMethods.ByName("EditComment")),
183+ connect.WithClientOptions(opts...),
184+ ),
185+ deleteComment: connect.NewClient[ermite.DeleteCommentRequest, ermite.DeleteCommentResponse](
186+ httpClient,
187+ baseURL+MergeRequestServiceDeleteCommentProcedure,
188+ connect.WithSchema(mergeRequestServiceMethods.ByName("DeleteComment")),
189+ connect.WithClientOptions(opts...),
190+ ),
152 resolveComment: connect.NewClient[ermite.ResolveCommentRequest, ermite.ResolveCommentResponse](191 resolveComment: connect.NewClient[ermite.ResolveCommentRequest, ermite.ResolveCommentResponse](
153 httpClient,192 httpClient,
154 baseURL+MergeRequestServiceResolveCommentProcedure,193 baseURL+MergeRequestServiceResolveCommentProcedure,
@@ -182,6 +221,8 @@ type mergeRequestServiceClient struct {
182 listMergeRequests *connect.Client[ermite.ListMergeRequestsRequest, ermite.ListMergeRequestsResponse]221 listMergeRequests *connect.Client[ermite.ListMergeRequestsRequest, ermite.ListMergeRequestsResponse]
183 getMergeRequest *connect.Client[ermite.GetMergeRequestRequest, ermite.GetMergeRequestResponse]222 getMergeRequest *connect.Client[ermite.GetMergeRequestRequest, ermite.GetMergeRequestResponse]
184 addComment *connect.Client[ermite.AddCommentRequest, ermite.AddCommentResponse]223 addComment *connect.Client[ermite.AddCommentRequest, ermite.AddCommentResponse]
224+ editComment *connect.Client[ermite.EditCommentRequest, ermite.EditCommentResponse]
225+ deleteComment *connect.Client[ermite.DeleteCommentRequest, ermite.DeleteCommentResponse]
185 resolveComment *connect.Client[ermite.ResolveCommentRequest, ermite.ResolveCommentResponse]226 resolveComment *connect.Client[ermite.ResolveCommentRequest, ermite.ResolveCommentResponse]
186 closeMergeRequest *connect.Client[ermite.CloseMergeRequestRequest, ermite.CloseMergeRequestResponse]227 closeMergeRequest *connect.Client[ermite.CloseMergeRequestRequest, ermite.CloseMergeRequestResponse]
187 mergeMergeRequest *connect.Client[ermite.MergeMergeRequestRequest, ermite.MergeMergeRequestResponse]228 mergeMergeRequest *connect.Client[ermite.MergeMergeRequestRequest, ermite.MergeMergeRequestResponse]
@@ -208,6 +249,16 @@ func (c *mergeRequestServiceClient) AddComment(ctx context.Context, req *connect
208 return c.addComment.CallUnary(ctx, req)249 return c.addComment.CallUnary(ctx, req)
209 }250 }
210 251
252+// EditComment calls ermite.MergeRequestService.EditComment.
253+func (c *mergeRequestServiceClient) EditComment(ctx context.Context, req *connect.Request[ermite.EditCommentRequest]) (*connect.Response[ermite.EditCommentResponse], error) {
254+ return c.editComment.CallUnary(ctx, req)
255+}
256+
257+// DeleteComment calls ermite.MergeRequestService.DeleteComment.
258+func (c *mergeRequestServiceClient) DeleteComment(ctx context.Context, req *connect.Request[ermite.DeleteCommentRequest]) (*connect.Response[ermite.DeleteCommentResponse], error) {
259+ return c.deleteComment.CallUnary(ctx, req)
260+}
261+
211 // ResolveComment calls ermite.MergeRequestService.ResolveComment.262 // ResolveComment calls ermite.MergeRequestService.ResolveComment.
212 func (c *mergeRequestServiceClient) ResolveComment(ctx context.Context, req *connect.Request[ermite.ResolveCommentRequest]) (*connect.Response[ermite.ResolveCommentResponse], error) {263 func (c *mergeRequestServiceClient) ResolveComment(ctx context.Context, req *connect.Request[ermite.ResolveCommentRequest]) (*connect.Response[ermite.ResolveCommentResponse], error) {
213 return c.resolveComment.CallUnary(ctx, req)264 return c.resolveComment.CallUnary(ctx, req)
@@ -246,14 +297,35 @@ type MergeRequestServiceHandler interface {
246 // caller last marked read with the current head. Fails with not_found if297 // caller last marked read with the current head. Fails with not_found if
247 // the repository or the number is unknown.298 // the repository or the number is unknown.
248 GetMergeRequest(context.Context, *connect.Request[ermite.GetMergeRequestRequest]) (*connect.Response[ermite.GetMergeRequestResponse], error)299 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.300+ // AddComment adds a general comment, a comment on a line or a range of
250- // Needs the mr:write scope. Fails with invalid_argument if the text breaks301+ // lines of the diff, or a reply to a root comment. Needs the mr:write
251- // a rule or if path, line and side do not designate a line of the current302+ // scope; setting a verdict also needs the mr:review scope (permission_denied
252- // diff of the merge request, and with failed_precondition if the merge303+ // without it). Fails with invalid_argument if the text breaks a rule (it may
253- // request is not open.304+ // be empty only with a verdict), if path, line, start_line and side do not
305+ // designate lines of the current diff of the merge request, if reply_to is
306+ // not the id of a root comment that is not deleted, if a reply sets path,
307+ // line, start_line, side or verdict, or if a verdict is set on a reply; and
308+ // with failed_precondition if the merge request is not open. A comment with
309+ // VERDICT_APPROVE is created resolved.
254 AddComment(context.Context, *connect.Request[ermite.AddCommentRequest]) (*connect.Response[ermite.AddCommentResponse], error)310 AddComment(context.Context, *connect.Request[ermite.AddCommentRequest]) (*connect.Response[ermite.AddCommentResponse], error)
255- // ResolveComment marks a comment resolved or unresolved. Needs the mr:write311+ // EditComment replaces the text of a comment. Needs the mr:write scope, and
256- // scope. Fails with not_found if the comment is unknown and with312+ // only the author of the comment can edit it (permission_denied
313+ // otherwise). Fails with invalid_argument if the text breaks a rule (it may
314+ // be empty only for a comment with a verdict), with not_found if the comment
315+ // is unknown, and with failed_precondition if the merge request is not open
316+ // or the comment was deleted. Sending the current text succeeds and changes
317+ // nothing.
318+ EditComment(context.Context, *connect.Request[ermite.EditCommentRequest]) (*connect.Response[ermite.EditCommentResponse], error)
319+ // DeleteComment deletes a comment: its text is emptied and deleted is set,
320+ // while its id, anchor, replies and verdict remain. Needs the mr:write
321+ // scope, and only the author of the comment can delete it
322+ // (permission_denied otherwise). Fails with not_found if the comment is
323+ // unknown and with failed_precondition if the merge request is not open.
324+ // Deleting a comment already deleted succeeds and changes nothing.
325+ DeleteComment(context.Context, *connect.Request[ermite.DeleteCommentRequest]) (*connect.Response[ermite.DeleteCommentResponse], error)
326+ // ResolveComment marks a thread resolved or unresolved. Needs the mr:write
327+ // scope. Resolution belongs to the root comment: on a reply it fails with
328+ // invalid_argument. Fails with not_found if the comment is unknown and with
257 // failed_precondition if the merge request is not open. Setting the state329 // failed_precondition if the merge request is not open. Setting the state
258 // the comment already has succeeds and changes nothing.330 // the comment already has succeeds and changes nothing.
259 ResolveComment(context.Context, *connect.Request[ermite.ResolveCommentRequest]) (*connect.Response[ermite.ResolveCommentResponse], error)331 ResolveComment(context.Context, *connect.Request[ermite.ResolveCommentRequest]) (*connect.Response[ermite.ResolveCommentResponse], error)
@@ -312,6 +384,18 @@ func NewMergeRequestServiceHandler(svc MergeRequestServiceHandler, opts ...conne
312 connect.WithSchema(mergeRequestServiceMethods.ByName("AddComment")),384 connect.WithSchema(mergeRequestServiceMethods.ByName("AddComment")),
313 connect.WithHandlerOptions(opts...),385 connect.WithHandlerOptions(opts...),
314 )386 )
387+ mergeRequestServiceEditCommentHandler := connect.NewUnaryHandler(
388+ MergeRequestServiceEditCommentProcedure,
389+ svc.EditComment,
390+ connect.WithSchema(mergeRequestServiceMethods.ByName("EditComment")),
391+ connect.WithHandlerOptions(opts...),
392+ )
393+ mergeRequestServiceDeleteCommentHandler := connect.NewUnaryHandler(
394+ MergeRequestServiceDeleteCommentProcedure,
395+ svc.DeleteComment,
396+ connect.WithSchema(mergeRequestServiceMethods.ByName("DeleteComment")),
397+ connect.WithHandlerOptions(opts...),
398+ )
315 mergeRequestServiceResolveCommentHandler := connect.NewUnaryHandler(399 mergeRequestServiceResolveCommentHandler := connect.NewUnaryHandler(
316 MergeRequestServiceResolveCommentProcedure,400 MergeRequestServiceResolveCommentProcedure,
317 svc.ResolveComment,401 svc.ResolveComment,
@@ -346,6 +430,10 @@ func NewMergeRequestServiceHandler(svc MergeRequestServiceHandler, opts ...conne
346 mergeRequestServiceGetMergeRequestHandler.ServeHTTP(w, r)430 mergeRequestServiceGetMergeRequestHandler.ServeHTTP(w, r)
347 case MergeRequestServiceAddCommentProcedure:431 case MergeRequestServiceAddCommentProcedure:
348 mergeRequestServiceAddCommentHandler.ServeHTTP(w, r)432 mergeRequestServiceAddCommentHandler.ServeHTTP(w, r)
433+ case MergeRequestServiceEditCommentProcedure:
434+ mergeRequestServiceEditCommentHandler.ServeHTTP(w, r)
435+ case MergeRequestServiceDeleteCommentProcedure:
436+ mergeRequestServiceDeleteCommentHandler.ServeHTTP(w, r)
349 case MergeRequestServiceResolveCommentProcedure:437 case MergeRequestServiceResolveCommentProcedure:
350 mergeRequestServiceResolveCommentHandler.ServeHTTP(w, r)438 mergeRequestServiceResolveCommentHandler.ServeHTTP(w, r)
351 case MergeRequestServiceCloseMergeRequestProcedure:439 case MergeRequestServiceCloseMergeRequestProcedure:
@@ -379,6 +467,14 @@ func (UnimplementedMergeRequestServiceHandler) AddComment(context.Context, *conn
379 return nil, connect.NewError(connect.CodeUnimplemented, errors.New("ermite.MergeRequestService.AddComment is not implemented"))467 return nil, connect.NewError(connect.CodeUnimplemented, errors.New("ermite.MergeRequestService.AddComment is not implemented"))
380 }468 }
381 469
470+func (UnimplementedMergeRequestServiceHandler) EditComment(context.Context, *connect.Request[ermite.EditCommentRequest]) (*connect.Response[ermite.EditCommentResponse], error) {
471+ return nil, connect.NewError(connect.CodeUnimplemented, errors.New("ermite.MergeRequestService.EditComment is not implemented"))
472+}
473+
474+func (UnimplementedMergeRequestServiceHandler) DeleteComment(context.Context, *connect.Request[ermite.DeleteCommentRequest]) (*connect.Response[ermite.DeleteCommentResponse], error) {
475+ return nil, connect.NewError(connect.CodeUnimplemented, errors.New("ermite.MergeRequestService.DeleteComment is not implemented"))
476+}
477+
382 func (UnimplementedMergeRequestServiceHandler) ResolveComment(context.Context, *connect.Request[ermite.ResolveCommentRequest]) (*connect.Response[ermite.ResolveCommentResponse], error) {478 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"))479 return nil, connect.NewError(connect.CodeUnimplemented, errors.New("ermite.MergeRequestService.ResolveComment is not implemented"))
384 }480 }
M gen/ermite/event.pb.go +314 -42 generated file: collapsed
M gen/ermite/merge_request.pb.go +661 -127 generated file: collapsed
M gen/ermite/token.pb.go +19 -12
@@ -59,21 +59,25 @@ const (
5959 // SCOPE_TOKEN_ADMIN allows creating, listing and revoking tokens. Wire name:
6060 // token:admin.
6161 Scope_SCOPE_TOKEN_ADMIN Scope = 9
62+ // SCOPE_MR_REVIEW allows setting a verdict (approve, changes requested) on
63+ // merge request notes. Wire name: mr:review.
64+ Scope_SCOPE_MR_REVIEW Scope = 10
6265 )
6366
6467 // Enum value maps for Scope.
6568 var (
6669 Scope_name = map[int32]string{
67- 0: "SCOPE_UNSPECIFIED",
68- 1: "SCOPE_REPO_READ",
69- 2: "SCOPE_REPO_WRITE",
70- 3: "SCOPE_REPO_ADMIN",
71- 4: "SCOPE_MR_WRITE",
72- 5: "SCOPE_MR_MERGE",
73- 6: "SCOPE_CI_WRITE",
74- 7: "SCOPE_SECRETS_WRITE",
75- 8: "SCOPE_RUNNER_REGISTER",
76- 9: "SCOPE_TOKEN_ADMIN",
70+ 0: "SCOPE_UNSPECIFIED",
71+ 1: "SCOPE_REPO_READ",
72+ 2: "SCOPE_REPO_WRITE",
73+ 3: "SCOPE_REPO_ADMIN",
74+ 4: "SCOPE_MR_WRITE",
75+ 5: "SCOPE_MR_MERGE",
76+ 6: "SCOPE_CI_WRITE",
77+ 7: "SCOPE_SECRETS_WRITE",
78+ 8: "SCOPE_RUNNER_REGISTER",
79+ 9: "SCOPE_TOKEN_ADMIN",
80+ 10: "SCOPE_MR_REVIEW",
7781 }
7882 Scope_value = map[string]int32{
7983 "SCOPE_UNSPECIFIED": 0,
@@ -86,6 +90,7 @@ var (
8690 "SCOPE_SECRETS_WRITE": 7,
8791 "SCOPE_RUNNER_REGISTER": 8,
8892 "SCOPE_TOKEN_ADMIN": 9,
93+ "SCOPE_MR_REVIEW": 10,
8994 }
9095 )
9196
@@ -610,7 +615,7 @@ const file_ermite_token_proto_rawDesc = "" +
610615 "\x0fnext_page_token\x18\x02 \x01(\tR\rnextPageToken\"$\n" +
611616 "\x12RevokeTokenRequest\x12\x0e\n" +
612617 "\x02id\x18\x01 \x01(\tR\x02id\"\x15\n" +
613- "\x13RevokeTokenResponse*\xe6\x01\n" +
618+ "\x13RevokeTokenResponse*\xfb\x01\n" +
614619 "\x05Scope\x12\x15\n" +
615620 "\x11SCOPE_UNSPECIFIED\x10\x00\x12\x13\n" +
616621 "\x0fSCOPE_REPO_READ\x10\x01\x12\x14\n" +
@@ -621,7 +626,9 @@ const file_ermite_token_proto_rawDesc = "" +
621626 "\x0eSCOPE_CI_WRITE\x10\x06\x12\x17\n" +
622627 "\x13SCOPE_SECRETS_WRITE\x10\a\x12\x19\n" +
623628 "\x15SCOPE_RUNNER_REGISTER\x10\b\x12\x15\n" +
624- "\x11SCOPE_TOKEN_ADMIN\x10\t2\xe3\x01\n" +
629+ "\x11SCOPE_TOKEN_ADMIN\x10\t\x12\x13\n" +
630+ "\x0fSCOPE_MR_REVIEW\x10\n" +
631+ "2\xe3\x01\n" +
625632 "\fTokenService\x12F\n" +
626633 "\vCreateToken\x12\x1a.ermite.CreateTokenRequest\x1a\x1b.ermite.CreateTokenResponse\x12C\n" +
627634 "\n" +
@@ -59,21 +59,25 @@ const (
59 // SCOPE_TOKEN_ADMIN allows creating, listing and revoking tokens. Wire name:59 // SCOPE_TOKEN_ADMIN allows creating, listing and revoking tokens. Wire name:
60 // token:admin.60 // token:admin.
61 Scope_SCOPE_TOKEN_ADMIN Scope = 961 Scope_SCOPE_TOKEN_ADMIN Scope = 9
62+ // SCOPE_MR_REVIEW allows setting a verdict (approve, changes requested) on
63+ // merge request notes. Wire name: mr:review.
64+ Scope_SCOPE_MR_REVIEW Scope = 10
62 )65 )
63 66
64 // Enum value maps for Scope.67 // Enum value maps for Scope.
65 var (68 var (
66 Scope_name = map[int32]string{69 Scope_name = map[int32]string{
67- 0: "SCOPE_UNSPECIFIED",70+ 0: "SCOPE_UNSPECIFIED",
68- 1: "SCOPE_REPO_READ",71+ 1: "SCOPE_REPO_READ",
69- 2: "SCOPE_REPO_WRITE",72+ 2: "SCOPE_REPO_WRITE",
70- 3: "SCOPE_REPO_ADMIN",73+ 3: "SCOPE_REPO_ADMIN",
71- 4: "SCOPE_MR_WRITE",74+ 4: "SCOPE_MR_WRITE",
72- 5: "SCOPE_MR_MERGE",75+ 5: "SCOPE_MR_MERGE",
73- 6: "SCOPE_CI_WRITE",76+ 6: "SCOPE_CI_WRITE",
74- 7: "SCOPE_SECRETS_WRITE",77+ 7: "SCOPE_SECRETS_WRITE",
75- 8: "SCOPE_RUNNER_REGISTER",78+ 8: "SCOPE_RUNNER_REGISTER",
76- 9: "SCOPE_TOKEN_ADMIN",79+ 9: "SCOPE_TOKEN_ADMIN",
80+ 10: "SCOPE_MR_REVIEW",
77 }81 }
78 Scope_value = map[string]int32{82 Scope_value = map[string]int32{
79 "SCOPE_UNSPECIFIED": 0,83 "SCOPE_UNSPECIFIED": 0,
@@ -86,6 +90,7 @@ var (
86 "SCOPE_SECRETS_WRITE": 7,90 "SCOPE_SECRETS_WRITE": 7,
87 "SCOPE_RUNNER_REGISTER": 8,91 "SCOPE_RUNNER_REGISTER": 8,
88 "SCOPE_TOKEN_ADMIN": 9,92 "SCOPE_TOKEN_ADMIN": 9,
93+ "SCOPE_MR_REVIEW": 10,
89 }94 }
90 )95 )
91 96
@@ -610,7 +615,7 @@ const file_ermite_token_proto_rawDesc = "" +
610 "\x0fnext_page_token\x18\x02 \x01(\tR\rnextPageToken\"$\n" +615 "\x0fnext_page_token\x18\x02 \x01(\tR\rnextPageToken\"$\n" +
611 "\x12RevokeTokenRequest\x12\x0e\n" +616 "\x12RevokeTokenRequest\x12\x0e\n" +
612 "\x02id\x18\x01 \x01(\tR\x02id\"\x15\n" +617 "\x02id\x18\x01 \x01(\tR\x02id\"\x15\n" +
613- "\x13RevokeTokenResponse*\xe6\x01\n" +618+ "\x13RevokeTokenResponse*\xfb\x01\n" +
614 "\x05Scope\x12\x15\n" +619 "\x05Scope\x12\x15\n" +
615 "\x11SCOPE_UNSPECIFIED\x10\x00\x12\x13\n" +620 "\x11SCOPE_UNSPECIFIED\x10\x00\x12\x13\n" +
616 "\x0fSCOPE_REPO_READ\x10\x01\x12\x14\n" +621 "\x0fSCOPE_REPO_READ\x10\x01\x12\x14\n" +
@@ -621,7 +626,9 @@ const file_ermite_token_proto_rawDesc = "" +
621 "\x0eSCOPE_CI_WRITE\x10\x06\x12\x17\n" +626 "\x0eSCOPE_CI_WRITE\x10\x06\x12\x17\n" +
622 "\x13SCOPE_SECRETS_WRITE\x10\a\x12\x19\n" +627 "\x13SCOPE_SECRETS_WRITE\x10\a\x12\x19\n" +
623 "\x15SCOPE_RUNNER_REGISTER\x10\b\x12\x15\n" +628 "\x15SCOPE_RUNNER_REGISTER\x10\b\x12\x15\n" +
624- "\x11SCOPE_TOKEN_ADMIN\x10\t2\xe3\x01\n" +629+ "\x11SCOPE_TOKEN_ADMIN\x10\t\x12\x13\n" +
630+ "\x0fSCOPE_MR_REVIEW\x10\n" +
631+ "2\xe3\x01\n" +
625 "\fTokenService\x12F\n" +632 "\fTokenService\x12F\n" +
626 "\vCreateToken\x12\x1a.ermite.CreateTokenRequest\x1a\x1b.ermite.CreateTokenResponse\x12C\n" +633 "\vCreateToken\x12\x1a.ermite.CreateTokenRequest\x1a\x1b.ermite.CreateTokenResponse\x12C\n" +
627 "\n" +634 "\n" +
M proto/ermite/event.proto +38 -0
@@ -102,6 +102,12 @@ message Event {
102102 RunUpdated run_updated = 12;
103103 // JobUpdated: a CI job was created or changed.
104104 JobUpdated job_updated = 13;
105+ // CommentEdited: the text of a merge request comment was changed.
106+ CommentEdited comment_edited = 14;
107+ // CommentDeleted: a merge request comment was deleted.
108+ CommentDeleted comment_deleted = 15;
109+ // CommentResolved: a merge request comment was resolved or unresolved.
110+ CommentResolved comment_resolved = 16;
105111 }
106112 }
107113
@@ -170,6 +176,38 @@ message CommentAdded {
170176 Comment comment = 3;
171177 }
172178
179+// CommentEdited reports a comment of a merge request whose text was changed.
180+message CommentEdited {
181+ // Repo is the name of the repository.
182+ string repo = 1;
183+ // Number is the number of the merge request.
184+ int64 number = 2;
185+ // Comment is the comment after the edit.
186+ Comment comment = 3;
187+}
188+
189+// CommentDeleted reports a comment of a merge request that was deleted. The
190+// comment stays in the thread with deleted set and an empty text.
191+message CommentDeleted {
192+ // Repo is the name of the repository.
193+ string repo = 1;
194+ // Number is the number of the merge request.
195+ int64 number = 2;
196+ // Comment is the comment after the deletion.
197+ Comment comment = 3;
198+}
199+
200+// CommentResolved reports a comment of a merge request whose resolved state
201+// changed.
202+message CommentResolved {
203+ // Repo is the name of the repository.
204+ string repo = 1;
205+ // Number is the number of the merge request.
206+ int64 number = 2;
207+ // Comment is the comment after the change.
208+ Comment comment = 3;
209+}
210+
173211 // Gap tells the client that events were lost: the requested resume point is
174212 // older than the server's buffer. It has no fields. The client should reload
175213 // the state it displays (repositories, refs, merge requests and their
@@ -102,6 +102,12 @@ message Event {
102 RunUpdated run_updated = 12;102 RunUpdated run_updated = 12;
103 // JobUpdated: a CI job was created or changed.103 // JobUpdated: a CI job was created or changed.
104 JobUpdated job_updated = 13;104 JobUpdated job_updated = 13;
105+ // CommentEdited: the text of a merge request comment was changed.
106+ CommentEdited comment_edited = 14;
107+ // CommentDeleted: a merge request comment was deleted.
108+ CommentDeleted comment_deleted = 15;
109+ // CommentResolved: a merge request comment was resolved or unresolved.
110+ CommentResolved comment_resolved = 16;
105 }111 }
106 }112 }
107 113
@@ -170,6 +176,38 @@ message CommentAdded {
170 Comment comment = 3;176 Comment comment = 3;
171 }177 }
172 178
179+// CommentEdited reports a comment of a merge request whose text was changed.
180+message CommentEdited {
181+ // Repo is the name of the repository.
182+ string repo = 1;
183+ // Number is the number of the merge request.
184+ int64 number = 2;
185+ // Comment is the comment after the edit.
186+ Comment comment = 3;
187+}
188+
189+// CommentDeleted reports a comment of a merge request that was deleted. The
190+// comment stays in the thread with deleted set and an empty text.
191+message CommentDeleted {
192+ // Repo is the name of the repository.
193+ string repo = 1;
194+ // Number is the number of the merge request.
195+ int64 number = 2;
196+ // Comment is the comment after the deletion.
197+ Comment comment = 3;
198+}
199+
200+// CommentResolved reports a comment of a merge request whose resolved state
201+// changed.
202+message CommentResolved {
203+ // Repo is the name of the repository.
204+ string repo = 1;
205+ // Number is the number of the merge request.
206+ int64 number = 2;
207+ // Comment is the comment after the change.
208+ Comment comment = 3;
209+}
210+
173 // Gap tells the client that events were lost: the requested resume point is211 // Gap tells the client that events were lost: the requested resume point is
174 // older than the server's buffer. It has no fields. The client should reload212 // older than the server's buffer. It has no fields. The client should reload
175 // the state it displays (repositories, refs, merge requests and their213 // the state it displays (repositories, refs, merge requests and their
M proto/ermite/merge_request.proto +128 -10
@@ -33,6 +33,17 @@ enum DiffSide {
3333 DIFF_SIDE_NEW = 2;
3434 }
3535
36+// Verdict is the judgement a reviewer gives with a comment.
37+enum Verdict {
38+ // Not set. The comment carries no verdict. In AddCommentRequest it means a
39+ // plain comment.
40+ VERDICT_UNSPECIFIED = 0;
41+ // The reviewer approves the merge request. The comment is created resolved.
42+ VERDICT_APPROVE = 1;
43+ // The reviewer asks for changes. The comment starts unresolved.
44+ VERDICT_CHANGES_REQUESTED = 2;
45+}
46+
3647 // MergeRequest asks to merge one branch of a repository into another branch
3748 // of the same repository. It is about a branch, not about who wrote it.
3849 message MergeRequest {
@@ -97,13 +108,50 @@ message Comment {
97108 // When it differs from the current head the line may have moved; clients
98109 // may show the comment as outdated.
99110 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.
111+ // Text is the Markdown source, at most 8,000 characters. Clients render it
112+ // as Markdown without raw HTML; "^<id>" refers to a note of the same merge
113+ // request. Empty when deleted is true, and possibly empty for a comment
114+ // that carries a verdict.
102115 string text = 7;
103116 // Resolved is true once the comment was marked resolved.
104117 bool resolved = 8;
105118 // CreatedAt is when the comment was written.
106119 google.protobuf.Timestamp created_at = 9;
120+ // ReplyTo is the id of the root comment this comment replies to. Empty for
121+ // a root comment. A reply has the path, line, start_line and side of its
122+ // root, and its commit_id is the head when the reply was written. Threads
123+ // have one level: a reply never has replies.
124+ string reply_to = 10;
125+ // StartLine is the 1-based first line of a range that ends at line, on the
126+ // same side. 0 for a comment on a single line or a general comment.
127+ int32 start_line = 11;
128+ // Verdict is the judgement given with the comment. Only a root comment has
129+ // one; it never changes after creation.
130+ Verdict verdict = 12;
131+ // EditedAt is when the text was last changed or the comment was deleted.
132+ // Unset if it never was.
133+ google.protobuf.Timestamp edited_at = 13;
134+ // Deleted is true once the author deleted the comment. Its text is then
135+ // empty; its id, anchor, replies and verdict remain.
136+ bool deleted = 14;
137+}
138+
139+// AuthorVerdict is the current verdict of one author on a merge request: the
140+// one given with the author's latest comment that carries a verdict.
141+message AuthorVerdict {
142+ // Author names who gave the verdict, like Comment.author.
143+ string author = 1;
144+ // Verdict is the current verdict. Never VERDICT_UNSPECIFIED.
145+ Verdict verdict = 2;
146+ // CommentId is the id of the comment that carries the verdict.
147+ string comment_id = 3;
148+ // CommitId is the head of the merge request when the verdict was given.
149+ string commit_id = 4;
150+ // CreatedAt is when the verdict was given.
151+ google.protobuf.Timestamp created_at = 5;
152+ // Stale is true for an approval given on a commit that is not the current
153+ // head. A request for changes is never stale.
154+ bool stale = 6;
107155 }
108156
109157 // MergeRequestService reviews and merges branches.
@@ -143,14 +191,35 @@ service MergeRequestService {
143191 // caller last marked read with the current head. Fails with not_found if
144192 // the repository or the number is unknown.
145193 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.
194+ // AddComment adds a general comment, a comment on a line or a range of
195+ // lines of the diff, or a reply to a root comment. Needs the mr:write
196+ // scope; setting a verdict also needs the mr:review scope (permission_denied
197+ // without it). Fails with invalid_argument if the text breaks a rule (it may
198+ // be empty only with a verdict), if path, line, start_line and side do not
199+ // designate lines of the current diff of the merge request, if reply_to is
200+ // not the id of a root comment that is not deleted, if a reply sets path,
201+ // line, start_line, side or verdict, or if a verdict is set on a reply; and
202+ // with failed_precondition if the merge request is not open. A comment with
203+ // VERDICT_APPROVE is created resolved.
151204 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
205+ // EditComment replaces the text of a comment. Needs the mr:write scope, and
206+ // only the author of the comment can edit it (permission_denied
207+ // otherwise). Fails with invalid_argument if the text breaks a rule (it may
208+ // be empty only for a comment with a verdict), with not_found if the comment
209+ // is unknown, and with failed_precondition if the merge request is not open
210+ // or the comment was deleted. Sending the current text succeeds and changes
211+ // nothing.
212+ rpc EditComment(EditCommentRequest) returns (EditCommentResponse);
213+ // DeleteComment deletes a comment: its text is emptied and deleted is set,
214+ // while its id, anchor, replies and verdict remain. Needs the mr:write
215+ // scope, and only the author of the comment can delete it
216+ // (permission_denied otherwise). Fails with not_found if the comment is
217+ // unknown and with failed_precondition if the merge request is not open.
218+ // Deleting a comment already deleted succeeds and changes nothing.
219+ rpc DeleteComment(DeleteCommentRequest) returns (DeleteCommentResponse);
220+ // ResolveComment marks a thread resolved or unresolved. Needs the mr:write
221+ // scope. Resolution belongs to the root comment: on a reply it fails with
222+ // invalid_argument. Fails with not_found if the comment is unknown and with
154223 // failed_precondition if the merge request is not open. Setting the state
155224 // the comment already has succeeds and changes nothing.
156225 rpc ResolveComment(ResolveCommentRequest) returns (ResolveCommentResponse);
@@ -272,6 +341,9 @@ message GetMergeRequestResponse {
272341 // diff against the target. A client should tell the reader to review the
273342 // whole change again.
274343 bool last_read_lost = 9;
344+ // Verdicts are the current verdicts, one per author who gave one, ordered
345+ // by the comment that carries them, oldest first.
346+ repeated AuthorVerdict verdicts = 10;
275347 }
276348
277349 // AddCommentRequest describes the comment to add.
@@ -290,8 +362,19 @@ message AddCommentRequest {
290362 int32 line = 4;
291363 // Side is the side line counts on. Required when path is set.
292364 DiffSide side = 5;
293- // Text is the plain-text content. Required.
365+ // Text is the Markdown source. Required, except when verdict is set.
294366 string text = 6;
367+ // ReplyTo is the id of the root comment to reply to. Empty for a new root
368+ // comment. A reply takes the anchor of its root: path, line, start_line,
369+ // side and verdict must then be unset.
370+ string reply_to = 7;
371+ // StartLine is the 1-based first line of a range ending at line. 0 for a
372+ // single line. Needs path; it must be lower than line, and both ends must
373+ // be lines of the diff on side.
374+ int32 start_line = 8;
375+ // Verdict is the judgement to give. Needs the mr:review scope and a root
376+ // comment. VERDICT_UNSPECIFIED for a plain comment.
377+ Verdict verdict = 9;
295378 }
296379
297380 // AddCommentResponse holds the new comment.
@@ -300,6 +383,41 @@ message AddCommentResponse {
300383 Comment comment = 1;
301384 }
302385
386+// EditCommentRequest describes the new text of a comment.
387+message EditCommentRequest {
388+ // Repo is the repository name.
389+ string repo = 1;
390+ // Number is the merge request number.
391+ int64 number = 2;
392+ // CommentId is the Comment.id to edit.
393+ string comment_id = 3;
394+ // Text is the new Markdown source. Required, except for a comment that
395+ // carries a verdict.
396+ string text = 4;
397+}
398+
399+// EditCommentResponse holds the comment after the edit.
400+message EditCommentResponse {
401+ // Comment is the updated comment.
402+ Comment comment = 1;
403+}
404+
405+// DeleteCommentRequest names the comment to delete.
406+message DeleteCommentRequest {
407+ // Repo is the repository name.
408+ string repo = 1;
409+ // Number is the merge request number.
410+ int64 number = 2;
411+ // CommentId is the Comment.id to delete.
412+ string comment_id = 3;
413+}
414+
415+// DeleteCommentResponse holds the comment after the deletion.
416+message DeleteCommentResponse {
417+ // Comment is the deleted comment.
418+ Comment comment = 1;
419+}
420+
303421 // ResolveCommentRequest changes the resolved state of a comment.
304422 message ResolveCommentRequest {
305423 // Repo is the repository name.
@@ -33,6 +33,17 @@ enum DiffSide {
33 DIFF_SIDE_NEW = 2;33 DIFF_SIDE_NEW = 2;
34 }34 }
35 35
36+// Verdict is the judgement a reviewer gives with a comment.
37+enum Verdict {
38+ // Not set. The comment carries no verdict. In AddCommentRequest it means a
39+ // plain comment.
40+ VERDICT_UNSPECIFIED = 0;
41+ // The reviewer approves the merge request. The comment is created resolved.
42+ VERDICT_APPROVE = 1;
43+ // The reviewer asks for changes. The comment starts unresolved.
44+ VERDICT_CHANGES_REQUESTED = 2;
45+}
46+
36 // MergeRequest asks to merge one branch of a repository into another branch47 // 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.48 // of the same repository. It is about a branch, not about who wrote it.
38 message MergeRequest {49 message MergeRequest {
@@ -97,13 +108,50 @@ message Comment {
97 // When it differs from the current head the line may have moved; clients108 // When it differs from the current head the line may have moved; clients
98 // may show the comment as outdated.109 // may show the comment as outdated.
99 string commit_id = 6;110 string commit_id = 6;
100- // Text is the plain-text content, at most 8,000 characters. Clients show it111+ // Text is the Markdown source, at most 8,000 characters. Clients render it
101- // escaped, with line breaks kept.112+ // as Markdown without raw HTML; "^<id>" refers to a note of the same merge
113+ // request. Empty when deleted is true, and possibly empty for a comment
114+ // that carries a verdict.
102 string text = 7;115 string text = 7;
103 // Resolved is true once the comment was marked resolved.116 // Resolved is true once the comment was marked resolved.
104 bool resolved = 8;117 bool resolved = 8;
105 // CreatedAt is when the comment was written.118 // CreatedAt is when the comment was written.
106 google.protobuf.Timestamp created_at = 9;119 google.protobuf.Timestamp created_at = 9;
120+ // ReplyTo is the id of the root comment this comment replies to. Empty for
121+ // a root comment. A reply has the path, line, start_line and side of its
122+ // root, and its commit_id is the head when the reply was written. Threads
123+ // have one level: a reply never has replies.
124+ string reply_to = 10;
125+ // StartLine is the 1-based first line of a range that ends at line, on the
126+ // same side. 0 for a comment on a single line or a general comment.
127+ int32 start_line = 11;
128+ // Verdict is the judgement given with the comment. Only a root comment has
129+ // one; it never changes after creation.
130+ Verdict verdict = 12;
131+ // EditedAt is when the text was last changed or the comment was deleted.
132+ // Unset if it never was.
133+ google.protobuf.Timestamp edited_at = 13;
134+ // Deleted is true once the author deleted the comment. Its text is then
135+ // empty; its id, anchor, replies and verdict remain.
136+ bool deleted = 14;
137+}
138+
139+// AuthorVerdict is the current verdict of one author on a merge request: the
140+// one given with the author's latest comment that carries a verdict.
141+message AuthorVerdict {
142+ // Author names who gave the verdict, like Comment.author.
143+ string author = 1;
144+ // Verdict is the current verdict. Never VERDICT_UNSPECIFIED.
145+ Verdict verdict = 2;
146+ // CommentId is the id of the comment that carries the verdict.
147+ string comment_id = 3;
148+ // CommitId is the head of the merge request when the verdict was given.
149+ string commit_id = 4;
150+ // CreatedAt is when the verdict was given.
151+ google.protobuf.Timestamp created_at = 5;
152+ // Stale is true for an approval given on a commit that is not the current
153+ // head. A request for changes is never stale.
154+ bool stale = 6;
107 }155 }
108 156
109 // MergeRequestService reviews and merges branches.157 // MergeRequestService reviews and merges branches.
@@ -143,14 +191,35 @@ service MergeRequestService {
143 // caller last marked read with the current head. Fails with not_found if191 // caller last marked read with the current head. Fails with not_found if
144 // the repository or the number is unknown.192 // the repository or the number is unknown.
145 rpc GetMergeRequest(GetMergeRequestRequest) returns (GetMergeRequestResponse);193 rpc GetMergeRequest(GetMergeRequestRequest) returns (GetMergeRequestResponse);
146- // AddComment adds a general comment or a comment on a line of the diff.194+ // AddComment adds a general comment, a comment on a line or a range of
147- // Needs the mr:write scope. Fails with invalid_argument if the text breaks195+ // lines of the diff, or a reply to a root comment. Needs the mr:write
148- // a rule or if path, line and side do not designate a line of the current196+ // scope; setting a verdict also needs the mr:review scope (permission_denied
149- // diff of the merge request, and with failed_precondition if the merge197+ // without it). Fails with invalid_argument if the text breaks a rule (it may
150- // request is not open.198+ // be empty only with a verdict), if path, line, start_line and side do not
199+ // designate lines of the current diff of the merge request, if reply_to is
200+ // not the id of a root comment that is not deleted, if a reply sets path,
201+ // line, start_line, side or verdict, or if a verdict is set on a reply; and
202+ // with failed_precondition if the merge request is not open. A comment with
203+ // VERDICT_APPROVE is created resolved.
151 rpc AddComment(AddCommentRequest) returns (AddCommentResponse);204 rpc AddComment(AddCommentRequest) returns (AddCommentResponse);
152- // ResolveComment marks a comment resolved or unresolved. Needs the mr:write205+ // EditComment replaces the text of a comment. Needs the mr:write scope, and
153- // scope. Fails with not_found if the comment is unknown and with206+ // only the author of the comment can edit it (permission_denied
207+ // otherwise). Fails with invalid_argument if the text breaks a rule (it may
208+ // be empty only for a comment with a verdict), with not_found if the comment
209+ // is unknown, and with failed_precondition if the merge request is not open
210+ // or the comment was deleted. Sending the current text succeeds and changes
211+ // nothing.
212+ rpc EditComment(EditCommentRequest) returns (EditCommentResponse);
213+ // DeleteComment deletes a comment: its text is emptied and deleted is set,
214+ // while its id, anchor, replies and verdict remain. Needs the mr:write
215+ // scope, and only the author of the comment can delete it
216+ // (permission_denied otherwise). Fails with not_found if the comment is
217+ // unknown and with failed_precondition if the merge request is not open.
218+ // Deleting a comment already deleted succeeds and changes nothing.
219+ rpc DeleteComment(DeleteCommentRequest) returns (DeleteCommentResponse);
220+ // ResolveComment marks a thread resolved or unresolved. Needs the mr:write
221+ // scope. Resolution belongs to the root comment: on a reply it fails with
222+ // invalid_argument. Fails with not_found if the comment is unknown and with
154 // failed_precondition if the merge request is not open. Setting the state223 // failed_precondition if the merge request is not open. Setting the state
155 // the comment already has succeeds and changes nothing.224 // the comment already has succeeds and changes nothing.
156 rpc ResolveComment(ResolveCommentRequest) returns (ResolveCommentResponse);225 rpc ResolveComment(ResolveCommentRequest) returns (ResolveCommentResponse);
@@ -272,6 +341,9 @@ message GetMergeRequestResponse {
272 // diff against the target. A client should tell the reader to review the341 // diff against the target. A client should tell the reader to review the
273 // whole change again.342 // whole change again.
274 bool last_read_lost = 9;343 bool last_read_lost = 9;
344+ // Verdicts are the current verdicts, one per author who gave one, ordered
345+ // by the comment that carries them, oldest first.
346+ repeated AuthorVerdict verdicts = 10;
275 }347 }
276 348
277 // AddCommentRequest describes the comment to add.349 // AddCommentRequest describes the comment to add.
@@ -290,8 +362,19 @@ message AddCommentRequest {
290 int32 line = 4;362 int32 line = 4;
291 // Side is the side line counts on. Required when path is set.363 // Side is the side line counts on. Required when path is set.
292 DiffSide side = 5;364 DiffSide side = 5;
293- // Text is the plain-text content. Required.365+ // Text is the Markdown source. Required, except when verdict is set.
294 string text = 6;366 string text = 6;
367+ // ReplyTo is the id of the root comment to reply to. Empty for a new root
368+ // comment. A reply takes the anchor of its root: path, line, start_line,
369+ // side and verdict must then be unset.
370+ string reply_to = 7;
371+ // StartLine is the 1-based first line of a range ending at line. 0 for a
372+ // single line. Needs path; it must be lower than line, and both ends must
373+ // be lines of the diff on side.
374+ int32 start_line = 8;
375+ // Verdict is the judgement to give. Needs the mr:review scope and a root
376+ // comment. VERDICT_UNSPECIFIED for a plain comment.
377+ Verdict verdict = 9;
295 }378 }
296 379
297 // AddCommentResponse holds the new comment.380 // AddCommentResponse holds the new comment.
@@ -300,6 +383,41 @@ message AddCommentResponse {
300 Comment comment = 1;383 Comment comment = 1;
301 }384 }
302 385
386+// EditCommentRequest describes the new text of a comment.
387+message EditCommentRequest {
388+ // Repo is the repository name.
389+ string repo = 1;
390+ // Number is the merge request number.
391+ int64 number = 2;
392+ // CommentId is the Comment.id to edit.
393+ string comment_id = 3;
394+ // Text is the new Markdown source. Required, except for a comment that
395+ // carries a verdict.
396+ string text = 4;
397+}
398+
399+// EditCommentResponse holds the comment after the edit.
400+message EditCommentResponse {
401+ // Comment is the updated comment.
402+ Comment comment = 1;
403+}
404+
405+// DeleteCommentRequest names the comment to delete.
406+message DeleteCommentRequest {
407+ // Repo is the repository name.
408+ string repo = 1;
409+ // Number is the merge request number.
410+ int64 number = 2;
411+ // CommentId is the Comment.id to delete.
412+ string comment_id = 3;
413+}
414+
415+// DeleteCommentResponse holds the comment after the deletion.
416+message DeleteCommentResponse {
417+ // Comment is the deleted comment.
418+ Comment comment = 1;
419+}
420+
303 // ResolveCommentRequest changes the resolved state of a comment.421 // ResolveCommentRequest changes the resolved state of a comment.
304 message ResolveCommentRequest {422 message ResolveCommentRequest {
305 // Repo is the repository name.423 // Repo is the repository name.
M proto/ermite/token.proto +3 -0
@@ -39,6 +39,9 @@ enum Scope {
3939 // SCOPE_TOKEN_ADMIN allows creating, listing and revoking tokens. Wire name:
4040 // token:admin.
4141 SCOPE_TOKEN_ADMIN = 9;
42+ // SCOPE_MR_REVIEW allows setting a verdict (approve, changes requested) on
43+ // merge request notes. Wire name: mr:review.
44+ SCOPE_MR_REVIEW = 10;
4245 }
4346
4447 // Token describes a stored token. It never contains the secret nor its hash.
@@ -39,6 +39,9 @@ enum Scope {
39 // SCOPE_TOKEN_ADMIN allows creating, listing and revoking tokens. Wire name:39 // SCOPE_TOKEN_ADMIN allows creating, listing and revoking tokens. Wire name:
40 // token:admin.40 // token:admin.
41 SCOPE_TOKEN_ADMIN = 9;41 SCOPE_TOKEN_ADMIN = 9;
42+ // SCOPE_MR_REVIEW allows setting a verdict (approve, changes requested) on
43+ // merge request notes. Wire name: mr:review.
44+ SCOPE_MR_REVIEW = 10;
42 }45 }
43 46
44 // Token describes a stored token. It never contains the secret nor its hash.47 // Token describes a stored token. It never contains the secret nor its hash.

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