1//
2// Copyright 2017, Sander van Harmelen
3//
4// Licensed under the Apache License, Version 2.0 (the "License");
5// you may not use this file except in compliance with the License.
6// You may obtain a copy of the License at
7//
8// http://www.apache.org/licenses/LICENSE-2.0
9//
10// Unless required by applicable law or agreed to in writing, software
11// distributed under the License is distributed on an "AS IS" BASIS,
12// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13// See the License for the specific language governing permissions and
14// limitations under the License.
15//
16
17package gitlab
18
19import (
20 "fmt"
21 "net/url"
22 "time"
23)
24
25// CommitsService handles communication with the commit related methods
26// of the GitLab API.
27//
28// GitLab API docs: https://docs.gitlab.com/ce/api/commits.html
29type CommitsService struct {
30 client *Client
31}
32
33// Commit represents a GitLab commit.
34//
35// GitLab API docs: https://docs.gitlab.com/ce/api/commits.html
36type Commit struct {
37 ID string `json:"id"`
38 ShortID string `json:"short_id"`
39 Title string `json:"title"`
40 AuthorName string `json:"author_name"`
41 AuthorEmail string `json:"author_email"`
42 AuthoredDate *time.Time `json:"authored_date"`
43 CommitterName string `json:"committer_name"`
44 CommitterEmail string `json:"committer_email"`
45 CommittedDate *time.Time `json:"committed_date"`
46 CreatedAt *time.Time `json:"created_at"`
47 Message string `json:"message"`
48 ParentIDs []string `json:"parent_ids"`
49 Stats *CommitStats `json:"stats"`
50 Status *BuildStateValue `json:"status"`
51 LastPipeline *PipelineInfo `json:"last_pipeline"`
52 ProjectID int `json:"project_id"`
53}
54
55// CommitStats represents the number of added and deleted files in a commit.
56//
57// GitLab API docs: https://docs.gitlab.com/ce/api/commits.html
58type CommitStats struct {
59 Additions int `json:"additions"`
60 Deletions int `json:"deletions"`
61 Total int `json:"total"`
62}
63
64func (c Commit) String() string {
65 return Stringify(c)
66}
67
68// ListCommitsOptions represents the available ListCommits() options.
69//
70// GitLab API docs: https://docs.gitlab.com/ce/api/commits.html#list-repository-commits
71type ListCommitsOptions struct {
72 ListOptions
73 RefName *string `url:"ref_name,omitempty" json:"ref_name,omitempty"`
74 Since *time.Time `url:"since,omitempty" json:"since,omitempty"`
75 Until *time.Time `url:"until,omitempty" json:"until,omitempty"`
76 Path *string `url:"path,omitempty" json:"path,omitempty"`
77 All *bool `url:"all,omitempty" json:"all,omitempty"`
78 WithStats *bool `url:"with_stats,omitempty" json:"with_stats,omitempty"`
79}
80
81// ListCommits gets a list of repository commits in a project.
82//
83// GitLab API docs: https://docs.gitlab.com/ce/api/commits.html#list-commits
84func (s *CommitsService) ListCommits(pid interface{}, opt *ListCommitsOptions, options ...OptionFunc) ([]*Commit, *Response, error) {
85 project, err := parseID(pid)
86 if err != nil {
87 return nil, nil, err
88 }
89 u := fmt.Sprintf("projects/%s/repository/commits", pathEscape(project))
90
91 req, err := s.client.NewRequest("GET", u, opt, options)
92 if err != nil {
93 return nil, nil, err
94 }
95
96 var c []*Commit
97 resp, err := s.client.Do(req, &c)
98 if err != nil {
99 return nil, resp, err
100 }
101
102 return c, resp, err
103}
104
105// FileAction represents the available actions that can be performed on a file.
106//
107// GitLab API docs: https://docs.gitlab.com/ce/api/commits.html#create-a-commit-with-multiple-files-and-actions
108type FileAction string
109
110// The available file actions.
111const (
112 FileCreate FileAction = "create"
113 FileDelete FileAction = "delete"
114 FileMove FileAction = "move"
115 FileUpdate FileAction = "update"
116)
117
118// CommitAction represents a single file action within a commit.
119type CommitAction struct {
120 Action FileAction `url:"action" json:"action"`
121 FilePath string `url:"file_path" json:"file_path"`
122 PreviousPath string `url:"previous_path,omitempty" json:"previous_path,omitempty"`
123 Content string `url:"content,omitempty" json:"content,omitempty"`
124 Encoding string `url:"encoding,omitempty" json:"encoding,omitempty"`
125}
126
127// CommitRef represents the reference of branches/tags in a commit.
128//
129// GitLab API docs:
130// https://docs.gitlab.com/ce/api/commits.html#get-references-a-commit-is-pushed-to
131type CommitRef struct {
132 Type string `json:"type"`
133 Name string `json:"name"`
134}
135
136// GetCommitRefsOptions represents the available GetCommitRefs() options.
137//
138// GitLab API docs:
139// https://docs.gitlab.com/ce/api/commits.html#get-references-a-commit-is-pushed-to
140type GetCommitRefsOptions struct {
141 ListOptions
142 Type *string `url:"type,omitempty" json:"type,omitempty"`
143}
144
145// GetCommitRefs gets all references (from branches or tags) a commit is pushed to
146//
147// GitLab API docs:
148// https://docs.gitlab.com/ce/api/commits.html#get-references-a-commit-is-pushed-to
149func (s *CommitsService) GetCommitRefs(pid interface{}, sha string, opt *GetCommitRefsOptions, options ...OptionFunc) ([]*CommitRef, *Response, error) {
150 project, err := parseID(pid)
151 if err != nil {
152 return nil, nil, err
153 }
154 u := fmt.Sprintf("projects/%s/repository/commits/%s/refs", pathEscape(project), url.PathEscape(sha))
155
156 req, err := s.client.NewRequest("GET", u, opt, options)
157 if err != nil {
158 return nil, nil, err
159 }
160
161 var cs []*CommitRef
162 resp, err := s.client.Do(req, &cs)
163 if err != nil {
164 return nil, resp, err
165 }
166
167 return cs, resp, err
168}
169
170// GetCommit gets a specific commit identified by the commit hash or name of a
171// branch or tag.
172//
173// GitLab API docs: https://docs.gitlab.com/ce/api/commits.html#get-a-single-commit
174func (s *CommitsService) GetCommit(pid interface{}, sha string, options ...OptionFunc) (*Commit, *Response, error) {
175 project, err := parseID(pid)
176 if err != nil {
177 return nil, nil, err
178 }
179 if sha == "" {
180 return nil, nil, fmt.Errorf("SHA must be a non-empty string")
181 }
182 u := fmt.Sprintf("projects/%s/repository/commits/%s", pathEscape(project), url.PathEscape(sha))
183
184 req, err := s.client.NewRequest("GET", u, nil, options)
185 if err != nil {
186 return nil, nil, err
187 }
188
189 c := new(Commit)
190 resp, err := s.client.Do(req, c)
191 if err != nil {
192 return nil, resp, err
193 }
194
195 return c, resp, err
196}
197
198// CreateCommitOptions represents the available options for a new commit.
199//
200// GitLab API docs: https://docs.gitlab.com/ce/api/commits.html#create-a-commit-with-multiple-files-and-actions
201type CreateCommitOptions struct {
202 Branch *string `url:"branch" json:"branch"`
203 CommitMessage *string `url:"commit_message" json:"commit_message"`
204 StartBranch *string `url:"start_branch,omitempty" json:"start_branch,omitempty"`
205 StartSHA *string `url:"start_sha,omitempty" json:"start_sha,omitempty"`
206 StartProject *string `url:"start_project,omitempty" json:"start_project,omitempty"`
207 Actions []*CommitAction `url:"actions" json:"actions"`
208 AuthorEmail *string `url:"author_email,omitempty" json:"author_email,omitempty"`
209 AuthorName *string `url:"author_name,omitempty" json:"author_name,omitempty"`
210 Stats *bool `url:"stats,omitempty" json:"stats,omitempty"`
211 Force *bool `url:"force,omitempty" json:"force,omitempty"`
212}
213
214// CreateCommit creates a commit with multiple files and actions.
215//
216// GitLab API docs: https://docs.gitlab.com/ce/api/commits.html#create-a-commit-with-multiple-files-and-actions
217func (s *CommitsService) CreateCommit(pid interface{}, opt *CreateCommitOptions, options ...OptionFunc) (*Commit, *Response, error) {
218 project, err := parseID(pid)
219 if err != nil {
220 return nil, nil, err
221 }
222 u := fmt.Sprintf("projects/%s/repository/commits", pathEscape(project))
223
224 req, err := s.client.NewRequest("POST", u, opt, options)
225 if err != nil {
226 return nil, nil, err
227 }
228
229 c := new(Commit)
230 resp, err := s.client.Do(req, &c)
231 if err != nil {
232 return nil, resp, err
233 }
234
235 return c, resp, err
236}
237
238// Diff represents a GitLab diff.
239//
240// GitLab API docs: https://docs.gitlab.com/ce/api/commits.html
241type Diff struct {
242 Diff string `json:"diff"`
243 NewPath string `json:"new_path"`
244 OldPath string `json:"old_path"`
245 AMode string `json:"a_mode"`
246 BMode string `json:"b_mode"`
247 NewFile bool `json:"new_file"`
248 RenamedFile bool `json:"renamed_file"`
249 DeletedFile bool `json:"deleted_file"`
250}
251
252func (d Diff) String() string {
253 return Stringify(d)
254}
255
256// GetCommitDiffOptions represents the available GetCommitDiff() options.
257//
258// GitLab API docs:
259// https://docs.gitlab.com/ce/api/commits.html#get-the-diff-of-a-commit
260type GetCommitDiffOptions ListOptions
261
262// GetCommitDiff gets the diff of a commit in a project..
263//
264// GitLab API docs:
265// https://docs.gitlab.com/ce/api/commits.html#get-the-diff-of-a-commit
266func (s *CommitsService) GetCommitDiff(pid interface{}, sha string, opt *GetCommitDiffOptions, options ...OptionFunc) ([]*Diff, *Response, error) {
267 project, err := parseID(pid)
268 if err != nil {
269 return nil, nil, err
270 }
271 u := fmt.Sprintf("projects/%s/repository/commits/%s/diff", pathEscape(project), url.PathEscape(sha))
272
273 req, err := s.client.NewRequest("GET", u, opt, options)
274 if err != nil {
275 return nil, nil, err
276 }
277
278 var d []*Diff
279 resp, err := s.client.Do(req, &d)
280 if err != nil {
281 return nil, resp, err
282 }
283
284 return d, resp, err
285}
286
287// CommitComment represents a GitLab commit comment.
288//
289// GitLab API docs: https://docs.gitlab.com/ce/api/commits.html
290type CommitComment struct {
291 Note string `json:"note"`
292 Path string `json:"path"`
293 Line int `json:"line"`
294 LineType string `json:"line_type"`
295 Author Author `json:"author"`
296}
297
298// Author represents a GitLab commit author
299type Author struct {
300 ID int `json:"id"`
301 Username string `json:"username"`
302 Email string `json:"email"`
303 Name string `json:"name"`
304 State string `json:"state"`
305 Blocked bool `json:"blocked"`
306 CreatedAt *time.Time `json:"created_at"`
307}
308
309func (c CommitComment) String() string {
310 return Stringify(c)
311}
312
313// GetCommitCommentsOptions represents the available GetCommitComments() options.
314//
315// GitLab API docs:
316// https://docs.gitlab.com/ce/api/commits.html#get-the-comments-of-a-commit
317type GetCommitCommentsOptions ListOptions
318
319// GetCommitComments gets the comments of a commit in a project.
320//
321// GitLab API docs:
322// https://docs.gitlab.com/ce/api/commits.html#get-the-comments-of-a-commit
323func (s *CommitsService) GetCommitComments(pid interface{}, sha string, opt *GetCommitCommentsOptions, options ...OptionFunc) ([]*CommitComment, *Response, error) {
324 project, err := parseID(pid)
325 if err != nil {
326 return nil, nil, err
327 }
328 u := fmt.Sprintf("projects/%s/repository/commits/%s/comments", pathEscape(project), url.PathEscape(sha))
329
330 req, err := s.client.NewRequest("GET", u, opt, options)
331 if err != nil {
332 return nil, nil, err
333 }
334
335 var c []*CommitComment
336 resp, err := s.client.Do(req, &c)
337 if err != nil {
338 return nil, resp, err
339 }
340
341 return c, resp, err
342}
343
344// PostCommitCommentOptions represents the available PostCommitComment()
345// options.
346//
347// GitLab API docs:
348// https://docs.gitlab.com/ce/api/commits.html#post-comment-to-commit
349type PostCommitCommentOptions struct {
350 Note *string `url:"note,omitempty" json:"note,omitempty"`
351 Path *string `url:"path" json:"path"`
352 Line *int `url:"line" json:"line"`
353 LineType *string `url:"line_type" json:"line_type"`
354}
355
356// PostCommitComment adds a comment to a commit. Optionally you can post
357// comments on a specific line of a commit. Therefor both path, line_new and
358// line_old are required.
359//
360// GitLab API docs:
361// https://docs.gitlab.com/ce/api/commits.html#post-comment-to-commit
362func (s *CommitsService) PostCommitComment(pid interface{}, sha string, opt *PostCommitCommentOptions, options ...OptionFunc) (*CommitComment, *Response, error) {
363 project, err := parseID(pid)
364 if err != nil {
365 return nil, nil, err
366 }
367 u := fmt.Sprintf("projects/%s/repository/commits/%s/comments", pathEscape(project), url.PathEscape(sha))
368
369 req, err := s.client.NewRequest("POST", u, opt, options)
370 if err != nil {
371 return nil, nil, err
372 }
373
374 c := new(CommitComment)
375 resp, err := s.client.Do(req, c)
376 if err != nil {
377 return nil, resp, err
378 }
379
380 return c, resp, err
381}
382
383// GetCommitStatusesOptions represents the available GetCommitStatuses() options.
384//
385// GitLab API docs: https://docs.gitlab.com/ce/api/commits.html#get-the-status-of-a-commit
386type GetCommitStatusesOptions struct {
387 ListOptions
388 Ref *string `url:"ref,omitempty" json:"ref,omitempty"`
389 Stage *string `url:"stage,omitempty" json:"stage,omitempty"`
390 Name *string `url:"name,omitempty" json:"name,omitempty"`
391 All *bool `url:"all,omitempty" json:"all,omitempty"`
392}
393
394// CommitStatus represents a GitLab commit status.
395//
396// GitLab API docs: https://docs.gitlab.com/ce/api/commits.html#get-the-status-of-a-commit
397type CommitStatus struct {
398 ID int `json:"id"`
399 SHA string `json:"sha"`
400 Ref string `json:"ref"`
401 Status string `json:"status"`
402 Name string `json:"name"`
403 TargetURL string `json:"target_url"`
404 Description string `json:"description"`
405 CreatedAt *time.Time `json:"created_at"`
406 StartedAt *time.Time `json:"started_at"`
407 FinishedAt *time.Time `json:"finished_at"`
408 Author Author `json:"author"`
409}
410
411// GetCommitStatuses gets the statuses of a commit in a project.
412//
413// GitLab API docs: https://docs.gitlab.com/ce/api/commits.html#get-the-status-of-a-commit
414func (s *CommitsService) GetCommitStatuses(pid interface{}, sha string, opt *GetCommitStatusesOptions, options ...OptionFunc) ([]*CommitStatus, *Response, error) {
415 project, err := parseID(pid)
416 if err != nil {
417 return nil, nil, err
418 }
419 u := fmt.Sprintf("projects/%s/repository/commits/%s/statuses", pathEscape(project), url.PathEscape(sha))
420
421 req, err := s.client.NewRequest("GET", u, opt, options)
422 if err != nil {
423 return nil, nil, err
424 }
425
426 var cs []*CommitStatus
427 resp, err := s.client.Do(req, &cs)
428 if err != nil {
429 return nil, resp, err
430 }
431
432 return cs, resp, err
433}
434
435// SetCommitStatusOptions represents the available SetCommitStatus() options.
436//
437// GitLab API docs: https://docs.gitlab.com/ce/api/commits.html#post-the-status-to-commit
438type SetCommitStatusOptions struct {
439 State BuildStateValue `url:"state" json:"state"`
440 Ref *string `url:"ref,omitempty" json:"ref,omitempty"`
441 Name *string `url:"name,omitempty" json:"name,omitempty"`
442 Context *string `url:"context,omitempty" json:"context,omitempty"`
443 TargetURL *string `url:"target_url,omitempty" json:"target_url,omitempty"`
444 Description *string `url:"description,omitempty" json:"description,omitempty"`
445}
446
447// SetCommitStatus sets the status of a commit in a project.
448//
449// GitLab API docs: https://docs.gitlab.com/ce/api/commits.html#post-the-status-to-commit
450func (s *CommitsService) SetCommitStatus(pid interface{}, sha string, opt *SetCommitStatusOptions, options ...OptionFunc) (*CommitStatus, *Response, error) {
451 project, err := parseID(pid)
452 if err != nil {
453 return nil, nil, err
454 }
455 u := fmt.Sprintf("projects/%s/statuses/%s", pathEscape(project), url.PathEscape(sha))
456
457 req, err := s.client.NewRequest("POST", u, opt, options)
458 if err != nil {
459 return nil, nil, err
460 }
461
462 cs := new(CommitStatus)
463 resp, err := s.client.Do(req, &cs)
464 if err != nil {
465 return nil, resp, err
466 }
467
468 return cs, resp, err
469}
470
471// GetMergeRequestsByCommit gets merge request associated with a commit.
472//
473// GitLab API docs:
474// https://docs.gitlab.com/ce/api/commits.html#list-merge-requests-associated-with-a-commit
475func (s *CommitsService) GetMergeRequestsByCommit(pid interface{}, sha string, options ...OptionFunc) ([]*MergeRequest, *Response, error) {
476 project, err := parseID(pid)
477 if err != nil {
478 return nil, nil, err
479 }
480 u := fmt.Sprintf("projects/%s/repository/commits/%s/merge_requests", pathEscape(project), url.PathEscape(sha))
481
482 req, err := s.client.NewRequest("GET", u, nil, options)
483 if err != nil {
484 return nil, nil, err
485 }
486
487 var mrs []*MergeRequest
488 resp, err := s.client.Do(req, &mrs)
489 if err != nil {
490 return nil, resp, err
491 }
492
493 return mrs, resp, err
494}
495
496// CherryPickCommitOptions represents the available CherryPickCommit() options.
497//
498// GitLab API docs: https://docs.gitlab.com/ce/api/commits.html#cherry-pick-a-commit
499type CherryPickCommitOptions struct {
500 Branch *string `url:"branch,omitempty" json:"branch,omitempty"`
501}
502
503// CherryPickCommit cherry picks a commit to a given branch.
504//
505// GitLab API docs: https://docs.gitlab.com/ce/api/commits.html#cherry-pick-a-commit
506func (s *CommitsService) CherryPickCommit(pid interface{}, sha string, opt *CherryPickCommitOptions, options ...OptionFunc) (*Commit, *Response, error) {
507 project, err := parseID(pid)
508 if err != nil {
509 return nil, nil, err
510 }
511 u := fmt.Sprintf("projects/%s/repository/commits/%s/cherry_pick", pathEscape(project), url.PathEscape(sha))
512
513 req, err := s.client.NewRequest("POST", u, opt, options)
514 if err != nil {
515 return nil, nil, err
516 }
517
518 c := new(Commit)
519 resp, err := s.client.Do(req, &c)
520 if err != nil {
521 return nil, resp, err
522 }
523
524 return c, resp, err
525}
526
527// RevertCommitOptions represents the available RevertCommit() options.
528//
529// GitLab API docs: https://docs.gitlab.com/ee/api/commits.html#revert-a-commit
530type RevertCommitOptions struct {
531 Branch *string `url:"branch,omitempty" json:"branch,omitempty"`
532}
533
534// RevertCommit reverts a commit in a given branch.
535//
536// GitLab API docs: https://docs.gitlab.com/ee/api/commits.html#revert-a-commit
537func (s *CommitsService) RevertCommit(pid interface{}, sha string, opt *RevertCommitOptions, options ...OptionFunc) (*Commit, *Response, error) {
538 project, err := parseID(pid)
539 if err != nil {
540 return nil, nil, err
541 }
542 u := fmt.Sprintf("projects/%s/repository/commits/%s/revert", pathEscape(project), url.PathEscape(sha))
543
544 req, err := s.client.NewRequest("POST", u, opt, options)
545 if err != nil {
546 return nil, nil, err
547 }
548
549 c := new(Commit)
550 resp, err := s.client.Do(req, &c)
551 if err != nil {
552 return nil, resp, err
553 }
554
555 return c, resp, err
556}
557
558// GPGSignature represents a Gitlab commit's GPG Signature.
559//
560// GitLab API docs:
561// https://docs.gitlab.com/ee/api/commits.html#get-gpg-signature-of-a-commit
562type GPGSignature struct {
563 KeyID int `json:"gpg_key_id"`
564 KeyPrimaryKeyID string `json:"gpg_key_primary_keyid"`
565 KeyUserName string `json:"gpg_key_user_name"`
566 KeyUserEmail string `json:"gpg_key_user_email"`
567 VerificationStatus string `json:"verification_status"`
568 KeySubkeyID int `json:"gpg_key_subkey_id"`
569}
570
571// GetGPGSiganature gets a GPG signature of a commit.
572//
573// GitLab API docs: https://docs.gitlab.com/ee/api/commits.html#get-gpg-signature-of-a-commit
574func (s *CommitsService) GetGPGSiganature(pid interface{}, sha string, options ...OptionFunc) (*GPGSignature, *Response, error) {
575 project, err := parseID(pid)
576 if err != nil {
577 return nil, nil, err
578 }
579 u := fmt.Sprintf("projects/%s/repository/commits/%s/signature", pathEscape(project), url.PathEscape(sha))
580
581 req, err := s.client.NewRequest("GET", u, nil, options)
582 if err != nil {
583 return nil, nil, err
584 }
585
586 sig := new(GPGSignature)
587 resp, err := s.client.Do(req, &sig)
588 if err != nil {
589 return nil, resp, err
590 }
591
592 return sig, resp, err
593}