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