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