refactor: move the transport onto the official Gitea SDK
The transport was hand-rolled net/http against the REST API. The payload shapes were ours, in internal/wire, which meant every field Gitea learned was a field somebody here had to notice; and "does this instance have issue dependencies?" had to be guessed from a status code, because a 404 from a missing route and a 404 from a missing issue look alike. The SDK settles both. The shapes are maintained by the people who maintain the server, and the client negotiates the server's version when it is built, so the dependency endpoint is now gated on `>= 1.20.0` — verified against the release where the route appears, not assumed. Below the gate nothing is requested at all. internal/wire keeps what the SDK has no answer for: addressing. The SDK takes an owner, a name and an int64 and never parses, while `42`, `#42`, `owner/repo#42` and an issue URL are four spellings of one address, all four are what somebody has in hand, and Key is what the ledger is keyed by. The payload structs go. Four things that had to survive the move, and did: - request bodies still land in .kettle/payload/, now via an http.RoundTripper on the client the SDK is given — which is better than before, because it files every request rather than the ones a call site remembered to name; - errors still carry the HTTP status AND the response body, and a decode failure on a 2xx is deliberately not an APIError, so the dependency probe cannot read a bad decode as "feature missing"; - the number -> slug ledger is untouched, entries still outlive the files they name; - Client.For(repo) still re-points at another repository for one call. What it cost, written down in AGENTS.md where it happened. internal/mapping's layering test was a fact about the import graph — nothing in its closure could open a socket — and the SDK ships its types and its client in one package, so the test now asserts what is still true: the bridge performs no I/O, checked on direct imports plus a grep for time.Now. A run makes one extra request before it does anything. Gitea's issue edit endpoint carries no labels, so a push whose labels changed needs a second call; push makes it and says so. go.mod requires go 1.26, which the SDK sets and which is now the floor for building this binary. internal/issue and internal/project are byte-identical. The domain did not notice, which is the whole argument for the layering. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
+629
@@ -0,0 +1,629 @@
|
||||
// Copyright 2014 The Gogs Authors. All rights reserved.
|
||||
// Copyright 2020 The Gitea Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package gitea
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Permission represents a set of permissions
|
||||
type Permission struct {
|
||||
Admin bool `json:"admin"`
|
||||
Push bool `json:"push"`
|
||||
Pull bool `json:"pull"`
|
||||
}
|
||||
|
||||
// InternalTracker represents settings for internal tracker
|
||||
type InternalTracker struct {
|
||||
// Enable time tracking (Built-in issue tracker)
|
||||
EnableTimeTracker bool `json:"enable_time_tracker"`
|
||||
// Let only contributors track time (Built-in issue tracker)
|
||||
AllowOnlyContributorsToTrackTime bool `json:"allow_only_contributors_to_track_time"`
|
||||
// Enable dependencies for issues and pull requests (Built-in issue tracker)
|
||||
EnableIssueDependencies bool `json:"enable_issue_dependencies"`
|
||||
}
|
||||
|
||||
// ExternalTracker represents settings for external tracker
|
||||
type ExternalTracker struct {
|
||||
// URL of external issue tracker.
|
||||
ExternalTrackerURL string `json:"external_tracker_url"`
|
||||
// External Issue Tracker URL Format. Use the placeholders {user}, {repo} and {index} for the username, repository name and issue index.
|
||||
ExternalTrackerFormat string `json:"external_tracker_format"`
|
||||
// External Issue Tracker Number Format, either `numeric` or `alphanumeric`
|
||||
ExternalTrackerStyle string `json:"external_tracker_style"`
|
||||
}
|
||||
|
||||
// ExternalWiki represents setting for external wiki
|
||||
type ExternalWiki struct {
|
||||
// URL of external wiki.
|
||||
ExternalWikiURL string `json:"external_wiki_url"`
|
||||
}
|
||||
|
||||
// ProjectsMode is used specify which kinds of projects to show for a repository
|
||||
type ProjectsMode string
|
||||
|
||||
const (
|
||||
// ProjectsModeRepo only allow repo-level projects
|
||||
ProjectsModeRepo ProjectsMode = "repo"
|
||||
// ProjectsModeOwner only allow owner projects
|
||||
ProjectsModeOwner ProjectsMode = "owner"
|
||||
// ProjectsModeAll only allow all projects
|
||||
ProjectsModeAll ProjectsMode = "all"
|
||||
)
|
||||
|
||||
// RepoTransfer represents a pending repository transfer
|
||||
type RepoTransfer struct {
|
||||
Doer *User `json:"doer"`
|
||||
Recipient *User `json:"recipient"`
|
||||
Teams []*Team `json:"teams"`
|
||||
}
|
||||
|
||||
// Repository represents a repository
|
||||
type Repository struct {
|
||||
ID int64 `json:"id"`
|
||||
Owner *User `json:"owner"`
|
||||
Name string `json:"name"`
|
||||
FullName string `json:"full_name"`
|
||||
Description string `json:"description"`
|
||||
Empty bool `json:"empty"`
|
||||
Private bool `json:"private"`
|
||||
Fork bool `json:"fork"`
|
||||
Template bool `json:"template"`
|
||||
Parent *Repository `json:"parent"`
|
||||
Mirror bool `json:"mirror"`
|
||||
Size int `json:"size"`
|
||||
Language string `json:"language"`
|
||||
LanguagesURL string `json:"languages_url"`
|
||||
HTMLURL string `json:"html_url"`
|
||||
URL string `json:"url"`
|
||||
Link string `json:"link"`
|
||||
SSHURL string `json:"ssh_url"`
|
||||
CloneURL string `json:"clone_url"`
|
||||
OriginalURL string `json:"original_url"`
|
||||
Website string `json:"website"`
|
||||
Stars int `json:"stars_count"`
|
||||
Forks int `json:"forks_count"`
|
||||
Watchers int `json:"watchers_count"`
|
||||
OpenIssues int `json:"open_issues_count"`
|
||||
OpenPulls int `json:"open_pr_counter"`
|
||||
Releases int `json:"release_counter"`
|
||||
DefaultBranch string `json:"default_branch"`
|
||||
Archived bool `json:"archived"`
|
||||
ArchivedAt time.Time `json:"archived_at"`
|
||||
Created time.Time `json:"created_at"`
|
||||
Updated time.Time `json:"updated_at"`
|
||||
Permissions *Permission `json:"permissions,omitempty"`
|
||||
HasIssues bool `json:"has_issues"`
|
||||
HasCode bool `json:"has_code"`
|
||||
InternalTracker *InternalTracker `json:"internal_tracker,omitempty"`
|
||||
ExternalTracker *ExternalTracker `json:"external_tracker,omitempty"`
|
||||
HasWiki bool `json:"has_wiki"`
|
||||
ExternalWiki *ExternalWiki `json:"external_wiki,omitempty"`
|
||||
HasPullRequests bool `json:"has_pull_requests"`
|
||||
HasProjects bool `json:"has_projects"`
|
||||
HasReleases bool `json:"has_releases,omitempty"`
|
||||
HasPackages bool `json:"has_packages,omitempty"`
|
||||
HasActions bool `json:"has_actions,omitempty"`
|
||||
IgnoreWhitespaceConflicts bool `json:"ignore_whitespace_conflicts"`
|
||||
AllowFastForwardOnlyMerge bool `json:"allow_fast_forward_only_merge"`
|
||||
AllowMerge bool `json:"allow_merge_commits"`
|
||||
AllowRebase bool `json:"allow_rebase"`
|
||||
AllowRebaseMerge bool `json:"allow_rebase_explicit"`
|
||||
AllowRebaseUpdate bool `json:"allow_rebase_update"`
|
||||
AllowSquash bool `json:"allow_squash_merge"`
|
||||
DefaultAllowMaintainerEdit bool `json:"default_allow_maintainer_edit"`
|
||||
AvatarURL string `json:"avatar_url"`
|
||||
Internal bool `json:"internal"`
|
||||
MirrorInterval string `json:"mirror_interval"`
|
||||
MirrorUpdated time.Time `json:"mirror_updated,omitempty"`
|
||||
DefaultMergeStyle MergeStyle `json:"default_merge_style"`
|
||||
ProjectsMode *ProjectsMode `json:"projects_mode"`
|
||||
DefaultDeleteBranchAfterMerge bool `json:"default_delete_branch_after_merge"`
|
||||
ObjectFormatName string `json:"object_format_name"`
|
||||
Topics []string `json:"topics"`
|
||||
Licenses []string `json:"licenses"`
|
||||
RepoTransfer *RepoTransfer `json:"repo_transfer,omitempty"`
|
||||
}
|
||||
|
||||
// RepoType represent repo type
|
||||
type RepoType string
|
||||
|
||||
const (
|
||||
// RepoTypeNone dont specify a type
|
||||
RepoTypeNone RepoType = ""
|
||||
// RepoTypeSource is the default repo type
|
||||
RepoTypeSource RepoType = "source"
|
||||
// RepoTypeFork is a repo witch was forked from an other one
|
||||
RepoTypeFork RepoType = "fork"
|
||||
// RepoTypeMirror represents an mirror repo
|
||||
RepoTypeMirror RepoType = "mirror"
|
||||
)
|
||||
|
||||
// TrustModel represent how git signatures are handled in a repository
|
||||
type TrustModel string
|
||||
|
||||
const (
|
||||
// TrustModelDefault use TM set by global config
|
||||
TrustModelDefault TrustModel = "default"
|
||||
// TrustModelCollaborator gpg signature has to be owned by a repo collaborator
|
||||
TrustModelCollaborator TrustModel = "collaborator"
|
||||
// TrustModelCommitter gpg signature has to match committer
|
||||
TrustModelCommitter TrustModel = "committer"
|
||||
// TrustModelCollaboratorCommitter gpg signature has to match committer and owned by a repo collaborator
|
||||
TrustModelCollaboratorCommitter TrustModel = "collaboratorcommitter"
|
||||
)
|
||||
|
||||
// ListReposOptions options for listing repositories
|
||||
type ListReposOptions struct {
|
||||
ListOptions
|
||||
}
|
||||
|
||||
// ListMyRepos lists all repositories for the authenticated user that has access to.
|
||||
func (c *Client) ListMyRepos(opt ListReposOptions) ([]*Repository, *Response, error) {
|
||||
opt.setDefaults()
|
||||
repos := make([]*Repository, 0, opt.PageSize)
|
||||
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/user/repos?%s", opt.getURLQuery().Encode()), nil, nil, &repos)
|
||||
return repos, resp, err
|
||||
}
|
||||
|
||||
// ListUserRepos list all repositories of one user by user's name
|
||||
func (c *Client) ListUserRepos(user string, opt ListReposOptions) ([]*Repository, *Response, error) {
|
||||
if err := escapeValidatePathSegments(&user); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
opt.setDefaults()
|
||||
repos := make([]*Repository, 0, opt.PageSize)
|
||||
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/users/%s/repos?%s", user, opt.getURLQuery().Encode()), nil, nil, &repos)
|
||||
return repos, resp, err
|
||||
}
|
||||
|
||||
// ListOrgReposOptions options for a organization's repositories
|
||||
type ListOrgReposOptions struct {
|
||||
ListOptions
|
||||
}
|
||||
|
||||
// ListOrgRepos list all repositories of one organization by organization's name
|
||||
func (c *Client) ListOrgRepos(org string, opt ListOrgReposOptions) ([]*Repository, *Response, error) {
|
||||
if err := escapeValidatePathSegments(&org); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
opt.setDefaults()
|
||||
repos := make([]*Repository, 0, opt.PageSize)
|
||||
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/orgs/%s/repos?%s", org, opt.getURLQuery().Encode()), nil, nil, &repos)
|
||||
return repos, resp, err
|
||||
}
|
||||
|
||||
// SearchRepoOptions options for searching repositories
|
||||
type SearchRepoOptions struct {
|
||||
ListOptions
|
||||
|
||||
// The keyword to query
|
||||
Keyword string
|
||||
// Limit search to repositories with keyword as topic
|
||||
KeywordIsTopic bool
|
||||
// Include search of keyword within repository description
|
||||
KeywordInDescription bool
|
||||
|
||||
/*
|
||||
User Filter
|
||||
*/
|
||||
|
||||
// Repo Owner
|
||||
OwnerID int64
|
||||
// Stared By UserID
|
||||
StarredByUserID int64
|
||||
|
||||
/*
|
||||
Repo Attributes
|
||||
*/
|
||||
|
||||
// pubic, private or all repositories (defaults to all)
|
||||
IsPrivate *bool
|
||||
// archived, non-archived or all repositories (defaults to all)
|
||||
IsArchived *bool
|
||||
// Exclude template repos from search
|
||||
ExcludeTemplate bool
|
||||
// Filter by "fork", "source", "mirror"
|
||||
Type RepoType
|
||||
|
||||
/*
|
||||
Sort Filters
|
||||
*/
|
||||
|
||||
// sort repos by attribute. Supported values are "alpha", "created", "updated", "size", and "id". Default is "alpha"
|
||||
Sort string
|
||||
// sort order, either "asc" (ascending) or "desc" (descending). Default is "asc", ignored if "sort" is not specified.
|
||||
Order string
|
||||
// Repo owner to prioritize in the results
|
||||
PrioritizedByOwnerID int64
|
||||
|
||||
/*
|
||||
Cover EdgeCases
|
||||
*/
|
||||
// if set all other options are ignored and this string is used as query
|
||||
RawQuery string
|
||||
}
|
||||
|
||||
// QueryEncode turns options into querystring argument
|
||||
func (opt *SearchRepoOptions) QueryEncode() string {
|
||||
query := opt.getURLQuery()
|
||||
if opt.Keyword != "" {
|
||||
query.Add("q", opt.Keyword)
|
||||
}
|
||||
if opt.KeywordIsTopic {
|
||||
query.Add("topic", "true")
|
||||
}
|
||||
if opt.KeywordInDescription {
|
||||
query.Add("includeDesc", "true")
|
||||
}
|
||||
|
||||
// User Filter
|
||||
if opt.OwnerID > 0 {
|
||||
query.Add("uid", fmt.Sprintf("%d", opt.OwnerID))
|
||||
query.Add("exclusive", "true")
|
||||
}
|
||||
if opt.StarredByUserID > 0 {
|
||||
query.Add("starredBy", fmt.Sprintf("%d", opt.StarredByUserID))
|
||||
}
|
||||
|
||||
// Repo Attributes
|
||||
if opt.IsPrivate != nil {
|
||||
query.Add("is_private", fmt.Sprintf("%t", *opt.IsPrivate))
|
||||
}
|
||||
if opt.IsArchived != nil {
|
||||
query.Add("archived", fmt.Sprintf("%t", *opt.IsArchived))
|
||||
}
|
||||
if opt.ExcludeTemplate {
|
||||
query.Add("template", "false")
|
||||
}
|
||||
if len(opt.Type) != 0 {
|
||||
query.Add("mode", string(opt.Type))
|
||||
}
|
||||
|
||||
// Sort Filters
|
||||
if opt.Sort != "" {
|
||||
query.Add("sort", opt.Sort)
|
||||
}
|
||||
if opt.PrioritizedByOwnerID > 0 {
|
||||
query.Add("priority_owner_id", fmt.Sprintf("%d", opt.PrioritizedByOwnerID))
|
||||
}
|
||||
if opt.Order != "" {
|
||||
query.Add("order", opt.Order)
|
||||
}
|
||||
|
||||
return query.Encode()
|
||||
}
|
||||
|
||||
type searchRepoResponse struct {
|
||||
Repos []*Repository `json:"data"`
|
||||
}
|
||||
|
||||
// SearchRepos searches for repositories matching the given filters
|
||||
func (c *Client) SearchRepos(opt SearchRepoOptions) ([]*Repository, *Response, error) {
|
||||
opt.setDefaults()
|
||||
repos := new(searchRepoResponse)
|
||||
|
||||
link, _ := url.Parse("/repos/search")
|
||||
|
||||
if len(opt.RawQuery) != 0 {
|
||||
link.RawQuery = opt.RawQuery
|
||||
} else {
|
||||
link.RawQuery = opt.QueryEncode()
|
||||
// IsPrivate only works on gitea >= 1.12.0
|
||||
if err := c.checkServerVersionGreaterThanOrEqual(version1_12_0); err != nil && opt.IsPrivate != nil {
|
||||
if *opt.IsPrivate {
|
||||
// private repos only not supported on gitea <= 1.11.x
|
||||
return nil, nil, err
|
||||
}
|
||||
newQuery := link.Query()
|
||||
newQuery.Add("private", "false")
|
||||
link.RawQuery = newQuery.Encode()
|
||||
}
|
||||
}
|
||||
|
||||
resp, err := c.getParsedResponse("GET", link.String(), nil, nil, &repos)
|
||||
return repos.Repos, resp, err
|
||||
}
|
||||
|
||||
// CreateRepoOption options when creating repository
|
||||
type CreateRepoOption struct {
|
||||
// Name of the repository to create
|
||||
Name string `json:"name"`
|
||||
// Description of the repository to create
|
||||
Description string `json:"description"`
|
||||
// Whether the repository is private
|
||||
Private bool `json:"private"`
|
||||
// Issue Label set to use
|
||||
IssueLabels string `json:"issue_labels"`
|
||||
// Whether the repository should be auto-intialized?
|
||||
AutoInit bool `json:"auto_init"`
|
||||
// Whether the repository is template
|
||||
Template bool `json:"template"`
|
||||
// Gitignores to use
|
||||
Gitignores string `json:"gitignores"`
|
||||
// License to use
|
||||
License string `json:"license"`
|
||||
// Readme of the repository to create
|
||||
Readme string `json:"readme"`
|
||||
// DefaultBranch of the repository (used when initializes and in template)
|
||||
DefaultBranch string `json:"default_branch"`
|
||||
// TrustModel of the repository
|
||||
TrustModel TrustModel `json:"trust_model"`
|
||||
// ObjectFormatName of the repository, could be sha1 or sha256, depends on Gitea version
|
||||
ObjectFormatName string `json:"object_format_name"`
|
||||
}
|
||||
|
||||
// Validate the CreateRepoOption struct
|
||||
func (opt CreateRepoOption) Validate(c *Client) error {
|
||||
if len(strings.TrimSpace(opt.Name)) == 0 {
|
||||
return fmt.Errorf("name is empty")
|
||||
}
|
||||
if len(opt.Name) > 100 {
|
||||
return fmt.Errorf("name has more than 100 chars")
|
||||
}
|
||||
if len(opt.Description) > 2048 {
|
||||
return fmt.Errorf("description has more than 2048 chars")
|
||||
}
|
||||
if len(opt.DefaultBranch) > 100 {
|
||||
return fmt.Errorf("default branch name has more than 100 chars")
|
||||
}
|
||||
if len(opt.TrustModel) != 0 {
|
||||
if err := c.checkServerVersionGreaterThanOrEqual(version1_13_0); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if len(opt.ObjectFormatName) != 0 {
|
||||
if opt.ObjectFormatName != "sha1" && opt.ObjectFormatName != "sha256" {
|
||||
return fmt.Errorf("object format must be sha1 or sha256")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// CreateRepo creates a repository for authenticated user.
|
||||
func (c *Client) CreateRepo(opt CreateRepoOption) (*Repository, *Response, error) {
|
||||
if err := opt.Validate(c); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
// object_format_name is only supported on gitea >= 1.22.0
|
||||
if c.checkServerVersionGreaterThanOrEqual(version1_22_0) != nil {
|
||||
opt.ObjectFormatName = ""
|
||||
}
|
||||
body, err := json.Marshal(&opt)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
repo := new(Repository)
|
||||
resp, err := c.getParsedResponse("POST", "/user/repos", jsonHeader, bytes.NewReader(body), repo)
|
||||
return repo, resp, err
|
||||
}
|
||||
|
||||
// CreateOrgRepo creates an organization repository for authenticated user.
|
||||
func (c *Client) CreateOrgRepo(org string, opt CreateRepoOption) (*Repository, *Response, error) {
|
||||
if err := escapeValidatePathSegments(&org); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
if err := opt.Validate(c); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
body, err := json.Marshal(&opt)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
repo := new(Repository)
|
||||
resp, err := c.getParsedResponse("POST", fmt.Sprintf("/orgs/%s/repos", org), jsonHeader, bytes.NewReader(body), repo)
|
||||
return repo, resp, err
|
||||
}
|
||||
|
||||
// GetRepo returns information of a repository of given owner.
|
||||
func (c *Client) GetRepo(owner, reponame string) (*Repository, *Response, error) {
|
||||
if err := escapeValidatePathSegments(&owner, &reponame); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
repo := new(Repository)
|
||||
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s", owner, reponame), nil, nil, repo)
|
||||
return repo, resp, err
|
||||
}
|
||||
|
||||
// GetRepoByID returns information of a repository by a giver repository ID.
|
||||
func (c *Client) GetRepoByID(id int64) (*Repository, *Response, error) {
|
||||
repo := new(Repository)
|
||||
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repositories/%d", id), nil, nil, repo)
|
||||
return repo, resp, err
|
||||
}
|
||||
|
||||
// EditRepoOption options when editing a repository's properties
|
||||
type EditRepoOption struct {
|
||||
// name of the repository
|
||||
Name *string `json:"name,omitempty"`
|
||||
// a short description of the repository.
|
||||
Description *string `json:"description,omitempty"`
|
||||
// a URL with more information about the repository.
|
||||
Website *string `json:"website,omitempty"`
|
||||
// either `true` to make the repository private or `false` to make it public.
|
||||
// Note: you will get a 422 error if the organization restricts changing repository visibility to organization
|
||||
// owners and a non-owner tries to change the value of private.
|
||||
Private *bool `json:"private,omitempty"`
|
||||
// either `true` to make this repository a template or `false` to make it a normal repository
|
||||
Template *bool `json:"template,omitempty"`
|
||||
// either `true` to enable issues for this repository or `false` to disable them.
|
||||
HasIssues *bool `json:"has_issues,omitempty"`
|
||||
// set this structure to configure internal issue tracker (requires has_issues)
|
||||
InternalTracker *InternalTracker `json:"internal_tracker,omitempty"`
|
||||
// set this structure to use external issue tracker (requires has_issues)
|
||||
ExternalTracker *ExternalTracker `json:"external_tracker,omitempty"`
|
||||
// either `true` to enable the wiki for this repository or `false` to disable it.
|
||||
HasWiki *bool `json:"has_wiki,omitempty"`
|
||||
// set this structure to use external wiki instead of internal (requires has_wiki)
|
||||
ExternalWiki *ExternalWiki `json:"external_wiki,omitempty"`
|
||||
// sets the default branch for this repository.
|
||||
DefaultBranch *string `json:"default_branch,omitempty"`
|
||||
// either `true` to allow pull requests, or `false` to prevent pull request.
|
||||
HasPullRequests *bool `json:"has_pull_requests,omitempty"`
|
||||
// either `true` to enable project unit, or `false` to disable them.
|
||||
HasProjects *bool `json:"has_projects,omitempty"`
|
||||
// either `true` to enable release, or `false` to disable them.
|
||||
HasReleases *bool `json:"has_releases,omitempty"`
|
||||
// either `true` to enable packages, or `false` to disable them.
|
||||
HasPackages *bool `json:"has_packages,omitempty"`
|
||||
// either `true` to enable actions, or `false` to disable them.
|
||||
HasActions *bool `json:"has_actions,omitempty"`
|
||||
// either `true` to ignore whitespace for conflicts, or `false` to not ignore whitespace. `has_pull_requests` must be `true`.
|
||||
IgnoreWhitespaceConflicts *bool `json:"ignore_whitespace_conflicts,omitempty"`
|
||||
// either `true` to allow merging pull requests with fast-forward only strategy, or `false` to prevent merging pull requests with a fast-forward only strategy. `has_pull_requests` must be `true`.
|
||||
AllowFastForwardOnlyMerge *bool `json:"allow_fast_forward_only_merge"`
|
||||
// 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`.
|
||||
AllowMerge *bool `json:"allow_merge_commits,omitempty"`
|
||||
// either `true` to allow rebase-merging pull requests, or `false` to prevent rebase-merging. `has_pull_requests` must be `true`.
|
||||
AllowRebase *bool `json:"allow_rebase,omitempty"`
|
||||
// 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`.
|
||||
AllowRebaseMerge *bool `json:"allow_rebase_explicit,omitempty"`
|
||||
// either `true` to allow squash-merging pull requests, or `false` to prevent squash-merging. `has_pull_requests` must be `true`.
|
||||
AllowSquash *bool `json:"allow_squash_merge,omitempty"`
|
||||
// set to `true` to archive this repository.
|
||||
Archived *bool `json:"archived,omitempty"`
|
||||
// set to a string like `8h30m0s` to set the mirror interval time
|
||||
MirrorInterval *string `json:"mirror_interval,omitempty"`
|
||||
// either `true` to allow mark pr as merged manually, or `false` to prevent it. `has_pull_requests` must be `true`.
|
||||
AllowManualMerge *bool `json:"allow_manual_merge,omitempty"`
|
||||
// either `true` to enable AutodetectManualMerge, or `false` to prevent it. `has_pull_requests` must be `true`, Note: In some special cases, misjudgments can occur.
|
||||
AutodetectManualMerge *bool `json:"autodetect_manual_merge,omitempty"`
|
||||
// set to a merge style to be used by this repository: "merge", "rebase", "rebase-merge", or "squash". `has_pull_requests` must be `true`.
|
||||
DefaultMergeStyle *MergeStyle `json:"default_merge_style,omitempty"`
|
||||
// set to a projects mode to be used by this repository, to specify which kinds of projects to show: "repo" to only allow repo-level projects, "owner" to only allow owner projects, "all" to allow all projects. `has_projects` must be `true`.
|
||||
ProjectsMode *ProjectsMode `json:"projects_mode"`
|
||||
// set to `true` to delete the pull request branch after merge by default. `has_pull_requests` must be `true`.
|
||||
DefaultDeleteBranchAfterMerge *bool `json:"default_delete_branch_after_merge"`
|
||||
}
|
||||
|
||||
// EditRepo edit the properties of a repository
|
||||
func (c *Client) EditRepo(owner, reponame string, opt EditRepoOption) (*Repository, *Response, error) {
|
||||
if err := escapeValidatePathSegments(&owner, &reponame); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
body, err := json.Marshal(&opt)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
repo := new(Repository)
|
||||
resp, err := c.getParsedResponse("PATCH", fmt.Sprintf("/repos/%s/%s", owner, reponame), jsonHeader, bytes.NewReader(body), repo)
|
||||
return repo, resp, err
|
||||
}
|
||||
|
||||
// DeleteRepo deletes a repository of user or organization.
|
||||
func (c *Client) DeleteRepo(owner, repo string) (*Response, error) {
|
||||
if err := escapeValidatePathSegments(&owner, &repo); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return c.doRequestWithStatusHandle("DELETE", fmt.Sprintf("/repos/%s/%s", owner, repo), nil, nil)
|
||||
}
|
||||
|
||||
// MirrorSync adds a mirrored repository to the mirror sync queue.
|
||||
func (c *Client) MirrorSync(owner, repo string) (*Response, error) {
|
||||
if err := escapeValidatePathSegments(&owner, &repo); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return c.doRequestWithStatusHandle("POST", fmt.Sprintf("/repos/%s/%s/mirror-sync", owner, repo), nil, nil)
|
||||
}
|
||||
|
||||
// GetRepoLanguages return language stats of a repo
|
||||
func (c *Client) GetRepoLanguages(owner, repo string) (map[string]int64, *Response, error) {
|
||||
if err := escapeValidatePathSegments(&owner, &repo); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
langMap := make(map[string]int64)
|
||||
|
||||
data, resp, err := c.getResponse("GET", fmt.Sprintf("/repos/%s/%s/languages", owner, repo), jsonHeader, nil)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
if err = json.Unmarshal(data, &langMap); err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
return langMap, resp, nil
|
||||
}
|
||||
|
||||
// ArchiveType represent supported archive formats by gitea
|
||||
type ArchiveType string
|
||||
|
||||
const (
|
||||
// ZipArchive represent zip format
|
||||
ZipArchive ArchiveType = ".zip"
|
||||
// TarGZArchive represent tar.gz format
|
||||
TarGZArchive ArchiveType = ".tar.gz"
|
||||
)
|
||||
|
||||
// GetArchive get an archive of a repository by git reference
|
||||
// e.g.: ref -> master, 70b7c74b33, v1.2.1, ...
|
||||
func (c *Client) GetArchive(owner, repo, ref string, ext ArchiveType) ([]byte, *Response, error) {
|
||||
if err := escapeValidatePathSegments(&owner, &repo); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
ref = pathEscapeSegments(ref)
|
||||
return c.getResponse("GET", fmt.Sprintf("/repos/%s/%s/archive/%s%s", owner, repo, ref, ext), nil, nil)
|
||||
}
|
||||
|
||||
// GetArchiveReader gets a `git archive` for a particular tree-ish git reference
|
||||
// such as a branch name (`master`), a commit hash (`70b7c74b33`), a tag
|
||||
// (`v1.2.1`). The archive is returned as a byte stream in a ReadCloser. It is
|
||||
// the responsibility of the client to close the reader.
|
||||
func (c *Client) GetArchiveReader(owner, repo, ref string, ext ArchiveType) (io.ReadCloser, *Response, error) {
|
||||
if err := escapeValidatePathSegments(&owner, &repo); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
ref = pathEscapeSegments(ref)
|
||||
return c.getResponseReader("GET", fmt.Sprintf("/repos/%s/%s/archive/%s%s", owner, repo, ref, ext), nil, nil)
|
||||
}
|
||||
|
||||
// UpdateRepoAvatarOption options for updating repository avatar
|
||||
type UpdateRepoAvatarOption struct {
|
||||
Image string `json:"image"` // base64 encoded image
|
||||
}
|
||||
|
||||
// UpdateRepoAvatar updates a repository's avatar
|
||||
func (c *Client) UpdateRepoAvatar(owner, repo string, opt UpdateRepoAvatarOption) (*Response, error) {
|
||||
if err := escapeValidatePathSegments(&owner, &repo); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
body, err := json.Marshal(&opt)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
status, resp, err := c.getStatusCode("POST",
|
||||
fmt.Sprintf("/repos/%s/%s/avatar", owner, repo),
|
||||
jsonHeader, bytes.NewReader(body))
|
||||
if err != nil {
|
||||
return resp, err
|
||||
}
|
||||
if status != http.StatusNoContent {
|
||||
return resp, fmt.Errorf("unexpected status: %d", status)
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// DeleteRepoAvatar deletes a repository's avatar
|
||||
func (c *Client) DeleteRepoAvatar(owner, repo string) (*Response, error) {
|
||||
if err := escapeValidatePathSegments(&owner, &repo); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
status, resp, err := c.getStatusCode("DELETE",
|
||||
fmt.Sprintf("/repos/%s/%s/avatar", owner, repo),
|
||||
jsonHeader, nil)
|
||||
if err != nil {
|
||||
return resp, err
|
||||
}
|
||||
if status != http.StatusNoContent {
|
||||
return resp, fmt.Errorf("unexpected status: %d", status)
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
Reference in New Issue
Block a user