Gitea 2 Gitea migration (#12657)
* first draft
* update gitea sdk to 9e280adb4da
* adapt feat of updated sdk
* releases now works
* break the Reactions loop
* use convertGiteaLabel
* fix endless loop because paggination is not supported there !!!
* rename gitea local uploader files
* pagination can bite you in the ass
* Version Checks
* lint
* docs
* rename gitea sdk import to miss future conficts
* go-swagger: dont scan the sdk structs
* make sure gitea can shutdown gracefully
* make GetPullRequests and GetIssues similar
* rm useles
* Add Test: started ...
* ... add tests ...
* Add tests and Fixing things
* Workaround missing SHA
* Adapt: Ensure that all migration requests are cancellable
(714ab71ddc
)
* LINT: fix misspells in test set
* adapt ListMergeRequestAwardEmoji
* update sdk
* Return error when creating giteadownloader failed
* update sdk
* adapt new sdk
* adopt new features
* check version before err
* adapt: 'migrate service type switch page'
* optimize
* Fix DefaultBranch
* impruve
* handle subPath
* fix test
* Fix ReviewCommentPosition
* test GetReviews
* add DefaultBranch int test set
* rm unused
* Update SDK to v0.13.0
* addopt sdk changes
* found better link
* format template
* Update Docs
* Update Gitea SDK (v0.13.1)
tokarchuk/v1.17
parent
dfa7291f8f
commit
49b1948cb1
@ -0,0 +1,671 @@ |
||||
// 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 migrations |
||||
|
||||
import ( |
||||
"context" |
||||
"errors" |
||||
"fmt" |
||||
"io" |
||||
"net/http" |
||||
"net/url" |
||||
"strings" |
||||
"time" |
||||
|
||||
"code.gitea.io/gitea/modules/log" |
||||
"code.gitea.io/gitea/modules/migrations/base" |
||||
"code.gitea.io/gitea/modules/structs" |
||||
|
||||
gitea_sdk "code.gitea.io/sdk/gitea" |
||||
) |
||||
|
||||
var ( |
||||
_ base.Downloader = &GiteaDownloader{} |
||||
_ base.DownloaderFactory = &GiteaDownloaderFactory{} |
||||
) |
||||
|
||||
func init() { |
||||
RegisterDownloaderFactory(&GiteaDownloaderFactory{}) |
||||
} |
||||
|
||||
// GiteaDownloaderFactory defines a gitea downloader factory
|
||||
type GiteaDownloaderFactory struct { |
||||
} |
||||
|
||||
// New returns a Downloader related to this factory according MigrateOptions
|
||||
func (f *GiteaDownloaderFactory) New(ctx context.Context, opts base.MigrateOptions) (base.Downloader, error) { |
||||
u, err := url.Parse(opts.CloneAddr) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
|
||||
baseURL := u.Scheme + "://" + u.Host |
||||
repoNameSpace := strings.TrimPrefix(u.Path, "/") |
||||
repoNameSpace = strings.TrimSuffix(repoNameSpace, ".git") |
||||
|
||||
path := strings.Split(repoNameSpace, "/") |
||||
if len(path) < 2 { |
||||
return nil, fmt.Errorf("invalid path") |
||||
} |
||||
|
||||
repoPath := strings.Join(path[len(path)-2:], "/") |
||||
if len(path) > 2 { |
||||
subPath := strings.Join(path[:len(path)-2], "/") |
||||
baseURL += "/" + subPath |
||||
} |
||||
|
||||
log.Trace("Create gitea downloader. BaseURL: %s RepoName: %s", baseURL, repoNameSpace) |
||||
|
||||
return NewGiteaDownloader(ctx, baseURL, repoPath, opts.AuthUsername, opts.AuthPassword, opts.AuthToken) |
||||
} |
||||
|
||||
// GitServiceType returns the type of git service
|
||||
func (f *GiteaDownloaderFactory) GitServiceType() structs.GitServiceType { |
||||
return structs.GiteaService |
||||
} |
||||
|
||||
// GiteaDownloader implements a Downloader interface to get repository information's
|
||||
type GiteaDownloader struct { |
||||
ctx context.Context |
||||
client *gitea_sdk.Client |
||||
repoOwner string |
||||
repoName string |
||||
pagination bool |
||||
maxPerPage int |
||||
} |
||||
|
||||
// NewGiteaDownloader creates a gitea Downloader via gitea API
|
||||
// Use either a username/password or personal token. token is preferred
|
||||
// Note: Public access only allows very basic access
|
||||
func NewGiteaDownloader(ctx context.Context, baseURL, repoPath, username, password, token string) (*GiteaDownloader, error) { |
||||
giteaClient, err := gitea_sdk.NewClient( |
||||
baseURL, |
||||
gitea_sdk.SetToken(token), |
||||
gitea_sdk.SetBasicAuth(username, password), |
||||
gitea_sdk.SetContext(ctx), |
||||
) |
||||
if err != nil { |
||||
log.Error(fmt.Sprintf("NewGiteaDownloader: %s", err.Error())) |
||||
return nil, err |
||||
} |
||||
|
||||
path := strings.Split(repoPath, "/") |
||||
|
||||
paginationSupport := true |
||||
if err := giteaClient.CheckServerVersionConstraint(">=1.12"); err != nil { |
||||
paginationSupport = false |
||||
} |
||||
|
||||
// set small maxPerPage since we can only guess
|
||||
// (default would be 50 but this can differ)
|
||||
maxPerPage := 10 |
||||
// new gitea instances can tell us what maximum they have
|
||||
if giteaClient.CheckServerVersionConstraint(">=1.13.0") == nil { |
||||
apiConf, _, err := giteaClient.GetGlobalAPISettings() |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
maxPerPage = apiConf.MaxResponseItems |
||||
} |
||||
|
||||
return &GiteaDownloader{ |
||||
ctx: ctx, |
||||
client: giteaClient, |
||||
repoOwner: path[0], |
||||
repoName: path[1], |
||||
pagination: paginationSupport, |
||||
maxPerPage: maxPerPage, |
||||
}, nil |
||||
} |
||||
|
||||
// SetContext set context
|
||||
func (g *GiteaDownloader) SetContext(ctx context.Context) { |
||||
g.ctx = ctx |
||||
} |
||||
|
||||
// GetRepoInfo returns a repository information
|
||||
func (g *GiteaDownloader) GetRepoInfo() (*base.Repository, error) { |
||||
if g == nil { |
||||
return nil, errors.New("error: GiteaDownloader is nil") |
||||
} |
||||
|
||||
repo, _, err := g.client.GetRepo(g.repoOwner, g.repoName) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
|
||||
return &base.Repository{ |
||||
Name: repo.Name, |
||||
Owner: repo.Owner.UserName, |
||||
IsPrivate: repo.Private, |
||||
Description: repo.Description, |
||||
CloneURL: repo.CloneURL, |
||||
OriginalURL: repo.HTMLURL, |
||||
DefaultBranch: repo.DefaultBranch, |
||||
}, nil |
||||
} |
||||
|
||||
// GetTopics return gitea topics
|
||||
func (g *GiteaDownloader) GetTopics() ([]string, error) { |
||||
topics, _, err := g.client.ListRepoTopics(g.repoOwner, g.repoName, gitea_sdk.ListRepoTopicsOptions{}) |
||||
return topics, err |
||||
} |
||||
|
||||
// GetMilestones returns milestones
|
||||
func (g *GiteaDownloader) GetMilestones() ([]*base.Milestone, error) { |
||||
var milestones = make([]*base.Milestone, 0, g.maxPerPage) |
||||
|
||||
for i := 1; ; i++ { |
||||
// make sure gitea can shutdown gracefully
|
||||
select { |
||||
case <-g.ctx.Done(): |
||||
return nil, nil |
||||
default: |
||||
} |
||||
|
||||
ms, _, err := g.client.ListRepoMilestones(g.repoOwner, g.repoName, gitea_sdk.ListMilestoneOption{ |
||||
ListOptions: gitea_sdk.ListOptions{ |
||||
PageSize: g.maxPerPage, |
||||
Page: i, |
||||
}, |
||||
State: gitea_sdk.StateAll, |
||||
}) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
|
||||
for i := range ms { |
||||
// old gitea instances dont have this information
|
||||
createdAT := time.Now() |
||||
var updatedAT *time.Time |
||||
if ms[i].Closed != nil { |
||||
createdAT = *ms[i].Closed |
||||
updatedAT = ms[i].Closed |
||||
} |
||||
|
||||
// new gitea instances (>=1.13) do
|
||||
if !ms[i].Created.IsZero() { |
||||
createdAT = ms[i].Created |
||||
} |
||||
if ms[i].Updated != nil && !ms[i].Updated.IsZero() { |
||||
updatedAT = ms[i].Updated |
||||
} |
||||
|
||||
milestones = append(milestones, &base.Milestone{ |
||||
Title: ms[i].Title, |
||||
Description: ms[i].Description, |
||||
Deadline: ms[i].Deadline, |
||||
Created: createdAT, |
||||
Updated: updatedAT, |
||||
Closed: ms[i].Closed, |
||||
State: string(ms[i].State), |
||||
}) |
||||
} |
||||
if !g.pagination || len(ms) < g.maxPerPage { |
||||
break |
||||
} |
||||
} |
||||
return milestones, nil |
||||
} |
||||
|
||||
func (g *GiteaDownloader) convertGiteaLabel(label *gitea_sdk.Label) *base.Label { |
||||
return &base.Label{ |
||||
Name: label.Name, |
||||
Color: label.Color, |
||||
Description: label.Description, |
||||
} |
||||
} |
||||
|
||||
// GetLabels returns labels
|
||||
func (g *GiteaDownloader) GetLabels() ([]*base.Label, error) { |
||||
var labels = make([]*base.Label, 0, g.maxPerPage) |
||||
|
||||
for i := 1; ; i++ { |
||||
// make sure gitea can shutdown gracefully
|
||||
select { |
||||
case <-g.ctx.Done(): |
||||
return nil, nil |
||||
default: |
||||
} |
||||
|
||||
ls, _, err := g.client.ListRepoLabels(g.repoOwner, g.repoName, gitea_sdk.ListLabelsOptions{ListOptions: gitea_sdk.ListOptions{ |
||||
PageSize: g.maxPerPage, |
||||
Page: i, |
||||
}}) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
|
||||
for i := range ls { |
||||
labels = append(labels, g.convertGiteaLabel(ls[i])) |
||||
} |
||||
if !g.pagination || len(ls) < g.maxPerPage { |
||||
break |
||||
} |
||||
} |
||||
return labels, nil |
||||
} |
||||
|
||||
func (g *GiteaDownloader) convertGiteaRelease(rel *gitea_sdk.Release) *base.Release { |
||||
r := &base.Release{ |
||||
TagName: rel.TagName, |
||||
TargetCommitish: rel.Target, |
||||
Name: rel.Title, |
||||
Body: rel.Note, |
||||
Draft: rel.IsDraft, |
||||
Prerelease: rel.IsPrerelease, |
||||
PublisherID: rel.Publisher.ID, |
||||
PublisherName: rel.Publisher.UserName, |
||||
PublisherEmail: rel.Publisher.Email, |
||||
Published: rel.PublishedAt, |
||||
Created: rel.CreatedAt, |
||||
} |
||||
|
||||
for _, asset := range rel.Attachments { |
||||
size := int(asset.Size) |
||||
dlCount := int(asset.DownloadCount) |
||||
r.Assets = append(r.Assets, base.ReleaseAsset{ |
||||
ID: asset.ID, |
||||
Name: asset.Name, |
||||
Size: &size, |
||||
DownloadCount: &dlCount, |
||||
Created: asset.Created, |
||||
DownloadURL: &asset.DownloadURL, |
||||
}) |
||||
} |
||||
return r |
||||
} |
||||
|
||||
// GetReleases returns releases
|
||||
func (g *GiteaDownloader) GetReleases() ([]*base.Release, error) { |
||||
var releases = make([]*base.Release, 0, g.maxPerPage) |
||||
|
||||
for i := 1; ; i++ { |
||||
// make sure gitea can shutdown gracefully
|
||||
select { |
||||
case <-g.ctx.Done(): |
||||
return nil, nil |
||||
default: |
||||
} |
||||
|
||||
rl, _, err := g.client.ListReleases(g.repoOwner, g.repoName, gitea_sdk.ListReleasesOptions{ListOptions: gitea_sdk.ListOptions{ |
||||
PageSize: g.maxPerPage, |
||||
Page: i, |
||||
}}) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
|
||||
for i := range rl { |
||||
releases = append(releases, g.convertGiteaRelease(rl[i])) |
||||
} |
||||
if !g.pagination || len(rl) < g.maxPerPage { |
||||
break |
||||
} |
||||
} |
||||
return releases, nil |
||||
} |
||||
|
||||
// GetAsset returns an asset
|
||||
func (g *GiteaDownloader) GetAsset(_ string, relID, id int64) (io.ReadCloser, error) { |
||||
asset, _, err := g.client.GetReleaseAttachment(g.repoOwner, g.repoName, relID, id) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
resp, err := http.Get(asset.DownloadURL) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
|
||||
// resp.Body is closed by the uploader
|
||||
return resp.Body, nil |
||||
} |
||||
|
||||
func (g *GiteaDownloader) getIssueReactions(index int64) ([]*base.Reaction, error) { |
||||
var reactions []*base.Reaction |
||||
if err := g.client.CheckServerVersionConstraint(">=1.11"); err != nil { |
||||
log.Info("GiteaDownloader: instance to old, skip getIssueReactions") |
||||
return reactions, nil |
||||
} |
||||
rl, _, err := g.client.GetIssueReactions(g.repoOwner, g.repoName, index) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
|
||||
for _, reaction := range rl { |
||||
reactions = append(reactions, &base.Reaction{ |
||||
UserID: reaction.User.ID, |
||||
UserName: reaction.User.UserName, |
||||
Content: reaction.Reaction, |
||||
}) |
||||
} |
||||
return reactions, nil |
||||
} |
||||
|
||||
func (g *GiteaDownloader) getCommentReactions(commentID int64) ([]*base.Reaction, error) { |
||||
var reactions []*base.Reaction |
||||
if err := g.client.CheckServerVersionConstraint(">=1.11"); err != nil { |
||||
log.Info("GiteaDownloader: instance to old, skip getCommentReactions") |
||||
return reactions, nil |
||||
} |
||||
rl, _, err := g.client.GetIssueCommentReactions(g.repoOwner, g.repoName, commentID) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
|
||||
for i := range rl { |
||||
reactions = append(reactions, &base.Reaction{ |
||||
UserID: rl[i].User.ID, |
||||
UserName: rl[i].User.UserName, |
||||
Content: rl[i].Reaction, |
||||
}) |
||||
} |
||||
return reactions, nil |
||||
} |
||||
|
||||
// GetIssues returns issues according start and limit
|
||||
func (g *GiteaDownloader) GetIssues(page, perPage int) ([]*base.Issue, bool, error) { |
||||
if perPage > g.maxPerPage { |
||||
perPage = g.maxPerPage |
||||
} |
||||
var allIssues = make([]*base.Issue, 0, perPage) |
||||
|
||||
issues, _, err := g.client.ListRepoIssues(g.repoOwner, g.repoName, gitea_sdk.ListIssueOption{ |
||||
ListOptions: gitea_sdk.ListOptions{Page: page, PageSize: perPage}, |
||||
State: gitea_sdk.StateAll, |
||||
Type: gitea_sdk.IssueTypeIssue, |
||||
}) |
||||
if err != nil { |
||||
return nil, false, fmt.Errorf("error while listing issues: %v", err) |
||||
} |
||||
for _, issue := range issues { |
||||
|
||||
var labels = make([]*base.Label, 0, len(issue.Labels)) |
||||
for i := range issue.Labels { |
||||
labels = append(labels, g.convertGiteaLabel(issue.Labels[i])) |
||||
} |
||||
|
||||
var milestone string |
||||
if issue.Milestone != nil { |
||||
milestone = issue.Milestone.Title |
||||
} |
||||
|
||||
reactions, err := g.getIssueReactions(issue.Index) |
||||
if err != nil { |
||||
return nil, false, fmt.Errorf("error while loading reactions: %v", err) |
||||
} |
||||
|
||||
var assignees []string |
||||
for i := range issue.Assignees { |
||||
assignees = append(assignees, issue.Assignees[i].UserName) |
||||
} |
||||
|
||||
allIssues = append(allIssues, &base.Issue{ |
||||
Title: issue.Title, |
||||
Number: issue.Index, |
||||
PosterID: issue.Poster.ID, |
||||
PosterName: issue.Poster.UserName, |
||||
PosterEmail: issue.Poster.Email, |
||||
Content: issue.Body, |
||||
Milestone: milestone, |
||||
State: string(issue.State), |
||||
Created: issue.Created, |
||||
Updated: issue.Updated, |
||||
Closed: issue.Closed, |
||||
Reactions: reactions, |
||||
Labels: labels, |
||||
Assignees: assignees, |
||||
IsLocked: issue.IsLocked, |
||||
}) |
||||
} |
||||
|
||||
isEnd := len(issues) < perPage |
||||
if !g.pagination { |
||||
isEnd = len(issues) == 0 |
||||
} |
||||
return allIssues, isEnd, nil |
||||
} |
||||
|
||||
// GetComments returns comments according issueNumber
|
||||
func (g *GiteaDownloader) GetComments(index int64) ([]*base.Comment, error) { |
||||
var allComments = make([]*base.Comment, 0, g.maxPerPage) |
||||
|
||||
// for i := 1; ; i++ {
|
||||
// make sure gitea can shutdown gracefully
|
||||
select { |
||||
case <-g.ctx.Done(): |
||||
return nil, nil |
||||
default: |
||||
} |
||||
|
||||
comments, _, err := g.client.ListIssueComments(g.repoOwner, g.repoName, index, gitea_sdk.ListIssueCommentOptions{ListOptions: gitea_sdk.ListOptions{ |
||||
// PageSize: g.maxPerPage,
|
||||
// Page: i,
|
||||
}}) |
||||
if err != nil { |
||||
return nil, fmt.Errorf("error while listing comments: %v", err) |
||||
} |
||||
|
||||
for _, comment := range comments { |
||||
reactions, err := g.getCommentReactions(comment.ID) |
||||
if err != nil { |
||||
return nil, fmt.Errorf("error while listing comment creactions: %v", err) |
||||
} |
||||
|
||||
allComments = append(allComments, &base.Comment{ |
||||
IssueIndex: index, |
||||
PosterID: comment.Poster.ID, |
||||
PosterName: comment.Poster.UserName, |
||||
PosterEmail: comment.Poster.Email, |
||||
Content: comment.Body, |
||||
Created: comment.Created, |
||||
Updated: comment.Updated, |
||||
Reactions: reactions, |
||||
}) |
||||
} |
||||
|
||||
// TODO enable pagination vor (gitea >= 1.14) when it got implemented
|
||||
// if !g.pagination || len(comments) < g.maxPerPage {
|
||||
// break
|
||||
// }
|
||||
//}
|
||||
return allComments, nil |
||||
} |
||||
|
||||
// GetPullRequests returns pull requests according page and perPage
|
||||
func (g *GiteaDownloader) GetPullRequests(page, perPage int) ([]*base.PullRequest, bool, error) { |
||||
if perPage > g.maxPerPage { |
||||
perPage = g.maxPerPage |
||||
} |
||||
var allPRs = make([]*base.PullRequest, 0, perPage) |
||||
|
||||
prs, _, err := g.client.ListRepoPullRequests(g.repoOwner, g.repoName, gitea_sdk.ListPullRequestsOptions{ |
||||
ListOptions: gitea_sdk.ListOptions{ |
||||
Page: page, |
||||
PageSize: perPage, |
||||
}, |
||||
State: gitea_sdk.StateAll, |
||||
}) |
||||
if err != nil { |
||||
return nil, false, fmt.Errorf("error while listing repos: %v", err) |
||||
} |
||||
for _, pr := range prs { |
||||
var milestone string |
||||
if pr.Milestone != nil { |
||||
milestone = pr.Milestone.Title |
||||
} |
||||
|
||||
var labels = make([]*base.Label, 0, len(pr.Labels)) |
||||
for i := range pr.Labels { |
||||
labels = append(labels, g.convertGiteaLabel(pr.Labels[i])) |
||||
} |
||||
|
||||
var ( |
||||
headUserName string |
||||
headRepoName string |
||||
headCloneURL string |
||||
headRef string |
||||
headSHA string |
||||
) |
||||
if pr.Head != nil { |
||||
if pr.Head.Repository != nil { |
||||
headUserName = pr.Head.Repository.Owner.UserName |
||||
headRepoName = pr.Head.Repository.Name |
||||
headCloneURL = pr.Head.Repository.CloneURL |
||||
} |
||||
headSHA = pr.Head.Sha |
||||
headRef = pr.Head.Ref |
||||
if headSHA == "" { |
||||
headCommit, _, err := g.client.GetSingleCommit(g.repoOwner, g.repoName, url.PathEscape(pr.Head.Ref)) |
||||
if err != nil { |
||||
return nil, false, fmt.Errorf("error while resolving git ref: %v", err) |
||||
} |
||||
headSHA = headCommit.SHA |
||||
} |
||||
} |
||||
|
||||
var mergeCommitSHA string |
||||
if pr.MergedCommitID != nil { |
||||
mergeCommitSHA = *pr.MergedCommitID |
||||
} |
||||
|
||||
reactions, err := g.getIssueReactions(pr.Index) |
||||
if err != nil { |
||||
return nil, false, fmt.Errorf("error while loading reactions: %v", err) |
||||
} |
||||
|
||||
var assignees []string |
||||
for i := range pr.Assignees { |
||||
assignees = append(assignees, pr.Assignees[i].UserName) |
||||
} |
||||
|
||||
createdAt := time.Now() |
||||
if pr.Created != nil { |
||||
createdAt = *pr.Created |
||||
} |
||||
updatedAt := time.Now() |
||||
if pr.Created != nil { |
||||
updatedAt = *pr.Updated |
||||
} |
||||
|
||||
closedAt := pr.Closed |
||||
if pr.Merged != nil && closedAt == nil { |
||||
closedAt = pr.Merged |
||||
} |
||||
|
||||
allPRs = append(allPRs, &base.PullRequest{ |
||||
Title: pr.Title, |
||||
Number: pr.Index, |
||||
PosterID: pr.Poster.ID, |
||||
PosterName: pr.Poster.UserName, |
||||
PosterEmail: pr.Poster.Email, |
||||
Content: pr.Body, |
||||
State: string(pr.State), |
||||
Created: createdAt, |
||||
Updated: updatedAt, |
||||
Closed: closedAt, |
||||
Labels: labels, |
||||
Milestone: milestone, |
||||
Reactions: reactions, |
||||
Assignees: assignees, |
||||
Merged: pr.HasMerged, |
||||
MergedTime: pr.Merged, |
||||
MergeCommitSHA: mergeCommitSHA, |
||||
IsLocked: pr.IsLocked, |
||||
PatchURL: pr.PatchURL, |
||||
Head: base.PullRequestBranch{ |
||||
Ref: headRef, |
||||
SHA: headSHA, |
||||
RepoName: headRepoName, |
||||
OwnerName: headUserName, |
||||
CloneURL: headCloneURL, |
||||
}, |
||||
Base: base.PullRequestBranch{ |
||||
Ref: pr.Base.Ref, |
||||
SHA: pr.Base.Sha, |
||||
RepoName: g.repoName, |
||||
OwnerName: g.repoOwner, |
||||
}, |
||||
}) |
||||
} |
||||
|
||||
isEnd := len(prs) < perPage |
||||
if !g.pagination { |
||||
isEnd = len(prs) == 0 |
||||
} |
||||
return allPRs, isEnd, nil |
||||
} |
||||
|
||||
// GetReviews returns pull requests review
|
||||
func (g *GiteaDownloader) GetReviews(index int64) ([]*base.Review, error) { |
||||
if err := g.client.CheckServerVersionConstraint(">=1.12"); err != nil { |
||||
log.Info("GiteaDownloader: instance to old, skip GetReviews") |
||||
return nil, nil |
||||
} |
||||
|
||||
var allReviews = make([]*base.Review, 0, g.maxPerPage) |
||||
|
||||
for i := 1; ; i++ { |
||||
// make sure gitea can shutdown gracefully
|
||||
select { |
||||
case <-g.ctx.Done(): |
||||
return nil, nil |
||||
default: |
||||
} |
||||
|
||||
prl, _, err := g.client.ListPullReviews(g.repoOwner, g.repoName, index, gitea_sdk.ListPullReviewsOptions{ListOptions: gitea_sdk.ListOptions{ |
||||
Page: i, |
||||
PageSize: g.maxPerPage, |
||||
}}) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
|
||||
for _, pr := range prl { |
||||
|
||||
rcl, _, err := g.client.ListPullReviewComments(g.repoOwner, g.repoName, index, pr.ID) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
var reviewComments []*base.ReviewComment |
||||
for i := range rcl { |
||||
line := int(rcl[i].LineNum) |
||||
if rcl[i].OldLineNum > 0 { |
||||
line = int(rcl[i].OldLineNum) * -1 |
||||
} |
||||
|
||||
reviewComments = append(reviewComments, &base.ReviewComment{ |
||||
ID: rcl[i].ID, |
||||
Content: rcl[i].Body, |
||||
TreePath: rcl[i].Path, |
||||
DiffHunk: rcl[i].DiffHunk, |
||||
Line: line, |
||||
CommitID: rcl[i].CommitID, |
||||
PosterID: rcl[i].Reviewer.ID, |
||||
CreatedAt: rcl[i].Created, |
||||
UpdatedAt: rcl[i].Updated, |
||||
}) |
||||
} |
||||
|
||||
allReviews = append(allReviews, &base.Review{ |
||||
ID: pr.ID, |
||||
IssueIndex: index, |
||||
ReviewerID: pr.Reviewer.ID, |
||||
ReviewerName: pr.Reviewer.UserName, |
||||
Official: pr.Official, |
||||
CommitID: pr.CommitID, |
||||
Content: pr.Body, |
||||
CreatedAt: pr.Submitted, |
||||
State: string(pr.State), |
||||
Comments: reviewComments, |
||||
}) |
||||
} |
||||
|
||||
if len(prl) < g.maxPerPage { |
||||
break |
||||
} |
||||
} |
||||
return allReviews, nil |
||||
} |
@ -0,0 +1,365 @@ |
||||
// 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 migrations |
||||
|
||||
import ( |
||||
"context" |
||||
"fmt" |
||||
"net/http" |
||||
"os" |
||||
"sort" |
||||
"testing" |
||||
"time" |
||||
|
||||
"code.gitea.io/gitea/modules/migrations/base" |
||||
|
||||
"github.com/stretchr/testify/assert" |
||||
) |
||||
|
||||
func assertEqualIssue(t *testing.T, issueExp, issueGet *base.Issue) { |
||||
assert.EqualValues(t, issueExp.Number, issueGet.Number) |
||||
assert.EqualValues(t, issueExp.Title, issueGet.Title) |
||||
assert.EqualValues(t, issueExp.Content, issueGet.Content) |
||||
assert.EqualValues(t, issueExp.Milestone, issueGet.Milestone) |
||||
assert.EqualValues(t, issueExp.PosterID, issueGet.PosterID) |
||||
assert.EqualValues(t, issueExp.PosterName, issueGet.PosterName) |
||||
assert.EqualValues(t, issueExp.PosterEmail, issueGet.PosterEmail) |
||||
assert.EqualValues(t, issueExp.IsLocked, issueGet.IsLocked) |
||||
assert.EqualValues(t, issueExp.Created.Unix(), issueGet.Created.Unix()) |
||||
assert.EqualValues(t, issueExp.Updated.Unix(), issueGet.Updated.Unix()) |
||||
if issueExp.Closed != nil { |
||||
assert.EqualValues(t, issueExp.Closed.Unix(), issueGet.Closed.Unix()) |
||||
} else { |
||||
assert.True(t, issueGet.Closed == nil) |
||||
} |
||||
sort.Strings(issueExp.Assignees) |
||||
sort.Strings(issueGet.Assignees) |
||||
assert.EqualValues(t, issueExp.Assignees, issueGet.Assignees) |
||||
assert.EqualValues(t, issueExp.Labels, issueGet.Labels) |
||||
assert.EqualValues(t, issueExp.Reactions, issueGet.Reactions) |
||||
} |
||||
|
||||
func TestGiteaDownloadRepo(t *testing.T) { |
||||
// Skip tests if Gitea token is not found
|
||||
giteaToken := os.Getenv("GITEA_TOKEN") |
||||
if giteaToken == "" { |
||||
t.Skip("skipped test because GITEA_TOKEN was not in the environment") |
||||
} |
||||
|
||||
resp, err := http.Get("https://gitea.com/gitea") |
||||
if err != nil || resp.StatusCode != 200 { |
||||
t.Skipf("Can't reach https://gitea.com, skipping %s", t.Name()) |
||||
} |
||||
|
||||
downloader, err := NewGiteaDownloader(context.Background(), "https://gitea.com", "gitea/test_repo", "", "", giteaToken) |
||||
if downloader == nil { |
||||
t.Fatal("NewGitlabDownloader is nil") |
||||
} |
||||
if !assert.NoError(t, err) { |
||||
t.Fatal("NewGitlabDownloader error occur") |
||||
} |
||||
|
||||
repo, err := downloader.GetRepoInfo() |
||||
assert.NoError(t, err) |
||||
assert.EqualValues(t, &base.Repository{ |
||||
Name: "test_repo", |
||||
Owner: "gitea", |
||||
IsPrivate: false, |
||||
Description: "Test repository for testing migration from gitea to gitea", |
||||
CloneURL: "https://gitea.com/gitea/test_repo.git", |
||||
OriginalURL: "https://gitea.com/gitea/test_repo", |
||||
DefaultBranch: "master", |
||||
}, repo) |
||||
|
||||
topics, err := downloader.GetTopics() |
||||
assert.NoError(t, err) |
||||
sort.Strings(topics) |
||||
assert.EqualValues(t, []string{"ci", "gitea", "migration", "test"}, topics) |
||||
|
||||
labels, err := downloader.GetLabels() |
||||
assert.NoError(t, err) |
||||
assert.Len(t, labels, 6) |
||||
for _, l := range labels { |
||||
switch l.Name { |
||||
case "Bug": |
||||
assertLabelEqual(t, "Bug", "e11d21", "", l) |
||||
case "documentation": |
||||
assertLabelEqual(t, "Enhancement", "207de5", "", l) |
||||
case "confirmed": |
||||
assertLabelEqual(t, "Feature", "0052cc", "a feature request", l) |
||||
case "enhancement": |
||||
assertLabelEqual(t, "Invalid", "d4c5f9", "", l) |
||||
case "critical": |
||||
assertLabelEqual(t, "Question", "fbca04", "", l) |
||||
case "discussion": |
||||
assertLabelEqual(t, "Valid", "53e917", "", l) |
||||
default: |
||||
assert.Error(t, fmt.Errorf("unexpected label: %s", l.Name)) |
||||
} |
||||
} |
||||
|
||||
milestones, err := downloader.GetMilestones() |
||||
assert.NoError(t, err) |
||||
assert.Len(t, milestones, 2) |
||||
|
||||
for _, milestone := range milestones { |
||||
switch milestone.Title { |
||||
case "V1": |
||||
assert.EqualValues(t, "Generate Content", milestone.Description) |
||||
// assert.EqualValues(t, "ToDo", milestone.Created)
|
||||
// assert.EqualValues(t, "ToDo", milestone.Updated)
|
||||
assert.EqualValues(t, 1598985406, milestone.Closed.Unix()) |
||||
assert.True(t, milestone.Deadline == nil) |
||||
assert.EqualValues(t, "closed", milestone.State) |
||||
case "V2 Finalize": |
||||
assert.EqualValues(t, "", milestone.Description) |
||||
// assert.EqualValues(t, "ToDo", milestone.Created)
|
||||
// assert.EqualValues(t, "ToDo", milestone.Updated)
|
||||
assert.True(t, milestone.Closed == nil) |
||||
assert.EqualValues(t, 1599263999, milestone.Deadline.Unix()) |
||||
assert.EqualValues(t, "open", milestone.State) |
||||
default: |
||||
assert.Error(t, fmt.Errorf("unexpected milestone: %s", milestone.Title)) |
||||
} |
||||
} |
||||
|
||||
releases, err := downloader.GetReleases() |
||||
assert.NoError(t, err) |
||||
assert.EqualValues(t, []*base.Release{ |
||||
{ |
||||
Name: "Second Release", |
||||
TagName: "v2-rc1", |
||||
TargetCommitish: "master", |
||||
Body: "this repo has:\r\n* reactions\r\n* wiki\r\n* issues (open/closed)\r\n* pulls (open/closed/merged) (external/internal)\r\n* pull reviews\r\n* projects\r\n* milestones\r\n* labels\r\n* releases\r\n\r\nto test migration against", |
||||
Draft: false, |
||||
Prerelease: true, |
||||
Created: time.Date(2020, 9, 1, 18, 2, 43, 0, time.UTC), |
||||
Published: time.Date(2020, 9, 1, 18, 2, 43, 0, time.UTC), |
||||
PublisherID: 689, |
||||
PublisherName: "6543", |
||||
PublisherEmail: "6543@noreply.gitea.io", |
||||
}, |
||||
{ |
||||
Name: "First Release", |
||||
TagName: "V1", |
||||
TargetCommitish: "master", |
||||
Body: "as title", |
||||
Draft: false, |
||||
Prerelease: false, |
||||
Created: time.Date(2020, 9, 1, 17, 30, 32, 0, time.UTC), |
||||
Published: time.Date(2020, 9, 1, 17, 30, 32, 0, time.UTC), |
||||
PublisherID: 689, |
||||
PublisherName: "6543", |
||||
PublisherEmail: "6543@noreply.gitea.io", |
||||
}, |
||||
}, releases) |
||||
|
||||
issues, isEnd, err := downloader.GetIssues(1, 50) |
||||
assert.NoError(t, err) |
||||
assert.EqualValues(t, 7, len(issues)) |
||||
assert.True(t, isEnd) |
||||
assert.EqualValues(t, "open", issues[0].State) |
||||
|
||||
issues, isEnd, err = downloader.GetIssues(3, 2) |
||||
assert.NoError(t, err) |
||||
assert.EqualValues(t, 2, len(issues)) |
||||
assert.False(t, isEnd) |
||||
|
||||
var ( |
||||
closed4 = time.Date(2020, 9, 1, 15, 49, 34, 0, time.UTC) |
||||
closed2 = time.Unix(1598969497, 0) |
||||
) |
||||
|
||||
assertEqualIssue(t, &base.Issue{ |
||||
Number: 4, |
||||
Title: "what is this repo about?", |
||||
Content: "", |
||||
Milestone: "V1", |
||||
PosterID: -1, |
||||
PosterName: "Ghost", |
||||
PosterEmail: "", |
||||
State: "closed", |
||||
IsLocked: true, |
||||
Created: time.Unix(1598975321, 0), |
||||
Updated: time.Unix(1598975400, 0), |
||||
Labels: []*base.Label{{ |
||||
Name: "Question", |
||||
Color: "fbca04", |
||||
Description: "", |
||||
}}, |
||||
Reactions: []*base.Reaction{ |
||||
{ |
||||
UserID: 689, |
||||
UserName: "6543", |
||||
Content: "gitea", |
||||
}, |
||||
{ |
||||
UserID: 689, |
||||
UserName: "6543", |
||||
Content: "laugh", |
||||
}, |
||||
}, |
||||
Closed: &closed4, |
||||
}, issues[0]) |
||||
assertEqualIssue(t, &base.Issue{ |
||||
Number: 2, |
||||
Title: "Spam", |
||||
Content: ":(", |
||||
Milestone: "", |
||||
PosterID: 689, |
||||
PosterName: "6543", |
||||
PosterEmail: "6543@noreply.gitea.io", |
||||
State: "closed", |
||||
IsLocked: false, |
||||
Created: time.Unix(1598919780, 0), |
||||
Updated: closed2, |
||||
Labels: []*base.Label{{ |
||||
Name: "Invalid", |
||||
Color: "d4c5f9", |
||||
Description: "", |
||||
}}, |
||||
Reactions: nil, |
||||
Closed: &closed2, |
||||
}, issues[1]) |
||||
|
||||
comments, err := downloader.GetComments(4) |
||||
assert.NoError(t, err) |
||||
assert.Len(t, comments, 2) |
||||
assert.EqualValues(t, 1598975370, comments[0].Created.Unix()) |
||||
assert.EqualValues(t, 1599070865, comments[0].Updated.Unix()) |
||||
assert.EqualValues(t, 1598975393, comments[1].Created.Unix()) |
||||
assert.EqualValues(t, 1598975393, comments[1].Updated.Unix()) |
||||
assert.EqualValues(t, []*base.Comment{ |
||||
{ |
||||
IssueIndex: 4, |
||||
PosterID: 689, |
||||
PosterName: "6543", |
||||
PosterEmail: "6543@noreply.gitea.io", |
||||
Created: comments[0].Created, |
||||
Updated: comments[0].Updated, |
||||
Content: "a really good question!\n\nIt is the used as TESTSET for gitea2gitea repo migration function", |
||||
}, |
||||
{ |
||||
IssueIndex: 4, |
||||
PosterID: -1, |
||||
PosterName: "Ghost", |
||||
PosterEmail: "", |
||||
Created: comments[1].Created, |
||||
Updated: comments[1].Updated, |
||||
Content: "Oh!", |
||||
}, |
||||
}, comments) |
||||
|
||||
prs, isEnd, err := downloader.GetPullRequests(1, 50) |
||||
assert.NoError(t, err) |
||||
assert.True(t, isEnd) |
||||
assert.Len(t, prs, 6) |
||||
prs, isEnd, err = downloader.GetPullRequests(1, 3) |
||||
assert.NoError(t, err) |
||||
assert.False(t, isEnd) |
||||
assert.Len(t, prs, 3) |
||||
merged12 := time.Unix(1598982934, 0) |
||||
assertEqualPulls(t, &base.PullRequest{ |
||||
Number: 12, |
||||
PosterID: 689, |
||||
PosterName: "6543", |
||||
PosterEmail: "6543@noreply.gitea.io", |
||||
Title: "Dont Touch", |
||||
Content: "\r\nadd dont touch note", |
||||
Milestone: "V2 Finalize", |
||||
State: "closed", |
||||
IsLocked: false, |
||||
Created: time.Unix(1598982759, 0), |
||||
Updated: time.Unix(1599023425, 0), |
||||
Closed: &merged12, |
||||
Assignees: []string{"techknowlogick"}, |
||||
Labels: []*base.Label{}, |
||||
|
||||
Base: base.PullRequestBranch{ |
||||
CloneURL: "", |
||||
Ref: "master", |
||||
SHA: "827aa28a907853e5ddfa40c8f9bc52471a2685fd", |
||||
RepoName: "test_repo", |
||||
OwnerName: "gitea", |
||||
}, |
||||
Head: base.PullRequestBranch{ |
||||
CloneURL: "https://gitea.com/6543-forks/test_repo.git", |
||||
Ref: "refs/pull/12/head", |
||||
SHA: "b6ab5d9ae000b579a5fff03f92c486da4ddf48b6", |
||||
RepoName: "test_repo", |
||||
OwnerName: "6543-forks", |
||||
}, |
||||
Merged: true, |
||||
MergedTime: &merged12, |
||||
MergeCommitSHA: "827aa28a907853e5ddfa40c8f9bc52471a2685fd", |
||||
PatchURL: "https://gitea.com/gitea/test_repo/pulls/12.patch", |
||||
}, prs[1]) |
||||
|
||||
reviews, err := downloader.GetReviews(7) |
||||
assert.NoError(t, err) |
||||
if assert.Len(t, reviews, 3) { |
||||
assert.EqualValues(t, 689, reviews[0].ReviewerID) |
||||
assert.EqualValues(t, "6543", reviews[0].ReviewerName) |
||||
assert.EqualValues(t, "techknowlogick", reviews[1].ReviewerName) |
||||
assert.EqualValues(t, "techknowlogick", reviews[2].ReviewerName) |
||||
assert.False(t, reviews[1].Official) |
||||
assert.EqualValues(t, "I think this needs some changes", reviews[1].Content) |
||||
assert.EqualValues(t, "REQUEST_CHANGES", reviews[1].State) |
||||
assert.True(t, reviews[2].Official) |
||||
assert.EqualValues(t, "looks good", reviews[2].Content) |
||||
assert.EqualValues(t, "APPROVED", reviews[2].State) |
||||
|
||||
// TODO: https://github.com/go-gitea/gitea/issues/12846
|
||||
// assert.EqualValues(t, 9, reviews[1].ReviewerID)
|
||||
// assert.EqualValues(t, 9, reviews[2].ReviewerID)
|
||||
|
||||
assert.Len(t, reviews[0].Comments, 1) |
||||
assert.EqualValues(t, &base.ReviewComment{ |
||||
ID: 116561, |
||||
InReplyTo: 0, |
||||
Content: "is one `\\newline` to less?", |
||||
TreePath: "README.md", |
||||
DiffHunk: "@@ -2,3 +2,3 @@\n \n-Test repository for testing migration from gitea 2 gitea\n\\ No newline at end of file\n+Test repository for testing migration from gitea 2 gitea", |
||||
Position: 0, |
||||
Line: 4, |
||||
CommitID: "187ece0cb6631e2858a6872e5733433bb3ca3b03", |
||||
PosterID: 689, |
||||
Reactions: nil, |
||||
CreatedAt: time.Date(2020, 9, 1, 16, 12, 58, 0, time.UTC), |
||||
UpdatedAt: time.Date(2020, 9, 1, 16, 12, 58, 0, time.UTC), |
||||
}, reviews[0].Comments[0]) |
||||
} |
||||
} |
||||
|
||||
func assertEqualPulls(t *testing.T, pullExp, pullGet *base.PullRequest) { |
||||
assertEqualIssue(t, pull2issue(pullExp), pull2issue(pullGet)) |
||||
assert.EqualValues(t, 0, pullGet.OriginalNumber) |
||||
assert.EqualValues(t, pullExp.PatchURL, pullGet.PatchURL) |
||||
assert.EqualValues(t, pullExp.Merged, pullGet.Merged) |
||||
assert.EqualValues(t, pullExp.MergedTime.Unix(), pullGet.MergedTime.Unix()) |
||||
assert.EqualValues(t, pullExp.MergeCommitSHA, pullGet.MergeCommitSHA) |
||||
assert.EqualValues(t, pullExp.Base, pullGet.Base) |
||||
assert.EqualValues(t, pullExp.Head, pullGet.Head) |
||||
} |
||||
|
||||
func pull2issue(pull *base.PullRequest) *base.Issue { |
||||
return &base.Issue{ |
||||
Number: pull.Number, |
||||
PosterID: pull.PosterID, |
||||
PosterName: pull.PosterName, |
||||
PosterEmail: pull.PosterEmail, |
||||
Title: pull.Title, |
||||
Content: pull.Content, |
||||
Milestone: pull.Milestone, |
||||
State: pull.State, |
||||
IsLocked: pull.IsLocked, |
||||
Created: pull.Created, |
||||
Updated: pull.Updated, |
||||
Closed: pull.Closed, |
||||
Labels: pull.Labels, |
||||
Reactions: pull.Reactions, |
||||
Assignees: pull.Assignees, |
||||
} |
||||
} |
After Width: | Height: | Size: 1.6 KiB |
@ -0,0 +1,137 @@ |
||||
{{template "base/head" .}} |
||||
<div class="repository new migrate"> |
||||
<div class="ui middle very relaxed page grid"> |
||||
<div class="column"> |
||||
<form class="ui form" action="{{.Link}}" method="post"> |
||||
{{.CsrfTokenHtml}} |
||||
<h3 class="ui top attached header"> |
||||
{{.i18n.Tr "repo.migrate.migrate" .service.Title}} |
||||
<input id="service_type" type="hidden" name="service" value="{{.service}}"> |
||||
</h3> |
||||
<div class="ui attached segment"> |
||||
{{template "base/alert" .}} |
||||
<div class="inline required field {{if .Err_CloneAddr}}error{{end}}"> |
||||
<label for="clone_addr">{{.i18n.Tr "repo.migrate.clone_address"}}</label> |
||||
<input id="clone_addr" name="clone_addr" value="{{.clone_addr}}" autofocus required> |
||||
<span class="help"> |
||||
{{.i18n.Tr "repo.migrate.clone_address_desc"}}{{if .ContextUser.CanImportLocal}} {{.i18n.Tr "repo.migrate.clone_local_path"}}{{end}} |
||||
{{if .LFSActive}}<br />{{.i18n.Tr "repo.migrate.lfs_mirror_unsupported"}}{{end}} |
||||
</span> |
||||
</div> |
||||
|
||||
<div class="inline field {{if .Err_Auth}}error{{end}}"> |
||||
<label for="auth_token">{{.i18n.Tr "access_token"}}</label> |
||||
<input id="auth_token" name="auth_token" value="{{.auth_token}}" {{if not .auth_token}} data-need-clear="true" {{end}}> |
||||
<a target=”_blank” href="https://docs.gitea.io/en-us/api-usage">{{svg "octicon-question"}}</a> |
||||
</div> |
||||
|
||||
<div class="inline field"> |
||||
<label>{{.i18n.Tr "repo.migrate_options"}}</label> |
||||
<div class="ui checkbox"> |
||||
{{if .DisableMirrors}} |
||||
<input id="mirror" name="mirror" type="checkbox" readonly> |
||||
<label>{{.i18n.Tr "repo.migrate_options_mirror_disabled"}}</label> |
||||
{{else}} |
||||
<input id="mirror" name="mirror" type="checkbox" {{if .mirror}} checked{{end}}> |
||||
<label>{{.i18n.Tr "repo.migrate_options_mirror_helper" | Safe}}</label> |
||||
{{end}} |
||||
</div> |
||||
</div> |
||||
|
||||
<span class="help">{{.i18n.Tr "repo.migrate.migrate_items_options"}}</span> |
||||
<div id="migrate_items"> |
||||
<div class="inline field"> |
||||
<label>{{.i18n.Tr "repo.migrate_items"}}</label> |
||||
<div class="ui checkbox"> |
||||
<input name="wiki" type="checkbox" {{if .wiki}} checked{{end}}> |
||||
<label>{{.i18n.Tr "repo.migrate_items_wiki" | Safe}}</label> |
||||
</div> |
||||
<div class="ui checkbox"> |
||||
<input name="milestones" type="checkbox" {{if .milestones}} checked{{end}}> |
||||
<label>{{.i18n.Tr "repo.migrate_items_milestones" | Safe}}</label> |
||||
</div> |
||||
</div> |
||||
<div class="inline field"> |
||||
<label></label> |
||||
<div class="ui checkbox"> |
||||
<input name="labels" type="checkbox" {{if .labels}} checked{{end}}> |
||||
<label>{{.i18n.Tr "repo.migrate_items_labels" | Safe}}</label> |
||||
</div> |
||||
<div class="ui checkbox"> |
||||
<input name="issues" type="checkbox" {{if .issues}} checked{{end}}> |
||||
<label>{{.i18n.Tr "repo.migrate_items_issues" | Safe}}</label> |
||||
</div> |
||||
</div> |
||||
<div class="inline field"> |
||||
<label></label> |
||||
<div class="ui checkbox"> |
||||
<input name="pull_requests" type="checkbox" {{if .pull_requests}} checked{{end}}> |
||||
<label>{{.i18n.Tr "repo.migrate_items_merge_requests" | Safe}}</label> |
||||
</div> |
||||
<div class="ui checkbox"> |
||||
<input name="releases" type="checkbox" {{if .releases}} checked{{end}}> |
||||
<label>{{.i18n.Tr "repo.migrate_items_releases" | Safe}}</label> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
|
||||
<div class="ui divider"></div> |
||||
|
||||
<div class="inline required field {{if .Err_Owner}}error{{end}}"> |
||||
<label>{{.i18n.Tr "repo.owner"}}</label> |
||||
<div class="ui selection owner dropdown"> |
||||
<input type="hidden" id="uid" name="uid" value="{{.ContextUser.ID}}" required> |
||||
<span class="text" title="{{.ContextUser.Name}}"> |
||||
<img class="ui mini image" src="{{.ContextUser.RelAvatarLink}}"> |
||||
{{.ContextUser.ShortName 20}} |
||||
</span> |
||||
<i class="dropdown icon"></i> |
||||
<div class="menu" title="{{.SignedUser.Name}}"> |
||||
<div class="item" data-value="{{.SignedUser.ID}}"> |
||||
<img class="ui mini image" src="{{.SignedUser.RelAvatarLink}}"> |
||||
{{.SignedUser.ShortName 20}} |
||||
</div> |
||||
{{range .Orgs}} |
||||
<div class="item" data-value="{{.ID}}" title="{{.Name}}"> |
||||
<img class="ui mini image" src="{{.RelAvatarLink}}"> |
||||
{{.ShortName 20}} |
||||
</div> |
||||
{{end}} |
||||
</div> |
||||
</div> |
||||
</div> |
||||
|
||||
<div class="inline required field {{if .Err_RepoName}}error{{end}}"> |
||||
<label for="repo_name">{{.i18n.Tr "repo.repo_name"}}</label> |
||||
<input id="repo_name" name="repo_name" value="{{.repo_name}}" required> |
||||
</div> |
||||
<div class="inline field"> |
||||
<label>{{.i18n.Tr "repo.visibility"}}</label> |
||||
<div class="ui checkbox"> |
||||
{{if .IsForcedPrivate}} |
||||
<input name="private" type="checkbox" checked readonly> |
||||
<label>{{.i18n.Tr "repo.visibility_helper_forced" | Safe}}</label> |
||||
{{else}} |
||||
<input name="private" type="checkbox" {{if .private}} checked{{end}}> |
||||
<label>{{.i18n.Tr "repo.visibility_helper" | Safe}}</label> |
||||
{{end}} |
||||
</div> |
||||
</div> |
||||
<div class="inline field {{if .Err_Description}}error{{end}}"> |
||||
<label for="description">{{.i18n.Tr "repo.repo_desc"}}</label> |
||||
<textarea id="description" name="description">{{.description}}</textarea> |
||||
</div> |
||||
|
||||
<div class="inline field"> |
||||
<label></label> |
||||
<button class="ui green button"> |
||||
{{.i18n.Tr "repo.migrate_repo"}} |
||||
</button> |
||||
<a class="ui button" href="{{AppSubUrl}}/">{{.i18n.Tr "cancel"}}</a> |
||||
</div> |
||||
</div> |
||||
</form> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
{{template "base/footer" .}} |
@ -0,0 +1,20 @@ |
||||
Copyright (c) 2016 The Gitea Authors |
||||
Copyright (c) 2014 The Gogs Authors |
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy |
||||
of this software and associated documentation files (the "Software"), to deal |
||||
in the Software without restriction, including without limitation the rights |
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
||||
copies of the Software, and to permit persons to whom the Software is |
||||
furnished to do so, subject to the following conditions: |
||||
|
||||
The above copyright notice and this permission notice shall be included in |
||||
all copies or substantial portions of the Software. |
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN |
||||
THE SOFTWARE. |
@ -0,0 +1,44 @@ |
||||
// 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 ( |
||||
"fmt" |
||||
"time" |
||||
) |
||||
|
||||
// CronTask represents a Cron task
|
||||
type CronTask struct { |
||||
Name string `json:"name"` |
||||
Schedule string `json:"schedule"` |
||||
Next time.Time `json:"next"` |
||||
Prev time.Time `json:"prev"` |
||||
ExecTimes int64 `json:"exec_times"` |
||||
} |
||||
|
||||
// ListCronTaskOptions list options for ListCronTasks
|
||||
type ListCronTaskOptions struct { |
||||
ListOptions |
||||
} |
||||
|
||||
// ListCronTasks list available cron tasks
|
||||
func (c *Client) ListCronTasks(opt ListCronTaskOptions) ([]*CronTask, *Response, error) { |
||||
if err := c.CheckServerVersionConstraint(">=1.13.0"); err != nil { |
||||
return nil, nil, err |
||||
} |
||||
opt.setDefaults() |
||||
ct := make([]*CronTask, 0, opt.PageSize) |
||||
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/admin/cron?%s", opt.getURLQuery().Encode()), jsonHeader, nil, &ct) |
||||
return ct, resp, err |
||||
} |
||||
|
||||
// RunCronTasks run a cron task
|
||||
func (c *Client) RunCronTasks(task string) (*Response, error) { |
||||
if err := c.CheckServerVersionConstraint(">=1.13.0"); err != nil { |
||||
return nil, err |
||||
} |
||||
_, resp, err := c.getResponse("POST", fmt.Sprintf("/admin/cron/%s", task), jsonHeader, nil) |
||||
return resp, err |
||||
} |
@ -0,0 +1,36 @@ |
||||
// Copyright 2015 The Gogs Authors. All rights reserved.
|
||||
// Copyright 2019 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" |
||||
) |
||||
|
||||
// AdminListOrgsOptions options for listing admin's organizations
|
||||
type AdminListOrgsOptions struct { |
||||
ListOptions |
||||
} |
||||
|
||||
// AdminListOrgs lists all orgs
|
||||
func (c *Client) AdminListOrgs(opt AdminListOrgsOptions) ([]*Organization, *Response, error) { |
||||
opt.setDefaults() |
||||
orgs := make([]*Organization, 0, opt.PageSize) |
||||
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/admin/orgs?%s", opt.getURLQuery().Encode()), nil, nil, &orgs) |
||||
return orgs, resp, err |
||||
} |
||||
|
||||
// AdminCreateOrg create an organization
|
||||
func (c *Client) AdminCreateOrg(user string, opt CreateOrgOption) (*Organization, *Response, error) { |
||||
body, err := json.Marshal(&opt) |
||||
if err != nil { |
||||
return nil, nil, err |
||||
} |
||||
org := new(Organization) |
||||
resp, err := c.getParsedResponse("POST", fmt.Sprintf("/admin/users/%s/orgs", user), jsonHeader, bytes.NewReader(body), org) |
||||
return org, resp, err |
||||
} |
@ -0,0 +1,22 @@ |
||||
// Copyright 2015 The Gogs 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" |
||||
) |
||||
|
||||
// AdminCreateRepo create a repo
|
||||
func (c *Client) AdminCreateRepo(user string, opt CreateRepoOption) (*Repository, *Response, error) { |
||||
body, err := json.Marshal(&opt) |
||||
if err != nil { |
||||
return nil, nil, err |
||||
} |
||||
repo := new(Repository) |
||||
resp, err := c.getParsedResponse("POST", fmt.Sprintf("/admin/users/%s/repos", user), jsonHeader, bytes.NewReader(body), repo) |
||||
return repo, resp, err |
||||
} |
@ -0,0 +1,114 @@ |
||||
// Copyright 2015 The Gogs Authors. All rights reserved.
|
||||
// Copyright 2019 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" |
||||
) |
||||
|
||||
// AdminListUsersOptions options for listing admin users
|
||||
type AdminListUsersOptions struct { |
||||
ListOptions |
||||
} |
||||
|
||||
// AdminListUsers lists all users
|
||||
func (c *Client) AdminListUsers(opt AdminListUsersOptions) ([]*User, *Response, error) { |
||||
opt.setDefaults() |
||||
users := make([]*User, 0, opt.PageSize) |
||||
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/admin/users?%s", opt.getURLQuery().Encode()), nil, nil, &users) |
||||
return users, resp, err |
||||
} |
||||
|
||||
// CreateUserOption create user options
|
||||
type CreateUserOption struct { |
||||
SourceID int64 `json:"source_id"` |
||||
LoginName string `json:"login_name"` |
||||
Username string `json:"username"` |
||||
FullName string `json:"full_name"` |
||||
Email string `json:"email"` |
||||
Password string `json:"password"` |
||||
MustChangePassword *bool `json:"must_change_password"` |
||||
SendNotify bool `json:"send_notify"` |
||||
} |
||||
|
||||
// Validate the CreateUserOption struct
|
||||
func (opt CreateUserOption) Validate() error { |
||||
if len(opt.Email) == 0 { |
||||
return fmt.Errorf("email is empty") |
||||
} |
||||
if len(opt.Username) == 0 { |
||||
return fmt.Errorf("username is empty") |
||||
} |
||||
return nil |
||||
} |
||||
|
||||
// AdminCreateUser create a user
|
||||
func (c *Client) AdminCreateUser(opt CreateUserOption) (*User, *Response, error) { |
||||
if err := opt.Validate(); err != nil { |
||||
return nil, nil, err |
||||
} |
||||
body, err := json.Marshal(&opt) |
||||
if err != nil { |
||||
return nil, nil, err |
||||
} |
||||
user := new(User) |
||||
resp, err := c.getParsedResponse("POST", "/admin/users", jsonHeader, bytes.NewReader(body), user) |
||||
return user, resp, err |
||||
} |
||||
|
||||
// EditUserOption edit user options
|
||||
type EditUserOption struct { |
||||
SourceID int64 `json:"source_id"` |
||||
LoginName string `json:"login_name"` |
||||
FullName string `json:"full_name"` |
||||
Email string `json:"email"` |
||||
Password string `json:"password"` |
||||
MustChangePassword *bool `json:"must_change_password"` |
||||
Website string `json:"website"` |
||||
Location string `json:"location"` |
||||
Active *bool `json:"active"` |
||||
Admin *bool `json:"admin"` |
||||
AllowGitHook *bool `json:"allow_git_hook"` |
||||
AllowImportLocal *bool `json:"allow_import_local"` |
||||
MaxRepoCreation *int `json:"max_repo_creation"` |
||||
ProhibitLogin *bool `json:"prohibit_login"` |
||||
AllowCreateOrganization *bool `json:"allow_create_organization"` |
||||
} |
||||
|
||||
// AdminEditUser modify user informations
|
||||
func (c *Client) AdminEditUser(user string, opt EditUserOption) (*Response, error) { |
||||
body, err := json.Marshal(&opt) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
_, resp, err := c.getResponse("PATCH", fmt.Sprintf("/admin/users/%s", user), jsonHeader, bytes.NewReader(body)) |
||||
return resp, err |
||||
} |
||||
|
||||
// AdminDeleteUser delete one user according name
|
||||
func (c *Client) AdminDeleteUser(user string) (*Response, error) { |
||||
_, resp, err := c.getResponse("DELETE", fmt.Sprintf("/admin/users/%s", user), nil, nil) |
||||
return resp, err |
||||
} |
||||
|
||||
// AdminCreateUserPublicKey adds a public key for the user
|
||||
func (c *Client) AdminCreateUserPublicKey(user string, opt CreateKeyOption) (*PublicKey, *Response, error) { |
||||
body, err := json.Marshal(&opt) |
||||
if err != nil { |
||||
return nil, nil, err |
||||
} |
||||
key := new(PublicKey) |
||||
resp, err := c.getParsedResponse("POST", fmt.Sprintf("/admin/users/%s/keys", user), jsonHeader, bytes.NewReader(body), key) |
||||
return key, resp, err |
||||
} |
||||
|
||||
// AdminDeleteUserPublicKey deletes a user's public key
|
||||
func (c *Client) AdminDeleteUserPublicKey(user string, keyID int) (*Response, error) { |
||||
_, resp, err := c.getResponse("DELETE", fmt.Sprintf("/admin/users/%s/keys/%d", user, keyID), nil, nil) |
||||
return resp, err |
||||
} |
@ -0,0 +1,96 @@ |
||||
// Copyright 2017 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 "code.gitea.io/sdk/gitea"
|
||||
import ( |
||||
"bytes" |
||||
"encoding/json" |
||||
"fmt" |
||||
"io" |
||||
"mime/multipart" |
||||
"net/http" |
||||
"time" |
||||
) |
||||
|
||||
// Attachment a generic attachment
|
||||
type Attachment struct { |
||||
ID int64 `json:"id"` |
||||
Name string `json:"name"` |
||||
Size int64 `json:"size"` |
||||
DownloadCount int64 `json:"download_count"` |
||||
Created time.Time `json:"created_at"` |
||||
UUID string `json:"uuid"` |
||||
DownloadURL string `json:"browser_download_url"` |
||||
} |
||||
|
||||
// ListReleaseAttachmentsOptions options for listing release's attachments
|
||||
type ListReleaseAttachmentsOptions struct { |
||||
ListOptions |
||||
} |
||||
|
||||
// ListReleaseAttachments list release's attachments
|
||||
func (c *Client) ListReleaseAttachments(user, repo string, release int64, opt ListReleaseAttachmentsOptions) ([]*Attachment, *Response, error) { |
||||
opt.setDefaults() |
||||
attachments := make([]*Attachment, 0, opt.PageSize) |
||||
resp, err := c.getParsedResponse("GET", |
||||
fmt.Sprintf("/repos/%s/%s/releases/%d/assets?%s", user, repo, release, opt.getURLQuery().Encode()), |
||||
nil, nil, &attachments) |
||||
return attachments, resp, err |
||||
} |
||||
|
||||
// GetReleaseAttachment returns the requested attachment
|
||||
func (c *Client) GetReleaseAttachment(user, repo string, release int64, id int64) (*Attachment, *Response, error) { |
||||
a := new(Attachment) |
||||
resp, err := c.getParsedResponse("GET", |
||||
fmt.Sprintf("/repos/%s/%s/releases/%d/assets/%d", user, repo, release, id), |
||||
nil, nil, &a) |
||||
return a, resp, err |
||||
} |
||||
|
||||
// CreateReleaseAttachment creates an attachment for the given release
|
||||
func (c *Client) CreateReleaseAttachment(user, repo string, release int64, file io.Reader, filename string) (*Attachment, *Response, error) { |
||||
// Write file to body
|
||||
body := new(bytes.Buffer) |
||||
writer := multipart.NewWriter(body) |
||||
part, err := writer.CreateFormFile("attachment", filename) |
||||
if err != nil { |
||||
return nil, nil, err |
||||
} |
||||
|
||||
if _, err = io.Copy(part, file); err != nil { |
||||
return nil, nil, err |
||||
} |
||||
if err = writer.Close(); err != nil { |
||||
return nil, nil, err |
||||
} |
||||
|
||||
// Send request
|
||||
attachment := new(Attachment) |
||||
resp, err := c.getParsedResponse("POST", |
||||
fmt.Sprintf("/repos/%s/%s/releases/%d/assets", user, repo, release), |
||||
http.Header{"Content-Type": {writer.FormDataContentType()}}, body, &attachment) |
||||
return attachment, resp, err |
||||
} |
||||
|
||||
// EditAttachmentOptions options for editing attachments
|
||||
type EditAttachmentOptions struct { |
||||
Name string `json:"name"` |
||||
} |
||||
|
||||
// EditReleaseAttachment updates the given attachment with the given options
|
||||
func (c *Client) EditReleaseAttachment(user, repo string, release int64, attachment int64, form EditAttachmentOptions) (*Attachment, *Response, error) { |
||||
body, err := json.Marshal(&form) |
||||
if err != nil { |
||||
return nil, nil, err |
||||
} |
||||
attach := new(Attachment) |
||||
resp, err := c.getParsedResponse("PATCH", fmt.Sprintf("/repos/%s/%s/releases/%d/assets/%d", user, repo, release, attachment), jsonHeader, bytes.NewReader(body), attach) |
||||
return attach, resp, err |
||||
} |
||||
|
||||
// DeleteReleaseAttachment deletes the given attachment including the uploaded file
|
||||
func (c *Client) DeleteReleaseAttachment(user, repo string, release int64, id int64) (*Response, error) { |
||||
_, resp, err := c.getResponse("DELETE", fmt.Sprintf("/repos/%s/%s/releases/%d/assets/%d", user, repo, release, id), nil, nil) |
||||
return resp, err |
||||
} |
@ -0,0 +1,253 @@ |
||||
// 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 ( |
||||
"context" |
||||
"encoding/json" |
||||
"errors" |
||||
"fmt" |
||||
"io" |
||||
"io/ioutil" |
||||
"net/http" |
||||
"strings" |
||||
"sync" |
||||
|
||||
"github.com/hashicorp/go-version" |
||||
) |
||||
|
||||
var jsonHeader = http.Header{"content-type": []string{"application/json"}} |
||||
|
||||
// Version return the library version
|
||||
func Version() string { |
||||
return "0.13.0" |
||||
} |
||||
|
||||
// Client represents a Gitea API client.
|
||||
type Client struct { |
||||
url string |
||||
accessToken string |
||||
username string |
||||
password string |
||||
otp string |
||||
sudo string |
||||
debug bool |
||||
client *http.Client |
||||
ctx context.Context |
||||
serverVersion *version.Version |
||||
versionLock sync.RWMutex |
||||
} |
||||
|
||||
// Response represents the gitea response
|
||||
type Response struct { |
||||
*http.Response |
||||
} |
||||
|
||||
// NewClient initializes and returns a API client.
|
||||
func NewClient(url string, options ...func(*Client)) (*Client, error) { |
||||
client := &Client{ |
||||
url: strings.TrimSuffix(url, "/"), |
||||
client: &http.Client{}, |
||||
ctx: context.Background(), |
||||
} |
||||
for _, opt := range options { |
||||
opt(client) |
||||
} |
||||
if err := client.CheckServerVersionConstraint(">=1.10"); err != nil { |
||||
return nil, err |
||||
} |
||||
return client, nil |
||||
} |
||||
|
||||
// NewClientWithHTTP creates an API client with a custom http client
|
||||
// Deprecated use SetHTTPClient option
|
||||
func NewClientWithHTTP(url string, httpClient *http.Client) *Client { |
||||
client, _ := NewClient(url, SetHTTPClient(httpClient)) |
||||
return client |
||||
} |
||||
|
||||
// SetHTTPClient is an option for NewClient to set custom http client
|
||||
func SetHTTPClient(httpClient *http.Client) func(client *Client) { |
||||
return func(client *Client) { |
||||
client.client = httpClient |
||||
} |
||||
} |
||||
|
||||
// SetToken is an option for NewClient to set token
|
||||
func SetToken(token string) func(client *Client) { |
||||
return func(client *Client) { |
||||
client.accessToken = token |
||||
} |
||||
} |
||||
|
||||
// SetBasicAuth is an option for NewClient to set username and password
|
||||
func SetBasicAuth(username, password string) func(client *Client) { |
||||
return func(client *Client) { |
||||
client.SetBasicAuth(username, password) |
||||
} |
||||
} |
||||
|
||||
// SetBasicAuth sets username and password
|
||||
func (c *Client) SetBasicAuth(username, password string) { |
||||
c.username, c.password = username, password |
||||
} |
||||
|
||||
// SetOTP is an option for NewClient to set OTP for 2FA
|
||||
func SetOTP(otp string) func(client *Client) { |
||||
return func(client *Client) { |
||||
client.SetOTP(otp) |
||||
} |
||||
} |
||||
|
||||
// SetOTP sets OTP for 2FA
|
||||
func (c *Client) SetOTP(otp string) { |
||||
c.otp = otp |
||||
} |
||||
|
||||
// SetContext is an option for NewClient to set context
|
||||
func SetContext(ctx context.Context) func(client *Client) { |
||||
return func(client *Client) { |
||||
client.SetContext(ctx) |
||||
} |
||||
} |
||||
|
||||
// SetContext set context witch is used for http requests
|
||||
func (c *Client) SetContext(ctx context.Context) { |
||||
c.ctx = ctx |
||||
} |
||||
|
||||
// SetHTTPClient replaces default http.Client with user given one.
|
||||
func (c *Client) SetHTTPClient(client *http.Client) { |
||||
c.client = client |
||||
} |
||||
|
||||
// SetSudo is an option for NewClient to set sudo header
|
||||
func SetSudo(sudo string) func(client *Client) { |
||||
return func(client *Client) { |
||||
client.SetSudo(sudo) |
||||
} |
||||
} |
||||
|
||||
// SetSudo sets username to impersonate.
|
||||
func (c *Client) SetSudo(sudo string) { |
||||
c.sudo = sudo |
||||
} |
||||
|
||||
// SetDebugMode is an option for NewClient to enable debug mode
|
||||
func SetDebugMode() func(client *Client) { |
||||
return func(client *Client) { |
||||
client.debug = true |
||||
} |
||||
} |
||||
|
||||
func (c *Client) getWebResponse(method, path string, body io.Reader) ([]byte, *Response, error) { |
||||
if c.debug { |
||||
fmt.Printf("%s: %s\nBody: %v\n", method, c.url+path, body) |
||||
} |
||||
req, err := http.NewRequestWithContext(c.ctx, method, c.url+path, body) |
||||
if err != nil { |
||||
return nil, nil, err |
||||
} |
||||
resp, err := c.client.Do(req) |
||||
if err != nil { |
||||
return nil, nil, err |
||||
} |
||||
|
||||
defer resp.Body.Close() |
||||
data, err := ioutil.ReadAll(resp.Body) |
||||
if c.debug { |
||||
fmt.Printf("Response: %v\n\n", resp) |
||||
} |
||||
return data, &Response{resp}, nil |
||||
} |
||||
|
||||
func (c *Client) doRequest(method, path string, header http.Header, body io.Reader) (*Response, error) { |
||||
if c.debug { |
||||
fmt.Printf("%s: %s\nHeader: %v\nBody: %s\n", method, c.url+"/api/v1"+path, header, body) |
||||
} |
||||
req, err := http.NewRequestWithContext(c.ctx, method, c.url+"/api/v1"+path, body) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
if len(c.accessToken) != 0 { |
||||
req.Header.Set("Authorization", "token "+c.accessToken) |
||||
} |
||||
if len(c.otp) != 0 { |
||||
req.Header.Set("X-GITEA-OTP", c.otp) |
||||
} |
||||
if len(c.username) != 0 { |
||||
req.SetBasicAuth(c.username, c.password) |
||||
} |
||||
if len(c.sudo) != 0 { |
||||
req.Header.Set("Sudo", c.sudo) |
||||
} |
||||
for k, v := range header { |
||||
req.Header[k] = v |
||||
} |
||||
|
||||
resp, err := c.client.Do(req) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
if c.debug { |
||||
fmt.Printf("Response: %v\n\n", resp) |
||||
} |
||||
return &Response{resp}, nil |
||||
} |
||||
|
||||
func (c *Client) getResponse(method, path string, header http.Header, body io.Reader) ([]byte, *Response, error) { |
||||
resp, err := c.doRequest(method, path, header, body) |
||||
if err != nil { |
||||
return nil, nil, err |
||||
} |
||||
defer resp.Body.Close() |
||||
|
||||
data, err := ioutil.ReadAll(resp.Body) |
||||
if err != nil { |
||||
return nil, resp, err |
||||
} |
||||
|
||||
switch resp.StatusCode { |
||||
case 403: |
||||
return data, resp, errors.New("403 Forbidden") |
||||
case 404: |
||||
return data, resp, errors.New("404 Not Found") |
||||
case 409: |
||||
return data, resp, errors.New("409 Conflict") |
||||
case 422: |
||||
return data, resp, fmt.Errorf("422 Unprocessable Entity: %s", string(data)) |
||||
} |
||||
|
||||
if resp.StatusCode/100 != 2 { |
||||
errMap := make(map[string]interface{}) |
||||
if err = json.Unmarshal(data, &errMap); err != nil { |
||||
// when the JSON can't be parsed, data was probably empty or a plain string,
|
||||
// so we try to return a helpful error anyway
|
||||
return data, resp, fmt.Errorf("Unknown API Error: %d\nRequest: '%s' with '%s' method '%s' header and '%s' body", resp.StatusCode, path, method, header, string(data)) |
||||
} |
||||
return data, resp, errors.New(errMap["message"].(string)) |
||||
} |
||||
|
||||
return data, resp, nil |
||||
} |
||||
|
||||
func (c *Client) getParsedResponse(method, path string, header http.Header, body io.Reader, obj interface{}) (*Response, error) { |
||||
data, resp, err := c.getResponse(method, path, header, body) |
||||
if err != nil { |
||||
return resp, err |
||||
} |
||||
return resp, json.Unmarshal(data, obj) |
||||
} |
||||
|
||||
func (c *Client) getStatusCode(method, path string, header http.Header, body io.Reader) (int, *Response, error) { |
||||
resp, err := c.doRequest(method, path, header, body) |
||||
if err != nil { |
||||
return -1, resp, err |
||||
} |
||||
defer resp.Body.Close() |
||||
|
||||
return resp.StatusCode, resp, nil |
||||
} |
@ -0,0 +1,5 @@ |
||||
// Copyright 2016 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 "code.gitea.io/sdk/gitea"
|
@ -0,0 +1,43 @@ |
||||
// Copyright 2016 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" |
||||
) |
||||
|
||||
// ListForksOptions options for listing repository's forks
|
||||
type ListForksOptions struct { |
||||
ListOptions |
||||
} |
||||
|
||||
// ListForks list a repository's forks
|
||||
func (c *Client) ListForks(user string, repo string, opt ListForksOptions) ([]*Repository, *Response, error) { |
||||
opt.setDefaults() |
||||
forks := make([]*Repository, opt.PageSize) |
||||
resp, err := c.getParsedResponse("GET", |
||||
fmt.Sprintf("/repos/%s/%s/forks?%s", user, repo, opt.getURLQuery().Encode()), |
||||
nil, nil, &forks) |
||||
return forks, resp, err |
||||
} |
||||
|
||||
// CreateForkOption options for creating a fork
|
||||
type CreateForkOption struct { |
||||
// organization name, if forking into an organization
|
||||
Organization *string `json:"organization"` |
||||
} |
||||
|
||||
// CreateFork create a fork of a repository
|
||||
func (c *Client) CreateFork(user, repo string, form CreateForkOption) (*Repository, *Response, error) { |
||||
body, err := json.Marshal(form) |
||||
if err != nil { |
||||
return nil, nil, err |
||||
} |
||||
fork := new(Repository) |
||||
resp, err := c.getParsedResponse("POST", fmt.Sprintf("/repos/%s/%s/forks", user, repo), jsonHeader, bytes.NewReader(body), &fork) |
||||
return fork, resp, err |
||||
} |
@ -0,0 +1,25 @@ |
||||
// 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 ( |
||||
"fmt" |
||||
) |
||||
|
||||
// GitBlobResponse represents a git blob
|
||||
type GitBlobResponse struct { |
||||
Content string `json:"content"` |
||||
Encoding string `json:"encoding"` |
||||
URL string `json:"url"` |
||||
SHA string `json:"sha"` |
||||
Size int64 `json:"size"` |
||||
} |
||||
|
||||
// GetBlob get the blob of a repository file
|
||||
func (c *Client) GetBlob(user, repo, sha string) (*GitBlobResponse, *Response, error) { |
||||
blob := new(GitBlobResponse) |
||||
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/git/blobs/%s", user, repo, sha), nil, nil, blob) |
||||
return blob, resp, err |
||||
} |
@ -0,0 +1,59 @@ |
||||
// Copyright 2019 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" |
||||
) |
||||
|
||||
// GitHook represents a Git repository hook
|
||||
type GitHook struct { |
||||
Name string `json:"name"` |
||||
IsActive bool `json:"is_active"` |
||||
Content string `json:"content,omitempty"` |
||||
} |
||||
|
||||
// ListRepoGitHooksOptions options for listing repository's githooks
|
||||
type ListRepoGitHooksOptions struct { |
||||
ListOptions |
||||
} |
||||
|
||||
// ListRepoGitHooks list all the Git hooks of one repository
|
||||
func (c *Client) ListRepoGitHooks(user, repo string, opt ListRepoGitHooksOptions) ([]*GitHook, *Response, error) { |
||||
opt.setDefaults() |
||||
hooks := make([]*GitHook, 0, opt.PageSize) |
||||
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/hooks/git?%s", user, repo, opt.getURLQuery().Encode()), nil, nil, &hooks) |
||||
return hooks, resp, err |
||||
} |
||||
|
||||
// GetRepoGitHook get a Git hook of a repository
|
||||
func (c *Client) GetRepoGitHook(user, repo, id string) (*GitHook, *Response, error) { |
||||
h := new(GitHook) |
||||
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/hooks/git/%s", user, repo, id), nil, nil, h) |
||||
return h, resp, err |
||||
} |
||||
|
||||
// EditGitHookOption options when modifying one Git hook
|
||||
type EditGitHookOption struct { |
||||
Content string `json:"content"` |
||||
} |
||||
|
||||
// EditRepoGitHook modify one Git hook of a repository
|
||||
func (c *Client) EditRepoGitHook(user, repo, id string, opt EditGitHookOption) (*Response, error) { |
||||
body, err := json.Marshal(&opt) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
_, resp, err := c.getResponse("PATCH", fmt.Sprintf("/repos/%s/%s/hooks/git/%s", user, repo, id), jsonHeader, bytes.NewReader(body)) |
||||
return resp, err |
||||
} |
||||
|
||||
// DeleteRepoGitHook delete one Git hook from a repository
|
||||
func (c *Client) DeleteRepoGitHook(user, repo, id string) (*Response, error) { |
||||
_, resp, err := c.getResponse("DELETE", fmt.Sprintf("/repos/%s/%s/hooks/git/%s", user, repo, id), nil, nil) |
||||
return resp, err |
||||
} |
@ -0,0 +1,8 @@ |
||||
module code.gitea.io/sdk/gitea |
||||
|
||||
go 1.12 |
||||
|
||||
require ( |
||||
github.com/hashicorp/go-version v1.2.0 |
||||
github.com/stretchr/testify v1.4.0 |
||||
) |
@ -0,0 +1,13 @@ |
||||
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= |
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= |
||||
github.com/hashicorp/go-version v1.2.0 h1:3vNe/fWF5CBgRIguda1meWhsZHy3m8gCJ5wx+dIzX/E= |
||||
github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= |
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= |
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= |
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= |
||||
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= |
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= |
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= |
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= |
||||
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= |
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= |
@ -0,0 +1,142 @@ |
||||
// Copyright 2014 The Gogs Authors. All rights reserved.
|
||||
// Copyright 2017 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" |
||||
"time" |
||||
) |
||||
|
||||
// Hook a hook is a web hook when one repository changed
|
||||
type Hook struct { |
||||
ID int64 `json:"id"` |
||||
Type string `json:"type"` |
||||
URL string `json:"-"` |
||||
Config map[string]string `json:"config"` |
||||
Events []string `json:"events"` |
||||
Active bool `json:"active"` |
||||
Updated time.Time `json:"updated_at"` |
||||
Created time.Time `json:"created_at"` |
||||
} |
||||
|
||||
// ListHooksOptions options for listing hooks
|
||||
type ListHooksOptions struct { |
||||
ListOptions |
||||
} |
||||
|
||||
// ListOrgHooks list all the hooks of one organization
|
||||
func (c *Client) ListOrgHooks(org string, opt ListHooksOptions) ([]*Hook, *Response, error) { |
||||
opt.setDefaults() |
||||
hooks := make([]*Hook, 0, opt.PageSize) |
||||
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/orgs/%s/hooks?%s", org, opt.getURLQuery().Encode()), nil, nil, &hooks) |
||||
return hooks, resp, err |
||||
} |
||||
|
||||
// ListRepoHooks list all the hooks of one repository
|
||||
func (c *Client) ListRepoHooks(user, repo string, opt ListHooksOptions) ([]*Hook, *Response, error) { |
||||
opt.setDefaults() |
||||
hooks := make([]*Hook, 0, opt.PageSize) |
||||
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/hooks?%s", user, repo, opt.getURLQuery().Encode()), nil, nil, &hooks) |
||||
return hooks, resp, err |
||||
} |
||||
|
||||
// GetOrgHook get a hook of an organization
|
||||
func (c *Client) GetOrgHook(org string, id int64) (*Hook, *Response, error) { |
||||
h := new(Hook) |
||||
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/orgs/%s/hooks/%d", org, id), nil, nil, h) |
||||
return h, resp, err |
||||
} |
||||
|
||||
// GetRepoHook get a hook of a repository
|
||||
func (c *Client) GetRepoHook(user, repo string, id int64) (*Hook, *Response, error) { |
||||
h := new(Hook) |
||||
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/hooks/%d", user, repo, id), nil, nil, h) |
||||
return h, resp, err |
||||
} |
||||
|
||||
// CreateHookOption options when create a hook
|
||||
type CreateHookOption struct { |
||||
Type string `json:"type"` |
||||
Config map[string]string `json:"config"` |
||||
Events []string `json:"events"` |
||||
BranchFilter string `json:"branch_filter"` |
||||
Active bool `json:"active"` |
||||
} |
||||
|
||||
// Validate the CreateHookOption struct
|
||||
func (opt CreateHookOption) Validate() error { |
||||
if len(opt.Type) == 0 { |
||||
return fmt.Errorf("hook type needed") |
||||
} |
||||
return nil |
||||
} |
||||
|
||||
// CreateOrgHook create one hook for an organization, with options
|
||||
func (c *Client) CreateOrgHook(org string, opt CreateHookOption) (*Hook, *Response, error) { |
||||
if err := opt.Validate(); err != nil { |
||||
return nil, nil, err |
||||
} |
||||
body, err := json.Marshal(&opt) |
||||
if err != nil { |
||||
return nil, nil, err |
||||
} |
||||
h := new(Hook) |
||||
resp, err := c.getParsedResponse("POST", fmt.Sprintf("/orgs/%s/hooks", org), jsonHeader, bytes.NewReader(body), h) |
||||
return h, resp, err |
||||
} |
||||
|
||||
// CreateRepoHook create one hook for a repository, with options
|
||||
func (c *Client) CreateRepoHook(user, repo string, opt CreateHookOption) (*Hook, *Response, error) { |
||||
body, err := json.Marshal(&opt) |
||||
if err != nil { |
||||
return nil, nil, err |
||||
} |
||||
h := new(Hook) |
||||
resp, err := c.getParsedResponse("POST", fmt.Sprintf("/repos/%s/%s/hooks", user, repo), jsonHeader, bytes.NewReader(body), h) |
||||
return h, resp, err |
||||
} |
||||
|
||||
// EditHookOption options when modify one hook
|
||||
type EditHookOption struct { |
||||
Config map[string]string `json:"config"` |
||||
Events []string `json:"events"` |
||||
BranchFilter string `json:"branch_filter"` |
||||
Active *bool `json:"active"` |
||||
} |
||||
|
||||
// EditOrgHook modify one hook of an organization, with hook id and options
|
||||
func (c *Client) EditOrgHook(org string, id int64, opt EditHookOption) (*Response, error) { |
||||
body, err := json.Marshal(&opt) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
_, resp, err := c.getResponse("PATCH", fmt.Sprintf("/orgs/%s/hooks/%d", org, id), jsonHeader, bytes.NewReader(body)) |
||||
return resp, err |
||||
} |
||||
|
||||
// EditRepoHook modify one hook of a repository, with hook id and options
|
||||
func (c *Client) EditRepoHook(user, repo string, id int64, opt EditHookOption) (*Response, error) { |
||||
body, err := json.Marshal(&opt) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
_, resp, err := c.getResponse("PATCH", fmt.Sprintf("/repos/%s/%s/hooks/%d", user, repo, id), jsonHeader, bytes.NewReader(body)) |
||||
return resp, err |
||||
} |
||||
|
||||
// DeleteOrgHook delete one hook from an organization, with hook id
|
||||
func (c *Client) DeleteOrgHook(org string, id int64) (*Response, error) { |
||||
_, resp, err := c.getResponse("DELETE", fmt.Sprintf("/orgs/%s/hooks/%d", org, id), nil, nil) |
||||
return resp, err |
||||
} |
||||
|
||||
// DeleteRepoHook delete one hook from a repository, with hook id
|
||||
func (c *Client) DeleteRepoHook(user, repo string, id int64) (*Response, error) { |
||||
_, resp, err := c.getResponse("DELETE", fmt.Sprintf("/repos/%s/%s/hooks/%d", user, repo, id), nil, nil) |
||||
return resp, err |
||||
} |
@ -0,0 +1,233 @@ |
||||
// Copyright 2016 The Gogs Authors. All rights reserved.
|
||||
// Copyright 2019 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" |
||||
"net/url" |
||||
"strings" |
||||
"time" |
||||
) |
||||
|
||||
// PullRequestMeta PR info if an issue is a PR
|
||||
type PullRequestMeta struct { |
||||
HasMerged bool `json:"merged"` |
||||
Merged *time.Time `json:"merged_at"` |
||||
} |
||||
|
||||
// RepositoryMeta basic repository information
|
||||
type RepositoryMeta struct { |
||||
ID int64 `json:"id"` |
||||
Name string `json:"name"` |
||||
Owner string `json:"owner"` |
||||
FullName string `json:"full_name"` |
||||
} |
||||
|
||||
// Issue represents an issue in a repository
|
||||
type Issue struct { |
||||
ID int64 `json:"id"` |
||||
URL string `json:"url"` |
||||
Index int64 `json:"number"` |
||||
Poster *User `json:"user"` |
||||
OriginalAuthor string `json:"original_author"` |
||||
OriginalAuthorID int64 `json:"original_author_id"` |
||||
Title string `json:"title"` |
||||
Body string `json:"body"` |
||||
Labels []*Label `json:"labels"` |
||||
Milestone *Milestone `json:"milestone"` |
||||
Assignee *User `json:"assignee"` |
||||
Assignees []*User `json:"assignees"` |
||||
// Whether the issue is open or closed
|
||||
State StateType `json:"state"` |
||||
IsLocked bool `json:"is_locked"` |
||||
Comments int `json:"comments"` |
||||
Created time.Time `json:"created_at"` |
||||
Updated time.Time `json:"updated_at"` |
||||
Closed *time.Time `json:"closed_at"` |
||||
Deadline *time.Time `json:"due_date"` |
||||
PullRequest *PullRequestMeta `json:"pull_request"` |
||||
Repository *RepositoryMeta `json:"repository"` |
||||
} |
||||
|
||||
// ListIssueOption list issue options
|
||||
type ListIssueOption struct { |
||||
ListOptions |
||||
State StateType |
||||
Type IssueType |
||||
Labels []string |
||||
Milestones []string |
||||
KeyWord string |
||||
} |
||||
|
||||
// StateType issue state type
|
||||
type StateType string |
||||
|
||||
const ( |
||||
// StateOpen pr/issue is opend
|
||||
StateOpen StateType = "open" |
||||
// StateClosed pr/issue is closed
|
||||
StateClosed StateType = "closed" |
||||
// StateAll is all
|
||||
StateAll StateType = "all" |
||||
) |
||||
|
||||
// IssueType is issue a pull or only an issue
|
||||
type IssueType string |
||||
|
||||
const ( |
||||
// IssueTypeAll pr and issue
|
||||
IssueTypeAll IssueType = "" |
||||
// IssueTypeIssue only issues
|
||||
IssueTypeIssue IssueType = "issues" |
||||
// IssueTypePull only pulls
|
||||
IssueTypePull IssueType = "pulls" |
||||
) |
||||
|
||||
// QueryEncode turns options into querystring argument
|
||||
func (opt *ListIssueOption) QueryEncode() string { |
||||
query := opt.getURLQuery() |
||||
|
||||
if len(opt.State) > 0 { |
||||
query.Add("state", string(opt.State)) |
||||
} |
||||
|
||||
if len(opt.Labels) > 0 { |
||||
query.Add("labels", strings.Join(opt.Labels, ",")) |
||||
} |
||||
|
||||
if len(opt.KeyWord) > 0 { |
||||
query.Add("q", opt.KeyWord) |
||||
} |
||||
|
||||
query.Add("type", string(opt.Type)) |
||||
|
||||
if len(opt.Milestones) > 0 { |
||||
query.Add("milestones", strings.Join(opt.Milestones, ",")) |
||||
} |
||||
|
||||
return query.Encode() |
||||
} |
||||
|
||||
// ListIssues returns all issues assigned the authenticated user
|
||||
func (c *Client) ListIssues(opt ListIssueOption) ([]*Issue, *Response, error) { |
||||
opt.setDefaults() |
||||
issues := make([]*Issue, 0, opt.PageSize) |
||||
|
||||
link, _ := url.Parse("/repos/issues/search") |
||||
link.RawQuery = opt.QueryEncode() |
||||
resp, err := c.getParsedResponse("GET", link.String(), jsonHeader, nil, &issues) |
||||
if e := c.CheckServerVersionConstraint(">=1.12.0"); e != nil { |
||||
for i := 0; i < len(issues); i++ { |
||||
if issues[i].Repository != nil { |
||||
issues[i].Repository.Owner = strings.Split(issues[i].Repository.FullName, "/")[0] |
||||
} |
||||
} |
||||
} |
||||
return issues, resp, err |
||||
} |
||||
|
||||
// ListRepoIssues returns all issues for a given repository
|
||||
func (c *Client) ListRepoIssues(owner, repo string, opt ListIssueOption) ([]*Issue, *Response, error) { |
||||
opt.setDefaults() |
||||
issues := make([]*Issue, 0, opt.PageSize) |
||||
|
||||
link, _ := url.Parse(fmt.Sprintf("/repos/%s/%s/issues", owner, repo)) |
||||
link.RawQuery = opt.QueryEncode() |
||||
resp, err := c.getParsedResponse("GET", link.String(), jsonHeader, nil, &issues) |
||||
if e := c.CheckServerVersionConstraint(">=1.12.0"); e != nil { |
||||
for i := 0; i < len(issues); i++ { |
||||
if issues[i].Repository != nil { |
||||
issues[i].Repository.Owner = strings.Split(issues[i].Repository.FullName, "/")[0] |
||||
} |
||||
} |
||||
} |
||||
return issues, resp, err |
||||
} |
||||
|
||||
// GetIssue returns a single issue for a given repository
|
||||
func (c *Client) GetIssue(owner, repo string, index int64) (*Issue, *Response, error) { |
||||
issue := new(Issue) |
||||
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/issues/%d", owner, repo, index), nil, nil, issue) |
||||
if e := c.CheckServerVersionConstraint(">=1.12.0"); e != nil && issue.Repository != nil { |
||||
issue.Repository.Owner = strings.Split(issue.Repository.FullName, "/")[0] |
||||
} |
||||
return issue, resp, err |
||||
} |
||||
|
||||
// CreateIssueOption options to create one issue
|
||||
type CreateIssueOption struct { |
||||
Title string `json:"title"` |
||||
Body string `json:"body"` |
||||
// username of assignee
|
||||
Assignee string `json:"assignee"` |
||||
Assignees []string `json:"assignees"` |
||||
Deadline *time.Time `json:"due_date"` |
||||
// milestone id
|
||||
Milestone int64 `json:"milestone"` |
||||
// list of label ids
|
||||
Labels []int64 `json:"labels"` |
||||
Closed bool `json:"closed"` |
||||
} |
||||
|
||||
// Validate the CreateIssueOption struct
|
||||
func (opt CreateIssueOption) Validate() error { |
||||
if len(strings.TrimSpace(opt.Title)) == 0 { |
||||
return fmt.Errorf("title is empty") |
||||
} |
||||
return nil |
||||
} |
||||
|
||||
// CreateIssue create a new issue for a given repository
|
||||
func (c *Client) CreateIssue(owner, repo string, opt CreateIssueOption) (*Issue, *Response, error) { |
||||
if err := opt.Validate(); err != nil { |
||||
return nil, nil, err |
||||
} |
||||
body, err := json.Marshal(&opt) |
||||
if err != nil { |
||||
return nil, nil, err |
||||
} |
||||
issue := new(Issue) |
||||
resp, err := c.getParsedResponse("POST", fmt.Sprintf("/repos/%s/%s/issues", owner, repo), |
||||
jsonHeader, bytes.NewReader(body), issue) |
||||
return issue, resp, err |
||||
} |
||||
|
||||
// EditIssueOption options for editing an issue
|
||||
type EditIssueOption struct { |
||||
Title string `json:"title"` |
||||
Body *string `json:"body"` |
||||
Assignee *string `json:"assignee"` |
||||
Assignees []string `json:"assignees"` |
||||
Milestone *int64 `json:"milestone"` |
||||
State *StateType `json:"state"` |
||||
Deadline *time.Time `json:"due_date"` |
||||
} |
||||
|
||||
// Validate the EditIssueOption struct
|
||||
func (opt EditIssueOption) Validate() error { |
||||
if len(opt.Title) != 0 && len(strings.TrimSpace(opt.Title)) == 0 { |
||||
return fmt.Errorf("title is empty") |
||||
} |
||||
return nil |
||||
} |
||||
|
||||
// EditIssue modify an existing issue for a given repository
|
||||
func (c *Client) EditIssue(owner, repo string, index int64, opt EditIssueOption) (*Issue, *Response, error) { |
||||
if err := opt.Validate(); err != nil { |
||||
return nil, nil, err |
||||
} |
||||
body, err := json.Marshal(&opt) |
||||
if err != nil { |
||||
return nil, nil, err |
||||
} |
||||
issue := new(Issue) |
||||
resp, err := c.getParsedResponse("PATCH", |
||||
fmt.Sprintf("/repos/%s/%s/issues/%d", owner, repo, index), |
||||
jsonHeader, bytes.NewReader(body), issue) |
||||
return issue, resp, err |
||||
} |
@ -0,0 +1,136 @@ |
||||
// Copyright 2016 The Gogs 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" |
||||
"net/url" |
||||
"time" |
||||
) |
||||
|
||||
// Comment represents a comment on a commit or issue
|
||||
type Comment struct { |
||||
ID int64 `json:"id"` |
||||
HTMLURL string `json:"html_url"` |
||||
PRURL string `json:"pull_request_url"` |
||||
IssueURL string `json:"issue_url"` |
||||
Poster *User `json:"user"` |
||||
OriginalAuthor string `json:"original_author"` |
||||
OriginalAuthorID int64 `json:"original_author_id"` |
||||
Body string `json:"body"` |
||||
Created time.Time `json:"created_at"` |
||||
Updated time.Time `json:"updated_at"` |
||||
} |
||||
|
||||
// ListIssueCommentOptions list comment options
|
||||
type ListIssueCommentOptions struct { |
||||
ListOptions |
||||
Since time.Time |
||||
Before time.Time |
||||
} |
||||
|
||||
// QueryEncode turns options into querystring argument
|
||||
func (opt *ListIssueCommentOptions) QueryEncode() string { |
||||
query := opt.getURLQuery() |
||||
if !opt.Since.IsZero() { |
||||
query.Add("since", opt.Since.Format(time.RFC3339)) |
||||
} |
||||
if !opt.Before.IsZero() { |
||||
query.Add("before", opt.Before.Format(time.RFC3339)) |
||||
} |
||||
return query.Encode() |
||||
} |
||||
|
||||
// ListIssueComments list comments on an issue.
|
||||
func (c *Client) ListIssueComments(owner, repo string, index int64, opt ListIssueCommentOptions) ([]*Comment, *Response, error) { |
||||
opt.setDefaults() |
||||
link, _ := url.Parse(fmt.Sprintf("/repos/%s/%s/issues/%d/comments", owner, repo, index)) |
||||
link.RawQuery = opt.QueryEncode() |
||||
comments := make([]*Comment, 0, opt.PageSize) |
||||
resp, err := c.getParsedResponse("GET", link.String(), nil, nil, &comments) |
||||
return comments, resp, err |
||||
} |
||||
|
||||
// ListRepoIssueComments list comments for a given repo.
|
||||
func (c *Client) ListRepoIssueComments(owner, repo string, opt ListIssueCommentOptions) ([]*Comment, *Response, error) { |
||||
opt.setDefaults() |
||||
link, _ := url.Parse(fmt.Sprintf("/repos/%s/%s/issues/comments", owner, repo)) |
||||
link.RawQuery = opt.QueryEncode() |
||||
comments := make([]*Comment, 0, opt.PageSize) |
||||
resp, err := c.getParsedResponse("GET", link.String(), nil, nil, &comments) |
||||
return comments, resp, err |
||||
} |
||||
|
||||
// GetIssueComment get a comment for a given repo by id.
|
||||
func (c *Client) GetIssueComment(owner, repo string, id int64) (*Comment, *Response, error) { |
||||
comment := new(Comment) |
||||
if err := c.CheckServerVersionConstraint(">=1.12.0"); err != nil { |
||||
return comment, nil, err |
||||
} |
||||
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/issues/comments/%d", owner, repo, id), nil, nil, &comment) |
||||
return comment, resp, err |
||||
} |
||||
|
||||
// CreateIssueCommentOption options for creating a comment on an issue
|
||||
type CreateIssueCommentOption struct { |
||||
Body string `json:"body"` |
||||
} |
||||
|
||||
// Validate the CreateIssueCommentOption struct
|
||||
func (opt CreateIssueCommentOption) Validate() error { |
||||
if len(opt.Body) == 0 { |
||||
return fmt.Errorf("body is empty") |
||||
} |
||||
return nil |
||||
} |
||||
|
||||
// CreateIssueComment create comment on an issue.
|
||||
func (c *Client) CreateIssueComment(owner, repo string, index int64, opt CreateIssueCommentOption) (*Comment, *Response, error) { |
||||
if err := opt.Validate(); err != nil { |
||||
return nil, nil, err |
||||
} |
||||
body, err := json.Marshal(&opt) |
||||
if err != nil { |
||||
return nil, nil, err |
||||
} |
||||
comment := new(Comment) |
||||
resp, err := c.getParsedResponse("POST", fmt.Sprintf("/repos/%s/%s/issues/%d/comments", owner, repo, index), jsonHeader, bytes.NewReader(body), comment) |
||||
return comment, resp, err |
||||
} |
||||
|
||||
// EditIssueCommentOption options for editing a comment
|
||||
type EditIssueCommentOption struct { |
||||
Body string `json:"body"` |
||||
} |
||||
|
||||
// Validate the EditIssueCommentOption struct
|
||||
func (opt EditIssueCommentOption) Validate() error { |
||||
if len(opt.Body) == 0 { |
||||
return fmt.Errorf("body is empty") |
||||
} |
||||
return nil |
||||
} |
||||
|
||||
// EditIssueComment edits an issue comment.
|
||||
func (c *Client) EditIssueComment(owner, repo string, commentID int64, opt EditIssueCommentOption) (*Comment, *Response, error) { |
||||
if err := opt.Validate(); err != nil { |
||||
return nil, nil, err |
||||
} |
||||
body, err := json.Marshal(&opt) |
||||
if err != nil { |
||||
return nil, nil, err |
||||
} |
||||
comment := new(Comment) |
||||
resp, err := c.getParsedResponse("PATCH", fmt.Sprintf("/repos/%s/%s/issues/comments/%d", owner, repo, commentID), jsonHeader, bytes.NewReader(body), comment) |
||||
return comment, resp, err |
||||
} |
||||
|
||||
// DeleteIssueComment deletes an issue comment.
|
||||
func (c *Client) DeleteIssueComment(owner, repo string, commentID int64) (*Response, error) { |
||||
_, resp, err := c.getResponse("DELETE", fmt.Sprintf("/repos/%s/%s/issues/comments/%d", owner, repo, commentID), nil, nil) |
||||
return resp, err |
||||
} |
@ -0,0 +1,181 @@ |
||||
// Copyright 2016 The Gogs 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" |
||||
"regexp" |
||||
"strings" |
||||
) |
||||
|
||||
// Label a label to an issue or a pr
|
||||
type Label struct { |
||||
ID int64 `json:"id"` |
||||
Name string `json:"name"` |
||||
// example: 00aabb
|
||||
Color string `json:"color"` |
||||
Description string `json:"description"` |
||||
URL string `json:"url"` |
||||
} |
||||
|
||||
// ListLabelsOptions options for listing repository's labels
|
||||
type ListLabelsOptions struct { |
||||
ListOptions |
||||
} |
||||
|
||||
// ListRepoLabels list labels of one repository
|
||||
func (c *Client) ListRepoLabels(owner, repo string, opt ListLabelsOptions) ([]*Label, *Response, error) { |
||||
opt.setDefaults() |
||||
labels := make([]*Label, 0, opt.PageSize) |
||||
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/labels?%s", owner, repo, opt.getURLQuery().Encode()), nil, nil, &labels) |
||||
return labels, resp, err |
||||
} |
||||
|
||||
// GetRepoLabel get one label of repository by repo it
|
||||
func (c *Client) GetRepoLabel(owner, repo string, id int64) (*Label, *Response, error) { |
||||
label := new(Label) |
||||
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/labels/%d", owner, repo, id), nil, nil, label) |
||||
return label, resp, err |
||||
} |
||||
|
||||
// CreateLabelOption options for creating a label
|
||||
type CreateLabelOption struct { |
||||
Name string `json:"name"` |
||||
// example: #00aabb
|
||||
Color string `json:"color"` |
||||
Description string `json:"description"` |
||||
} |
||||
|
||||
// Validate the CreateLabelOption struct
|
||||
func (opt CreateLabelOption) Validate() error { |
||||
aw, err := regexp.MatchString("^#?[0-9,a-f,A-F]{6}$", opt.Color) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
if !aw { |
||||
return fmt.Errorf("invalid color format") |
||||
} |
||||
if len(strings.TrimSpace(opt.Name)) == 0 { |
||||
return fmt.Errorf("empty name not allowed") |
||||
} |
||||
return nil |
||||
} |
||||
|
||||
// CreateLabel create one label of repository
|
||||
func (c *Client) CreateLabel(owner, repo string, opt CreateLabelOption) (*Label, *Response, error) { |
||||
if err := opt.Validate(); err != nil { |
||||
return nil, nil, err |
||||
} |
||||
if len(opt.Color) == 6 { |
||||
if err := c.CheckServerVersionConstraint(">=1.12.0"); err != nil { |
||||
opt.Color = "#" + opt.Color |
||||
} |
||||
} |
||||
body, err := json.Marshal(&opt) |
||||
if err != nil { |
||||
return nil, nil, err |
||||
} |
||||
label := new(Label) |
||||
resp, err := c.getParsedResponse("POST", |
||||
fmt.Sprintf("/repos/%s/%s/labels", owner, repo), |
||||
jsonHeader, bytes.NewReader(body), label) |
||||
return label, resp, err |
||||
} |
||||
|
||||
// EditLabelOption options for editing a label
|
||||
type EditLabelOption struct { |
||||
Name *string `json:"name"` |
||||
Color *string `json:"color"` |
||||
Description *string `json:"description"` |
||||
} |
||||
|
||||
// Validate the EditLabelOption struct
|
||||
func (opt EditLabelOption) Validate() error { |
||||
if opt.Color != nil { |
||||
aw, err := regexp.MatchString("^#?[0-9,a-f,A-F]{6}$", *opt.Color) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
if !aw { |
||||
return fmt.Errorf("invalid color format") |
||||
} |
||||
} |
||||
if opt.Name != nil { |
||||
if len(strings.TrimSpace(*opt.Name)) == 0 { |
||||
return fmt.Errorf("empty name not allowed") |
||||
} |
||||
} |
||||
return nil |
||||
} |
||||
|
||||
// EditLabel modify one label with options
|
||||
func (c *Client) EditLabel(owner, repo string, id int64, opt EditLabelOption) (*Label, *Response, error) { |
||||
if err := opt.Validate(); err != nil { |
||||
return nil, nil, err |
||||
} |
||||
body, err := json.Marshal(&opt) |
||||
if err != nil { |
||||
return nil, nil, err |
||||
} |
||||
label := new(Label) |
||||
resp, err := c.getParsedResponse("PATCH", fmt.Sprintf("/repos/%s/%s/labels/%d", owner, repo, id), jsonHeader, bytes.NewReader(body), label) |
||||
return label, resp, err |
||||
} |
||||
|
||||
// DeleteLabel delete one label of repository by id
|
||||
func (c *Client) DeleteLabel(owner, repo string, id int64) (*Response, error) { |
||||
_, resp, err := c.getResponse("DELETE", fmt.Sprintf("/repos/%s/%s/labels/%d", owner, repo, id), nil, nil) |
||||
return resp, err |
||||
} |
||||
|
||||
// GetIssueLabels get labels of one issue via issue id
|
||||
func (c *Client) GetIssueLabels(owner, repo string, index int64, opts ListLabelsOptions) ([]*Label, *Response, error) { |
||||
labels := make([]*Label, 0, 5) |
||||
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/issues/%d/labels?%s", owner, repo, index, opts.getURLQuery().Encode()), nil, nil, &labels) |
||||
return labels, resp, err |
||||
} |
||||
|
||||
// IssueLabelsOption a collection of labels
|
||||
type IssueLabelsOption struct { |
||||
// list of label IDs
|
||||
Labels []int64 `json:"labels"` |
||||
} |
||||
|
||||
// AddIssueLabels add one or more labels to one issue
|
||||
func (c *Client) AddIssueLabels(owner, repo string, index int64, opt IssueLabelsOption) ([]*Label, *Response, error) { |
||||
body, err := json.Marshal(&opt) |
||||
if err != nil { |
||||
return nil, nil, err |
||||
} |
||||
var labels []*Label |
||||
resp, err := c.getParsedResponse("POST", fmt.Sprintf("/repos/%s/%s/issues/%d/labels", owner, repo, index), jsonHeader, bytes.NewReader(body), &labels) |
||||
return labels, resp, err |
||||
} |
||||
|
||||
// ReplaceIssueLabels replace old labels of issue with new labels
|
||||
func (c *Client) ReplaceIssueLabels(owner, repo string, index int64, opt IssueLabelsOption) ([]*Label, *Response, error) { |
||||
body, err := json.Marshal(&opt) |
||||
if err != nil { |
||||
return nil, nil, err |
||||
} |
||||
var labels []*Label |
||||
resp, err := c.getParsedResponse("PUT", fmt.Sprintf("/repos/%s/%s/issues/%d/labels", owner, repo, index), jsonHeader, bytes.NewReader(body), &labels) |
||||
return labels, resp, err |
||||
} |
||||
|
||||
// DeleteIssueLabel delete one label of one issue by issue id and label id
|
||||
// TODO: maybe we need delete by label name and issue id
|
||||
func (c *Client) DeleteIssueLabel(owner, repo string, index, label int64) (*Response, error) { |
||||
_, resp, err := c.getResponse("DELETE", fmt.Sprintf("/repos/%s/%s/issues/%d/labels/%d", owner, repo, index, label), nil, nil) |
||||
return resp, err |
||||
} |
||||
|
||||
// ClearIssueLabels delete all the labels of one issue.
|
||||
func (c *Client) ClearIssueLabels(owner, repo string, index int64) (*Response, error) { |
||||
_, resp, err := c.getResponse("DELETE", fmt.Sprintf("/repos/%s/%s/issues/%d/labels", owner, repo, index), nil, nil) |
||||
return resp, err |
||||
} |
@ -0,0 +1,213 @@ |
||||
// Copyright 2016 The Gogs 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" |
||||
"net/url" |
||||
"strings" |
||||
"time" |
||||
) |
||||
|
||||
// Milestone milestone is a collection of issues on one repository
|
||||
type Milestone struct { |
||||
ID int64 `json:"id"` |
||||
Title string `json:"title"` |
||||
Description string `json:"description"` |
||||
State StateType `json:"state"` |
||||
OpenIssues int `json:"open_issues"` |
||||
ClosedIssues int `json:"closed_issues"` |
||||
Created time.Time `json:"created_at"` |
||||
Updated *time.Time `json:"updated_at"` |
||||
Closed *time.Time `json:"closed_at"` |
||||
Deadline *time.Time `json:"due_on"` |
||||
} |
||||
|
||||
// ListMilestoneOption list milestone options
|
||||
type ListMilestoneOption struct { |
||||
ListOptions |
||||
// open, closed, all
|
||||
State StateType |
||||
Name string |
||||
} |
||||
|
||||
// QueryEncode turns options into querystring argument
|
||||
func (opt *ListMilestoneOption) QueryEncode() string { |
||||
query := opt.getURLQuery() |
||||
if opt.State != "" { |
||||
query.Add("state", string(opt.State)) |
||||
} |
||||
if len(opt.Name) != 0 { |
||||
query.Add("name", opt.Name) |
||||
} |
||||
return query.Encode() |
||||
} |
||||
|
||||
// ListRepoMilestones list all the milestones of one repository
|
||||
func (c *Client) ListRepoMilestones(owner, repo string, opt ListMilestoneOption) ([]*Milestone, *Response, error) { |
||||
opt.setDefaults() |
||||
milestones := make([]*Milestone, 0, opt.PageSize) |
||||
|
||||
link, _ := url.Parse(fmt.Sprintf("/repos/%s/%s/milestones", owner, repo)) |
||||
link.RawQuery = opt.QueryEncode() |
||||
resp, err := c.getParsedResponse("GET", link.String(), nil, nil, &milestones) |
||||
return milestones, resp, err |
||||
} |
||||
|
||||
// GetMilestone get one milestone by repo name and milestone id
|
||||
func (c *Client) GetMilestone(owner, repo string, id int64) (*Milestone, *Response, error) { |
||||
milestone := new(Milestone) |
||||
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/milestones/%d", owner, repo, id), nil, nil, milestone) |
||||
return milestone, resp, err |
||||
} |
||||
|
||||
// GetMilestoneByName get one milestone by repo and milestone name
|
||||
func (c *Client) GetMilestoneByName(owner, repo string, name string) (*Milestone, *Response, error) { |
||||
if c.CheckServerVersionConstraint(">=1.13") != nil { |
||||
// backwards compatibility mode
|
||||
m, resp, err := c.resolveMilestoneByName(owner, repo, name) |
||||
return m, resp, err |
||||
} |
||||
milestone := new(Milestone) |
||||
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/milestones/%s", owner, repo, name), nil, nil, milestone) |
||||
return milestone, resp, err |
||||
} |
||||
|
||||
// CreateMilestoneOption options for creating a milestone
|
||||
type CreateMilestoneOption struct { |
||||
Title string `json:"title"` |
||||
Description string `json:"description"` |
||||
State StateType `json:"state"` |
||||
Deadline *time.Time `json:"due_on"` |
||||
} |
||||
|
||||
// Validate the CreateMilestoneOption struct
|
||||
func (opt CreateMilestoneOption) Validate() error { |
||||
if len(strings.TrimSpace(opt.Title)) == 0 { |
||||
return fmt.Errorf("title is empty") |
||||
} |
||||
return nil |
||||
} |
||||
|
||||
// CreateMilestone create one milestone with options
|
||||
func (c *Client) CreateMilestone(owner, repo string, opt CreateMilestoneOption) (*Milestone, *Response, error) { |
||||
if err := opt.Validate(); err != nil { |
||||
return nil, nil, err |
||||
} |
||||
body, err := json.Marshal(&opt) |
||||
if err != nil { |
||||
return nil, nil, err |
||||
} |
||||
milestone := new(Milestone) |
||||
resp, err := c.getParsedResponse("POST", fmt.Sprintf("/repos/%s/%s/milestones", owner, repo), jsonHeader, bytes.NewReader(body), milestone) |
||||
|
||||
// make creating closed milestones need gitea >= v1.13.0
|
||||
// this make it backwards compatible
|
||||
if err == nil && opt.State == StateClosed && milestone.State != StateClosed { |
||||
closed := StateClosed |
||||
return c.EditMilestone(owner, repo, milestone.ID, EditMilestoneOption{ |
||||
State: &closed, |
||||
}) |
||||
} |
||||
|
||||
return milestone, resp, err |
||||
} |
||||
|
||||
// EditMilestoneOption options for editing a milestone
|
||||
type EditMilestoneOption struct { |
||||
Title string `json:"title"` |
||||
Description *string `json:"description"` |
||||
State *StateType `json:"state"` |
||||
Deadline *time.Time `json:"due_on"` |
||||
} |
||||
|
||||
// Validate the EditMilestoneOption struct
|
||||
func (opt EditMilestoneOption) Validate() error { |
||||
if len(opt.Title) != 0 && len(strings.TrimSpace(opt.Title)) == 0 { |
||||
return fmt.Errorf("title is empty") |
||||
} |
||||
return nil |
||||
} |
||||
|
||||
// EditMilestone modify milestone with options
|
||||
func (c *Client) EditMilestone(owner, repo string, id int64, opt EditMilestoneOption) (*Milestone, *Response, error) { |
||||
if err := opt.Validate(); err != nil { |
||||
return nil, nil, err |
||||
} |
||||
body, err := json.Marshal(&opt) |
||||
if err != nil { |
||||
return nil, nil, err |
||||
} |
||||
milestone := new(Milestone) |
||||
resp, err := c.getParsedResponse("PATCH", fmt.Sprintf("/repos/%s/%s/milestones/%d", owner, repo, id), jsonHeader, bytes.NewReader(body), milestone) |
||||
return milestone, resp, err |
||||
} |
||||
|
||||
// EditMilestoneByName modify milestone with options
|
||||
func (c *Client) EditMilestoneByName(owner, repo string, name string, opt EditMilestoneOption) (*Milestone, *Response, error) { |
||||
if c.CheckServerVersionConstraint(">=1.13") != nil { |
||||
// backwards compatibility mode
|
||||
m, _, err := c.resolveMilestoneByName(owner, repo, name) |
||||
if err != nil { |
||||
return nil, nil, err |
||||
} |
||||
return c.EditMilestone(owner, repo, m.ID, opt) |
||||
} |
||||
if err := opt.Validate(); err != nil { |
||||
return nil, nil, err |
||||
} |
||||
body, err := json.Marshal(&opt) |
||||
if err != nil { |
||||
return nil, nil, err |
||||
} |
||||
milestone := new(Milestone) |
||||
resp, err := c.getParsedResponse("PATCH", fmt.Sprintf("/repos/%s/%s/milestones/%s", owner, repo, name), jsonHeader, bytes.NewReader(body), milestone) |
||||
return milestone, resp, err |
||||
} |
||||
|
||||
// DeleteMilestone delete one milestone by id
|
||||
func (c *Client) DeleteMilestone(owner, repo string, id int64) (*Response, error) { |
||||
_, resp, err := c.getResponse("DELETE", fmt.Sprintf("/repos/%s/%s/milestones/%d", owner, repo, id), nil, nil) |
||||
return resp, err |
||||
} |
||||
|
||||
// DeleteMilestoneByName delete one milestone by name
|
||||
func (c *Client) DeleteMilestoneByName(owner, repo string, name string) (*Response, error) { |
||||
if c.CheckServerVersionConstraint(">=1.13") != nil { |
||||
// backwards compatibility mode
|
||||
m, _, err := c.resolveMilestoneByName(owner, repo, name) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
return c.DeleteMilestone(owner, repo, m.ID) |
||||
} |
||||
_, resp, err := c.getResponse("DELETE", fmt.Sprintf("/repos/%s/%s/milestones/%s", owner, repo, name), nil, nil) |
||||
return resp, err |
||||
} |
||||
|
||||
// resolveMilestoneByName is a fallback method to find milestone id by name
|
||||
func (c *Client) resolveMilestoneByName(owner, repo, name string) (*Milestone, *Response, error) { |
||||
for i := 1; ; i++ { |
||||
miles, resp, err := c.ListRepoMilestones(owner, repo, ListMilestoneOption{ |
||||
ListOptions: ListOptions{ |
||||
Page: i, |
||||
}, |
||||
State: "all", |
||||
}) |
||||
if err != nil { |
||||
return nil, nil, err |
||||
} |
||||
if len(miles) == 0 { |
||||
return nil, nil, fmt.Errorf("milestone '%s' do not exist", name) |
||||
} |
||||
for _, m := range miles { |
||||
if strings.ToLower(strings.TrimSpace(m.Title)) == strings.ToLower(strings.TrimSpace(name)) { |
||||
return m, resp, nil |
||||
} |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,104 @@ |
||||
// 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" |
||||
"time" |
||||
) |
||||
|
||||
// Reaction contain one reaction
|
||||
type Reaction struct { |
||||
User *User `json:"user"` |
||||
Reaction string `json:"content"` |
||||
Created time.Time `json:"created_at"` |
||||
} |
||||
|
||||
// GetIssueReactions get a list reactions of an issue
|
||||
func (c *Client) GetIssueReactions(owner, repo string, index int64) ([]*Reaction, *Response, error) { |
||||
if err := c.CheckServerVersionConstraint(">=1.11.0"); err != nil { |
||||
return nil, nil, err |
||||
} |
||||
reactions := make([]*Reaction, 0, 10) |
||||
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/issues/%d/reactions", owner, repo, index), nil, nil, &reactions) |
||||
return reactions, resp, err |
||||
} |
||||
|
||||
// GetIssueCommentReactions get a list of reactions from a comment of an issue
|
||||
func (c *Client) GetIssueCommentReactions(owner, repo string, commentID int64) ([]*Reaction, *Response, error) { |
||||
if err := c.CheckServerVersionConstraint(">=1.11.0"); err != nil { |
||||
return nil, nil, err |
||||
} |
||||
reactions := make([]*Reaction, 0, 10) |
||||
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/issues/comments/%d/reactions", owner, repo, commentID), nil, nil, &reactions) |
||||
return reactions, resp, err |
||||
} |
||||
|
||||
// editReactionOption contain the reaction type
|
||||
type editReactionOption struct { |
||||
Reaction string `json:"content"` |
||||
} |
||||
|
||||
// PostIssueReaction add a reaction to an issue
|
||||
func (c *Client) PostIssueReaction(owner, repo string, index int64, reaction string) (*Reaction, *Response, error) { |
||||
if err := c.CheckServerVersionConstraint(">=1.11.0"); err != nil { |
||||
return nil, nil, err |
||||
} |
||||
reactionResponse := new(Reaction) |
||||
body, err := json.Marshal(&editReactionOption{Reaction: reaction}) |
||||
if err != nil { |
||||
return nil, nil, err |
||||
} |
||||
resp, err := c.getParsedResponse("POST", |
||||
fmt.Sprintf("/repos/%s/%s/issues/%d/reactions", owner, repo, index), |
||||
jsonHeader, bytes.NewReader(body), reactionResponse) |
||||
return reactionResponse, resp, err |
||||
} |
||||
|
||||
// DeleteIssueReaction remove a reaction from an issue
|
||||
func (c *Client) DeleteIssueReaction(owner, repo string, index int64, reaction string) (*Response, error) { |
||||
if err := c.CheckServerVersionConstraint(">=1.11.0"); err != nil { |
||||
return nil, err |
||||
} |
||||
body, err := json.Marshal(&editReactionOption{Reaction: reaction}) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
_, resp, err := c.getResponse("DELETE", fmt.Sprintf("/repos/%s/%s/issues/%d/reactions", owner, repo, index), jsonHeader, bytes.NewReader(body)) |
||||
return resp, err |
||||
} |
||||
|
||||
// PostIssueCommentReaction add a reaction to a comment of an issue
|
||||
func (c *Client) PostIssueCommentReaction(owner, repo string, commentID int64, reaction string) (*Reaction, *Response, error) { |
||||
if err := c.CheckServerVersionConstraint(">=1.11.0"); err != nil { |
||||
return nil, nil, err |
||||
} |
||||
reactionResponse := new(Reaction) |
||||
body, err := json.Marshal(&editReactionOption{Reaction: reaction}) |
||||
if err != nil { |
||||
return nil, nil, err |
||||
} |
||||
resp, err := c.getParsedResponse("POST", |
||||
fmt.Sprintf("/repos/%s/%s/issues/comments/%d/reactions", owner, repo, commentID), |
||||
jsonHeader, bytes.NewReader(body), reactionResponse) |
||||
return reactionResponse, resp, err |
||||
} |
||||
|
||||
// DeleteIssueCommentReaction remove a reaction from a comment of an issue
|
||||
func (c *Client) DeleteIssueCommentReaction(owner, repo string, commentID int64, reaction string) (*Response, error) { |
||||
if err := c.CheckServerVersionConstraint(">=1.11.0"); err != nil { |
||||
return nil, err |
||||
} |
||||
body, err := json.Marshal(&editReactionOption{Reaction: reaction}) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
_, resp, err := c.getResponse("DELETE", |
||||
fmt.Sprintf("/repos/%s/%s/issues/comments/%d/reactions", owner, repo, commentID), |
||||
jsonHeader, bytes.NewReader(body)) |
||||
return resp, err |
||||
} |
@ -0,0 +1,43 @@ |
||||
// 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 ( |
||||
"fmt" |
||||
"time" |
||||
) |
||||
|
||||
// StopWatch represents a running stopwatch of an issue / pr
|
||||
type StopWatch struct { |
||||
Created time.Time `json:"created"` |
||||
IssueIndex int64 `json:"issue_index"` |
||||
} |
||||
|
||||
// GetMyStopwatches list all stopwatches
|
||||
func (c *Client) GetMyStopwatches() ([]*StopWatch, *Response, error) { |
||||
stopwatches := make([]*StopWatch, 0, 1) |
||||
resp, err := c.getParsedResponse("GET", "/user/stopwatches", nil, nil, &stopwatches) |
||||
return stopwatches, resp, err |
||||
} |
||||
|
||||
// DeleteIssueStopwatch delete / cancel a specific stopwatch
|
||||
func (c *Client) DeleteIssueStopwatch(owner, repo string, index int64) (*Response, error) { |
||||
_, resp, err := c.getResponse("DELETE", fmt.Sprintf("/repos/%s/%s/issues/%d/stopwatch/delete", owner, repo, index), nil, nil) |
||||
return resp, err |
||||
} |
||||
|
||||
// StartIssueStopWatch starts a stopwatch for an existing issue for a given
|
||||
// repository
|
||||
func (c *Client) StartIssueStopWatch(owner, repo string, index int64) (*Response, error) { |
||||
_, resp, err := c.getResponse("POST", fmt.Sprintf("/repos/%s/%s/issues/%d/stopwatch/start", owner, repo, index), nil, nil) |
||||
return resp, err |
||||
} |
||||
|
||||
// StopIssueStopWatch stops an existing stopwatch for an issue in a given
|
||||
// repository
|
||||
func (c *Client) StopIssueStopWatch(owner, repo string, index int64) (*Response, error) { |
||||
_, resp, err := c.getResponse("POST", fmt.Sprintf("/repos/%s/%s/issues/%d/stopwatch/stop", owner, repo, index), nil, nil) |
||||
return resp, err |
||||
} |
@ -0,0 +1,84 @@ |
||||
// 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 ( |
||||
"fmt" |
||||
"net/http" |
||||
) |
||||
|
||||
// GetIssueSubscribers get list of users who subscribed on an issue
|
||||
func (c *Client) GetIssueSubscribers(owner, repo string, index int64) ([]*User, *Response, error) { |
||||
if err := c.CheckServerVersionConstraint(">=1.11.0"); err != nil { |
||||
return nil, nil, err |
||||
} |
||||
subscribers := make([]*User, 0, 10) |
||||
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/issues/%d/subscriptions", owner, repo, index), nil, nil, &subscribers) |
||||
return subscribers, resp, err |
||||
} |
||||
|
||||
// AddIssueSubscription Subscribe user to issue
|
||||
func (c *Client) AddIssueSubscription(owner, repo string, index int64, user string) (*Response, error) { |
||||
if err := c.CheckServerVersionConstraint(">=1.11.0"); err != nil { |
||||
return nil, err |
||||
} |
||||
status, resp, err := c.getStatusCode("PUT", fmt.Sprintf("/repos/%s/%s/issues/%d/subscriptions/%s", owner, repo, index, user), nil, nil) |
||||
if err != nil { |
||||
return resp, err |
||||
} |
||||
if status == http.StatusCreated { |
||||
return resp, nil |
||||
} |
||||
if status == http.StatusOK { |
||||
return resp, fmt.Errorf("already subscribed") |
||||
} |
||||
return resp, fmt.Errorf("unexpected Status: %d", status) |
||||
} |
||||
|
||||
// DeleteIssueSubscription unsubscribe user from issue
|
||||
func (c *Client) DeleteIssueSubscription(owner, repo string, index int64, user string) (*Response, error) { |
||||
if err := c.CheckServerVersionConstraint(">=1.11.0"); err != nil { |
||||
return nil, err |
||||
} |
||||
status, resp, err := c.getStatusCode("DELETE", fmt.Sprintf("/repos/%s/%s/issues/%d/subscriptions/%s", owner, repo, index, user), nil, nil) |
||||
if err != nil { |
||||
return resp, err |
||||
} |
||||
if status == http.StatusCreated { |
||||
return resp, nil |
||||
} |
||||
if status == http.StatusOK { |
||||
return resp, fmt.Errorf("already unsubscribed") |
||||
} |
||||
return resp, fmt.Errorf("unexpected Status: %d", status) |
||||
} |
||||
|
||||
// CheckIssueSubscription check if current user is subscribed to an issue
|
||||
func (c *Client) CheckIssueSubscription(owner, repo string, index int64) (*WatchInfo, *Response, error) { |
||||
if err := c.CheckServerVersionConstraint(">=1.12.0"); err != nil { |
||||
return nil, nil, err |
||||
} |
||||
wi := new(WatchInfo) |
||||
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/issues/%d/subscriptions/check", owner, repo, index), nil, nil, wi) |
||||
return wi, resp, err |
||||
} |
||||
|
||||
// IssueSubscribe subscribe current user to an issue
|
||||
func (c *Client) IssueSubscribe(owner, repo string, index int64) (*Response, error) { |
||||
u, _, err := c.GetMyUserInfo() |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
return c.AddIssueSubscription(owner, repo, index, u.UserName) |
||||
} |
||||
|
||||
// IssueUnSubscribe unsubscribe current user from an issue
|
||||
func (c *Client) IssueUnSubscribe(owner, repo string, index int64) (*Response, error) { |
||||
u, _, err := c.GetMyUserInfo() |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
return c.DeleteIssueSubscription(owner, repo, index, u.UserName) |
||||
} |
@ -0,0 +1,127 @@ |
||||
// Copyright 2017 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" |
||||
"time" |
||||
) |
||||
|
||||
// TrackedTime worked time for an issue / pr
|
||||
type TrackedTime struct { |
||||
ID int64 `json:"id"` |
||||
Created time.Time `json:"created"` |
||||
// Time in seconds
|
||||
Time int64 `json:"time"` |
||||
// deprecated (only for backwards compatibility)
|
||||
UserID int64 `json:"user_id"` |
||||
UserName string `json:"user_name"` |
||||
// deprecated (only for backwards compatibility)
|
||||
IssueID int64 `json:"issue_id"` |
||||
Issue *Issue `json:"issue"` |
||||
} |
||||
|
||||
// GetUserTrackedTimes list tracked times of a user
|
||||
func (c *Client) GetUserTrackedTimes(owner, repo, user string) ([]*TrackedTime, *Response, error) { |
||||
if err := c.CheckServerVersionConstraint(">=1.11.0"); err != nil { |
||||
return nil, nil, err |
||||
} |
||||
times := make([]*TrackedTime, 0, 10) |
||||
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/times/%s", owner, repo, user), nil, nil, ×) |
||||
return times, resp, err |
||||
} |
||||
|
||||
// GetRepoTrackedTimes list tracked times of a repository
|
||||
func (c *Client) GetRepoTrackedTimes(owner, repo string) ([]*TrackedTime, *Response, error) { |
||||
if err := c.CheckServerVersionConstraint(">=1.11.0"); err != nil { |
||||
return nil, nil, err |
||||
} |
||||
times := make([]*TrackedTime, 0, 10) |
||||
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/times", owner, repo), nil, nil, ×) |
||||
return times, resp, err |
||||
} |
||||
|
||||
// GetMyTrackedTimes list tracked times of the current user
|
||||
func (c *Client) GetMyTrackedTimes() ([]*TrackedTime, *Response, error) { |
||||
if err := c.CheckServerVersionConstraint(">=1.11.0"); err != nil { |
||||
return nil, nil, err |
||||
} |
||||
times := make([]*TrackedTime, 0, 10) |
||||
resp, err := c.getParsedResponse("GET", "/user/times", nil, nil, ×) |
||||
return times, resp, err |
||||
} |
||||
|
||||
// AddTimeOption options for adding time to an issue
|
||||
type AddTimeOption struct { |
||||
// time in seconds
|
||||
Time int64 `json:"time"` |
||||
// optional
|
||||
Created time.Time `json:"created"` |
||||
// optional
|
||||
User string `json:"user_name"` |
||||
} |
||||
|
||||
// Validate the AddTimeOption struct
|
||||
func (opt AddTimeOption) Validate() error { |
||||
if opt.Time == 0 { |
||||
return fmt.Errorf("no time to add") |
||||
} |
||||
return nil |
||||
} |
||||
|
||||
// AddTime adds time to issue with the given index
|
||||
func (c *Client) AddTime(owner, repo string, index int64, opt AddTimeOption) (*TrackedTime, *Response, error) { |
||||
if err := c.CheckServerVersionConstraint(">=1.11.0"); err != nil { |
||||
return nil, nil, err |
||||
} |
||||
if err := opt.Validate(); err != nil { |
||||
return nil, nil, err |
||||
} |
||||
body, err := json.Marshal(&opt) |
||||
if err != nil { |
||||
return nil, nil, err |
||||
} |
||||
t := new(TrackedTime) |
||||
resp, err := c.getParsedResponse("POST", |
||||
fmt.Sprintf("/repos/%s/%s/issues/%d/times", owner, repo, index), |
||||
jsonHeader, bytes.NewReader(body), t) |
||||
return t, resp, err |
||||
} |
||||
|
||||
// ListTrackedTimesOptions options for listing repository's tracked times
|
||||
type ListTrackedTimesOptions struct { |
||||
ListOptions |
||||
} |
||||
|
||||
// ListTrackedTimes list tracked times of a single issue for a given repository
|
||||
func (c *Client) ListTrackedTimes(owner, repo string, index int64, opt ListTrackedTimesOptions) ([]*TrackedTime, *Response, error) { |
||||
if err := c.CheckServerVersionConstraint(">=1.11.0"); err != nil { |
||||
return nil, nil, err |
||||
} |
||||
opt.setDefaults() |
||||
times := make([]*TrackedTime, 0, opt.PageSize) |
||||
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/issues/%d/times?%s", owner, repo, index, opt.getURLQuery().Encode()), nil, nil, ×) |
||||
return times, resp, err |
||||
} |
||||
|
||||
// ResetIssueTime reset tracked time of a single issue for a given repository
|
||||
func (c *Client) ResetIssueTime(owner, repo string, index int64) (*Response, error) { |
||||
if err := c.CheckServerVersionConstraint(">=1.11.0"); err != nil { |
||||
return nil, err |
||||
} |
||||
_, resp, err := c.getResponse("DELETE", fmt.Sprintf("/repos/%s/%s/issues/%d/times", owner, repo, index), nil, nil) |
||||
return resp, err |
||||
} |
||||
|
||||
// DeleteTime delete a specific tracked time by id of a single issue for a given repository
|
||||
func (c *Client) DeleteTime(owner, repo string, index, timeID int64) (*Response, error) { |
||||
if err := c.CheckServerVersionConstraint(">=1.11.0"); err != nil { |
||||
return nil, err |
||||
} |
||||
_, resp, err := c.getResponse("DELETE", fmt.Sprintf("/repos/%s/%s/issues/%d/times/%d", owner, repo, index, timeID), nil, nil) |
||||
return resp, err |
||||
} |
@ -0,0 +1,37 @@ |
||||
// 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 ( |
||||
"fmt" |
||||
"net/url" |
||||
) |
||||
|
||||
const defaultPageSize = 10 |
||||
const maxPageSize = 50 |
||||
|
||||
// ListOptions options for using Gitea's API pagination
|
||||
type ListOptions struct { |
||||
Page int |
||||
PageSize int |
||||
} |
||||
|
||||
func (o ListOptions) getURLQuery() url.Values { |
||||
query := make(url.Values) |
||||
query.Add("page", fmt.Sprintf("%d", o.Page)) |
||||
query.Add("limit", fmt.Sprintf("%d", o.PageSize)) |
||||
|
||||
return query |
||||
} |
||||
|
||||
func (o ListOptions) setDefaults() { |
||||
if o.Page < 1 { |
||||
o.Page = 1 |
||||
} |
||||
|
||||
if o.PageSize < 0 || o.PageSize > maxPageSize { |
||||
o.PageSize = defaultPageSize |
||||
} |
||||
} |
@ -0,0 +1,199 @@ |
||||
// 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 ( |
||||
"fmt" |
||||
"net/url" |
||||
"time" |
||||
) |
||||
|
||||
// NotificationThread expose Notification on API
|
||||
type NotificationThread struct { |
||||
ID int64 `json:"id"` |
||||
Repository *Repository `json:"repository"` |
||||
Subject *NotificationSubject `json:"subject"` |
||||
Unread bool `json:"unread"` |
||||
Pinned bool `json:"pinned"` |
||||
UpdatedAt time.Time `json:"updated_at"` |
||||
URL string `json:"url"` |
||||
} |
||||
|
||||
// NotificationSubject contains the notification subject (Issue/Pull/Commit)
|
||||
type NotificationSubject struct { |
||||
Title string `json:"title"` |
||||
URL string `json:"url"` |
||||
LatestCommentURL string `json:"latest_comment_url"` |
||||
Type string `json:"type"` |
||||
State StateType `json:"state"` |
||||
} |
||||
|
||||
// NotifyStatus notification status type
|
||||
type NotifyStatus string |
||||
|
||||
const ( |
||||
// NotifyStatusUnread was not read
|
||||
NotifyStatusUnread NotifyStatus = "unread" |
||||
// NotifyStatusRead was already read by user
|
||||
NotifyStatusRead NotifyStatus = "read" |
||||
// NotifyStatusPinned notification is pinned by user
|
||||
NotifyStatusPinned NotifyStatus = "pinned" |
||||
) |
||||
|
||||
// ListNotificationOptions represents the filter options
|
||||
type ListNotificationOptions struct { |
||||
ListOptions |
||||
Since time.Time |
||||
Before time.Time |
||||
Status []NotifyStatus |
||||
} |
||||
|
||||
// MarkNotificationOptions represents the filter & modify options
|
||||
type MarkNotificationOptions struct { |
||||
LastReadAt time.Time |
||||
Status []NotifyStatus |
||||
ToStatus NotifyStatus |
||||
} |
||||
|
||||
// QueryEncode encode options to url query
|
||||
func (opt *ListNotificationOptions) QueryEncode() string { |
||||
query := opt.getURLQuery() |
||||
if !opt.Since.IsZero() { |
||||
query.Add("since", opt.Since.Format(time.RFC3339)) |
||||
} |
||||
if !opt.Before.IsZero() { |
||||
query.Add("before", opt.Before.Format(time.RFC3339)) |
||||
} |
||||
for _, s := range opt.Status { |
||||
query.Add("status-types", string(s)) |
||||
} |
||||
return query.Encode() |
||||
} |
||||
|
||||
// Validate the CreateUserOption struct
|
||||
func (opt ListNotificationOptions) Validate(c *Client) error { |
||||
if len(opt.Status) != 0 { |
||||
return c.CheckServerVersionConstraint(">=1.12.3") |
||||
} |
||||
return nil |
||||
} |
||||
|
||||
// QueryEncode encode options to url query
|
||||
func (opt *MarkNotificationOptions) QueryEncode() string { |
||||
query := make(url.Values) |
||||
if !opt.LastReadAt.IsZero() { |
||||
query.Add("last_read_at", opt.LastReadAt.Format(time.RFC3339)) |
||||
} |
||||
for _, s := range opt.Status { |
||||
query.Add("status-types", string(s)) |
||||
} |
||||
if len(opt.ToStatus) != 0 { |
||||
query.Add("to-status", string(opt.ToStatus)) |
||||
} |
||||
return query.Encode() |
||||
} |
||||
|
||||
// Validate the CreateUserOption struct
|
||||
func (opt MarkNotificationOptions) Validate(c *Client) error { |
||||
if len(opt.Status) != 0 || len(opt.ToStatus) != 0 { |
||||
return c.CheckServerVersionConstraint(">=1.12.3") |
||||
} |
||||
return nil |
||||
} |
||||
|
||||
// CheckNotifications list users's notification threads
|
||||
func (c *Client) CheckNotifications() (int64, *Response, error) { |
||||
if err := c.CheckServerVersionConstraint(">=1.12.0"); err != nil { |
||||
return 0, nil, err |
||||
} |
||||
new := struct { |
||||
New int64 `json:"new"` |
||||
}{} |
||||
|
||||
resp, err := c.getParsedResponse("GET", "/notifications/new", jsonHeader, nil, &new) |
||||
return new.New, resp, err |
||||
} |
||||
|
||||
// GetNotification get notification thread by ID
|
||||
func (c *Client) GetNotification(id int64) (*NotificationThread, *Response, error) { |
||||
if err := c.CheckServerVersionConstraint(">=1.12.0"); err != nil { |
||||
return nil, nil, err |
||||
} |
||||
thread := new(NotificationThread) |
||||
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/notifications/threads/%d", id), nil, nil, thread) |
||||
return thread, resp, err |
||||
} |
||||
|
||||
// ReadNotification mark notification thread as read by ID
|
||||
// It optionally takes a second argument if status has to be set other than 'read'
|
||||
func (c *Client) ReadNotification(id int64, status ...NotifyStatus) (*Response, error) { |
||||
if err := c.CheckServerVersionConstraint(">=1.12.0"); err != nil { |
||||
return nil, err |
||||
} |
||||
link := fmt.Sprintf("/notifications/threads/%d", id) |
||||
if len(status) != 0 { |
||||
link += fmt.Sprintf("?to-status=%s", status[0]) |
||||
} |
||||
_, resp, err := c.getResponse("PATCH", link, nil, nil) |
||||
return resp, err |
||||
} |
||||
|
||||
// ListNotifications list users's notification threads
|
||||
func (c *Client) ListNotifications(opt ListNotificationOptions) ([]*NotificationThread, *Response, error) { |
||||
if err := c.CheckServerVersionConstraint(">=1.12.0"); err != nil { |
||||
return nil, nil, err |
||||
} |
||||
if err := opt.Validate(c); err != nil { |
||||
return nil, nil, err |
||||
} |
||||
link, _ := url.Parse("/notifications") |
||||
link.RawQuery = opt.QueryEncode() |
||||
threads := make([]*NotificationThread, 0, 10) |
||||
resp, err := c.getParsedResponse("GET", link.String(), nil, nil, &threads) |
||||
return threads, resp, err |
||||
} |
||||
|
||||
// ReadNotifications mark notification threads as read
|
||||
func (c *Client) ReadNotifications(opt MarkNotificationOptions) (*Response, error) { |
||||
if err := c.CheckServerVersionConstraint(">=1.12.0"); err != nil { |
||||
return nil, err |
||||
} |
||||
if err := opt.Validate(c); err != nil { |
||||
return nil, err |
||||
} |
||||
link, _ := url.Parse("/notifications") |
||||
link.RawQuery = opt.QueryEncode() |
||||
_, resp, err := c.getResponse("PUT", link.String(), nil, nil) |
||||
return resp, err |
||||
} |
||||
|
||||
// ListRepoNotifications list users's notification threads on a specific repo
|
||||
func (c *Client) ListRepoNotifications(owner, reponame string, opt ListNotificationOptions) ([]*NotificationThread, *Response, error) { |
||||
if err := c.CheckServerVersionConstraint(">=1.12.0"); err != nil { |
||||
return nil, nil, err |
||||
} |
||||
if err := opt.Validate(c); err != nil { |
||||
return nil, nil, err |
||||
} |
||||
link, _ := url.Parse(fmt.Sprintf("/repos/%s/%s/notifications", owner, reponame)) |
||||
link.RawQuery = opt.QueryEncode() |
||||
threads := make([]*NotificationThread, 0, 10) |
||||
resp, err := c.getParsedResponse("GET", link.String(), nil, nil, &threads) |
||||
return threads, resp, err |
||||
} |
||||
|
||||
// ReadRepoNotifications mark notification threads as read on a specific repo
|
||||
func (c *Client) ReadRepoNotifications(owner, reponame string, opt MarkNotificationOptions) (*Response, error) { |
||||
if err := c.CheckServerVersionConstraint(">=1.12.0"); err != nil { |
||||
return nil, err |
||||
} |
||||
if err := opt.Validate(c); err != nil { |
||||
return nil, err |
||||
} |
||||
link, _ := url.Parse(fmt.Sprintf("/repos/%s/%s/notifications", owner, reponame)) |
||||
link.RawQuery = opt.QueryEncode() |
||||
_, resp, err := c.getResponse("PUT", link.String(), nil, nil) |
||||
return resp, err |
||||
} |
@ -0,0 +1,91 @@ |
||||
// 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" |
||||
"time" |
||||
) |
||||
|
||||
// Oauth2 represents an Oauth2 Application
|
||||
type Oauth2 struct { |
||||
ID int64 `json:"id"` |
||||
Name string `json:"name"` |
||||
ClientID string `json:"client_id"` |
||||
ClientSecret string `json:"client_secret"` |
||||
RedirectURIs []string `json:"redirect_uris"` |
||||
Created time.Time `json:"created"` |
||||
} |
||||
|
||||
// ListOauth2Option for listing Oauth2 Applications
|
||||
type ListOauth2Option struct { |
||||
ListOptions |
||||
} |
||||
|
||||
// CreateOauth2Option required options for creating an Application
|
||||
type CreateOauth2Option struct { |
||||
Name string `json:"name"` |
||||
RedirectURIs []string `json:"redirect_uris"` |
||||
} |
||||
|
||||
// CreateOauth2 create an Oauth2 Application and returns a completed Oauth2 object.
|
||||
func (c *Client) CreateOauth2(opt CreateOauth2Option) (*Oauth2, *Response, error) { |
||||
if err := c.CheckServerVersionConstraint(">=1.12.0"); err != nil { |
||||
return nil, nil, err |
||||
} |
||||
body, err := json.Marshal(&opt) |
||||
if err != nil { |
||||
return nil, nil, err |
||||
} |
||||
oauth := new(Oauth2) |
||||
resp, err := c.getParsedResponse("POST", "/user/applications/oauth2", jsonHeader, bytes.NewReader(body), oauth) |
||||
return oauth, resp, err |
||||
} |
||||
|
||||
// UpdateOauth2 a specific Oauth2 Application by ID and return a completed Oauth2 object.
|
||||
func (c *Client) UpdateOauth2(oauth2id int64, opt CreateOauth2Option) (*Oauth2, *Response, error) { |
||||
if err := c.CheckServerVersionConstraint(">=1.12.0"); err != nil { |
||||
return nil, nil, err |
||||
} |
||||
body, err := json.Marshal(&opt) |
||||
if err != nil { |
||||
return nil, nil, err |
||||
} |
||||
oauth := new(Oauth2) |
||||
resp, err := c.getParsedResponse("PATCH", fmt.Sprintf("/user/applications/oauth2/%d", oauth2id), jsonHeader, bytes.NewReader(body), oauth) |
||||
return oauth, resp, err |
||||
} |
||||
|
||||
// GetOauth2 a specific Oauth2 Application by ID.
|
||||
func (c *Client) GetOauth2(oauth2id int64) (*Oauth2, *Response, error) { |
||||
if err := c.CheckServerVersionConstraint(">=1.12.0"); err != nil { |
||||
return nil, nil, err |
||||
} |
||||
oauth2s := &Oauth2{} |
||||
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/user/applications/oauth2/%d", oauth2id), nil, nil, &oauth2s) |
||||
return oauth2s, resp, err |
||||
} |
||||
|
||||
// ListOauth2 all of your Oauth2 Applications.
|
||||
func (c *Client) ListOauth2(opt ListOauth2Option) ([]*Oauth2, *Response, error) { |
||||
if err := c.CheckServerVersionConstraint(">=1.12.0"); err != nil { |
||||
return nil, nil, err |
||||
} |
||||
opt.setDefaults() |
||||
oauth2s := make([]*Oauth2, 0, opt.PageSize) |
||||
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/user/applications/oauth2?%s", opt.getURLQuery().Encode()), nil, nil, &oauth2s) |
||||
return oauth2s, resp, err |
||||
} |
||||
|
||||
// DeleteOauth2 delete an Oauth2 application by ID
|
||||
func (c *Client) DeleteOauth2(oauth2id int64) (*Response, error) { |
||||
if err := c.CheckServerVersionConstraint(">=1.12.0"); err != nil { |
||||
return nil, err |
||||
} |
||||
_, resp, err := c.getResponse("DELETE", fmt.Sprintf("/user/applications/oauth2/%d", oauth2id), nil, nil) |
||||
return resp, err |
||||
} |
@ -0,0 +1,142 @@ |
||||
// Copyright 2015 The Gogs Authors. All rights reserved.
|
||||
// Copyright 2019 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" |
||||
) |
||||
|
||||
// Organization represents an organization
|
||||
type Organization struct { |
||||
ID int64 `json:"id"` |
||||
UserName string `json:"username"` |
||||
FullName string `json:"full_name"` |
||||
AvatarURL string `json:"avatar_url"` |
||||
Description string `json:"description"` |
||||
Website string `json:"website"` |
||||
Location string `json:"location"` |
||||
Visibility string `json:"visibility"` |
||||
} |
||||
|
||||
// VisibleType defines the visibility
|
||||
type VisibleType string |
||||
|
||||
const ( |
||||
// VisibleTypePublic Visible for everyone
|
||||
VisibleTypePublic VisibleType = "public" |
||||
|
||||
// VisibleTypeLimited Visible for every connected user
|
||||
VisibleTypeLimited VisibleType = "limited" |
||||
|
||||
// VisibleTypePrivate Visible only for organization's members
|
||||
VisibleTypePrivate VisibleType = "private" |
||||
) |
||||
|
||||
// ListOrgsOptions options for listing organizations
|
||||
type ListOrgsOptions struct { |
||||
ListOptions |
||||
} |
||||
|
||||
// ListMyOrgs list all of current user's organizations
|
||||
func (c *Client) ListMyOrgs(opt ListOrgsOptions) ([]*Organization, *Response, error) { |
||||
opt.setDefaults() |
||||
orgs := make([]*Organization, 0, opt.PageSize) |
||||
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/user/orgs?%s", opt.getURLQuery().Encode()), nil, nil, &orgs) |
||||
return orgs, resp, err |
||||
} |
||||
|
||||
// ListUserOrgs list all of some user's organizations
|
||||
func (c *Client) ListUserOrgs(user string, opt ListOrgsOptions) ([]*Organization, *Response, error) { |
||||
opt.setDefaults() |
||||
orgs := make([]*Organization, 0, opt.PageSize) |
||||
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/users/%s/orgs?%s", user, opt.getURLQuery().Encode()), nil, nil, &orgs) |
||||
return orgs, resp, err |
||||
} |
||||
|
||||
// GetOrg get one organization by name
|
||||
func (c *Client) GetOrg(orgname string) (*Organization, *Response, error) { |
||||
org := new(Organization) |
||||
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/orgs/%s", orgname), nil, nil, org) |
||||
return org, resp, err |
||||
} |
||||
|
||||
// CreateOrgOption options for creating an organization
|
||||
type CreateOrgOption struct { |
||||
Name string `json:"username"` |
||||
FullName string `json:"full_name"` |
||||
Description string `json:"description"` |
||||
Website string `json:"website"` |
||||
Location string `json:"location"` |
||||
Visibility VisibleType `json:"visibility"` |
||||
} |
||||
|
||||
// checkVisibilityOpt check if mode exist
|
||||
func checkVisibilityOpt(v VisibleType) bool { |
||||
return v == VisibleTypePublic || v == VisibleTypeLimited || v == VisibleTypePrivate |
||||
} |
||||
|
||||
// Validate the CreateOrgOption struct
|
||||
func (opt CreateOrgOption) Validate() error { |
||||
if len(opt.Name) == 0 { |
||||
return fmt.Errorf("empty org name") |
||||
} |
||||
if len(opt.Visibility) != 0 && !checkVisibilityOpt(opt.Visibility) { |
||||
return fmt.Errorf("infalid bisibility option") |
||||
} |
||||
return nil |
||||
} |
||||
|
||||
// CreateOrg creates an organization
|
||||
func (c *Client) CreateOrg(opt CreateOrgOption) (*Organization, *Response, error) { |
||||
if err := opt.Validate(); err != nil { |
||||
return nil, nil, err |
||||
} |
||||
body, err := json.Marshal(&opt) |
||||
if err != nil { |
||||
return nil, nil, err |
||||
} |
||||
org := new(Organization) |
||||
resp, err := c.getParsedResponse("POST", "/orgs", jsonHeader, bytes.NewReader(body), org) |
||||
return org, resp, err |
||||
} |
||||
|
||||
// EditOrgOption options for editing an organization
|
||||
type EditOrgOption struct { |
||||
FullName string `json:"full_name"` |
||||
Description string `json:"description"` |
||||
Website string `json:"website"` |
||||
Location string `json:"location"` |
||||
Visibility VisibleType `json:"visibility"` |
||||
} |
||||
|
||||
// Validate the EditOrgOption struct
|
||||
func (opt EditOrgOption) Validate() error { |
||||
if len(opt.Visibility) != 0 && !checkVisibilityOpt(opt.Visibility) { |
||||
return fmt.Errorf("infalid bisibility option") |
||||
} |
||||
return nil |
||||
} |
||||
|
||||
// EditOrg modify one organization via options
|
||||
func (c *Client) EditOrg(orgname string, opt EditOrgOption) (*Response, error) { |
||||
if err := opt.Validate(); err != nil { |
||||
return nil, err |
||||
} |
||||
body, err := json.Marshal(&opt) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
_, resp, err := c.getResponse("PATCH", fmt.Sprintf("/orgs/%s", orgname), jsonHeader, bytes.NewReader(body)) |
||||
return resp, err |
||||
} |
||||
|
||||
// DeleteOrg deletes an organization
|
||||
func (c *Client) DeleteOrg(orgname string) (*Response, error) { |
||||
_, resp, err := c.getResponse("DELETE", fmt.Sprintf("/orgs/%s", orgname), jsonHeader, nil) |
||||
return resp, err |
||||
} |
@ -0,0 +1,101 @@ |
||||
// 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 ( |
||||
"fmt" |
||||
"net/http" |
||||
"net/url" |
||||
) |
||||
|
||||
// DeleteOrgMembership remove a member from an organization
|
||||
func (c *Client) DeleteOrgMembership(org, user string) (*Response, error) { |
||||
_, resp, err := c.getResponse("DELETE", fmt.Sprintf("/orgs/%s/members/%s", url.PathEscape(org), url.PathEscape(user)), nil, nil) |
||||
return resp, err |
||||
} |
||||
|
||||
// ListOrgMembershipOption list OrgMembership options
|
||||
type ListOrgMembershipOption struct { |
||||
ListOptions |
||||
} |
||||
|
||||
// ListOrgMembership list an organization's members
|
||||
func (c *Client) ListOrgMembership(org string, opt ListOrgMembershipOption) ([]*User, *Response, error) { |
||||
opt.setDefaults() |
||||
users := make([]*User, 0, opt.PageSize) |
||||
|
||||
link, _ := url.Parse(fmt.Sprintf("/orgs/%s/members", url.PathEscape(org))) |
||||
link.RawQuery = opt.getURLQuery().Encode() |
||||
resp, err := c.getParsedResponse("GET", link.String(), jsonHeader, nil, &users) |
||||
return users, resp, err |
||||
} |
||||
|
||||
// ListPublicOrgMembership list an organization's members
|
||||
func (c *Client) ListPublicOrgMembership(org string, opt ListOrgMembershipOption) ([]*User, *Response, error) { |
||||
opt.setDefaults() |
||||
users := make([]*User, 0, opt.PageSize) |
||||
|
||||
link, _ := url.Parse(fmt.Sprintf("/orgs/%s/public_members", url.PathEscape(org))) |
||||
link.RawQuery = opt.getURLQuery().Encode() |
||||
resp, err := c.getParsedResponse("GET", link.String(), jsonHeader, nil, &users) |
||||
return users, resp, err |
||||
} |
||||
|
||||
// CheckOrgMembership Check if a user is a member of an organization
|
||||
func (c *Client) CheckOrgMembership(org, user string) (bool, *Response, error) { |
||||
status, resp, err := c.getStatusCode("GET", fmt.Sprintf("/orgs/%s/members/%s", url.PathEscape(org), url.PathEscape(user)), nil, nil) |
||||
if err != nil { |
||||
return false, resp, err |
||||
} |
||||
switch status { |
||||
case http.StatusNoContent: |
||||
return true, resp, nil |
||||
case http.StatusNotFound: |
||||
return false, resp, nil |
||||
default: |
||||
return false, resp, fmt.Errorf("unexpected Status: %d", status) |
||||
} |
||||
} |
||||
|
||||
// CheckPublicOrgMembership Check if a user is a member of an organization
|
||||
func (c *Client) CheckPublicOrgMembership(org, user string) (bool, *Response, error) { |
||||
status, resp, err := c.getStatusCode("GET", fmt.Sprintf("/orgs/%s/public_members/%s", url.PathEscape(org), url.PathEscape(user)), nil, nil) |
||||
if err != nil { |
||||
return false, resp, err |
||||
} |
||||
switch status { |
||||
case http.StatusNoContent: |
||||
return true, resp, nil |
||||
case http.StatusNotFound: |
||||
return false, resp, nil |
||||
default: |
||||
return false, resp, fmt.Errorf("unexpected Status: %d", status) |
||||
} |
||||
} |
||||
|
||||
// SetPublicOrgMembership publicize/conceal a user's membership
|
||||
func (c *Client) SetPublicOrgMembership(org, user string, visible bool) (*Response, error) { |
||||
var ( |
||||
status int |
||||
err error |
||||
resp *Response |
||||
) |
||||
if visible { |
||||
status, resp, err = c.getStatusCode("PUT", fmt.Sprintf("/orgs/%s/public_members/%s", url.PathEscape(org), url.PathEscape(user)), nil, nil) |
||||
} else { |
||||
status, resp, err = c.getStatusCode("DELETE", fmt.Sprintf("/orgs/%s/public_members/%s", url.PathEscape(org), url.PathEscape(user)), nil, nil) |
||||
} |
||||
if err != nil { |
||||
return resp, err |
||||
} |
||||
switch status { |
||||
case http.StatusNoContent: |
||||
return resp, nil |
||||
case http.StatusNotFound: |
||||
return resp, fmt.Errorf("forbidden") |
||||
default: |
||||
return resp, fmt.Errorf("unexpected Status: %d", status) |
||||
} |
||||
} |
@ -0,0 +1,202 @@ |
||||
// Copyright 2019 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" |
||||
) |
||||
|
||||
// Team represents a team in an organization
|
||||
type Team struct { |
||||
ID int64 `json:"id"` |
||||
Name string `json:"name"` |
||||
Description string `json:"description"` |
||||
Organization *Organization `json:"organization"` |
||||
Permission AccessMode `json:"permission"` |
||||
CanCreateOrgRepo bool `json:"can_create_org_repo"` |
||||
IncludesAllRepositories bool `json:"includes_all_repositories"` |
||||
// example: ["repo.code","repo.issues","repo.ext_issues","repo.wiki","repo.pulls","repo.releases","repo.ext_wiki"]
|
||||
Units []string `json:"units"` |
||||
} |
||||
|
||||
// ListTeamsOptions options for listing teams
|
||||
type ListTeamsOptions struct { |
||||
ListOptions |
||||
} |
||||
|
||||
// ListOrgTeams lists all teams of an organization
|
||||
func (c *Client) ListOrgTeams(org string, opt ListTeamsOptions) ([]*Team, *Response, error) { |
||||
opt.setDefaults() |
||||
teams := make([]*Team, 0, opt.PageSize) |
||||
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/orgs/%s/teams?%s", org, opt.getURLQuery().Encode()), nil, nil, &teams) |
||||
return teams, resp, err |
||||
} |
||||
|
||||
// ListMyTeams lists all the teams of the current user
|
||||
func (c *Client) ListMyTeams(opt *ListTeamsOptions) ([]*Team, *Response, error) { |
||||
opt.setDefaults() |
||||
teams := make([]*Team, 0, opt.PageSize) |
||||
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/user/teams?%s", opt.getURLQuery().Encode()), nil, nil, &teams) |
||||
return teams, resp, err |
||||
} |
||||
|
||||
// GetTeam gets a team by ID
|
||||
func (c *Client) GetTeam(id int64) (*Team, *Response, error) { |
||||
t := new(Team) |
||||
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/teams/%d", id), nil, nil, t) |
||||
return t, resp, err |
||||
} |
||||
|
||||
// CreateTeamOption options for creating a team
|
||||
type CreateTeamOption struct { |
||||
Name string `json:"name"` |
||||
Description string `json:"description"` |
||||
Permission AccessMode `json:"permission"` |
||||
CanCreateOrgRepo bool `json:"can_create_org_repo"` |
||||
IncludesAllRepositories bool `json:"includes_all_repositories"` |
||||
// example: ["repo.code","repo.issues","repo.ext_issues","repo.wiki","repo.pulls","repo.releases","repo.ext_wiki"]
|
||||
Units []string `json:"units"` |
||||
} |
||||
|
||||
// Validate the CreateTeamOption struct
|
||||
func (opt CreateTeamOption) Validate() error { |
||||
if opt.Permission == AccessModeOwner { |
||||
opt.Permission = AccessModeAdmin |
||||
} else if opt.Permission != AccessModeRead && opt.Permission != AccessModeWrite && opt.Permission != AccessModeAdmin { |
||||
return fmt.Errorf("permission mode invalid") |
||||
} |
||||
if len(opt.Name) == 0 { |
||||
return fmt.Errorf("name required") |
||||
} |
||||
if len(opt.Name) > 30 { |
||||
return fmt.Errorf("name to long") |
||||
} |
||||
if len(opt.Description) > 255 { |
||||
return fmt.Errorf("description to long") |
||||
} |
||||
return nil |
||||
} |
||||
|
||||
// CreateTeam creates a team for an organization
|
||||
func (c *Client) CreateTeam(org string, opt CreateTeamOption) (*Team, *Response, error) { |
||||
if err := opt.Validate(); err != nil { |
||||
return nil, nil, err |
||||
} |
||||
body, err := json.Marshal(&opt) |
||||
if err != nil { |
||||
return nil, nil, err |
||||
} |
||||
t := new(Team) |
||||
resp, err := c.getParsedResponse("POST", fmt.Sprintf("/orgs/%s/teams", org), jsonHeader, bytes.NewReader(body), t) |
||||
return t, resp, err |
||||
} |
||||
|
||||
// EditTeamOption options for editing a team
|
||||
type EditTeamOption struct { |
||||
Name string `json:"name"` |
||||
Description *string `json:"description"` |
||||
Permission AccessMode `json:"permission"` |
||||
CanCreateOrgRepo *bool `json:"can_create_org_repo"` |
||||
IncludesAllRepositories *bool `json:"includes_all_repositories"` |
||||
// example: ["repo.code","repo.issues","repo.ext_issues","repo.wiki","repo.pulls","repo.releases","repo.ext_wiki"]
|
||||
Units []string `json:"units"` |
||||
} |
||||
|
||||
// Validate the EditTeamOption struct
|
||||
func (opt EditTeamOption) Validate() error { |
||||
if opt.Permission == AccessModeOwner { |
||||
opt.Permission = AccessModeAdmin |
||||
} else if opt.Permission != AccessModeRead && opt.Permission != AccessModeWrite && opt.Permission != AccessModeAdmin { |
||||
return fmt.Errorf("permission mode invalid") |
||||
} |
||||
if len(opt.Name) == 0 { |
||||
return fmt.Errorf("name required") |
||||
} |
||||
if len(opt.Name) > 30 { |
||||
return fmt.Errorf("name to long") |
||||
} |
||||
if opt.Description != nil && len(*opt.Description) > 255 { |
||||
return fmt.Errorf("description to long") |
||||
} |
||||
return nil |
||||
} |
||||
|
||||
// EditTeam edits a team of an organization
|
||||
func (c *Client) EditTeam(id int64, opt EditTeamOption) (*Response, error) { |
||||
if err := opt.Validate(); err != nil { |
||||
return nil, err |
||||
} |
||||
body, err := json.Marshal(&opt) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
_, resp, err := c.getResponse("PATCH", fmt.Sprintf("/teams/%d", id), jsonHeader, bytes.NewReader(body)) |
||||
return resp, err |
||||
} |
||||
|
||||
// DeleteTeam deletes a team of an organization
|
||||
func (c *Client) DeleteTeam(id int64) (*Response, error) { |
||||
_, resp, err := c.getResponse("DELETE", fmt.Sprintf("/teams/%d", id), nil, nil) |
||||
return resp, err |
||||
} |
||||
|
||||
// ListTeamMembersOptions options for listing team's members
|
||||
type ListTeamMembersOptions struct { |
||||
ListOptions |
||||
} |
||||
|
||||
// ListTeamMembers lists all members of a team
|
||||
func (c *Client) ListTeamMembers(id int64, opt ListTeamMembersOptions) ([]*User, *Response, error) { |
||||
opt.setDefaults() |
||||
members := make([]*User, 0, opt.PageSize) |
||||
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/teams/%d/members?%s", id, opt.getURLQuery().Encode()), nil, nil, &members) |
||||
return members, resp, err |
||||
} |
||||
|
||||
// GetTeamMember gets a member of a team
|
||||
func (c *Client) GetTeamMember(id int64, user string) (*User, *Response, error) { |
||||
m := new(User) |
||||
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/teams/%d/members/%s", id, user), nil, nil, m) |
||||
return m, resp, err |
||||
} |
||||
|
||||
// AddTeamMember adds a member to a team
|
||||
func (c *Client) AddTeamMember(id int64, user string) (*Response, error) { |
||||
_, resp, err := c.getResponse("PUT", fmt.Sprintf("/teams/%d/members/%s", id, user), nil, nil) |
||||
return resp, err |
||||
} |
||||
|
||||
// RemoveTeamMember removes a member from a team
|
||||
func (c *Client) RemoveTeamMember(id int64, user string) (*Response, error) { |
||||
_, resp, err := c.getResponse("DELETE", fmt.Sprintf("/teams/%d/members/%s", id, user), nil, nil) |
||||
return resp, err |
||||
} |
||||
|
||||
// ListTeamRepositoriesOptions options for listing team's repositories
|
||||
type ListTeamRepositoriesOptions struct { |
||||
ListOptions |
||||
} |
||||
|
||||
// ListTeamRepositories lists all repositories of a team
|
||||
func (c *Client) ListTeamRepositories(id int64, opt ListTeamRepositoriesOptions) ([]*Repository, *Response, error) { |
||||
opt.setDefaults() |
||||
repos := make([]*Repository, 0, opt.PageSize) |
||||
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/teams/%d/repos?%s", id, opt.getURLQuery().Encode()), nil, nil, &repos) |
||||
return repos, resp, err |
||||
} |
||||
|
||||
// AddTeamRepository adds a repository to a team
|
||||
func (c *Client) AddTeamRepository(id int64, org, repo string) (*Response, error) { |
||||
_, resp, err := c.getResponse("PUT", fmt.Sprintf("/teams/%d/repos/%s/%s", id, org, repo), nil, nil) |
||||
return resp, err |
||||
} |
||||
|
||||
// RemoveTeamRepository removes a repository from a team
|
||||
func (c *Client) RemoveTeamRepository(id int64, org, repo string) (*Response, error) { |
||||
_, resp, err := c.getResponse("DELETE", fmt.Sprintf("/teams/%d/repos/%s/%s", id, org, repo), nil, nil) |
||||
return resp, err |
||||
} |
@ -0,0 +1,253 @@ |
||||
// Copyright 2016 The Gogs Authors. All rights reserved.
|
||||
// Copyright 2019 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" |
||||
"net/url" |
||||
"strings" |
||||
"time" |
||||
) |
||||
|
||||
// PRBranchInfo information about a branch
|
||||
type PRBranchInfo struct { |
||||
Name string `json:"label"` |
||||
Ref string `json:"ref"` |
||||
Sha string `json:"sha"` |
||||
RepoID int64 `json:"repo_id"` |
||||
Repository *Repository `json:"repo"` |
||||
} |
||||
|
||||
// PullRequest represents a pull request
|
||||
type PullRequest struct { |
||||
ID int64 `json:"id"` |
||||
URL string `json:"url"` |
||||
Index int64 `json:"number"` |
||||
Poster *User `json:"user"` |
||||
Title string `json:"title"` |
||||
Body string `json:"body"` |
||||
Labels []*Label `json:"labels"` |
||||
Milestone *Milestone `json:"milestone"` |
||||
Assignee *User `json:"assignee"` |
||||
Assignees []*User `json:"assignees"` |
||||
State StateType `json:"state"` |
||||
IsLocked bool `json:"is_locked"` |
||||
Comments int `json:"comments"` |
||||
|
||||
HTMLURL string `json:"html_url"` |
||||
DiffURL string `json:"diff_url"` |
||||
PatchURL string `json:"patch_url"` |
||||
|
||||
Mergeable bool `json:"mergeable"` |
||||
HasMerged bool `json:"merged"` |
||||
Merged *time.Time `json:"merged_at"` |
||||
MergedCommitID *string `json:"merge_commit_sha"` |
||||
MergedBy *User `json:"merged_by"` |
||||
|
||||
Base *PRBranchInfo `json:"base"` |
||||
Head *PRBranchInfo `json:"head"` |
||||
MergeBase string `json:"merge_base"` |
||||
|
||||
Deadline *time.Time `json:"due_date"` |
||||
Created *time.Time `json:"created_at"` |
||||
Updated *time.Time `json:"updated_at"` |
||||
Closed *time.Time `json:"closed_at"` |
||||
} |
||||
|
||||
// ListPullRequestsOptions options for listing pull requests
|
||||
type ListPullRequestsOptions struct { |
||||
ListOptions |
||||
State StateType `json:"state"` |
||||
// oldest, recentupdate, leastupdate, mostcomment, leastcomment, priority
|
||||
Sort string |
||||
Milestone int64 |
||||
} |
||||
|
||||
// MergeStyle is used specify how a pull is merged
|
||||
type MergeStyle string |
||||
|
||||
const ( |
||||
// MergeStyleMerge merge pull as usual
|
||||
MergeStyleMerge MergeStyle = "merge" |
||||
// MergeStyleRebase rebase pull
|
||||
MergeStyleRebase MergeStyle = "rebase" |
||||
// MergeStyleRebaseMerge rebase and merge pull
|
||||
MergeStyleRebaseMerge MergeStyle = "rebase-merge" |
||||
// MergeStyleSquash squash and merge pull
|
||||
MergeStyleSquash MergeStyle = "squash" |
||||
) |
||||
|
||||
// QueryEncode turns options into querystring argument
|
||||
func (opt *ListPullRequestsOptions) QueryEncode() string { |
||||
query := opt.getURLQuery() |
||||
if len(opt.State) > 0 { |
||||
query.Add("state", string(opt.State)) |
||||
} |
||||
if len(opt.Sort) > 0 { |
||||
query.Add("sort", opt.Sort) |
||||
} |
||||
if opt.Milestone > 0 { |
||||
query.Add("milestone", fmt.Sprintf("%d", opt.Milestone)) |
||||
} |
||||
return query.Encode() |
||||
} |
||||
|
||||
// ListRepoPullRequests list PRs of one repository
|
||||
func (c *Client) ListRepoPullRequests(owner, repo string, opt ListPullRequestsOptions) ([]*PullRequest, *Response, error) { |
||||
opt.setDefaults() |
||||
prs := make([]*PullRequest, 0, opt.PageSize) |
||||
|
||||
link, _ := url.Parse(fmt.Sprintf("/repos/%s/%s/pulls", owner, repo)) |
||||
link.RawQuery = opt.QueryEncode() |
||||
resp, err := c.getParsedResponse("GET", link.String(), jsonHeader, nil, &prs) |
||||
return prs, resp, err |
||||
} |
||||
|
||||
// GetPullRequest get information of one PR
|
||||
func (c *Client) GetPullRequest(owner, repo string, index int64) (*PullRequest, *Response, error) { |
||||
pr := new(PullRequest) |
||||
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/pulls/%d", owner, repo, index), nil, nil, pr) |
||||
return pr, resp, err |
||||
} |
||||
|
||||
// CreatePullRequestOption options when creating a pull request
|
||||
type CreatePullRequestOption struct { |
||||
Head string `json:"head"` |
||||
Base string `json:"base"` |
||||
Title string `json:"title"` |
||||
Body string `json:"body"` |
||||
Assignee string `json:"assignee"` |
||||
Assignees []string `json:"assignees"` |
||||
Milestone int64 `json:"milestone"` |
||||
Labels []int64 `json:"labels"` |
||||
Deadline *time.Time `json:"due_date"` |
||||
} |
||||
|
||||
// CreatePullRequest create pull request with options
|
||||
func (c *Client) CreatePullRequest(owner, repo string, opt CreatePullRequestOption) (*PullRequest, *Response, error) { |
||||
body, err := json.Marshal(&opt) |
||||
if err != nil { |
||||
return nil, nil, err |
||||
} |
||||
pr := new(PullRequest) |
||||
resp, err := c.getParsedResponse("POST", |
||||
fmt.Sprintf("/repos/%s/%s/pulls", owner, repo), |
||||
jsonHeader, bytes.NewReader(body), pr) |
||||
return pr, resp, err |
||||
} |
||||
|
||||
// EditPullRequestOption options when modify pull request
|
||||
type EditPullRequestOption struct { |
||||
Title string `json:"title"` |
||||
Body string `json:"body"` |
||||
Base string `json:"base"` |
||||
Assignee string `json:"assignee"` |
||||
Assignees []string `json:"assignees"` |
||||
Milestone int64 `json:"milestone"` |
||||
Labels []int64 `json:"labels"` |
||||
State *StateType `json:"state"` |
||||
Deadline *time.Time `json:"due_date"` |
||||
} |
||||
|
||||
// Validate the EditPullRequestOption struct
|
||||
func (opt EditPullRequestOption) Validate(c *Client) error { |
||||
if len(opt.Title) != 0 && len(strings.TrimSpace(opt.Title)) == 0 { |
||||
return fmt.Errorf("title is empty") |
||||
} |
||||
if len(opt.Base) != 0 { |
||||
if err := c.CheckServerVersionConstraint(">=1.12.0"); err != nil { |
||||
return fmt.Errorf("can not change base gitea to old") |
||||
} |
||||
} |
||||
return nil |
||||
} |
||||
|
||||
// EditPullRequest modify pull request with PR id and options
|
||||
func (c *Client) EditPullRequest(owner, repo string, index int64, opt EditPullRequestOption) (*PullRequest, *Response, error) { |
||||
if err := opt.Validate(c); err != nil { |
||||
return nil, nil, err |
||||
} |
||||
body, err := json.Marshal(&opt) |
||||
if err != nil { |
||||
return nil, nil, err |
||||
} |
||||
pr := new(PullRequest) |
||||
resp, err := c.getParsedResponse("PATCH", |
||||
fmt.Sprintf("/repos/%s/%s/pulls/%d", owner, repo, index), |
||||
jsonHeader, bytes.NewReader(body), pr) |
||||
return pr, resp, err |
||||
} |
||||
|
||||
// MergePullRequestOption options when merging a pull request
|
||||
type MergePullRequestOption struct { |
||||
Style MergeStyle `json:"Do"` |
||||
Title string `json:"MergeTitleField"` |
||||
Message string `json:"MergeMessageField"` |
||||
} |
||||
|
||||
// Validate the MergePullRequestOption struct
|
||||
func (opt MergePullRequestOption) Validate(c *Client) error { |
||||
if opt.Style == MergeStyleSquash { |
||||
if err := c.CheckServerVersionConstraint(">=1.11.5"); err != nil { |
||||
return err |
||||
} |
||||
} |
||||
return nil |
||||
} |
||||
|
||||
// MergePullRequest merge a PR to repository by PR id
|
||||
func (c *Client) MergePullRequest(owner, repo string, index int64, opt MergePullRequestOption) (bool, *Response, error) { |
||||
if err := opt.Validate(c); err != nil { |
||||
return false, nil, err |
||||
} |
||||
body, err := json.Marshal(&opt) |
||||
if err != nil { |
||||
return false, nil, err |
||||
} |
||||
status, resp, err := c.getStatusCode("POST", fmt.Sprintf("/repos/%s/%s/pulls/%d/merge", owner, repo, index), jsonHeader, bytes.NewReader(body)) |
||||
if err != nil { |
||||
return false, resp, err |
||||
} |
||||
return status == 200, resp, nil |
||||
} |
||||
|
||||
// IsPullRequestMerged test if one PR is merged to one repository
|
||||
func (c *Client) IsPullRequestMerged(owner, repo string, index int64) (bool, *Response, error) { |
||||
status, resp, err := c.getStatusCode("GET", fmt.Sprintf("/repos/%s/%s/pulls/%d/merge", owner, repo, index), nil, nil) |
||||
|
||||
if err != nil { |
||||
return false, resp, err |
||||
} |
||||
|
||||
return status == 204, resp, nil |
||||
} |
||||
|
||||
// getPullRequestDiffOrPatch gets the patch or diff file as bytes for a PR
|
||||
func (c *Client) getPullRequestDiffOrPatch(owner, repo, kind string, index int64) ([]byte, *Response, error) { |
||||
if err := c.CheckServerVersionConstraint(">=1.13.0"); err != nil { |
||||
r, _, err2 := c.GetRepo(owner, repo) |
||||
if err2 != nil { |
||||
return nil, nil, err |
||||
} |
||||
if r.Private { |
||||
return nil, nil, err |
||||
} |
||||
return c.getWebResponse("GET", fmt.Sprintf("/%s/%s/pulls/%d.%s", owner, repo, index, kind), nil) |
||||
} |
||||
return c.getResponse("GET", fmt.Sprintf("/repos/%s/%s/pulls/%d.%s", owner, repo, index, kind), nil, nil) |
||||
} |
||||
|
||||
// GetPullRequestPatch gets the .patch file as bytes for a PR
|
||||
func (c *Client) GetPullRequestPatch(owner, repo string, index int64) ([]byte, *Response, error) { |
||||
return c.getPullRequestDiffOrPatch(owner, repo, "patch", index) |
||||
} |
||||
|
||||
// GetPullRequestDiff gets the .diff file as bytes for a PR
|
||||
func (c *Client) GetPullRequestDiff(owner, repo string, index int64) ([]byte, *Response, error) { |
||||
return c.getPullRequestDiffOrPatch(owner, repo, "diff", index) |
||||
} |
@ -0,0 +1,219 @@ |
||||
// 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" |
||||
"net/url" |
||||
"strings" |
||||
"time" |
||||
) |
||||
|
||||
// ReviewStateType review state type
|
||||
type ReviewStateType string |
||||
|
||||
const ( |
||||
// ReviewStateApproved pr is approved
|
||||
ReviewStateApproved ReviewStateType = "APPROVED" |
||||
// ReviewStatePending pr state is pending
|
||||
ReviewStatePending ReviewStateType = "PENDING" |
||||
// ReviewStateComment is a comment review
|
||||
ReviewStateComment ReviewStateType = "COMMENT" |
||||
// ReviewStateRequestChanges changes for pr are requested
|
||||
ReviewStateRequestChanges ReviewStateType = "REQUEST_CHANGES" |
||||
// ReviewStateRequestReview review is requested from user
|
||||
ReviewStateRequestReview ReviewStateType = "REQUEST_REVIEW" |
||||
// ReviewStateUnknown state of pr is unknown
|
||||
ReviewStateUnknown ReviewStateType = "" |
||||
) |
||||
|
||||
// PullReview represents a pull request review
|
||||
type PullReview struct { |
||||
ID int64 `json:"id"` |
||||
Reviewer *User `json:"user"` |
||||
State ReviewStateType `json:"state"` |
||||
Body string `json:"body"` |
||||
CommitID string `json:"commit_id"` |
||||
Stale bool `json:"stale"` |
||||
Official bool `json:"official"` |
||||
CodeCommentsCount int `json:"comments_count"` |
||||
Submitted time.Time `json:"submitted_at"` |
||||
|
||||
HTMLURL string `json:"html_url"` |
||||
HTMLPullURL string `json:"pull_request_url"` |
||||
} |
||||
|
||||
// PullReviewComment represents a comment on a pull request review
|
||||
type PullReviewComment struct { |
||||
ID int64 `json:"id"` |
||||
Body string `json:"body"` |
||||
Reviewer *User `json:"user"` |
||||
ReviewID int64 `json:"pull_request_review_id"` |
||||
|
||||
Created time.Time `json:"created_at"` |
||||
Updated time.Time `json:"updated_at"` |
||||
|
||||
Path string `json:"path"` |
||||
CommitID string `json:"commit_id"` |
||||
OrigCommitID string `json:"original_commit_id"` |
||||
DiffHunk string `json:"diff_hunk"` |
||||
LineNum uint64 `json:"position"` |
||||
OldLineNum uint64 `json:"original_position"` |
||||
|
||||
HTMLURL string `json:"html_url"` |
||||
HTMLPullURL string `json:"pull_request_url"` |
||||
} |
||||
|
||||
// CreatePullReviewOptions are options to create a pull review
|
||||
type CreatePullReviewOptions struct { |
||||
State ReviewStateType `json:"event"` |
||||
Body string `json:"body"` |
||||
CommitID string `json:"commit_id"` |
||||
Comments []CreatePullReviewComment `json:"comments"` |
||||
} |
||||
|
||||
// CreatePullReviewComment represent a review comment for creation api
|
||||
type CreatePullReviewComment struct { |
||||
// the tree path
|
||||
Path string `json:"path"` |
||||
Body string `json:"body"` |
||||
// if comment to old file line or 0
|
||||
OldLineNum int64 `json:"old_position"` |
||||
// if comment to new file line or 0
|
||||
NewLineNum int64 `json:"new_position"` |
||||
} |
||||
|
||||
// SubmitPullReviewOptions are options to submit a pending pull review
|
||||
type SubmitPullReviewOptions struct { |
||||
State ReviewStateType `json:"event"` |
||||
Body string `json:"body"` |
||||
} |
||||
|
||||
// ListPullReviewsOptions options for listing PullReviews
|
||||
type ListPullReviewsOptions struct { |
||||
ListOptions |
||||
} |
||||
|
||||
// Validate the CreatePullReviewOptions struct
|
||||
func (opt CreatePullReviewOptions) Validate() error { |
||||
if opt.State != ReviewStateApproved && len(strings.TrimSpace(opt.Body)) == 0 { |
||||
return fmt.Errorf("body is empty") |
||||
} |
||||
for i := range opt.Comments { |
||||
if err := opt.Comments[i].Validate(); err != nil { |
||||
return err |
||||
} |
||||
} |
||||
return nil |
||||
} |
||||
|
||||
// Validate the SubmitPullReviewOptions struct
|
||||
func (opt SubmitPullReviewOptions) Validate() error { |
||||
if opt.State != ReviewStateApproved && len(strings.TrimSpace(opt.Body)) == 0 { |
||||
return fmt.Errorf("body is empty") |
||||
} |
||||
return nil |
||||
} |
||||
|
||||
// Validate the CreatePullReviewComment struct
|
||||
func (opt CreatePullReviewComment) Validate() error { |
||||
if len(strings.TrimSpace(opt.Body)) == 0 { |
||||
return fmt.Errorf("body is empty") |
||||
} |
||||
if opt.NewLineNum != 0 && opt.OldLineNum != 0 { |
||||
return fmt.Errorf("old and new line num are set, cant identify the code comment position") |
||||
} |
||||
return nil |
||||
} |
||||
|
||||
// ListPullReviews lists all reviews of a pull request
|
||||
func (c *Client) ListPullReviews(owner, repo string, index int64, opt ListPullReviewsOptions) ([]*PullReview, *Response, error) { |
||||
if err := c.CheckServerVersionConstraint(">=1.12.0"); err != nil { |
||||
return nil, nil, err |
||||
} |
||||
opt.setDefaults() |
||||
rs := make([]*PullReview, 0, opt.PageSize) |
||||
|
||||
link, _ := url.Parse(fmt.Sprintf("/repos/%s/%s/pulls/%d/reviews", owner, repo, index)) |
||||
link.RawQuery = opt.ListOptions.getURLQuery().Encode() |
||||
|
||||
resp, err := c.getParsedResponse("GET", link.String(), jsonHeader, nil, &rs) |
||||
return rs, resp, err |
||||
} |
||||
|
||||
// GetPullReview gets a specific review of a pull request
|
||||
func (c *Client) GetPullReview(owner, repo string, index, id int64) (*PullReview, *Response, error) { |
||||
if err := c.CheckServerVersionConstraint(">=1.12.0"); err != nil { |
||||
return nil, nil, err |
||||
} |
||||
|
||||
r := new(PullReview) |
||||
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/pulls/%d/reviews/%d", owner, repo, index, id), jsonHeader, nil, &r) |
||||
return r, resp, err |
||||
} |
||||
|
||||
// ListPullReviewComments lists all comments of a pull request review
|
||||
func (c *Client) ListPullReviewComments(owner, repo string, index, id int64) ([]*PullReviewComment, *Response, error) { |
||||
if err := c.CheckServerVersionConstraint(">=1.12.0"); err != nil { |
||||
return nil, nil, err |
||||
} |
||||
rcl := make([]*PullReviewComment, 0, 4) |
||||
link, _ := url.Parse(fmt.Sprintf("/repos/%s/%s/pulls/%d/reviews/%d/comments", owner, repo, index, id)) |
||||
|
||||
resp, err := c.getParsedResponse("GET", link.String(), jsonHeader, nil, &rcl) |
||||
return rcl, resp, err |
||||
} |
||||
|
||||
// DeletePullReview delete a specific review from a pull request
|
||||
func (c *Client) DeletePullReview(owner, repo string, index, id int64) (*Response, error) { |
||||
if err := c.CheckServerVersionConstraint(">=1.12.0"); err != nil { |
||||
return nil, err |
||||
} |
||||
|
||||
_, resp, err := c.getResponse("DELETE", fmt.Sprintf("/repos/%s/%s/pulls/%d/reviews/%d", owner, repo, index, id), jsonHeader, nil) |
||||
return resp, err |
||||
} |
||||
|
||||
// CreatePullReview create a review to an pull request
|
||||
func (c *Client) CreatePullReview(owner, repo string, index int64, opt CreatePullReviewOptions) (*PullReview, *Response, error) { |
||||
if err := c.CheckServerVersionConstraint(">=1.12.0"); err != nil { |
||||
return nil, nil, err |
||||
} |
||||
if err := opt.Validate(); err != nil { |
||||
return nil, nil, err |
||||
} |
||||
body, err := json.Marshal(&opt) |
||||
if err != nil { |
||||
return nil, nil, err |
||||
} |
||||
|
||||
r := new(PullReview) |
||||
resp, err := c.getParsedResponse("POST", |
||||
fmt.Sprintf("/repos/%s/%s/pulls/%d/reviews", owner, repo, index), |
||||
jsonHeader, bytes.NewReader(body), r) |
||||
return r, resp, err |
||||
} |
||||
|
||||
// SubmitPullReview submit a pending review to an pull request
|
||||
func (c *Client) SubmitPullReview(owner, repo string, index, id int64, opt SubmitPullReviewOptions) (*PullReview, *Response, error) { |
||||
if err := c.CheckServerVersionConstraint(">=1.12.0"); err != nil { |
||||
return nil, nil, err |
||||
} |
||||
if err := opt.Validate(); err != nil { |
||||
return nil, nil, err |
||||
} |
||||
body, err := json.Marshal(&opt) |
||||
if err != nil { |
||||
return nil, nil, err |
||||
} |
||||
|
||||
r := new(PullReview) |
||||
resp, err := c.getParsedResponse("POST", |
||||
fmt.Sprintf("/repos/%s/%s/pulls/%d/reviews/%d", owner, repo, index, id), |
||||
jsonHeader, bytes.NewReader(body), r) |
||||
return r, resp, err |
||||
} |
@ -0,0 +1,153 @@ |
||||
// Copyright 2016 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" |
||||
"net/http" |
||||
"strings" |
||||
"time" |
||||
) |
||||
|
||||
// Release represents a repository release
|
||||
type Release struct { |
||||
ID int64 `json:"id"` |
||||
TagName string `json:"tag_name"` |
||||
Target string `json:"target_commitish"` |
||||
Title string `json:"name"` |
||||
Note string `json:"body"` |
||||
URL string `json:"url"` |
||||
TarURL string `json:"tarball_url"` |
||||
ZipURL string `json:"zipball_url"` |
||||
IsDraft bool `json:"draft"` |
||||
IsPrerelease bool `json:"prerelease"` |
||||
CreatedAt time.Time `json:"created_at"` |
||||
PublishedAt time.Time `json:"published_at"` |
||||
Publisher *User `json:"author"` |
||||
Attachments []*Attachment `json:"assets"` |
||||
} |
||||
|
||||
// ListReleasesOptions options for listing repository's releases
|
||||
type ListReleasesOptions struct { |
||||
ListOptions |
||||
} |
||||
|
||||
// ListReleases list releases of a repository
|
||||
func (c *Client) ListReleases(user, repo string, opt ListReleasesOptions) ([]*Release, *Response, error) { |
||||
opt.setDefaults() |
||||
releases := make([]*Release, 0, opt.PageSize) |
||||
resp, err := c.getParsedResponse("GET", |
||||
fmt.Sprintf("/repos/%s/%s/releases?%s", user, repo, opt.getURLQuery().Encode()), |
||||
nil, nil, &releases) |
||||
return releases, resp, err |
||||
} |
||||
|
||||
// GetRelease get a release of a repository by id
|
||||
func (c *Client) GetRelease(user, repo string, id int64) (*Release, *Response, error) { |
||||
r := new(Release) |
||||
resp, err := c.getParsedResponse("GET", |
||||
fmt.Sprintf("/repos/%s/%s/releases/%d", user, repo, id), |
||||
jsonHeader, nil, &r) |
||||
return r, resp, err |
||||
} |
||||
|
||||
// GetReleaseByTag get a release of a repository by tag
|
||||
func (c *Client) GetReleaseByTag(user, repo string, tag string) (*Release, *Response, error) { |
||||
if c.CheckServerVersionConstraint(">=1.13.0") != nil { |
||||
return c.fallbackGetReleaseByTag(user, repo, tag) |
||||
} |
||||
r := new(Release) |
||||
resp, err := c.getParsedResponse("GET", |
||||
fmt.Sprintf("/repos/%s/%s/releases/tags/%s", user, repo, tag), |
||||
nil, nil, &r) |
||||
return r, resp, err |
||||
} |
||||
|
||||
// CreateReleaseOption options when creating a release
|
||||
type CreateReleaseOption struct { |
||||
TagName string `json:"tag_name"` |
||||
Target string `json:"target_commitish"` |
||||
Title string `json:"name"` |
||||
Note string `json:"body"` |
||||
IsDraft bool `json:"draft"` |
||||
IsPrerelease bool `json:"prerelease"` |
||||
} |
||||
|
||||
// Validate the CreateReleaseOption struct
|
||||
func (opt CreateReleaseOption) Validate() error { |
||||
if len(strings.TrimSpace(opt.Title)) == 0 { |
||||
return fmt.Errorf("title is empty") |
||||
} |
||||
return nil |
||||
} |
||||
|
||||
// CreateRelease create a release
|
||||
func (c *Client) CreateRelease(user, repo string, opt CreateReleaseOption) (*Release, *Response, error) { |
||||
if err := opt.Validate(); err != nil { |
||||
return nil, nil, err |
||||
} |
||||
body, err := json.Marshal(opt) |
||||
if err != nil { |
||||
return nil, nil, err |
||||
} |
||||
r := new(Release) |
||||
resp, err := c.getParsedResponse("POST", |
||||
fmt.Sprintf("/repos/%s/%s/releases", user, repo), |
||||
jsonHeader, bytes.NewReader(body), r) |
||||
return r, resp, err |
||||
} |
||||
|
||||
// EditReleaseOption options when editing a release
|
||||
type EditReleaseOption struct { |
||||
TagName string `json:"tag_name"` |
||||
Target string `json:"target_commitish"` |
||||
Title string `json:"name"` |
||||
Note string `json:"body"` |
||||
IsDraft *bool `json:"draft"` |
||||
IsPrerelease *bool `json:"prerelease"` |
||||
} |
||||
|
||||
// EditRelease edit a release
|
||||
func (c *Client) EditRelease(user, repo string, id int64, form EditReleaseOption) (*Release, *Response, error) { |
||||
body, err := json.Marshal(form) |
||||
if err != nil { |
||||
return nil, nil, err |
||||
} |
||||
r := new(Release) |
||||
resp, err := c.getParsedResponse("PATCH", |
||||
fmt.Sprintf("/repos/%s/%s/releases/%d", user, repo, id), |
||||
jsonHeader, bytes.NewReader(body), r) |
||||
return r, resp, err |
||||
} |
||||
|
||||
// DeleteRelease delete a release from a repository
|
||||
func (c *Client) DeleteRelease(user, repo string, id int64) (*Response, error) { |
||||
_, resp, err := c.getResponse("DELETE", |
||||
fmt.Sprintf("/repos/%s/%s/releases/%d", user, repo, id), |
||||
nil, nil) |
||||
return resp, err |
||||
} |
||||
|
||||
// fallbackGetReleaseByTag is fallback for old gitea installations ( < 1.13.0 )
|
||||
func (c *Client) fallbackGetReleaseByTag(user, repo string, tag string) (*Release, *Response, error) { |
||||
for i := 1; ; i++ { |
||||
rl, resp, err := c.ListReleases(user, repo, ListReleasesOptions{ListOptions{Page: i}}) |
||||
if err != nil { |
||||
return nil, resp, err |
||||
} |
||||
if len(rl) == 0 { |
||||
return nil, |
||||
&Response{&http.Response{StatusCode: 404}}, |
||||
fmt.Errorf("release with tag '%s' not found", tag) |
||||
} |
||||
for _, r := range rl { |
||||
if r.TagName == tag { |
||||
return r, resp, nil |
||||
} |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,390 @@ |
||||
// 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" |
||||
"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"` |
||||
} |
||||
|
||||
// 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"` |
||||
Parent *Repository `json:"parent"` |
||||
Mirror bool `json:"mirror"` |
||||
Size int `json:"size"` |
||||
HTMLURL string `json:"html_url"` |
||||
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"` |
||||
DefaultBranch string `json:"default_branch"` |
||||
Archived bool `json:"archived"` |
||||
Created time.Time `json:"created_at"` |
||||
Updated time.Time `json:"updated_at"` |
||||
Permissions *Permission `json:"permissions,omitempty"` |
||||
HasIssues bool `json:"has_issues"` |
||||
HasWiki bool `json:"has_wiki"` |
||||
HasPullRequests bool `json:"has_pull_requests"` |
||||
IgnoreWhitespaceConflicts bool `json:"ignore_whitespace_conflicts"` |
||||
AllowMerge bool `json:"allow_merge_commits"` |
||||
AllowRebase bool `json:"allow_rebase"` |
||||
AllowRebaseMerge bool `json:"allow_rebase_explicit"` |
||||
AllowSquash bool `json:"allow_squash_merge"` |
||||
AvatarURL string `json:"avatar_url"` |
||||
} |
||||
|
||||
// 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" |
||||
) |
||||
|
||||
// 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) { |
||||
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) { |
||||
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("%v", opt.IsPrivate)) |
||||
} |
||||
if opt.IsArchived != nil { |
||||
query.Add("archived", fmt.Sprintf("%v", 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.CheckServerVersionConstraint(">=1.12.0"); err != nil && opt.IsPrivate != nil { |
||||
if *opt.IsPrivate { |
||||
// private repos only not supported on gitea <= 1.11.x
|
||||
return nil, nil, err |
||||
} |
||||
link.Query().Add("private", "false") |
||||
} |
||||
} |
||||
|
||||
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"` |
||||
// 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"` |
||||
} |
||||
|
||||
// Validate the CreateRepoOption struct
|
||||
func (opt CreateRepoOption) Validate() error { |
||||
if len(strings.TrimSpace(opt.Name)) == 0 { |
||||
return fmt.Errorf("name is empty") |
||||
} |
||||
return nil |
||||
} |
||||
|
||||
// CreateRepo creates a repository for authenticated user.
|
||||
func (c *Client) CreateRepo(opt CreateRepoOption) (*Repository, *Response, error) { |
||||
if err := opt.Validate(); 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", "/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 := opt.Validate(); 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("/org/%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) { |
||||
repo := new(Repository) |
||||
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s", owner, reponame), 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 enable issues for this repository or `false` to disable them.
|
||||
HasIssues *bool `json:"has_issues,omitempty"` |
||||
// either `true` to enable the wiki for this repository or `false` to disable it.
|
||||
HasWiki *bool `json:"has_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 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 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"` |
||||
} |
||||
|
||||
// EditRepo edit the properties of a repository
|
||||
func (c *Client) EditRepo(owner, reponame string, opt EditRepoOption) (*Repository, *Response, error) { |
||||
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) { |
||||
_, resp, err := c.getResponse("DELETE", fmt.Sprintf("/repos/%s/%s", owner, repo), nil, nil) |
||||
return resp, err |
||||
} |
||||
|
||||
// MirrorSync adds a mirrored repository to the mirror sync queue.
|
||||
func (c *Client) MirrorSync(owner, repo string) (*Response, error) { |
||||
_, resp, err := c.getResponse("POST", fmt.Sprintf("/repos/%s/%s/mirror-sync", owner, repo), nil, nil) |
||||
return resp, err |
||||
} |
||||
|
||||
// GetRepoLanguages return language stats of a repo
|
||||
func (c *Client) GetRepoLanguages(owner, repo string) (map[string]int64, *Response, error) { |
||||
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) { |
||||
return c.getResponse("GET", fmt.Sprintf("/repos/%s/%s/archive/%s%s", owner, repo, url.PathEscape(ref), ext), nil, nil) |
||||
} |
@ -0,0 +1,134 @@ |
||||
// Copyright 2016 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" |
||||
"time" |
||||
) |
||||
|
||||
// PayloadUser represents the author or committer of a commit
|
||||
type PayloadUser struct { |
||||
// Full name of the commit author
|
||||
Name string `json:"name"` |
||||
Email string `json:"email"` |
||||
UserName string `json:"username"` |
||||
} |
||||
|
||||
// FIXME: consider using same format as API when commits API are added.
|
||||
// applies to PayloadCommit and PayloadCommitVerification
|
||||
|
||||
// PayloadCommit represents a commit
|
||||
type PayloadCommit struct { |
||||
// sha1 hash of the commit
|
||||
ID string `json:"id"` |
||||
Message string `json:"message"` |
||||
URL string `json:"url"` |
||||
Author *PayloadUser `json:"author"` |
||||
Committer *PayloadUser `json:"committer"` |
||||
Verification *PayloadCommitVerification `json:"verification"` |
||||
Timestamp time.Time `json:"timestamp"` |
||||
Added []string `json:"added"` |
||||
Removed []string `json:"removed"` |
||||
Modified []string `json:"modified"` |
||||
} |
||||
|
||||
// PayloadCommitVerification represents the GPG verification of a commit
|
||||
type PayloadCommitVerification struct { |
||||
Verified bool `json:"verified"` |
||||
Reason string `json:"reason"` |
||||
Signature string `json:"signature"` |
||||
Payload string `json:"payload"` |
||||
} |
||||
|
||||
// Branch represents a repository branch
|
||||
type Branch struct { |
||||
Name string `json:"name"` |
||||
Commit *PayloadCommit `json:"commit"` |
||||
Protected bool `json:"protected"` |
||||
RequiredApprovals int64 `json:"required_approvals"` |
||||
EnableStatusCheck bool `json:"enable_status_check"` |
||||
StatusCheckContexts []string `json:"status_check_contexts"` |
||||
UserCanPush bool `json:"user_can_push"` |
||||
UserCanMerge bool `json:"user_can_merge"` |
||||
EffectiveBranchProtectionName string `json:"effective_branch_protection_name"` |
||||
} |
||||
|
||||
// ListRepoBranchesOptions options for listing a repository's branches
|
||||
type ListRepoBranchesOptions struct { |
||||
ListOptions |
||||
} |
||||
|
||||
// ListRepoBranches list all the branches of one repository
|
||||
func (c *Client) ListRepoBranches(user, repo string, opt ListRepoBranchesOptions) ([]*Branch, *Response, error) { |
||||
opt.setDefaults() |
||||
branches := make([]*Branch, 0, opt.PageSize) |
||||
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/branches?%s", user, repo, opt.getURLQuery().Encode()), nil, nil, &branches) |
||||
return branches, resp, err |
||||
} |
||||
|
||||
// GetRepoBranch get one branch's information of one repository
|
||||
func (c *Client) GetRepoBranch(user, repo, branch string) (*Branch, *Response, error) { |
||||
b := new(Branch) |
||||
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/branches/%s", user, repo, branch), nil, nil, &b) |
||||
if err != nil { |
||||
return nil, resp, err |
||||
} |
||||
return b, resp, nil |
||||
} |
||||
|
||||
// DeleteRepoBranch delete a branch in a repository
|
||||
func (c *Client) DeleteRepoBranch(user, repo, branch string) (bool, *Response, error) { |
||||
if err := c.CheckServerVersionConstraint(">=1.12.0"); err != nil { |
||||
return false, nil, err |
||||
} |
||||
status, resp, err := c.getStatusCode("DELETE", fmt.Sprintf("/repos/%s/%s/branches/%s", user, repo, branch), nil, nil) |
||||
if err != nil { |
||||
return false, resp, err |
||||
} |
||||
return status == 204, resp, nil |
||||
} |
||||
|
||||
// CreateBranchOption options when creating a branch in a repository
|
||||
type CreateBranchOption struct { |
||||
// Name of the branch to create
|
||||
BranchName string `json:"new_branch_name"` |
||||
// Name of the old branch to create from (optional)
|
||||
OldBranchName string `json:"old_branch_name"` |
||||
} |
||||
|
||||
// Validate the CreateBranchOption struct
|
||||
func (opt CreateBranchOption) Validate() error { |
||||
if len(opt.BranchName) == 0 { |
||||
return fmt.Errorf("BranchName is empty") |
||||
} |
||||
if len(opt.BranchName) > 100 { |
||||
return fmt.Errorf("BranchName to long") |
||||
} |
||||
if len(opt.OldBranchName) > 100 { |
||||
return fmt.Errorf("OldBranchName to long") |
||||
} |
||||
return nil |
||||
} |
||||
|
||||
// CreateBranch creates a branch for a user's repository
|
||||
func (c *Client) CreateBranch(owner, repo string, opt CreateBranchOption) (*Branch, *Response, error) { |
||||
if err := c.CheckServerVersionConstraint(">=1.13.0"); err != nil { |
||||
return nil, nil, err |
||||
} |
||||
if err := opt.Validate(); err != nil { |
||||
return nil, nil, err |
||||
} |
||||
body, err := json.Marshal(&opt) |
||||
if err != nil { |
||||
return nil, nil, err |
||||
} |
||||
branch := new(Branch) |
||||
resp, err := c.getParsedResponse("POST", fmt.Sprintf("/repos/%s/%s/branches", owner, repo), jsonHeader, bytes.NewReader(body), branch) |
||||
return branch, resp, err |
||||
} |
@ -0,0 +1,150 @@ |
||||
// 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" |
||||
"net/url" |
||||
"time" |
||||
) |
||||
|
||||
// BranchProtection represents a branch protection for a repository
|
||||
type BranchProtection struct { |
||||
BranchName string `json:"branch_name"` |
||||
EnablePush bool `json:"enable_push"` |
||||
EnablePushWhitelist bool `json:"enable_push_whitelist"` |
||||
PushWhitelistUsernames []string `json:"push_whitelist_usernames"` |
||||
PushWhitelistTeams []string `json:"push_whitelist_teams"` |
||||
PushWhitelistDeployKeys bool `json:"push_whitelist_deploy_keys"` |
||||
EnableMergeWhitelist bool `json:"enable_merge_whitelist"` |
||||
MergeWhitelistUsernames []string `json:"merge_whitelist_usernames"` |
||||
MergeWhitelistTeams []string `json:"merge_whitelist_teams"` |
||||
EnableStatusCheck bool `json:"enable_status_check"` |
||||
StatusCheckContexts []string `json:"status_check_contexts"` |
||||
RequiredApprovals int64 `json:"required_approvals"` |
||||
EnableApprovalsWhitelist bool `json:"enable_approvals_whitelist"` |
||||
ApprovalsWhitelistUsernames []string `json:"approvals_whitelist_username"` |
||||
ApprovalsWhitelistTeams []string `json:"approvals_whitelist_teams"` |
||||
BlockOnRejectedReviews bool `json:"block_on_rejected_reviews"` |
||||
BlockOnOutdatedBranch bool `json:"block_on_outdated_branch"` |
||||
DismissStaleApprovals bool `json:"dismiss_stale_approvals"` |
||||
RequireSignedCommits bool `json:"require_signed_commits"` |
||||
ProtectedFilePatterns string `json:"protected_file_patterns"` |
||||
Created time.Time `json:"created_at"` |
||||
Updated time.Time `json:"updated_at"` |
||||
} |
||||
|
||||
// CreateBranchProtectionOption options for creating a branch protection
|
||||
type CreateBranchProtectionOption struct { |
||||
BranchName string `json:"branch_name"` |
||||
EnablePush bool `json:"enable_push"` |
||||
EnablePushWhitelist bool `json:"enable_push_whitelist"` |
||||
PushWhitelistUsernames []string `json:"push_whitelist_usernames"` |
||||
PushWhitelistTeams []string `json:"push_whitelist_teams"` |
||||
PushWhitelistDeployKeys bool `json:"push_whitelist_deploy_keys"` |
||||
EnableMergeWhitelist bool `json:"enable_merge_whitelist"` |
||||
MergeWhitelistUsernames []string `json:"merge_whitelist_usernames"` |
||||
MergeWhitelistTeams []string `json:"merge_whitelist_teams"` |
||||
EnableStatusCheck bool `json:"enable_status_check"` |
||||
StatusCheckContexts []string `json:"status_check_contexts"` |
||||
RequiredApprovals int64 `json:"required_approvals"` |
||||
EnableApprovalsWhitelist bool `json:"enable_approvals_whitelist"` |
||||
ApprovalsWhitelistUsernames []string `json:"approvals_whitelist_username"` |
||||
ApprovalsWhitelistTeams []string `json:"approvals_whitelist_teams"` |
||||
BlockOnRejectedReviews bool `json:"block_on_rejected_reviews"` |
||||
BlockOnOutdatedBranch bool `json:"block_on_outdated_branch"` |
||||
DismissStaleApprovals bool `json:"dismiss_stale_approvals"` |
||||
RequireSignedCommits bool `json:"require_signed_commits"` |
||||
ProtectedFilePatterns string `json:"protected_file_patterns"` |
||||
} |
||||
|
||||
// EditBranchProtectionOption options for editing a branch protection
|
||||
type EditBranchProtectionOption struct { |
||||
EnablePush *bool `json:"enable_push"` |
||||
EnablePushWhitelist *bool `json:"enable_push_whitelist"` |
||||
PushWhitelistUsernames []string `json:"push_whitelist_usernames"` |
||||
PushWhitelistTeams []string `json:"push_whitelist_teams"` |
||||
PushWhitelistDeployKeys *bool `json:"push_whitelist_deploy_keys"` |
||||
EnableMergeWhitelist *bool `json:"enable_merge_whitelist"` |
||||
MergeWhitelistUsernames []string `json:"merge_whitelist_usernames"` |
||||
MergeWhitelistTeams []string `json:"merge_whitelist_teams"` |
||||
EnableStatusCheck *bool `json:"enable_status_check"` |
||||
StatusCheckContexts []string `json:"status_check_contexts"` |
||||
RequiredApprovals *int64 `json:"required_approvals"` |
||||
EnableApprovalsWhitelist *bool `json:"enable_approvals_whitelist"` |
||||
ApprovalsWhitelistUsernames []string `json:"approvals_whitelist_username"` |
||||
ApprovalsWhitelistTeams []string `json:"approvals_whitelist_teams"` |
||||
BlockOnRejectedReviews *bool `json:"block_on_rejected_reviews"` |
||||
BlockOnOutdatedBranch *bool `json:"block_on_outdated_branch"` |
||||
DismissStaleApprovals *bool `json:"dismiss_stale_approvals"` |
||||
RequireSignedCommits *bool `json:"require_signed_commits"` |
||||
ProtectedFilePatterns *string `json:"protected_file_patterns"` |
||||
} |
||||
|
||||
// ListBranchProtectionsOptions list branch protection options
|
||||
type ListBranchProtectionsOptions struct { |
||||
ListOptions |
||||
} |
||||
|
||||
// ListBranchProtections list branch protections for a repo
|
||||
func (c *Client) ListBranchProtections(owner, repo string, opt ListBranchProtectionsOptions) ([]*BranchProtection, *Response, error) { |
||||
if err := c.CheckServerVersionConstraint(">=1.12.0"); err != nil { |
||||
return nil, nil, err |
||||
} |
||||
bps := make([]*BranchProtection, 0, opt.PageSize) |
||||
link, _ := url.Parse(fmt.Sprintf("/repos/%s/%s/branch_protections", owner, repo)) |
||||
link.RawQuery = opt.getURLQuery().Encode() |
||||
resp, err := c.getParsedResponse("GET", link.String(), jsonHeader, nil, &bps) |
||||
return bps, resp, err |
||||
} |
||||
|
||||
// GetBranchProtection gets a branch protection
|
||||
func (c *Client) GetBranchProtection(owner, repo, name string) (*BranchProtection, *Response, error) { |
||||
if err := c.CheckServerVersionConstraint(">=1.12.0"); err != nil { |
||||
return nil, nil, err |
||||
} |
||||
bp := new(BranchProtection) |
||||
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/branch_protections/%s", owner, repo, name), jsonHeader, nil, bp) |
||||
return bp, resp, err |
||||
} |
||||
|
||||
// CreateBranchProtection creates a branch protection for a repo
|
||||
func (c *Client) CreateBranchProtection(owner, repo string, opt CreateBranchProtectionOption) (*BranchProtection, *Response, error) { |
||||
if err := c.CheckServerVersionConstraint(">=1.12.0"); err != nil { |
||||
return nil, nil, err |
||||
} |
||||
bp := new(BranchProtection) |
||||
body, err := json.Marshal(&opt) |
||||
if err != nil { |
||||
return nil, nil, err |
||||
} |
||||
resp, err := c.getParsedResponse("POST", fmt.Sprintf("/repos/%s/%s/branch_protections", owner, repo), jsonHeader, bytes.NewReader(body), bp) |
||||
return bp, resp, err |
||||
} |
||||
|
||||
// EditBranchProtection edits a branch protection for a repo
|
||||
func (c *Client) EditBranchProtection(owner, repo, name string, opt EditBranchProtectionOption) (*BranchProtection, *Response, error) { |
||||
if err := c.CheckServerVersionConstraint(">=1.12.0"); err != nil { |
||||
return nil, nil, err |
||||
} |
||||
bp := new(BranchProtection) |
||||
body, err := json.Marshal(&opt) |
||||
if err != nil { |
||||
return nil, nil, err |
||||
} |
||||
resp, err := c.getParsedResponse("PATCH", fmt.Sprintf("/repos/%s/%s/branch_protections/%s", owner, repo, name), jsonHeader, bytes.NewReader(body), bp) |
||||
return bp, resp, err |
||||
} |
||||
|
||||
// DeleteBranchProtection deletes a branch protection for a repo
|
||||
func (c *Client) DeleteBranchProtection(owner, repo, name string) (*Response, error) { |
||||
if err := c.CheckServerVersionConstraint(">=1.12.0"); err != nil { |
||||
return nil, err |
||||
} |
||||
_, resp, err := c.getResponse("DELETE", fmt.Sprintf("/repos/%s/%s/branch_protections/%s", owner, repo, name), jsonHeader, nil) |
||||
return resp, err |
||||
} |
@ -0,0 +1,97 @@ |
||||
// Copyright 2016 The Gogs 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" |
||||
) |
||||
|
||||
// ListCollaboratorsOptions options for listing a repository's collaborators
|
||||
type ListCollaboratorsOptions struct { |
||||
ListOptions |
||||
} |
||||
|
||||
// ListCollaborators list a repository's collaborators
|
||||
func (c *Client) ListCollaborators(user, repo string, opt ListCollaboratorsOptions) ([]*User, *Response, error) { |
||||
opt.setDefaults() |
||||
collaborators := make([]*User, 0, opt.PageSize) |
||||
resp, err := c.getParsedResponse("GET", |
||||
fmt.Sprintf("/repos/%s/%s/collaborators?%s", user, repo, opt.getURLQuery().Encode()), |
||||
nil, nil, &collaborators) |
||||
return collaborators, resp, err |
||||
} |
||||
|
||||
// IsCollaborator check if a user is a collaborator of a repository
|
||||
func (c *Client) IsCollaborator(user, repo, collaborator string) (bool, *Response, error) { |
||||
status, resp, err := c.getStatusCode("GET", fmt.Sprintf("/repos/%s/%s/collaborators/%s", user, repo, collaborator), nil, nil) |
||||
if err != nil { |
||||
return false, resp, err |
||||
} |
||||
if status == 204 { |
||||
return true, resp, nil |
||||
} |
||||
return false, resp, nil |
||||
} |
||||
|
||||
// AddCollaboratorOption options when adding a user as a collaborator of a repository
|
||||
type AddCollaboratorOption struct { |
||||
Permission *AccessMode `json:"permission"` |
||||
} |
||||
|
||||
// AccessMode represent the grade of access you have to something
|
||||
type AccessMode string |
||||
|
||||
const ( |
||||
// AccessModeNone no access
|
||||
AccessModeNone AccessMode = "none" |
||||
// AccessModeRead read access
|
||||
AccessModeRead AccessMode = "read" |
||||
// AccessModeWrite write access
|
||||
AccessModeWrite AccessMode = "write" |
||||
// AccessModeAdmin admin access
|
||||
AccessModeAdmin AccessMode = "admin" |
||||
// AccessModeOwner owner
|
||||
AccessModeOwner AccessMode = "owner" |
||||
) |
||||
|
||||
// Validate the AddCollaboratorOption struct
|
||||
func (opt AddCollaboratorOption) Validate() error { |
||||
if opt.Permission != nil { |
||||
if *opt.Permission == AccessModeOwner { |
||||
*opt.Permission = AccessModeAdmin |
||||
return nil |
||||
} |
||||
if *opt.Permission == AccessModeNone { |
||||
opt.Permission = nil |
||||
return nil |
||||
} |
||||
if *opt.Permission != AccessModeRead && *opt.Permission != AccessModeWrite && *opt.Permission != AccessModeAdmin { |
||||
return fmt.Errorf("permission mode invalid") |
||||
} |
||||
} |
||||
return nil |
||||
} |
||||
|
||||
// AddCollaborator add some user as a collaborator of a repository
|
||||
func (c *Client) AddCollaborator(user, repo, collaborator string, opt AddCollaboratorOption) (*Response, error) { |
||||
if err := opt.Validate(); err != nil { |
||||
return nil, err |
||||
} |
||||
body, err := json.Marshal(&opt) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
_, resp, err := c.getResponse("PUT", fmt.Sprintf("/repos/%s/%s/collaborators/%s", user, repo, collaborator), jsonHeader, bytes.NewReader(body)) |
||||
return resp, err |
||||
} |
||||
|
||||
// DeleteCollaborator remove a collaborator from a repository
|
||||
func (c *Client) DeleteCollaborator(user, repo, collaborator string) (*Response, error) { |
||||
_, resp, err := c.getResponse("DELETE", |
||||
fmt.Sprintf("/repos/%s/%s/collaborators/%s", user, repo, collaborator), nil, nil) |
||||
return resp, err |
||||
} |
@ -0,0 +1,88 @@ |
||||
// Copyright 2018 The Gogs Authors. All rights reserved.
|
||||
// Copyright 2019 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 ( |
||||
"fmt" |
||||
"net/url" |
||||
"time" |
||||
) |
||||
|
||||
// Identity for a person's identity like an author or committer
|
||||
type Identity struct { |
||||
Name string `json:"name"` |
||||
Email string `json:"email"` |
||||
} |
||||
|
||||
// CommitMeta contains meta information of a commit in terms of API.
|
||||
type CommitMeta struct { |
||||
URL string `json:"url"` |
||||
SHA string `json:"sha"` |
||||
} |
||||
|
||||
// CommitUser contains information of a user in the context of a commit.
|
||||
type CommitUser struct { |
||||
Identity |
||||
Date string `json:"date"` |
||||
} |
||||
|
||||
// RepoCommit contains information of a commit in the context of a repository.
|
||||
type RepoCommit struct { |
||||
URL string `json:"url"` |
||||
Author *CommitUser `json:"author"` |
||||
Committer *CommitUser `json:"committer"` |
||||
Message string `json:"message"` |
||||
Tree *CommitMeta `json:"tree"` |
||||
} |
||||
|
||||
// Commit contains information generated from a Git commit.
|
||||
type Commit struct { |
||||
*CommitMeta |
||||
HTMLURL string `json:"html_url"` |
||||
RepoCommit *RepoCommit `json:"commit"` |
||||
Author *User `json:"author"` |
||||
Committer *User `json:"committer"` |
||||
Parents []*CommitMeta `json:"parents"` |
||||
} |
||||
|
||||
// CommitDateOptions store dates for GIT_AUTHOR_DATE and GIT_COMMITTER_DATE
|
||||
type CommitDateOptions struct { |
||||
Author time.Time `json:"author"` |
||||
Committer time.Time `json:"committer"` |
||||
} |
||||
|
||||
// GetSingleCommit returns a single commit
|
||||
func (c *Client) GetSingleCommit(user, repo, commitID string) (*Commit, *Response, error) { |
||||
commit := new(Commit) |
||||
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/git/commits/%s", user, repo, commitID), nil, nil, &commit) |
||||
return commit, resp, err |
||||
} |
||||
|
||||
// ListCommitOptions list commit options
|
||||
type ListCommitOptions struct { |
||||
ListOptions |
||||
//SHA or branch to start listing commits from (usually 'master')
|
||||
SHA string |
||||
} |
||||
|
||||
// QueryEncode turns options into querystring argument
|
||||
func (opt *ListCommitOptions) QueryEncode() string { |
||||
query := opt.ListOptions.getURLQuery() |
||||
if opt.SHA != "" { |
||||
query.Add("sha", opt.SHA) |
||||
} |
||||
return query.Encode() |
||||
} |
||||
|
||||
// ListRepoCommits return list of commits from a repo
|
||||
func (c *Client) ListRepoCommits(user, repo string, opt ListCommitOptions) ([]*Commit, *Response, error) { |
||||
link, _ := url.Parse(fmt.Sprintf("/repos/%s/%s/commits", user, repo)) |
||||
opt.setDefaults() |
||||
commits := make([]*Commit, 0, opt.PageSize) |
||||
link.RawQuery = opt.QueryEncode() |
||||
resp, err := c.getParsedResponse("GET", link.String(), nil, nil, &commits) |
||||
return commits, resp, err |
||||
} |
@ -0,0 +1,194 @@ |
||||
// Copyright 2014 The Gogs Authors. All rights reserved.
|
||||
// Copyright 2019 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" |
||||
) |
||||
|
||||
// FileOptions options for all file APIs
|
||||
type FileOptions struct { |
||||
// message (optional) for the commit of this file. if not supplied, a default message will be used
|
||||
Message string `json:"message"` |
||||
// branch (optional) to base this file from. if not given, the default branch is used
|
||||
BranchName string `json:"branch"` |
||||
// new_branch (optional) will make a new branch from `branch` before creating the file
|
||||
NewBranchName string `json:"new_branch"` |
||||
// `author` and `committer` are optional (if only one is given, it will be used for the other, otherwise the authenticated user will be used)
|
||||
Author Identity `json:"author"` |
||||
Committer Identity `json:"committer"` |
||||
Dates CommitDateOptions `json:"dates"` |
||||
} |
||||
|
||||
// CreateFileOptions options for creating files
|
||||
// Note: `author` and `committer` are optional (if only one is given, it will be used for the other, otherwise the authenticated user will be used)
|
||||
type CreateFileOptions struct { |
||||
FileOptions |
||||
// content must be base64 encoded
|
||||
// required: true
|
||||
Content string `json:"content"` |
||||
} |
||||
|
||||
// DeleteFileOptions options for deleting files (used for other File structs below)
|
||||
// Note: `author` and `committer` are optional (if only one is given, it will be used for the other, otherwise the authenticated user will be used)
|
||||
type DeleteFileOptions struct { |
||||
FileOptions |
||||
// sha is the SHA for the file that already exists
|
||||
// required: true
|
||||
SHA string `json:"sha"` |
||||
} |
||||
|
||||
// UpdateFileOptions options for updating files
|
||||
// Note: `author` and `committer` are optional (if only one is given, it will be used for the other, otherwise the authenticated user will be used)
|
||||
type UpdateFileOptions struct { |
||||
FileOptions |
||||
// sha is the SHA for the file that already exists
|
||||
// required: true
|
||||
SHA string `json:"sha"` |
||||
// content must be base64 encoded
|
||||
// required: true
|
||||
Content string `json:"content"` |
||||
// from_path (optional) is the path of the original file which will be moved/renamed to the path in the URL
|
||||
FromPath string `json:"from_path"` |
||||
} |
||||
|
||||
// FileLinksResponse contains the links for a repo's file
|
||||
type FileLinksResponse struct { |
||||
Self *string `json:"self"` |
||||
GitURL *string `json:"git"` |
||||
HTMLURL *string `json:"html"` |
||||
} |
||||
|
||||
// ContentsResponse contains information about a repo's entry's (dir, file, symlink, submodule) metadata and content
|
||||
type ContentsResponse struct { |
||||
Name string `json:"name"` |
||||
Path string `json:"path"` |
||||
SHA string `json:"sha"` |
||||
// `type` will be `file`, `dir`, `symlink`, or `submodule`
|
||||
Type string `json:"type"` |
||||
Size int64 `json:"size"` |
||||
// `encoding` is populated when `type` is `file`, otherwise null
|
||||
Encoding *string `json:"encoding"` |
||||
// `content` is populated when `type` is `file`, otherwise null
|
||||
Content *string `json:"content"` |
||||
// `target` is populated when `type` is `symlink`, otherwise null
|
||||
Target *string `json:"target"` |
||||
URL *string `json:"url"` |
||||
HTMLURL *string `json:"html_url"` |
||||
GitURL *string `json:"git_url"` |
||||
DownloadURL *string `json:"download_url"` |
||||
// `submodule_git_url` is populated when `type` is `submodule`, otherwise null
|
||||
SubmoduleGitURL *string `json:"submodule_git_url"` |
||||
Links *FileLinksResponse `json:"_links"` |
||||
} |
||||
|
||||
// FileCommitResponse contains information generated from a Git commit for a repo's file.
|
||||
type FileCommitResponse struct { |
||||
CommitMeta |
||||
HTMLURL string `json:"html_url"` |
||||
Author *CommitUser `json:"author"` |
||||
Committer *CommitUser `json:"committer"` |
||||
Parents []*CommitMeta `json:"parents"` |
||||
Message string `json:"message"` |
||||
Tree *CommitMeta `json:"tree"` |
||||
} |
||||
|
||||
// FileResponse contains information about a repo's file
|
||||
type FileResponse struct { |
||||
Content *ContentsResponse `json:"content"` |
||||
Commit *FileCommitResponse `json:"commit"` |
||||
Verification *PayloadCommitVerification `json:"verification"` |
||||
} |
||||
|
||||
// FileDeleteResponse contains information about a repo's file that was deleted
|
||||
type FileDeleteResponse struct { |
||||
Content interface{} `json:"content"` // to be set to nil
|
||||
Commit *FileCommitResponse `json:"commit"` |
||||
Verification *PayloadCommitVerification `json:"verification"` |
||||
} |
||||
|
||||
// GetFile downloads a file of repository, ref can be branch/tag/commit.
|
||||
// e.g.: ref -> master, tree -> macaron.go(no leading slash)
|
||||
func (c *Client) GetFile(user, repo, ref, tree string) ([]byte, *Response, error) { |
||||
return c.getResponse("GET", fmt.Sprintf("/repos/%s/%s/raw/%s/%s", user, repo, ref, tree), nil, nil) |
||||
} |
||||
|
||||
// GetContents get the metadata and contents (if a file) of an entry in a repository, or a list of entries if a dir
|
||||
// ref is optional
|
||||
func (c *Client) GetContents(owner, repo, ref, filepath string) (*ContentsResponse, *Response, error) { |
||||
cr := new(ContentsResponse) |
||||
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/contents/%s?ref=%s", owner, repo, filepath, ref), jsonHeader, nil, cr) |
||||
return cr, resp, err |
||||
} |
||||
|
||||
// CreateFile create a file in a repository
|
||||
func (c *Client) CreateFile(owner, repo, filepath string, opt CreateFileOptions) (*FileResponse, *Response, error) { |
||||
var err error |
||||
if opt.BranchName, err = c.setDefaultBranchForOldVersions(owner, repo, opt.BranchName); err != nil { |
||||
return nil, nil, err |
||||
} |
||||
|
||||
body, err := json.Marshal(&opt) |
||||
if err != nil { |
||||
return nil, nil, err |
||||
} |
||||
fr := new(FileResponse) |
||||
resp, err := c.getParsedResponse("POST", fmt.Sprintf("/repos/%s/%s/contents/%s", owner, repo, filepath), jsonHeader, bytes.NewReader(body), fr) |
||||
return fr, resp, err |
||||
} |
||||
|
||||
// UpdateFile update a file in a repository
|
||||
func (c *Client) UpdateFile(owner, repo, filepath string, opt UpdateFileOptions) (*FileResponse, *Response, error) { |
||||
var err error |
||||
if opt.BranchName, err = c.setDefaultBranchForOldVersions(owner, repo, opt.BranchName); err != nil { |
||||
return nil, nil, err |
||||
} |
||||
|
||||
body, err := json.Marshal(&opt) |
||||
if err != nil { |
||||
return nil, nil, err |
||||
} |
||||
fr := new(FileResponse) |
||||
resp, err := c.getParsedResponse("PUT", fmt.Sprintf("/repos/%s/%s/contents/%s", owner, repo, filepath), jsonHeader, bytes.NewReader(body), fr) |
||||
return fr, resp, err |
||||
} |
||||
|
||||
// DeleteFile delete a file from repository
|
||||
func (c *Client) DeleteFile(owner, repo, filepath string, opt DeleteFileOptions) (*Response, error) { |
||||
var err error |
||||
if opt.BranchName, err = c.setDefaultBranchForOldVersions(owner, repo, opt.BranchName); err != nil { |
||||
return nil, err |
||||
} |
||||
|
||||
body, err := json.Marshal(&opt) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
status, resp, err := c.getStatusCode("DELETE", fmt.Sprintf("/repos/%s/%s/contents/%s", owner, repo, filepath), jsonHeader, bytes.NewReader(body)) |
||||
if err != nil { |
||||
return resp, err |
||||
} |
||||
if status != 200 && status != 204 { |
||||
return resp, fmt.Errorf("unexpected Status: %d", status) |
||||
} |
||||
return resp, nil |
||||
} |
||||
|
||||
func (c *Client) setDefaultBranchForOldVersions(owner, repo, branch string) (string, error) { |
||||
if len(branch) == 0 { |
||||
// Gitea >= 1.12.0 Use DefaultBranch on "", mimic this for older versions
|
||||
if c.CheckServerVersionConstraint(">=1.12.0") != nil { |
||||
r, _, err := c.GetRepo(owner, repo) |
||||
if err != nil { |
||||
return "", err |
||||
} |
||||
return r.DefaultBranch, nil |
||||
} |
||||
} |
||||
return branch, nil |
||||
} |
@ -0,0 +1,79 @@ |
||||
// Copyright 2015 The Gogs 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" |
||||
"net/url" |
||||
"time" |
||||
) |
||||
|
||||
// DeployKey a deploy key
|
||||
type DeployKey struct { |
||||
ID int64 `json:"id"` |
||||
KeyID int64 `json:"key_id"` |
||||
Key string `json:"key"` |
||||
URL string `json:"url"` |
||||
Title string `json:"title"` |
||||
Fingerprint string `json:"fingerprint"` |
||||
Created time.Time `json:"created_at"` |
||||
ReadOnly bool `json:"read_only"` |
||||
Repository *Repository `json:"repository,omitempty"` |
||||
} |
||||
|
||||
// ListDeployKeysOptions options for listing a repository's deploy keys
|
||||
type ListDeployKeysOptions struct { |
||||
ListOptions |
||||
KeyID int64 |
||||
Fingerprint string |
||||
} |
||||
|
||||
// QueryEncode turns options into querystring argument
|
||||
func (opt *ListDeployKeysOptions) QueryEncode() string { |
||||
query := opt.getURLQuery() |
||||
if opt.KeyID > 0 { |
||||
query.Add("key_id", fmt.Sprintf("%d", opt.KeyID)) |
||||
} |
||||
if len(opt.Fingerprint) > 0 { |
||||
query.Add("fingerprint", opt.Fingerprint) |
||||
} |
||||
return query.Encode() |
||||
} |
||||
|
||||
// ListDeployKeys list all the deploy keys of one repository
|
||||
func (c *Client) ListDeployKeys(user, repo string, opt ListDeployKeysOptions) ([]*DeployKey, *Response, error) { |
||||
link, _ := url.Parse(fmt.Sprintf("/repos/%s/%s/keys", user, repo)) |
||||
opt.setDefaults() |
||||
link.RawQuery = opt.QueryEncode() |
||||
keys := make([]*DeployKey, 0, opt.PageSize) |
||||
resp, err := c.getParsedResponse("GET", link.String(), nil, nil, &keys) |
||||
return keys, resp, err |
||||
} |
||||
|
||||
// GetDeployKey get one deploy key with key id
|
||||
func (c *Client) GetDeployKey(user, repo string, keyID int64) (*DeployKey, *Response, error) { |
||||
key := new(DeployKey) |
||||
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/keys/%d", user, repo, keyID), nil, nil, &key) |
||||
return key, resp, err |
||||
} |
||||
|
||||
// CreateDeployKey options when create one deploy key
|
||||
func (c *Client) CreateDeployKey(user, repo string, opt CreateKeyOption) (*DeployKey, *Response, error) { |
||||
body, err := json.Marshal(&opt) |
||||
if err != nil { |
||||
return nil, nil, err |
||||
} |
||||
key := new(DeployKey) |
||||
resp, err := c.getParsedResponse("POST", fmt.Sprintf("/repos/%s/%s/keys", user, repo), jsonHeader, bytes.NewReader(body), key) |
||||
return key, resp, err |
||||
} |
||||
|
||||
// DeleteDeployKey delete deploy key with key id
|
||||
func (c *Client) DeleteDeployKey(owner, repo string, keyID int64) (*Response, error) { |
||||
_, resp, err := c.getResponse("DELETE", fmt.Sprintf("/repos/%s/%s/keys/%d", owner, repo, keyID), nil, nil) |
||||
return resp, err |
||||
} |
@ -0,0 +1,115 @@ |
||||
// 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" |
||||
) |
||||
|
||||
// GitServiceType represents a git service
|
||||
type GitServiceType string |
||||
|
||||
const ( |
||||
// GitServicePlain represents a plain git service
|
||||
GitServicePlain GitServiceType = "git" |
||||
//GitServiceGithub represents github.com
|
||||
GitServiceGithub GitServiceType = "github" |
||||
// GitServiceGitlab represents a gitlab service
|
||||
GitServiceGitlab GitServiceType = "gitlab" |
||||
|
||||
// Not supported jet
|
||||
// // GitServiceGitea represents a gitea service
|
||||
// GitServiceGitea GitServiceType = "gitea"
|
||||
// // GitServiceGogs represents a gogs service
|
||||
// GitServiceGogs GitServiceType = "gogs"
|
||||
) |
||||
|
||||
// MigrateRepoOption options for migrating a repository from an external service
|
||||
type MigrateRepoOption struct { |
||||
RepoName string `json:"repo_name"` |
||||
RepoOwner string `json:"repo_owner"` |
||||
// deprecated use RepoOwner
|
||||
RepoOwnerID int64 `json:"uid"` |
||||
CloneAddr string `json:"clone_addr"` |
||||
Service GitServiceType `json:"service"` |
||||
AuthUsername string `json:"auth_username"` |
||||
AuthPassword string `json:"auth_password"` |
||||
AuthToken string `json:"auth_token"` |
||||
Mirror bool `json:"mirror"` |
||||
Private bool `json:"private"` |
||||
Description string `json:"description"` |
||||
Wiki bool `json:"wiki"` |
||||
Milestones bool `json:"milestones"` |
||||
Labels bool `json:"labels"` |
||||
Issues bool `json:"issues"` |
||||
PullRequests bool `json:"pull_requests"` |
||||
Releases bool `json:"releases"` |
||||
} |
||||
|
||||
// Validate the MigrateRepoOption struct
|
||||
func (opt *MigrateRepoOption) Validate() error { |
||||
// check user options
|
||||
if len(opt.CloneAddr) == 0 { |
||||
return fmt.Errorf("CloneAddr required") |
||||
} |
||||
if len(opt.RepoName) == 0 { |
||||
return fmt.Errorf("RepoName required") |
||||
} else if len(opt.RepoName) > 100 { |
||||
return fmt.Errorf("RepoName to long") |
||||
} |
||||
if len(opt.Description) > 255 { |
||||
return fmt.Errorf("Description to long") |
||||
} |
||||
switch opt.Service { |
||||
case GitServiceGithub: |
||||
if len(opt.AuthToken) == 0 { |
||||
return fmt.Errorf("github require token authentication") |
||||
} |
||||
} |
||||
return nil |
||||
} |
||||
|
||||
// MigrateRepo migrates a repository from other Git hosting sources for the authenticated user.
|
||||
//
|
||||
// To migrate a repository for a organization, the authenticated user must be a
|
||||
// owner of the specified organization.
|
||||
func (c *Client) MigrateRepo(opt MigrateRepoOption) (*Repository, *Response, error) { |
||||
if err := opt.Validate(); err != nil { |
||||
return nil, nil, err |
||||
} |
||||
|
||||
if err := c.CheckServerVersionConstraint(">=1.13.0"); err != nil { |
||||
if len(opt.AuthToken) != 0 { |
||||
// gitea <= 1.12 dont understand AuthToken
|
||||
opt.AuthUsername = opt.AuthToken |
||||
opt.AuthPassword, opt.AuthToken = "", "" |
||||
} |
||||
if len(opt.RepoOwner) != 0 { |
||||
// gitea <= 1.12 dont understand RepoOwner
|
||||
u, _, err := c.GetUserInfo(opt.RepoOwner) |
||||
if err != nil { |
||||
return nil, nil, err |
||||
} |
||||
opt.RepoOwnerID = u.ID |
||||
} else if opt.RepoOwnerID == 0 { |
||||
// gitea <= 1.12 require RepoOwnerID
|
||||
u, _, err := c.GetMyUserInfo() |
||||
if err != nil { |
||||
return nil, nil, err |
||||
} |
||||
opt.RepoOwnerID = u.ID |
||||
} |
||||
} |
||||
|
||||
body, err := json.Marshal(&opt) |
||||
if err != nil { |
||||
return nil, nil, err |
||||
} |
||||
repo := new(Repository) |
||||
resp, err := c.getParsedResponse("POST", "/repos/migrate", jsonHeader, bytes.NewReader(body), repo) |
||||
return repo, resp, err |
||||
} |
@ -0,0 +1,69 @@ |
||||
// Copyright 2018 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 ( |
||||
"encoding/json" |
||||
"errors" |
||||
"fmt" |
||||
"strings" |
||||
) |
||||
|
||||
// Reference represents a Git reference.
|
||||
type Reference struct { |
||||
Ref string `json:"ref"` |
||||
URL string `json:"url"` |
||||
Object *GitObject `json:"object"` |
||||
} |
||||
|
||||
// GitObject represents a Git object.
|
||||
type GitObject struct { |
||||
Type string `json:"type"` |
||||
SHA string `json:"sha"` |
||||
URL string `json:"url"` |
||||
} |
||||
|
||||
// GetRepoRef get one ref's information of one repository
|
||||
func (c *Client) GetRepoRef(user, repo, ref string) (*Reference, *Response, error) { |
||||
ref = strings.TrimPrefix(ref, "refs/") |
||||
r := new(Reference) |
||||
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/git/refs/%s", user, repo, ref), nil, nil, &r) |
||||
if _, ok := err.(*json.UnmarshalTypeError); ok { |
||||
// Multiple refs
|
||||
return nil, resp, errors.New("no exact match found for this ref") |
||||
} else if err != nil { |
||||
return nil, resp, err |
||||
} |
||||
|
||||
return r, resp, nil |
||||
} |
||||
|
||||
// GetRepoRefs get list of ref's information of one repository
|
||||
func (c *Client) GetRepoRefs(user, repo, ref string) ([]*Reference, *Response, error) { |
||||
ref = strings.TrimPrefix(ref, "refs/") |
||||
data, resp, err := c.getResponse("GET", fmt.Sprintf("/repos/%s/%s/git/refs/%s", user, repo, ref), nil, nil) |
||||
if err != nil { |
||||
return nil, resp, err |
||||
} |
||||
|
||||
// Attempt to unmarshal single returned ref.
|
||||
r := new(Reference) |
||||
refErr := json.Unmarshal(data, r) |
||||
if refErr == nil { |
||||
return []*Reference{r}, resp, nil |
||||
} |
||||
|
||||
// Attempt to unmarshal multiple refs.
|
||||
var rs []*Reference |
||||
refsErr := json.Unmarshal(data, &rs) |
||||
if refsErr == nil { |
||||
if len(rs) == 0 { |
||||
return nil, resp, errors.New("unexpected response: an array of refs with length 0") |
||||
} |
||||
return rs, resp, nil |
||||
} |
||||
|
||||
return nil, resp, fmt.Errorf("unmarshalling failed for both single and multiple refs: %s and %s", refErr, refsErr) |
||||
} |
@ -0,0 +1,31 @@ |
||||
// Copyright 2019 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 ( |
||||
"fmt" |
||||
) |
||||
|
||||
// Tag represents a repository tag
|
||||
type Tag struct { |
||||
Name string `json:"name"` |
||||
ID string `json:"id"` |
||||
Commit *CommitMeta `json:"commit"` |
||||
ZipballURL string `json:"zipball_url"` |
||||
TarballURL string `json:"tarball_url"` |
||||
} |
||||
|
||||
// ListRepoTagsOptions options for listing a repository's tags
|
||||
type ListRepoTagsOptions struct { |
||||
ListOptions |
||||
} |
||||
|
||||
// ListRepoTags list all the branches of one repository
|
||||
func (c *Client) ListRepoTags(user, repo string, opt ListRepoTagsOptions) ([]*Tag, *Response, error) { |
||||
opt.setDefaults() |
||||
tags := make([]*Tag, 0, opt.PageSize) |
||||
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/tags?%s", user, repo, opt.getURLQuery().Encode()), nil, nil, &tags) |
||||
return tags, resp, err |
||||
} |
@ -0,0 +1,58 @@ |
||||
// Copyright 2019 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" |
||||
) |
||||
|
||||
// ListRepoTopicsOptions options for listing repo's topics
|
||||
type ListRepoTopicsOptions struct { |
||||
ListOptions |
||||
} |
||||
|
||||
// topicsList represents a list of repo's topics
|
||||
type topicsList struct { |
||||
Topics []string `json:"topics"` |
||||
} |
||||
|
||||
// ListRepoTopics list all repository's topics
|
||||
func (c *Client) ListRepoTopics(user, repo string, opt ListRepoTopicsOptions) ([]string, *Response, error) { |
||||
opt.setDefaults() |
||||
|
||||
list := new(topicsList) |
||||
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/topics?%s", user, repo, opt.getURLQuery().Encode()), nil, nil, list) |
||||
if err != nil { |
||||
return nil, resp, err |
||||
} |
||||
return list.Topics, resp, nil |
||||
} |
||||
|
||||
// SetRepoTopics replaces the list of repo's topics
|
||||
func (c *Client) SetRepoTopics(user, repo string, list []string) (*Response, error) { |
||||
|
||||
l := topicsList{Topics: list} |
||||
|
||||
body, err := json.Marshal(&l) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
_, resp, err := c.getResponse("PUT", fmt.Sprintf("/repos/%s/%s/topics", user, repo), jsonHeader, bytes.NewReader(body)) |
||||
return resp, err |
||||
} |
||||
|
||||
// AddRepoTopic adds a topic to a repo's topics list
|
||||
func (c *Client) AddRepoTopic(user, repo, topic string) (*Response, error) { |
||||
_, resp, err := c.getResponse("PUT", fmt.Sprintf("/repos/%s/%s/topics/%s", user, repo, topic), nil, nil) |
||||
return resp, err |
||||
} |
||||
|
||||
// DeleteRepoTopic deletes a topic from repo's topics list
|
||||
func (c *Client) DeleteRepoTopic(user, repo, topic string) (*Response, error) { |
||||
_, resp, err := c.getResponse("DELETE", fmt.Sprintf("/repos/%s/%s/topics/%s", user, repo, topic), nil, nil) |
||||
return resp, err |
||||
} |
@ -0,0 +1,33 @@ |
||||
// 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" |
||||
) |
||||
|
||||
// TransferRepoOption options when transfer a repository's ownership
|
||||
type TransferRepoOption struct { |
||||
// required: true
|
||||
NewOwner string `json:"new_owner"` |
||||
// ID of the team or teams to add to the repository. Teams can only be added to organization-owned repositories.
|
||||
TeamIDs *[]int64 `json:"team_ids"` |
||||
} |
||||
|
||||
// TransferRepo transfers the ownership of a repository
|
||||
func (c *Client) TransferRepo(owner, reponame string, opt TransferRepoOption) (*Repository, *Response, error) { |
||||
if err := c.CheckServerVersionConstraint(">=1.12.0"); 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("/repos/%s/%s/transfer", owner, reponame), jsonHeader, bytes.NewReader(body), repo) |
||||
return repo, resp, err |
||||
} |
@ -0,0 +1,41 @@ |
||||
// Copyright 2018 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 ( |
||||
"fmt" |
||||
) |
||||
|
||||
// GitEntry represents a git tree
|
||||
type GitEntry struct { |
||||
Path string `json:"path"` |
||||
Mode string `json:"mode"` |
||||
Type string `json:"type"` |
||||
Size int64 `json:"size"` |
||||
SHA string `json:"sha"` |
||||
URL string `json:"url"` |
||||
} |
||||
|
||||
// GitTreeResponse returns a git tree
|
||||
type GitTreeResponse struct { |
||||
SHA string `json:"sha"` |
||||
URL string `json:"url"` |
||||
Entries []GitEntry `json:"tree"` |
||||
Truncated bool `json:"truncated"` |
||||
Page int `json:"page"` |
||||
TotalCount int `json:"total_count"` |
||||
} |
||||
|
||||
// GetTrees downloads a file of repository, ref can be branch/tag/commit.
|
||||
// e.g.: ref -> master, tree -> macaron.go(no leading slash)
|
||||
func (c *Client) GetTrees(user, repo, ref string, recursive bool) (*GitTreeResponse, *Response, error) { |
||||
trees := new(GitTreeResponse) |
||||
var path = fmt.Sprintf("/repos/%s/%s/git/trees/%s", user, repo, ref) |
||||
if recursive { |
||||
path += "?recursive=1" |
||||
} |
||||
resp, err := c.getParsedResponse("GET", path, nil, nil, trees) |
||||
return trees, resp, err |
||||
} |
@ -0,0 +1,75 @@ |
||||
// Copyright 2017 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 ( |
||||
"fmt" |
||||
"net/http" |
||||
"time" |
||||
) |
||||
|
||||
// WatchInfo represents an API watch status of one repository
|
||||
type WatchInfo struct { |
||||
Subscribed bool `json:"subscribed"` |
||||
Ignored bool `json:"ignored"` |
||||
Reason interface{} `json:"reason"` |
||||
CreatedAt time.Time `json:"created_at"` |
||||
URL string `json:"url"` |
||||
RepositoryURL string `json:"repository_url"` |
||||
} |
||||
|
||||
// GetWatchedRepos list all the watched repos of user
|
||||
func (c *Client) GetWatchedRepos(user string) ([]*Repository, *Response, error) { |
||||
repos := make([]*Repository, 0, 10) |
||||
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/users/%s/subscriptions", user), nil, nil, &repos) |
||||
return repos, resp, err |
||||
} |
||||
|
||||
// GetMyWatchedRepos list repositories watched by the authenticated user
|
||||
func (c *Client) GetMyWatchedRepos() ([]*Repository, *Response, error) { |
||||
repos := make([]*Repository, 0, 10) |
||||
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/user/subscriptions"), nil, nil, &repos) |
||||
return repos, resp, err |
||||
} |
||||
|
||||
// CheckRepoWatch check if the current user is watching a repo
|
||||
func (c *Client) CheckRepoWatch(repoUser, repoName string) (bool, *Response, error) { |
||||
status, resp, err := c.getStatusCode("GET", fmt.Sprintf("/repos/%s/%s/subscription", repoUser, repoName), nil, nil) |
||||
if err != nil { |
||||
return false, resp, err |
||||
} |
||||
switch status { |
||||
case http.StatusNotFound: |
||||
return false, resp, nil |
||||
case http.StatusOK: |
||||
return true, resp, nil |
||||
default: |
||||
return false, resp, fmt.Errorf("unexpected Status: %d", status) |
||||
} |
||||
} |
||||
|
||||
// WatchRepo start to watch a repository
|
||||
func (c *Client) WatchRepo(repoUser, repoName string) (*Response, error) { |
||||
status, resp, err := c.getStatusCode("PUT", fmt.Sprintf("/repos/%s/%s/subscription", repoUser, repoName), nil, nil) |
||||
if err != nil { |
||||
return resp, err |
||||
} |
||||
if status == http.StatusOK { |
||||
return resp, nil |
||||
} |
||||
return resp, fmt.Errorf("unexpected Status: %d", status) |
||||
} |
||||
|
||||
// UnWatchRepo stop to watch a repository
|
||||
func (c *Client) UnWatchRepo(repoUser, repoName string) (*Response, error) { |
||||
status, resp, err := c.getStatusCode("DELETE", fmt.Sprintf("/repos/%s/%s/subscription", repoUser, repoName), nil, nil) |
||||
if err != nil { |
||||
return resp, err |
||||
} |
||||
if status == http.StatusNoContent { |
||||
return resp, nil |
||||
} |
||||
return resp, fmt.Errorf("unexpected Status: %d", status) |
||||
} |
@ -0,0 +1,72 @@ |
||||
// 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 |
||||
|
||||
// GlobalUISettings represent the global ui settings of a gitea instance witch is exposed by API
|
||||
type GlobalUISettings struct { |
||||
AllowedReactions []string `json:"allowed_reactions"` |
||||
} |
||||
|
||||
// GlobalRepoSettings represent the global repository settings of a gitea instance witch is exposed by API
|
||||
type GlobalRepoSettings struct { |
||||
MirrorsDisabled bool `json:"mirrors_disabled"` |
||||
HTTPGitDisabled bool `json:"http_git_disabled"` |
||||
} |
||||
|
||||
// GlobalAPISettings contains global api settings exposed by it
|
||||
type GlobalAPISettings struct { |
||||
MaxResponseItems int `json:"max_response_items"` |
||||
DefaultPagingNum int `json:"default_paging_num"` |
||||
DefaultGitTreesPerPage int `json:"default_git_trees_per_page"` |
||||
DefaultMaxBlobSize int64 `json:"default_max_blob_size"` |
||||
} |
||||
|
||||
// GlobalAttachmentSettings contains global Attachment settings exposed by API
|
||||
type GlobalAttachmentSettings struct { |
||||
Enabled bool `json:"enabled"` |
||||
AllowedTypes string `json:"allowed_types"` |
||||
MaxSize int64 `json:"max_size"` |
||||
MaxFiles int `json:"max_files"` |
||||
} |
||||
|
||||
// GetGlobalUISettings get global ui settings witch are exposed by API
|
||||
func (c *Client) GetGlobalUISettings() (*GlobalUISettings, *Response, error) { |
||||
if err := c.CheckServerVersionConstraint(">=1.13.0"); err != nil { |
||||
return nil, nil, err |
||||
} |
||||
conf := new(GlobalUISettings) |
||||
resp, err := c.getParsedResponse("GET", "/settings/ui", jsonHeader, nil, &conf) |
||||
return conf, resp, err |
||||
} |
||||
|
||||
// GetGlobalRepoSettings get global repository settings witch are exposed by API
|
||||
func (c *Client) GetGlobalRepoSettings() (*GlobalRepoSettings, *Response, error) { |
||||
if err := c.CheckServerVersionConstraint(">=1.13.0"); err != nil { |
||||
return nil, nil, err |
||||
} |
||||
conf := new(GlobalRepoSettings) |
||||
resp, err := c.getParsedResponse("GET", "/settings/repository", jsonHeader, nil, &conf) |
||||
return conf, resp, err |
||||
} |
||||
|
||||
// GetGlobalAPISettings get global api settings witch are exposed by it
|
||||
func (c *Client) GetGlobalAPISettings() (*GlobalAPISettings, *Response, error) { |
||||
if err := c.CheckServerVersionConstraint(">=1.13.0"); err != nil { |
||||
return nil, nil, err |
||||
} |
||||
conf := new(GlobalAPISettings) |
||||
resp, err := c.getParsedResponse("GET", "/settings/api", jsonHeader, nil, &conf) |
||||
return conf, resp, err |
||||
} |
||||
|
||||
// GetGlobalAttachmentSettings get global repository settings witch are exposed by API
|
||||
func (c *Client) GetGlobalAttachmentSettings() (*GlobalAttachmentSettings, *Response, error) { |
||||
if err := c.CheckServerVersionConstraint(">=1.13.0"); err != nil { |
||||
return nil, nil, err |
||||
} |
||||
conf := new(GlobalAttachmentSettings) |
||||
resp, err := c.getParsedResponse("GET", "/settings/attachment", jsonHeader, nil, &conf) |
||||
return conf, resp, err |
||||
} |
@ -0,0 +1,92 @@ |
||||
// Copyright 2017 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" |
||||
"time" |
||||
) |
||||
|
||||
// StatusState holds the state of a Status
|
||||
// It can be "pending", "success", "error", "failure", and "warning"
|
||||
type StatusState string |
||||
|
||||
const ( |
||||
// StatusPending is for when the Status is Pending
|
||||
StatusPending StatusState = "pending" |
||||
// StatusSuccess is for when the Status is Success
|
||||
StatusSuccess StatusState = "success" |
||||
// StatusError is for when the Status is Error
|
||||
StatusError StatusState = "error" |
||||
// StatusFailure is for when the Status is Failure
|
||||
StatusFailure StatusState = "failure" |
||||
// StatusWarning is for when the Status is Warning
|
||||
StatusWarning StatusState = "warning" |
||||
) |
||||
|
||||
// Status holds a single Status of a single Commit
|
||||
type Status struct { |
||||
ID int64 `json:"id"` |
||||
State StatusState `json:"status"` |
||||
TargetURL string `json:"target_url"` |
||||
Description string `json:"description"` |
||||
URL string `json:"url"` |
||||
Context string `json:"context"` |
||||
Creator *User `json:"creator"` |
||||
Created time.Time `json:"created_at"` |
||||
Updated time.Time `json:"updated_at"` |
||||
} |
||||
|
||||
// CreateStatusOption holds the information needed to create a new Status for a Commit
|
||||
type CreateStatusOption struct { |
||||
State StatusState `json:"state"` |
||||
TargetURL string `json:"target_url"` |
||||
Description string `json:"description"` |
||||
Context string `json:"context"` |
||||
} |
||||
|
||||
// CreateStatus creates a new Status for a given Commit
|
||||
func (c *Client) CreateStatus(owner, repo, sha string, opts CreateStatusOption) (*Status, *Response, error) { |
||||
body, err := json.Marshal(&opts) |
||||
if err != nil { |
||||
return nil, nil, err |
||||
} |
||||
status := new(Status) |
||||
resp, err := c.getParsedResponse("POST", fmt.Sprintf("/repos/%s/%s/statuses/%s", owner, repo, sha), jsonHeader, bytes.NewReader(body), status) |
||||
return status, resp, err |
||||
} |
||||
|
||||
// ListStatusesOption options for listing a repository's commit's statuses
|
||||
type ListStatusesOption struct { |
||||
ListOptions |
||||
} |
||||
|
||||
// ListStatuses returns all statuses for a given Commit
|
||||
func (c *Client) ListStatuses(owner, repo, sha string, opt ListStatusesOption) ([]*Status, *Response, error) { |
||||
opt.setDefaults() |
||||
statuses := make([]*Status, 0, opt.PageSize) |
||||
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/commits/%s/statuses?%s", owner, repo, sha, opt.getURLQuery().Encode()), nil, nil, &statuses) |
||||
return statuses, resp, err |
||||
} |
||||
|
||||
// CombinedStatus holds the combined state of several statuses for a single commit
|
||||
type CombinedStatus struct { |
||||
State StatusState `json:"state"` |
||||
SHA string `json:"sha"` |
||||
TotalCount int `json:"total_count"` |
||||
Statuses []*Status `json:"statuses"` |
||||
Repository *Repository `json:"repository"` |
||||
CommitURL string `json:"commit_url"` |
||||
URL string `json:"url"` |
||||
} |
||||
|
||||
// GetCombinedStatus returns the CombinedStatus for a given Commit
|
||||
func (c *Client) GetCombinedStatus(owner, repo, sha string) (*CombinedStatus, *Response, error) { |
||||
status := new(CombinedStatus) |
||||
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/commits/%s/status", owner, repo, sha), nil, nil, status) |
||||
return status, resp, err |
||||
} |
@ -0,0 +1,43 @@ |
||||
// Copyright 2014 The Gogs 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 ( |
||||
"fmt" |
||||
"time" |
||||
) |
||||
|
||||
// User represents a user
|
||||
type User struct { |
||||
// the user's id
|
||||
ID int64 `json:"id"` |
||||
// the user's username
|
||||
UserName string `json:"login"` |
||||
// the user's full name
|
||||
FullName string `json:"full_name"` |
||||
Email string `json:"email"` |
||||
// URL to the user's avatar
|
||||
AvatarURL string `json:"avatar_url"` |
||||
// User locale
|
||||
Language string `json:"language"` |
||||
// Is the user an administrator
|
||||
IsAdmin bool `json:"is_admin"` |
||||
LastLogin time.Time `json:"last_login,omitempty"` |
||||
Created time.Time `json:"created,omitempty"` |
||||
} |
||||
|
||||
// GetUserInfo get user info by user's name
|
||||
func (c *Client) GetUserInfo(user string) (*User, *Response, error) { |
||||
u := new(User) |
||||
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/users/%s", user), nil, nil, u) |
||||
return u, resp, err |
||||
} |
||||
|
||||
// GetMyUserInfo get user info of current user
|
||||
func (c *Client) GetMyUserInfo() (*User, *Response, error) { |
||||
u := new(User) |
||||
resp, err := c.getParsedResponse("GET", "/user", nil, nil, u) |
||||
return u, resp, err |
||||
} |
@ -0,0 +1,80 @@ |
||||
// Copyright 2014 The Gogs Authors. All rights reserved.
|
||||
// Copyright 2019 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" |
||||
"reflect" |
||||
) |
||||
|
||||
// AccessToken represents an API access token.
|
||||
type AccessToken struct { |
||||
ID int64 `json:"id"` |
||||
Name string `json:"name"` |
||||
Token string `json:"sha1"` |
||||
TokenLastEight string `json:"token_last_eight"` |
||||
} |
||||
|
||||
// ListAccessTokensOptions options for listing a users's access tokens
|
||||
type ListAccessTokensOptions struct { |
||||
ListOptions |
||||
} |
||||
|
||||
// ListAccessTokens lists all the access tokens of user
|
||||
func (c *Client) ListAccessTokens(opts ListAccessTokensOptions) ([]*AccessToken, *Response, error) { |
||||
if len(c.username) == 0 { |
||||
return nil, nil, fmt.Errorf("\"username\" not set: only BasicAuth allowed") |
||||
} |
||||
opts.setDefaults() |
||||
tokens := make([]*AccessToken, 0, opts.PageSize) |
||||
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/users/%s/tokens?%s", c.username, opts.getURLQuery().Encode()), jsonHeader, nil, &tokens) |
||||
return tokens, resp, err |
||||
} |
||||
|
||||
// CreateAccessTokenOption options when create access token
|
||||
type CreateAccessTokenOption struct { |
||||
Name string `json:"name"` |
||||
} |
||||
|
||||
// CreateAccessToken create one access token with options
|
||||
func (c *Client) CreateAccessToken(opt CreateAccessTokenOption) (*AccessToken, *Response, error) { |
||||
if len(c.username) == 0 { |
||||
return nil, nil, fmt.Errorf("\"username\" not set: only BasicAuth allowed") |
||||
} |
||||
body, err := json.Marshal(&opt) |
||||
if err != nil { |
||||
return nil, nil, err |
||||
} |
||||
t := new(AccessToken) |
||||
resp, err := c.getParsedResponse("POST", fmt.Sprintf("/users/%s/tokens", c.username), jsonHeader, bytes.NewReader(body), t) |
||||
return t, resp, err |
||||
} |
||||
|
||||
// DeleteAccessToken delete token, identified by ID and if not available by name
|
||||
func (c *Client) DeleteAccessToken(value interface{}) (*Response, error) { |
||||
if len(c.username) == 0 { |
||||
return nil, fmt.Errorf("\"username\" not set: only BasicAuth allowed") |
||||
} |
||||
|
||||
var token = "" |
||||
|
||||
switch reflect.ValueOf(value).Kind() { |
||||
case reflect.Int64: |
||||
token = fmt.Sprintf("%d", value.(int64)) |
||||
case reflect.String: |
||||
if err := c.CheckServerVersionConstraint(">= 1.13.0"); err != nil { |
||||
return nil, err |
||||
} |
||||
token = value.(string) |
||||
default: |
||||
return nil, fmt.Errorf("only string and int64 supported") |
||||
} |
||||
|
||||
_, resp, err := c.getResponse("DELETE", fmt.Sprintf("/users/%s/tokens/%s", c.username, token), jsonHeader, nil) |
||||
return resp, err |
||||
} |
@ -0,0 +1,64 @@ |
||||
// Copyright 2015 The Gogs 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" |
||||
) |
||||
|
||||
// Email an email address belonging to a user
|
||||
type Email struct { |
||||
Email string `json:"email"` |
||||
Verified bool `json:"verified"` |
||||
Primary bool `json:"primary"` |
||||
} |
||||
|
||||
// ListEmailsOptions options for listing current's user emails
|
||||
type ListEmailsOptions struct { |
||||
ListOptions |
||||
} |
||||
|
||||
// ListEmails all the email addresses of user
|
||||
func (c *Client) ListEmails(opt ListEmailsOptions) ([]*Email, *Response, error) { |
||||
opt.setDefaults() |
||||
emails := make([]*Email, 0, opt.PageSize) |
||||
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/user/emails?%s", opt.getURLQuery().Encode()), nil, nil, &emails) |
||||
return emails, resp, err |
||||
} |
||||
|
||||
// CreateEmailOption options when creating email addresses
|
||||
type CreateEmailOption struct { |
||||
// email addresses to add
|
||||
Emails []string `json:"emails"` |
||||
} |
||||
|
||||
// AddEmail add one email to current user with options
|
||||
func (c *Client) AddEmail(opt CreateEmailOption) ([]*Email, *Response, error) { |
||||
body, err := json.Marshal(&opt) |
||||
if err != nil { |
||||
return nil, nil, err |
||||
} |
||||
emails := make([]*Email, 0, 3) |
||||
resp, err := c.getParsedResponse("POST", "/user/emails", jsonHeader, bytes.NewReader(body), &emails) |
||||
return emails, resp, err |
||||
} |
||||
|
||||
// DeleteEmailOption options when deleting email addresses
|
||||
type DeleteEmailOption struct { |
||||
// email addresses to delete
|
||||
Emails []string `json:"emails"` |
||||
} |
||||
|
||||
// DeleteEmail delete one email of current users'
|
||||
func (c *Client) DeleteEmail(opt DeleteEmailOption) (*Response, error) { |
||||
body, err := json.Marshal(&opt) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
_, resp, err := c.getResponse("DELETE", "/user/emails", jsonHeader, bytes.NewReader(body)) |
||||
return resp, err |
||||
} |
@ -0,0 +1,73 @@ |
||||
// Copyright 2015 The Gogs 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 "fmt" |
||||
|
||||
// ListFollowersOptions options for listing followers
|
||||
type ListFollowersOptions struct { |
||||
ListOptions |
||||
} |
||||
|
||||
// ListMyFollowers list all the followers of current user
|
||||
func (c *Client) ListMyFollowers(opt ListFollowersOptions) ([]*User, *Response, error) { |
||||
opt.setDefaults() |
||||
users := make([]*User, 0, opt.PageSize) |
||||
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/user/followers?%s", opt.getURLQuery().Encode()), nil, nil, &users) |
||||
return users, resp, err |
||||
} |
||||
|
||||
// ListFollowers list all the followers of one user
|
||||
func (c *Client) ListFollowers(user string, opt ListFollowersOptions) ([]*User, *Response, error) { |
||||
opt.setDefaults() |
||||
users := make([]*User, 0, opt.PageSize) |
||||
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/users/%s/followers?%s", user, opt.getURLQuery().Encode()), nil, nil, &users) |
||||
return users, resp, err |
||||
} |
||||
|
||||
// ListFollowingOptions options for listing a user's users being followed
|
||||
type ListFollowingOptions struct { |
||||
ListOptions |
||||
} |
||||
|
||||
// ListMyFollowing list all the users current user followed
|
||||
func (c *Client) ListMyFollowing(opt ListFollowingOptions) ([]*User, *Response, error) { |
||||
opt.setDefaults() |
||||
users := make([]*User, 0, opt.PageSize) |
||||
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/user/following?%s", opt.getURLQuery().Encode()), nil, nil, &users) |
||||
return users, resp, err |
||||
} |
||||
|
||||
// ListFollowing list all the users the user followed
|
||||
func (c *Client) ListFollowing(user string, opt ListFollowingOptions) ([]*User, *Response, error) { |
||||
opt.setDefaults() |
||||
users := make([]*User, 0, opt.PageSize) |
||||
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/users/%s/following?%s", user, opt.getURLQuery().Encode()), nil, nil, &users) |
||||
return users, resp, err |
||||
} |
||||
|
||||
// IsFollowing if current user followed the target
|
||||
func (c *Client) IsFollowing(target string) (bool, *Response) { |
||||
_, resp, err := c.getResponse("GET", fmt.Sprintf("/user/following/%s", target), nil, nil) |
||||
return err == nil, resp |
||||
} |
||||
|
||||
// IsUserFollowing if the user followed the target
|
||||
func (c *Client) IsUserFollowing(user, target string) (bool, *Response) { |
||||
_, resp, err := c.getResponse("GET", fmt.Sprintf("/users/%s/following/%s", user, target), nil, nil) |
||||
return err == nil, resp |
||||
} |
||||
|
||||
// Follow set current user follow the target
|
||||
func (c *Client) Follow(target string) (*Response, error) { |
||||
_, resp, err := c.getResponse("PUT", fmt.Sprintf("/user/following/%s", target), nil, nil) |
||||
return resp, err |
||||
} |
||||
|
||||
// Unfollow set current user unfollow the target
|
||||
func (c *Client) Unfollow(target string) (*Response, error) { |
||||
_, resp, err := c.getResponse("DELETE", fmt.Sprintf("/user/following/%s", target), nil, nil) |
||||
return resp, err |
||||
} |
@ -0,0 +1,86 @@ |
||||
// Copyright 2017 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" |
||||
"time" |
||||
) |
||||
|
||||
// GPGKey a user GPG key to sign commit and tag in repository
|
||||
type GPGKey struct { |
||||
ID int64 `json:"id"` |
||||
PrimaryKeyID string `json:"primary_key_id"` |
||||
KeyID string `json:"key_id"` |
||||
PublicKey string `json:"public_key"` |
||||
Emails []*GPGKeyEmail `json:"emails"` |
||||
SubsKey []*GPGKey `json:"subkeys"` |
||||
CanSign bool `json:"can_sign"` |
||||
CanEncryptComms bool `json:"can_encrypt_comms"` |
||||
CanEncryptStorage bool `json:"can_encrypt_storage"` |
||||
CanCertify bool `json:"can_certify"` |
||||
Created time.Time `json:"created_at,omitempty"` |
||||
Expires time.Time `json:"expires_at,omitempty"` |
||||
} |
||||
|
||||
// GPGKeyEmail an email attached to a GPGKey
|
||||
type GPGKeyEmail struct { |
||||
Email string `json:"email"` |
||||
Verified bool `json:"verified"` |
||||
} |
||||
|
||||
// ListGPGKeysOptions options for listing a user's GPGKeys
|
||||
type ListGPGKeysOptions struct { |
||||
ListOptions |
||||
} |
||||
|
||||
// ListGPGKeys list all the GPG keys of the user
|
||||
func (c *Client) ListGPGKeys(user string, opt ListGPGKeysOptions) ([]*GPGKey, *Response, error) { |
||||
opt.setDefaults() |
||||
keys := make([]*GPGKey, 0, opt.PageSize) |
||||
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/users/%s/gpg_keys?%s", user, opt.getURLQuery().Encode()), nil, nil, &keys) |
||||
return keys, resp, err |
||||
} |
||||
|
||||
// ListMyGPGKeys list all the GPG keys of current user
|
||||
func (c *Client) ListMyGPGKeys(opt *ListGPGKeysOptions) ([]*GPGKey, *Response, error) { |
||||
opt.setDefaults() |
||||
keys := make([]*GPGKey, 0, opt.PageSize) |
||||
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/user/gpg_keys?%s", opt.getURLQuery().Encode()), nil, nil, &keys) |
||||
return keys, resp, err |
||||
} |
||||
|
||||
// GetGPGKey get current user's GPG key by key id
|
||||
func (c *Client) GetGPGKey(keyID int64) (*GPGKey, *Response, error) { |
||||
key := new(GPGKey) |
||||
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/user/gpg_keys/%d", keyID), nil, nil, &key) |
||||
return key, resp, err |
||||
} |
||||
|
||||
// CreateGPGKeyOption options create user GPG key
|
||||
type CreateGPGKeyOption struct { |
||||
// An armored GPG key to add
|
||||
//
|
||||
ArmoredKey string `json:"armored_public_key"` |
||||
} |
||||
|
||||
// CreateGPGKey create GPG key with options
|
||||
func (c *Client) CreateGPGKey(opt CreateGPGKeyOption) (*GPGKey, *Response, error) { |
||||
body, err := json.Marshal(&opt) |
||||
if err != nil { |
||||
return nil, nil, err |
||||
} |
||||
key := new(GPGKey) |
||||
resp, err := c.getParsedResponse("POST", "/user/gpg_keys", jsonHeader, bytes.NewReader(body), key) |
||||
return key, resp, err |
||||
} |
||||
|
||||
// DeleteGPGKey delete GPG key with key id
|
||||
func (c *Client) DeleteGPGKey(keyID int64) (*Response, error) { |
||||
_, resp, err := c.getResponse("DELETE", fmt.Sprintf("/user/gpg_keys/%d", keyID), nil, nil) |
||||
return resp, err |
||||
} |
@ -0,0 +1,80 @@ |
||||
// Copyright 2015 The Gogs 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" |
||||
"time" |
||||
) |
||||
|
||||
// PublicKey publickey is a user key to push code to repository
|
||||
type PublicKey struct { |
||||
ID int64 `json:"id"` |
||||
Key string `json:"key"` |
||||
URL string `json:"url,omitempty"` |
||||
Title string `json:"title,omitempty"` |
||||
Fingerprint string `json:"fingerprint,omitempty"` |
||||
Created time.Time `json:"created_at,omitempty"` |
||||
Owner *User `json:"user,omitempty"` |
||||
ReadOnly bool `json:"read_only,omitempty"` |
||||
KeyType string `json:"key_type,omitempty"` |
||||
} |
||||
|
||||
// ListPublicKeysOptions options for listing a user's PublicKeys
|
||||
type ListPublicKeysOptions struct { |
||||
ListOptions |
||||
} |
||||
|
||||
// ListPublicKeys list all the public keys of the user
|
||||
func (c *Client) ListPublicKeys(user string, opt ListPublicKeysOptions) ([]*PublicKey, *Response, error) { |
||||
opt.setDefaults() |
||||
keys := make([]*PublicKey, 0, opt.PageSize) |
||||
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/users/%s/keys?%s", user, opt.getURLQuery().Encode()), nil, nil, &keys) |
||||
return keys, resp, err |
||||
} |
||||
|
||||
// ListMyPublicKeys list all the public keys of current user
|
||||
func (c *Client) ListMyPublicKeys(opt ListPublicKeysOptions) ([]*PublicKey, *Response, error) { |
||||
opt.setDefaults() |
||||
keys := make([]*PublicKey, 0, opt.PageSize) |
||||
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/user/keys?%s", opt.getURLQuery().Encode()), nil, nil, &keys) |
||||
return keys, resp, err |
||||
} |
||||
|
||||
// GetPublicKey get current user's public key by key id
|
||||
func (c *Client) GetPublicKey(keyID int64) (*PublicKey, *Response, error) { |
||||
key := new(PublicKey) |
||||
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/user/keys/%d", keyID), nil, nil, &key) |
||||
return key, resp, err |
||||
} |
||||
|
||||
// CreateKeyOption options when creating a key
|
||||
type CreateKeyOption struct { |
||||
// Title of the key to add
|
||||
Title string `json:"title"` |
||||
// An armored SSH key to add
|
||||
Key string `json:"key"` |
||||
// Describe if the key has only read access or read/write
|
||||
ReadOnly bool `json:"read_only"` |
||||
} |
||||
|
||||
// CreatePublicKey create public key with options
|
||||
func (c *Client) CreatePublicKey(opt CreateKeyOption) (*PublicKey, *Response, error) { |
||||
body, err := json.Marshal(&opt) |
||||
if err != nil { |
||||
return nil, nil, err |
||||
} |
||||
key := new(PublicKey) |
||||
resp, err := c.getParsedResponse("POST", "/user/keys", jsonHeader, bytes.NewReader(body), key) |
||||
return key, resp, err |
||||
} |
||||
|
||||
// DeletePublicKey delete public key with key id
|
||||
func (c *Client) DeletePublicKey(keyID int64) (*Response, error) { |
||||
_, resp, err := c.getResponse("DELETE", fmt.Sprintf("/user/keys/%d", keyID), nil, nil) |
||||
return resp, err |
||||
} |
@ -0,0 +1,44 @@ |
||||
// 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 ( |
||||
"fmt" |
||||
"net/url" |
||||
) |
||||
|
||||
type searchUsersResponse struct { |
||||
Users []*User `json:"data"` |
||||
} |
||||
|
||||
// SearchUsersOption options for SearchUsers
|
||||
type SearchUsersOption struct { |
||||
ListOptions |
||||
KeyWord string |
||||
} |
||||
|
||||
// QueryEncode turns options into querystring argument
|
||||
func (opt *SearchUsersOption) QueryEncode() string { |
||||
query := make(url.Values) |
||||
if opt.Page > 0 { |
||||
query.Add("page", fmt.Sprintf("%d", opt.Page)) |
||||
} |
||||
if opt.PageSize > 0 { |
||||
query.Add("limit", fmt.Sprintf("%d", opt.PageSize)) |
||||
} |
||||
if len(opt.KeyWord) > 0 { |
||||
query.Add("q", opt.KeyWord) |
||||
} |
||||
return query.Encode() |
||||
} |
||||
|
||||
// SearchUsers finds users by query
|
||||
func (c *Client) SearchUsers(opt SearchUsersOption) ([]*User, *Response, error) { |
||||
link, _ := url.Parse("/users/search") |
||||
link.RawQuery = opt.QueryEncode() |
||||
userResp := new(searchUsersResponse) |
||||
resp, err := c.getParsedResponse("GET", link.String(), nil, nil, &userResp) |
||||
return userResp.Users, resp, err |
||||
} |
@ -0,0 +1,58 @@ |
||||
// 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 ( |
||||
"fmt" |
||||
|
||||
"github.com/hashicorp/go-version" |
||||
) |
||||
|
||||
// ServerVersion returns the version of the server
|
||||
func (c *Client) ServerVersion() (string, *Response, error) { |
||||
var v = struct { |
||||
Version string `json:"version"` |
||||
}{} |
||||
resp, err := c.getParsedResponse("GET", "/version", nil, nil, &v) |
||||
return v.Version, resp, err |
||||
} |
||||
|
||||
// CheckServerVersionConstraint validates that the login's server satisfies a
|
||||
// given version constraint such as ">= 1.11.0+dev"
|
||||
func (c *Client) CheckServerVersionConstraint(constraint string) error { |
||||
c.versionLock.RLock() |
||||
if c.serverVersion == nil { |
||||
c.versionLock.RUnlock() |
||||
if err := c.loadClientServerVersion(); err != nil { |
||||
return err |
||||
} |
||||
} else { |
||||
c.versionLock.RUnlock() |
||||
} |
||||
|
||||
check, err := version.NewConstraint(constraint) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
if !check.Check(c.serverVersion) { |
||||
return fmt.Errorf("gitea server at %s does not satisfy version constraint %s", c.url, constraint) |
||||
} |
||||
return nil |
||||
} |
||||
|
||||
// loadClientServerVersion init the serverVersion variable
|
||||
func (c *Client) loadClientServerVersion() error { |
||||
c.versionLock.Lock() |
||||
defer c.versionLock.Unlock() |
||||
|
||||
raw, _, err := c.ServerVersion() |
||||
if err != nil { |
||||
return err |
||||
} |
||||
if c.serverVersion, err = version.NewVersion(raw); err != nil { |
||||
return err |
||||
} |
||||
return nil |
||||
} |
After Width: | Height: | Size: 1.7 KiB |
Loading…
Reference in new issue