Repository: xanzy/go-gitlab
Pull request #691 (2019-09-26 05:45:53)

GetCommit should return err if sha is an empty string
Change-Id: If54baf0aded1a3b4ec7dccc2e206d510a8fee833


Patch:
diff --git a/.travis.yml b/.travis.yml
index ee77c02..0c9e885 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -4,7 +4,6 @@ go:
   - 1.10.x
   - 1.11.x
   - 1.12.x
-  - 1.13.x
   - master
 
 stages:
diff --git a/commits.go b/commits.go
index 28c73e2..27dd564 100644
--- a/commits.go
+++ b/commits.go
@@ -176,6 +176,9 @@ func (s *CommitsService) GetCommit(pid interface{}, sha string, options ...Optio
 	if err != nil {
 		return nil, nil, err
 	}
+	if sha == "" {
+		return nil, nil, fmt.Errorf("SHA must be a non-empty string")
+	}
 	u := fmt.Sprintf("projects/%s/repository/commits/%s", pathEscape(project), url.PathEscape(sha))
 
 	req, err := s.client.NewRequest("GET", u, nil, options)
diff --git a/gitlab.go b/gitlab.go
index 7b0dfc6..9b928de 100644
--- a/gitlab.go
+++ b/gitlab.go
@@ -99,7 +99,7 @@ const iso8601 = "2006-01-02"
 func (t ISOTime) MarshalJSON() ([]byte, error) {
 	if y := time.Time(t).Year(); y < 0 || y >= 10000 {
 		// ISO 8901 uses 4 digits for the years
-		return nil, errors.New("json: ISOTime year outside of range [0,9999]")
+		return nil, errors.New("ISOTime.MarshalJSON: year outside of range [0,9999]")
 	}
 
 	b := make([]byte, 0, len(iso8601)+2)
diff --git a/issues.go b/issues.go
index 8d4861d..ed9c2bf 100644
--- a/issues.go
+++ b/issues.go
@@ -75,7 +75,7 @@ type Issue struct {
 	Assignee          *IssueAssignee   `json:"assignee"`
 	Upvotes           int              `json:"upvotes"`
 	Downvotes         int              `json:"downvotes"`
-	Labels            Labels           `json:"labels"`
+	Labels            []string         `json:"labels"`
 	Title             string           `json:"title"`
 	UpdatedAt         *time.Time       `json:"updated_at"`
 	CreatedAt         *time.Time       `json:"created_at"`
@@ -111,7 +111,7 @@ func (l *Labels) MarshalJSON() ([]byte, error) {
 type ListIssuesOptions struct {
 	ListOptions
 	State            *string    `url:"state,omitempty" json:"state,omitempty"`
-	Labels           *Labels    `url:"labels,omitempty" json:"labels,omitempty"`
+	Labels           Labels     `url:"labels,comma,omitempty" json:"labels,omitempty"`
 	WithLabelDetails *bool      `url:"with_labels_details,omitempty" json:"with_labels_details,omitempty"`
 	Milestone        *string    `url:"milestone,omitempty" json:"milestone,omitempty"`
 	Scope            *string    `url:"scope,omitempty" json:"scope,omitempty"`
@@ -154,7 +154,7 @@ func (s *IssuesService) ListIssues(opt *ListIssuesOptions, options ...OptionFunc
 type ListGroupIssuesOptions struct {
 	ListOptions
 	State           *string    `url:"state,omitempty" json:"state,omitempty"`
-	Labels          *Labels    `url:"labels,omitempty" json:"labels,omitempty"`
+	Labels          Labels     `url:"labels,comma,omitempty" json:"labels,omitempty"`
 	IIDs            []int      `url:"iids[],omitempty" json:"iids,omitempty"`
 	Milestone       *string    `url:"milestone,omitempty" json:"milestone,omitempty"`
 	Scope           *string    `url:"scope,omitempty" json:"scope,omitempty"`
@@ -203,7 +203,7 @@ type ListProjectIssuesOptions struct {
 	ListOptions
 	IIDs             []int      `url:"iids[],omitempty" json:"iids,omitempty"`
 	State            *string    `url:"state,omitempty" json:"state,omitempty"`
-	Labels           *Labels    `url:"labels,omitempty" json:"labels,omitempty"`
+	Labels           Labels     `url:"labels,comma,omitempty" json:"labels,omitempty"`
 	WithLabelDetails *bool      `url:"with_labels_details,omitempty" json:"with_labels_details,omitempty"`
 	Milestone        *string    `url:"milestone,omitempty" json:"milestone,omitempty"`
 	Scope            *string    `url:"scope,omitempty" json:"scope,omitempty"`
@@ -280,7 +280,7 @@ type CreateIssueOptions struct {
 	Confidential                       *bool      `url:"confidential,omitempty" json:"confidential,omitempty"`
 	AssigneeIDs                        []int      `url:"assignee_ids,omitempty" json:"assignee_ids,omitempty"`
 	MilestoneID                        *int       `url:"milestone_id,omitempty" json:"milestone_id,omitempty"`
-	Labels                             *Labels    `url:"labels,omitempty" json:"labels,omitempty"`
+	Labels                             Labels     `url:"labels,comma,omitempty" json:"labels,omitempty"`
 	CreatedAt                          *time.Time `url:"created_at,omitempty" json:"created_at,omitempty"`
 	DueDate                            *ISOTime   `url:"due_date,omitempty" json:"due_date,omitempty"`
 	MergeRequestToResolveDiscussionsOf *int       `url:"merge_request_to_resolve_discussions_of,omitempty" json:"merge_request_to_resolve_discussions_of,omitempty"`
@@ -321,7 +321,7 @@ type UpdateIssueOptions struct {
 	Confidential     *bool      `url:"confidential,omitempty" json:"confidential,omitempty"`
 	AssigneeIDs      []int      `url:"assignee_ids,omitempty" json:"assignee_ids,omitempty"`
 	MilestoneID      *int       `url:"milestone_id,omitempty" json:"milestone_id,omitempty"`
-	Labels           *Labels    `url:"labels,omitempty" json:"labels,omitempty"`
+	Labels           Labels     `url:"labels,comma,omitempty" json:"labels,omitempty"`
 	StateEvent       *string    `url:"state_event,omitempty" json:"state_event,omitempty"`
 	UpdatedAt        *time.Time `url:"updated_at,omitempty" json:"updated_at,omitempty"`
 	DueDate          *ISOTime   `url:"due_date,omitempty" json:"due_date,omitempty"`
diff --git a/merge_requests.go b/merge_requests.go
index 834484a..918b163 100644
--- a/merge_requests.go
+++ b/merge_requests.go
@@ -50,7 +50,7 @@ type MergeRequest struct {
 	Assignees                 []*BasicUser `json:"assignees"`
 	SourceProjectID           int          `json:"source_project_id"`
 	TargetProjectID           int          `json:"target_project_id"`
-	Labels                    Labels       `json:"labels"`
+	Labels                    []string     `json:"labels"`
 	Description               string       `json:"description"`
 	WorkInProgress            bool         `json:"work_in_progress"`
 	Milestone                 *Milestone   `json:"milestone"`
@@ -136,7 +136,7 @@ type ListMergeRequestsOptions struct {
 	Sort            *string    `url:"sort,omitempty" json:"sort,omitempty"`
 	Milestone       *string    `url:"milestone,omitempty" json:"milestone,omitempty"`
 	View            *string    `url:"view,omitempty" json:"view,omitempty"`
-	Labels          *Labels    `url:"labels,omitempty" json:"labels,omitempty"`
+	Labels          Labels     `url:"labels,omitempty" json:"labels,omitempty"`
 	CreatedAfter    *time.Time `url:"created_after,omitempty" json:"created_after,omitempty"`
 	CreatedBefore   *time.Time `url:"created_before,omitempty" json:"created_before,omitempty"`
 	UpdatedAfter    *time.Time `url:"updated_after,omitempty" json:"updated_after,omitempty"`
@@ -186,7 +186,7 @@ type ListGroupMergeRequestsOptions struct {
 	Sort            *string    `url:"sort,omitempty" json:"sort,omitempty"`
 	Milestone       *string    `url:"milestone,omitempty" json:"milestone,omitempty"`
 	View            *string    `url:"view,omitempty" json:"view,omitempty"`
-	Labels          *Labels    `url:"labels,omitempty" json:"labels,omitempty"`
+	Labels          Labels     `url:"labels,omitempty" json:"labels,omitempty"`
 	CreatedAfter    *time.Time `url:"created_after,omitempty" json:"created_after,omitempty"`
 	CreatedBefore   *time.Time `url:"created_before,omitempty" json:"created_before,omitempty"`
 	UpdatedAfter    *time.Time `url:"updated_after,omitempty" json:"updated_after,omitempty"`
@@ -238,7 +238,7 @@ type ListProjectMergeRequestsOptions struct {
 	Sort            *string    `url:"sort,omitempty" json:"sort,omitempty"`
 	Milestone       *string    `url:"milestone,omitempty" json:"milestone,omitempty"`
 	View            *string    `url:"view,omitempty" json:"view,omitempty"`
-	Labels          *Labels    `url:"labels,omitempty" json:"labels,omitempty"`
+	Labels          Labels     `url:"labels,omitempty" json:"labels,omitempty"`
 	CreatedAfter    *time.Time `url:"created_after,omitempty" json:"created_after,omitempty"`
 	CreatedBefore   *time.Time `url:"created_before,omitempty" json:"created_before,omitempty"`
 	UpdatedAfter    *time.Time `url:"updated_after,omitempty" json:"updated_after,omitempty"`
@@ -465,7 +465,7 @@ type CreateMergeRequestOptions struct {
 	Description        *string `url:"description,omitempty" json:"description,omitempty"`
 	SourceBranch       *string `url:"source_branch,omitempty" json:"source_branch,omitempty"`
 	TargetBranch       *string `url:"target_branch,omitempty" json:"target_branch,omitempty"`
-	Labels             *Labels `url:"labels,comma,omitempty" json:"labels,omitempty"`
+	Labels             Labels  `url:"labels,comma,omitempty" json:"labels,omitempty"`
 	AssigneeID         *int    `url:"assignee_id,omitempty" json:"assignee_id,omitempty"`
 	AssigneeIDs        []int   `url:"assignee_ids,omitempty" json:"assignee_ids,omitempty"`
 	TargetProjectID    *int    `url:"target_project_id,omitempty" json:"target_project_id,omitempty"`
@@ -511,7 +511,7 @@ type UpdateMergeRequestOptions struct {
 	TargetBranch       *string `url:"target_branch,omitempty" json:"target_branch,omitempty"`
 	AssigneeID         *int    `url:"assignee_id,omitempty" json:"assignee_id,omitempty"`
 	AssigneeIDs        []int   `url:"assignee_ids,omitempty" json:"assignee_ids,omitempty"`
-	Labels             *Labels `url:"labels,comma,omitempty" json:"labels,omitempty"`
+	Labels             Labels  `url:"labels,comma,omitempty" json:"labels,omitempty"`
 	MilestoneID        *int    `url:"milestone_id,omitempty" json:"milestone_id,omitempty"`
 	StateEvent         *string `url:"state_event,omitempty" json:"state_event,omitempty"`
 	RemoveSourceBranch *bool   `url:"remove_source_branch,omitempty" json:"remove_source_branch,omitempty"`
diff --git a/project_members.go b/project_members.go
index 54a25fd..abd964a 100644
--- a/project_members.go
+++ b/project_members.go
@@ -124,7 +124,6 @@ func (s *ProjectMembersService) GetProjectMember(pid interface{}, user int, opti
 type AddProjectMemberOptions struct {
 	UserID      *int              `url:"user_id,omitempty" json:"user_id,omitempty"`
 	AccessLevel *AccessLevelValue `url:"access_level,omitempty" json:"access_level,omitempty"`
-	ExpiresAt   *string           `url:"expires_at,omitempty" json:"expires_at"`
 }
 
 // AddProjectMember adds a user to a project team. This is an idempotent
@@ -161,7 +160,6 @@ func (s *ProjectMembersService) AddProjectMember(pid interface{}, opt *AddProjec
 // https://docs.gitlab.com/ce/api/members.html#edit-a-member-of-a-group-or-project
 type EditProjectMemberOptions struct {
 	AccessLevel *AccessLevelValue `url:"access_level,omitempty" json:"access_level,omitempty"`
-	ExpiresAt   *string           `url:"expires_at,omitempty" json:"expires_at"`
 }
 
 // EditProjectMember updates a project team member to a specified access level..
diff --git a/projects.go b/projects.go
index 90a1307..f5e2035 100644
--- a/projects.go
+++ b/projects.go
@@ -449,9 +449,6 @@ type CreateProjectOptions struct {
 	Mirror                                    *bool             `url:"mirror,omitempty" json:"mirror,omitempty"`
 	MirrorTriggerBuilds                       *bool             `url:"mirror_trigger_builds,omitempty" json:"mirror_trigger_builds,omitempty"`
 	InitializeWithReadme                      *bool             `url:"initialize_with_readme,omitempty" json:"initialize_with_readme,omitempty"`
-	TemplateName                              *string           `url:"template_name,omitempty" json:"template_name,omitempty"`
-	UseCustomTemplate                         *bool             `url:"use_custom_template,omitempty" json:"use_custom_template,omitempty"`
-	GroupWithProjectTemplatesID               *int              `url:"group_with_project_templates_id,omitempty" json:"group_with_project_templates_id,omitempty"`
 }
 
 // CreateProject creates a new project owned by the authenticated user.
@@ -772,7 +769,6 @@ type ProjectMember struct {
 	Name        string           `json:"name"`
 	State       string           `json:"state"`
 	CreatedAt   *time.Time       `json:"created_at"`
-	ExpiresAt   *ISOTime         `json:"expires_at"`
 	AccessLevel AccessLevelValue `json:"access_level"`
 }
 
@@ -1319,8 +1315,8 @@ func (s *ProjectsService) ChangeApprovalConfiguration(pid interface{}, opt *Chan
 // GitLab API docs:
 // https://docs.gitlab.com/ee/api/merge_request_approvals.html#change-allowed-approvers
 type ChangeAllowedApproversOptions struct {
-	ApproverIDs      []*int `url:"approver_ids" json:"approver_ids"`
-	ApproverGroupIDs []*int `url:"approver_group_ids" json:"approver_group_ids"`
+	ApproverIDs      []*int `url:"approver_ids,omitempty" json:"approver_ids,omitempty"`
+	ApproverGroupIDs []*int `url:"approver_group_ids,omitempty" json:"approver_group_ids,omitempty"`
 }
 
 // ChangeAllowedApprovers updates the list of approvers and approver groups.
diff --git a/runners.go b/runners.go
index d3330d4..365b612 100644
--- a/runners.go
+++ b/runners.go
@@ -72,11 +72,6 @@ type RunnerDetails struct {
 	Version        string   `json:"version"`
 	AccessLevel    string   `json:"access_level"`
 	MaximumTimeout int      `json:"maximum_timeout"`
-	Groups         []struct {
-		ID     int    `json:"id"`
-		Name   string `json:"name"`
-		WebURL string `json:"web_url"`
-	} `json:"groups"`
 }
 
 // ListRunnersOptions represents the available ListRunners() options.
@@ -85,10 +80,9 @@ type RunnerDetails struct {
 // https://docs.gitlab.com/ce/api/runners.html#list-owned-runners
 type ListRunnersOptions struct {
 	ListOptions
-	Scope   *string  `url:"scope,omitempty" json:"scope,omitempty"`
-	Type    *string  `url:"type,omitempty" json:"type,omitempty"`
-	Status  *string  `url:"status,omitempty" json:"status,omitempty"`
-	TagList []string `url:"tag_list,comma,omitempty" json:"tag_list,omitempty"`
+	Scope  *string `url:"scope,omitempty" json:"scope,omitempty"`
+	Status *string `url:"status,omitempty" json:"status,omitempty"`
+	Type   *string `url:"type,omitempty" json:"type,omitempty"`
 }
 
 // ListRunners gets a list of runners accessible by the authenticated user.
@@ -217,12 +211,10 @@ func (s *RunnersService) RemoveRunner(rid interface{}, options ...OptionFunc) (*
 // options. Status can be one of: running, success, failed, canceled.
 //
 // GitLab API docs:
-// https://docs.gitlab.com/ce/api/runners.html#list-runners-jobs
+// https://docs.gitlab.com/ce/api/runners.html#list-runner-39-s-jobs
 type ListRunnerJobsOptions struct {
 	ListOptions
-	Status  *string `url:"status,omitempty" json:"status,omitempty"`
-	OrderBy *string `url:"order_by,omitempty" json:"order_by,omitempty"`
-	Sort    *string `url:"sort,omitempty" json:"sort,omitempty"`
+	Status *string `url:"status,omitempty" json:"status,omitempty"`
 }
 
 // ListRunnerJobs gets a list of jobs that are being processed or were processed by specified Runner.
diff --git a/services.go b/services.go
index e9df5a6..8c1c553 100644
--- a/services.go
+++ b/services.go
@@ -255,20 +255,27 @@ type SlackService struct {
 // GitLab API docs:
 // https://docs.gitlab.com/ce/api/services.html#slack
 type SlackServiceProperties struct {
+	// Note: NotifyOnlyBrokenPipelines and NotifyOnlyDefaultBranch are not
+	// just "bool" because in some cases gitlab returns
+	// "notify_only_broken_pipelines": true, and in other cases
+	// "notify_only_broken_pipelines": "1". The same is for
+	// "notify_only_default_branch" field.
+	// We need to handle this, until the bug will be fixed.
+	// Ref: https://gitlab.com/gitlab-org/gitlab-ce/issues/50122
+
+	NotifyOnlyBrokenPipelines BoolValue `json:"notify_only_broken_pipelines,omitempty"`
+	NotifyOnlyDefaultBranch   BoolValue `json:"notify_only_default_branch,omitempty"`
 	WebHook                   string    `json:"webhook,omitempty"`
 	Username                  string    `json:"username,omitempty"`
 	Channel                   string    `json:"channel,omitempty"`
-	NotifyOnlyBrokenPipelines BoolValue `json:"notify_only_broken_pipelines,omitempty"`
-	NotifyOnlyDefaultBranch   BoolValue `json:"notify_only_default_branch,omitempty"`
-	ConfidentialIssueChannel  string    `json:"confidential_issue_channel,omitempty"`
-	ConfidentialNoteChannel   string    `json:"confidential_note_channel,omitempty"`
-	DeploymentChannel         string    `json:"deployment_channel,omitempty"`
+	PushChannel               string    `json:"push_channel,omitempty"`
 	IssueChannel              string    `json:"issue_channel,omitempty"`
+	ConfidentialIssueChannel  string    `json:"confidential_issue_channel,omitempty"`
 	MergeRequestChannel       string    `json:"merge_request_channel,omitempty"`
 	NoteChannel               string    `json:"note_channel,omitempty"`
+	ConfidentialNoteChannel   string    `json:"confidential_note_channel,omitempty"`
 	TagPushChannel            string    `json:"tag_push_channel,omitempty"`
 	PipelineChannel           string    `json:"pipeline_channel,omitempty"`
-	PushChannel               string    `json:"push_channel,omitempty"`
 	WikiPageChannel           string    `json:"wiki_page_channel,omitempty"`
 }
 
@@ -308,29 +315,27 @@ type SetSlackServiceOptions struct {
 	Channel                   *string `url:"channel,omitempty" json:"channel,omitempty"`
 	NotifyOnlyBrokenPipelines *bool   `url:"notify_only_broken_pipelines,omitempty" json:"notify_only_broken_pipelines,omitempty"`
 	NotifyOnlyDefaultBranch   *bool   `url:"notify_only_default_branch,omitempty" json:"notify_only_default_branch,omitempty"`
-	ConfidentialIssueChannel  *string `url:"confidential_issue_channel,omitempty" json:"confidential_issue_channel,omitempty"`
+	PushEvents                *bool   `url:"push_events,omitempty" json:"push_events,omitempty"`
+	PushChannel               *string `url:"push_channel,omitempty" json:"push_channel,omitempty"`
+	IssuesEvents              *bool   `url:"issues_events,omitempty" json:"issues_events,omitempty"`
+	IssueChannel              *string `url:"issue_channel,omitempty" json:"issue_channel,omitempty"`
 	ConfidentialIssuesEvents  *bool   `url:"confidential_issues_events,omitempty" json:"confidential_issues_events,omitempty"`
+	ConfidentialIssueChannel  *string `url:"confidential_issue_channel,omitempty" json:"confidential_issue_channel,omitempty"`
+	MergeRequestsEvents       *bool   `url:"merge_requests_events,omitempty" json:"merge_requests_events,omitempty"`
+	MergeRequestChannel       *string `url:"merge_request_channel,omitempty" json:"merge_request_channel,omitempty"`
+	TagPushEvents             *bool   `url:"tag_push_events,omitempty" json:"tag_push_events,omitempty"`
+	TagPushChannel            *string `url:"tag_push_channel,omitempty" json:"tag_push_channel,omitempty"`
+	NoteEvents                *bool   `url:"note_events,omitempty" json:"note_events,omitempty"`
+	NoteChannel               *string `url:"note_channel,omitempty" json:"note_channel,omitempty"`
+	ConfidentialNoteEvents    *bool   `url:"confidential_note_events" json:"confidential_note_events"`
 	// TODO: Currently, GitLab ignores this option (not implemented yet?), so
 	// there is no way to set it. Uncomment when this is fixed.
 	// See: https://gitlab.com/gitlab-org/gitlab-ce/issues/49730
 	//ConfidentialNoteChannel   *string `json:"confidential_note_channel,omitempty"`
-	ConfidentialNoteEvents *bool   `url:"confidential_note_events,omitempty" json:"confidential_note_events,omitempty"`
-	DeploymentChannel      *string `url:"deployment_channel,omitempty" json:"deployment_channel,omitempty"`
-	DeploymentEvents       *bool   `url:"deployment_events,omitempty" json:"deployment_events,omitempty"`
-	IssueChannel           *string `url:"issue_channel,omitempty" json:"issue_channel,omitempty"`
-	IssuesEvents           *bool   `url:"issues_events,omitempty" json:"issues_events,omitempty"`
-	MergeRequestChannel    *string `url:"merge_request_channel,omitempty" json:"merge_request_channel,omitempty"`
-	MergeRequestsEvents    *bool   `url:"merge_requests_events,omitempty" json:"merge_requests_events,omitempty"`
-	TagPushChannel         *string `url:"tag_push_channel,omitempty" json:"tag_push_channel,omitempty"`
-	TagPushEvents          *bool   `url:"tag_push_events,omitempty" json:"tag_push_events,omitempty"`
-	NoteChannel            *string `url:"note_channel,omitempty" json:"note_channel,omitempty"`
-	NoteEvents             *bool   `url:"note_events,omitempty" json:"note_events,omitempty"`
-	PipelineChannel        *string `url:"pipeline_channel,omitempty" json:"pipeline_channel,omitempty"`
-	PipelineEvents         *bool   `url:"pipeline_events,omitempty" json:"pipeline_events,omitempty"`
-	PushChannel            *string `url:"push_channel,omitempty" json:"push_channel,omitempty"`
-	PushEvents             *bool   `url:"push_events,omitempty" json:"push_events,omitempty"`
-	WikiPageChannel        *string `url:"wiki_page_channel,omitempty" json:"wiki_page_channel,omitempty"`
-	WikiPageEvents         *bool   `url:"wiki_page_events,omitempty" json:"wiki_page_events,omitempty"`
+	PipelineEvents  *bool   `url:"pipeline_events,omitempty" json:"pipeline_events,omitempty"`
+	PipelineChannel *string `url:"pipeline_channel,omitempty" json:"pipeline_channel,omitempty"`
+	WikiPageChannel *string `url:"wiki_page_channel,omitempty" json:"wiki_page_channel,omitempty"`
+	WikiPageEvents  *bool   `url:"wiki_page_events" json:"wiki_page_events"`
 }
 
 // SetSlackService sets Slack service for a project


Test patch:
diff --git a/merge_requests_test.go b/merge_requests_test.go
index a7baaf5..3edb376 100644
--- a/merge_requests_test.go
+++ b/merge_requests_test.go
@@ -27,7 +27,7 @@ var (
 		WebURL:    "https://gitlab.com/tkuah",
 	}
 	getOpts = GetMergeRequestsOptions{}
-	labels  = Labels{
+	labels  = []string{
 		"GitLab Enterprise Edition",
 		"backend",
 		"database",