...

Source file src/github.com/xanzy/go-gitlab/projects.go

Documentation: github.com/xanzy/go-gitlab

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

View as plain text