1// Copyright 2014 The Gogs Authors. All rights reserved. 2// Copyright 2020 The Gitea Authors. All rights reserved. 3// Use of this source code is governed by a MIT-style 4// license that can be found in the LICENSE file. 5 6package gitea 7 8import ( 9 "bytes" 10 "encoding/json" 11 "fmt" 12 "io" 13 "net/url" 14 "strings" 15 "time" 16) 17 18// Permission represents a set of permissions 19type Permission struct { 20 Admin bool `json:"admin"` 21 Push bool `json:"push"` 22 Pull bool `json:"pull"` 23} 24 25// InternalTracker represents settings for internal tracker 26type InternalTracker struct { 27 // Enable time tracking (Built-in issue tracker) 28 EnableTimeTracker bool `json:"enable_time_tracker"` 29 // Let only contributors track time (Built-in issue tracker) 30 AllowOnlyContributorsToTrackTime bool `json:"allow_only_contributors_to_track_time"` 31 // Enable dependencies for issues and pull requests (Built-in issue tracker) 32 EnableIssueDependencies bool `json:"enable_issue_dependencies"` 33} 34 35// ExternalTracker represents settings for external tracker 36type ExternalTracker struct { 37 // URL of external issue tracker. 38 ExternalTrackerURL string `json:"external_tracker_url"` 39 // External Issue Tracker URL Format. Use the placeholders {user}, {repo} and {index} for the username, repository name and issue index. 40 ExternalTrackerFormat string `json:"external_tracker_format"` 41 // External Issue Tracker Number Format, either `numeric` or `alphanumeric` 42 ExternalTrackerStyle string `json:"external_tracker_style"` 43} 44 45// ExternalWiki represents setting for external wiki 46type ExternalWiki struct { 47 // URL of external wiki. 48 ExternalWikiURL string `json:"external_wiki_url"` 49} 50 51// Repository represents a repository 52type Repository struct { 53 ID int64 `json:"id"` 54 Owner *User `json:"owner"` 55 Name string `json:"name"` 56 FullName string `json:"full_name"` 57 Description string `json:"description"` 58 Empty bool `json:"empty"` 59 Private bool `json:"private"` 60 Fork bool `json:"fork"` 61 Template bool `json:"template"` 62 Parent *Repository `json:"parent"` 63 Mirror bool `json:"mirror"` 64 Size int `json:"size"` 65 HTMLURL string `json:"html_url"` 66 SSHURL string `json:"ssh_url"` 67 CloneURL string `json:"clone_url"` 68 OriginalURL string `json:"original_url"` 69 Website string `json:"website"` 70 Stars int `json:"stars_count"` 71 Forks int `json:"forks_count"` 72 Watchers int `json:"watchers_count"` 73 OpenIssues int `json:"open_issues_count"` 74 OpenPulls int `json:"open_pr_counter"` 75 Releases int `json:"release_counter"` 76 DefaultBranch string `json:"default_branch"` 77 Archived bool `json:"archived"` 78 Created time.Time `json:"created_at"` 79 Updated time.Time `json:"updated_at"` 80 Permissions *Permission `json:"permissions,omitempty"` 81 HasIssues bool `json:"has_issues"` 82 InternalTracker *InternalTracker `json:"internal_tracker,omitempty"` 83 ExternalTracker *ExternalTracker `json:"external_tracker,omitempty"` 84 HasWiki bool `json:"has_wiki"` 85 ExternalWiki *ExternalWiki `json:"external_wiki,omitempty"` 86 HasPullRequests bool `json:"has_pull_requests"` 87 HasProjects bool `json:"has_projects"` 88 IgnoreWhitespaceConflicts bool `json:"ignore_whitespace_conflicts"` 89 AllowMerge bool `json:"allow_merge_commits"` 90 AllowRebase bool `json:"allow_rebase"` 91 AllowRebaseMerge bool `json:"allow_rebase_explicit"` 92 AllowSquash bool `json:"allow_squash_merge"` 93 AvatarURL string `json:"avatar_url"` 94 Internal bool `json:"internal"` 95 MirrorInterval string `json:"mirror_interval"` 96 DefaultMergeStyle MergeStyle `json:"default_merge_style"` 97} 98 99// RepoType represent repo type 100type RepoType string 101 102const ( 103 // RepoTypeNone dont specify a type 104 RepoTypeNone RepoType = "" 105 // RepoTypeSource is the default repo type 106 RepoTypeSource RepoType = "source" 107 // RepoTypeFork is a repo witch was forked from an other one 108 RepoTypeFork RepoType = "fork" 109 // RepoTypeMirror represents an mirror repo 110 RepoTypeMirror RepoType = "mirror" 111) 112 113// TrustModel represent how git signatures are handled in a repository 114type TrustModel string 115 116const ( 117 // TrustModelDefault use TM set by global config 118 TrustModelDefault TrustModel = "default" 119 // TrustModelCollaborator gpg signature has to be owned by a repo collaborator 120 TrustModelCollaborator TrustModel = "collaborator" 121 // TrustModelCommitter gpg signature has to match committer 122 TrustModelCommitter TrustModel = "committer" 123 // TrustModelCollaboratorCommitter gpg signature has to match committer and owned by a repo collaborator 124 TrustModelCollaboratorCommitter TrustModel = "collaboratorcommitter" 125) 126 127// ListReposOptions options for listing repositories 128type ListReposOptions struct { 129 ListOptions 130} 131 132// ListMyRepos lists all repositories for the authenticated user that has access to. 133func (c *Client) ListMyRepos(opt ListReposOptions) ([]*Repository, *Response, error) { 134 opt.setDefaults() 135 repos := make([]*Repository, 0, opt.PageSize) 136 resp, err := c.getParsedResponse("GET", fmt.Sprintf("/user/repos?%s", opt.getURLQuery().Encode()), nil, nil, &repos) 137 return repos, resp, err 138} 139 140// ListUserRepos list all repositories of one user by user's name 141func (c *Client) ListUserRepos(user string, opt ListReposOptions) ([]*Repository, *Response, error) { 142 if err := escapeValidatePathSegments(&user); err != nil { 143 return nil, nil, err 144 } 145 opt.setDefaults() 146 repos := make([]*Repository, 0, opt.PageSize) 147 resp, err := c.getParsedResponse("GET", fmt.Sprintf("/users/%s/repos?%s", user, opt.getURLQuery().Encode()), nil, nil, &repos) 148 return repos, resp, err 149} 150 151// ListOrgReposOptions options for a organization's repositories 152type ListOrgReposOptions struct { 153 ListOptions 154} 155 156// ListOrgRepos list all repositories of one organization by organization's name 157func (c *Client) ListOrgRepos(org string, opt ListOrgReposOptions) ([]*Repository, *Response, error) { 158 if err := escapeValidatePathSegments(&org); err != nil { 159 return nil, nil, err 160 } 161 opt.setDefaults() 162 repos := make([]*Repository, 0, opt.PageSize) 163 resp, err := c.getParsedResponse("GET", fmt.Sprintf("/orgs/%s/repos?%s", org, opt.getURLQuery().Encode()), nil, nil, &repos) 164 return repos, resp, err 165} 166 167// SearchRepoOptions options for searching repositories 168type SearchRepoOptions struct { 169 ListOptions 170 171 // The keyword to query 172 Keyword string 173 // Limit search to repositories with keyword as topic 174 KeywordIsTopic bool 175 // Include search of keyword within repository description 176 KeywordInDescription bool 177 178 /* 179 User Filter 180 */ 181 182 // Repo Owner 183 OwnerID int64 184 // Stared By UserID 185 StarredByUserID int64 186 187 /* 188 Repo Attributes 189 */ 190 191 // pubic, private or all repositories (defaults to all) 192 IsPrivate *bool 193 // archived, non-archived or all repositories (defaults to all) 194 IsArchived *bool 195 // Exclude template repos from search 196 ExcludeTemplate bool 197 // Filter by "fork", "source", "mirror" 198 Type RepoType 199 200 /* 201 Sort Filters 202 */ 203 204 // sort repos by attribute. Supported values are "alpha", "created", "updated", "size", and "id". Default is "alpha" 205 Sort string 206 // sort order, either "asc" (ascending) or "desc" (descending). Default is "asc", ignored if "sort" is not specified. 207 Order string 208 // Repo owner to prioritize in the results 209 PrioritizedByOwnerID int64 210 211 /* 212 Cover EdgeCases 213 */ 214 // if set all other options are ignored and this string is used as query 215 RawQuery string 216} 217 218// QueryEncode turns options into querystring argument 219func (opt *SearchRepoOptions) QueryEncode() string { 220 query := opt.getURLQuery() 221 if opt.Keyword != "" { 222 query.Add("q", opt.Keyword) 223 } 224 if opt.KeywordIsTopic { 225 query.Add("topic", "true") 226 } 227 if opt.KeywordInDescription { 228 query.Add("includeDesc", "true") 229 } 230 231 // User Filter 232 if opt.OwnerID > 0 { 233 query.Add("uid", fmt.Sprintf("%d", opt.OwnerID)) 234 query.Add("exclusive", "true") 235 } 236 if opt.StarredByUserID > 0 { 237 query.Add("starredBy", fmt.Sprintf("%d", opt.StarredByUserID)) 238 } 239 240 // Repo Attributes 241 if opt.IsPrivate != nil { 242 query.Add("is_private", fmt.Sprintf("%v", opt.IsPrivate)) 243 } 244 if opt.IsArchived != nil { 245 query.Add("archived", fmt.Sprintf("%v", opt.IsArchived)) 246 } 247 if opt.ExcludeTemplate { 248 query.Add("template", "false") 249 } 250 if len(opt.Type) != 0 { 251 query.Add("mode", string(opt.Type)) 252 } 253 254 // Sort Filters 255 if opt.Sort != "" { 256 query.Add("sort", opt.Sort) 257 } 258 if opt.PrioritizedByOwnerID > 0 { 259 query.Add("priority_owner_id", fmt.Sprintf("%d", opt.PrioritizedByOwnerID)) 260 } 261 if opt.Order != "" { 262 query.Add("order", opt.Order) 263 } 264 265 return query.Encode() 266} 267 268type searchRepoResponse struct { 269 Repos []*Repository `json:"data"` 270} 271 272// SearchRepos searches for repositories matching the given filters 273func (c *Client) SearchRepos(opt SearchRepoOptions) ([]*Repository, *Response, error) { 274 opt.setDefaults() 275 repos := new(searchRepoResponse) 276 277 link, _ := url.Parse("/repos/search") 278 279 if len(opt.RawQuery) != 0 { 280 link.RawQuery = opt.RawQuery 281 } else { 282 link.RawQuery = opt.QueryEncode() 283 // IsPrivate only works on gitea >= 1.12.0 284 if err := c.checkServerVersionGreaterThanOrEqual(version1_12_0); err != nil && opt.IsPrivate != nil { 285 if *opt.IsPrivate { 286 // private repos only not supported on gitea <= 1.11.x 287 return nil, nil, err 288 } 289 link.Query().Add("private", "false") 290 } 291 } 292 293 resp, err := c.getParsedResponse("GET", link.String(), nil, nil, &repos) 294 return repos.Repos, resp, err 295} 296 297// CreateRepoOption options when creating repository 298type CreateRepoOption struct { 299 // Name of the repository to create 300 Name string `json:"name"` 301 // Description of the repository to create 302 Description string `json:"description"` 303 // Whether the repository is private 304 Private bool `json:"private"` 305 // Issue Label set to use 306 IssueLabels string `json:"issue_labels"` 307 // Whether the repository should be auto-intialized? 308 AutoInit bool `json:"auto_init"` 309 // Whether the repository is template 310 Template bool `json:"template"` 311 // Gitignores to use 312 Gitignores string `json:"gitignores"` 313 // License to use 314 License string `json:"license"` 315 // Readme of the repository to create 316 Readme string `json:"readme"` 317 // DefaultBranch of the repository (used when initializes and in template) 318 DefaultBranch string `json:"default_branch"` 319 // TrustModel of the repository 320 TrustModel TrustModel `json:"trust_model"` 321} 322 323// Validate the CreateRepoOption struct 324func (opt CreateRepoOption) Validate(c *Client) error { 325 if len(strings.TrimSpace(opt.Name)) == 0 { 326 return fmt.Errorf("name is empty") 327 } 328 if len(opt.Name) > 100 { 329 return fmt.Errorf("name has more than 100 chars") 330 } 331 if len(opt.Description) > 255 { 332 return fmt.Errorf("name has more than 255 chars") 333 } 334 if len(opt.DefaultBranch) > 100 { 335 return fmt.Errorf("name has more than 100 chars") 336 } 337 if len(opt.TrustModel) != 0 { 338 if err := c.checkServerVersionGreaterThanOrEqual(version1_13_0); err != nil { 339 return err 340 } 341 } 342 return nil 343} 344 345// CreateRepo creates a repository for authenticated user. 346func (c *Client) CreateRepo(opt CreateRepoOption) (*Repository, *Response, error) { 347 if err := opt.Validate(c); err != nil { 348 return nil, nil, err 349 } 350 body, err := json.Marshal(&opt) 351 if err != nil { 352 return nil, nil, err 353 } 354 repo := new(Repository) 355 resp, err := c.getParsedResponse("POST", "/user/repos", jsonHeader, bytes.NewReader(body), repo) 356 return repo, resp, err 357} 358 359// CreateOrgRepo creates an organization repository for authenticated user. 360func (c *Client) CreateOrgRepo(org string, opt CreateRepoOption) (*Repository, *Response, error) { 361 if err := escapeValidatePathSegments(&org); err != nil { 362 return nil, nil, err 363 } 364 if err := opt.Validate(c); err != nil { 365 return nil, nil, err 366 } 367 body, err := json.Marshal(&opt) 368 if err != nil { 369 return nil, nil, err 370 } 371 repo := new(Repository) 372 resp, err := c.getParsedResponse("POST", fmt.Sprintf("/org/%s/repos", org), jsonHeader, bytes.NewReader(body), repo) 373 return repo, resp, err 374} 375 376// GetRepo returns information of a repository of given owner. 377func (c *Client) GetRepo(owner, reponame string) (*Repository, *Response, error) { 378 if err := escapeValidatePathSegments(&owner, &reponame); err != nil { 379 return nil, nil, err 380 } 381 repo := new(Repository) 382 resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s", owner, reponame), nil, nil, repo) 383 return repo, resp, err 384} 385 386// GetRepoByID returns information of a repository by a giver repository ID. 387func (c *Client) GetRepoByID(id int64) (*Repository, *Response, error) { 388 repo := new(Repository) 389 resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repositories/%d", id), nil, nil, repo) 390 return repo, resp, err 391} 392 393// EditRepoOption options when editing a repository's properties 394type EditRepoOption struct { 395 // name of the repository 396 Name *string `json:"name,omitempty"` 397 // a short description of the repository. 398 Description *string `json:"description,omitempty"` 399 // a URL with more information about the repository. 400 Website *string `json:"website,omitempty"` 401 // either `true` to make the repository private or `false` to make it public. 402 // Note: you will get a 422 error if the organization restricts changing repository visibility to organization 403 // owners and a non-owner tries to change the value of private. 404 Private *bool `json:"private,omitempty"` 405 // either `true` to make this repository a template or `false` to make it a normal repository 406 Template *bool `json:"template,omitempty"` 407 // either `true` to enable issues for this repository or `false` to disable them. 408 HasIssues *bool `json:"has_issues,omitempty"` 409 // set this structure to configure internal issue tracker (requires has_issues) 410 InternalTracker *InternalTracker `json:"internal_tracker,omitempty"` 411 // set this structure to use external issue tracker (requires has_issues) 412 ExternalTracker *ExternalTracker `json:"external_tracker,omitempty"` 413 // either `true` to enable the wiki for this repository or `false` to disable it. 414 HasWiki *bool `json:"has_wiki,omitempty"` 415 // set this structure to use external wiki instead of internal (requires has_wiki) 416 ExternalWiki *ExternalWiki `json:"external_wiki,omitempty"` 417 // sets the default branch for this repository. 418 DefaultBranch *string `json:"default_branch,omitempty"` 419 // either `true` to allow pull requests, or `false` to prevent pull request. 420 HasPullRequests *bool `json:"has_pull_requests,omitempty"` 421 // either `true` to enable project unit, or `false` to disable them. 422 HasProjects *bool `json:"has_projects,omitempty"` 423 // either `true` to ignore whitespace for conflicts, or `false` to not ignore whitespace. `has_pull_requests` must be `true`. 424 IgnoreWhitespaceConflicts *bool `json:"ignore_whitespace_conflicts,omitempty"` 425 // either `true` to allow merging pull requests with a merge commit, or `false` to prevent merging pull requests with merge commits. `has_pull_requests` must be `true`. 426 AllowMerge *bool `json:"allow_merge_commits,omitempty"` 427 // either `true` to allow rebase-merging pull requests, or `false` to prevent rebase-merging. `has_pull_requests` must be `true`. 428 AllowRebase *bool `json:"allow_rebase,omitempty"` 429 // either `true` to allow rebase with explicit merge commits (--no-ff), or `false` to prevent rebase with explicit merge commits. `has_pull_requests` must be `true`. 430 AllowRebaseMerge *bool `json:"allow_rebase_explicit,omitempty"` 431 // either `true` to allow squash-merging pull requests, or `false` to prevent squash-merging. `has_pull_requests` must be `true`. 432 AllowSquash *bool `json:"allow_squash_merge,omitempty"` 433 // set to `true` to archive this repository. 434 Archived *bool `json:"archived,omitempty"` 435 // set to a string like `8h30m0s` to set the mirror interval time 436 MirrorInterval *string `json:"mirror_interval,omitempty"` 437 // either `true` to allow mark pr as merged manually, or `false` to prevent it. `has_pull_requests` must be `true`. 438 AllowManualMerge *bool `json:"allow_manual_merge,omitempty"` 439 // either `true` to enable AutodetectManualMerge, or `false` to prevent it. `has_pull_requests` must be `true`, Note: In some special cases, misjudgments can occur. 440 AutodetectManualMerge *bool `json:"autodetect_manual_merge,omitempty"` 441 // set to a merge style to be used by this repository: "merge", "rebase", "rebase-merge", or "squash". `has_pull_requests` must be `true`. 442 DefaultMergeStyle *MergeStyle `json:"default_merge_style,omitempty"` 443 // set to `true` to archive this repository. 444} 445 446// EditRepo edit the properties of a repository 447func (c *Client) EditRepo(owner, reponame string, opt EditRepoOption) (*Repository, *Response, error) { 448 if err := escapeValidatePathSegments(&owner, &reponame); err != nil { 449 return nil, nil, err 450 } 451 body, err := json.Marshal(&opt) 452 if err != nil { 453 return nil, nil, err 454 } 455 repo := new(Repository) 456 resp, err := c.getParsedResponse("PATCH", fmt.Sprintf("/repos/%s/%s", owner, reponame), jsonHeader, bytes.NewReader(body), repo) 457 return repo, resp, err 458} 459 460// DeleteRepo deletes a repository of user or organization. 461func (c *Client) DeleteRepo(owner, repo string) (*Response, error) { 462 if err := escapeValidatePathSegments(&owner, &repo); err != nil { 463 return nil, err 464 } 465 _, resp, err := c.getResponse("DELETE", fmt.Sprintf("/repos/%s/%s", owner, repo), nil, nil) 466 return resp, err 467} 468 469// MirrorSync adds a mirrored repository to the mirror sync queue. 470func (c *Client) MirrorSync(owner, repo string) (*Response, error) { 471 if err := escapeValidatePathSegments(&owner, &repo); err != nil { 472 return nil, err 473 } 474 _, resp, err := c.getResponse("POST", fmt.Sprintf("/repos/%s/%s/mirror-sync", owner, repo), nil, nil) 475 return resp, err 476} 477 478// GetRepoLanguages return language stats of a repo 479func (c *Client) GetRepoLanguages(owner, repo string) (map[string]int64, *Response, error) { 480 if err := escapeValidatePathSegments(&owner, &repo); err != nil { 481 return nil, nil, err 482 } 483 langMap := make(map[string]int64) 484 485 data, resp, err := c.getResponse("GET", fmt.Sprintf("/repos/%s/%s/languages", owner, repo), jsonHeader, nil) 486 if err != nil { 487 return nil, resp, err 488 } 489 if err = json.Unmarshal(data, &langMap); err != nil { 490 return nil, resp, err 491 } 492 return langMap, resp, nil 493} 494 495// ArchiveType represent supported archive formats by gitea 496type ArchiveType string 497 498const ( 499 // ZipArchive represent zip format 500 ZipArchive ArchiveType = ".zip" 501 // TarGZArchive represent tar.gz format 502 TarGZArchive ArchiveType = ".tar.gz" 503) 504 505// GetArchive get an archive of a repository by git reference 506// e.g.: ref -> master, 70b7c74b33, v1.2.1, ... 507func (c *Client) GetArchive(owner, repo, ref string, ext ArchiveType) ([]byte, *Response, error) { 508 if err := escapeValidatePathSegments(&owner, &repo); err != nil { 509 return nil, nil, err 510 } 511 ref = pathEscapeSegments(ref) 512 return c.getResponse("GET", fmt.Sprintf("/repos/%s/%s/archive/%s%s", owner, repo, ref, ext), nil, nil) 513} 514 515// GetArchiveReader gets a `git archive` for a particular tree-ish git reference 516// such as a branch name (`master`), a commit hash (`70b7c74b33`), a tag 517// (`v1.2.1`). The archive is returned as a byte stream in a ReadCloser. It is 518// the responsibility of the client to close the reader. 519func (c *Client) GetArchiveReader(owner, repo, ref string, ext ArchiveType) (io.ReadCloser, *Response, error) { 520 if err := escapeValidatePathSegments(&owner, &repo); err != nil { 521 return nil, nil, err 522 } 523 ref = pathEscapeSegments(ref) 524 resp, err := c.doRequest("GET", fmt.Sprintf("/repos/%s/%s/archive/%s%s", owner, repo, ref, ext), nil, nil) 525 if err != nil { 526 return nil, resp, err 527 } 528 529 if _, err := statusCodeToErr(resp); err != nil { 530 return nil, resp, err 531 } 532 533 return resp.Body, resp, nil 534} 535