nebius/SWE-rebench-V2-PRs huggingface dataset:
Repository: xanzy/go-gitlab
Pull request #635 (2019-06-11 17:15:13)

Add support for merge_requests_author_approval in ApprovalConfigurations


Patch:
diff --git a/projects.go b/projects.go
index 8bd4f3f..3117b40 100644
--- a/projects.go
+++ b/projects.go
@@ -1243,6 +1243,7 @@ type ProjectApprovals struct {
 	ApprovalsBeforeMerge                      int                          `json:"approvals_before_merge"`
 	ResetApprovalsOnPush                      bool                         `json:"reset_approvals_on_push"`
 	DisableOverridingApproversPerMergeRequest bool                         `json:"disable_overriding_approvers_per_merge_request"`
+	MergeRequestsAuthorApproval               bool                         `json:"merge_requests_author_approval"`
 }
 
 // GetApprovalConfiguration get the approval configuration for a project.
@@ -1279,6 +1280,7 @@ type ChangeApprovalConfigurationOptions struct {
 	ApprovalsBeforeMerge                      *int  `url:"approvals_before_merge,omitempty" json:"approvals_before_merge,omitempty"`
 	ResetApprovalsOnPush                      *bool `url:"reset_approvals_on_push,omitempty" json:"reset_approvals_on_push,omitempty"`
 	DisableOverridingApproversPerMergeRequest *bool `url:"disable_overriding_approvers_per_merge_request,omitempty" json:"disable_overriding_approvers_per_merge_request,omitempty"`
+	MergeRequestsAuthorApproval               *bool `url:"merge_requests_author_approval,omitempty" json:"merge_requests_author_approval,omitempty"`
 }
 
 // ChangeApprovalConfiguration updates the approval configuration for a project.


Test patch:
diff --git a/projects_test.go b/projects_test.go
index a15a0fb..abe5255 100644
--- a/projects_test.go
+++ b/projects_test.go
@@ -399,7 +399,8 @@ func TestGetApprovalConfiguration(t *testing.T) {
 			"approver_groups": [],
 			"approvals_before_merge": 3,
 			"reset_approvals_on_push": false,
-			"disable_overriding_approvers_per_merge_request": false
+			"disable_overriding_approvers_per_merge_request": false,
+			"merge_requests_author_approval": true
 		}`)
 	})
 
@@ -414,6 +415,7 @@ func TestGetApprovalConfiguration(t *testing.T) {
 		ApprovalsBeforeMerge: 3,
 		ResetApprovalsOnPush: false,
 		DisableOverridingApproversPerMergeRequest: false,
+		MergeRequestsAuthorApproval:               true,
 	}
 
 	if !reflect.DeepEqual(want, approvals) {
@@ -433,7 +435,8 @@ func TestChangeApprovalConfiguration(t *testing.T) {
 			"approver_groups": [],
 			"approvals_before_merge": 3,
 			"reset_approvals_on_push": false,
-			"disable_overriding_approvers_per_merge_request": false
+			"disable_overriding_approvers_per_merge_request": false,
+			"merge_requests_author_approval": true
 		}`)
 	})
 
@@ -452,6 +455,7 @@ func TestChangeApprovalConfiguration(t *testing.T) {
 		ApprovalsBeforeMerge: 3,
 		ResetApprovalsOnPush: false,
 		DisableOverridingApproversPerMergeRequest: false,
+		MergeRequestsAuthorApproval:               true,
 	}
 
 	if !reflect.DeepEqual(want, approvals) {
Repository: xanzy/go-gitlab
Pull request #641 (2019-06-22 10:06:05)

implemneted get environment
I am using this package quite a bit, would be happy to give back some.  
This change was tested with real gitlab api, made sure it didn't break ListEnvironments either.  



Patch:
diff --git a/environments.go b/environments.go
index f7c81f0..ee77307 100644
--- a/environments.go
+++ b/environments.go
@@ -32,10 +32,11 @@ type EnvironmentsService struct {
 //
 // GitLab API docs: https://docs.gitlab.com/ce/api/environments.html
 type Environment struct {
-	ID          int    `json:"id"`
-	Name        string `json:"name"`
-	Slug        string `json:"slug"`
-	ExternalURL string `json:"external_url"`
+	ID             int         `json:"id"`
+	Name           string      `json:"name"`
+	Slug           string      `json:"slug"`
+	ExternalURL    string      `json:"external_url"`
+	LastDeployment *Deployment `json:"last_deployment"`
 }
 
 func (env Environment) String() string {
@@ -74,6 +75,31 @@ func (s *EnvironmentsService) ListEnvironments(pid interface{}, opts *ListEnviro
 	return envs, resp, err
 }
 
+// GetEnvironment gets a specific environment from a project.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ee/api/environments.html#get-a-specific-environment
+func (s *EnvironmentsService) GetEnvironment(pid interface{}, environment int, options ...OptionFunc) (*Environment, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("projects/%s/environments/%d", pathEscape(project), environment)
+
+	req, err := s.client.NewRequest("GET", u, nil, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	env := new(Environment)
+	resp, err := s.client.Do(req, env)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return env, resp, err
+}
+
 // CreateEnvironmentOptions represents the available CreateEnvironment() options.
 //
 // GitLab API docs:


Test patch:
diff --git a/environments_test.go b/environments_test.go
index 329c667..281c43a 100644
--- a/environments_test.go
+++ b/environments_test.go
@@ -29,6 +29,26 @@ func TestListEnvironments(t *testing.T) {
 	}
 }
 
+func TestGetEnvironment(t *testing.T) {
+	mux, server, client := setup()
+	defer teardown(server)
+
+	mux.HandleFunc("/api/v4/projects/1/environments/5949167", func(w http.ResponseWriter, r *http.Request) {
+		testMethod(t, r, "GET")
+		fmt.Fprint(w, `{"id":1,"name":"test/test"}`)
+	})
+
+	env, _, err := client.Environments.GetEnvironment(1, 5949167)
+	if err != nil {
+		t.Errorf("Environemtns.GetEnvironment returned error: %v", err)
+	}
+
+	want := &Environment{ID: 1, Name: "test/test"}
+	if !reflect.DeepEqual(want, env) {
+		t.Errorf("Environments.GetEnvironment returned %+v, want %+v", env, want)
+	}
+}
+
 func TestCreateEnvironment(t *testing.T) {
 	mux, server, client := setup()
 	defer teardown(server)
Repository: xanzy/go-gitlab
Pull request #725 (2019-10-28 06:29:02)

Add `variable_type` to PipelineVariable and PipelineScheduleVariables
This PR adds `variable_type` to pipeline variable and pipeline schedule variables.

Docs for the attribute: https://docs.gitlab.com/ee/api/pipeline_schedules.html#create-a-new-pipeline-schedule-variable


Patch:
diff --git a/pipeline_schedules.go b/pipeline_schedules.go
index 3790ea3..ebcf829 100644
--- a/pipeline_schedules.go
+++ b/pipeline_schedules.go
@@ -236,8 +236,9 @@ func (s *PipelineSchedulesService) DeletePipelineSchedule(pid interface{}, sched
 // GitLab API docs:
 // https://docs.gitlab.com/ce/api/pipeline_schedules.html#create-a-new-pipeline-schedule
 type CreatePipelineScheduleVariableOptions struct {
-	Key   *string `url:"key" json:"key"`
-	Value *string `url:"value" json:"value"`
+	Key          *string `url:"key" json:"key"`
+	Value        *string `url:"value" json:"value"`
+	VariableType *string `url:"variable_type,omitempty" json:"variable_type,omitempty"`
 }
 
 // CreatePipelineScheduleVariable creates a pipeline schedule variable.
@@ -271,7 +272,8 @@ func (s *PipelineSchedulesService) CreatePipelineScheduleVariable(pid interface{
 // GitLab API docs:
 // https://docs.gitlab.com/ce/api/pipeline_schedules.html#edit-a-pipeline-schedule-variable
 type EditPipelineScheduleVariableOptions struct {
-	Value *string `url:"value" json:"value"`
+	Value        *string `url:"value" json:"value"`
+	VariableType *string `url:"variable_type,omitempty" json:"variable_type,omitempty"`
 }
 
 // EditPipelineScheduleVariable creates a pipeline schedule variable.
diff --git a/pipelines.go b/pipelines.go
index bf3f814..972a5b6 100644
--- a/pipelines.go
+++ b/pipelines.go
@@ -33,8 +33,9 @@ type PipelinesService struct {
 //
 // GitLab API docs: https://docs.gitlab.com/ce/api/pipelines.html
 type PipelineVariable struct {
-	Key   string `json:"key"`
-	Value string `json:"value"`
+	Key          string `json:"key"`
+	Value        string `json:"value"`
+	VariableType string `json:"variable_type"`
 }
 
 // Pipeline represents a GitLab pipeline.


Test patch:
diff --git a/pipelines_test.go b/pipelines_test.go
index 2e16e52..94ccd25 100644
--- a/pipelines_test.go
+++ b/pipelines_test.go
@@ -62,7 +62,7 @@ func TestGetPipelineVariables(t *testing.T) {
 		t.Errorf("Pipelines.GetPipelineVariables returned error: %v", err)
 	}
 
-	want := []*PipelineVariable{{Key: "RUN_NIGHTLY_BUILD", Value: "true"}, {Key: "foo", Value: "bar"}}
+	want := []*PipelineVariable{{Key: "RUN_NIGHTLY_BUILD", Value: "true", VariableType: "env_var"}, {Key: "foo", Value: "bar"}}
 	if !reflect.DeepEqual(want, variables) {
 		t.Errorf("Pipelines.GetPipelineVariables returned %+v, want %+v", variables, want)
 	}
Repository: xanzy/go-gitlab
Pull request #729 (2019-11-04 04:18:56)

Update environment structure


Patch:
diff --git a/environments.go b/environments.go
index ee77307..397c2d2 100644
--- a/environments.go
+++ b/environments.go
@@ -35,7 +35,9 @@ type Environment struct {
 	ID             int         `json:"id"`
 	Name           string      `json:"name"`
 	Slug           string      `json:"slug"`
+	State          string      `json:"state"`
 	ExternalURL    string      `json:"external_url"`
+	Project        *Project    `json:"project"`
 	LastDeployment *Deployment `json:"last_deployment"`
 }
 


Test patch:
diff --git a/environments_test.go b/environments_test.go
index 281c43a..0b42c01 100644
--- a/environments_test.go
+++ b/environments_test.go
@@ -1,11 +1,14 @@
 package gitlab
 
 import (
+	"encoding/json"
 	"fmt"
 	"log"
 	"net/http"
 	"reflect"
 	"testing"
+
+	"github.com/stretchr/testify/assert"
 )
 
 func TestListEnvironments(t *testing.T) {
@@ -118,3 +121,56 @@ func TestStopEnvironment(t *testing.T) {
 		log.Fatal(err)
 	}
 }
+
+func TestUnmarshal(t *testing.T) {
+	jsonObject := `
+    {
+        "id": 10,
+        "name": "production",
+        "slug": "production",
+        "external_url": "https://example.com",
+        "project": {
+            "id": 1,
+            "description": "",
+            "name": "Awesome Project",
+            "name_with_namespace": "FooBar Group / Awesome Project",
+            "path": "awesome-project",
+            "path_with_namespace": "foobar-group/awesome-project",
+            "created_at": "2017-09-30T11:10:08.476-04:00",
+            "default_branch": "develop",
+            "tag_list": [],
+            "ssh_url_to_repo": "git@example.gitlab.com:foobar-group/api.git",
+            "http_url_to_repo": "https://example.gitlab.com/foobar-group/api.git",
+            "web_url": "https://example.gitlab.com/foobar-group/api",
+            "readme_url": null,
+            "avatar_url": null,
+            "star_count": 0,
+            "forks_count": 1,
+            "last_activity_at": "2019-11-03T22:22:46.564-05:00",
+            "namespace": {
+                "id": 15,
+                "name": "FooBar Group",
+                "path": "foobar-group",
+                "kind": "group",
+                "full_path": "foobar-group",
+                "parent_id": null,
+                "avatar_url": null,
+                "web_url": "https://example.gitlab.com/groups/foobar-group"
+            }
+        },
+        "state": "available"
+    }`
+
+	var env Environment
+	err := json.Unmarshal([]byte(jsonObject), &env)
+
+	if assert.NoError(t, err) {
+		assert.Equal(t, 10, env.ID)
+		assert.Equal(t, "production", env.Name)
+		assert.Equal(t, "https://example.com", env.ExternalURL)
+		assert.Equal(t, "available", env.State)
+		if assert.NotNil(t, env.Project) {
+			assert.Equal(t, "Awesome Project", env.Project.Name)
+		}
+	}
+}
\ No newline at end of file
Repository: xanzy/go-gitlab
Pull request #735 (2019-11-16 18:11:09)

Add `Locked` to `RunnerDetails` struct
It's available in the API, but not exposed until now.


Patch:
diff --git a/runners.go b/runners.go
index d3330d4..eab394d 100644
--- a/runners.go
+++ b/runners.go
@@ -70,6 +70,7 @@ type RunnerDetails struct {
 	Revision       string   `json:"revision"`
 	TagList        []string `json:"tag_list"`
 	Version        string   `json:"version"`
+	Locked         bool `json:"locked"`
 	AccessLevel    string   `json:"access_level"`
 	MaximumTimeout int      `json:"maximum_timeout"`
 	Groups         []struct {


Test patch:
diff --git a/runners_test.go b/runners_test.go
index 6dadd96..b74f6f3 100644
--- a/runners_test.go
+++ b/runners_test.go
@@ -104,7 +104,8 @@ const exampleDetailRsp = `{
 	],
 	"version": null,
 	"access_level": "ref_protected",
-	"maximum_timeout": 3600
+	"maximum_timeout": 3600,
+	"locked": false
 }`
 
 func TestUpdateRunnersDetails(t *testing.T) {
@@ -178,6 +179,7 @@ func expectedParsedDetails() *RunnerDetails {
 			PathWithNamespace string `json:"path_with_namespace"`
 		}{proj},
 		MaximumTimeout: 3600,
+		Locked:         false,
 	}
 }
Repository: xanzy/go-gitlab
Pull request #785 (2020-02-26 18:29:34)

Add jira service flags
Jira integration like to spam a lot, these flags will save many
eyeballs.


Patch:
diff --git a/services.go b/services.go
index bc75d42..0dd441c 100644
--- a/services.go
+++ b/services.go
@@ -536,6 +536,9 @@ type JiraServiceProperties struct {
 	Username              string `json:"username,omitempty" `
 	Password              string `json:"password,omitempty" `
 	JiraIssueTransitionID string `json:"jira_issue_transition_id,omitempty"`
+	CommitEvents          bool   `json:"commit_events,omitempty" `
+	MergeRequestsEvents   bool   `json:"merge_requests_events,omitempty" `
+	CommentOnEventEnabled bool   `json:"comment_on_event_enabled,omitempty" `
 }
 
 // UnmarshalJSON decodes the Jira Service Properties.
@@ -605,6 +608,9 @@ type SetJiraServiceOptions struct {
 	Username              *string `url:"username,omitempty" json:"username,omitempty" `
 	Password              *string `url:"password,omitempty" json:"password,omitempty" `
 	JiraIssueTransitionID *string `url:"jira_issue_transition_id,omitempty" json:"jira_issue_transition_id,omitempty"`
+	CommitEvents          *bool   `url:"commit_events,omitempty" json:"commit_events,omitempty"`
+	MergeRequestsEvents   *bool   `url:"merge_requests_events,omitempty" json:"merge_requests_events,omitempty"`
+	CommentOnEventEnabled *bool   `url:"comment_on_event_enabled,omitempty" json:"comment_on_event_enabled,omitempty"`
 }
 
 // SetJiraService sets Jira service for a project


Test patch:
diff --git a/services_test.go b/services_test.go
index 7380c18..7bb6dab 100644
--- a/services_test.go
+++ b/services_test.go
@@ -132,6 +132,9 @@ func TestSetJiraService(t *testing.T) {
 		Username:              String("aas"),
 		Password:              String("asd"),
 		JiraIssueTransitionID: String("2,3"),
+		CommitEvents:          Bool(true),
+		CommentOnEventEnabled: Bool(true),
+		MergeRequestsEvents:   Bool(true),
 	}
 
 	_, err := client.Services.SetJiraService(1, opt)
Repository: xanzy/go-gitlab
Pull request #788 (2020-03-02 17:33:12)

Add support for HasConflicts attribute


Patch:
diff --git a/merge_requests.go b/merge_requests.go
index 5b90de5..7b64458 100644
--- a/merge_requests.go
+++ b/merge_requests.go
@@ -98,6 +98,7 @@ type MergeRequest struct {
 		Count          int `json:"count"`
 		CompletedCount int `json:"completed_count"`
 	} `json:"task_completion_status"`
+	HasConflicts bool `json:"has_conflicts"`
 }
 
 func (m MergeRequest) String() string {


Test patch:
diff --git a/merge_requests_test.go b/merge_requests_test.go
index 9bc39bf..361487a 100644
--- a/merge_requests_test.go
+++ b/merge_requests_test.go
@@ -120,6 +120,7 @@ func TestGetMergeRequest(t *testing.T) {
 	require.Equal(t, mergeRequest.CreatedAt, &mrCreation)
 	mrUpdate := time.Date(2019, 8, 20, 9, 9, 56, 690000000, time.UTC)
 	require.Equal(t, mergeRequest.UpdatedAt, &mrUpdate)
+	require.Equal(t, mergeRequest.HasConflicts, true)
 }
 
 func TestListProjectMergeRequests(t *testing.T) {
diff --git a/testdata/get_merge_request.json b/testdata/get_merge_request.json
index 3c1a503..af4447a 100644
--- a/testdata/get_merge_request.json
+++ b/testdata/get_merge_request.json
@@ -148,5 +148,6 @@
   "user": {
     "can_merge": false
   },
-  "approvals_before_merge": 1
+  "approvals_before_merge": 1,
+  "has_conflicts": true
 }
Repository: xanzy/go-gitlab
Pull request #883 (2020-07-05 19:22:06)

Add labels.PromoteLabel
Add more labels filter and hability to promote project label to group one.

pseudo code sample
```
	opt := &gitlab.ListLabelsOptions{
		ListOptions: gitlab.ListOptions{
			Page:    page,
			PerPage: 100,
		},
		WithCounts:            gitlab.Bool(true),
		IncludeAncestorGroups: gitlab.Bool(true),
	}
	labels, resp, err = git.Labels.ListLabels(666, opt, nil)
	if err != nil {
		log.Fatalf("Error listing: %v (%v)", err.Error(), resp)
	}

	for _, label := range labels {
		if !label.IsProjectLabel {
			log.Print(label.Name)
			continue
		}
		log.Printf("Promote: %v", label.Name)
		git.Labels.PromoteLabel(666, label.ID)
	}
```


Patch:
diff --git a/labels.go b/labels.go
index 37e7277..a126bda 100644
--- a/labels.go
+++ b/labels.go
@@ -73,7 +73,11 @@ func (l Label) String() string {
 // ListLabelsOptions represents the available ListLabels() options.
 //
 // GitLab API docs: https://docs.gitlab.com/ce/api/labels.html#list-labels
-type ListLabelsOptions ListOptions
+type ListLabelsOptions struct {
+	ListOptions
+	WithCounts            *bool `url:"with_counts,omitempty" json:"with_counts,omitempty"`
+	IncludeAncestorGroups *bool `url:"include_ancestor_groups,omitempty" json:"include_ancestor_groups,omitempty"`
+}
 
 // ListLabels gets all labels for given project.
 //
@@ -248,3 +252,26 @@ func (s *LabelsService) UnsubscribeFromLabel(pid interface{}, labelID interface{
 
 	return s.client.Do(req, nil)
 }
+
+// PromoteLabel Promotes a project label to a group label.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/labels.html#promote-a-project-label-to-a-group-label
+func (s *LabelsService) PromoteLabel(pid interface{}, labelID interface{}, options ...RequestOptionFunc) (*Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, err
+	}
+	label, err := parseID(labelID)
+	if err != nil {
+		return nil, err
+	}
+	u := fmt.Sprintf("projects/%s/labels/%s/promote", pathEscape(project), label)
+
+	req, err := s.client.NewRequest("PUT", u, nil, options)
+	if err != nil {
+		return nil, err
+	}
+
+	return s.client.Do(req, nil)
+}


Test patch:
diff --git a/labels_test.go b/labels_test.go
index a6ca327..bb99cd8 100644
--- a/labels_test.go
+++ b/labels_test.go
@@ -128,8 +128,10 @@ func TestListLabels(t *testing.T) {
 	})
 
 	o := &ListLabelsOptions{
-		Page:    1,
-		PerPage: 10,
+		ListOptions: ListOptions{
+			Page:    1,
+			PerPage: 10,
+		},
 	}
 	label, _, err := client.Labels.ListLabels("1", o)
 	if err != nil {
Repository: xanzy/go-gitlab
Pull request #902 (2020-08-06 11:45:19)

Add support for creating a pipeline for a merge request
This pull request adds support for creating a pipeline for a merge request, see https://docs.gitlab.com/ce/api/merge_requests.html#create-mr-pipeline.


Patch:
diff --git a/merge_requests.go b/merge_requests.go
index bcd41d3..ad79926 100644
--- a/merge_requests.go
+++ b/merge_requests.go
@@ -449,6 +449,31 @@ func (s *MergeRequestsService) ListMergeRequestPipelines(pid interface{}, mergeR
 	return p, resp, err
 }
 
+// CreateMergeRequestPipeline creates a new pipeline for a merge request.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/merge_requests.html#create-mr-pipeline
+func (s *MergeRequestsService) CreateMergeRequestPipeline(pid interface{}, mergeRequest int, options ...RequestOptionFunc) (*PipelineInfo, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("projects/%s/merge_requests/%d/pipelines", pathEscape(project), mergeRequest)
+
+	req, err := s.client.NewRequest("POST", u, nil, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	p := new(PipelineInfo)
+	resp, err := s.client.Do(req, p)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return p, resp, err
+}
+
 // GetIssuesClosedOnMergeOptions represents the available GetIssuesClosedOnMerge()
 // options.
 //


Test patch:
diff --git a/merge_requests_test.go b/merge_requests_test.go
index 65595b8..3d6d446 100644
--- a/merge_requests_test.go
+++ b/merge_requests_test.go
@@ -1,6 +1,7 @@
 package gitlab
 
 import (
+	"fmt"
 	"net/http"
 	"testing"
 	"time"
@@ -159,3 +160,22 @@ func TestListProjectMergeRequests(t *testing.T) {
 		assert.Equal(t, "", mr.DiffRefs.HeadSha)
 	}
 }
+
+func TestCreateMergeRequestPipeline(t *testing.T) {
+	mux, server, client := setup(t)
+	defer teardown(server)
+
+	mux.HandleFunc("/api/v4/projects/1/merge_requests/1/pipelines", func(w http.ResponseWriter, r *http.Request) {
+		testMethod(t, r, "POST")
+		fmt.Fprint(w, `{"id":1, "status":"pending"}`)
+	})
+
+	pipeline, _, err := client.MergeRequests.CreateMergeRequestPipeline(1, 1)
+
+	if err != nil {
+		t.Errorf("MergeRequests.CreateMergeRequestPipeline returned error: %v", err)
+	}
+
+	assert.Equal(t, 1, pipeline.ID)
+	assert.Equal(t, "pending", pipeline.Status)
+}
Repository: xanzy/go-gitlab
Pull request #923 (2020-08-28 00:38:37)

Add ListServices method
I was looking for this method to use in some internal code I am working on, and since I couldn't find it I thought I'd add it.


Patch:
diff --git a/services.go b/services.go
index e171749..0f36c4e 100644
--- a/services.go
+++ b/services.go
@@ -54,6 +54,30 @@ type Service struct {
 	WikiPageEvents           bool       `json:"wiki_page_events"`
 }
 
+// ListServices gets a list of all active services.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/services.html#list-all-active-services
+func (s *ServicesService) ListServices(pid interface{}, options ...RequestOptionFunc) ([]*Service, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("projects/%s/services", pathEscape(project))
+
+	req, err := s.client.NewRequest("GET", u, nil, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	var svcs []*Service
+	resp, err := s.client.Do(req, &svcs)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return svcs, resp, err
+}
+
 // DroneCIService represents Drone CI service settings.
 //
 // GitLab API docs:


Test patch:
diff --git a/services_test.go b/services_test.go
index 9c25663..c23a990 100644
--- a/services_test.go
+++ b/services_test.go
@@ -7,6 +7,25 @@ import (
 	"testing"
 )
 
+func TestListServices(t *testing.T) {
+	mux, server, client := setup(t)
+	defer teardown(server)
+
+	mux.HandleFunc("/api/v4/projects/1/services", func(w http.ResponseWriter, r *http.Request) {
+		testMethod(t, r, "GET")
+		fmt.Fprint(w, `[{"id":1},{"id":2}]`)
+	})
+	want := []*Service{{ID: 1}, {ID: 2}}
+
+	services, _, err := client.Services.ListServices(1)
+	if err != nil {
+		t.Fatalf("Services.ListServices returns an error: %v", err)
+	}
+	if !reflect.DeepEqual(want, services) {
+		t.Errorf("Services.ListServices returned %+v, want %+v", services, want)
+	}
+}
+
 func TestGetDroneCIService(t *testing.T) {
 	mux, server, client := setup(t)
 	defer teardown(server)