1// SPDX-License-Identifier: MIT
2
3syntax = "proto3";
4
5package ermite;
6
7import "ermite/git.proto";
8import "ermite/types.proto";
9import "google/protobuf/timestamp.proto";
10
11// MergeRequestState is where a merge request is in its life.
12enum MergeRequestState {
13 // Not set. In ListMergeRequestsRequest.state it means every state. Never
14 // returned by the server.
15 MERGE_REQUEST_STATE_UNSPECIFIED = 0;
16 // The merge request awaits review; it can be commented on, merged or closed.
17 MERGE_REQUEST_STATE_OPEN = 1;
18 // The source branch was merged into the target. Final.
19 MERGE_REQUEST_STATE_MERGED = 2;
20 // The merge request was closed without merging. Final.
21 MERGE_REQUEST_STATE_CLOSED = 3;
22}
23
24// DiffSide is the side of a diff a line comment is attached to.
25enum DiffSide {
26 // Not set. On a Comment it means the comment is general, not attached to a
27 // line. In AddCommentRequest it is valid only for a general comment.
28 DIFF_SIDE_UNSPECIFIED = 0;
29 // The old side: the line is a Line.old_number (a deletion or a context line).
30 DIFF_SIDE_OLD = 1;
31 // The new side: the line is a Line.new_number (an addition or a context
32 // line).
33 DIFF_SIDE_NEW = 2;
34}
35
36// Verdict is the judgement a reviewer gives with a comment.
37enum 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
47// MergeabilityState tells whether the head of an open merge request can be
48// merged into its target.
49enum MergeabilityState {
50 // Not set.
51 MERGEABILITY_STATE_UNSPECIFIED = 0;
52 // The server has no result yet for the current tips of both branches. It
53 // is replaced by another state when the check completes.
54 MERGEABILITY_STATE_CHECKING = 1;
55 // The target is an ancestor of the head: every mode can merge.
56 MERGEABILITY_STATE_UP_TO_DATE = 2;
57 // The target has commits the head lacks, but the head merges cleanly:
58 // squash and merge commit can merge, fast-forward cannot.
59 MERGEABILITY_STATE_BEHIND = 3;
60 // The head does not merge cleanly into the target, or the branches share no
61 // history: no mode can merge.
62 MERGEABILITY_STATE_CONFLICT = 4;
63}
64
65// Mergeability is the result of checking an open merge request against the
66// tips of its branches.
67message Mergeability {
68 // State is the result.
69 MergeabilityState state = 1;
70 // TargetId is the commit the target branch pointed to when checked. Empty
71 // while CHECKING.
72 string target_id = 2;
73 // HeadId is the commit the source branch pointed to when checked. Empty
74 // while CHECKING.
75 string head_id = 3;
76 // Behind is the number of commits of the target that the head lacks. Set
77 // only when state is BEHIND.
78 int32 behind = 4;
79 // BehindCapped says that Behind is a lower bound: the server stops counting
80 // at 1,000.
81 bool behind_capped = 5;
82 // ConflictPaths are the paths that conflict, at most 20. Set only when
83 // state is CONFLICT; empty when the branches share no history.
84 repeated string conflict_paths = 6;
85 // ConflictPathsTruncated says that more paths conflict than listed.
86 bool conflict_paths_truncated = 7;
87 // CheckedAt is when the server computed the result. Unset while CHECKING.
88 google.protobuf.Timestamp checked_at = 8;
89}
90
91// MergeRequest asks to merge one branch of a repository into another branch
92// of the same repository. It is about a branch, not about who wrote it.
93message MergeRequest {
94 // Repo is the name of the repository.
95 string repo = 1;
96 // Number identifies the merge request within its repository, starting at 1
97 // and never reused. It is written "!<number>" in messages and URLs.
98 int64 number = 2;
99 // Title is a one-line plain-text title, at most 120 characters.
100 string title = 3;
101 // Description is a plain-text description, at most 8,000 characters. It may
102 // be empty. Clients show it escaped, with line breaks kept.
103 string description = 4;
104 // SourceBranch is the short name of the branch to merge, for example
105 // "feat/x".
106 string source_branch = 5;
107 // TargetBranch is the short name of the branch to merge into, for example
108 // "main".
109 string target_branch = 6;
110 // State is where the merge request is in its life.
111 MergeRequestState state = 7;
112 // Author names who created the merge request: the label of the token that
113 // was used, "root" for the root token, or "owner" for a web session. It is
114 // informational: labels are not unique and the token may be gone.
115 string author = 8;
116 // CreatedAt is when the merge request was created.
117 google.protobuf.Timestamp created_at = 9;
118 // UpdatedAt is when the merge request last changed: title, description,
119 // state, or a comment added or resolved. Pushes to the source branch do not
120 // change it.
121 google.protobuf.Timestamp updated_at = 10;
122 // HeadId is the id of the commit the source branch points to. While the
123 // merge request is open it is read from Git at every call, so it changes
124 // when the branch is pushed; it is empty if the branch was deleted. Once
125 // merged or closed it is the head at that moment.
126 string head_id = 11;
127 // MergedCommitId is the id of the commit the target branch points to after
128 // the merge: the head for a fast-forward, the new commit for a squash or a
129 // merge commit.
130 // Empty unless state is MERGED.
131 string merged_commit_id = 12;
132 // MergeMode is how the merge request was merged. MERGE_MODE_UNSPECIFIED
133 // unless state is MERGED.
134 MergeMode merge_mode = 13;
135 // Mergeability tells whether the head can be merged into the target. Set
136 // while the merge request is OPEN, with state CHECKING until the server has
137 // a result for the current tips; never set once merged or closed. It is a
138 // hint: MergeMergeRequest checks again. Set only by servers with the
139 // "merge_requests.mergeability" feature.
140 Mergeability mergeability = 14;
141}
142
143// Comment is a note on a merge request, either general or attached to a line
144// of its diff.
145message Comment {
146 // Id identifies the comment within its merge request. Opaque.
147 string id = 1;
148 // Author names who wrote the comment, like MergeRequest.author.
149 string author = 2;
150 // Path is the path of the commented file in the diff (the new path, or the
151 // old path of a deleted file). Empty for a general comment.
152 string path = 3;
153 // Line is the 1-based line number on side. 0 for a general comment.
154 int32 line = 4;
155 // Side tells whether line counts on the old or the new side of the diff.
156 // DIFF_SIDE_UNSPECIFIED for a general comment.
157 DiffSide side = 5;
158 // CommitId is the head of the merge request when the comment was written.
159 // When it differs from the current head the line may have moved; clients
160 // may show the comment as outdated.
161 string commit_id = 6;
162 // Text is the Markdown source, at most 8,000 characters. Clients render it
163 // as Markdown without raw HTML; "^<id>" refers to a note of the same merge
164 // request. Empty when deleted is true, and possibly empty for a comment
165 // that carries a verdict.
166 string text = 7;
167 // Resolved is true once the comment was marked resolved.
168 bool resolved = 8;
169 // CreatedAt is when the comment was written.
170 google.protobuf.Timestamp created_at = 9;
171 // ReplyTo is the id of the root comment this comment replies to. Empty for
172 // a root comment. A reply has the path, line, start_line and side of its
173 // root, and its commit_id is the head when the reply was written. Threads
174 // have one level: a reply never has replies.
175 string reply_to = 10;
176 // StartLine is the 1-based first line of a range that ends at line, on the
177 // same side. 0 for a comment on a single line or a general comment.
178 int32 start_line = 11;
179 // Verdict is the judgement given with the comment. Only a root comment has
180 // one; it never changes after creation.
181 Verdict verdict = 12;
182 // EditedAt is when the text was last changed or the comment was deleted.
183 // Unset if it never was.
184 google.protobuf.Timestamp edited_at = 13;
185 // Deleted is true once the author deleted the comment. Its text is then
186 // empty; its id, anchor, replies and verdict remain.
187 bool deleted = 14;
188}
189
190// AuthorVerdict is the current verdict of one author on a merge request: the
191// one given with the author's latest comment that carries a verdict.
192message AuthorVerdict {
193 // Author names who gave the verdict, like Comment.author.
194 string author = 1;
195 // Verdict is the current verdict. Never VERDICT_UNSPECIFIED.
196 Verdict verdict = 2;
197 // CommentId is the id of the comment that carries the verdict.
198 string comment_id = 3;
199 // CommitId is the head of the merge request when the verdict was given.
200 string commit_id = 4;
201 // CreatedAt is when the verdict was given.
202 google.protobuf.Timestamp created_at = 5;
203 // Stale is true for an approval given on a commit that is not the current
204 // head. A request for changes is never stale.
205 bool stale = 6;
206}
207
208// MergeRequestService reviews and merges branches.
209//
210// Every RPC takes the repository name and, except for creation and listing,
211// the merge request number. A repository that does not exist or that the
212// caller cannot read gives not_found; so does an unknown number. Reading
213// (ListMergeRequests, GetMergeRequest) needs the same access as reading the
214// repository: nothing for a public one, the repo:read scope for a private
215// one. Writing needs a scope on the repository, given per RPC; a caller
216// without it gets permission_denied.
217//
218// Text fields follow the same rules everywhere: valid UTF-8, no control
219// characters (except the line break in description and comment text) and no
220// Unicode bidirectional override or isolate characters; title is one line of
221// at most 120 characters, description and comment text at most 8,000
222// characters. A request that breaks a rule is rejected whole with
223// invalid_argument, and the error carries a BadRequest detail naming the
224// fields.
225//
226// Servers that implement this service list "merge_requests" in
227// GetVersionResponse.features.
228service MergeRequestService {
229 // CreateMergeRequest opens a merge request. Needs the mr:write scope. Fails
230 // with invalid_argument if a text rule is broken, if a branch does not exist
231 // or if source and target are the same branch, and with already_exists if an
232 // open merge request already has the same source and target.
233 rpc CreateMergeRequest(CreateMergeRequestRequest) returns (CreateMergeRequestResponse);
234 // ListMergeRequests lists the merge requests of a repository, highest
235 // number (newest) first, optionally restricted to one state. The listed
236 // merge requests carry no diff or comments; head_id is set. Fails with
237 // not_found if the repository does not exist or is not readable.
238 rpc ListMergeRequests(ListMergeRequestsRequest) returns (ListMergeRequestsResponse);
239 // GetMergeRequest returns one merge request with its commits, its diff and
240 // its comments. The diff compares the target with the head (what the merge
241 // would bring: "target...head"), or, with since_last_read, the head the
242 // caller last marked read with the current head. Fails with not_found if
243 // the repository or the number is unknown.
244 rpc GetMergeRequest(GetMergeRequestRequest) returns (GetMergeRequestResponse);
245 // AddComment adds a general comment, a comment on a line or a range of
246 // lines of the diff, or a reply to a root comment. Needs the mr:write
247 // scope; setting a verdict also needs the mr:review scope (permission_denied
248 // without it). Fails with invalid_argument if the text breaks a rule (it may
249 // be empty only with a verdict), if path, line, start_line and side do not
250 // designate lines of the current diff of the merge request, if reply_to is
251 // not the id of a root comment that is not deleted, if a reply sets path,
252 // line, start_line, side or verdict, or if a verdict is set on a reply; and
253 // with failed_precondition if the merge request is not open. A comment with
254 // VERDICT_APPROVE is created resolved.
255 rpc AddComment(AddCommentRequest) returns (AddCommentResponse);
256 // EditComment replaces the text of a comment. Needs the mr:write scope, and
257 // only the author of the comment can edit it (permission_denied
258 // otherwise). Fails with invalid_argument if the text breaks a rule (it may
259 // be empty only for a comment with a verdict), with not_found if the comment
260 // is unknown, and with failed_precondition if the merge request is not open
261 // or the comment was deleted. Sending the current text succeeds and changes
262 // nothing.
263 rpc EditComment(EditCommentRequest) returns (EditCommentResponse);
264 // DeleteComment deletes a comment: its text is emptied and deleted is set,
265 // while its id, anchor, replies and verdict remain. Needs the mr:write
266 // scope, and only the author of the comment can delete it
267 // (permission_denied otherwise). Fails with not_found if the comment is
268 // unknown and with failed_precondition if the merge request is not open.
269 // Deleting a comment already deleted succeeds and changes nothing.
270 rpc DeleteComment(DeleteCommentRequest) returns (DeleteCommentResponse);
271 // ResolveComment marks a thread resolved or unresolved. Needs the mr:write
272 // scope. Resolution belongs to the root comment: on a reply it fails with
273 // invalid_argument. Fails with not_found if the comment is unknown and with
274 // failed_precondition if the merge request is not open. Setting the state
275 // the comment already has succeeds and changes nothing.
276 rpc ResolveComment(ResolveCommentRequest) returns (ResolveCommentResponse);
277 // CloseMergeRequest closes an open merge request without merging it. Needs
278 // the mr:write scope. Fails with failed_precondition if the merge request
279 // is not open. A closed merge request cannot be reopened: open a new one.
280 rpc CloseMergeRequest(CloseMergeRequestRequest) returns (CloseMergeRequestResponse);
281 // MergeMergeRequest merges an open merge request. Needs the mr:merge scope.
282 // The caller passes the head it reviewed: the server merges only if the
283 // source branch still points to it, so a push between review and merge is
284 // never merged unseen. Fails with invalid_argument if head_id is empty or
285 // not a commit id, and with failed_precondition if the merge request is not
286 // open, if the source branch no longer points to head_id ("branch moved,
287 // read again"), if the mode is fast-forward and the target is not an
288 // ancestor of the head (rebase and push again, or merge with squash or
289 // merge commit), if the mode is squash or merge commit and the head
290 // conflicts with the target or shares no history with it (rebase and push
291 // again), if the mode is not allowed by RepoSettings, or if a check fails
292 // and RepoSettings.failing_checks_block is set. Merges of one
293 // repository run one at a time. Repeating a call that succeeded fails with
294 // failed_precondition (not open). On success the server publishes
295 // MergeRequestMerged then Pushed for the target branch.
296 rpc MergeMergeRequest(MergeMergeRequestRequest) returns (MergeMergeRequestResponse);
297 // MarkRead records the head the caller has reviewed, for the since_last_read
298 // option of GetMergeRequest. There is one read position per merge request,
299 // the reviewer's. Needs the mr:write scope. Marking a commit that does not
300 // exist in the repository is invalid_argument.
301 rpc MarkRead(MarkReadRequest) returns (MarkReadResponse);
302}
303
304// CreateMergeRequestRequest describes the merge request to open.
305message CreateMergeRequestRequest {
306 // Repo is the repository name.
307 string repo = 1;
308 // Title is the one-line title. Required.
309 string title = 2;
310 // Description is the optional description.
311 string description = 3;
312 // SourceBranch is the short name of the branch to merge. Required; the
313 // branch must exist.
314 string source_branch = 4;
315 // TargetBranch is the short name of the branch to merge into. Empty means
316 // the default branch of the repository. The branch must exist and differ
317 // from source_branch.
318 string target_branch = 5;
319}
320
321// CreateMergeRequestResponse holds the new merge request.
322message CreateMergeRequestResponse {
323 // MergeRequest is the created merge request.
324 MergeRequest merge_request = 1;
325}
326
327// ListMergeRequestsRequest selects a page of merge requests.
328message ListMergeRequestsRequest {
329 // Repo is the repository name.
330 string repo = 1;
331 // State restricts the result to one state. MERGE_REQUEST_STATE_UNSPECIFIED
332 // means every state.
333 MergeRequestState state = 2;
334 // PageSize is the maximum number of merge requests to return. 0 means the
335 // server default; values above the server maximum are lowered to it.
336 int32 page_size = 3;
337 // PageToken is the next_page_token of a previous response, to continue the
338 // same listing. Empty for the first page. Other fields must be unchanged.
339 string page_token = 4;
340}
341
342// ListMergeRequestsResponse is one page of merge requests.
343message ListMergeRequestsResponse {
344 // MergeRequests are the merge requests of this page, highest number first.
345 repeated MergeRequest merge_requests = 1;
346 // NextPageToken is passed as page_token to get the next page. Empty when
347 // there is no next page.
348 string next_page_token = 2;
349}
350
351// GetMergeRequestRequest selects a merge request.
352message GetMergeRequestRequest {
353 // Repo is the repository name.
354 string repo = 1;
355 // Number is the merge request number.
356 int64 number = 2;
357 // SinceLastRead asks for the diff between the head recorded by MarkRead and
358 // the current head instead of the diff against the target. It works after a
359 // force-push as long as the recorded commit still exists in the repository.
360 // When nothing was marked read, or the recorded commit is gone, the response
361 // holds the diff against the target and says so in last_read_id and
362 // last_read_lost.
363 bool since_last_read = 3;
364}
365
366// GetMergeRequestResponse holds a merge request with its review data.
367message GetMergeRequestResponse {
368 // MergeRequest is the merge request.
369 MergeRequest merge_request = 1;
370 // Commits are the commits of the head that are not in the target
371 // ("target..head"), newest first. At most 100 are returned (a server limit
372 // that may change; rely on commits_truncated, not on the number). Empty when
373 // the source branch was deleted.
374 repeated Commit commits = 2;
375 // CommitsTruncated is true when the branch has more commits than commits
376 // holds.
377 bool commits_truncated = 3;
378 // DiffBaseId is the id of the commit the diff compares from: the merge base
379 // of the target and the head, or the last read head when since_last_read
380 // applied. Empty when the source branch was deleted; diff is then empty.
381 string diff_base_id = 4;
382 // Stats are the totals of the diff, including files that it omits or
383 // truncates.
384 DiffStats stats = 5;
385 // Diff is the structured diff from diff_base_id to the head, with the same
386 // limits and truncated flags as GetCommitResponse.diff.
387 Diff diff = 6;
388 // Comments are every comment of the merge request, oldest first.
389 repeated Comment comments = 7;
390 // LastReadId is the head recorded by MarkRead, whether or not
391 // since_last_read was set. Empty when nothing was marked read.
392 string last_read_id = 8;
393 // LastReadLost is true when since_last_read was set, a head was recorded,
394 // but that commit no longer exists in the repository: the diff is then the
395 // diff against the target. A client should tell the reader to review the
396 // whole change again.
397 bool last_read_lost = 9;
398 // Verdicts are the current verdicts, one per author who gave one, ordered
399 // by the comment that carries them, oldest first.
400 repeated AuthorVerdict verdicts = 10;
401}
402
403// AddCommentRequest describes the comment to add.
404message AddCommentRequest {
405 // Repo is the repository name.
406 string repo = 1;
407 // Number is the merge request number.
408 int64 number = 2;
409 // Path is the file the comment is attached to, as given by the diff of the
410 // merge request: FileDiff.new_path, or old_path for a deleted file. Empty
411 // for a general comment; then line must be 0 and side unspecified.
412 string path = 3;
413 // Line is the 1-based line number on side. It must be the old_number or
414 // new_number of a Line of a hunk of the file; a line outside the hunks is
415 // invalid_argument. 0 for a general comment.
416 int32 line = 4;
417 // Side is the side line counts on. Required when path is set.
418 DiffSide side = 5;
419 // Text is the Markdown source. Required, except when verdict is set.
420 string text = 6;
421 // ReplyTo is the id of the root comment to reply to. Empty for a new root
422 // comment. A reply takes the anchor of its root: path, line, start_line,
423 // side and verdict must then be unset.
424 string reply_to = 7;
425 // StartLine is the 1-based first line of a range ending at line. 0 for a
426 // single line. Needs path; it must be lower than line, and both ends must
427 // be lines of the diff on side.
428 int32 start_line = 8;
429 // Verdict is the judgement to give. Needs the mr:review scope and a root
430 // comment. VERDICT_UNSPECIFIED for a plain comment.
431 Verdict verdict = 9;
432}
433
434// AddCommentResponse holds the new comment.
435message AddCommentResponse {
436 // Comment is the created comment.
437 Comment comment = 1;
438}
439
440// EditCommentRequest describes the new text of a comment.
441message EditCommentRequest {
442 // Repo is the repository name.
443 string repo = 1;
444 // Number is the merge request number.
445 int64 number = 2;
446 // CommentId is the Comment.id to edit.
447 string comment_id = 3;
448 // Text is the new Markdown source. Required, except for a comment that
449 // carries a verdict.
450 string text = 4;
451}
452
453// EditCommentResponse holds the comment after the edit.
454message EditCommentResponse {
455 // Comment is the updated comment.
456 Comment comment = 1;
457}
458
459// DeleteCommentRequest names the comment to delete.
460message DeleteCommentRequest {
461 // Repo is the repository name.
462 string repo = 1;
463 // Number is the merge request number.
464 int64 number = 2;
465 // CommentId is the Comment.id to delete.
466 string comment_id = 3;
467}
468
469// DeleteCommentResponse holds the comment after the deletion.
470message DeleteCommentResponse {
471 // Comment is the deleted comment.
472 Comment comment = 1;
473}
474
475// ResolveCommentRequest changes the resolved state of a comment.
476message ResolveCommentRequest {
477 // Repo is the repository name.
478 string repo = 1;
479 // Number is the merge request number.
480 int64 number = 2;
481 // CommentId is the Comment.id to change.
482 string comment_id = 3;
483 // Resolved is the new state: true to resolve, false to unresolve.
484 bool resolved = 4;
485}
486
487// ResolveCommentResponse holds the comment after the change.
488message ResolveCommentResponse {
489 // Comment is the updated comment.
490 Comment comment = 1;
491}
492
493// CloseMergeRequestRequest names the merge request to close.
494message CloseMergeRequestRequest {
495 // Repo is the repository name.
496 string repo = 1;
497 // Number is the merge request number.
498 int64 number = 2;
499}
500
501// CloseMergeRequestResponse holds the merge request after closing.
502message CloseMergeRequestResponse {
503 // MergeRequest is the closed merge request.
504 MergeRequest merge_request = 1;
505}
506
507// MergeMergeRequestRequest describes the merge to perform.
508message MergeMergeRequestRequest {
509 // Repo is the repository name.
510 string repo = 1;
511 // Number is the merge request number.
512 int64 number = 2;
513 // Mode is how to merge. MERGE_MODE_UNSPECIFIED means fast-forward. The mode
514 // must be in RepoSettings.allowed_merge_modes of the repository.
515 MergeMode mode = 3;
516 // HeadId is the full id of the commit the caller reviewed, that is the
517 // MergeRequest.head_id it was shown. Required. The merge happens only if the
518 // source branch still points to it.
519 string head_id = 4;
520 // DeleteSourceBranch deletes the source branch after the merge. The merge
521 // is refused before anything changes when the source branch is the default
522 // branch of the repository or the source or target of another open merge
523 // request. Servers without the "merge_requests.delete_branch" feature ignore
524 // it.
525 bool delete_source_branch = 5;
526}
527
528// MergeMergeRequestResponse holds the merged merge request.
529message MergeMergeRequestResponse {
530 // MergeRequest is the merge request after the merge: state MERGED,
531 // merged_commit_id set to the new tip of the target branch and merge_mode
532 // set to the mode used.
533 MergeRequest merge_request = 1;
534 // SourceBranchDeleted is true when delete_source_branch was set: the server
535 // deletes the branch after answering. The deletion is skipped, and the
536 // branch kept, if the branch moved or another open merge request uses it
537 // by then; the merge stands either way. Watch reports the deletion with a
538 // Pushed event for the source branch.
539 bool source_branch_deleted = 2;
540}
541
542// MarkReadRequest records a read position.
543message MarkReadRequest {
544 // Repo is the repository name.
545 string repo = 1;
546 // Number is the merge request number.
547 int64 number = 2;
548 // HeadId is the full id of the commit the caller has reviewed, usually the
549 // MergeRequest.head_id it was shown. Empty means the current head of the
550 // source branch.
551 string head_id = 3;
552}
553
554// MarkReadResponse is empty.
555message MarkReadResponse {}