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// 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.
38message MergeRequest {
39 // Repo is the name of the repository.
40 string repo = 1;
41 // Number identifies the merge request within its repository, starting at 1
42 // and never reused. It is written "!<number>" in messages and URLs.
43 int64 number = 2;
44 // Title is a one-line plain-text title, at most 120 characters.
45 string title = 3;
46 // Description is a plain-text description, at most 8,000 characters. It may
47 // be empty. Clients show it escaped, with line breaks kept.
48 string description = 4;
49 // SourceBranch is the short name of the branch to merge, for example
50 // "feat/x".
51 string source_branch = 5;
52 // TargetBranch is the short name of the branch to merge into, for example
53 // "main".
54 string target_branch = 6;
55 // State is where the merge request is in its life.
56 MergeRequestState state = 7;
57 // Author names who created the merge request: the label of the token that
58 // was used, "root" for the root token, or "owner" for a web session. It is
59 // informational: labels are not unique and the token may be gone.
60 string author = 8;
61 // CreatedAt is when the merge request was created.
62 google.protobuf.Timestamp created_at = 9;
63 // UpdatedAt is when the merge request last changed: title, description,
64 // state, or a comment added or resolved. Pushes to the source branch do not
65 // change it.
66 google.protobuf.Timestamp updated_at = 10;
67 // HeadId is the id of the commit the source branch points to. While the
68 // merge request is open it is read from Git at every call, so it changes
69 // when the branch is pushed; it is empty if the branch was deleted. Once
70 // merged or closed it is the head at that moment.
71 string head_id = 11;
72 // MergedCommitId is the id of the commit the target branch points to after
73 // the merge: the head for a fast-forward, the new commit for a squash.
74 // Empty unless state is MERGED.
75 string merged_commit_id = 12;
76 // MergeMode is how the merge request was merged. MERGE_MODE_UNSPECIFIED
77 // unless state is MERGED.
78 MergeMode merge_mode = 13;
79}
80
81// Comment is a note on a merge request, either general or attached to a line
82// of its diff.
83message Comment {
84 // Id identifies the comment within its merge request. Opaque.
85 string id = 1;
86 // Author names who wrote the comment, like MergeRequest.author.
87 string author = 2;
88 // Path is the path of the commented file in the diff (the new path, or the
89 // old path of a deleted file). Empty for a general comment.
90 string path = 3;
91 // Line is the 1-based line number on side. 0 for a general comment.
92 int32 line = 4;
93 // Side tells whether line counts on the old or the new side of the diff.
94 // DIFF_SIDE_UNSPECIFIED for a general comment.
95 DiffSide side = 5;
96 // CommitId is the head of the merge request when the comment was written.
97 // When it differs from the current head the line may have moved; clients
98 // may show the comment as outdated.
99 string commit_id = 6;
100 // Text is the plain-text content, at most 8,000 characters. Clients show it
101 // escaped, with line breaks kept.
102 string text = 7;
103 // Resolved is true once the comment was marked resolved.
104 bool resolved = 8;
105 // CreatedAt is when the comment was written.
106 google.protobuf.Timestamp created_at = 9;
107}
108
109// MergeRequestService reviews and merges branches.
110//
111// Every RPC takes the repository name and, except for creation and listing,
112// the merge request number. A repository that does not exist or that the
113// caller cannot read gives not_found; so does an unknown number. Reading
114// (ListMergeRequests, GetMergeRequest) needs the same access as reading the
115// repository: nothing for a public one, the repo:read scope for a private
116// one. Writing needs a scope on the repository, given per RPC; a caller
117// without it gets permission_denied.
118//
119// Text fields follow the same rules everywhere: valid UTF-8, no control
120// characters (except the line break in description and comment text) and no
121// Unicode bidirectional override or isolate characters; title is one line of
122// at most 120 characters, description and comment text at most 8,000
123// characters. A request that breaks a rule is rejected whole with
124// invalid_argument, and the error carries a BadRequest detail naming the
125// fields.
126//
127// Servers that implement this service list "merge_requests" in
128// GetVersionResponse.features.
129service MergeRequestService {
130 // CreateMergeRequest opens a merge request. Needs the mr:write scope. Fails
131 // with invalid_argument if a text rule is broken, if a branch does not exist
132 // or if source and target are the same branch, and with already_exists if an
133 // open merge request already has the same source and target.
134 rpc CreateMergeRequest(CreateMergeRequestRequest) returns (CreateMergeRequestResponse);
135 // ListMergeRequests lists the merge requests of a repository, highest
136 // number (newest) first, optionally restricted to one state. The listed
137 // merge requests carry no diff or comments; head_id is set. Fails with
138 // not_found if the repository does not exist or is not readable.
139 rpc ListMergeRequests(ListMergeRequestsRequest) returns (ListMergeRequestsResponse);
140 // GetMergeRequest returns one merge request with its commits, its diff and
141 // its comments. The diff compares the target with the head (what the merge
142 // would bring: "target...head"), or, with since_last_read, the head the
143 // caller last marked read with the current head. Fails with not_found if
144 // the repository or the number is unknown.
145 rpc GetMergeRequest(GetMergeRequestRequest) returns (GetMergeRequestResponse);
146 // AddComment adds a general comment or a comment on a line of the diff.
147 // Needs the mr:write scope. Fails with invalid_argument if the text breaks
148 // a rule or if path, line and side do not designate a line of the current
149 // diff of the merge request, and with failed_precondition if the merge
150 // request is not open.
151 rpc AddComment(AddCommentRequest) returns (AddCommentResponse);
152 // ResolveComment marks a comment resolved or unresolved. Needs the mr:write
153 // scope. Fails with not_found if the comment is unknown and with
154 // failed_precondition if the merge request is not open. Setting the state
155 // the comment already has succeeds and changes nothing.
156 rpc ResolveComment(ResolveCommentRequest) returns (ResolveCommentResponse);
157 // CloseMergeRequest closes an open merge request without merging it. Needs
158 // the mr:write scope. Fails with failed_precondition if the merge request
159 // is not open. A closed merge request cannot be reopened: open a new one.
160 rpc CloseMergeRequest(CloseMergeRequestRequest) returns (CloseMergeRequestResponse);
161 // MergeMergeRequest merges an open merge request. Needs the mr:merge scope.
162 // The caller passes the head it reviewed: the server merges only if the
163 // source branch still points to it, so a push between review and merge is
164 // never merged unseen. Fails with invalid_argument if head_id is empty or
165 // not a commit id, and with failed_precondition if the merge request is not
166 // open, if the source branch no longer points to head_id ("branch moved,
167 // read again"), if the target is not an ancestor of the head (rebase and
168 // push again), if the mode is not allowed by RepoSettings, or if a check
169 // fails and RepoSettings.failing_checks_block is set. Merges of one
170 // repository run one at a time. Repeating a call that succeeded fails with
171 // failed_precondition (not open). On success the server publishes
172 // MergeRequestMerged then Pushed for the target branch.
173 rpc MergeMergeRequest(MergeMergeRequestRequest) returns (MergeMergeRequestResponse);
174 // MarkRead records the head the caller has reviewed, for the since_last_read
175 // option of GetMergeRequest. There is one read position per merge request,
176 // the reviewer's. Needs the mr:write scope. Marking a commit that does not
177 // exist in the repository is invalid_argument.
178 rpc MarkRead(MarkReadRequest) returns (MarkReadResponse);
179}
180
181// CreateMergeRequestRequest describes the merge request to open.
182message CreateMergeRequestRequest {
183 // Repo is the repository name.
184 string repo = 1;
185 // Title is the one-line title. Required.
186 string title = 2;
187 // Description is the optional description.
188 string description = 3;
189 // SourceBranch is the short name of the branch to merge. Required; the
190 // branch must exist.
191 string source_branch = 4;
192 // TargetBranch is the short name of the branch to merge into. Empty means
193 // the default branch of the repository. The branch must exist and differ
194 // from source_branch.
195 string target_branch = 5;
196}
197
198// CreateMergeRequestResponse holds the new merge request.
199message CreateMergeRequestResponse {
200 // MergeRequest is the created merge request.
201 MergeRequest merge_request = 1;
202}
203
204// ListMergeRequestsRequest selects a page of merge requests.
205message ListMergeRequestsRequest {
206 // Repo is the repository name.
207 string repo = 1;
208 // State restricts the result to one state. MERGE_REQUEST_STATE_UNSPECIFIED
209 // means every state.
210 MergeRequestState state = 2;
211 // PageSize is the maximum number of merge requests to return. 0 means the
212 // server default; values above the server maximum are lowered to it.
213 int32 page_size = 3;
214 // PageToken is the next_page_token of a previous response, to continue the
215 // same listing. Empty for the first page. Other fields must be unchanged.
216 string page_token = 4;
217}
218
219// ListMergeRequestsResponse is one page of merge requests.
220message ListMergeRequestsResponse {
221 // MergeRequests are the merge requests of this page, highest number first.
222 repeated MergeRequest merge_requests = 1;
223 // NextPageToken is passed as page_token to get the next page. Empty when
224 // there is no next page.
225 string next_page_token = 2;
226}
227
228// GetMergeRequestRequest selects a merge request.
229message GetMergeRequestRequest {
230 // Repo is the repository name.
231 string repo = 1;
232 // Number is the merge request number.
233 int64 number = 2;
234 // SinceLastRead asks for the diff between the head recorded by MarkRead and
235 // the current head instead of the diff against the target. It works after a
236 // force-push as long as the recorded commit still exists in the repository.
237 // When nothing was marked read, or the recorded commit is gone, the response
238 // holds the diff against the target and says so in last_read_id and
239 // last_read_lost.
240 bool since_last_read = 3;
241}
242
243// GetMergeRequestResponse holds a merge request with its review data.
244message GetMergeRequestResponse {
245 // MergeRequest is the merge request.
246 MergeRequest merge_request = 1;
247 // Commits are the commits of the head that are not in the target
248 // ("target..head"), newest first. At most 100 are returned (a server limit
249 // that may change; rely on commits_truncated, not on the number). Empty when
250 // the source branch was deleted.
251 repeated Commit commits = 2;
252 // CommitsTruncated is true when the branch has more commits than commits
253 // holds.
254 bool commits_truncated = 3;
255 // DiffBaseId is the id of the commit the diff compares from: the merge base
256 // of the target and the head, or the last read head when since_last_read
257 // applied. Empty when the source branch was deleted; diff is then empty.
258 string diff_base_id = 4;
259 // Stats are the totals of the diff, including files that it omits or
260 // truncates.
261 DiffStats stats = 5;
262 // Diff is the structured diff from diff_base_id to the head, with the same
263 // limits and truncated flags as GetCommitResponse.diff.
264 Diff diff = 6;
265 // Comments are every comment of the merge request, oldest first.
266 repeated Comment comments = 7;
267 // LastReadId is the head recorded by MarkRead, whether or not
268 // since_last_read was set. Empty when nothing was marked read.
269 string last_read_id = 8;
270 // LastReadLost is true when since_last_read was set, a head was recorded,
271 // but that commit no longer exists in the repository: the diff is then the
272 // diff against the target. A client should tell the reader to review the
273 // whole change again.
274 bool last_read_lost = 9;
275}
276
277// AddCommentRequest describes the comment to add.
278message AddCommentRequest {
279 // Repo is the repository name.
280 string repo = 1;
281 // Number is the merge request number.
282 int64 number = 2;
283 // Path is the file the comment is attached to, as given by the diff of the
284 // merge request: FileDiff.new_path, or old_path for a deleted file. Empty
285 // for a general comment; then line must be 0 and side unspecified.
286 string path = 3;
287 // Line is the 1-based line number on side. It must be the old_number or
288 // new_number of a Line of a hunk of the file; a line outside the hunks is
289 // invalid_argument. 0 for a general comment.
290 int32 line = 4;
291 // Side is the side line counts on. Required when path is set.
292 DiffSide side = 5;
293 // Text is the plain-text content. Required.
294 string text = 6;
295}
296
297// AddCommentResponse holds the new comment.
298message AddCommentResponse {
299 // Comment is the created comment.
300 Comment comment = 1;
301}
302
303// ResolveCommentRequest changes the resolved state of a comment.
304message ResolveCommentRequest {
305 // Repo is the repository name.
306 string repo = 1;
307 // Number is the merge request number.
308 int64 number = 2;
309 // CommentId is the Comment.id to change.
310 string comment_id = 3;
311 // Resolved is the new state: true to resolve, false to unresolve.
312 bool resolved = 4;
313}
314
315// ResolveCommentResponse holds the comment after the change.
316message ResolveCommentResponse {
317 // Comment is the updated comment.
318 Comment comment = 1;
319}
320
321// CloseMergeRequestRequest names the merge request to close.
322message CloseMergeRequestRequest {
323 // Repo is the repository name.
324 string repo = 1;
325 // Number is the merge request number.
326 int64 number = 2;
327}
328
329// CloseMergeRequestResponse holds the merge request after closing.
330message CloseMergeRequestResponse {
331 // MergeRequest is the closed merge request.
332 MergeRequest merge_request = 1;
333}
334
335// MergeMergeRequestRequest describes the merge to perform.
336message MergeMergeRequestRequest {
337 // Repo is the repository name.
338 string repo = 1;
339 // Number is the merge request number.
340 int64 number = 2;
341 // Mode is how to merge. MERGE_MODE_UNSPECIFIED means fast-forward. The mode
342 // must be in RepoSettings.allowed_merge_modes of the repository.
343 MergeMode mode = 3;
344 // HeadId is the full id of the commit the caller reviewed, that is the
345 // MergeRequest.head_id it was shown. Required. The merge happens only if the
346 // source branch still points to it.
347 string head_id = 4;
348 // DeleteSourceBranch deletes the source branch after the merge. The merge
349 // is refused before anything changes when the source branch is the default
350 // branch of the repository or the source or target of another open merge
351 // request. Servers without the "merge_requests.delete_branch" feature ignore
352 // it.
353 bool delete_source_branch = 5;
354}
355
356// MergeMergeRequestResponse holds the merged merge request.
357message MergeMergeRequestResponse {
358 // MergeRequest is the merge request after the merge: state MERGED,
359 // merged_commit_id set to the new tip of the target branch and merge_mode
360 // set to the mode used.
361 MergeRequest merge_request = 1;
362 // SourceBranchDeleted is true when delete_source_branch was set and the
363 // branch is gone. It is false when the deletion failed after the merge (the
364 // branch moved in between, say): the merge stands and the branch is kept.
365 bool source_branch_deleted = 2;
366}
367
368// MarkReadRequest records a read position.
369message MarkReadRequest {
370 // Repo is the repository name.
371 string repo = 1;
372 // Number is the merge request number.
373 int64 number = 2;
374 // HeadId is the full id of the commit the caller has reviewed, usually the
375 // MergeRequest.head_id it was shown. Empty means the current head of the
376 // source branch.
377 string head_id = 3;
378}
379
380// MarkReadResponse is empty.
381message MarkReadResponse {}