detached b394bb8

Merge 81ede3b81040f2594180d0ca4f180a63b2cea9fd into ffa9a5ed3b92f780404c89cbe9288e74108a1ed0

commit
b394bb84e4b109668771066a112438ab8df26154
author
ermite · · 2026-10-22 18:34 UTC
merge of
ffa9a5e + 81ede3b · diff against the first parent
signature
unsigned
10 files · +1123 -390
M docs/services.md +2 -2
@@ -6,9 +6,9 @@
66 | `GitService` | `ListRefs`, `GetTree`, `ListPaths`, `GetBlob` (streamed), `ListCommits`, `GetCommit` (structured diff), `ResolveRevision` |
77 | `TokenService` | `CreateToken` (secret shown once), `ListTokens`, `RevokeToken` |
88 | `MergeRequestService` | `CreateMergeRequest`, `ListMergeRequests`, `GetMergeRequest` (commits, diff, comments), `AddComment`, `EditComment`, `DeleteComment`, `ResolveComment`, `CloseMergeRequest`, `MarkReady`, `MergeMergeRequest` (modes fast-forward, squash, merge commit), `MarkRead`, `EnableAutoMerge`, `CancelAutoMerge` (merge by itself once a check passed on the head and all its runs are complete; setting `allow_auto_merge`, `MergeRequest.auto_merge`, events `MergeRequestAutoMergeEnabled` and `MergeRequestAutoMergeCancelled`, `MergeRequestMerged.auto`; feature `merge_requests.auto_merge`). Open merge requests carry `mergeability` (up to date, behind, conflict, or checking) and `Watch` publishes `MergeRequestMergeability` each time a result is settled for new branch tips (feature `merge_requests.mergeability`) |
9-| `IssueService` | `CreateIssue`, `ListIssues`, `GetIssue` (comments, merge requests naming the issue, `closed_by_merge_request`), `AddIssueComment`, `CloseIssue`, `ReopenIssue`; owner only: `ListPendingIssues`, `ApproveIssue`, `RejectIssue`. Features `issues`, `issues.links`. Issues share numbers with merge requests |
9+| `IssueService` | `CreateIssue`, `ListIssues`, `GetIssue` (comments, merge requests naming the issue, history events, `closed_by_merge_request`), `AddIssueComment`, `CloseIssue`, `ResolveIssue`, `ReopenIssue`; owner only: `ListPendingIssues`, `ApproveIssue`, `RejectIssue`. States `OPEN`, `CLOSED`, `RESOLVED`; `ResolveIssue` needs `issue:write`. Features `issues`, `issues.links`, `issues.resolve`. Issues share numbers with merge requests |
1010 | `SessionService` | `CreateLoginLink`, `SignOutEverywhere` (owner only) |
11-| `EventService` | `Watch` (streamed, resumable). Topics `home`, `repo:<name>` and `owner` (owner rights only). Events 17–21: `issue_opened`, `issue_closed`, `issue_reopened`, `issue_commented`, and `issue_submitted` (only on `owner`, no content). Event 22: `merge_request_mergeability` |
11+| `EventService` | `Watch` (streamed, resumable). Topics `home`, `repo:<name>` and `owner` (owner rights only). Events 17–21: `issue_opened`, `issue_closed`, `issue_reopened`, `issue_commented`, and `issue_submitted` (only on `owner`, no content). Event 22: `merge_request_mergeability`. Event 31: `issue_resolved` |
1212 | `ServerService` | `GetVersion` (version and features), `GetSigningKey`, `RotateOIDCKey` (owner only) |
1313 | `TrustedKeyService` | `ListTrustedKeys`, `AddTrustedKey` and `RemoveTrustedKey` (owner only). `Commit.verification` carries the result of the check (feature `commit_signatures`) |
1414 | `RunService` | `ListRuns`, `GetRun`, `GetJob`, `GetJobLog` (streamed, follow), `ListAnnotations`, `GetStepSummaries`, `CancelRun`, `RerunRun`, `DispatchWorkflow`, `ListWorkflows`, `DisableWorkflow`, `EnableWorkflow`, `ListChecks`, `ListRunners`, `RemoveRunner` |
@@ -6,9 +6,9 @@
6 | `GitService` | `ListRefs`, `GetTree`, `ListPaths`, `GetBlob` (streamed), `ListCommits`, `GetCommit` (structured diff), `ResolveRevision` |6 | `GitService` | `ListRefs`, `GetTree`, `ListPaths`, `GetBlob` (streamed), `ListCommits`, `GetCommit` (structured diff), `ResolveRevision` |
7 | `TokenService` | `CreateToken` (secret shown once), `ListTokens`, `RevokeToken` |7 | `TokenService` | `CreateToken` (secret shown once), `ListTokens`, `RevokeToken` |
8 | `MergeRequestService` | `CreateMergeRequest`, `ListMergeRequests`, `GetMergeRequest` (commits, diff, comments), `AddComment`, `EditComment`, `DeleteComment`, `ResolveComment`, `CloseMergeRequest`, `MarkReady`, `MergeMergeRequest` (modes fast-forward, squash, merge commit), `MarkRead`, `EnableAutoMerge`, `CancelAutoMerge` (merge by itself once a check passed on the head and all its runs are complete; setting `allow_auto_merge`, `MergeRequest.auto_merge`, events `MergeRequestAutoMergeEnabled` and `MergeRequestAutoMergeCancelled`, `MergeRequestMerged.auto`; feature `merge_requests.auto_merge`). Open merge requests carry `mergeability` (up to date, behind, conflict, or checking) and `Watch` publishes `MergeRequestMergeability` each time a result is settled for new branch tips (feature `merge_requests.mergeability`) |8 | `MergeRequestService` | `CreateMergeRequest`, `ListMergeRequests`, `GetMergeRequest` (commits, diff, comments), `AddComment`, `EditComment`, `DeleteComment`, `ResolveComment`, `CloseMergeRequest`, `MarkReady`, `MergeMergeRequest` (modes fast-forward, squash, merge commit), `MarkRead`, `EnableAutoMerge`, `CancelAutoMerge` (merge by itself once a check passed on the head and all its runs are complete; setting `allow_auto_merge`, `MergeRequest.auto_merge`, events `MergeRequestAutoMergeEnabled` and `MergeRequestAutoMergeCancelled`, `MergeRequestMerged.auto`; feature `merge_requests.auto_merge`). Open merge requests carry `mergeability` (up to date, behind, conflict, or checking) and `Watch` publishes `MergeRequestMergeability` each time a result is settled for new branch tips (feature `merge_requests.mergeability`) |
9-| `IssueService` | `CreateIssue`, `ListIssues`, `GetIssue` (comments, merge requests naming the issue, `closed_by_merge_request`), `AddIssueComment`, `CloseIssue`, `ReopenIssue`; owner only: `ListPendingIssues`, `ApproveIssue`, `RejectIssue`. Features `issues`, `issues.links`. Issues share numbers with merge requests |9+| `IssueService` | `CreateIssue`, `ListIssues`, `GetIssue` (comments, merge requests naming the issue, history events, `closed_by_merge_request`), `AddIssueComment`, `CloseIssue`, `ResolveIssue`, `ReopenIssue`; owner only: `ListPendingIssues`, `ApproveIssue`, `RejectIssue`. States `OPEN`, `CLOSED`, `RESOLVED`; `ResolveIssue` needs `issue:write`. Features `issues`, `issues.links`, `issues.resolve`. Issues share numbers with merge requests |
10 | `SessionService` | `CreateLoginLink`, `SignOutEverywhere` (owner only) |10 | `SessionService` | `CreateLoginLink`, `SignOutEverywhere` (owner only) |
11-| `EventService` | `Watch` (streamed, resumable). Topics `home`, `repo:<name>` and `owner` (owner rights only). Events 17–21: `issue_opened`, `issue_closed`, `issue_reopened`, `issue_commented`, and `issue_submitted` (only on `owner`, no content). Event 22: `merge_request_mergeability` |11+| `EventService` | `Watch` (streamed, resumable). Topics `home`, `repo:<name>` and `owner` (owner rights only). Events 17–21: `issue_opened`, `issue_closed`, `issue_reopened`, `issue_commented`, and `issue_submitted` (only on `owner`, no content). Event 22: `merge_request_mergeability`. Event 31: `issue_resolved` |
12 | `ServerService` | `GetVersion` (version and features), `GetSigningKey`, `RotateOIDCKey` (owner only) |12 | `ServerService` | `GetVersion` (version and features), `GetSigningKey`, `RotateOIDCKey` (owner only) |
13 | `TrustedKeyService` | `ListTrustedKeys`, `AddTrustedKey` and `RemoveTrustedKey` (owner only). `Commit.verification` carries the result of the check (feature `commit_signatures`) |13 | `TrustedKeyService` | `ListTrustedKeys`, `AddTrustedKey` and `RemoveTrustedKey` (owner only). `Commit.verification` carries the result of the check (feature `commit_signatures`) |
14 | `RunService` | `ListRuns`, `GetRun`, `GetJob`, `GetJobLog` (streamed, follow), `ListAnnotations`, `GetStepSummaries`, `CancelRun`, `RerunRun`, `DispatchWorkflow`, `ListWorkflows`, `DisableWorkflow`, `EnableWorkflow`, `ListChecks`, `ListRunners`, `RemoveRunner` |14 | `RunService` | `ListRuns`, `GetRun`, `GetJob`, `GetJobLog` (streamed, follow), `ListAnnotations`, `GetStepSummaries`, `CancelRun`, `RerunRun`, `DispatchWorkflow`, `ListWorkflows`, `DisableWorkflow`, `EnableWorkflow`, `ListChecks`, `ListRunners`, `RemoveRunner` |
M gen/ermite/ermiteconnect/event.connect.go +8 -6
@@ -49,9 +49,10 @@ type EventServiceClient interface {
4949 // for merge requests, merge_request_opened, merge_request_merged,
5050 // merge_request_closed, comment_added, comment_edited, comment_deleted and
5151 // comment_resolved (a thread resolved or reopened); for issues, issue_opened,
52- // issue_closed, issue_reopened and issue_commented, and issue_submitted. Each
53- // one is delivered on the "repo:<name>" topic of its repository and on
54- // "home", subject to the visibility of the repository, except
52+ // issue_closed, issue_resolved, issue_reopened and issue_commented, and
53+ // issue_submitted. Each one is delivered on the "repo:<name>" topic of its
54+ // repository and on "home", subject to the visibility of the repository,
55+ // except
5556 // issue_submitted: it is delivered only on the "owner" topic, never on
5657 // "home" or "repo:<name>", and carries no content (see IssueSubmitted).
5758 //
@@ -125,9 +126,10 @@ type EventServiceHandler interface {
125126 // for merge requests, merge_request_opened, merge_request_merged,
126127 // merge_request_closed, comment_added, comment_edited, comment_deleted and
127128 // comment_resolved (a thread resolved or reopened); for issues, issue_opened,
128- // issue_closed, issue_reopened and issue_commented, and issue_submitted. Each
129- // one is delivered on the "repo:<name>" topic of its repository and on
130- // "home", subject to the visibility of the repository, except
129+ // issue_closed, issue_resolved, issue_reopened and issue_commented, and
130+ // issue_submitted. Each one is delivered on the "repo:<name>" topic of its
131+ // repository and on "home", subject to the visibility of the repository,
132+ // except
131133 // issue_submitted: it is delivered only on the "owner" topic, never on
132134 // "home" or "repo:<name>", and carries no content (see IssueSubmitted).
133135 //
@@ -49,9 +49,10 @@ type EventServiceClient interface {
49 // for merge requests, merge_request_opened, merge_request_merged,49 // for merge requests, merge_request_opened, merge_request_merged,
50 // merge_request_closed, comment_added, comment_edited, comment_deleted and50 // merge_request_closed, comment_added, comment_edited, comment_deleted and
51 // comment_resolved (a thread resolved or reopened); for issues, issue_opened,51 // comment_resolved (a thread resolved or reopened); for issues, issue_opened,
52- // issue_closed, issue_reopened and issue_commented, and issue_submitted. Each52+ // issue_closed, issue_resolved, issue_reopened and issue_commented, and
53- // one is delivered on the "repo:<name>" topic of its repository and on53+ // issue_submitted. Each one is delivered on the "repo:<name>" topic of its
54- // "home", subject to the visibility of the repository, except54+ // repository and on "home", subject to the visibility of the repository,
55+ // except
55 // issue_submitted: it is delivered only on the "owner" topic, never on56 // issue_submitted: it is delivered only on the "owner" topic, never on
56 // "home" or "repo:<name>", and carries no content (see IssueSubmitted).57 // "home" or "repo:<name>", and carries no content (see IssueSubmitted).
57 //58 //
@@ -125,9 +126,10 @@ type EventServiceHandler interface {
125 // for merge requests, merge_request_opened, merge_request_merged,126 // for merge requests, merge_request_opened, merge_request_merged,
126 // merge_request_closed, comment_added, comment_edited, comment_deleted and127 // merge_request_closed, comment_added, comment_edited, comment_deleted and
127 // comment_resolved (a thread resolved or reopened); for issues, issue_opened,128 // comment_resolved (a thread resolved or reopened); for issues, issue_opened,
128- // issue_closed, issue_reopened and issue_commented, and issue_submitted. Each129+ // issue_closed, issue_resolved, issue_reopened and issue_commented, and
129- // one is delivered on the "repo:<name>" topic of its repository and on130+ // issue_submitted. Each one is delivered on the "repo:<name>" topic of its
130- // "home", subject to the visibility of the repository, except131+ // repository and on "home", subject to the visibility of the repository,
132+ // except
131 // issue_submitted: it is delivered only on the "owner" topic, never on133 // issue_submitted: it is delivered only on the "owner" topic, never on
132 // "home" or "repo:<name>", and carries no content (see IssueSubmitted).134 // "home" or "repo:<name>", and carries no content (see IssueSubmitted).
133 //135 //
M gen/ermite/ermiteconnect/issue.connect.go +35 -2
@@ -47,6 +47,9 @@ const (
4747 IssueServiceAddIssueCommentProcedure = "/ermite.IssueService/AddIssueComment"
4848 // IssueServiceCloseIssueProcedure is the fully-qualified name of the IssueService's CloseIssue RPC.
4949 IssueServiceCloseIssueProcedure = "/ermite.IssueService/CloseIssue"
50+ // IssueServiceResolveIssueProcedure is the fully-qualified name of the IssueService's ResolveIssue
51+ // RPC.
52+ IssueServiceResolveIssueProcedure = "/ermite.IssueService/ResolveIssue"
5053 // IssueServiceReopenIssueProcedure is the fully-qualified name of the IssueService's ReopenIssue
5154 // RPC.
5255 IssueServiceReopenIssueProcedure = "/ermite.IssueService/ReopenIssue"
@@ -73,7 +76,10 @@ type IssueServiceClient interface {
7376 AddIssueComment(context.Context, *connect.Request[ermite.AddIssueCommentRequest]) (*connect.Response[ermite.AddIssueCommentResponse], error)
7477 // CloseIssue closes an issue. Needs the issue:write scope.
7578 CloseIssue(context.Context, *connect.Request[ermite.CloseIssueRequest]) (*connect.Response[ermite.CloseIssueResponse], error)
76- // ReopenIssue reopens an issue. Needs the issue:write scope.
79+ // ResolveIssue marks an open issue as resolved. Needs the issue:write scope.
80+ ResolveIssue(context.Context, *connect.Request[ermite.ResolveIssueRequest]) (*connect.Response[ermite.ResolveIssueResponse], error)
81+ // ReopenIssue reopens a closed or resolved issue. Needs the issue:write
82+ // scope.
7783 ReopenIssue(context.Context, *connect.Request[ermite.ReopenIssueRequest]) (*connect.Response[ermite.ReopenIssueResponse], error)
7884 // ListPendingIssues lists issues awaiting moderation, oldest first. Owner
7985 // rights only; anyone else gets NOT_FOUND.
@@ -127,6 +133,12 @@ func NewIssueServiceClient(httpClient connect.HTTPClient, baseURL string, opts .
127133 connect.WithSchema(issueServiceMethods.ByName("CloseIssue")),
128134 connect.WithClientOptions(opts...),
129135 ),
136+ resolveIssue: connect.NewClient[ermite.ResolveIssueRequest, ermite.ResolveIssueResponse](
137+ httpClient,
138+ baseURL+IssueServiceResolveIssueProcedure,
139+ connect.WithSchema(issueServiceMethods.ByName("ResolveIssue")),
140+ connect.WithClientOptions(opts...),
141+ ),
130142 reopenIssue: connect.NewClient[ermite.ReopenIssueRequest, ermite.ReopenIssueResponse](
131143 httpClient,
132144 baseURL+IssueServiceReopenIssueProcedure,
@@ -161,6 +173,7 @@ type issueServiceClient struct {
161173 getIssue *connect.Client[ermite.GetIssueRequest, ermite.GetIssueResponse]
162174 addIssueComment *connect.Client[ermite.AddIssueCommentRequest, ermite.AddIssueCommentResponse]
163175 closeIssue *connect.Client[ermite.CloseIssueRequest, ermite.CloseIssueResponse]
176+ resolveIssue *connect.Client[ermite.ResolveIssueRequest, ermite.ResolveIssueResponse]
164177 reopenIssue *connect.Client[ermite.ReopenIssueRequest, ermite.ReopenIssueResponse]
165178 listPendingIssues *connect.Client[ermite.ListPendingIssuesRequest, ermite.ListPendingIssuesResponse]
166179 approveIssue *connect.Client[ermite.ApproveIssueRequest, ermite.ApproveIssueResponse]
@@ -192,6 +205,11 @@ func (c *issueServiceClient) CloseIssue(ctx context.Context, req *connect.Reques
192205 return c.closeIssue.CallUnary(ctx, req)
193206 }
194207
208+// ResolveIssue calls ermite.IssueService.ResolveIssue.
209+func (c *issueServiceClient) ResolveIssue(ctx context.Context, req *connect.Request[ermite.ResolveIssueRequest]) (*connect.Response[ermite.ResolveIssueResponse], error) {
210+ return c.resolveIssue.CallUnary(ctx, req)
211+}
212+
195213 // ReopenIssue calls ermite.IssueService.ReopenIssue.
196214 func (c *issueServiceClient) ReopenIssue(ctx context.Context, req *connect.Request[ermite.ReopenIssueRequest]) (*connect.Response[ermite.ReopenIssueResponse], error) {
197215 return c.reopenIssue.CallUnary(ctx, req)
@@ -224,7 +242,10 @@ type IssueServiceHandler interface {
224242 AddIssueComment(context.Context, *connect.Request[ermite.AddIssueCommentRequest]) (*connect.Response[ermite.AddIssueCommentResponse], error)
225243 // CloseIssue closes an issue. Needs the issue:write scope.
226244 CloseIssue(context.Context, *connect.Request[ermite.CloseIssueRequest]) (*connect.Response[ermite.CloseIssueResponse], error)
227- // ReopenIssue reopens an issue. Needs the issue:write scope.
245+ // ResolveIssue marks an open issue as resolved. Needs the issue:write scope.
246+ ResolveIssue(context.Context, *connect.Request[ermite.ResolveIssueRequest]) (*connect.Response[ermite.ResolveIssueResponse], error)
247+ // ReopenIssue reopens a closed or resolved issue. Needs the issue:write
248+ // scope.
228249 ReopenIssue(context.Context, *connect.Request[ermite.ReopenIssueRequest]) (*connect.Response[ermite.ReopenIssueResponse], error)
229250 // ListPendingIssues lists issues awaiting moderation, oldest first. Owner
230251 // rights only; anyone else gets NOT_FOUND.
@@ -274,6 +295,12 @@ func NewIssueServiceHandler(svc IssueServiceHandler, opts ...connect.HandlerOpti
274295 connect.WithSchema(issueServiceMethods.ByName("CloseIssue")),
275296 connect.WithHandlerOptions(opts...),
276297 )
298+ issueServiceResolveIssueHandler := connect.NewUnaryHandler(
299+ IssueServiceResolveIssueProcedure,
300+ svc.ResolveIssue,
301+ connect.WithSchema(issueServiceMethods.ByName("ResolveIssue")),
302+ connect.WithHandlerOptions(opts...),
303+ )
277304 issueServiceReopenIssueHandler := connect.NewUnaryHandler(
278305 IssueServiceReopenIssueProcedure,
279306 svc.ReopenIssue,
@@ -310,6 +337,8 @@ func NewIssueServiceHandler(svc IssueServiceHandler, opts ...connect.HandlerOpti
310337 issueServiceAddIssueCommentHandler.ServeHTTP(w, r)
311338 case IssueServiceCloseIssueProcedure:
312339 issueServiceCloseIssueHandler.ServeHTTP(w, r)
340+ case IssueServiceResolveIssueProcedure:
341+ issueServiceResolveIssueHandler.ServeHTTP(w, r)
313342 case IssueServiceReopenIssueProcedure:
314343 issueServiceReopenIssueHandler.ServeHTTP(w, r)
315344 case IssueServiceListPendingIssuesProcedure:
@@ -347,6 +376,10 @@ func (UnimplementedIssueServiceHandler) CloseIssue(context.Context, *connect.Req
347376 return nil, connect.NewError(connect.CodeUnimplemented, errors.New("ermite.IssueService.CloseIssue is not implemented"))
348377 }
349378
379+func (UnimplementedIssueServiceHandler) ResolveIssue(context.Context, *connect.Request[ermite.ResolveIssueRequest]) (*connect.Response[ermite.ResolveIssueResponse], error) {
380+ return nil, connect.NewError(connect.CodeUnimplemented, errors.New("ermite.IssueService.ResolveIssue is not implemented"))
381+}
382+
350383 func (UnimplementedIssueServiceHandler) ReopenIssue(context.Context, *connect.Request[ermite.ReopenIssueRequest]) (*connect.Response[ermite.ReopenIssueResponse], error) {
351384 return nil, connect.NewError(connect.CodeUnimplemented, errors.New("ermite.IssueService.ReopenIssue is not implemented"))
352385 }
@@ -47,6 +47,9 @@ const (
47 IssueServiceAddIssueCommentProcedure = "/ermite.IssueService/AddIssueComment"47 IssueServiceAddIssueCommentProcedure = "/ermite.IssueService/AddIssueComment"
48 // IssueServiceCloseIssueProcedure is the fully-qualified name of the IssueService's CloseIssue RPC.48 // IssueServiceCloseIssueProcedure is the fully-qualified name of the IssueService's CloseIssue RPC.
49 IssueServiceCloseIssueProcedure = "/ermite.IssueService/CloseIssue"49 IssueServiceCloseIssueProcedure = "/ermite.IssueService/CloseIssue"
50+ // IssueServiceResolveIssueProcedure is the fully-qualified name of the IssueService's ResolveIssue
51+ // RPC.
52+ IssueServiceResolveIssueProcedure = "/ermite.IssueService/ResolveIssue"
50 // IssueServiceReopenIssueProcedure is the fully-qualified name of the IssueService's ReopenIssue53 // IssueServiceReopenIssueProcedure is the fully-qualified name of the IssueService's ReopenIssue
51 // RPC.54 // RPC.
52 IssueServiceReopenIssueProcedure = "/ermite.IssueService/ReopenIssue"55 IssueServiceReopenIssueProcedure = "/ermite.IssueService/ReopenIssue"
@@ -73,7 +76,10 @@ type IssueServiceClient interface {
73 AddIssueComment(context.Context, *connect.Request[ermite.AddIssueCommentRequest]) (*connect.Response[ermite.AddIssueCommentResponse], error)76 AddIssueComment(context.Context, *connect.Request[ermite.AddIssueCommentRequest]) (*connect.Response[ermite.AddIssueCommentResponse], error)
74 // CloseIssue closes an issue. Needs the issue:write scope.77 // CloseIssue closes an issue. Needs the issue:write scope.
75 CloseIssue(context.Context, *connect.Request[ermite.CloseIssueRequest]) (*connect.Response[ermite.CloseIssueResponse], error)78 CloseIssue(context.Context, *connect.Request[ermite.CloseIssueRequest]) (*connect.Response[ermite.CloseIssueResponse], error)
76- // ReopenIssue reopens an issue. Needs the issue:write scope.79+ // ResolveIssue marks an open issue as resolved. Needs the issue:write scope.
80+ ResolveIssue(context.Context, *connect.Request[ermite.ResolveIssueRequest]) (*connect.Response[ermite.ResolveIssueResponse], error)
81+ // ReopenIssue reopens a closed or resolved issue. Needs the issue:write
82+ // scope.
77 ReopenIssue(context.Context, *connect.Request[ermite.ReopenIssueRequest]) (*connect.Response[ermite.ReopenIssueResponse], error)83 ReopenIssue(context.Context, *connect.Request[ermite.ReopenIssueRequest]) (*connect.Response[ermite.ReopenIssueResponse], error)
78 // ListPendingIssues lists issues awaiting moderation, oldest first. Owner84 // ListPendingIssues lists issues awaiting moderation, oldest first. Owner
79 // rights only; anyone else gets NOT_FOUND.85 // rights only; anyone else gets NOT_FOUND.
@@ -127,6 +133,12 @@ func NewIssueServiceClient(httpClient connect.HTTPClient, baseURL string, opts .
127 connect.WithSchema(issueServiceMethods.ByName("CloseIssue")),133 connect.WithSchema(issueServiceMethods.ByName("CloseIssue")),
128 connect.WithClientOptions(opts...),134 connect.WithClientOptions(opts...),
129 ),135 ),
136+ resolveIssue: connect.NewClient[ermite.ResolveIssueRequest, ermite.ResolveIssueResponse](
137+ httpClient,
138+ baseURL+IssueServiceResolveIssueProcedure,
139+ connect.WithSchema(issueServiceMethods.ByName("ResolveIssue")),
140+ connect.WithClientOptions(opts...),
141+ ),
130 reopenIssue: connect.NewClient[ermite.ReopenIssueRequest, ermite.ReopenIssueResponse](142 reopenIssue: connect.NewClient[ermite.ReopenIssueRequest, ermite.ReopenIssueResponse](
131 httpClient,143 httpClient,
132 baseURL+IssueServiceReopenIssueProcedure,144 baseURL+IssueServiceReopenIssueProcedure,
@@ -161,6 +173,7 @@ type issueServiceClient struct {
161 getIssue *connect.Client[ermite.GetIssueRequest, ermite.GetIssueResponse]173 getIssue *connect.Client[ermite.GetIssueRequest, ermite.GetIssueResponse]
162 addIssueComment *connect.Client[ermite.AddIssueCommentRequest, ermite.AddIssueCommentResponse]174 addIssueComment *connect.Client[ermite.AddIssueCommentRequest, ermite.AddIssueCommentResponse]
163 closeIssue *connect.Client[ermite.CloseIssueRequest, ermite.CloseIssueResponse]175 closeIssue *connect.Client[ermite.CloseIssueRequest, ermite.CloseIssueResponse]
176+ resolveIssue *connect.Client[ermite.ResolveIssueRequest, ermite.ResolveIssueResponse]
164 reopenIssue *connect.Client[ermite.ReopenIssueRequest, ermite.ReopenIssueResponse]177 reopenIssue *connect.Client[ermite.ReopenIssueRequest, ermite.ReopenIssueResponse]
165 listPendingIssues *connect.Client[ermite.ListPendingIssuesRequest, ermite.ListPendingIssuesResponse]178 listPendingIssues *connect.Client[ermite.ListPendingIssuesRequest, ermite.ListPendingIssuesResponse]
166 approveIssue *connect.Client[ermite.ApproveIssueRequest, ermite.ApproveIssueResponse]179 approveIssue *connect.Client[ermite.ApproveIssueRequest, ermite.ApproveIssueResponse]
@@ -192,6 +205,11 @@ func (c *issueServiceClient) CloseIssue(ctx context.Context, req *connect.Reques
192 return c.closeIssue.CallUnary(ctx, req)205 return c.closeIssue.CallUnary(ctx, req)
193 }206 }
194 207
208+// ResolveIssue calls ermite.IssueService.ResolveIssue.
209+func (c *issueServiceClient) ResolveIssue(ctx context.Context, req *connect.Request[ermite.ResolveIssueRequest]) (*connect.Response[ermite.ResolveIssueResponse], error) {
210+ return c.resolveIssue.CallUnary(ctx, req)
211+}
212+
195 // ReopenIssue calls ermite.IssueService.ReopenIssue.213 // ReopenIssue calls ermite.IssueService.ReopenIssue.
196 func (c *issueServiceClient) ReopenIssue(ctx context.Context, req *connect.Request[ermite.ReopenIssueRequest]) (*connect.Response[ermite.ReopenIssueResponse], error) {214 func (c *issueServiceClient) ReopenIssue(ctx context.Context, req *connect.Request[ermite.ReopenIssueRequest]) (*connect.Response[ermite.ReopenIssueResponse], error) {
197 return c.reopenIssue.CallUnary(ctx, req)215 return c.reopenIssue.CallUnary(ctx, req)
@@ -224,7 +242,10 @@ type IssueServiceHandler interface {
224 AddIssueComment(context.Context, *connect.Request[ermite.AddIssueCommentRequest]) (*connect.Response[ermite.AddIssueCommentResponse], error)242 AddIssueComment(context.Context, *connect.Request[ermite.AddIssueCommentRequest]) (*connect.Response[ermite.AddIssueCommentResponse], error)
225 // CloseIssue closes an issue. Needs the issue:write scope.243 // CloseIssue closes an issue. Needs the issue:write scope.
226 CloseIssue(context.Context, *connect.Request[ermite.CloseIssueRequest]) (*connect.Response[ermite.CloseIssueResponse], error)244 CloseIssue(context.Context, *connect.Request[ermite.CloseIssueRequest]) (*connect.Response[ermite.CloseIssueResponse], error)
227- // ReopenIssue reopens an issue. Needs the issue:write scope.245+ // ResolveIssue marks an open issue as resolved. Needs the issue:write scope.
246+ ResolveIssue(context.Context, *connect.Request[ermite.ResolveIssueRequest]) (*connect.Response[ermite.ResolveIssueResponse], error)
247+ // ReopenIssue reopens a closed or resolved issue. Needs the issue:write
248+ // scope.
228 ReopenIssue(context.Context, *connect.Request[ermite.ReopenIssueRequest]) (*connect.Response[ermite.ReopenIssueResponse], error)249 ReopenIssue(context.Context, *connect.Request[ermite.ReopenIssueRequest]) (*connect.Response[ermite.ReopenIssueResponse], error)
229 // ListPendingIssues lists issues awaiting moderation, oldest first. Owner250 // ListPendingIssues lists issues awaiting moderation, oldest first. Owner
230 // rights only; anyone else gets NOT_FOUND.251 // rights only; anyone else gets NOT_FOUND.
@@ -274,6 +295,12 @@ func NewIssueServiceHandler(svc IssueServiceHandler, opts ...connect.HandlerOpti
274 connect.WithSchema(issueServiceMethods.ByName("CloseIssue")),295 connect.WithSchema(issueServiceMethods.ByName("CloseIssue")),
275 connect.WithHandlerOptions(opts...),296 connect.WithHandlerOptions(opts...),
276 )297 )
298+ issueServiceResolveIssueHandler := connect.NewUnaryHandler(
299+ IssueServiceResolveIssueProcedure,
300+ svc.ResolveIssue,
301+ connect.WithSchema(issueServiceMethods.ByName("ResolveIssue")),
302+ connect.WithHandlerOptions(opts...),
303+ )
277 issueServiceReopenIssueHandler := connect.NewUnaryHandler(304 issueServiceReopenIssueHandler := connect.NewUnaryHandler(
278 IssueServiceReopenIssueProcedure,305 IssueServiceReopenIssueProcedure,
279 svc.ReopenIssue,306 svc.ReopenIssue,
@@ -310,6 +337,8 @@ func NewIssueServiceHandler(svc IssueServiceHandler, opts ...connect.HandlerOpti
310 issueServiceAddIssueCommentHandler.ServeHTTP(w, r)337 issueServiceAddIssueCommentHandler.ServeHTTP(w, r)
311 case IssueServiceCloseIssueProcedure:338 case IssueServiceCloseIssueProcedure:
312 issueServiceCloseIssueHandler.ServeHTTP(w, r)339 issueServiceCloseIssueHandler.ServeHTTP(w, r)
340+ case IssueServiceResolveIssueProcedure:
341+ issueServiceResolveIssueHandler.ServeHTTP(w, r)
313 case IssueServiceReopenIssueProcedure:342 case IssueServiceReopenIssueProcedure:
314 issueServiceReopenIssueHandler.ServeHTTP(w, r)343 issueServiceReopenIssueHandler.ServeHTTP(w, r)
315 case IssueServiceListPendingIssuesProcedure:344 case IssueServiceListPendingIssuesProcedure:
@@ -347,6 +376,10 @@ func (UnimplementedIssueServiceHandler) CloseIssue(context.Context, *connect.Req
347 return nil, connect.NewError(connect.CodeUnimplemented, errors.New("ermite.IssueService.CloseIssue is not implemented"))376 return nil, connect.NewError(connect.CodeUnimplemented, errors.New("ermite.IssueService.CloseIssue is not implemented"))
348 }377 }
349 378
379+func (UnimplementedIssueServiceHandler) ResolveIssue(context.Context, *connect.Request[ermite.ResolveIssueRequest]) (*connect.Response[ermite.ResolveIssueResponse], error) {
380+ return nil, connect.NewError(connect.CodeUnimplemented, errors.New("ermite.IssueService.ResolveIssue is not implemented"))
381+}
382+
350 func (UnimplementedIssueServiceHandler) ReopenIssue(context.Context, *connect.Request[ermite.ReopenIssueRequest]) (*connect.Response[ermite.ReopenIssueResponse], error) {383 func (UnimplementedIssueServiceHandler) ReopenIssue(context.Context, *connect.Request[ermite.ReopenIssueRequest]) (*connect.Response[ermite.ReopenIssueResponse], error) {
351 return nil, connect.NewError(connect.CodeUnimplemented, errors.New("ermite.IssueService.ReopenIssue is not implemented"))384 return nil, connect.NewError(connect.CodeUnimplemented, errors.New("ermite.IssueService.ReopenIssue is not implemented"))
352 }385 }
M gen/ermite/event.pb.go +175 -103 generated file: collapsed
M gen/ermite/issue.pb.go +216 -83 generated file: collapsed
M gen/ermite/merge_request.pb.go +546 -174 generated file: collapsed
M issue_test.go +2 -1
@@ -19,6 +19,7 @@ func TestIssueProcedures(t *testing.T) {
1919 ermiteconnect.IssueServiceAddIssueCommentProcedure: "AddIssueComment",
2020 ermiteconnect.IssueServiceCloseIssueProcedure: "CloseIssue",
2121 ermiteconnect.IssueServiceReopenIssueProcedure: "ReopenIssue",
22+ ermiteconnect.IssueServiceResolveIssueProcedure: "ResolveIssue",
2223 ermiteconnect.IssueServiceListPendingIssuesProcedure: "ListPendingIssues",
2324 ermiteconnect.IssueServiceApproveIssueProcedure: "ApproveIssue",
2425 ermiteconnect.IssueServiceRejectIssueProcedure: "RejectIssue",
@@ -58,7 +59,7 @@ func TestIssueContract(t *testing.T) {
5859 if ermite.Scope_SCOPE_ISSUE_WRITE != 11 {
5960 t.Fatal("issue:write scope number changed")
6061 }
61- for name, want := range map[string]int32{"ISSUE_STATE_UNSPECIFIED": 0, "ISSUE_STATE_OPEN": 1, "ISSUE_STATE_CLOSED": 2, "ISSUE_STATE_PENDING": 3} {
62+ for name, want := range map[string]int32{"ISSUE_STATE_UNSPECIFIED": 0, "ISSUE_STATE_OPEN": 1, "ISSUE_STATE_CLOSED": 2, "ISSUE_STATE_PENDING": 3, "ISSUE_STATE_RESOLVED": 4} {
6263 if got := ermite.IssueState_value[name]; got != want {
6364 t.Errorf("%s = %d, want %d", name, got, want)
6465 }
@@ -19,6 +19,7 @@ func TestIssueProcedures(t *testing.T) {
19 ermiteconnect.IssueServiceAddIssueCommentProcedure: "AddIssueComment",19 ermiteconnect.IssueServiceAddIssueCommentProcedure: "AddIssueComment",
20 ermiteconnect.IssueServiceCloseIssueProcedure: "CloseIssue",20 ermiteconnect.IssueServiceCloseIssueProcedure: "CloseIssue",
21 ermiteconnect.IssueServiceReopenIssueProcedure: "ReopenIssue",21 ermiteconnect.IssueServiceReopenIssueProcedure: "ReopenIssue",
22+ ermiteconnect.IssueServiceResolveIssueProcedure: "ResolveIssue",
22 ermiteconnect.IssueServiceListPendingIssuesProcedure: "ListPendingIssues",23 ermiteconnect.IssueServiceListPendingIssuesProcedure: "ListPendingIssues",
23 ermiteconnect.IssueServiceApproveIssueProcedure: "ApproveIssue",24 ermiteconnect.IssueServiceApproveIssueProcedure: "ApproveIssue",
24 ermiteconnect.IssueServiceRejectIssueProcedure: "RejectIssue",25 ermiteconnect.IssueServiceRejectIssueProcedure: "RejectIssue",
@@ -58,7 +59,7 @@ func TestIssueContract(t *testing.T) {
58 if ermite.Scope_SCOPE_ISSUE_WRITE != 11 {59 if ermite.Scope_SCOPE_ISSUE_WRITE != 11 {
59 t.Fatal("issue:write scope number changed")60 t.Fatal("issue:write scope number changed")
60 }61 }
61- for name, want := range map[string]int32{"ISSUE_STATE_UNSPECIFIED": 0, "ISSUE_STATE_OPEN": 1, "ISSUE_STATE_CLOSED": 2, "ISSUE_STATE_PENDING": 3} {62+ for name, want := range map[string]int32{"ISSUE_STATE_UNSPECIFIED": 0, "ISSUE_STATE_OPEN": 1, "ISSUE_STATE_CLOSED": 2, "ISSUE_STATE_PENDING": 3, "ISSUE_STATE_RESOLVED": 4} {
62 if got := ermite.IssueState_value[name]; got != want {63 if got := ermite.IssueState_value[name]; got != want {
63 t.Errorf("%s = %d, want %d", name, got, want)64 t.Errorf("%s = %d, want %d", name, got, want)
64 }65 }
M proto/ermite/event.proto +15 -4
@@ -21,9 +21,10 @@ service EventService {
2121 // for merge requests, merge_request_opened, merge_request_merged,
2222 // merge_request_closed, comment_added, comment_edited, comment_deleted and
2323 // comment_resolved (a thread resolved or reopened); for issues, issue_opened,
24- // issue_closed, issue_reopened and issue_commented, and issue_submitted. Each
25- // one is delivered on the "repo:<name>" topic of its repository and on
26- // "home", subject to the visibility of the repository, except
24+ // issue_closed, issue_resolved, issue_reopened and issue_commented, and
25+ // issue_submitted. Each one is delivered on the "repo:<name>" topic of its
26+ // repository and on "home", subject to the visibility of the repository,
27+ // except
2728 // issue_submitted: it is delivered only on the "owner" topic, never on
2829 // "home" or "repo:<name>", and carries no content (see IssueSubmitted).
2930 //
@@ -153,6 +154,8 @@ message Event {
153154 ReleaseDeleted release_deleted = 29;
154155 // ReleaseUnpublished reports a release turned into a draft after tag deletion.
155156 ReleaseUnpublished release_unpublished = 30;
157+ // IssueResolved: an issue was resolved.
158+ IssueResolved issue_resolved = 31;
156159 }
157160 }
158161
@@ -290,12 +293,20 @@ message IssueOpened {
290293 Issue issue = 1;
291294 }
292295
293-// IssueClosed reports a closed issue.
296+// IssueClosed reports a closed issue. A merge that resolves an issue sends
297+// issue_resolved, not issue_closed.
294298 message IssueClosed {
295299 // Issue is the issue after closing, including its repository and number.
296300 Issue issue = 1;
297301 }
298302
303+// IssueResolved reports a resolved issue.
304+message IssueResolved {
305+ // Issue is the issue after resolving, including its repository, number and
306+ // closed_by_merge_request.
307+ Issue issue = 1;
308+}
309+
299310 // IssueReopened reports a reopened issue.
300311 message IssueReopened {
301312 // Issue is the issue after reopening, including its repository and number.
@@ -21,9 +21,10 @@ service EventService {
21 // for merge requests, merge_request_opened, merge_request_merged,21 // for merge requests, merge_request_opened, merge_request_merged,
22 // merge_request_closed, comment_added, comment_edited, comment_deleted and22 // merge_request_closed, comment_added, comment_edited, comment_deleted and
23 // comment_resolved (a thread resolved or reopened); for issues, issue_opened,23 // comment_resolved (a thread resolved or reopened); for issues, issue_opened,
24- // issue_closed, issue_reopened and issue_commented, and issue_submitted. Each24+ // issue_closed, issue_resolved, issue_reopened and issue_commented, and
25- // one is delivered on the "repo:<name>" topic of its repository and on25+ // issue_submitted. Each one is delivered on the "repo:<name>" topic of its
26- // "home", subject to the visibility of the repository, except26+ // repository and on "home", subject to the visibility of the repository,
27+ // except
27 // issue_submitted: it is delivered only on the "owner" topic, never on28 // issue_submitted: it is delivered only on the "owner" topic, never on
28 // "home" or "repo:<name>", and carries no content (see IssueSubmitted).29 // "home" or "repo:<name>", and carries no content (see IssueSubmitted).
29 //30 //
@@ -153,6 +154,8 @@ message Event {
153 ReleaseDeleted release_deleted = 29;154 ReleaseDeleted release_deleted = 29;
154 // ReleaseUnpublished reports a release turned into a draft after tag deletion.155 // ReleaseUnpublished reports a release turned into a draft after tag deletion.
155 ReleaseUnpublished release_unpublished = 30;156 ReleaseUnpublished release_unpublished = 30;
157+ // IssueResolved: an issue was resolved.
158+ IssueResolved issue_resolved = 31;
156 }159 }
157 }160 }
158 161
@@ -290,12 +293,20 @@ message IssueOpened {
290 Issue issue = 1;293 Issue issue = 1;
291 }294 }
292 295
293-// IssueClosed reports a closed issue.296+// IssueClosed reports a closed issue. A merge that resolves an issue sends
297+// issue_resolved, not issue_closed.
294 message IssueClosed {298 message IssueClosed {
295 // Issue is the issue after closing, including its repository and number.299 // Issue is the issue after closing, including its repository and number.
296 Issue issue = 1;300 Issue issue = 1;
297 }301 }
298 302
303+// IssueResolved reports a resolved issue.
304+message IssueResolved {
305+ // Issue is the issue after resolving, including its repository, number and
306+ // closed_by_merge_request.
307+ Issue issue = 1;
308+}
309+
299 // IssueReopened reports a reopened issue.310 // IssueReopened reports a reopened issue.
300 message IssueReopened {311 message IssueReopened {
301 // Issue is the issue after reopening, including its repository and number.312 // Issue is the issue after reopening, including its repository and number.
M proto/ermite/issue.proto +46 -15
@@ -14,10 +14,14 @@ enum IssueState {
1414 ISSUE_STATE_UNSPECIFIED = 0;
1515 // The issue is open.
1616 ISSUE_STATE_OPEN = 1;
17- // The issue is closed.
17+ // The issue is closed without being resolved (won't fix, duplicate, ...).
1818 ISSUE_STATE_CLOSED = 2;
1919 // Reserved for later use: awaiting moderation, owner-only.
2020 ISSUE_STATE_PENDING = 3;
21+ // The issue was fixed: by the merge of a merge request with a closing
22+ // reference, or by hand (ResolveIssue). CLOSED means closed without being
23+ // resolved.
24+ ISSUE_STATE_RESOLVED = 4;
2125 }
2226
2327 // Issue is a discussion in a repository.
@@ -45,9 +49,10 @@ message Issue {
4549 google.protobuf.Timestamp updated_at = 8;
4650 // CommentCount is the number of comments on the issue.
4751 int32 comment_count = 9;
48- // ClosedByMergeRequest is the number of the merge request whose merge closed
49- // the issue (a closing reference such as "Fixes #N"); 0 when the issue was
50- // closed by hand or is open. Reset when the issue is reopened.
52+ // ClosedByMergeRequest is the number of the merge request whose merge
53+ // resolved the issue (a closing reference such as "Fixes #N"); 0 when the
54+ // issue was resolved by hand, is closed, or is open. Reset when the issue is
55+ // reopened.
5156 int64 closed_by_merge_request = 10;
5257 }
5358
@@ -86,29 +91,35 @@ message IssueComment {
8691 //
8792 // Reading (ListIssues, GetIssue) needs the same access as reading the
8893 // repository: nothing for a public one, the repo:read scope for a private one.
89-// Writing (CreateIssue, AddIssueComment, CloseIssue, ReopenIssue) needs the
94+// Writing (CreateIssue, AddIssueComment, CloseIssue, ResolveIssue,
95+// ReopenIssue) needs the
9096 // owner or a token with the issue:write scope on the repository; branch
9197 // prefixes do not restrict it. A caller without credentials gets
9298 // unauthenticated, one with an insufficient scope gets permission_denied, and
9399 // one that cannot read the repository gets not_found.
94100 //
95101 // A closing reference ("Fixes #N", "Closes #N", "Resolves #N") in the title,
96-// description or commit messages of a merge request closes the issue when the
97-// request is merged, and only then; the issue.closed event and Issue carry
98-// closed_by_merge_request. Servers that report these links (GetIssueResponse.
99-// references, Issue.closed_by_merge_request) list "issues.links" in
100-// GetVersionResponse.features; older servers leave them empty.
102+// description or commit messages of a merge request resolves the issue when the
103+// request is merged, and only then: the issue gets the state RESOLVED, an
104+// issue.resolved event is sent, and Issue carries closed_by_merge_request.
105+// Servers that report these links (GetIssueResponse.references,
106+// Issue.closed_by_merge_request) list "issues.links" in
107+// GetVersionResponse.features; older servers leave them empty. Servers that
108+// also keep the history of issues (GetIssueResponse.events), implement
109+// ResolveIssue and the RESOLVED state list "issues.resolve".
101110 //
102111 // Text fields are normalized to NFC and stripped of control and Unicode
103112 // bidirectional characters (line breaks and tabs stay in bodies and comments).
104113 // The title is one line of 1 to 120 code points; body and comment text are at
105114 // most 10,000 code points and a comment must not be blank. A text that breaks
106115 // a rule is rejected with invalid_argument. A comment on an issue that already
107-// holds 1,000 comments gives resource_exhausted. Closing an issue that is not
108-// open, or reopening one that is not closed, gives failed_precondition.
116+// holds 1,000 comments gives resource_exhausted. Closing or resolving an issue
117+// that is not open, or reopening one that is neither closed nor resolved,
118+// gives failed_precondition.
109119 //
110-// The ordinary RPCs only return issues in state OPEN or CLOSED, and
111-// ListIssues with state ISSUE_STATE_PENDING is invalid_argument. Submissions
120+// The ordinary RPCs return issues in state OPEN, CLOSED or RESOLVED, and
121+// ListIssues accepts these three states; ISSUE_STATE_PENDING is
122+// invalid_argument. Submissions
112123 // from the public form wait as PendingIssue records, reachable only through
113124 // the three moderation RPCs (ListPendingIssues, ApproveIssue, RejectIssue).
114125 // These need owner rights (root, an owner session, or a token holding every
@@ -126,7 +137,10 @@ service IssueService {
126137 rpc AddIssueComment(AddIssueCommentRequest) returns (AddIssueCommentResponse);
127138 // CloseIssue closes an issue. Needs the issue:write scope.
128139 rpc CloseIssue(CloseIssueRequest) returns (CloseIssueResponse);
129- // ReopenIssue reopens an issue. Needs the issue:write scope.
140+ // ResolveIssue marks an open issue as resolved. Needs the issue:write scope.
141+ rpc ResolveIssue(ResolveIssueRequest) returns (ResolveIssueResponse);
142+ // ReopenIssue reopens a closed or resolved issue. Needs the issue:write
143+ // scope.
130144 rpc ReopenIssue(ReopenIssueRequest) returns (ReopenIssueResponse);
131145 // ListPendingIssues lists issues awaiting moderation, oldest first. Owner
132146 // rights only; anyone else gets NOT_FOUND.
@@ -196,6 +210,9 @@ message GetIssueResponse {
196210 // References are the merge requests naming the issue, ascending by number,
197211 // at most 100.
198212 repeated IssueReference references = 3;
213+ // Events are the history of the issue, oldest first, at most 500 (a server
214+ // limit). Clients interleave them with the comments by created_at.
215+ repeated IssueEvent events = 4;
199216 }
200217
201218 // AddIssueCommentRequest describes the comment to add.
@@ -228,6 +245,20 @@ message CloseIssueResponse {
228245 Issue issue = 1;
229246 }
230247
248+// ResolveIssueRequest names the issue to resolve.
249+message ResolveIssueRequest {
250+ // Repo is the repository name.
251+ string repo = 1;
252+ // Number is the issue number.
253+ int64 number = 2;
254+}
255+
256+// ResolveIssueResponse holds the issue after resolving.
257+message ResolveIssueResponse {
258+ // Issue is the resolved issue.
259+ Issue issue = 1;
260+}
261+
231262 // ReopenIssueRequest names the issue to reopen.
232263 message ReopenIssueRequest {
233264 // Repo is the repository name.
@@ -14,10 +14,14 @@ enum IssueState {
14 ISSUE_STATE_UNSPECIFIED = 0;14 ISSUE_STATE_UNSPECIFIED = 0;
15 // The issue is open.15 // The issue is open.
16 ISSUE_STATE_OPEN = 1;16 ISSUE_STATE_OPEN = 1;
17- // The issue is closed.17+ // The issue is closed without being resolved (won't fix, duplicate, ...).
18 ISSUE_STATE_CLOSED = 2;18 ISSUE_STATE_CLOSED = 2;
19 // Reserved for later use: awaiting moderation, owner-only.19 // Reserved for later use: awaiting moderation, owner-only.
20 ISSUE_STATE_PENDING = 3;20 ISSUE_STATE_PENDING = 3;
21+ // The issue was fixed: by the merge of a merge request with a closing
22+ // reference, or by hand (ResolveIssue). CLOSED means closed without being
23+ // resolved.
24+ ISSUE_STATE_RESOLVED = 4;
21 }25 }
22 26
23 // Issue is a discussion in a repository.27 // Issue is a discussion in a repository.
@@ -45,9 +49,10 @@ message Issue {
45 google.protobuf.Timestamp updated_at = 8;49 google.protobuf.Timestamp updated_at = 8;
46 // CommentCount is the number of comments on the issue.50 // CommentCount is the number of comments on the issue.
47 int32 comment_count = 9;51 int32 comment_count = 9;
48- // ClosedByMergeRequest is the number of the merge request whose merge closed52+ // ClosedByMergeRequest is the number of the merge request whose merge
49- // the issue (a closing reference such as "Fixes #N"); 0 when the issue was53+ // resolved the issue (a closing reference such as "Fixes #N"); 0 when the
50- // closed by hand or is open. Reset when the issue is reopened.54+ // issue was resolved by hand, is closed, or is open. Reset when the issue is
55+ // reopened.
51 int64 closed_by_merge_request = 10;56 int64 closed_by_merge_request = 10;
52 }57 }
53 58
@@ -86,29 +91,35 @@ message IssueComment {
86 //91 //
87 // Reading (ListIssues, GetIssue) needs the same access as reading the92 // Reading (ListIssues, GetIssue) needs the same access as reading the
88 // repository: nothing for a public one, the repo:read scope for a private one.93 // repository: nothing for a public one, the repo:read scope for a private one.
89-// Writing (CreateIssue, AddIssueComment, CloseIssue, ReopenIssue) needs the94+// Writing (CreateIssue, AddIssueComment, CloseIssue, ResolveIssue,
95+// ReopenIssue) needs the
90 // owner or a token with the issue:write scope on the repository; branch96 // owner or a token with the issue:write scope on the repository; branch
91 // prefixes do not restrict it. A caller without credentials gets97 // prefixes do not restrict it. A caller without credentials gets
92 // unauthenticated, one with an insufficient scope gets permission_denied, and98 // unauthenticated, one with an insufficient scope gets permission_denied, and
93 // one that cannot read the repository gets not_found.99 // one that cannot read the repository gets not_found.
94 //100 //
95 // A closing reference ("Fixes #N", "Closes #N", "Resolves #N") in the title,101 // A closing reference ("Fixes #N", "Closes #N", "Resolves #N") in the title,
96-// description or commit messages of a merge request closes the issue when the102+// description or commit messages of a merge request resolves the issue when the
97-// request is merged, and only then; the issue.closed event and Issue carry103+// request is merged, and only then: the issue gets the state RESOLVED, an
98-// closed_by_merge_request. Servers that report these links (GetIssueResponse.104+// issue.resolved event is sent, and Issue carries closed_by_merge_request.
99-// references, Issue.closed_by_merge_request) list "issues.links" in105+// Servers that report these links (GetIssueResponse.references,
100-// GetVersionResponse.features; older servers leave them empty.106+// Issue.closed_by_merge_request) list "issues.links" in
107+// GetVersionResponse.features; older servers leave them empty. Servers that
108+// also keep the history of issues (GetIssueResponse.events), implement
109+// ResolveIssue and the RESOLVED state list "issues.resolve".
101 //110 //
102 // Text fields are normalized to NFC and stripped of control and Unicode111 // Text fields are normalized to NFC and stripped of control and Unicode
103 // bidirectional characters (line breaks and tabs stay in bodies and comments).112 // bidirectional characters (line breaks and tabs stay in bodies and comments).
104 // The title is one line of 1 to 120 code points; body and comment text are at113 // The title is one line of 1 to 120 code points; body and comment text are at
105 // most 10,000 code points and a comment must not be blank. A text that breaks114 // most 10,000 code points and a comment must not be blank. A text that breaks
106 // a rule is rejected with invalid_argument. A comment on an issue that already115 // a rule is rejected with invalid_argument. A comment on an issue that already
107-// holds 1,000 comments gives resource_exhausted. Closing an issue that is not116+// holds 1,000 comments gives resource_exhausted. Closing or resolving an issue
108-// open, or reopening one that is not closed, gives failed_precondition.117+// that is not open, or reopening one that is neither closed nor resolved,
118+// gives failed_precondition.
109 //119 //
110-// The ordinary RPCs only return issues in state OPEN or CLOSED, and120+// The ordinary RPCs return issues in state OPEN, CLOSED or RESOLVED, and
111-// ListIssues with state ISSUE_STATE_PENDING is invalid_argument. Submissions121+// ListIssues accepts these three states; ISSUE_STATE_PENDING is
122+// invalid_argument. Submissions
112 // from the public form wait as PendingIssue records, reachable only through123 // from the public form wait as PendingIssue records, reachable only through
113 // the three moderation RPCs (ListPendingIssues, ApproveIssue, RejectIssue).124 // the three moderation RPCs (ListPendingIssues, ApproveIssue, RejectIssue).
114 // These need owner rights (root, an owner session, or a token holding every125 // These need owner rights (root, an owner session, or a token holding every
@@ -126,7 +137,10 @@ service IssueService {
126 rpc AddIssueComment(AddIssueCommentRequest) returns (AddIssueCommentResponse);137 rpc AddIssueComment(AddIssueCommentRequest) returns (AddIssueCommentResponse);
127 // CloseIssue closes an issue. Needs the issue:write scope.138 // CloseIssue closes an issue. Needs the issue:write scope.
128 rpc CloseIssue(CloseIssueRequest) returns (CloseIssueResponse);139 rpc CloseIssue(CloseIssueRequest) returns (CloseIssueResponse);
129- // ReopenIssue reopens an issue. Needs the issue:write scope.140+ // ResolveIssue marks an open issue as resolved. Needs the issue:write scope.
141+ rpc ResolveIssue(ResolveIssueRequest) returns (ResolveIssueResponse);
142+ // ReopenIssue reopens a closed or resolved issue. Needs the issue:write
143+ // scope.
130 rpc ReopenIssue(ReopenIssueRequest) returns (ReopenIssueResponse);144 rpc ReopenIssue(ReopenIssueRequest) returns (ReopenIssueResponse);
131 // ListPendingIssues lists issues awaiting moderation, oldest first. Owner145 // ListPendingIssues lists issues awaiting moderation, oldest first. Owner
132 // rights only; anyone else gets NOT_FOUND.146 // rights only; anyone else gets NOT_FOUND.
@@ -196,6 +210,9 @@ message GetIssueResponse {
196 // References are the merge requests naming the issue, ascending by number,210 // References are the merge requests naming the issue, ascending by number,
197 // at most 100.211 // at most 100.
198 repeated IssueReference references = 3;212 repeated IssueReference references = 3;
213+ // Events are the history of the issue, oldest first, at most 500 (a server
214+ // limit). Clients interleave them with the comments by created_at.
215+ repeated IssueEvent events = 4;
199 }216 }
200 217
201 // AddIssueCommentRequest describes the comment to add.218 // AddIssueCommentRequest describes the comment to add.
@@ -228,6 +245,20 @@ message CloseIssueResponse {
228 Issue issue = 1;245 Issue issue = 1;
229 }246 }
230 247
248+// ResolveIssueRequest names the issue to resolve.
249+message ResolveIssueRequest {
250+ // Repo is the repository name.
251+ string repo = 1;
252+ // Number is the issue number.
253+ int64 number = 2;
254+}
255+
256+// ResolveIssueResponse holds the issue after resolving.
257+message ResolveIssueResponse {
258+ // Issue is the resolved issue.
259+ Issue issue = 1;
260+}
261+
231 // ReopenIssueRequest names the issue to reopen.262 // ReopenIssueRequest names the issue to reopen.
232 message ReopenIssueRequest {263 message ReopenIssueRequest {
233 // Repo is the repository name.264 // Repo is the repository name.
M proto/ermite/merge_request.proto +78 -0
@@ -402,6 +402,76 @@ message ListMergeRequestsResponse {
402402 string next_page_token = 2;
403403 }
404404
405+// MergeRequestIssueState is where an issue named by a merge request is in its
406+// life. Values share their numbers with IssueState (3, awaiting moderation,
407+// is never shown here). It exists because issue.proto imports this file and
408+// cannot be imported back.
409+enum MergeRequestIssueState {
410+ // Not set. Never returned by the server.
411+ MERGE_REQUEST_ISSUE_STATE_UNSPECIFIED = 0;
412+ // The issue is open.
413+ MERGE_REQUEST_ISSUE_STATE_OPEN = 1;
414+ // The issue is closed without being resolved.
415+ MERGE_REQUEST_ISSUE_STATE_CLOSED = 2;
416+ // The issue was resolved, by a merge or by hand.
417+ MERGE_REQUEST_ISSUE_STATE_RESOLVED = 4;
418+}
419+
420+// MergeRequestIssue is an issue of the same repository that a merge request
421+// names ("#N").
422+message MergeRequestIssue {
423+ // Number is the number of the issue.
424+ int64 number = 1;
425+ // Title is the title of the issue.
426+ string title = 2;
427+ // State is where the issue is in its life.
428+ MergeRequestIssueState state = 3;
429+ // Closes says that a closing reference names the issue: merging the request
430+ // resolves it.
431+ bool closes = 4;
432+}
433+
434+// IssueEventKind is what happened in an IssueEvent.
435+enum IssueEventKind {
436+ // Not set. Never returned by the server.
437+ ISSUE_EVENT_KIND_UNSPECIFIED = 0;
438+ // A merge request started naming the issue; closes says whether with a
439+ // closing reference.
440+ ISSUE_EVENT_KIND_REFERENCED = 1;
441+ // The issue was closed without being resolved.
442+ ISSUE_EVENT_KIND_CLOSED = 2;
443+ // The issue was reopened.
444+ ISSUE_EVENT_KIND_REOPENED = 3;
445+ // The issue was resolved; merge_request is set when a merge resolved it.
446+ ISSUE_EVENT_KIND_RESOLVED = 4;
447+}
448+
449+// IssueEvent is one entry in the history of an issue. Events are append-only:
450+// a reference that later disappears does not remove its event.
451+message IssueEvent {
452+ // Id identifies the event within its issue. Opaque. Events of different
453+ // issues can share an id: key them by (issue, id).
454+ string id = 1;
455+ // Kind is what happened.
456+ IssueEventKind kind = 2;
457+ // Issue is the number of the issue.
458+ int64 issue = 3;
459+ // MergeRequest is the number of the merge request involved; 0 if none.
460+ int64 merge_request = 4;
461+ // MergeRequestTitle is the title of that merge request at read time; empty
462+ // if none.
463+ string merge_request_title = 5;
464+ // Closes says that the reference was a closing one. Only set for
465+ // ISSUE_EVENT_KIND_REFERENCED.
466+ bool closes = 6;
467+ // Actor names who caused the event, like Issue.author: the label of the
468+ // token or session. For events triggered by a merge, the label of who
469+ // merged.
470+ string actor = 7;
471+ // CreatedAt is when the event happened.
472+ google.protobuf.Timestamp created_at = 8;
473+}
474+
405475 // GetMergeRequestRequest selects a merge request.
406476 message GetMergeRequestRequest {
407477 // Repo is the repository name.
@@ -452,6 +522,14 @@ message GetMergeRequestResponse {
452522 // Verdicts are the current verdicts, one per author who gave one, ordered
453523 // by the comment that carries them, oldest first.
454524 repeated AuthorVerdict verdicts = 10;
525+ // Issues are the issues of the same repository that the merge request names,
526+ // ascending by number, at most 100. Empty when issues are off.
527+ repeated MergeRequestIssue issues = 11;
528+ // IssueEvents are the history entries that involve this merge request: the
529+ // REFERENCED and RESOLVED events of the issues it names or once named,
530+ // oldest first, at most 500. An event can name an issue that is no longer
531+ // in issues. Empty when issues are off.
532+ repeated IssueEvent issue_events = 12;
455533 }
456534
457535 // AddCommentRequest describes the comment to add.
@@ -402,6 +402,76 @@ message ListMergeRequestsResponse {
402 string next_page_token = 2;402 string next_page_token = 2;
403 }403 }
404 404
405+// MergeRequestIssueState is where an issue named by a merge request is in its
406+// life. Values share their numbers with IssueState (3, awaiting moderation,
407+// is never shown here). It exists because issue.proto imports this file and
408+// cannot be imported back.
409+enum MergeRequestIssueState {
410+ // Not set. Never returned by the server.
411+ MERGE_REQUEST_ISSUE_STATE_UNSPECIFIED = 0;
412+ // The issue is open.
413+ MERGE_REQUEST_ISSUE_STATE_OPEN = 1;
414+ // The issue is closed without being resolved.
415+ MERGE_REQUEST_ISSUE_STATE_CLOSED = 2;
416+ // The issue was resolved, by a merge or by hand.
417+ MERGE_REQUEST_ISSUE_STATE_RESOLVED = 4;
418+}
419+
420+// MergeRequestIssue is an issue of the same repository that a merge request
421+// names ("#N").
422+message MergeRequestIssue {
423+ // Number is the number of the issue.
424+ int64 number = 1;
425+ // Title is the title of the issue.
426+ string title = 2;
427+ // State is where the issue is in its life.
428+ MergeRequestIssueState state = 3;
429+ // Closes says that a closing reference names the issue: merging the request
430+ // resolves it.
431+ bool closes = 4;
432+}
433+
434+// IssueEventKind is what happened in an IssueEvent.
435+enum IssueEventKind {
436+ // Not set. Never returned by the server.
437+ ISSUE_EVENT_KIND_UNSPECIFIED = 0;
438+ // A merge request started naming the issue; closes says whether with a
439+ // closing reference.
440+ ISSUE_EVENT_KIND_REFERENCED = 1;
441+ // The issue was closed without being resolved.
442+ ISSUE_EVENT_KIND_CLOSED = 2;
443+ // The issue was reopened.
444+ ISSUE_EVENT_KIND_REOPENED = 3;
445+ // The issue was resolved; merge_request is set when a merge resolved it.
446+ ISSUE_EVENT_KIND_RESOLVED = 4;
447+}
448+
449+// IssueEvent is one entry in the history of an issue. Events are append-only:
450+// a reference that later disappears does not remove its event.
451+message IssueEvent {
452+ // Id identifies the event within its issue. Opaque. Events of different
453+ // issues can share an id: key them by (issue, id).
454+ string id = 1;
455+ // Kind is what happened.
456+ IssueEventKind kind = 2;
457+ // Issue is the number of the issue.
458+ int64 issue = 3;
459+ // MergeRequest is the number of the merge request involved; 0 if none.
460+ int64 merge_request = 4;
461+ // MergeRequestTitle is the title of that merge request at read time; empty
462+ // if none.
463+ string merge_request_title = 5;
464+ // Closes says that the reference was a closing one. Only set for
465+ // ISSUE_EVENT_KIND_REFERENCED.
466+ bool closes = 6;
467+ // Actor names who caused the event, like Issue.author: the label of the
468+ // token or session. For events triggered by a merge, the label of who
469+ // merged.
470+ string actor = 7;
471+ // CreatedAt is when the event happened.
472+ google.protobuf.Timestamp created_at = 8;
473+}
474+
405 // GetMergeRequestRequest selects a merge request.475 // GetMergeRequestRequest selects a merge request.
406 message GetMergeRequestRequest {476 message GetMergeRequestRequest {
407 // Repo is the repository name.477 // Repo is the repository name.
@@ -452,6 +522,14 @@ message GetMergeRequestResponse {
452 // Verdicts are the current verdicts, one per author who gave one, ordered522 // Verdicts are the current verdicts, one per author who gave one, ordered
453 // by the comment that carries them, oldest first.523 // by the comment that carries them, oldest first.
454 repeated AuthorVerdict verdicts = 10;524 repeated AuthorVerdict verdicts = 10;
525+ // Issues are the issues of the same repository that the merge request names,
526+ // ascending by number, at most 100. Empty when issues are off.
527+ repeated MergeRequestIssue issues = 11;
528+ // IssueEvents are the history entries that involve this merge request: the
529+ // REFERENCED and RESOLVED events of the issues it names or once named,
530+ // oldest first, at most 500. An event can name an issue that is no longer
531+ // in issues. Empty when issues are off.
532+ repeated IssueEvent issue_events = 12;
455 }533 }
456 534
457 // AddCommentRequest describes the comment to add.535 // AddCommentRequest describes the comment to add.

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