projects.go

   1//
   2// Copyright 2017, Sander van Harmelen
   3//
   4// Licensed under the Apache License, Version 2.0 (the "License");
   5// you may not use this file except in compliance with the License.
   6// You may obtain a copy of the License at
   7//
   8//     http://www.apache.org/licenses/LICENSE-2.0
   9//
  10// Unless required by applicable law or agreed to in writing, software
  11// distributed under the License is distributed on an "AS IS" BASIS,
  12// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13// See the License for the specific language governing permissions and
  14// limitations under the License.
  15//
  16
  17package gitlab
  18
  19import (
  20	"bytes"
  21	"fmt"
  22	"io"
  23	"io/ioutil"
  24	"mime/multipart"
  25	"os"
  26	"time"
  27)
  28
  29// ProjectsService handles communication with the repositories related methods
  30// of the GitLab API.
  31//
  32// GitLab API docs: https://docs.gitlab.com/ce/api/projects.html
  33type ProjectsService struct {
  34	client *Client
  35}
  36
  37// Project represents a GitLab project.
  38//
  39// GitLab API docs: https://docs.gitlab.com/ce/api/projects.html
  40type Project struct {
  41	ID                                        int               `json:"id"`
  42	Description                               string            `json:"description"`
  43	DefaultBranch                             string            `json:"default_branch"`
  44	Public                                    bool              `json:"public"`
  45	Visibility                                VisibilityValue   `json:"visibility"`
  46	SSHURLToRepo                              string            `json:"ssh_url_to_repo"`
  47	HTTPURLToRepo                             string            `json:"http_url_to_repo"`
  48	WebURL                                    string            `json:"web_url"`
  49	ReadmeURL                                 string            `json:"readme_url"`
  50	TagList                                   []string          `json:"tag_list"`
  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	ContainerRegistryEnabled                  bool              `json:"container_registry_enabled"`
  65	CreatedAt                                 *time.Time        `json:"created_at,omitempty"`
  66	LastActivityAt                            *time.Time        `json:"last_activity_at,omitempty"`
  67	CreatorID                                 int               `json:"creator_id"`
  68	Namespace                                 *ProjectNamespace `json:"namespace"`
  69	ImportStatus                              string            `json:"import_status"`
  70	ImportError                               string            `json:"import_error"`
  71	Permissions                               *Permissions      `json:"permissions"`
  72	Archived                                  bool              `json:"archived"`
  73	AvatarURL                                 string            `json:"avatar_url"`
  74	SharedRunnersEnabled                      bool              `json:"shared_runners_enabled"`
  75	ForksCount                                int               `json:"forks_count"`
  76	StarCount                                 int               `json:"star_count"`
  77	RunnersToken                              string            `json:"runners_token"`
  78	PublicBuilds                              bool              `json:"public_builds"`
  79	OnlyAllowMergeIfPipelineSucceeds          bool              `json:"only_allow_merge_if_pipeline_succeeds"`
  80	OnlyAllowMergeIfAllDiscussionsAreResolved bool              `json:"only_allow_merge_if_all_discussions_are_resolved"`
  81	LFSEnabled                                bool              `json:"lfs_enabled"`
  82	RequestAccessEnabled                      bool              `json:"request_access_enabled"`
  83	MergeMethod                               MergeMethodValue  `json:"merge_method"`
  84	ForkedFromProject                         *ForkParent       `json:"forked_from_project"`
  85	Mirror                                    bool              `json:"mirror"`
  86	MirrorUserID                              int               `json:"mirror_user_id"`
  87	MirrorTriggerBuilds                       bool              `json:"mirror_trigger_builds"`
  88	OnlyMirrorProtectedBranches               bool              `json:"only_mirror_protected_branches"`
  89	MirrorOverwritesDivergedBranches          bool              `json:"mirror_overwrites_diverged_branches"`
  90	SharedWithGroups                          []struct {
  91		GroupID          int    `json:"group_id"`
  92		GroupName        string `json:"group_name"`
  93		GroupAccessLevel int    `json:"group_access_level"`
  94	} `json:"shared_with_groups"`
  95	Statistics       *ProjectStatistics `json:"statistics"`
  96	Links            *Links             `json:"_links,omitempty"`
  97	CIConfigPath     *string            `json:"ci_config_path"`
  98	CustomAttributes []*CustomAttribute `json:"custom_attributes"`
  99}
 100
 101// Repository represents a repository.
 102type Repository struct {
 103	Name              string          `json:"name"`
 104	Description       string          `json:"description"`
 105	WebURL            string          `json:"web_url"`
 106	AvatarURL         string          `json:"avatar_url"`
 107	GitSSHURL         string          `json:"git_ssh_url"`
 108	GitHTTPURL        string          `json:"git_http_url"`
 109	Namespace         string          `json:"namespace"`
 110	Visibility        VisibilityValue `json:"visibility"`
 111	PathWithNamespace string          `json:"path_with_namespace"`
 112	DefaultBranch     string          `json:"default_branch"`
 113	Homepage          string          `json:"homepage"`
 114	URL               string          `json:"url"`
 115	SSHURL            string          `json:"ssh_url"`
 116	HTTPURL           string          `json:"http_url"`
 117}
 118
 119// ProjectNamespace represents a project namespace.
 120type ProjectNamespace struct {
 121	ID       int    `json:"id"`
 122	Name     string `json:"name"`
 123	Path     string `json:"path"`
 124	Kind     string `json:"kind"`
 125	FullPath string `json:"full_path"`
 126}
 127
 128// StorageStatistics represents a statistics record for a group or project.
 129type StorageStatistics struct {
 130	StorageSize      int64 `json:"storage_size"`
 131	RepositorySize   int64 `json:"repository_size"`
 132	LfsObjectsSize   int64 `json:"lfs_objects_size"`
 133	JobArtifactsSize int64 `json:"job_artifacts_size"`
 134}
 135
 136// ProjectStatistics represents a statistics record for a project.
 137type ProjectStatistics struct {
 138	StorageStatistics
 139	CommitCount int `json:"commit_count"`
 140}
 141
 142// Permissions represents permissions.
 143type Permissions struct {
 144	ProjectAccess *ProjectAccess `json:"project_access"`
 145	GroupAccess   *GroupAccess   `json:"group_access"`
 146}
 147
 148// ProjectAccess represents project access.
 149type ProjectAccess struct {
 150	AccessLevel       AccessLevelValue       `json:"access_level"`
 151	NotificationLevel NotificationLevelValue `json:"notification_level"`
 152}
 153
 154// GroupAccess represents group access.
 155type GroupAccess struct {
 156	AccessLevel       AccessLevelValue       `json:"access_level"`
 157	NotificationLevel NotificationLevelValue `json:"notification_level"`
 158}
 159
 160// ForkParent represents the parent project when this is a fork.
 161type ForkParent struct {
 162	HTTPURLToRepo     string `json:"http_url_to_repo"`
 163	ID                int    `json:"id"`
 164	Name              string `json:"name"`
 165	NameWithNamespace string `json:"name_with_namespace"`
 166	Path              string `json:"path"`
 167	PathWithNamespace string `json:"path_with_namespace"`
 168	WebURL            string `json:"web_url"`
 169}
 170
 171// Links represents a project web links for self, issues, merge_requests,
 172// repo_branches, labels, events, members.
 173type Links struct {
 174	Self          string `json:"self"`
 175	Issues        string `json:"issues"`
 176	MergeRequests string `json:"merge_requests"`
 177	RepoBranches  string `json:"repo_branches"`
 178	Labels        string `json:"labels"`
 179	Events        string `json:"events"`
 180	Members       string `json:"members"`
 181}
 182
 183func (s Project) String() string {
 184	return Stringify(s)
 185}
 186
 187// ProjectApprovalRule represents a GitLab project approval rule.
 188//
 189// GitLab API docs:
 190// https://docs.gitlab.com/ee/api/merge_request_approvals.html#get-project-level-rules
 191type ProjectApprovalRule struct {
 192	ID                   int          `json:"id"`
 193	Name                 string       `json:"name"`
 194	RuleType             string       `json:"rule_type"`
 195	EligibleApprovers    []*BasicUser `json:"eligible_approvers"`
 196	ApprovalsRequired    int          `json:"approvals_required"`
 197	Users                []*BasicUser `json:"users"`
 198	Groups               []*Group     `json:"groups"`
 199	ContainsHiddenGroups bool         `json:"contains_hidden_groups"`
 200}
 201
 202func (s ProjectApprovalRule) String() string {
 203	return Stringify(s)
 204}
 205
 206// ListProjectsOptions represents the available ListProjects() options.
 207//
 208// GitLab API docs: https://docs.gitlab.com/ce/api/projects.html#list-projects
 209type ListProjectsOptions struct {
 210	ListOptions
 211	Archived                 *bool             `url:"archived,omitempty" json:"archived,omitempty"`
 212	OrderBy                  *string           `url:"order_by,omitempty" json:"order_by,omitempty"`
 213	Sort                     *string           `url:"sort,omitempty" json:"sort,omitempty"`
 214	Search                   *string           `url:"search,omitempty" json:"search,omitempty"`
 215	Simple                   *bool             `url:"simple,omitempty" json:"simple,omitempty"`
 216	Owned                    *bool             `url:"owned,omitempty" json:"owned,omitempty"`
 217	Membership               *bool             `url:"membership,omitempty" json:"membership,omitempty"`
 218	Starred                  *bool             `url:"starred,omitempty" json:"starred,omitempty"`
 219	Statistics               *bool             `url:"statistics,omitempty" json:"statistics,omitempty"`
 220	Visibility               *VisibilityValue  `url:"visibility,omitempty" json:"visibility,omitempty"`
 221	WithIssuesEnabled        *bool             `url:"with_issues_enabled,omitempty" json:"with_issues_enabled,omitempty"`
 222	WithMergeRequestsEnabled *bool             `url:"with_merge_requests_enabled,omitempty" json:"with_merge_requests_enabled,omitempty"`
 223	MinAccessLevel           *AccessLevelValue `url:"min_access_level,omitempty" json:"min_access_level,omitempty"`
 224	WithCustomAttributes     *bool             `url:"with_custom_attributes,omitempty" json:"with_custom_attributes,omitempty"`
 225	WithProgrammingLanguage  *string           `url:"with_programming_language,omitempty" json:"with_programming_language,omitempty"`
 226}
 227
 228// ListProjects gets a list of projects accessible by the authenticated user.
 229//
 230// GitLab API docs: https://docs.gitlab.com/ce/api/projects.html#list-projects
 231func (s *ProjectsService) ListProjects(opt *ListProjectsOptions, options ...OptionFunc) ([]*Project, *Response, error) {
 232	req, err := s.client.NewRequest("GET", "projects", opt, options)
 233	if err != nil {
 234		return nil, nil, err
 235	}
 236
 237	var p []*Project
 238	resp, err := s.client.Do(req, &p)
 239	if err != nil {
 240		return nil, resp, err
 241	}
 242
 243	return p, resp, err
 244}
 245
 246// ListUserProjects gets a list of projects for the given user.
 247//
 248// GitLab API docs:
 249// https://docs.gitlab.com/ce/api/projects.html#list-user-projects
 250func (s *ProjectsService) ListUserProjects(uid interface{}, opt *ListProjectsOptions, options ...OptionFunc) ([]*Project, *Response, error) {
 251	user, err := parseID(uid)
 252	if err != nil {
 253		return nil, nil, err
 254	}
 255	u := fmt.Sprintf("users/%s/projects", user)
 256
 257	req, err := s.client.NewRequest("GET", u, opt, options)
 258	if err != nil {
 259		return nil, nil, err
 260	}
 261
 262	var p []*Project
 263	resp, err := s.client.Do(req, &p)
 264	if err != nil {
 265		return nil, resp, err
 266	}
 267
 268	return p, resp, err
 269}
 270
 271// ProjectUser represents a GitLab project user.
 272type ProjectUser struct {
 273	ID        int    `json:"id"`
 274	Name      string `json:"name"`
 275	Username  string `json:"username"`
 276	State     string `json:"state"`
 277	AvatarURL string `json:"avatar_url"`
 278	WebURL    string `json:"web_url"`
 279}
 280
 281// ListProjectUserOptions represents the available ListProjectsUsers() options.
 282//
 283// GitLab API docs: https://docs.gitlab.com/ce/api/projects.html#get-project-users
 284type ListProjectUserOptions struct {
 285	ListOptions
 286	Search *string `url:"search,omitempty" json:"search,omitempty"`
 287}
 288
 289// ListProjectsUsers gets a list of users for the given project.
 290//
 291// GitLab API docs:
 292// https://docs.gitlab.com/ce/api/projects.html#get-project-users
 293func (s *ProjectsService) ListProjectsUsers(pid interface{}, opt *ListProjectUserOptions, options ...OptionFunc) ([]*ProjectUser, *Response, error) {
 294	project, err := parseID(pid)
 295	if err != nil {
 296		return nil, nil, err
 297	}
 298	u := fmt.Sprintf("projects/%s/users", pathEscape(project))
 299
 300	req, err := s.client.NewRequest("GET", u, opt, options)
 301	if err != nil {
 302		return nil, nil, err
 303	}
 304
 305	var p []*ProjectUser
 306	resp, err := s.client.Do(req, &p)
 307	if err != nil {
 308		return nil, resp, err
 309	}
 310
 311	return p, resp, err
 312}
 313
 314// ProjectLanguages is a map of strings because the response is arbitrary
 315//
 316// Gitlab API docs: https://docs.gitlab.com/ce/api/projects.html#languages
 317type ProjectLanguages map[string]float32
 318
 319// GetProjectLanguages gets a list of languages used by the project
 320//
 321// GitLab API docs:  https://docs.gitlab.com/ce/api/projects.html#languages
 322func (s *ProjectsService) GetProjectLanguages(pid interface{}, options ...OptionFunc) (*ProjectLanguages, *Response, error) {
 323	project, err := parseID(pid)
 324	if err != nil {
 325		return nil, nil, err
 326	}
 327	u := fmt.Sprintf("projects/%s/languages", pathEscape(project))
 328
 329	req, err := s.client.NewRequest("GET", u, nil, options)
 330	if err != nil {
 331		return nil, nil, err
 332	}
 333
 334	p := new(ProjectLanguages)
 335	resp, err := s.client.Do(req, p)
 336	if err != nil {
 337		return nil, resp, err
 338	}
 339
 340	return p, resp, err
 341}
 342
 343// GetProjectOptions represents the available GetProject() options.
 344//
 345// GitLab API docs: https://docs.gitlab.com/ee/api/projects.html#get-single-project
 346type GetProjectOptions struct {
 347	Statistics           *bool `url:"statistics,omitempty" json:"statistics,omitempty"`
 348	License              *bool `url:"license,omitempty" json:"license,omitempty"`
 349	WithCustomAttributes *bool `url:"with_custom_attributes,omitempty" json:"with_custom_attributes,omitempty"`
 350}
 351
 352// GetProject gets a specific project, identified by project ID or
 353// NAMESPACE/PROJECT_NAME, which is owned by the authenticated user.
 354//
 355// GitLab API docs:
 356// https://docs.gitlab.com/ce/api/projects.html#get-single-project
 357func (s *ProjectsService) GetProject(pid interface{}, opt *GetProjectOptions, options ...OptionFunc) (*Project, *Response, error) {
 358	project, err := parseID(pid)
 359	if err != nil {
 360		return nil, nil, err
 361	}
 362	u := fmt.Sprintf("projects/%s", pathEscape(project))
 363
 364	req, err := s.client.NewRequest("GET", u, opt, options)
 365	if err != nil {
 366		return nil, nil, err
 367	}
 368
 369	p := new(Project)
 370	resp, err := s.client.Do(req, p)
 371	if err != nil {
 372		return nil, resp, err
 373	}
 374
 375	return p, resp, err
 376}
 377
 378// ProjectEvent represents a GitLab project event.
 379//
 380// GitLab API docs:
 381// https://docs.gitlab.com/ce/api/projects.html#get-project-events
 382type ProjectEvent struct {
 383	Title          interface{} `json:"title"`
 384	ProjectID      int         `json:"project_id"`
 385	ActionName     string      `json:"action_name"`
 386	TargetID       interface{} `json:"target_id"`
 387	TargetType     interface{} `json:"target_type"`
 388	AuthorID       int         `json:"author_id"`
 389	AuthorUsername string      `json:"author_username"`
 390	Data           struct {
 391		Before            string      `json:"before"`
 392		After             string      `json:"after"`
 393		Ref               string      `json:"ref"`
 394		UserID            int         `json:"user_id"`
 395		UserName          string      `json:"user_name"`
 396		Repository        *Repository `json:"repository"`
 397		Commits           []*Commit   `json:"commits"`
 398		TotalCommitsCount int         `json:"total_commits_count"`
 399	} `json:"data"`
 400	TargetTitle interface{} `json:"target_title"`
 401}
 402
 403func (s ProjectEvent) String() string {
 404	return Stringify(s)
 405}
 406
 407// GetProjectEventsOptions represents the available GetProjectEvents() options.
 408//
 409// GitLab API docs:
 410// https://docs.gitlab.com/ce/api/projects.html#get-project-events
 411type GetProjectEventsOptions ListOptions
 412
 413// GetProjectEvents gets the events for the specified project. Sorted from
 414// newest to latest.
 415//
 416// GitLab API docs:
 417// https://docs.gitlab.com/ce/api/projects.html#get-project-events
 418func (s *ProjectsService) GetProjectEvents(pid interface{}, opt *GetProjectEventsOptions, options ...OptionFunc) ([]*ProjectEvent, *Response, error) {
 419	project, err := parseID(pid)
 420	if err != nil {
 421		return nil, nil, err
 422	}
 423	u := fmt.Sprintf("projects/%s/events", pathEscape(project))
 424
 425	req, err := s.client.NewRequest("GET", u, opt, options)
 426	if err != nil {
 427		return nil, nil, err
 428	}
 429
 430	var p []*ProjectEvent
 431	resp, err := s.client.Do(req, &p)
 432	if err != nil {
 433		return nil, resp, err
 434	}
 435
 436	return p, resp, err
 437}
 438
 439// CreateProjectOptions represents the available CreateProject() options.
 440//
 441// GitLab API docs: https://docs.gitlab.com/ee/api/projects.html#create-project
 442type CreateProjectOptions struct {
 443	Name                                      *string           `url:"name,omitempty" json:"name,omitempty"`
 444	Path                                      *string           `url:"path,omitempty" json:"path,omitempty"`
 445	DefaultBranch                             *string           `url:"default_branch,omitempty" json:"default_branch,omitempty"`
 446	NamespaceID                               *int              `url:"namespace_id,omitempty" json:"namespace_id,omitempty"`
 447	Description                               *string           `url:"description,omitempty" json:"description,omitempty"`
 448	IssuesEnabled                             *bool             `url:"issues_enabled,omitempty" json:"issues_enabled,omitempty"`
 449	MergeRequestsEnabled                      *bool             `url:"merge_requests_enabled,omitempty" json:"merge_requests_enabled,omitempty"`
 450	JobsEnabled                               *bool             `url:"jobs_enabled,omitempty" json:"jobs_enabled,omitempty"`
 451	WikiEnabled                               *bool             `url:"wiki_enabled,omitempty" json:"wiki_enabled,omitempty"`
 452	SnippetsEnabled                           *bool             `url:"snippets_enabled,omitempty" json:"snippets_enabled,omitempty"`
 453	ResolveOutdatedDiffDiscussions            *bool             `url:"resolve_outdated_diff_discussions,omitempty" json:"resolve_outdated_diff_discussions,omitempty"`
 454	ContainerRegistryEnabled                  *bool             `url:"container_registry_enabled,omitempty" json:"container_registry_enabled,omitempty"`
 455	SharedRunnersEnabled                      *bool             `url:"shared_runners_enabled,omitempty" json:"shared_runners_enabled,omitempty"`
 456	Visibility                                *VisibilityValue  `url:"visibility,omitempty" json:"visibility,omitempty"`
 457	ImportURL                                 *string           `url:"import_url,omitempty" json:"import_url,omitempty"`
 458	PublicBuilds                              *bool             `url:"public_builds,omitempty" json:"public_builds,omitempty"`
 459	OnlyAllowMergeIfPipelineSucceeds          *bool             `url:"only_allow_merge_if_pipeline_succeeds,omitempty" json:"only_allow_merge_if_pipeline_succeeds,omitempty"`
 460	OnlyAllowMergeIfAllDiscussionsAreResolved *bool             `url:"only_allow_merge_if_all_discussions_are_resolved,omitempty" json:"only_allow_merge_if_all_discussions_are_resolved,omitempty"`
 461	MergeMethod                               *MergeMethodValue `url:"merge_method,omitempty" json:"merge_method,omitempty"`
 462	LFSEnabled                                *bool             `url:"lfs_enabled,omitempty" json:"lfs_enabled,omitempty"`
 463	RequestAccessEnabled                      *bool             `url:"request_access_enabled,omitempty" json:"request_access_enabled,omitempty"`
 464	TagList                                   *[]string         `url:"tag_list,omitempty" json:"tag_list,omitempty"`
 465	PrintingMergeRequestLinkEnabled           *bool             `url:"printing_merge_request_link_enabled,omitempty" json:"printing_merge_request_link_enabled,omitempty"`
 466	CIConfigPath                              *string           `url:"ci_config_path,omitempty" json:"ci_config_path,omitempty"`
 467	ApprovalsBeforeMerge                      *int              `url:"approvals_before_merge,omitempty" json:"approvals_before_merge,omitempty"`
 468	Mirror                                    *bool             `url:"mirror,omitempty" json:"mirror,omitempty"`
 469	MirrorTriggerBuilds                       *bool             `url:"mirror_trigger_builds,omitempty" json:"mirror_trigger_builds,omitempty"`
 470	InitializeWithReadme                      *bool             `url:"initialize_with_readme,omitempty" json:"initialize_with_readme,omitempty"`
 471	TemplateName                              *string           `url:"template_name,omitempty" json:"template_name,omitempty"`
 472	UseCustomTemplate                         *bool             `url:"use_custom_template,omitempty" json:"use_custom_template,omitempty"`
 473	GroupWithProjectTemplatesID               *int              `url:"group_with_project_templates_id,omitempty" json:"group_with_project_templates_id,omitempty"`
 474}
 475
 476// CreateProject creates a new project owned by the authenticated user.
 477//
 478// GitLab API docs: https://docs.gitlab.com/ce/api/projects.html#create-project
 479func (s *ProjectsService) CreateProject(opt *CreateProjectOptions, options ...OptionFunc) (*Project, *Response, error) {
 480	req, err := s.client.NewRequest("POST", "projects", opt, options)
 481	if err != nil {
 482		return nil, nil, err
 483	}
 484
 485	p := new(Project)
 486	resp, err := s.client.Do(req, p)
 487	if err != nil {
 488		return nil, resp, err
 489	}
 490
 491	return p, resp, err
 492}
 493
 494// CreateProjectForUserOptions represents the available CreateProjectForUser()
 495// options.
 496//
 497// GitLab API docs:
 498// https://docs.gitlab.com/ce/api/projects.html#create-project-for-user
 499type CreateProjectForUserOptions CreateProjectOptions
 500
 501// CreateProjectForUser creates a new project owned by the specified user.
 502// Available only for admins.
 503//
 504// GitLab API docs:
 505// https://docs.gitlab.com/ce/api/projects.html#create-project-for-user
 506func (s *ProjectsService) CreateProjectForUser(user int, opt *CreateProjectForUserOptions, options ...OptionFunc) (*Project, *Response, error) {
 507	u := fmt.Sprintf("projects/user/%d", user)
 508
 509	req, err := s.client.NewRequest("POST", u, opt, options)
 510	if err != nil {
 511		return nil, nil, err
 512	}
 513
 514	p := new(Project)
 515	resp, err := s.client.Do(req, p)
 516	if err != nil {
 517		return nil, resp, err
 518	}
 519
 520	return p, resp, err
 521}
 522
 523// EditProjectOptions represents the available EditProject() options.
 524//
 525// GitLab API docs: https://docs.gitlab.com/ce/api/projects.html#edit-project
 526type EditProjectOptions struct {
 527	Name                                      *string           `url:"name,omitempty" json:"name,omitempty"`
 528	Path                                      *string           `url:"path,omitempty" json:"path,omitempty"`
 529	DefaultBranch                             *string           `url:"default_branch,omitempty" json:"default_branch,omitempty"`
 530	Description                               *string           `url:"description,omitempty" json:"description,omitempty"`
 531	IssuesEnabled                             *bool             `url:"issues_enabled,omitempty" json:"issues_enabled,omitempty"`
 532	MergeRequestsEnabled                      *bool             `url:"merge_requests_enabled,omitempty" json:"merge_requests_enabled,omitempty"`
 533	JobsEnabled                               *bool             `url:"jobs_enabled,omitempty" json:"jobs_enabled,omitempty"`
 534	WikiEnabled                               *bool             `url:"wiki_enabled,omitempty" json:"wiki_enabled,omitempty"`
 535	SnippetsEnabled                           *bool             `url:"snippets_enabled,omitempty" json:"snippets_enabled,omitempty"`
 536	ResolveOutdatedDiffDiscussions            *bool             `url:"resolve_outdated_diff_discussions,omitempty" json:"resolve_outdated_diff_discussions,omitempty"`
 537	ContainerRegistryEnabled                  *bool             `url:"container_registry_enabled,omitempty" json:"container_registry_enabled,omitempty"`
 538	SharedRunnersEnabled                      *bool             `url:"shared_runners_enabled,omitempty" json:"shared_runners_enabled,omitempty"`
 539	Visibility                                *VisibilityValue  `url:"visibility,omitempty" json:"visibility,omitempty"`
 540	ImportURL                                 *string           `url:"import_url,omitempty" json:"import_url,omitempty"`
 541	PublicBuilds                              *bool             `url:"public_builds,omitempty" json:"public_builds,omitempty"`
 542	OnlyAllowMergeIfPipelineSucceeds          *bool             `url:"only_allow_merge_if_pipeline_succeeds,omitempty" json:"only_allow_merge_if_pipeline_succeeds,omitempty"`
 543	OnlyAllowMergeIfAllDiscussionsAreResolved *bool             `url:"only_allow_merge_if_all_discussions_are_resolved,omitempty" json:"only_allow_merge_if_all_discussions_are_resolved,omitempty"`
 544	MergeMethod                               *MergeMethodValue `url:"merge_method,omitempty" json:"merge_method,omitempty"`
 545	LFSEnabled                                *bool             `url:"lfs_enabled,omitempty" json:"lfs_enabled,omitempty"`
 546	RequestAccessEnabled                      *bool             `url:"request_access_enabled,omitempty" json:"request_access_enabled,omitempty"`
 547	TagList                                   *[]string         `url:"tag_list,omitempty" json:"tag_list,omitempty"`
 548	CIConfigPath                              *string           `url:"ci_config_path,omitempty" json:"ci_config_path,omitempty"`
 549	ApprovalsBeforeMerge                      *int              `url:"approvals_before_merge,omitempty" json:"approvals_before_merge,omitempty"`
 550	ExternalAuthorizationClassificationLabel  *string           `url:"external_authorization_classification_label,omitempty" json:"external_authorization_classification_label,omitempty"`
 551	Mirror                                    *bool             `url:"mirror,omitempty" json:"mirror,omitempty"`
 552	MirrorTriggerBuilds                       *bool             `url:"mirror_trigger_builds,omitempty" json:"mirror_trigger_builds,omitempty"`
 553	MirrorUserID                              *int              `url:"mirror_user_id,omitempty" json:"mirror_user_id,omitempty"`
 554	OnlyMirrorProtectedBranches               *bool             `url:"only_mirror_protected_branches,omitempty" json:"only_mirror_protected_branches,omitempty"`
 555	MirrorOverwritesDivergedBranches          *bool             `url:"mirror_overwrites_diverged_branches,omitempty" json:"mirror_overwrites_diverged_branches,omitempty"`
 556	PackagesEnabled                           *bool             `url:"packages_enabled,omitempty" json:"packages_enabled,omitempty"`
 557}
 558
 559// EditProject updates an existing project.
 560//
 561// GitLab API docs: https://docs.gitlab.com/ce/api/projects.html#edit-project
 562func (s *ProjectsService) EditProject(pid interface{}, opt *EditProjectOptions, options ...OptionFunc) (*Project, *Response, error) {
 563	project, err := parseID(pid)
 564	if err != nil {
 565		return nil, nil, err
 566	}
 567	u := fmt.Sprintf("projects/%s", pathEscape(project))
 568
 569	req, err := s.client.NewRequest("PUT", u, opt, options)
 570	if err != nil {
 571		return nil, nil, err
 572	}
 573
 574	p := new(Project)
 575	resp, err := s.client.Do(req, p)
 576	if err != nil {
 577		return nil, resp, err
 578	}
 579
 580	return p, resp, err
 581}
 582
 583// ForkProjectOptions represents the available ForkProject() options.
 584//
 585// GitLab API docs: https://docs.gitlab.com/ce/api/projects.html#fork-project
 586type ForkProjectOptions struct {
 587	Namespace *string `url:"namespace,omitempty" json:"namespace,omitempty"`
 588	Name      *string `url:"name,omitempty" json:"name,omitempty" `
 589	Path      *string `url:"path,omitempty" json:"path,omitempty"`
 590}
 591
 592// ForkProject forks a project into the user namespace of the authenticated
 593// user.
 594//
 595// GitLab API docs: https://docs.gitlab.com/ce/api/projects.html#fork-project
 596func (s *ProjectsService) ForkProject(pid interface{}, opt *ForkProjectOptions, options ...OptionFunc) (*Project, *Response, error) {
 597	project, err := parseID(pid)
 598	if err != nil {
 599		return nil, nil, err
 600	}
 601	u := fmt.Sprintf("projects/%s/fork", pathEscape(project))
 602
 603	req, err := s.client.NewRequest("POST", u, opt, options)
 604	if err != nil {
 605		return nil, nil, err
 606	}
 607
 608	p := new(Project)
 609	resp, err := s.client.Do(req, p)
 610	if err != nil {
 611		return nil, resp, err
 612	}
 613
 614	return p, resp, err
 615}
 616
 617// StarProject stars a given the project.
 618//
 619// GitLab API docs:
 620// https://docs.gitlab.com/ce/api/projects.html#star-a-project
 621func (s *ProjectsService) StarProject(pid interface{}, options ...OptionFunc) (*Project, *Response, error) {
 622	project, err := parseID(pid)
 623	if err != nil {
 624		return nil, nil, err
 625	}
 626	u := fmt.Sprintf("projects/%s/star", pathEscape(project))
 627
 628	req, err := s.client.NewRequest("POST", u, nil, options)
 629	if err != nil {
 630		return nil, nil, err
 631	}
 632
 633	p := new(Project)
 634	resp, err := s.client.Do(req, p)
 635	if err != nil {
 636		return nil, resp, err
 637	}
 638
 639	return p, resp, err
 640}
 641
 642// UnstarProject unstars a given project.
 643//
 644// GitLab API docs:
 645// https://docs.gitlab.com/ce/api/projects.html#unstar-a-project
 646func (s *ProjectsService) UnstarProject(pid interface{}, options ...OptionFunc) (*Project, *Response, error) {
 647	project, err := parseID(pid)
 648	if err != nil {
 649		return nil, nil, err
 650	}
 651	u := fmt.Sprintf("projects/%s/unstar", pathEscape(project))
 652
 653	req, err := s.client.NewRequest("POST", u, nil, options)
 654	if err != nil {
 655		return nil, nil, err
 656	}
 657
 658	p := new(Project)
 659	resp, err := s.client.Do(req, p)
 660	if err != nil {
 661		return nil, resp, err
 662	}
 663
 664	return p, resp, err
 665}
 666
 667// ArchiveProject archives the project if the user is either admin or the
 668// project owner of this project.
 669//
 670// GitLab API docs:
 671// https://docs.gitlab.com/ce/api/projects.html#archive-a-project
 672func (s *ProjectsService) ArchiveProject(pid interface{}, options ...OptionFunc) (*Project, *Response, error) {
 673	project, err := parseID(pid)
 674	if err != nil {
 675		return nil, nil, err
 676	}
 677	u := fmt.Sprintf("projects/%s/archive", pathEscape(project))
 678
 679	req, err := s.client.NewRequest("POST", u, nil, options)
 680	if err != nil {
 681		return nil, nil, err
 682	}
 683
 684	p := new(Project)
 685	resp, err := s.client.Do(req, p)
 686	if err != nil {
 687		return nil, resp, err
 688	}
 689
 690	return p, resp, err
 691}
 692
 693// UnarchiveProject unarchives the project if the user is either admin or
 694// the project owner of this project.
 695//
 696// GitLab API docs:
 697// https://docs.gitlab.com/ce/api/projects.html#unarchive-a-project
 698func (s *ProjectsService) UnarchiveProject(pid interface{}, options ...OptionFunc) (*Project, *Response, error) {
 699	project, err := parseID(pid)
 700	if err != nil {
 701		return nil, nil, err
 702	}
 703	u := fmt.Sprintf("projects/%s/unarchive", pathEscape(project))
 704
 705	req, err := s.client.NewRequest("POST", u, nil, options)
 706	if err != nil {
 707		return nil, nil, err
 708	}
 709
 710	p := new(Project)
 711	resp, err := s.client.Do(req, p)
 712	if err != nil {
 713		return nil, resp, err
 714	}
 715
 716	return p, resp, err
 717}
 718
 719// DeleteProject removes a project including all associated resources
 720// (issues, merge requests etc.)
 721//
 722// GitLab API docs: https://docs.gitlab.com/ce/api/projects.html#remove-project
 723func (s *ProjectsService) DeleteProject(pid interface{}, options ...OptionFunc) (*Response, error) {
 724	project, err := parseID(pid)
 725	if err != nil {
 726		return nil, err
 727	}
 728	u := fmt.Sprintf("projects/%s", pathEscape(project))
 729
 730	req, err := s.client.NewRequest("DELETE", u, nil, options)
 731	if err != nil {
 732		return nil, err
 733	}
 734
 735	return s.client.Do(req, nil)
 736}
 737
 738// ShareWithGroupOptions represents options to share project with groups
 739//
 740// GitLab API docs: https://docs.gitlab.com/ce/api/projects.html#share-project-with-group
 741type ShareWithGroupOptions struct {
 742	GroupID     *int              `url:"group_id" json:"group_id"`
 743	GroupAccess *AccessLevelValue `url:"group_access" json:"group_access"`
 744	ExpiresAt   *string           `url:"expires_at" json:"expires_at"`
 745}
 746
 747// ShareProjectWithGroup allows to share a project with a group.
 748//
 749// GitLab API docs: https://docs.gitlab.com/ce/api/projects.html#share-project-with-group
 750func (s *ProjectsService) ShareProjectWithGroup(pid interface{}, opt *ShareWithGroupOptions, options ...OptionFunc) (*Response, error) {
 751	project, err := parseID(pid)
 752	if err != nil {
 753		return nil, err
 754	}
 755	u := fmt.Sprintf("projects/%s/share", pathEscape(project))
 756
 757	req, err := s.client.NewRequest("POST", u, opt, options)
 758	if err != nil {
 759		return nil, err
 760	}
 761
 762	return s.client.Do(req, nil)
 763}
 764
 765// DeleteSharedProjectFromGroup allows to unshare a project from a group.
 766//
 767// GitLab API docs: https://docs.gitlab.com/ce/api/projects.html#delete-a-shared-project-link-within-a-group
 768func (s *ProjectsService) DeleteSharedProjectFromGroup(pid interface{}, groupID int, options ...OptionFunc) (*Response, error) {
 769	project, err := parseID(pid)
 770	if err != nil {
 771		return nil, err
 772	}
 773	u := fmt.Sprintf("projects/%s/share/%d", pathEscape(project), groupID)
 774
 775	req, err := s.client.NewRequest("DELETE", u, nil, options)
 776	if err != nil {
 777		return nil, err
 778	}
 779
 780	return s.client.Do(req, nil)
 781}
 782
 783// ProjectMember represents a project member.
 784//
 785// GitLab API docs:
 786// https://docs.gitlab.com/ce/api/projects.html#list-project-team-members
 787type ProjectMember struct {
 788	ID          int              `json:"id"`
 789	Username    string           `json:"username"`
 790	Email       string           `json:"email"`
 791	Name        string           `json:"name"`
 792	State       string           `json:"state"`
 793	CreatedAt   *time.Time       `json:"created_at"`
 794	ExpiresAt   *ISOTime         `json:"expires_at"`
 795	AccessLevel AccessLevelValue `json:"access_level"`
 796}
 797
 798// ProjectHook represents a project hook.
 799//
 800// GitLab API docs:
 801// https://docs.gitlab.com/ce/api/projects.html#list-project-hooks
 802type ProjectHook struct {
 803	ID                       int        `json:"id"`
 804	URL                      string     `json:"url"`
 805	ProjectID                int        `json:"project_id"`
 806	PushEvents               bool       `json:"push_events"`
 807	IssuesEvents             bool       `json:"issues_events"`
 808	ConfidentialIssuesEvents bool       `json:"confidential_issues_events"`
 809	MergeRequestsEvents      bool       `json:"merge_requests_events"`
 810	TagPushEvents            bool       `json:"tag_push_events"`
 811	NoteEvents               bool       `json:"note_events"`
 812	JobEvents                bool       `json:"job_events"`
 813	PipelineEvents           bool       `json:"pipeline_events"`
 814	WikiPageEvents           bool       `json:"wiki_page_events"`
 815	EnableSSLVerification    bool       `json:"enable_ssl_verification"`
 816	CreatedAt                *time.Time `json:"created_at"`
 817}
 818
 819// ListProjectHooksOptions represents the available ListProjectHooks() options.
 820//
 821// GitLab API docs: https://docs.gitlab.com/ce/api/projects.html#list-project-hooks
 822type ListProjectHooksOptions ListOptions
 823
 824// ListProjectHooks gets a list of project hooks.
 825//
 826// GitLab API docs:
 827// https://docs.gitlab.com/ce/api/projects.html#list-project-hooks
 828func (s *ProjectsService) ListProjectHooks(pid interface{}, opt *ListProjectHooksOptions, options ...OptionFunc) ([]*ProjectHook, *Response, error) {
 829	project, err := parseID(pid)
 830	if err != nil {
 831		return nil, nil, err
 832	}
 833	u := fmt.Sprintf("projects/%s/hooks", pathEscape(project))
 834
 835	req, err := s.client.NewRequest("GET", u, opt, options)
 836	if err != nil {
 837		return nil, nil, err
 838	}
 839
 840	var ph []*ProjectHook
 841	resp, err := s.client.Do(req, &ph)
 842	if err != nil {
 843		return nil, resp, err
 844	}
 845
 846	return ph, resp, err
 847}
 848
 849// GetProjectHook gets a specific hook for a project.
 850//
 851// GitLab API docs:
 852// https://docs.gitlab.com/ce/api/projects.html#get-project-hook
 853func (s *ProjectsService) GetProjectHook(pid interface{}, hook int, options ...OptionFunc) (*ProjectHook, *Response, error) {
 854	project, err := parseID(pid)
 855	if err != nil {
 856		return nil, nil, err
 857	}
 858	u := fmt.Sprintf("projects/%s/hooks/%d", pathEscape(project), hook)
 859
 860	req, err := s.client.NewRequest("GET", u, nil, options)
 861	if err != nil {
 862		return nil, nil, err
 863	}
 864
 865	ph := new(ProjectHook)
 866	resp, err := s.client.Do(req, ph)
 867	if err != nil {
 868		return nil, resp, err
 869	}
 870
 871	return ph, resp, err
 872}
 873
 874// AddProjectHookOptions represents the available AddProjectHook() options.
 875//
 876// GitLab API docs:
 877// https://docs.gitlab.com/ce/api/projects.html#add-project-hook
 878type AddProjectHookOptions struct {
 879	URL                      *string `url:"url,omitempty" json:"url,omitempty"`
 880	PushEvents               *bool   `url:"push_events,omitempty" json:"push_events,omitempty"`
 881	IssuesEvents             *bool   `url:"issues_events,omitempty" json:"issues_events,omitempty"`
 882	ConfidentialIssuesEvents *bool   `url:"confidential_issues_events,omitempty" json:"confidential_issues_events,omitempty"`
 883	MergeRequestsEvents      *bool   `url:"merge_requests_events,omitempty" json:"merge_requests_events,omitempty"`
 884	TagPushEvents            *bool   `url:"tag_push_events,omitempty" json:"tag_push_events,omitempty"`
 885	NoteEvents               *bool   `url:"note_events,omitempty" json:"note_events,omitempty"`
 886	JobEvents                *bool   `url:"job_events,omitempty" json:"job_events,omitempty"`
 887	PipelineEvents           *bool   `url:"pipeline_events,omitempty" json:"pipeline_events,omitempty"`
 888	WikiPageEvents           *bool   `url:"wiki_page_events,omitempty" json:"wiki_page_events,omitempty"`
 889	EnableSSLVerification    *bool   `url:"enable_ssl_verification,omitempty" json:"enable_ssl_verification,omitempty"`
 890	Token                    *string `url:"token,omitempty" json:"token,omitempty"`
 891}
 892
 893// AddProjectHook adds a hook to a specified project.
 894//
 895// GitLab API docs:
 896// https://docs.gitlab.com/ce/api/projects.html#add-project-hook
 897func (s *ProjectsService) AddProjectHook(pid interface{}, opt *AddProjectHookOptions, options ...OptionFunc) (*ProjectHook, *Response, error) {
 898	project, err := parseID(pid)
 899	if err != nil {
 900		return nil, nil, err
 901	}
 902	u := fmt.Sprintf("projects/%s/hooks", pathEscape(project))
 903
 904	req, err := s.client.NewRequest("POST", u, opt, options)
 905	if err != nil {
 906		return nil, nil, err
 907	}
 908
 909	ph := new(ProjectHook)
 910	resp, err := s.client.Do(req, ph)
 911	if err != nil {
 912		return nil, resp, err
 913	}
 914
 915	return ph, resp, err
 916}
 917
 918// EditProjectHookOptions represents the available EditProjectHook() options.
 919//
 920// GitLab API docs:
 921// https://docs.gitlab.com/ce/api/projects.html#edit-project-hook
 922type EditProjectHookOptions struct {
 923	URL                      *string `url:"url,omitempty" json:"url,omitempty"`
 924	PushEvents               *bool   `url:"push_events,omitempty" json:"push_events,omitempty"`
 925	IssuesEvents             *bool   `url:"issues_events,omitempty" json:"issues_events,omitempty"`
 926	ConfidentialIssuesEvents *bool   `url:"confidential_issues_events,omitempty" json:"confidential_issues_events,omitempty"`
 927	MergeRequestsEvents      *bool   `url:"merge_requests_events,omitempty" json:"merge_requests_events,omitempty"`
 928	TagPushEvents            *bool   `url:"tag_push_events,omitempty" json:"tag_push_events,omitempty"`
 929	NoteEvents               *bool   `url:"note_events,omitempty" json:"note_events,omitempty"`
 930	JobEvents                *bool   `url:"job_events,omitempty" json:"job_events,omitempty"`
 931	PipelineEvents           *bool   `url:"pipeline_events,omitempty" json:"pipeline_events,omitempty"`
 932	WikiPageEvents           *bool   `url:"wiki_page_events,omitempty" json:"wiki_page_events,omitempty"`
 933	EnableSSLVerification    *bool   `url:"enable_ssl_verification,omitempty" json:"enable_ssl_verification,omitempty"`
 934	Token                    *string `url:"token,omitempty" json:"token,omitempty"`
 935}
 936
 937// EditProjectHook edits a hook for a specified project.
 938//
 939// GitLab API docs:
 940// https://docs.gitlab.com/ce/api/projects.html#edit-project-hook
 941func (s *ProjectsService) EditProjectHook(pid interface{}, hook int, opt *EditProjectHookOptions, options ...OptionFunc) (*ProjectHook, *Response, error) {
 942	project, err := parseID(pid)
 943	if err != nil {
 944		return nil, nil, err
 945	}
 946	u := fmt.Sprintf("projects/%s/hooks/%d", pathEscape(project), hook)
 947
 948	req, err := s.client.NewRequest("PUT", u, opt, options)
 949	if err != nil {
 950		return nil, nil, err
 951	}
 952
 953	ph := new(ProjectHook)
 954	resp, err := s.client.Do(req, ph)
 955	if err != nil {
 956		return nil, resp, err
 957	}
 958
 959	return ph, resp, err
 960}
 961
 962// DeleteProjectHook removes a hook from a project. This is an idempotent
 963// method and can be called multiple times. Either the hook is available or not.
 964//
 965// GitLab API docs:
 966// https://docs.gitlab.com/ce/api/projects.html#delete-project-hook
 967func (s *ProjectsService) DeleteProjectHook(pid interface{}, hook int, options ...OptionFunc) (*Response, error) {
 968	project, err := parseID(pid)
 969	if err != nil {
 970		return nil, err
 971	}
 972	u := fmt.Sprintf("projects/%s/hooks/%d", pathEscape(project), hook)
 973
 974	req, err := s.client.NewRequest("DELETE", u, nil, options)
 975	if err != nil {
 976		return nil, err
 977	}
 978
 979	return s.client.Do(req, nil)
 980}
 981
 982// ProjectForkRelation represents a project fork relationship.
 983//
 984// GitLab API docs:
 985// https://docs.gitlab.com/ce/api/projects.html#admin-fork-relation
 986type ProjectForkRelation struct {
 987	ID                  int        `json:"id"`
 988	ForkedToProjectID   int        `json:"forked_to_project_id"`
 989	ForkedFromProjectID int        `json:"forked_from_project_id"`
 990	CreatedAt           *time.Time `json:"created_at"`
 991	UpdatedAt           *time.Time `json:"updated_at"`
 992}
 993
 994// CreateProjectForkRelation creates a forked from/to relation between
 995// existing projects.
 996//
 997// GitLab API docs:
 998// https://docs.gitlab.com/ce/api/projects.html#create-a-forked-fromto-relation-between-existing-projects.
 999func (s *ProjectsService) CreateProjectForkRelation(pid int, fork int, options ...OptionFunc) (*ProjectForkRelation, *Response, error) {
1000	u := fmt.Sprintf("projects/%d/fork/%d", pid, fork)
1001
1002	req, err := s.client.NewRequest("POST", u, nil, options)
1003	if err != nil {
1004		return nil, nil, err
1005	}
1006
1007	pfr := new(ProjectForkRelation)
1008	resp, err := s.client.Do(req, pfr)
1009	if err != nil {
1010		return nil, resp, err
1011	}
1012
1013	return pfr, resp, err
1014}
1015
1016// DeleteProjectForkRelation deletes an existing forked from relationship.
1017//
1018// GitLab API docs:
1019// https://docs.gitlab.com/ce/api/projects.html#delete-an-existing-forked-from-relationship
1020func (s *ProjectsService) DeleteProjectForkRelation(pid int, options ...OptionFunc) (*Response, error) {
1021	u := fmt.Sprintf("projects/%d/fork", pid)
1022
1023	req, err := s.client.NewRequest("DELETE", u, nil, options)
1024	if err != nil {
1025		return nil, err
1026	}
1027
1028	return s.client.Do(req, nil)
1029}
1030
1031// ProjectFile represents an uploaded project file
1032//
1033// GitLab API docs: https://docs.gitlab.com/ce/api/projects.html#upload-a-file
1034type ProjectFile struct {
1035	Alt      string `json:"alt"`
1036	URL      string `json:"url"`
1037	Markdown string `json:"markdown"`
1038}
1039
1040// UploadFile upload a file from disk
1041//
1042// GitLab API docs: https://docs.gitlab.com/ce/api/projects.html#upload-a-file
1043func (s *ProjectsService) UploadFile(pid interface{}, file string, options ...OptionFunc) (*ProjectFile, *Response, error) {
1044	project, err := parseID(pid)
1045	if err != nil {
1046		return nil, nil, err
1047	}
1048	u := fmt.Sprintf("projects/%s/uploads", pathEscape(project))
1049
1050	f, err := os.Open(file)
1051	if err != nil {
1052		return nil, nil, err
1053	}
1054	defer f.Close()
1055
1056	b := &bytes.Buffer{}
1057	w := multipart.NewWriter(b)
1058
1059	fw, err := w.CreateFormFile("file", file)
1060	if err != nil {
1061		return nil, nil, err
1062	}
1063
1064	_, err = io.Copy(fw, f)
1065	if err != nil {
1066		return nil, nil, err
1067	}
1068	w.Close()
1069
1070	req, err := s.client.NewRequest("", u, nil, options)
1071	if err != nil {
1072		return nil, nil, err
1073	}
1074
1075	req.Body = ioutil.NopCloser(b)
1076	req.ContentLength = int64(b.Len())
1077	req.Header.Set("Content-Type", w.FormDataContentType())
1078	req.Method = "POST"
1079
1080	uf := &ProjectFile{}
1081	resp, err := s.client.Do(req, uf)
1082	if err != nil {
1083		return nil, resp, err
1084	}
1085
1086	return uf, resp, nil
1087}
1088
1089// ListProjectForks gets a list of project forks.
1090//
1091// GitLab API docs:
1092// https://docs.gitlab.com/ce/api/projects.html#list-forks-of-a-project
1093func (s *ProjectsService) ListProjectForks(pid interface{}, opt *ListProjectsOptions, options ...OptionFunc) ([]*Project, *Response, error) {
1094	project, err := parseID(pid)
1095	if err != nil {
1096		return nil, nil, err
1097	}
1098	u := fmt.Sprintf("projects/%s/forks", pathEscape(project))
1099
1100	req, err := s.client.NewRequest("GET", u, opt, options)
1101	if err != nil {
1102		return nil, nil, err
1103	}
1104
1105	var forks []*Project
1106	resp, err := s.client.Do(req, &forks)
1107	if err != nil {
1108		return nil, resp, err
1109	}
1110
1111	return forks, resp, err
1112}
1113
1114// ProjectPushRules represents a project push rule.
1115//
1116// GitLab API docs:
1117// https://docs.gitlab.com/ee/api/projects.html#push-rules
1118type ProjectPushRules struct {
1119	ID                 int        `json:"id"`
1120	ProjectID          int        `json:"project_id"`
1121	CommitMessageRegex string     `json:"commit_message_regex"`
1122	BranchNameRegex    string     `json:"branch_name_regex"`
1123	DenyDeleteTag      bool       `json:"deny_delete_tag"`
1124	CreatedAt          *time.Time `json:"created_at"`
1125	MemberCheck        bool       `json:"member_check"`
1126	PreventSecrets     bool       `json:"prevent_secrets"`
1127	AuthorEmailRegex   string     `json:"author_email_regex"`
1128	FileNameRegex      string     `json:"file_name_regex"`
1129	MaxFileSize        int        `json:"max_file_size"`
1130}
1131
1132// GetProjectPushRules gets the push rules of a project.
1133//
1134// GitLab API docs:
1135// https://docs.gitlab.com/ee/api/projects.html#get-project-push-rules
1136func (s *ProjectsService) GetProjectPushRules(pid interface{}, options ...OptionFunc) (*ProjectPushRules, *Response, error) {
1137	project, err := parseID(pid)
1138	if err != nil {
1139		return nil, nil, err
1140	}
1141	u := fmt.Sprintf("projects/%s/push_rule", pathEscape(project))
1142
1143	req, err := s.client.NewRequest("GET", u, nil, options)
1144	if err != nil {
1145		return nil, nil, err
1146	}
1147
1148	ppr := new(ProjectPushRules)
1149	resp, err := s.client.Do(req, ppr)
1150	if err != nil {
1151		return nil, resp, err
1152	}
1153
1154	return ppr, resp, err
1155}
1156
1157// AddProjectPushRuleOptions represents the available AddProjectPushRule()
1158// options.
1159//
1160// GitLab API docs:
1161// https://docs.gitlab.com/ee/api/projects.html#add-project-push-rule
1162type AddProjectPushRuleOptions struct {
1163	DenyDeleteTag      *bool   `url:"deny_delete_tag,omitempty" json:"deny_delete_tag,omitempty"`
1164	MemberCheck        *bool   `url:"member_check,omitempty" json:"member_check,omitempty"`
1165	PreventSecrets     *bool   `url:"prevent_secrets,omitempty" json:"prevent_secrets,omitempty"`
1166	CommitMessageRegex *string `url:"commit_message_regex,omitempty" json:"commit_message_regex,omitempty"`
1167	BranchNameRegex    *string `url:"branch_name_regex,omitempty" json:"branch_name_regex,omitempty"`
1168	AuthorEmailRegex   *string `url:"author_email_regex,omitempty" json:"author_email_regex,omitempty"`
1169	FileNameRegex      *string `url:"file_name_regex,omitempty" json:"file_name_regex,omitempty"`
1170	MaxFileSize        *int    `url:"max_file_size,omitempty" json:"max_file_size,omitempty"`
1171}
1172
1173// AddProjectPushRule adds a push rule to a specified project.
1174//
1175// GitLab API docs:
1176// https://docs.gitlab.com/ee/api/projects.html#add-project-push-rule
1177func (s *ProjectsService) AddProjectPushRule(pid interface{}, opt *AddProjectPushRuleOptions, options ...OptionFunc) (*ProjectPushRules, *Response, error) {
1178	project, err := parseID(pid)
1179	if err != nil {
1180		return nil, nil, err
1181	}
1182	u := fmt.Sprintf("projects/%s/push_rule", pathEscape(project))
1183
1184	req, err := s.client.NewRequest("POST", u, opt, options)
1185	if err != nil {
1186		return nil, nil, err
1187	}
1188
1189	ppr := new(ProjectPushRules)
1190	resp, err := s.client.Do(req, ppr)
1191	if err != nil {
1192		return nil, resp, err
1193	}
1194
1195	return ppr, resp, err
1196}
1197
1198// EditProjectPushRuleOptions represents the available EditProjectPushRule()
1199// options.
1200//
1201// GitLab API docs:
1202// https://docs.gitlab.com/ee/api/projects.html#edit-project-push-rule
1203type EditProjectPushRuleOptions struct {
1204	AuthorEmailRegex   *string `url:"author_email_regex,omitempty" json:"author_email_regex,omitempty"`
1205	BranchNameRegex    *string `url:"branch_name_regex,omitempty" json:"branch_name_regex,omitempty"`
1206	CommitMessageRegex *string `url:"commit_message_regex,omitempty" json:"commit_message_regex,omitempty"`
1207	FileNameRegex      *string `url:"file_name_regex,omitempty" json:"file_name_regex,omitempty"`
1208	DenyDeleteTag      *bool   `url:"deny_delete_tag,omitempty" json:"deny_delete_tag,omitempty"`
1209	MemberCheck        *bool   `url:"member_check,omitempty" json:"member_check,omitempty"`
1210	PreventSecrets     *bool   `url:"prevent_secrets,omitempty" json:"prevent_secrets,omitempty"`
1211	MaxFileSize        *int    `url:"max_file_size,omitempty" json:"max_file_size,omitempty"`
1212}
1213
1214// EditProjectPushRule edits a push rule for a specified project.
1215//
1216// GitLab API docs:
1217// https://docs.gitlab.com/ee/api/projects.html#edit-project-push-rule
1218func (s *ProjectsService) EditProjectPushRule(pid interface{}, opt *EditProjectPushRuleOptions, options ...OptionFunc) (*ProjectPushRules, *Response, error) {
1219	project, err := parseID(pid)
1220	if err != nil {
1221		return nil, nil, err
1222	}
1223	u := fmt.Sprintf("projects/%s/push_rule", pathEscape(project))
1224
1225	req, err := s.client.NewRequest("PUT", u, opt, options)
1226	if err != nil {
1227		return nil, nil, err
1228	}
1229
1230	ppr := new(ProjectPushRules)
1231	resp, err := s.client.Do(req, ppr)
1232	if err != nil {
1233		return nil, resp, err
1234	}
1235
1236	return ppr, resp, err
1237}
1238
1239// DeleteProjectPushRule removes a push rule from a project. This is an
1240// idempotent method and can be called multiple times. Either the push rule is
1241// available or not.
1242//
1243// GitLab API docs:
1244// https://docs.gitlab.com/ee/api/projects.html#delete-project-push-rule
1245func (s *ProjectsService) DeleteProjectPushRule(pid interface{}, options ...OptionFunc) (*Response, error) {
1246	project, err := parseID(pid)
1247	if err != nil {
1248		return nil, err
1249	}
1250	u := fmt.Sprintf("projects/%s/push_rule", pathEscape(project))
1251
1252	req, err := s.client.NewRequest("DELETE", u, nil, options)
1253	if err != nil {
1254		return nil, err
1255	}
1256
1257	return s.client.Do(req, nil)
1258}
1259
1260// ProjectApprovals represents GitLab project level merge request approvals.
1261//
1262// GitLab API docs:
1263// https://docs.gitlab.com/ee/api/merge_request_approvals.html#project-level-mr-approvals
1264type ProjectApprovals struct {
1265	Approvers                                 []*MergeRequestApproverUser  `json:"approvers"`
1266	ApproverGroups                            []*MergeRequestApproverGroup `json:"approver_groups"`
1267	ApprovalsBeforeMerge                      int                          `json:"approvals_before_merge"`
1268	ResetApprovalsOnPush                      bool                         `json:"reset_approvals_on_push"`
1269	DisableOverridingApproversPerMergeRequest bool                         `json:"disable_overriding_approvers_per_merge_request"`
1270	MergeRequestsAuthorApproval               bool                         `json:"merge_requests_author_approval"`
1271	MergeRequestsDisableCommittersApproval    bool                         `json:"merge_requests_disable_committers_approval"`
1272}
1273
1274// GetApprovalConfiguration get the approval configuration for a project.
1275//
1276// GitLab API docs:
1277// https://docs.gitlab.com/ee/api/merge_request_approvals.html#get-configuration
1278func (s *ProjectsService) GetApprovalConfiguration(pid interface{}, options ...OptionFunc) (*ProjectApprovals, *Response, error) {
1279	project, err := parseID(pid)
1280	if err != nil {
1281		return nil, nil, err
1282	}
1283	u := fmt.Sprintf("projects/%s/approvals", pathEscape(project))
1284
1285	req, err := s.client.NewRequest("GET", u, nil, options)
1286	if err != nil {
1287		return nil, nil, err
1288	}
1289
1290	pa := new(ProjectApprovals)
1291	resp, err := s.client.Do(req, pa)
1292	if err != nil {
1293		return nil, resp, err
1294	}
1295
1296	return pa, resp, err
1297}
1298
1299// ChangeApprovalConfigurationOptions represents the available
1300// ApprovalConfiguration() options.
1301//
1302// GitLab API docs:
1303// https://docs.gitlab.com/ee/api/merge_request_approvals.html#change-configuration
1304type ChangeApprovalConfigurationOptions struct {
1305	ApprovalsBeforeMerge                      *int  `url:"approvals_before_merge,omitempty" json:"approvals_before_merge,omitempty"`
1306	ResetApprovalsOnPush                      *bool `url:"reset_approvals_on_push,omitempty" json:"reset_approvals_on_push,omitempty"`
1307	DisableOverridingApproversPerMergeRequest *bool `url:"disable_overriding_approvers_per_merge_request,omitempty" json:"disable_overriding_approvers_per_merge_request,omitempty"`
1308	MergeRequestsAuthorApproval               *bool `url:"merge_requests_author_approval,omitempty" json:"merge_requests_author_approval,omitempty"`
1309	MergeRequestsDisableCommittersApproval    *bool `url:"merge_requests_disable_committers_approval,omitempty" json:"merge_requests_disable_committers_approval,omitempty"`
1310}
1311
1312// ChangeApprovalConfiguration updates the approval configuration for a project.
1313//
1314// GitLab API docs:
1315// https://docs.gitlab.com/ee/api/merge_request_approvals.html#change-configuration
1316func (s *ProjectsService) ChangeApprovalConfiguration(pid interface{}, opt *ChangeApprovalConfigurationOptions, options ...OptionFunc) (*ProjectApprovals, *Response, error) {
1317	project, err := parseID(pid)
1318	if err != nil {
1319		return nil, nil, err
1320	}
1321	u := fmt.Sprintf("projects/%s/approvals", pathEscape(project))
1322
1323	req, err := s.client.NewRequest("POST", u, opt, options)
1324	if err != nil {
1325		return nil, nil, err
1326	}
1327
1328	pa := new(ProjectApprovals)
1329	resp, err := s.client.Do(req, pa)
1330	if err != nil {
1331		return nil, resp, err
1332	}
1333
1334	return pa, resp, err
1335}
1336
1337// GetProjectApprovalRules looks up the list of project level approvers.
1338//
1339// GitLab API docs:
1340// https://docs.gitlab.com/ee/api/merge_request_approvals.html#get-project-level-rules
1341func (s *ProjectsService) GetProjectApprovalRules(pid interface{}, options ...OptionFunc) ([]*ProjectApprovalRule, *Response, error) {
1342	project, err := parseID(pid)
1343	if err != nil {
1344		return nil, nil, err
1345	}
1346	u := fmt.Sprintf("projects/%s/approval_rules", pathEscape(project))
1347
1348	req, err := s.client.NewRequest("GET", u, nil, options)
1349	if err != nil {
1350		return nil, nil, err
1351	}
1352
1353	var par []*ProjectApprovalRule
1354	resp, err := s.client.Do(req, &par)
1355	if err != nil {
1356		return nil, resp, err
1357	}
1358
1359	return par, resp, err
1360}
1361
1362// CreateProjectLevelRuleOptions represents the available CreateProjectApprovalRule()
1363// options.
1364//
1365// GitLab API docs:
1366// https://docs.gitlab.com/ee/api/merge_request_approvals.html#create-project-level-rules
1367type CreateProjectLevelRuleOptions struct {
1368	Name              *string `url:"name,omitempty" json:"name,omitempty"`
1369	ApprovalsRequired *int    `url:"approvals_required,omitempty" json:"approvals_required,omitempty"`
1370	UserIDs           []int   `url:"user_ids,omitempty" json:"user_ids,omitempty"`
1371	GroupIDs          []int   `url:"group_ids,omitempty" json:"group_ids,omitempty"`
1372}
1373
1374// CreateProjectApprovalRule creates a new project-level approval rule.
1375//
1376// GitLab API docs:
1377// https://docs.gitlab.com/ee/api/merge_request_approvals.html#create-project-level-rules
1378func (s *ProjectsService) CreateProjectApprovalRule(pid interface{}, opt *CreateProjectLevelRuleOptions, options ...OptionFunc) (*ProjectApprovalRule, *Response, error) {
1379	project, err := parseID(pid)
1380	if err != nil {
1381		return nil, nil, err
1382	}
1383	u := fmt.Sprintf("projects/%s/approval_rules", pathEscape(project))
1384
1385	req, err := s.client.NewRequest("POST", u, opt, options)
1386	if err != nil {
1387		return nil, nil, err
1388	}
1389
1390	par := new(ProjectApprovalRule)
1391	resp, err := s.client.Do(req, &par)
1392	if err != nil {
1393		return nil, resp, err
1394	}
1395
1396	return par, resp, err
1397}
1398
1399// UpdateProjectLevelRuleOptions represents the available UpdateProjectApprovalRule()
1400// options.
1401//
1402// GitLab API docs:
1403// https://docs.gitlab.com/ee/api/merge_request_approvals.html#update-project-level-rules
1404type UpdateProjectLevelRuleOptions struct {
1405	Name              *string `url:"name,omitempty" json:"name,omitempty"`
1406	ApprovalsRequired *int    `url:"approvals_required,omitempty" json:"approvals_required,omitempty"`
1407	UserIDs           []int   `url:"user_ids,omitempty" json:"user_ids,omitempty"`
1408	GroupIDs          []int   `url:"group_ids,omitempty" json:"group_ids,omitempty"`
1409}
1410
1411// UpdateProjectApprovalRule updates an existing approval rule with new options.
1412//
1413// GitLab API docs:
1414// https://docs.gitlab.com/ee/api/merge_request_approvals.html#update-project-level-rules
1415func (s *ProjectsService) UpdateProjectApprovalRule(pid interface{}, approvalRule int, opt *UpdateProjectLevelRuleOptions, options ...OptionFunc) (*ProjectApprovalRule, *Response, error) {
1416	project, err := parseID(pid)
1417	if err != nil {
1418		return nil, nil, err
1419	}
1420	u := fmt.Sprintf("projects/%s/approval_rules/%d", pathEscape(project), approvalRule)
1421
1422	req, err := s.client.NewRequest("PUT", u, opt, options)
1423	if err != nil {
1424		return nil, nil, err
1425	}
1426
1427	par := new(ProjectApprovalRule)
1428	resp, err := s.client.Do(req, &par)
1429	if err != nil {
1430		return nil, resp, err
1431	}
1432
1433	return par, resp, err
1434}
1435
1436// DeleteProjectApprovalRule deletes a project-level approval rule.
1437//
1438// GitLab API docs:
1439// https://docs.gitlab.com/ee/api/merge_request_approvals.html#delete-project-level-rules
1440func (s *ProjectsService) DeleteProjectApprovalRule(pid interface{}, approvalRule int, options ...OptionFunc) (*Response, error) {
1441	project, err := parseID(pid)
1442	if err != nil {
1443		return nil, err
1444	}
1445	u := fmt.Sprintf("projects/%s/approval_rules/%d", pathEscape(project), approvalRule)
1446
1447	req, err := s.client.NewRequest("DELETE", u, nil, options)
1448	if err != nil {
1449		return nil, err
1450	}
1451
1452	return s.client.Do(req, nil)
1453}
1454
1455// ChangeAllowedApproversOptions represents the available ChangeAllowedApprovers()
1456// options.
1457//
1458// GitLab API docs:
1459// https://docs.gitlab.com/ee/api/merge_request_approvals.html#change-allowed-approvers
1460type ChangeAllowedApproversOptions struct {
1461	ApproverIDs      []int `url:"approver_ids,omitempty" json:"approver_ids,omitempty"`
1462	ApproverGroupIDs []int `url:"approver_group_ids,omitempty" json:"approver_group_ids,omitempty"`
1463}
1464
1465// ChangeAllowedApprovers updates the list of approvers and approver groups.
1466//
1467// GitLab API docs:
1468// https://docs.gitlab.com/ee/api/merge_request_approvals.html#change-allowed-approvers
1469func (s *ProjectsService) ChangeAllowedApprovers(pid interface{}, opt *ChangeAllowedApproversOptions, options ...OptionFunc) (*ProjectApprovals, *Response, error) {
1470	project, err := parseID(pid)
1471	if err != nil {
1472		return nil, nil, err
1473	}
1474	u := fmt.Sprintf("projects/%s/approvers", pathEscape(project))
1475
1476	req, err := s.client.NewRequest("PUT", u, opt, options)
1477	if err != nil {
1478		return nil, nil, err
1479	}
1480
1481	pa := new(ProjectApprovals)
1482	resp, err := s.client.Do(req, pa)
1483	if err != nil {
1484		return nil, resp, err
1485	}
1486
1487	return pa, resp, err
1488}
1489
1490// StartMirroringProject start the pull mirroring process for a project.
1491//
1492// GitLab API docs:
1493// https://docs.gitlab.com/ee/api/projects.html#start-the-pull-mirroring-process-for-a-project-starter
1494func (s *ProjectsService) StartMirroringProject(pid interface{}, options ...OptionFunc) (*Response, error) {
1495	project, err := parseID(pid)
1496	if err != nil {
1497		return nil, err
1498	}
1499	u := fmt.Sprintf("projects/%s/mirror/pull", pathEscape(project))
1500
1501	req, err := s.client.NewRequest("POST", u, nil, options)
1502	if err != nil {
1503		return nil, err
1504	}
1505
1506	resp, err := s.client.Do(req, nil)
1507	if err != nil {
1508		return resp, err
1509	}
1510
1511	return resp, err
1512}