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	"bytes"
21	"fmt"
22	"io"
23	"mime/multipart"
24	"net/http"
25	"os"
26	"path/filepath"
27	"time"
28)
29
30// ProjectsService handles communication with the repositories related methods
31// of the GitLab API.
32//
33// GitLab API docs: https://docs.gitlab.com/ce/api/projects.html
34type ProjectsService struct {
35	client *Client
36}
37
38// Project represents a GitLab project.
39//
40// GitLab API docs: https://docs.gitlab.com/ce/api/projects.html
41type Project struct {
42	ID                                        int                        `json:"id"`
43	Description                               string                     `json:"description"`
44	DefaultBranch                             string                     `json:"default_branch"`
45	Public                                    bool                       `json:"public"`
46	Visibility                                VisibilityValue            `json:"visibility"`
47	SSHURLToRepo                              string                     `json:"ssh_url_to_repo"`
48	HTTPURLToRepo                             string                     `json:"http_url_to_repo"`
49	WebURL                                    string                     `json:"web_url"`
50	ReadmeURL                                 string                     `json:"readme_url"`
51	TagList                                   []string                   `json:"tag_list"`
52	Owner                                     *User                      `json:"owner"`
53	Name                                      string                     `json:"name"`
54	NameWithNamespace                         string                     `json:"name_with_namespace"`
55	Path                                      string                     `json:"path"`
56	PathWithNamespace                         string                     `json:"path_with_namespace"`
57	IssuesEnabled                             bool                       `json:"issues_enabled"`
58	OpenIssuesCount                           int                        `json:"open_issues_count"`
59	MergeRequestsEnabled                      bool                       `json:"merge_requests_enabled"`
60	ApprovalsBeforeMerge                      int                        `json:"approvals_before_merge"`
61	JobsEnabled                               bool                       `json:"jobs_enabled"`
62	WikiEnabled                               bool                       `json:"wiki_enabled"`
63	SnippetsEnabled                           bool                       `json:"snippets_enabled"`
64	ResolveOutdatedDiffDiscussions            bool                       `json:"resolve_outdated_diff_discussions"`
65	ContainerExpirationPolicy                 *ContainerExpirationPolicy `json:"container_expiration_policy,omitempty"`
66	ContainerRegistryEnabled                  bool                       `json:"container_registry_enabled"`
67	CreatedAt                                 *time.Time                 `json:"created_at,omitempty"`
68	LastActivityAt                            *time.Time                 `json:"last_activity_at,omitempty"`
69	CreatorID                                 int                        `json:"creator_id"`
70	Namespace                                 *ProjectNamespace          `json:"namespace"`
71	ImportStatus                              string                     `json:"import_status"`
72	ImportError                               string                     `json:"import_error"`
73	Permissions                               *Permissions               `json:"permissions"`
74	MarkedForDeletionAt                       *ISOTime                   `json:"marked_for_deletion_at"`
75	EmptyRepo                                 bool                       `json:"empty_repo"`
76	Archived                                  bool                       `json:"archived"`
77	AvatarURL                                 string                     `json:"avatar_url"`
78	LicenseURL                                string                     `json:"license_url"`
79	License                                   *ProjectLicense            `json:"license"`
80	SharedRunnersEnabled                      bool                       `json:"shared_runners_enabled"`
81	ForksCount                                int                        `json:"forks_count"`
82	StarCount                                 int                        `json:"star_count"`
83	RunnersToken                              string                     `json:"runners_token"`
84	PublicBuilds                              bool                       `json:"public_builds"`
85	AllowMergeOnSkippedPipeline               bool                       `json:"allow_merge_on_skipped_pipeline"`
86	OnlyAllowMergeIfPipelineSucceeds          bool                       `json:"only_allow_merge_if_pipeline_succeeds"`
87	OnlyAllowMergeIfAllDiscussionsAreResolved bool                       `json:"only_allow_merge_if_all_discussions_are_resolved"`
88	RemoveSourceBranchAfterMerge              bool                       `json:"remove_source_branch_after_merge"`
89	LFSEnabled                                bool                       `json:"lfs_enabled"`
90	RequestAccessEnabled                      bool                       `json:"request_access_enabled"`
91	MergeMethod                               MergeMethodValue           `json:"merge_method"`
92	ForkedFromProject                         *ForkParent                `json:"forked_from_project"`
93	Mirror                                    bool                       `json:"mirror"`
94	MirrorUserID                              int                        `json:"mirror_user_id"`
95	MirrorTriggerBuilds                       bool                       `json:"mirror_trigger_builds"`
96	OnlyMirrorProtectedBranches               bool                       `json:"only_mirror_protected_branches"`
97	MirrorOverwritesDivergedBranches          bool                       `json:"mirror_overwrites_diverged_branches"`
98	PackagesEnabled                           bool                       `json:"packages_enabled"`
99	ServiceDeskEnabled                        bool                       `json:"service_desk_enabled"`
100	ServiceDeskAddress                        string                     `json:"service_desk_address"`
101	IssuesAccessLevel                         AccessControlValue         `json:"issues_access_level"`
102	RepositoryAccessLevel                     AccessControlValue         `json:"repository_access_level"`
103	MergeRequestsAccessLevel                  AccessControlValue         `json:"merge_requests_access_level"`
104	ForkingAccessLevel                        AccessControlValue         `json:"forking_access_level"`
105	WikiAccessLevel                           AccessControlValue         `json:"wiki_access_level"`
106	BuildsAccessLevel                         AccessControlValue         `json:"builds_access_level"`
107	SnippetsAccessLevel                       AccessControlValue         `json:"snippets_access_level"`
108	PagesAccessLevel                          AccessControlValue         `json:"pages_access_level"`
109	OperationsAccessLevel                     AccessControlValue         `json:"operations_access_level"`
110	AutocloseReferencedIssues                 bool                       `json:"autoclose_referenced_issues"`
111	SuggestionCommitMessage                   string                     `json:"suggestion_commit_message"`
112	CIForwardDeploymentEnabled                bool                       `json:"ci_forward_deployment_enabled"`
113	SharedWithGroups                          []struct {
114		GroupID          int    `json:"group_id"`
115		GroupName        string `json:"group_name"`
116		GroupAccessLevel int    `json:"group_access_level"`
117	} `json:"shared_with_groups"`
118	Statistics            *ProjectStatistics `json:"statistics"`
119	Links                 *Links             `json:"_links,omitempty"`
120	CIConfigPath          string             `json:"ci_config_path"`
121	CIDefaultGitDepth     int                `json:"ci_default_git_depth"`
122	CustomAttributes      []*CustomAttribute `json:"custom_attributes"`
123	ComplianceFrameworks  []string           `json:"compliance_frameworks"`
124	BuildCoverageRegex    string             `json:"build_coverage_regex"`
125	IssuesTemplate        string             `json:"issues_template"`
126	MergeRequestsTemplate string             `json:"merge_requests_template"`
127}
128
129// BasicProject included in other service responses (such as todos).
130type BasicProject struct {
131	ID                int        `json:"id"`
132	Description       string     `json:"description"`
133	Name              string     `json:"name"`
134	NameWithNamespace string     `json:"name_with_namespace"`
135	Path              string     `json:"path"`
136	PathWithNamespace string     `json:"path_with_namespace"`
137	CreatedAt         *time.Time `json:"created_at"`
138}
139
140// ContainerExpirationPolicy represents the container expiration policy.
141type ContainerExpirationPolicy struct {
142	Cadence         string     `json:"cadence"`
143	KeepN           int        `json:"keep_n"`
144	OlderThan       string     `json:"older_than"`
145	NameRegexDelete string     `json:"name_regex_delete"`
146	NameRegexKeep   string     `json:"name_regex_keep"`
147	Enabled         bool       `json:"enabled"`
148	NextRunAt       *time.Time `json:"next_run_at"`
149}
150
151// ForkParent represents the parent project when this is a fork.
152type ForkParent struct {
153	HTTPURLToRepo     string `json:"http_url_to_repo"`
154	ID                int    `json:"id"`
155	Name              string `json:"name"`
156	NameWithNamespace string `json:"name_with_namespace"`
157	Path              string `json:"path"`
158	PathWithNamespace string `json:"path_with_namespace"`
159	WebURL            string `json:"web_url"`
160}
161
162// GroupAccess represents group access.
163type GroupAccess struct {
164	AccessLevel       AccessLevelValue       `json:"access_level"`
165	NotificationLevel NotificationLevelValue `json:"notification_level"`
166}
167
168// Links represents a project web links for self, issues, merge_requests,
169// repo_branches, labels, events, members.
170type Links struct {
171	Self          string `json:"self"`
172	Issues        string `json:"issues"`
173	MergeRequests string `json:"merge_requests"`
174	RepoBranches  string `json:"repo_branches"`
175	Labels        string `json:"labels"`
176	Events        string `json:"events"`
177	Members       string `json:"members"`
178}
179
180// Permissions represents permissions.
181type Permissions struct {
182	ProjectAccess *ProjectAccess `json:"project_access"`
183	GroupAccess   *GroupAccess   `json:"group_access"`
184}
185
186// ProjectAccess represents project access.
187type ProjectAccess struct {
188	AccessLevel       AccessLevelValue       `json:"access_level"`
189	NotificationLevel NotificationLevelValue `json:"notification_level"`
190}
191
192// ProjectLicense represent the license for a project.
193type ProjectLicense struct {
194	Key       string `json:"key"`
195	Name      string `json:"name"`
196	Nickname  string `json:"nickname"`
197	HTMLURL   string `json:"html_url"`
198	SourceURL string `json:"source_url"`
199}
200
201// ProjectNamespace represents a project namespace.
202type ProjectNamespace struct {
203	ID        int    `json:"id"`
204	Name      string `json:"name"`
205	Path      string `json:"path"`
206	Kind      string `json:"kind"`
207	FullPath  string `json:"full_path"`
208	AvatarURL string `json:"avatar_url"`
209	WebURL    string `json:"web_url"`
210}
211
212// ProjectStatistics represents a statistics record for a project.
213type ProjectStatistics struct {
214	StorageStatistics
215	CommitCount int `json:"commit_count"`
216}
217
218// Repository represents a repository.
219type Repository struct {
220	Name              string          `json:"name"`
221	Description       string          `json:"description"`
222	WebURL            string          `json:"web_url"`
223	AvatarURL         string          `json:"avatar_url"`
224	GitSSHURL         string          `json:"git_ssh_url"`
225	GitHTTPURL        string          `json:"git_http_url"`
226	Namespace         string          `json:"namespace"`
227	Visibility        VisibilityValue `json:"visibility"`
228	PathWithNamespace string          `json:"path_with_namespace"`
229	DefaultBranch     string          `json:"default_branch"`
230	Homepage          string          `json:"homepage"`
231	URL               string          `json:"url"`
232	SSHURL            string          `json:"ssh_url"`
233	HTTPURL           string          `json:"http_url"`
234}
235
236// StorageStatistics represents a statistics record for a group or project.
237type StorageStatistics struct {
238	StorageSize      int64 `json:"storage_size"`
239	RepositorySize   int64 `json:"repository_size"`
240	LfsObjectsSize   int64 `json:"lfs_objects_size"`
241	JobArtifactsSize int64 `json:"job_artifacts_size"`
242}
243
244func (s Project) String() string {
245	return Stringify(s)
246}
247
248// ProjectApprovalRule represents a GitLab project approval rule.
249//
250// GitLab API docs:
251// https://docs.gitlab.com/ee/api/merge_request_approvals.html#get-project-level-rules
252type ProjectApprovalRule struct {
253	ID                   int                `json:"id"`
254	Name                 string             `json:"name"`
255	RuleType             string             `json:"rule_type"`
256	EligibleApprovers    []*BasicUser       `json:"eligible_approvers"`
257	ApprovalsRequired    int                `json:"approvals_required"`
258	Users                []*BasicUser       `json:"users"`
259	Groups               []*Group           `json:"groups"`
260	ContainsHiddenGroups bool               `json:"contains_hidden_groups"`
261	ProtectedBranches    []*ProtectedBranch `json:"protected_branches"`
262}
263
264func (s ProjectApprovalRule) String() string {
265	return Stringify(s)
266}
267
268// ListProjectsOptions represents the available ListProjects() options.
269//
270// GitLab API docs: https://docs.gitlab.com/ce/api/projects.html#list-projects
271type ListProjectsOptions struct {
272	ListOptions
273	Archived                 *bool             `url:"archived,omitempty" json:"archived,omitempty"`
274	Visibility               *VisibilityValue  `url:"visibility,omitempty" json:"visibility,omitempty"`
275	OrderBy                  *string           `url:"order_by,omitempty" json:"order_by,omitempty"`
276	Sort                     *string           `url:"sort,omitempty" json:"sort,omitempty"`
277	Search                   *string           `url:"search,omitempty" json:"search,omitempty"`
278	SearchNamespaces         *bool             `url:"search_namespaces,omitempty" json:"search_namespaces,omitempty"`
279	Simple                   *bool             `url:"simple,omitempty" json:"simple,omitempty"`
280	Owned                    *bool             `url:"owned,omitempty" json:"owned,omitempty"`
281	Membership               *bool             `url:"membership,omitempty" json:"membership,omitempty"`
282	Starred                  *bool             `url:"starred,omitempty" json:"starred,omitempty"`
283	Statistics               *bool             `url:"statistics,omitempty" json:"statistics,omitempty"`
284	Topic                    *string           `url:"topic,omitempty" json:"topic,omitempty"`
285	WithCustomAttributes     *bool             `url:"with_custom_attributes,omitempty" json:"with_custom_attributes,omitempty"`
286	WithIssuesEnabled        *bool             `url:"with_issues_enabled,omitempty" json:"with_issues_enabled,omitempty"`
287	WithMergeRequestsEnabled *bool             `url:"with_merge_requests_enabled,omitempty" json:"with_merge_requests_enabled,omitempty"`
288	WithProgrammingLanguage  *string           `url:"with_programming_language,omitempty" json:"with_programming_language,omitempty"`
289	WikiChecksumFailed       *bool             `url:"wiki_checksum_failed,omitempty" json:"wiki_checksum_failed,omitempty"`
290	RepositoryChecksumFailed *bool             `url:"repository_checksum_failed,omitempty" json:"repository_checksum_failed,omitempty"`
291	MinAccessLevel           *AccessLevelValue `url:"min_access_level,omitempty" json:"min_access_level,omitempty"`
292	IDAfter                  *int              `url:"id_after,omitempty" json:"id_after,omitempty"`
293	IDBefore                 *int              `url:"id_before,omitempty" json:"id_before,omitempty"`
294	LastActivityAfter        *time.Time        `url:"last_activity_after,omitempty" json:"last_activity_after,omitempty"`
295	LastActivityBefore       *time.Time        `url:"last_activity_before,omitempty" json:"last_activity_before,omitempty"`
296}
297
298// ListProjects gets a list of projects accessible by the authenticated user.
299//
300// GitLab API docs: https://docs.gitlab.com/ce/api/projects.html#list-projects
301func (s *ProjectsService) ListProjects(opt *ListProjectsOptions, options ...RequestOptionFunc) ([]*Project, *Response, error) {
302	req, err := s.client.NewRequest(http.MethodGet, "projects", opt, options)
303	if err != nil {
304		return nil, nil, err
305	}
306
307	var p []*Project
308	resp, err := s.client.Do(req, &p)
309	if err != nil {
310		return nil, resp, err
311	}
312
313	return p, resp, err
314}
315
316// ListUserProjects gets a list of projects for the given user.
317//
318// GitLab API docs:
319// https://docs.gitlab.com/ce/api/projects.html#list-user-projects
320func (s *ProjectsService) ListUserProjects(uid interface{}, opt *ListProjectsOptions, options ...RequestOptionFunc) ([]*Project, *Response, error) {
321	user, err := parseID(uid)
322	if err != nil {
323		return nil, nil, err
324	}
325	u := fmt.Sprintf("users/%s/projects", user)
326
327	req, err := s.client.NewRequest(http.MethodGet, u, opt, options)
328	if err != nil {
329		return nil, nil, err
330	}
331
332	var p []*Project
333	resp, err := s.client.Do(req, &p)
334	if err != nil {
335		return nil, resp, err
336	}
337
338	return p, resp, err
339}
340
341// ProjectUser represents a GitLab project user.
342type ProjectUser struct {
343	ID        int    `json:"id"`
344	Name      string `json:"name"`
345	Username  string `json:"username"`
346	State     string `json:"state"`
347	AvatarURL string `json:"avatar_url"`
348	WebURL    string `json:"web_url"`
349}
350
351// ListProjectUserOptions represents the available ListProjectsUsers() options.
352//
353// GitLab API docs: https://docs.gitlab.com/ce/api/projects.html#get-project-users
354type ListProjectUserOptions struct {
355	ListOptions
356	Search *string `url:"search,omitempty" json:"search,omitempty"`
357}
358
359// ListProjectsUsers gets a list of users for the given project.
360//
361// GitLab API docs:
362// https://docs.gitlab.com/ce/api/projects.html#get-project-users
363func (s *ProjectsService) ListProjectsUsers(pid interface{}, opt *ListProjectUserOptions, options ...RequestOptionFunc) ([]*ProjectUser, *Response, error) {
364	project, err := parseID(pid)
365	if err != nil {
366		return nil, nil, err
367	}
368	u := fmt.Sprintf("projects/%s/users", pathEscape(project))
369
370	req, err := s.client.NewRequest(http.MethodGet, u, opt, options)
371	if err != nil {
372		return nil, nil, err
373	}
374
375	var p []*ProjectUser
376	resp, err := s.client.Do(req, &p)
377	if err != nil {
378		return nil, resp, err
379	}
380
381	return p, resp, err
382}
383
384// ProjectGroup represents a GitLab project group.
385type ProjectGroup struct {
386	ID        int    `json:"id"`
387	Name      string `json:"name"`
388	AvatarURL string `json:"avatar_url"`
389	WebURL    string `json:"web_url"`
390	FullName  string `json:"full_name"`
391	FullPath  string `json:"full_path"`
392}
393
394// ListProjectGroupOptions represents the available ListProjectsGroups() options.
395//
396// GitLab API docs: https://docs.gitlab.com/ce/api/projects.html#list-a-projects-groups
397type ListProjectGroupOptions struct {
398	ListOptions
399	Search               *string           `url:"search,omitempty" json:"search,omitempty"`
400	SkipGroups           []int             `url:"skip_groups,omitempty" json:"skip_groups,omitempty"`
401	WithShared           *bool             `url:"with_shared,omitempty" json:"with_shared,omitempty"`
402	SharedMinAccessLevel *AccessLevelValue `url:"shared_min_access_level,omitempty" json:"shared_min_access_level,omitempty"`
403	SharedVisiableOnly   *bool             `url:"shared_visible_only,omitempty" json:"shared_visible_only,omitempty"`
404}
405
406// ListProjectsGroups gets a list of groups for the given project.
407//
408// GitLab API docs:
409// https://docs.gitlab.com/ce/api/projects.html#list-a-projects-groups
410func (s *ProjectsService) ListProjectsGroups(pid interface{}, opt *ListProjectGroupOptions, options ...RequestOptionFunc) ([]*ProjectGroup, *Response, error) {
411	project, err := parseID(pid)
412	if err != nil {
413		return nil, nil, err
414	}
415	u := fmt.Sprintf("projects/%s/groups", pathEscape(project))
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 p []*ProjectGroup
423	resp, err := s.client.Do(req, &p)
424	if err != nil {
425		return nil, resp, err
426	}
427
428	return p, resp, err
429}
430
431// ProjectLanguages is a map of strings because the response is arbitrary
432//
433// Gitlab API docs: https://docs.gitlab.com/ce/api/projects.html#languages
434type ProjectLanguages map[string]float32
435
436// GetProjectLanguages gets a list of languages used by the project
437//
438// GitLab API docs:  https://docs.gitlab.com/ce/api/projects.html#languages
439func (s *ProjectsService) GetProjectLanguages(pid interface{}, options ...RequestOptionFunc) (*ProjectLanguages, *Response, error) {
440	project, err := parseID(pid)
441	if err != nil {
442		return nil, nil, err
443	}
444	u := fmt.Sprintf("projects/%s/languages", pathEscape(project))
445
446	req, err := s.client.NewRequest(http.MethodGet, u, nil, options)
447	if err != nil {
448		return nil, nil, err
449	}
450
451	p := new(ProjectLanguages)
452	resp, err := s.client.Do(req, p)
453	if err != nil {
454		return nil, resp, err
455	}
456
457	return p, resp, err
458}
459
460// GetProjectOptions represents the available GetProject() options.
461//
462// GitLab API docs: https://docs.gitlab.com/ee/api/projects.html#get-single-project
463type GetProjectOptions struct {
464	Statistics           *bool `url:"statistics,omitempty" json:"statistics,omitempty"`
465	License              *bool `url:"license,omitempty" json:"license,omitempty"`
466	WithCustomAttributes *bool `url:"with_custom_attributes,omitempty" json:"with_custom_attributes,omitempty"`
467}
468
469// GetProject gets a specific project, identified by project ID or
470// NAMESPACE/PROJECT_NAME, which is owned by the authenticated user.
471//
472// GitLab API docs:
473// https://docs.gitlab.com/ce/api/projects.html#get-single-project
474func (s *ProjectsService) GetProject(pid interface{}, opt *GetProjectOptions, options ...RequestOptionFunc) (*Project, *Response, error) {
475	project, err := parseID(pid)
476	if err != nil {
477		return nil, nil, err
478	}
479	u := fmt.Sprintf("projects/%s", pathEscape(project))
480
481	req, err := s.client.NewRequest(http.MethodGet, u, opt, options)
482	if err != nil {
483		return nil, nil, err
484	}
485
486	p := new(Project)
487	resp, err := s.client.Do(req, p)
488	if err != nil {
489		return nil, resp, err
490	}
491
492	return p, resp, err
493}
494
495// ProjectEvent represents a GitLab project event.
496//
497// GitLab API docs:
498// https://docs.gitlab.com/ce/api/projects.html#get-project-events
499type ProjectEvent struct {
500	Title          interface{} `json:"title"`
501	ProjectID      int         `json:"project_id"`
502	ActionName     string      `json:"action_name"`
503	TargetID       interface{} `json:"target_id"`
504	TargetType     interface{} `json:"target_type"`
505	AuthorID       int         `json:"author_id"`
506	AuthorUsername string      `json:"author_username"`
507	Data           struct {
508		Before            string      `json:"before"`
509		After             string      `json:"after"`
510		Ref               string      `json:"ref"`
511		UserID            int         `json:"user_id"`
512		UserName          string      `json:"user_name"`
513		Repository        *Repository `json:"repository"`
514		Commits           []*Commit   `json:"commits"`
515		TotalCommitsCount int         `json:"total_commits_count"`
516	} `json:"data"`
517	TargetTitle interface{} `json:"target_title"`
518}
519
520func (s ProjectEvent) String() string {
521	return Stringify(s)
522}
523
524// GetProjectEventsOptions represents the available GetProjectEvents() options.
525//
526// GitLab API docs:
527// https://docs.gitlab.com/ce/api/projects.html#get-project-events
528type GetProjectEventsOptions ListOptions
529
530// GetProjectEvents gets the events for the specified project. Sorted from
531// newest to latest.
532//
533// GitLab API docs:
534// https://docs.gitlab.com/ce/api/projects.html#get-project-events
535func (s *ProjectsService) GetProjectEvents(pid interface{}, opt *GetProjectEventsOptions, options ...RequestOptionFunc) ([]*ProjectEvent, *Response, error) {
536	project, err := parseID(pid)
537	if err != nil {
538		return nil, nil, err
539	}
540	u := fmt.Sprintf("projects/%s/events", pathEscape(project))
541
542	req, err := s.client.NewRequest(http.MethodGet, u, opt, options)
543	if err != nil {
544		return nil, nil, err
545	}
546
547	var p []*ProjectEvent
548	resp, err := s.client.Do(req, &p)
549	if err != nil {
550		return nil, resp, err
551	}
552
553	return p, resp, err
554}
555
556// CreateProjectOptions represents the available CreateProject() options.
557//
558// GitLab API docs: https://docs.gitlab.com/ee/api/projects.html#create-project
559type CreateProjectOptions struct {
560	Name                                      *string                              `url:"name,omitempty" json:"name,omitempty"`
561	Path                                      *string                              `url:"path,omitempty" json:"path,omitempty"`
562	NamespaceID                               *int                                 `url:"namespace_id,omitempty" json:"namespace_id,omitempty"`
563	DefaultBranch                             *string                              `url:"default_branch,omitempty" json:"default_branch,omitempty"`
564	Description                               *string                              `url:"description,omitempty" json:"description,omitempty"`
565	IssuesAccessLevel                         *AccessControlValue                  `url:"issues_access_level,omitempty" json:"issues_access_level,omitempty"`
566	RepositoryAccessLevel                     *AccessControlValue                  `url:"repository_access_level,omitempty" json:"repository_access_level,omitempty"`
567	MergeRequestsAccessLevel                  *AccessControlValue                  `url:"merge_requests_access_level,omitempty" json:"merge_requests_access_level,omitempty"`
568	ForkingAccessLevel                        *AccessControlValue                  `url:"forking_access_level,omitempty" json:"forking_access_level,omitempty"`
569	BuildsAccessLevel                         *AccessControlValue                  `url:"builds_access_level,omitempty" json:"builds_access_level,omitempty"`
570	WikiAccessLevel                           *AccessControlValue                  `url:"wiki_access_level,omitempty" json:"wiki_access_level,omitempty"`
571	SnippetsAccessLevel                       *AccessControlValue                  `url:"snippets_access_level,omitempty" json:"snippets_access_level,omitempty"`
572	PagesAccessLevel                          *AccessControlValue                  `url:"pages_access_level,omitempty" json:"pages_access_level,omitempty"`
573	OperationsAccessLevel                     *AccessControlValue                  `url:"operations_access_level,omitempty" json:"operations_access_level,omitempty"`
574	EmailsDisabled                            *bool                                `url:"emails_disabled,omitempty" json:"emails_disabled,omitempty"`
575	ResolveOutdatedDiffDiscussions            *bool                                `url:"resolve_outdated_diff_discussions,omitempty" json:"resolve_outdated_diff_discussions,omitempty"`
576	ContainerExpirationPolicyAttributes       *ContainerExpirationPolicyAttributes `url:"container_expiration_policy_attributes,omitempty" json:"container_expiration_policy_attributes,omitempty"`
577	ContainerRegistryEnabled                  *bool                                `url:"container_registry_enabled,omitempty" json:"container_registry_enabled,omitempty"`
578	SharedRunnersEnabled                      *bool                                `url:"shared_runners_enabled,omitempty" json:"shared_runners_enabled,omitempty"`
579	Visibility                                *VisibilityValue                     `url:"visibility,omitempty" json:"visibility,omitempty"`
580	ImportURL                                 *string                              `url:"import_url,omitempty" json:"import_url,omitempty"`
581	PublicBuilds                              *bool                                `url:"public_builds,omitempty" json:"public_builds,omitempty"`
582	AllowMergeOnSkippedPipeline               *bool                                `url:"allow_merge_on_skipped_pipeline,omitempty" json:"allow_merge_on_skipped_pipeline,omitempty"`
583	OnlyAllowMergeIfPipelineSucceeds          *bool                                `url:"only_allow_merge_if_pipeline_succeeds,omitempty" json:"only_allow_merge_if_pipeline_succeeds,omitempty"`
584	OnlyAllowMergeIfAllDiscussionsAreResolved *bool                                `url:"only_allow_merge_if_all_discussions_are_resolved,omitempty" json:"only_allow_merge_if_all_discussions_are_resolved,omitempty"`
585	MergeMethod                               *MergeMethodValue                    `url:"merge_method,omitempty" json:"merge_method,omitempty"`
586	RemoveSourceBranchAfterMerge              *bool                                `url:"remove_source_branch_after_merge,omitempty" json:"remove_source_branch_after_merge,omitempty"`
587	LFSEnabled                                *bool                                `url:"lfs_enabled,omitempty" json:"lfs_enabled,omitempty"`
588	RequestAccessEnabled                      *bool                                `url:"request_access_enabled,omitempty" json:"request_access_enabled,omitempty"`
589	TagList                                   *[]string                            `url:"tag_list,omitempty" json:"tag_list,omitempty"`
590	PrintingMergeRequestLinkEnabled           *bool                                `url:"printing_merge_request_link_enabled,omitempty" json:"printing_merge_request_link_enabled,omitempty"`
591	BuildGitStrategy                          *string                              `url:"build_git_strategy,omitempty" json:"build_git_strategy,omitempty"`
592	BuildTimeout                              *int                                 `url:"build_timeout,omitempty" json:"build_timeout,omitempty"`
593	AutoCancelPendingPipelines                *string                              `url:"auto_cancel_pending_pipelines,omitempty" json:"auto_cancel_pending_pipelines,omitempty"`
594	BuildCoverageRegex                        *string                              `url:"build_coverage_regex,omitempty" json:"build_coverage_regex,omitempty"`
595	CIConfigPath                              *string                              `url:"ci_config_path,omitempty" json:"ci_config_path,omitempty"`
596	CIForwardDeploymentEnabled                *bool                                `url:"ci_forward_deployment_enabled,omitempty" json:"ci_forward_deployment_enabled,omitempty"`
597	AutoDevopsEnabled                         *bool                                `url:"auto_devops_enabled,omitempty" json:"auto_devops_enabled,omitempty"`
598	AutoDevopsDeployStrategy                  *string                              `url:"auto_devops_deploy_strategy,omitempty" json:"auto_devops_deploy_strategy,omitempty"`
599	ApprovalsBeforeMerge                      *int                                 `url:"approvals_before_merge,omitempty" json:"approvals_before_merge,omitempty"`
600	ExternalAuthorizationClassificationLabel  *string                              `url:"external_authorization_classification_label,omitempty" json:"external_authorization_classification_label,omitempty"`
601	Mirror                                    *bool                                `url:"mirror,omitempty" json:"mirror,omitempty"`
602	MirrorTriggerBuilds                       *bool                                `url:"mirror_trigger_builds,omitempty" json:"mirror_trigger_builds,omitempty"`
603	InitializeWithReadme                      *bool                                `url:"initialize_with_readme,omitempty" json:"initialize_with_readme,omitempty"`
604	TemplateName                              *string                              `url:"template_name,omitempty" json:"template_name,omitempty"`
605	TemplateProjectID                         *int                                 `url:"template_project_id,omitempty" json:"template_project_id,omitempty"`
606	UseCustomTemplate                         *bool                                `url:"use_custom_template,omitempty" json:"use_custom_template,omitempty"`
607	GroupWithProjectTemplatesID               *int                                 `url:"group_with_project_templates_id,omitempty" json:"group_with_project_templates_id,omitempty"`
608	PackagesEnabled                           *bool                                `url:"packages_enabled,omitempty" json:"packages_enabled,omitempty"`
609	ServiceDeskEnabled                        *bool                                `url:"service_desk_enabled,omitempty" json:"service_desk_enabled,omitempty"`
610	AutocloseReferencedIssues                 *bool                                `url:"autoclose_referenced_issues,omitempty" json:"autoclose_referenced_issues,omitempty"`
611	SuggestionCommitMessage                   *string                              `url:"suggestion_commit_message,omitempty" json:"suggestion_commit_message,omitempty"`
612	IssuesTemplate                            *string                              `url:"issues_template,omitempty" json:"issues_template,omitempty"`
613	MergeRequestsTemplate                     *string                              `url:"merge_requests_template,omitempty" json:"merge_requests_template,omitempty"`
614
615	// Deprecated members
616	IssuesEnabled        *bool `url:"issues_enabled,omitempty" json:"issues_enabled,omitempty"`
617	MergeRequestsEnabled *bool `url:"merge_requests_enabled,omitempty" json:"merge_requests_enabled,omitempty"`
618	JobsEnabled          *bool `url:"jobs_enabled,omitempty" json:"jobs_enabled,omitempty"`
619	WikiEnabled          *bool `url:"wiki_enabled,omitempty" json:"wiki_enabled,omitempty"`
620	SnippetsEnabled      *bool `url:"snippets_enabled,omitempty" json:"snippets_enabled,omitempty"`
621}
622
623// ContainerExpirationPolicyAttributes represents the available container
624// expiration policy attributes.
625//
626// GitLab API docs: https://docs.gitlab.com/ee/api/projects.html#create-project
627type ContainerExpirationPolicyAttributes struct {
628	Cadence         *string `url:"cadence,omitempty" json:"cadence,omitempty"`
629	KeepN           *int    `url:"keep_n,omitempty" json:"keep_n,omitempty"`
630	OlderThan       *string `url:"older_than,omitempty" json:"older_than,omitempty"`
631	NameRegexDelete *string `url:"name_regex_delete,omitempty" json:"name_regex_delete,omitempty"`
632	NameRegexKeep   *string `url:"name_regex_keep,omitempty" json:"name_regex_keep,omitempty"`
633	Enabled         *bool   `url:"enabled,omitempty" json:"enabled,omitempty"`
634
635	// Deprecated members
636	NameRegex *string `url:"name_regex,omitempty" json:"name_regex,omitempty"`
637}
638
639// CreateProject creates a new project owned by the authenticated user.
640//
641// GitLab API docs: https://docs.gitlab.com/ce/api/projects.html#create-project
642func (s *ProjectsService) CreateProject(opt *CreateProjectOptions, options ...RequestOptionFunc) (*Project, *Response, error) {
643	if opt.ContainerExpirationPolicyAttributes != nil {
644		// This is needed to satisfy the API. Should be deleted
645		// when NameRegex is removed (it's now deprecated).
646		opt.ContainerExpirationPolicyAttributes.NameRegex =
647			opt.ContainerExpirationPolicyAttributes.NameRegexDelete
648	}
649
650	req, err := s.client.NewRequest(http.MethodPost, "projects", opt, options)
651	if err != nil {
652		return nil, nil, err
653	}
654
655	p := new(Project)
656	resp, err := s.client.Do(req, p)
657	if err != nil {
658		return nil, resp, err
659	}
660
661	return p, resp, err
662}
663
664// CreateProjectForUserOptions represents the available CreateProjectForUser()
665// options.
666//
667// GitLab API docs:
668// https://docs.gitlab.com/ce/api/projects.html#create-project-for-user
669type CreateProjectForUserOptions CreateProjectOptions
670
671// CreateProjectForUser creates a new project owned by the specified user.
672// Available only for admins.
673//
674// GitLab API docs:
675// https://docs.gitlab.com/ce/api/projects.html#create-project-for-user
676func (s *ProjectsService) CreateProjectForUser(user int, opt *CreateProjectForUserOptions, options ...RequestOptionFunc) (*Project, *Response, error) {
677	if opt.ContainerExpirationPolicyAttributes != nil {
678		// This is needed to satisfy the API. Should be deleted
679		// when NameRegex is removed (it's now deprecated).
680		opt.ContainerExpirationPolicyAttributes.NameRegex =
681			opt.ContainerExpirationPolicyAttributes.NameRegexDelete
682	}
683
684	u := fmt.Sprintf("projects/user/%d", user)
685	req, err := s.client.NewRequest(http.MethodPost, u, opt, options)
686	if err != nil {
687		return nil, nil, err
688	}
689
690	p := new(Project)
691	resp, err := s.client.Do(req, p)
692	if err != nil {
693		return nil, resp, err
694	}
695
696	return p, resp, err
697}
698
699// EditProjectOptions represents the available EditProject() options.
700//
701// GitLab API docs: https://docs.gitlab.com/ce/api/projects.html#edit-project
702type EditProjectOptions struct {
703	Name                                      *string                              `url:"name,omitempty" json:"name,omitempty"`
704	Path                                      *string                              `url:"path,omitempty" json:"path,omitempty"`
705	DefaultBranch                             *string                              `url:"default_branch,omitempty" json:"default_branch,omitempty"`
706	Description                               *string                              `url:"description,omitempty" json:"description,omitempty"`
707	IssuesAccessLevel                         *AccessControlValue                  `url:"issues_access_level,omitempty" json:"issues_access_level,omitempty"`
708	RepositoryAccessLevel                     *AccessControlValue                  `url:"repository_access_level,omitempty" json:"repository_access_level,omitempty"`
709	MergeRequestsAccessLevel                  *AccessControlValue                  `url:"merge_requests_access_level,omitempty" json:"merge_requests_access_level,omitempty"`
710	ForkingAccessLevel                        *AccessControlValue                  `url:"forking_access_level,omitempty" json:"forking_access_level,omitempty"`
711	BuildsAccessLevel                         *AccessControlValue                  `url:"builds_access_level,omitempty" json:"builds_access_level,omitempty"`
712	WikiAccessLevel                           *AccessControlValue                  `url:"wiki_access_level,omitempty" json:"wiki_access_level,omitempty"`
713	SnippetsAccessLevel                       *AccessControlValue                  `url:"snippets_access_level,omitempty" json:"snippets_access_level,omitempty"`
714	PagesAccessLevel                          *AccessControlValue                  `url:"pages_access_level,omitempty" json:"pages_access_level,omitempty"`
715	OperationsAccessLevel                     *AccessControlValue                  `url:"operations_access_level,omitempty" json:"operations_access_level,omitempty"`
716	EmailsDisabled                            *bool                                `url:"emails_disabled,omitempty" json:"emails_disabled,omitempty"`
717	ResolveOutdatedDiffDiscussions            *bool                                `url:"resolve_outdated_diff_discussions,omitempty" json:"resolve_outdated_diff_discussions,omitempty"`
718	ContainerExpirationPolicyAttributes       *ContainerExpirationPolicyAttributes `url:"container_expiration_policy_attributes,omitempty" json:"container_expiration_policy_attributes,omitempty"`
719	ContainerRegistryEnabled                  *bool                                `url:"container_registry_enabled,omitempty" json:"container_registry_enabled,omitempty"`
720	SharedRunnersEnabled                      *bool                                `url:"shared_runners_enabled,omitempty" json:"shared_runners_enabled,omitempty"`
721	Visibility                                *VisibilityValue                     `url:"visibility,omitempty" json:"visibility,omitempty"`
722	ImportURL                                 *string                              `url:"import_url,omitempty" json:"import_url,omitempty"`
723	PublicBuilds                              *bool                                `url:"public_builds,omitempty" json:"public_builds,omitempty"`
724	AllowMergeOnSkippedPipeline               *bool                                `url:"allow_merge_on_skipped_pipeline,omitempty" json:"allow_merge_on_skipped_pipeline,omitempty"`
725	OnlyAllowMergeIfPipelineSucceeds          *bool                                `url:"only_allow_merge_if_pipeline_succeeds,omitempty" json:"only_allow_merge_if_pipeline_succeeds,omitempty"`
726	OnlyAllowMergeIfAllDiscussionsAreResolved *bool                                `url:"only_allow_merge_if_all_discussions_are_resolved,omitempty" json:"only_allow_merge_if_all_discussions_are_resolved,omitempty"`
727	MergeMethod                               *MergeMethodValue                    `url:"merge_method,omitempty" json:"merge_method,omitempty"`
728	RemoveSourceBranchAfterMerge              *bool                                `url:"remove_source_branch_after_merge,omitempty" json:"remove_source_branch_after_merge,omitempty"`
729	LFSEnabled                                *bool                                `url:"lfs_enabled,omitempty" json:"lfs_enabled,omitempty"`
730	RequestAccessEnabled                      *bool                                `url:"request_access_enabled,omitempty" json:"request_access_enabled,omitempty"`
731	TagList                                   *[]string                            `url:"tag_list,omitempty" json:"tag_list,omitempty"`
732	BuildGitStrategy                          *string                              `url:"build_git_strategy,omitempty" json:"build_git_strategy,omitempty"`
733	BuildTimeout                              *int                                 `url:"build_timeout,omitempty" json:"build_timeout,omitempty"`
734	AutoCancelPendingPipelines                *string                              `url:"auto_cancel_pending_pipelines,omitempty" json:"auto_cancel_pending_pipelines,omitempty"`
735	BuildCoverageRegex                        *string                              `url:"build_coverage_regex,omitempty" json:"build_coverage_regex,omitempty"`
736	CIConfigPath                              *string                              `url:"ci_config_path,omitempty" json:"ci_config_path,omitempty"`
737	CIForwardDeploymentEnabled                *bool                                `url:"ci_forward_deployment_enabled,omitempty" json:"ci_forward_deployment_enabled,omitempty"`
738	CIDefaultGitDepth                         *int                                 `url:"ci_default_git_depth,omitempty" json:"ci_default_git_depth,omitempty"`
739	AutoDevopsEnabled                         *bool                                `url:"auto_devops_enabled,omitempty" json:"auto_devops_enabled,omitempty"`
740	AutoDevopsDeployStrategy                  *string                              `url:"auto_devops_deploy_strategy,omitempty" json:"auto_devops_deploy_strategy,omitempty"`
741	ApprovalsBeforeMerge                      *int                                 `url:"approvals_before_merge,omitempty" json:"approvals_before_merge,omitempty"`
742	ExternalAuthorizationClassificationLabel  *string                              `url:"external_authorization_classification_label,omitempty" json:"external_authorization_classification_label,omitempty"`
743	Mirror                                    *bool                                `url:"mirror,omitempty" json:"mirror,omitempty"`
744	MirrorUserID                              *int                                 `url:"mirror_user_id,omitempty" json:"mirror_user_id,omitempty"`
745	MirrorTriggerBuilds                       *bool                                `url:"mirror_trigger_builds,omitempty" json:"mirror_trigger_builds,omitempty"`
746	OnlyMirrorProtectedBranches               *bool                                `url:"only_mirror_protected_branches,omitempty" json:"only_mirror_protected_branches,omitempty"`
747	MirrorOverwritesDivergedBranches          *bool                                `url:"mirror_overwrites_diverged_branches,omitempty" json:"mirror_overwrites_diverged_branches,omitempty"`
748	PackagesEnabled                           *bool                                `url:"packages_enabled,omitempty" json:"packages_enabled,omitempty"`
749	ServiceDeskEnabled                        *bool                                `url:"service_desk_enabled,omitempty" json:"service_desk_enabled,omitempty"`
750	AutocloseReferencedIssues                 *bool                                `url:"autoclose_referenced_issues,omitempty" json:"autoclose_referenced_issues,omitempty"`
751	SuggestionCommitMessage                   *string                              `url:"suggestion_commit_message,omitempty" json:"suggestion_commit_message,omitempty"`
752	IssuesTemplate                            *string                              `url:"issues_template,omitempty" json:"issues_template,omitempty"`
753	MergeRequestsTemplate                     *string                              `url:"merge_requests_template,omitempty" json:"merge_requests_template,omitempty"`
754
755	// Deprecated members
756	IssuesEnabled        *bool `url:"issues_enabled,omitempty" json:"issues_enabled,omitempty"`
757	MergeRequestsEnabled *bool `url:"merge_requests_enabled,omitempty" json:"merge_requests_enabled,omitempty"`
758	JobsEnabled          *bool `url:"jobs_enabled,omitempty" json:"jobs_enabled,omitempty"`
759	WikiEnabled          *bool `url:"wiki_enabled,omitempty" json:"wiki_enabled,omitempty"`
760	SnippetsEnabled      *bool `url:"snippets_enabled,omitempty" json:"snippets_enabled,omitempty"`
761}
762
763// EditProject updates an existing project.
764//
765// GitLab API docs: https://docs.gitlab.com/ce/api/projects.html#edit-project
766func (s *ProjectsService) EditProject(pid interface{}, opt *EditProjectOptions, options ...RequestOptionFunc) (*Project, *Response, error) {
767	if opt.ContainerExpirationPolicyAttributes != nil {
768		// This is needed to satisfy the API. Should be deleted
769		// when NameRegex is removed (it's now deprecated).
770		opt.ContainerExpirationPolicyAttributes.NameRegex =
771			opt.ContainerExpirationPolicyAttributes.NameRegexDelete
772	}
773
774	project, err := parseID(pid)
775	if err != nil {
776		return nil, nil, err
777	}
778	u := fmt.Sprintf("projects/%s", pathEscape(project))
779
780	req, err := s.client.NewRequest(http.MethodPut, u, opt, options)
781	if err != nil {
782		return nil, nil, err
783	}
784
785	p := new(Project)
786	resp, err := s.client.Do(req, p)
787	if err != nil {
788		return nil, resp, err
789	}
790
791	return p, resp, err
792}
793
794// ForkProjectOptions represents the available ForkProject() options.
795//
796// GitLab API docs: https://docs.gitlab.com/ce/api/projects.html#fork-project
797type ForkProjectOptions struct {
798	Namespace *string `url:"namespace,omitempty" json:"namespace,omitempty"`
799	Name      *string `url:"name,omitempty" json:"name,omitempty" `
800	Path      *string `url:"path,omitempty" json:"path,omitempty"`
801}
802
803// ForkProject forks a project into the user namespace of the authenticated
804// user.
805//
806// GitLab API docs: https://docs.gitlab.com/ce/api/projects.html#fork-project
807func (s *ProjectsService) ForkProject(pid interface{}, opt *ForkProjectOptions, options ...RequestOptionFunc) (*Project, *Response, error) {
808	project, err := parseID(pid)
809	if err != nil {
810		return nil, nil, err
811	}
812	u := fmt.Sprintf("projects/%s/fork", pathEscape(project))
813
814	req, err := s.client.NewRequest(http.MethodPost, u, opt, options)
815	if err != nil {
816		return nil, nil, err
817	}
818
819	p := new(Project)
820	resp, err := s.client.Do(req, p)
821	if err != nil {
822		return nil, resp, err
823	}
824
825	return p, resp, err
826}
827
828// StarProject stars a given the project.
829//
830// GitLab API docs:
831// https://docs.gitlab.com/ce/api/projects.html#star-a-project
832func (s *ProjectsService) StarProject(pid interface{}, options ...RequestOptionFunc) (*Project, *Response, error) {
833	project, err := parseID(pid)
834	if err != nil {
835		return nil, nil, err
836	}
837	u := fmt.Sprintf("projects/%s/star", pathEscape(project))
838
839	req, err := s.client.NewRequest(http.MethodPost, u, nil, options)
840	if err != nil {
841		return nil, nil, err
842	}
843
844	p := new(Project)
845	resp, err := s.client.Do(req, p)
846	if err != nil {
847		return nil, resp, err
848	}
849
850	return p, resp, err
851}
852
853// UnstarProject unstars a given project.
854//
855// GitLab API docs:
856// https://docs.gitlab.com/ce/api/projects.html#unstar-a-project
857func (s *ProjectsService) UnstarProject(pid interface{}, options ...RequestOptionFunc) (*Project, *Response, error) {
858	project, err := parseID(pid)
859	if err != nil {
860		return nil, nil, err
861	}
862	u := fmt.Sprintf("projects/%s/unstar", pathEscape(project))
863
864	req, err := s.client.NewRequest(http.MethodPost, u, nil, options)
865	if err != nil {
866		return nil, nil, err
867	}
868
869	p := new(Project)
870	resp, err := s.client.Do(req, p)
871	if err != nil {
872		return nil, resp, err
873	}
874
875	return p, resp, err
876}
877
878// ArchiveProject archives the project if the user is either admin or the
879// project owner of this project.
880//
881// GitLab API docs:
882// https://docs.gitlab.com/ce/api/projects.html#archive-a-project
883func (s *ProjectsService) ArchiveProject(pid interface{}, options ...RequestOptionFunc) (*Project, *Response, error) {
884	project, err := parseID(pid)
885	if err != nil {
886		return nil, nil, err
887	}
888	u := fmt.Sprintf("projects/%s/archive", pathEscape(project))
889
890	req, err := s.client.NewRequest(http.MethodPost, u, nil, options)
891	if err != nil {
892		return nil, nil, err
893	}
894
895	p := new(Project)
896	resp, err := s.client.Do(req, p)
897	if err != nil {
898		return nil, resp, err
899	}
900
901	return p, resp, err
902}
903
904// UnarchiveProject unarchives the project if the user is either admin or
905// the project owner of this project.
906//
907// GitLab API docs:
908// https://docs.gitlab.com/ce/api/projects.html#unarchive-a-project
909func (s *ProjectsService) UnarchiveProject(pid interface{}, options ...RequestOptionFunc) (*Project, *Response, error) {
910	project, err := parseID(pid)
911	if err != nil {
912		return nil, nil, err
913	}
914	u := fmt.Sprintf("projects/%s/unarchive", pathEscape(project))
915
916	req, err := s.client.NewRequest(http.MethodPost, u, nil, options)
917	if err != nil {
918		return nil, nil, err
919	}
920
921	p := new(Project)
922	resp, err := s.client.Do(req, p)
923	if err != nil {
924		return nil, resp, err
925	}
926
927	return p, resp, err
928}
929
930// DeleteProject removes a project including all associated resources
931// (issues, merge requests etc.)
932//
933// GitLab API docs: https://docs.gitlab.com/ce/api/projects.html#remove-project
934func (s *ProjectsService) DeleteProject(pid interface{}, options ...RequestOptionFunc) (*Response, error) {
935	project, err := parseID(pid)
936	if err != nil {
937		return nil, err
938	}
939	u := fmt.Sprintf("projects/%s", pathEscape(project))
940
941	req, err := s.client.NewRequest(http.MethodDelete, u, nil, options)
942	if err != nil {
943		return nil, err
944	}
945
946	return s.client.Do(req, nil)
947}
948
949// ShareWithGroupOptions represents options to share project with groups
950//
951// GitLab API docs: https://docs.gitlab.com/ce/api/projects.html#share-project-with-group
952type ShareWithGroupOptions struct {
953	GroupID     *int              `url:"group_id" json:"group_id"`
954	GroupAccess *AccessLevelValue `url:"group_access" json:"group_access"`
955	ExpiresAt   *string           `url:"expires_at" json:"expires_at"`
956}
957
958// ShareProjectWithGroup allows to share a project with a group.
959//
960// GitLab API docs: https://docs.gitlab.com/ce/api/projects.html#share-project-with-group
961func (s *ProjectsService) ShareProjectWithGroup(pid interface{}, opt *ShareWithGroupOptions, options ...RequestOptionFunc) (*Response, error) {
962	project, err := parseID(pid)
963	if err != nil {
964		return nil, err
965	}
966	u := fmt.Sprintf("projects/%s/share", pathEscape(project))
967
968	req, err := s.client.NewRequest(http.MethodPost, u, opt, options)
969	if err != nil {
970		return nil, err
971	}
972
973	return s.client.Do(req, nil)
974}
975
976// DeleteSharedProjectFromGroup allows to unshare a project from a group.
977//
978// GitLab API docs: https://docs.gitlab.com/ce/api/projects.html#delete-a-shared-project-link-within-a-group
979func (s *ProjectsService) DeleteSharedProjectFromGroup(pid interface{}, groupID int, options ...RequestOptionFunc) (*Response, error) {
980	project, err := parseID(pid)
981	if err != nil {
982		return nil, err
983	}
984	u := fmt.Sprintf("projects/%s/share/%d", pathEscape(project), groupID)
985
986	req, err := s.client.NewRequest(http.MethodDelete, u, nil, options)
987	if err != nil {
988		return nil, err
989	}
990
991	return s.client.Do(req, nil)
992}
993
994// ProjectMember represents a project member.
995//
996// GitLab API docs:
997// https://docs.gitlab.com/ce/api/projects.html#list-project-team-members
998type ProjectMember struct {
999	ID          int              `json:"id"`
1000	Username    string           `json:"username"`
1001	Email       string           `json:"email"`
1002	Name        string           `json:"name"`
1003	State       string           `json:"state"`
1004	CreatedAt   *time.Time       `json:"created_at"`
1005	ExpiresAt   *ISOTime         `json:"expires_at"`
1006	AccessLevel AccessLevelValue `json:"access_level"`
1007	WebURL      string           `json:"web_url"`
1008	AvatarURL   string           `json:"avatar_url"`
1009}
1010
1011// ProjectHook represents a project hook.
1012//
1013// GitLab API docs:
1014// https://docs.gitlab.com/ce/api/projects.html#list-project-hooks
1015type ProjectHook struct {
1016	ID                       int        `json:"id"`
1017	URL                      string     `json:"url"`
1018	ConfidentialNoteEvents   bool       `json:"confidential_note_events"`
1019	ProjectID                int        `json:"project_id"`
1020	PushEvents               bool       `json:"push_events"`
1021	PushEventsBranchFilter   string     `json:"push_events_branch_filter"`
1022	IssuesEvents             bool       `json:"issues_events"`
1023	ConfidentialIssuesEvents bool       `json:"confidential_issues_events"`
1024	MergeRequestsEvents      bool       `json:"merge_requests_events"`
1025	TagPushEvents            bool       `json:"tag_push_events"`
1026	NoteEvents               bool       `json:"note_events"`
1027	JobEvents                bool       `json:"job_events"`
1028	PipelineEvents           bool       `json:"pipeline_events"`
1029	WikiPageEvents           bool       `json:"wiki_page_events"`
1030	DeploymentEvents         bool       `json:"deployment_events"`
1031	ReleasesEvents           bool       `json:"releases_events"`
1032	EnableSSLVerification    bool       `json:"enable_ssl_verification"`
1033	CreatedAt                *time.Time `json:"created_at"`
1034}
1035
1036// ListProjectHooksOptions represents the available ListProjectHooks() options.
1037//
1038// GitLab API docs: https://docs.gitlab.com/ce/api/projects.html#list-project-hooks
1039type ListProjectHooksOptions ListOptions
1040
1041// ListProjectHooks gets a list of project hooks.
1042//
1043// GitLab API docs:
1044// https://docs.gitlab.com/ce/api/projects.html#list-project-hooks
1045func (s *ProjectsService) ListProjectHooks(pid interface{}, opt *ListProjectHooksOptions, options ...RequestOptionFunc) ([]*ProjectHook, *Response, error) {
1046	project, err := parseID(pid)
1047	if err != nil {
1048		return nil, nil, err
1049	}
1050	u := fmt.Sprintf("projects/%s/hooks", pathEscape(project))
1051
1052	req, err := s.client.NewRequest(http.MethodGet, u, opt, options)
1053	if err != nil {
1054		return nil, nil, err
1055	}
1056
1057	var ph []*ProjectHook
1058	resp, err := s.client.Do(req, &ph)
1059	if err != nil {
1060		return nil, resp, err
1061	}
1062
1063	return ph, resp, err
1064}
1065
1066// GetProjectHook gets a specific hook for a project.
1067//
1068// GitLab API docs:
1069// https://docs.gitlab.com/ce/api/projects.html#get-project-hook
1070func (s *ProjectsService) GetProjectHook(pid interface{}, hook int, options ...RequestOptionFunc) (*ProjectHook, *Response, error) {
1071	project, err := parseID(pid)
1072	if err != nil {
1073		return nil, nil, err
1074	}
1075	u := fmt.Sprintf("projects/%s/hooks/%d", pathEscape(project), hook)
1076
1077	req, err := s.client.NewRequest(http.MethodGet, u, nil, options)
1078	if err != nil {
1079		return nil, nil, err
1080	}
1081
1082	ph := new(ProjectHook)
1083	resp, err := s.client.Do(req, ph)
1084	if err != nil {
1085		return nil, resp, err
1086	}
1087
1088	return ph, resp, err
1089}
1090
1091// AddProjectHookOptions represents the available AddProjectHook() options.
1092//
1093// GitLab API docs:
1094// https://docs.gitlab.com/ce/api/projects.html#add-project-hook
1095type AddProjectHookOptions struct {
1096	URL                      *string `url:"url,omitempty" json:"url,omitempty"`
1097	ConfidentialNoteEvents   *bool   `url:"confidential_note_events,omitempty" json:"confidential_note_events,omitempty"`
1098	PushEvents               *bool   `url:"push_events,omitempty" json:"push_events,omitempty"`
1099	PushEventsBranchFilter   *string `url:"push_events_branch_filter,omitempty" json:"push_events_branch_filter,omitempty"`
1100	IssuesEvents             *bool   `url:"issues_events,omitempty" json:"issues_events,omitempty"`
1101	ConfidentialIssuesEvents *bool   `url:"confidential_issues_events,omitempty" json:"confidential_issues_events,omitempty"`
1102	MergeRequestsEvents      *bool   `url:"merge_requests_events,omitempty" json:"merge_requests_events,omitempty"`
1103	TagPushEvents            *bool   `url:"tag_push_events,omitempty" json:"tag_push_events,omitempty"`
1104	NoteEvents               *bool   `url:"note_events,omitempty" json:"note_events,omitempty"`
1105	JobEvents                *bool   `url:"job_events,omitempty" json:"job_events,omitempty"`
1106	PipelineEvents           *bool   `url:"pipeline_events,omitempty" json:"pipeline_events,omitempty"`
1107	WikiPageEvents           *bool   `url:"wiki_page_events,omitempty" json:"wiki_page_events,omitempty"`
1108	DeploymentEvents         *bool   `url:"deployment_events,omitempty" json:"deployment_events,omitempty"`
1109	ReleasesEvents           *bool   `url:"releases_events,omitempty" json:"releases_events,omitempty"`
1110	EnableSSLVerification    *bool   `url:"enable_ssl_verification,omitempty" json:"enable_ssl_verification,omitempty"`
1111	Token                    *string `url:"token,omitempty" json:"token,omitempty"`
1112}
1113
1114// AddProjectHook adds a hook to a specified project.
1115//
1116// GitLab API docs:
1117// https://docs.gitlab.com/ce/api/projects.html#add-project-hook
1118func (s *ProjectsService) AddProjectHook(pid interface{}, opt *AddProjectHookOptions, options ...RequestOptionFunc) (*ProjectHook, *Response, error) {
1119	project, err := parseID(pid)
1120	if err != nil {
1121		return nil, nil, err
1122	}
1123	u := fmt.Sprintf("projects/%s/hooks", pathEscape(project))
1124
1125	req, err := s.client.NewRequest(http.MethodPost, u, opt, options)
1126	if err != nil {
1127		return nil, nil, err
1128	}
1129
1130	ph := new(ProjectHook)
1131	resp, err := s.client.Do(req, ph)
1132	if err != nil {
1133		return nil, resp, err
1134	}
1135
1136	return ph, resp, err
1137}
1138
1139// EditProjectHookOptions represents the available EditProjectHook() options.
1140//
1141// GitLab API docs:
1142// https://docs.gitlab.com/ce/api/projects.html#edit-project-hook
1143type EditProjectHookOptions struct {
1144	URL                      *string `url:"url,omitempty" json:"url,omitempty"`
1145	ConfidentialNoteEvents   *bool   `url:"confidential_note_events,omitempty" json:"confidential_note_events,omitempty"`
1146	PushEvents               *bool   `url:"push_events,omitempty" json:"push_events,omitempty"`
1147	PushEventsBranchFilter   *string `url:"push_events_branch_filter,omitempty" json:"push_events_branch_filter,omitempty"`
1148	IssuesEvents             *bool   `url:"issues_events,omitempty" json:"issues_events,omitempty"`
1149	ConfidentialIssuesEvents *bool   `url:"confidential_issues_events,omitempty" json:"confidential_issues_events,omitempty"`
1150	MergeRequestsEvents      *bool   `url:"merge_requests_events,omitempty" json:"merge_requests_events,omitempty"`
1151	TagPushEvents            *bool   `url:"tag_push_events,omitempty" json:"tag_push_events,omitempty"`
1152	NoteEvents               *bool   `url:"note_events,omitempty" json:"note_events,omitempty"`
1153	JobEvents                *bool   `url:"job_events,omitempty" json:"job_events,omitempty"`
1154	PipelineEvents           *bool   `url:"pipeline_events,omitempty" json:"pipeline_events,omitempty"`
1155	WikiPageEvents           *bool   `url:"wiki_page_events,omitempty" json:"wiki_page_events,omitempty"`
1156	DeploymentEvents         *bool   `url:"deployment_events,omitempty" json:"deployment_events,omitempty"`
1157	ReleasesEvents           *bool   `url:"releases_events,omitempty" json:"releases_events,omitempty"`
1158	EnableSSLVerification    *bool   `url:"enable_ssl_verification,omitempty" json:"enable_ssl_verification,omitempty"`
1159	Token                    *string `url:"token,omitempty" json:"token,omitempty"`
1160}
1161
1162// EditProjectHook edits a hook for a specified project.
1163//
1164// GitLab API docs:
1165// https://docs.gitlab.com/ce/api/projects.html#edit-project-hook
1166func (s *ProjectsService) EditProjectHook(pid interface{}, hook int, opt *EditProjectHookOptions, options ...RequestOptionFunc) (*ProjectHook, *Response, error) {
1167	project, err := parseID(pid)
1168	if err != nil {
1169		return nil, nil, err
1170	}
1171	u := fmt.Sprintf("projects/%s/hooks/%d", pathEscape(project), hook)
1172
1173	req, err := s.client.NewRequest(http.MethodPut, u, opt, options)
1174	if err != nil {
1175		return nil, nil, err
1176	}
1177
1178	ph := new(ProjectHook)
1179	resp, err := s.client.Do(req, ph)
1180	if err != nil {
1181		return nil, resp, err
1182	}
1183
1184	return ph, resp, err
1185}
1186
1187// DeleteProjectHook removes a hook from a project. This is an idempotent
1188// method and can be called multiple times. Either the hook is available or not.
1189//
1190// GitLab API docs:
1191// https://docs.gitlab.com/ce/api/projects.html#delete-project-hook
1192func (s *ProjectsService) DeleteProjectHook(pid interface{}, hook int, options ...RequestOptionFunc) (*Response, error) {
1193	project, err := parseID(pid)
1194	if err != nil {
1195		return nil, err
1196	}
1197	u := fmt.Sprintf("projects/%s/hooks/%d", pathEscape(project), hook)
1198
1199	req, err := s.client.NewRequest(http.MethodDelete, u, nil, options)
1200	if err != nil {
1201		return nil, err
1202	}
1203
1204	return s.client.Do(req, nil)
1205}
1206
1207// ProjectForkRelation represents a project fork relationship.
1208//
1209// GitLab API docs:
1210// https://docs.gitlab.com/ce/api/projects.html#admin-fork-relation
1211type ProjectForkRelation struct {
1212	ID                  int        `json:"id"`
1213	ForkedToProjectID   int        `json:"forked_to_project_id"`
1214	ForkedFromProjectID int        `json:"forked_from_project_id"`
1215	CreatedAt           *time.Time `json:"created_at"`
1216	UpdatedAt           *time.Time `json:"updated_at"`
1217}
1218
1219// CreateProjectForkRelation creates a forked from/to relation between
1220// existing projects.
1221//
1222// GitLab API docs:
1223// https://docs.gitlab.com/ce/api/projects.html#create-a-forked-fromto-relation-between-existing-projects.
1224func (s *ProjectsService) CreateProjectForkRelation(pid int, fork int, options ...RequestOptionFunc) (*ProjectForkRelation, *Response, error) {
1225	u := fmt.Sprintf("projects/%d/fork/%d", pid, fork)
1226
1227	req, err := s.client.NewRequest(http.MethodPost, u, nil, options)
1228	if err != nil {
1229		return nil, nil, err
1230	}
1231
1232	pfr := new(ProjectForkRelation)
1233	resp, err := s.client.Do(req, pfr)
1234	if err != nil {
1235		return nil, resp, err
1236	}
1237
1238	return pfr, resp, err
1239}
1240
1241// DeleteProjectForkRelation deletes an existing forked from relationship.
1242//
1243// GitLab API docs:
1244// https://docs.gitlab.com/ce/api/projects.html#delete-an-existing-forked-from-relationship
1245func (s *ProjectsService) DeleteProjectForkRelation(pid int, options ...RequestOptionFunc) (*Response, error) {
1246	u := fmt.Sprintf("projects/%d/fork", pid)
1247
1248	req, err := s.client.NewRequest(http.MethodDelete, u, nil, options)
1249	if err != nil {
1250		return nil, err
1251	}
1252
1253	return s.client.Do(req, nil)
1254}
1255
1256// ProjectFile represents an uploaded project file
1257//
1258// GitLab API docs: https://docs.gitlab.com/ce/api/projects.html#upload-a-file
1259type ProjectFile struct {
1260	Alt      string `json:"alt"`
1261	URL      string `json:"url"`
1262	Markdown string `json:"markdown"`
1263}
1264
1265// UploadFile upload a file from disk
1266//
1267// GitLab API docs: https://docs.gitlab.com/ce/api/projects.html#upload-a-file
1268func (s *ProjectsService) UploadFile(pid interface{}, file string, options ...RequestOptionFunc) (*ProjectFile, *Response, error) {
1269	project, err := parseID(pid)
1270	if err != nil {
1271		return nil, nil, err
1272	}
1273	u := fmt.Sprintf("projects/%s/uploads", pathEscape(project))
1274
1275	f, err := os.Open(file)
1276	if err != nil {
1277		return nil, nil, err
1278	}
1279	defer f.Close()
1280
1281	b := &bytes.Buffer{}
1282	w := multipart.NewWriter(b)
1283
1284	_, filename := filepath.Split(file)
1285	fw, err := w.CreateFormFile("file", filename)
1286	if err != nil {
1287		return nil, nil, err
1288	}
1289
1290	_, err = io.Copy(fw, f)
1291	if err != nil {
1292		return nil, nil, err
1293	}
1294	w.Close()
1295
1296	req, err := s.client.NewRequest(http.MethodPost, u, nil, options)
1297	if err != nil {
1298		return nil, nil, err
1299	}
1300
1301	// Set the buffer as the request body.
1302	if err = req.SetBody(b); err != nil {
1303		return nil, nil, err
1304	}
1305
1306	// Overwrite the default content type.
1307	req.Header.Set("Content-Type", w.FormDataContentType())
1308
1309	uf := &ProjectFile{}
1310	resp, err := s.client.Do(req, uf)
1311	if err != nil {
1312		return nil, resp, err
1313	}
1314
1315	return uf, resp, nil
1316}
1317
1318// ListProjectForks gets a list of project forks.
1319//
1320// GitLab API docs:
1321// https://docs.gitlab.com/ce/api/projects.html#list-forks-of-a-project
1322func (s *ProjectsService) ListProjectForks(pid interface{}, opt *ListProjectsOptions, options ...RequestOptionFunc) ([]*Project, *Response, error) {
1323	project, err := parseID(pid)
1324	if err != nil {
1325		return nil, nil, err
1326	}
1327	u := fmt.Sprintf("projects/%s/forks", pathEscape(project))
1328
1329	req, err := s.client.NewRequest(http.MethodGet, u, opt, options)
1330	if err != nil {
1331		return nil, nil, err
1332	}
1333
1334	var forks []*Project
1335	resp, err := s.client.Do(req, &forks)
1336	if err != nil {
1337		return nil, resp, err
1338	}
1339
1340	return forks, resp, err
1341}
1342
1343// ProjectPushRules represents a project push rule.
1344//
1345// GitLab API docs:
1346// https://docs.gitlab.com/ee/api/projects.html#push-rules
1347type ProjectPushRules struct {
1348	ID                         int        `json:"id"`
1349	ProjectID                  int        `json:"project_id"`
1350	CommitMessageRegex         string     `json:"commit_message_regex"`
1351	CommitMessageNegativeRegex string     `json:"commit_message_negative_regex"`
1352	BranchNameRegex            string     `json:"branch_name_regex"`
1353	DenyDeleteTag              bool       `json:"deny_delete_tag"`
1354	CreatedAt                  *time.Time `json:"created_at"`
1355	MemberCheck                bool       `json:"member_check"`
1356	PreventSecrets             bool       `json:"prevent_secrets"`
1357	AuthorEmailRegex           string     `json:"author_email_regex"`
1358	FileNameRegex              string     `json:"file_name_regex"`
1359	MaxFileSize                int        `json:"max_file_size"`
1360	CommitCommitterCheck       bool       `json:"commit_committer_check"`
1361	RejectUnsignedCommits      bool       `json:"reject_unsigned_commits"`
1362}
1363
1364// GetProjectPushRules gets the push rules of a project.
1365//
1366// GitLab API docs:
1367// https://docs.gitlab.com/ee/api/projects.html#get-project-push-rules
1368func (s *ProjectsService) GetProjectPushRules(pid interface{}, options ...RequestOptionFunc) (*ProjectPushRules, *Response, error) {
1369	project, err := parseID(pid)
1370	if err != nil {
1371		return nil, nil, err
1372	}
1373	u := fmt.Sprintf("projects/%s/push_rule", pathEscape(project))
1374
1375	req, err := s.client.NewRequest(http.MethodGet, u, nil, options)
1376	if err != nil {
1377		return nil, nil, err
1378	}
1379
1380	ppr := new(ProjectPushRules)
1381	resp, err := s.client.Do(req, ppr)
1382	if err != nil {
1383		return nil, resp, err
1384	}
1385
1386	return ppr, resp, err
1387}
1388
1389// AddProjectPushRuleOptions represents the available AddProjectPushRule()
1390// options.
1391//
1392// GitLab API docs:
1393// https://docs.gitlab.com/ee/api/projects.html#add-project-push-rule
1394type AddProjectPushRuleOptions struct {
1395	DenyDeleteTag              *bool   `url:"deny_delete_tag,omitempty" json:"deny_delete_tag,omitempty"`
1396	MemberCheck                *bool   `url:"member_check,omitempty" json:"member_check,omitempty"`
1397	PreventSecrets             *bool   `url:"prevent_secrets,omitempty" json:"prevent_secrets,omitempty"`
1398	CommitMessageRegex         *string `url:"commit_message_regex,omitempty" json:"commit_message_regex,omitempty"`
1399	CommitMessageNegativeRegex *string `url:"commit_message_negative_regex,omitempty" json:"commit_message_negative_regex,omitempty"`
1400	BranchNameRegex            *string `url:"branch_name_regex,omitempty" json:"branch_name_regex,omitempty"`
1401	AuthorEmailRegex           *string `url:"author_email_regex,omitempty" json:"author_email_regex,omitempty"`
1402	FileNameRegex              *string `url:"file_name_regex,omitempty" json:"file_name_regex,omitempty"`
1403	MaxFileSize                *int    `url:"max_file_size,omitempty" json:"max_file_size,omitempty"`
1404	CommitCommitterCheck       *bool   `url:"commit_committer_check,omitempty" json:"commit_committer_check,omitempty"`
1405	RejectUnsignedCommits      *bool   `url:"reject_unsigned_commits,omitempty" json:"reject_unsigned_commits,omitempty"`
1406}
1407
1408// AddProjectPushRule adds a push rule to a specified project.
1409//
1410// GitLab API docs:
1411// https://docs.gitlab.com/ee/api/projects.html#add-project-push-rule
1412func (s *ProjectsService) AddProjectPushRule(pid interface{}, opt *AddProjectPushRuleOptions, options ...RequestOptionFunc) (*ProjectPushRules, *Response, error) {
1413	project, err := parseID(pid)
1414	if err != nil {
1415		return nil, nil, err
1416	}
1417	u := fmt.Sprintf("projects/%s/push_rule", pathEscape(project))
1418
1419	req, err := s.client.NewRequest(http.MethodPost, u, opt, options)
1420	if err != nil {
1421		return nil, nil, err
1422	}
1423
1424	ppr := new(ProjectPushRules)
1425	resp, err := s.client.Do(req, ppr)
1426	if err != nil {
1427		return nil, resp, err
1428	}
1429
1430	return ppr, resp, err
1431}
1432
1433// EditProjectPushRuleOptions represents the available EditProjectPushRule()
1434// options.
1435//
1436// GitLab API docs:
1437// https://docs.gitlab.com/ee/api/projects.html#edit-project-push-rule
1438type EditProjectPushRuleOptions struct {
1439	AuthorEmailRegex           *string `url:"author_email_regex,omitempty" json:"author_email_regex,omitempty"`
1440	BranchNameRegex            *string `url:"branch_name_regex,omitempty" json:"branch_name_regex,omitempty"`
1441	CommitMessageRegex         *string `url:"commit_message_regex,omitempty" json:"commit_message_regex,omitempty"`
1442	CommitMessageNegativeRegex *string `url:"commit_message_negative_regex,omitempty" json:"commit_message_negative_regex,omitempty"`
1443	FileNameRegex              *string `url:"file_name_regex,omitempty" json:"file_name_regex,omitempty"`
1444	DenyDeleteTag              *bool   `url:"deny_delete_tag,omitempty" json:"deny_delete_tag,omitempty"`
1445	MemberCheck                *bool   `url:"member_check,omitempty" json:"member_check,omitempty"`
1446	PreventSecrets             *bool   `url:"prevent_secrets,omitempty" json:"prevent_secrets,omitempty"`
1447	MaxFileSize                *int    `url:"max_file_size,omitempty" json:"max_file_size,omitempty"`
1448	CommitCommitterCheck       *bool   `url:"commit_committer_check,omitempty" json:"commit_committer_check,omitempty"`
1449	RejectUnsignedCommits      *bool   `url:"reject_unsigned_commits,omitempty" json:"reject_unsigned_commits,omitempty"`
1450}
1451
1452// EditProjectPushRule edits a push rule for a specified project.
1453//
1454// GitLab API docs:
1455// https://docs.gitlab.com/ee/api/projects.html#edit-project-push-rule
1456func (s *ProjectsService) EditProjectPushRule(pid interface{}, opt *EditProjectPushRuleOptions, options ...RequestOptionFunc) (*ProjectPushRules, *Response, error) {
1457	project, err := parseID(pid)
1458	if err != nil {
1459		return nil, nil, err
1460	}
1461	u := fmt.Sprintf("projects/%s/push_rule", pathEscape(project))
1462
1463	req, err := s.client.NewRequest(http.MethodPut, u, opt, options)
1464	if err != nil {
1465		return nil, nil, err
1466	}
1467
1468	ppr := new(ProjectPushRules)
1469	resp, err := s.client.Do(req, ppr)
1470	if err != nil {
1471		return nil, resp, err
1472	}
1473
1474	return ppr, resp, err
1475}
1476
1477// DeleteProjectPushRule removes a push rule from a project. This is an
1478// idempotent method and can be called multiple times. Either the push rule is
1479// available or not.
1480//
1481// GitLab API docs:
1482// https://docs.gitlab.com/ee/api/projects.html#delete-project-push-rule
1483func (s *ProjectsService) DeleteProjectPushRule(pid interface{}, options ...RequestOptionFunc) (*Response, error) {
1484	project, err := parseID(pid)
1485	if err != nil {
1486		return nil, err
1487	}
1488	u := fmt.Sprintf("projects/%s/push_rule", pathEscape(project))
1489
1490	req, err := s.client.NewRequest(http.MethodDelete, u, nil, options)
1491	if err != nil {
1492		return nil, err
1493	}
1494
1495	return s.client.Do(req, nil)
1496}
1497
1498// ProjectApprovals represents GitLab project level merge request approvals.
1499//
1500// GitLab API docs:
1501// https://docs.gitlab.com/ee/api/merge_request_approvals.html#project-level-mr-approvals
1502type ProjectApprovals struct {
1503	Approvers                                 []*MergeRequestApproverUser  `json:"approvers"`
1504	ApproverGroups                            []*MergeRequestApproverGroup `json:"approver_groups"`
1505	ApprovalsBeforeMerge                      int                          `json:"approvals_before_merge"`
1506	ResetApprovalsOnPush                      bool                         `json:"reset_approvals_on_push"`
1507	DisableOverridingApproversPerMergeRequest bool                         `json:"disable_overriding_approvers_per_merge_request"`
1508	MergeRequestsAuthorApproval               bool                         `json:"merge_requests_author_approval"`
1509	MergeRequestsDisableCommittersApproval    bool                         `json:"merge_requests_disable_committers_approval"`
1510	RequirePasswordToApprove                  bool                         `json:"require_password_to_approve"`
1511}
1512
1513// GetApprovalConfiguration get the approval configuration for a project.
1514//
1515// GitLab API docs:
1516// https://docs.gitlab.com/ee/api/merge_request_approvals.html#get-configuration
1517func (s *ProjectsService) GetApprovalConfiguration(pid interface{}, options ...RequestOptionFunc) (*ProjectApprovals, *Response, error) {
1518	project, err := parseID(pid)
1519	if err != nil {
1520		return nil, nil, err
1521	}
1522	u := fmt.Sprintf("projects/%s/approvals", pathEscape(project))
1523
1524	req, err := s.client.NewRequest(http.MethodGet, u, nil, options)
1525	if err != nil {
1526		return nil, nil, err
1527	}
1528
1529	pa := new(ProjectApprovals)
1530	resp, err := s.client.Do(req, pa)
1531	if err != nil {
1532		return nil, resp, err
1533	}
1534
1535	return pa, resp, err
1536}
1537
1538// ChangeApprovalConfigurationOptions represents the available
1539// ApprovalConfiguration() options.
1540//
1541// GitLab API docs:
1542// https://docs.gitlab.com/ee/api/merge_request_approvals.html#change-configuration
1543type ChangeApprovalConfigurationOptions struct {
1544	ApprovalsBeforeMerge                      *int  `url:"approvals_before_merge,omitempty" json:"approvals_before_merge,omitempty"`
1545	ResetApprovalsOnPush                      *bool `url:"reset_approvals_on_push,omitempty" json:"reset_approvals_on_push,omitempty"`
1546	DisableOverridingApproversPerMergeRequest *bool `url:"disable_overriding_approvers_per_merge_request,omitempty" json:"disable_overriding_approvers_per_merge_request,omitempty"`
1547	MergeRequestsAuthorApproval               *bool `url:"merge_requests_author_approval,omitempty" json:"merge_requests_author_approval,omitempty"`
1548	MergeRequestsDisableCommittersApproval    *bool `url:"merge_requests_disable_committers_approval,omitempty" json:"merge_requests_disable_committers_approval,omitempty"`
1549	RequirePasswordToApprove                  *bool `url:"require_password_to_approve,omitempty" json:"require_password_to_approve,omitempty"`
1550}
1551
1552// ChangeApprovalConfiguration updates the approval configuration for a project.
1553//
1554// GitLab API docs:
1555// https://docs.gitlab.com/ee/api/merge_request_approvals.html#change-configuration
1556func (s *ProjectsService) ChangeApprovalConfiguration(pid interface{}, opt *ChangeApprovalConfigurationOptions, options ...RequestOptionFunc) (*ProjectApprovals, *Response, error) {
1557	project, err := parseID(pid)
1558	if err != nil {
1559		return nil, nil, err
1560	}
1561	u := fmt.Sprintf("projects/%s/approvals", pathEscape(project))
1562
1563	req, err := s.client.NewRequest(http.MethodPost, u, opt, options)
1564	if err != nil {
1565		return nil, nil, err
1566	}
1567
1568	pa := new(ProjectApprovals)
1569	resp, err := s.client.Do(req, pa)
1570	if err != nil {
1571		return nil, resp, err
1572	}
1573
1574	return pa, resp, err
1575}
1576
1577// GetProjectApprovalRules looks up the list of project level approvers.
1578//
1579// GitLab API docs:
1580// https://docs.gitlab.com/ee/api/merge_request_approvals.html#get-project-level-rules
1581func (s *ProjectsService) GetProjectApprovalRules(pid interface{}, options ...RequestOptionFunc) ([]*ProjectApprovalRule, *Response, error) {
1582	project, err := parseID(pid)
1583	if err != nil {
1584		return nil, nil, err
1585	}
1586	u := fmt.Sprintf("projects/%s/approval_rules", pathEscape(project))
1587
1588	req, err := s.client.NewRequest(http.MethodGet, u, nil, options)
1589	if err != nil {
1590		return nil, nil, err
1591	}
1592
1593	var par []*ProjectApprovalRule
1594	resp, err := s.client.Do(req, &par)
1595	if err != nil {
1596		return nil, resp, err
1597	}
1598
1599	return par, resp, err
1600}
1601
1602// CreateProjectLevelRuleOptions represents the available CreateProjectApprovalRule()
1603// options.
1604//
1605// GitLab API docs:
1606// https://docs.gitlab.com/ee/api/merge_request_approvals.html#create-project-level-rules
1607type CreateProjectLevelRuleOptions struct {
1608	Name               *string `url:"name,omitempty" json:"name,omitempty"`
1609	ApprovalsRequired  *int    `url:"approvals_required,omitempty" json:"approvals_required,omitempty"`
1610	UserIDs            []int   `url:"user_ids,omitempty" json:"user_ids,omitempty"`
1611	GroupIDs           []int   `url:"group_ids,omitempty" json:"group_ids,omitempty"`
1612	ProtectedBranchIDs []int   `url:"protected_branch_ids,omitempty" json:"protected_branch_ids,omitempty"`
1613}
1614
1615// CreateProjectApprovalRule creates a new project-level approval rule.
1616//
1617// GitLab API docs:
1618// https://docs.gitlab.com/ee/api/merge_request_approvals.html#create-project-level-rules
1619func (s *ProjectsService) CreateProjectApprovalRule(pid interface{}, opt *CreateProjectLevelRuleOptions, options ...RequestOptionFunc) (*ProjectApprovalRule, *Response, error) {
1620	project, err := parseID(pid)
1621	if err != nil {
1622		return nil, nil, err
1623	}
1624	u := fmt.Sprintf("projects/%s/approval_rules", pathEscape(project))
1625
1626	req, err := s.client.NewRequest(http.MethodPost, u, opt, options)
1627	if err != nil {
1628		return nil, nil, err
1629	}
1630
1631	par := new(ProjectApprovalRule)
1632	resp, err := s.client.Do(req, &par)
1633	if err != nil {
1634		return nil, resp, err
1635	}
1636
1637	return par, resp, err
1638}
1639
1640// UpdateProjectLevelRuleOptions represents the available UpdateProjectApprovalRule()
1641// options.
1642//
1643// GitLab API docs:
1644// https://docs.gitlab.com/ee/api/merge_request_approvals.html#update-project-level-rules
1645type UpdateProjectLevelRuleOptions struct {
1646	Name               *string `url:"name,omitempty" json:"name,omitempty"`
1647	ApprovalsRequired  *int    `url:"approvals_required,omitempty" json:"approvals_required,omitempty"`
1648	UserIDs            []int   `url:"user_ids,omitempty" json:"user_ids,omitempty"`
1649	GroupIDs           []int   `url:"group_ids,omitempty" json:"group_ids,omitempty"`
1650	ProtectedBranchIDs []int   `url:"protected_branch_ids,omitempty" json:"protected_branch_ids,omitempty"`
1651}
1652
1653// UpdateProjectApprovalRule updates an existing approval rule with new options.
1654//
1655// GitLab API docs:
1656// https://docs.gitlab.com/ee/api/merge_request_approvals.html#update-project-level-rules
1657func (s *ProjectsService) UpdateProjectApprovalRule(pid interface{}, approvalRule int, opt *UpdateProjectLevelRuleOptions, options ...RequestOptionFunc) (*ProjectApprovalRule, *Response, error) {
1658	project, err := parseID(pid)
1659	if err != nil {
1660		return nil, nil, err
1661	}
1662	u := fmt.Sprintf("projects/%s/approval_rules/%d", pathEscape(project), approvalRule)
1663
1664	req, err := s.client.NewRequest(http.MethodPut, u, opt, options)
1665	if err != nil {
1666		return nil, nil, err
1667	}
1668
1669	par := new(ProjectApprovalRule)
1670	resp, err := s.client.Do(req, &par)
1671	if err != nil {
1672		return nil, resp, err
1673	}
1674
1675	return par, resp, err
1676}
1677
1678// DeleteProjectApprovalRule deletes a project-level approval rule.
1679//
1680// GitLab API docs:
1681// https://docs.gitlab.com/ee/api/merge_request_approvals.html#delete-project-level-rules
1682func (s *ProjectsService) DeleteProjectApprovalRule(pid interface{}, approvalRule int, options ...RequestOptionFunc) (*Response, error) {
1683	project, err := parseID(pid)
1684	if err != nil {
1685		return nil, err
1686	}
1687	u := fmt.Sprintf("projects/%s/approval_rules/%d", pathEscape(project), approvalRule)
1688
1689	req, err := s.client.NewRequest(http.MethodDelete, u, nil, options)
1690	if err != nil {
1691		return nil, err
1692	}
1693
1694	return s.client.Do(req, nil)
1695}
1696
1697// ChangeAllowedApproversOptions represents the available ChangeAllowedApprovers()
1698// options.
1699//
1700// GitLab API docs:
1701// https://docs.gitlab.com/ee/api/merge_request_approvals.html#change-allowed-approvers
1702type ChangeAllowedApproversOptions struct {
1703	ApproverIDs      []int `url:"approver_ids,omitempty" json:"approver_ids,omitempty"`
1704	ApproverGroupIDs []int `url:"approver_group_ids,omitempty" json:"approver_group_ids,omitempty"`
1705}
1706
1707// ChangeAllowedApprovers updates the list of approvers and approver groups.
1708//
1709// GitLab API docs:
1710// https://docs.gitlab.com/ee/api/merge_request_approvals.html#change-allowed-approvers
1711func (s *ProjectsService) ChangeAllowedApprovers(pid interface{}, opt *ChangeAllowedApproversOptions, options ...RequestOptionFunc) (*ProjectApprovals, *Response, error) {
1712	project, err := parseID(pid)
1713	if err != nil {
1714		return nil, nil, err
1715	}
1716	u := fmt.Sprintf("projects/%s/approvers", pathEscape(project))
1717
1718	req, err := s.client.NewRequest(http.MethodPut, u, opt, options)
1719	if err != nil {
1720		return nil, nil, err
1721	}
1722
1723	pa := new(ProjectApprovals)
1724	resp, err := s.client.Do(req, pa)
1725	if err != nil {
1726		return nil, resp, err
1727	}
1728
1729	return pa, resp, err
1730}
1731
1732// StartMirroringProject start the pull mirroring process for a project.
1733//
1734// GitLab API docs:
1735// https://docs.gitlab.com/ee/api/projects.html#start-the-pull-mirroring-process-for-a-project-starter
1736func (s *ProjectsService) StartMirroringProject(pid interface{}, options ...RequestOptionFunc) (*Response, error) {
1737	project, err := parseID(pid)
1738	if err != nil {
1739		return nil, err
1740	}
1741	u := fmt.Sprintf("projects/%s/mirror/pull", pathEscape(project))
1742
1743	req, err := s.client.NewRequest(http.MethodPost, u, nil, options)
1744	if err != nil {
1745		return nil, err
1746	}
1747
1748	resp, err := s.client.Do(req, nil)
1749	if err != nil {
1750		return resp, err
1751	}
1752
1753	return resp, err
1754}
1755
1756// TransferProjectOptions represents the available TransferProject() options.
1757//
1758// GitLab API docs: https://docs.gitlab.com/ce/api/projects.html#transfer-a-project-to-a-new-namespace
1759type TransferProjectOptions struct {
1760	Namespace interface{} `url:"namespace,omitempty" json:"namespace,omitempty"`
1761}
1762
1763// TransferProject transfer a project into the specified namespace
1764//
1765// GitLab API docs: https://docs.gitlab.com/ce/api/projects.html#transfer-a-project-to-a-new-namespace
1766func (s *ProjectsService) TransferProject(pid interface{}, opt *TransferProjectOptions, options ...RequestOptionFunc) (*Project, *Response, error) {
1767	project, err := parseID(pid)
1768	if err != nil {
1769		return nil, nil, err
1770	}
1771	u := fmt.Sprintf("projects/%s/transfer", pathEscape(project))
1772
1773	req, err := s.client.NewRequest(http.MethodPut, u, opt, options)
1774	if err != nil {
1775		return nil, nil, err
1776	}
1777
1778	p := new(Project)
1779	resp, err := s.client.Do(req, p)
1780	if err != nil {
1781		return nil, resp, err
1782	}
1783
1784	return p, resp, err
1785}
1786