1package gerrit
2
3import (
4	"errors"
5	"fmt"
6	"io/ioutil"
7	"net/http"
8)
9
10// ChangesService contains Change related REST endpoints
11//
12// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api-changes.html
13type ChangesService struct {
14	client *Client
15}
16
17// WebLinkInfo entity describes a link to an external site.
18//
19// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api-changes.html#web-link-info
20type WebLinkInfo struct {
21	Name     string `json:"name"`
22	URL      string `json:"url"`
23	ImageURL string `json:"image_url"`
24}
25
26// GitPersonInfo entity contains information about the author/committer of a commit.
27type GitPersonInfo struct {
28	Name  string    `json:"name"`
29	Email string    `json:"email"`
30	Date  Timestamp `json:"date"`
31	TZ    int       `json:"tz"`
32}
33
34// NotifyInfo entity contains detailed information about who should be
35// notified about an update
36type NotifyInfo struct {
37	Accounts []AccountInfo `json:"accounts"`
38}
39
40// AbandonInput entity contains information for abandoning a change.
41type AbandonInput struct {
42	Message       string       `json:"message,omitempty"`
43	Notify        string       `json:"notify"`
44	NotifyDetails []NotifyInfo `json:"notify_details"`
45}
46
47// ApprovalInfo entity contains information about an approval from a user for a label on a change.
48type ApprovalInfo struct {
49	AccountInfo
50	Value int    `json:"value,omitempty"`
51	Date  string `json:"date,omitempty"`
52}
53
54// ChangeEditInput entity contains information for restoring a path within change edit.
55type ChangeEditInput struct {
56	RestorePath string `json:"restore_path,omitempty"`
57	OldPath     string `json:"old_path,omitempty"`
58	NewPath     string `json:"new_path,omitempty"`
59}
60
61// ChangeEditMessageInput entity contains information for changing the commit message within a change edit.
62type ChangeEditMessageInput struct {
63	Message string `json:"message"`
64}
65
66// ChangeMessageInfo entity contains information about a message attached to a change.
67type ChangeMessageInfo struct {
68	ID             string      `json:"id"`
69	Author         AccountInfo `json:"author,omitempty"`
70	Date           Timestamp   `json:"date"`
71	Message        string      `json:"message"`
72	Tag            string      `json:"tag,omitempty"`
73	RevisionNumber int         `json:"_revision_number,omitempty"`
74}
75
76// CherryPickInput entity contains information for cherry-picking a change to a new branch.
77type CherryPickInput struct {
78	Message     string `json:"message"`
79	Destination string `json:"destination"`
80}
81
82// CommentRange entity describes the range of an inline comment.
83type CommentRange struct {
84	StartLine      int `json:"start_line"`
85	StartCharacter int `json:"start_character"`
86	EndLine        int `json:"end_line"`
87	EndCharacter   int `json:"end_character"`
88}
89
90// DiffFileMetaInfo entity contains meta information about a file diff
91type DiffFileMetaInfo struct {
92	Name        string        `json:"name"`
93	ContentType string        `json:"content_type"`
94	Lines       int           `json:"lines"`
95	WebLinks    []WebLinkInfo `json:"web_links,omitempty"`
96}
97
98// DiffWebLinkInfo entity describes a link on a diff screen to an external site.
99type DiffWebLinkInfo struct {
100	Name                     string `json:"name"`
101	URL                      string `json:"url"`
102	ImageURL                 string `json:"image_url"`
103	ShowOnSideBySideDiffView bool   `json:"show_on_side_by_side_diff_view"`
104	ShowOnUnifiedDiffView    bool   `json:"show_on_unified_diff_view"`
105}
106
107// FetchInfo entity contains information about how to fetch a patch set via a certain protocol.
108type FetchInfo struct {
109	URL      string            `json:"url"`
110	Ref      string            `json:"ref"`
111	Commands map[string]string `json:"commands,omitempty"`
112}
113
114// FixInput entity contains options for fixing commits using the fix change endpoint.
115type FixInput struct {
116	DeletePatchSetIfCommitMissing bool   `json:"delete_patch_set_if_commit_missing"`
117	ExpectMergedAs                string `json:"expect_merged_as"`
118}
119
120// GroupBaseInfo entity contains base information about the group.
121type GroupBaseInfo struct {
122	ID   int    `json:"id"`
123	Name string `json:"name"`
124}
125
126// IncludedInInfo entity contains information about the branches a change was merged into and tags it was tagged with.
127type IncludedInInfo struct {
128	Branches []string          `json:"branches"`
129	Tags     []string          `json:"tags"`
130	External map[string]string `json:"external,omitempty"`
131}
132
133// ProblemInfo entity contains a description of a potential consistency problem with a change.
134// These are not related to the code review process, but rather indicate some inconsistency in Gerrit’s database or repository metadata related to the enclosing change.
135type ProblemInfo struct {
136	Message string `json:"message"`
137	Status  string `json:"status,omitempty"`
138	Outcome string `json:"outcome,omitempty"`
139}
140
141// RebaseInput entity contains information for changing parent when rebasing.
142type RebaseInput struct {
143	Base string `json:"base,omitempty"`
144}
145
146// RestoreInput entity contains information for restoring a change.
147type RestoreInput struct {
148	Message string `json:"message,omitempty"`
149}
150
151// RevertInput entity contains information for reverting a change.
152type RevertInput struct {
153	Message string `json:"message,omitempty"`
154}
155
156// ReviewInfo entity contains information about a review.
157type ReviewInfo struct {
158	Labels map[string]int `json:"labels"`
159}
160
161// ReviewResult entity contains information regarding the updates that were
162// made to a review.
163type ReviewResult struct {
164	ReviewInfo
165	Reviewers map[string]AddReviewerResult `json:"reviewers,omitempty"`
166	Ready     bool                         `json:"ready,omitempty"`
167}
168
169// TopicInput entity contains information for setting a topic.
170type TopicInput struct {
171	Topic string `json:"topic,omitempty"`
172}
173
174// SubmitRecord entity describes results from a submit_rule.
175type SubmitRecord struct {
176	Status       string                            `json:"status"`
177	Ok           map[string]map[string]AccountInfo `json:"ok,omitempty"`
178	Reject       map[string]map[string]AccountInfo `json:"reject,omitempty"`
179	Need         map[string]interface{}            `json:"need,omitempty"`
180	May          map[string]map[string]AccountInfo `json:"may,omitempty"`
181	Impossible   map[string]interface{}            `json:"impossible,omitempty"`
182	ErrorMessage string                            `json:"error_message,omitempty"`
183}
184
185// SubmitInput entity contains information for submitting a change.
186type SubmitInput struct {
187	WaitForMerge bool `json:"wait_for_merge"`
188}
189
190// SubmitInfo entity contains information about the change status after submitting.
191type SubmitInfo struct {
192	Status     string `json:"status"`
193	OnBehalfOf string `json:"on_behalf_of,omitempty"`
194}
195
196// RuleInput entity contains information to test a Prolog rule.
197type RuleInput struct {
198	Rule    string `json:"rule"`
199	Filters string `json:"filters,omitempty"`
200}
201
202// ReviewerInput entity contains information for adding a reviewer to a change.
203type ReviewerInput struct {
204	Reviewer  string `json:"reviewer"`
205	Confirmed bool   `json:"confirmed,omitempty"`
206}
207
208// ReviewInput entity contains information for adding a review to a revision.
209type ReviewInput struct {
210	Message               string                         `json:"message,omitempty"`
211	Tag                   string                         `json:"tag,omitempty"`
212	Labels                map[string]string              `json:"labels,omitempty"`
213	Comments              map[string][]CommentInput      `json:"comments,omitempty"`
214	RobotComments         map[string][]RobotCommentInput `json:"robot_comments,omitempty"`
215	StrictLabels          bool                           `json:"strict_labels,omitempty"`
216	Drafts                string                         `json:"drafts,omitempty"`
217	Notify                string                         `json:"notify,omitempty"`
218	OmitDuplicateComments bool                           `json:"omit_duplicate_comments,omitempty"`
219	OnBehalfOf            string                         `json:"on_behalf_of,omitempty"`
220}
221
222// RelatedChangeAndCommitInfo entity contains information about a related change and commit.
223type RelatedChangeAndCommitInfo struct {
224	ChangeID              string     `json:"change_id,omitempty"`
225	Commit                CommitInfo `json:"commit"`
226	ChangeNumber          int        `json:"_change_number,omitempty"`
227	RevisionNumber        int        `json:"_revision_number,omitempty"`
228	CurrentRevisionNumber int        `json:"_current_revision_number,omitempty"`
229	Status                string     `json:"status,omitempty"`
230}
231
232// DiffContent entity contains information about the content differences in a file.
233type DiffContent struct {
234	A      []string          `json:"a,omitempty"`
235	B      []string          `json:"b,omitempty"`
236	AB     []string          `json:"ab,omitempty"`
237	EditA  DiffIntralineInfo `json:"edit_a,omitempty"`
238	EditB  DiffIntralineInfo `json:"edit_b,omitempty"`
239	Skip   int               `json:"skip,omitempty"`
240	Common bool              `json:"common,omitempty"`
241}
242
243// CommentInput entity contains information for creating an inline comment.
244type CommentInput struct {
245	ID        string        `json:"id,omitempty"`
246	Path      string        `json:"path,omitempty"`
247	Side      string        `json:"side,omitempty"`
248	Line      int           `json:"line,omitempty"`
249	Range     *CommentRange `json:"range,omitempty"`
250	InReplyTo string        `json:"in_reply_to,omitempty"`
251	Updated   *Timestamp    `json:"updated,omitempty"`
252	Message   string        `json:"message,omitempty"`
253}
254
255// RobotCommentInput entity contains information for creating an inline robot comment.
256// https://gerrit-review.googlesource.com/Documentation/rest-api-changes.html#robot-comment-input
257type RobotCommentInput struct {
258	CommentInput
259
260	// The ID of the robot that generated this comment.
261	RobotID string `json:"robot_id"`
262	// An ID of the run of the robot.
263	RobotRunID string `json:"robot_run_id"`
264	// URL to more information.
265	URL string `json:"url,omitempty"`
266	// Robot specific properties as map that maps arbitrary keys to values.
267	Properties *map[string]*string `json:"properties,omitempty"`
268	// Suggested fixes for this robot comment as a list of FixSuggestionInfo
269	// entities.
270	FixSuggestions *FixSuggestionInfo `json:"fix_suggestions,omitempty"`
271}
272
273// RobotCommentInfo entity contains information about a robot inline comment
274// RobotCommentInfo has the same fields as CommentInfo. In addition RobotCommentInfo has the following fields:
275// https://gerrit-review.googlesource.com/Documentation/rest-api-changes.html#robot-comment-info
276type RobotCommentInfo struct {
277	CommentInfo
278
279	// The ID of the robot that generated this comment.
280	RobotID string `json:"robot_id"`
281	// An ID of the run of the robot.
282	RobotRunID string `json:"robot_run_id"`
283	// URL to more information.
284	URL string `json:"url,omitempty"`
285	// Robot specific properties as map that maps arbitrary keys to values.
286	Properties map[string]string `json:"properties,omitempty"`
287	// Suggested fixes for this robot comment as a list of FixSuggestionInfo
288	// entities.
289	FixSuggestions *FixSuggestionInfo `json:"fix_suggestions,omitempty"`
290}
291
292// FixSuggestionInfo entity represents a suggested fix.
293// https://gerrit-review.googlesource.com/Documentation/rest-api-changes.html#fix-suggestion-info
294type FixSuggestionInfo struct {
295	// The UUID of the suggested fix. It will be generated automatically and
296	// hence will be ignored if it’s set for input objects.
297	FixID string `json:"fix_id"`
298	// A description of the suggested fix.
299	Description string `json:"description"`
300	// A list of FixReplacementInfo entities indicating how the content of one or
301	// several files should be modified. Within a file, they should refer to
302	// non-overlapping regions.
303	Replacements FixReplacementInfo `json:"replacements"`
304}
305
306// FixReplacementInfo entity describes how the content of a file should be replaced by another content.
307// https://gerrit-review.googlesource.com/Documentation/rest-api-changes.html#fix-replacement-info
308type FixReplacementInfo struct {
309	// The path of the file which should be modified. Any file in the repository may be modified.
310	Path string `json:"path"`
311
312	// A CommentRange indicating which content of the file should be replaced.
313	// Lines in the file are assumed to be separated by the line feed character,
314	// the carriage return character, the carriage return followed by the line
315	// feed character, or one of the other Unicode linebreak sequences supported
316	// by Java.
317	Range CommentRange `json:"range"`
318
319	// The content which should be used instead of the current one.
320	Replacement string `json:"replacement,omitempty"`
321}
322
323// DiffIntralineInfo entity contains information about intraline edits in a file.
324//
325// The information consists of a list of <skip length, mark length> pairs,
326// where the skip length is the number of characters between the end of
327// the previous edit and the start of this edit, and the mark length is the
328// number of edited characters following the skip. The start of the edits
329// is from the beginning of the related diff content lines.
330//
331// Note that the implied newline character at the end of each line
332// is included in the length calculation, and thus it is possible for
333// the edits to span newlines.
334type DiffIntralineInfo [][2]int
335
336// ChangeInfo entity contains information about a change.
337type ChangeInfo struct {
338	ID                 string                   `json:"id"`
339	URL                string                   `json:"url,omitempty"`
340	Project            string                   `json:"project"`
341	Branch             string                   `json:"branch"`
342	Topic              string                   `json:"topic,omitempty"`
343	Hashtags           []string                 `json:"hashtags,omitempty"`
344	ChangeID           string                   `json:"change_id"`
345	Subject            string                   `json:"subject"`
346	Status             string                   `json:"status"`
347	Created            Timestamp                `json:"created"`
348	Updated            Timestamp                `json:"updated"`
349	Submitted          *Timestamp               `json:"submitted,omitempty"`
350	Starred            bool                     `json:"starred,omitempty"`
351	Reviewed           bool                     `json:"reviewed,omitempty"`
352	Mergeable          bool                     `json:"mergeable,omitempty"`
353	Insertions         int                      `json:"insertions"`
354	Deletions          int                      `json:"deletions"`
355	Number             int                      `json:"_number"`
356	Owner              AccountInfo              `json:"owner"`
357	Actions            map[string]ActionInfo    `json:"actions,omitempty"`
358	Labels             map[string]LabelInfo     `json:"labels,omitempty"`
359	PermittedLabels    map[string][]string      `json:"permitted_labels,omitempty"`
360	RemovableReviewers []AccountInfo            `json:"removable_reviewers,omitempty"`
361	Reviewers          map[string][]AccountInfo `json:"reviewers,omitempty"`
362	Messages           []ChangeMessageInfo      `json:"messages,omitempty"`
363	CurrentRevision    string                   `json:"current_revision,omitempty"`
364	Revisions          map[string]RevisionInfo  `json:"revisions,omitempty"`
365	MoreChanges        bool                     `json:"_more_changes,omitempty"`
366	Problems           []ProblemInfo            `json:"problems,omitempty"`
367	BaseChange         string                   `json:"base_change,omitempty"`
368}
369
370// LabelInfo entity contains information about a label on a change, always corresponding to the current patch set.
371type LabelInfo struct {
372	Optional bool `json:"optional,omitempty"`
373
374	// Fields set by LABELS
375	Approved     AccountInfo `json:"approved,omitempty"`
376	Rejected     AccountInfo `json:"rejected,omitempty"`
377	Recommended  AccountInfo `json:"recommended,omitempty"`
378	Disliked     AccountInfo `json:"disliked,omitempty"`
379	Blocking     bool        `json:"blocking,omitempty"`
380	Value        int         `json:"value,omitempty"`
381	DefaultValue int         `json:"default_value,omitempty"`
382
383	// Fields set by DETAILED_LABELS
384	All    []ApprovalInfo    `json:"all,omitempty"`
385	Values map[string]string `json:"values,omitempty"`
386}
387
388// RevisionInfo entity contains information about a patch set.
389type RevisionInfo struct {
390	Draft             bool                  `json:"draft,omitempty"`
391	Number            int                   `json:"_number"`
392	Created           Timestamp             `json:"created"`
393	Uploader          AccountInfo           `json:"uploader"`
394	Ref               string                `json:"ref"`
395	Fetch             map[string]FetchInfo  `json:"fetch"`
396	Commit            CommitInfo            `json:"commit,omitempty"`
397	Files             map[string]FileInfo   `json:"files,omitempty"`
398	Actions           map[string]ActionInfo `json:"actions,omitempty"`
399	Reviewed          bool                  `json:"reviewed,omitempty"`
400	MessageWithFooter string                `json:"messageWithFooter,omitempty"`
401}
402
403// CommentInfo entity contains information about an inline comment.
404type CommentInfo struct {
405	PatchSet  int           `json:"patch_set,omitempty"`
406	ID        string        `json:"id"`
407	Path      string        `json:"path,omitempty"`
408	Side      string        `json:"side,omitempty"`
409	Line      int           `json:"line,omitempty"`
410	Range     *CommentRange `json:"range,omitempty"`
411	InReplyTo string        `json:"in_reply_to,omitempty"`
412	Message   string        `json:"message,omitempty"`
413	Updated   *Timestamp    `json:"updated"`
414	Author    AccountInfo   `json:"author,omitempty"`
415}
416
417// QueryOptions specifies global parameters to query changes / reviewers.
418//
419// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api-changes.html#list-changes
420type QueryOptions struct {
421	// Query parameter
422	// Clients are allowed to specify more than one query by setting the q parameter multiple times.
423	// In this case the result is an array of arrays, one per query in the same order the queries were given in.
424	//
425	// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/user-search.html#_search_operators
426	Query []string `url:"q,omitempty"`
427
428	// The n parameter can be used to limit the returned results.
429	// If the n query parameter is supplied and additional changes exist that match the query beyond the end, the last change object has a _more_changes: true JSON field set.
430	Limit int `url:"n,omitempty"`
431}
432
433// QueryChangeOptions specifies the parameters to the ChangesService.QueryChanges.
434//
435// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api-changes.html#list-changes
436type QueryChangeOptions struct {
437	QueryOptions
438
439	// The S or start query parameter can be supplied to skip a number of changes from the list.
440	Skip  int `url:"S,omitempty"`
441	Start int `url:"start,omitempty"`
442
443	ChangeOptions
444}
445
446// ChangeOptions specifies the parameters for Query changes.
447//
448// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api-changes.html#list-changes
449type ChangeOptions struct {
450	// Additional fields can be obtained by adding o parameters, each option requires more database lookups and slows down the query response time to the client so they are generally disabled by default.
451	//
452	// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api-changes.html#list-changes
453	AdditionalFields []string `url:"o,omitempty"`
454}
455
456// QueryChanges lists changes visible to the caller.
457// The query string must be provided by the q parameter.
458// The n parameter can be used to limit the returned results.
459//
460// The change output is sorted by the last update time, most recently updated to oldest updated.
461//
462// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api-changes.html#list-changes
463func (s *ChangesService) QueryChanges(opt *QueryChangeOptions) (*[]ChangeInfo, *Response, error) {
464	u := "changes/"
465
466	u, err := addOptions(u, opt)
467	if err != nil {
468		return nil, nil, err
469	}
470
471	req, err := s.client.NewRequest("GET", u, nil)
472	if err != nil {
473		return nil, nil, err
474	}
475
476	v := new([]ChangeInfo)
477	resp, err := s.client.Do(req, v)
478	if err != nil {
479		return nil, resp, err
480	}
481
482	return v, resp, err
483}
484
485// GetChange retrieves a change.
486// Additional fields can be obtained by adding o parameters, each option requires more database lookups and slows down the query response time to the client so they are generally disabled by default.
487//
488// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api-changes.html#get-change
489func (s *ChangesService) GetChange(changeID string, opt *ChangeOptions) (*ChangeInfo, *Response, error) {
490	u := fmt.Sprintf("changes/%s", changeID)
491	return s.getChangeInfoResponse(u, opt)
492}
493
494// GetChangeDetail retrieves a change with labels, detailed labels, detailed accounts, and messages.
495// Additional fields can be obtained by adding o parameters, each option requires more database lookups and slows down the query response time to the client so they are generally disabled by default.
496//
497// This response will contain all votes for each label and include one combined vote.
498// The combined label vote is calculated in the following order (from highest to lowest): REJECTED > APPROVED > DISLIKED > RECOMMENDED.
499//
500// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api-changes.html#get-change-detail
501func (s *ChangesService) GetChangeDetail(changeID string, opt *ChangeOptions) (*ChangeInfo, *Response, error) {
502	u := fmt.Sprintf("changes/%s/detail", changeID)
503	return s.getChangeInfoResponse(u, opt)
504}
505
506// getChangeInfoResponse retrieved a single ChangeInfo Response for a GET request
507func (s *ChangesService) getChangeInfoResponse(u string, opt *ChangeOptions) (*ChangeInfo, *Response, error) {
508	u, err := addOptions(u, opt)
509	if err != nil {
510		return nil, nil, err
511	}
512
513	req, err := s.client.NewRequest("GET", u, nil)
514	if err != nil {
515		return nil, nil, err
516	}
517
518	v := new(ChangeInfo)
519	resp, err := s.client.Do(req, v)
520	if err != nil {
521		return nil, resp, err
522	}
523
524	return v, resp, err
525}
526
527// GetTopic retrieves the topic of a change.
528//
529// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api-changes.html#get-topic
530func (s *ChangesService) GetTopic(changeID string) (string, *Response, error) {
531	u := fmt.Sprintf("changes/%s/topic", changeID)
532	return getStringResponseWithoutOptions(s.client, u)
533}
534
535// ChangesSubmittedTogether returns a list of all changes which are submitted when {submit} is called for this change, including the current change itself.
536// An empty list is returned if this change will be submitted by itself (no other changes).
537//
538// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api-changes.html#submitted_together
539func (s *ChangesService) ChangesSubmittedTogether(changeID string) (*[]ChangeInfo, *Response, error) {
540	u := fmt.Sprintf("changes/%s/submitted_together", changeID)
541
542	req, err := s.client.NewRequest("GET", u, nil)
543	if err != nil {
544		return nil, nil, err
545	}
546
547	v := new([]ChangeInfo)
548	resp, err := s.client.Do(req, v)
549	if err != nil {
550		return nil, resp, err
551	}
552
553	return v, resp, err
554}
555
556// GetIncludedIn retrieves the branches and tags in which a change is included.
557//
558// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api-changes.html#get-included-in
559func (s *ChangesService) GetIncludedIn(changeID string) (*IncludedInInfo, *Response, error) {
560	u := fmt.Sprintf("changes/%s/in", changeID)
561
562	req, err := s.client.NewRequest("GET", u, nil)
563	if err != nil {
564		return nil, nil, err
565	}
566
567	v := new(IncludedInInfo)
568	resp, err := s.client.Do(req, v)
569	if err != nil {
570		return nil, resp, err
571	}
572
573	return v, resp, err
574}
575
576// ListChangeComments lists the published comments of all revisions of the change.
577// The entries in the map are sorted by file path, and the comments for each path are sorted by patch set number.
578// Each comment has the patch_set and author fields set.
579//
580// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api-changes.html#list-change-comments
581func (s *ChangesService) ListChangeComments(changeID string) (*map[string][]CommentInfo, *Response, error) {
582	u := fmt.Sprintf("changes/%s/comments", changeID)
583	return s.getCommentInfoMapResponse(u)
584}
585
586// ListChangeDrafts lLists the draft comments of all revisions of the change that belong to the calling user.
587// The entries in the map are sorted by file path, and the comments for each path are sorted by patch set number.
588// Each comment has the patch_set field set, and no author.
589//
590// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api-changes.html#list-change-drafts
591func (s *ChangesService) ListChangeDrafts(changeID string) (*map[string][]CommentInfo, *Response, error) {
592	u := fmt.Sprintf("changes/%s/drafts", changeID)
593	return s.getCommentInfoMapResponse(u)
594}
595
596// getCommentInfoMapResponse retrieved a map of CommentInfo Response for a GET request
597func (s *ChangesService) getCommentInfoMapResponse(u string) (*map[string][]CommentInfo, *Response, error) {
598	req, err := s.client.NewRequest("GET", u, nil)
599	if err != nil {
600		return nil, nil, err
601	}
602
603	v := new(map[string][]CommentInfo)
604	resp, err := s.client.Do(req, v)
605	if err != nil {
606		return nil, resp, err
607	}
608
609	return v, resp, err
610}
611
612// CheckChange performs consistency checks on the change, and returns a ChangeInfo entity with the problems field set to a list of ProblemInfo entities.
613// Depending on the type of problem, some fields not marked optional may be missing from the result.
614// At least id, project, branch, and _number will be present.
615//
616// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api-changes.html#check-change
617func (s *ChangesService) CheckChange(changeID string) (*ChangeInfo, *Response, error) {
618	u := fmt.Sprintf("changes/%s/check", changeID)
619	return s.getChangeInfoResponse(u, nil)
620}
621
622// getCommentInfoResponse retrieved a CommentInfo Response for a GET request
623func (s *ChangesService) getCommentInfoResponse(u string) (*CommentInfo, *Response, error) {
624	req, err := s.client.NewRequest("GET", u, nil)
625	if err != nil {
626		return nil, nil, err
627	}
628
629	v := new(CommentInfo)
630	resp, err := s.client.Do(req, v)
631	if err != nil {
632		return nil, resp, err
633	}
634
635	return v, resp, err
636}
637
638// getCommentInfoMapSliceResponse retrieved a map with a slice of CommentInfo Response for a GET request
639func (s *ChangesService) getCommentInfoMapSliceResponse(u string) (*map[string][]CommentInfo, *Response, error) {
640	req, err := s.client.NewRequest("GET", u, nil)
641	if err != nil {
642		return nil, nil, err
643	}
644
645	v := new(map[string][]CommentInfo)
646	resp, err := s.client.Do(req, v)
647	if err != nil {
648		return nil, resp, err
649	}
650
651	return v, resp, err
652}
653
654// CreateChange creates a new change.
655// The change info ChangeInfo entity must be provided in the request body.
656// Only the following attributes are honored: project, branch, subject, status and topic.
657// The first three attributes are mandatory.
658// Valid values for status are: DRAFT and NEW.
659//
660// As response a ChangeInfo entity is returned that describes the resulting change.
661//
662// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api-changes.html#create-change
663func (s *ChangesService) CreateChange(input *ChangeInfo) (*ChangeInfo, *Response, error) {
664	u := "changes/"
665
666	req, err := s.client.NewRequest("POST", u, input)
667	if err != nil {
668		return nil, nil, err
669	}
670
671	v := new(ChangeInfo)
672	resp, err := s.client.Do(req, v)
673	if err != nil {
674		return nil, resp, err
675	}
676
677	return v, resp, err
678}
679
680// SetTopic sets the topic of a change.
681// The new topic must be provided in the request body inside a TopicInput entity.
682//
683// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api-changes.html#set-topic
684func (s *ChangesService) SetTopic(changeID string, input *TopicInput) (*string, *Response, error) {
685	u := fmt.Sprintf("changes/%s/topic", changeID)
686
687	req, err := s.client.NewRequest("PUT", u, input)
688	if err != nil {
689		return nil, nil, err
690	}
691
692	v := new(string)
693	resp, err := s.client.Do(req, v)
694	if err != nil {
695		return nil, resp, err
696	}
697
698	return v, resp, err
699}
700
701// DeleteTopic deletes the topic of a change.
702// The request body does not need to include a TopicInput entity if no review comment is added.
703//
704// Please note that some proxies prohibit request bodies for DELETE requests.
705// In this case, if you want to specify a commit message, use PUT to delete the topic.
706//
707// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api-changes.html#delete-topic
708func (s *ChangesService) DeleteTopic(changeID string) (*Response, error) {
709	u := fmt.Sprintf("changes/%s/topic", changeID)
710	return s.client.DeleteRequest(u, nil)
711}
712
713// DeleteDraftChange deletes a draft change.
714//
715// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api-changes.html#delete-draft-change
716func (s *ChangesService) DeleteDraftChange(changeID string) (*Response, error) {
717	u := fmt.Sprintf("changes/%s", changeID)
718	return s.client.DeleteRequest(u, nil)
719}
720
721// PublishDraftChange publishes a draft change.
722//
723// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api-changes.html#publish-draft-change
724func (s *ChangesService) PublishDraftChange(changeID, notify string) (*Response, error) {
725	u := fmt.Sprintf("changes/%s/publish", changeID)
726
727	req, err := s.client.NewRequest("POST", u, map[string]string{
728		"notify": notify,
729	})
730	if err != nil {
731		return nil, err
732	}
733	return s.client.Do(req, nil)
734}
735
736// IndexChange adds or updates the change in the secondary index.
737//
738// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api-changes.html#index-change
739func (s *ChangesService) IndexChange(changeID string) (*Response, error) {
740	u := fmt.Sprintf("changes/%s/index", changeID)
741
742	req, err := s.client.NewRequest("POST", u, nil)
743	if err != nil {
744		return nil, err
745	}
746	return s.client.Do(req, nil)
747}
748
749// FixChange performs consistency checks on the change as with GET /check, and additionally fixes any problems that can be fixed automatically.
750// The returned field values reflect any fixes.
751//
752// Some fixes have options controlling their behavior, which can be set in the FixInput entity body.
753// Only the change owner, a project owner, or an administrator may fix changes.
754//
755// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api-changes.html#fix-change
756func (s *ChangesService) FixChange(changeID string, input *FixInput) (*ChangeInfo, *Response, error) {
757	u := fmt.Sprintf("changes/%s/check", changeID)
758
759	req, err := s.client.NewRequest("PUT", u, input)
760	if err != nil {
761		return nil, nil, err
762	}
763
764	v := new(ChangeInfo)
765	resp, err := s.client.Do(req, v)
766	if err != nil {
767		return nil, resp, err
768	}
769
770	return v, resp, err
771}
772
773// change is an internal function to consolidate code used by SubmitChange,
774// AbandonChange and other similar functions.
775func (s *ChangesService) change(tail string, changeID string, input interface{}) (*ChangeInfo, *Response, error) {
776	u := fmt.Sprintf("changes/%s/%s", changeID, tail)
777	req, err := s.client.NewRequest("POST", u, input)
778	if err != nil {
779		return nil, nil, err
780	}
781
782	v := new(ChangeInfo)
783	resp, err := s.client.Do(req, v)
784	if err != nil {
785		return nil, resp, err
786	}
787	if resp.StatusCode == http.StatusConflict {
788		body, err := ioutil.ReadAll(resp.Body)
789		if err != nil {
790			return v, resp, err
791		}
792		return v, resp, errors.New(string(body[:]))
793	}
794	return v, resp, nil
795}
796
797// SubmitChange submits a change.
798//
799// The request body only needs to include a SubmitInput entity if submitting on behalf of another user.
800//
801// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api-changes.html#submit-change
802func (s *ChangesService) SubmitChange(changeID string, input *SubmitInput) (*ChangeInfo, *Response, error) {
803	return s.change("submit", changeID, input)
804}
805
806// AbandonChange abandons a change.
807//
808// The request body does not need to include a AbandonInput entity if no review
809// comment is added.
810//
811// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api-changes.html#abandon-change
812func (s *ChangesService) AbandonChange(changeID string, input *AbandonInput) (*ChangeInfo, *Response, error) {
813	return s.change("abandon", changeID, input)
814}
815
816// RebaseChange rebases a change.
817//
818// Optionally, the parent revision can be changed to another patch set through
819// the RebaseInput entity.
820//
821// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api-changes.html#rebase-change
822func (s *ChangesService) RebaseChange(changeID string, input *RebaseInput) (*ChangeInfo, *Response, error) {
823	return s.change("rebase", changeID, input)
824}
825
826// RestoreChange restores a change.
827//
828// The request body does not need to include a RestoreInput entity if no review
829// comment is added.
830//
831// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api-changes.html#restore-change
832func (s *ChangesService) RestoreChange(changeID string, input *RestoreInput) (*ChangeInfo, *Response, error) {
833	return s.change("restore", changeID, input)
834}
835
836// RevertChange reverts a change.
837//
838// The request body does not need to include a RevertInput entity if no
839// review comment is added.
840//
841// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api-changes.html#revert-change
842func (s *ChangesService) RevertChange(changeID string, input *RevertInput) (*ChangeInfo, *Response, error) {
843	return s.change("revert", changeID, input)
844}
845