Merge branch 'master' into fix-6409

This commit is contained in:
Nicolas Gourdon
2019-07-11 15:38:13 +02:00
1152 changed files with 117643 additions and 29961 deletions
+7
View File
@@ -45,6 +45,11 @@ func CreateOrg(ctx *context.APIContext, form api.CreateOrgOption) {
return
}
visibility := api.VisibleTypePublic
if form.Visibility != "" {
visibility = api.VisibilityModes[form.Visibility]
}
org := &models.User{
Name: form.UserName,
FullName: form.FullName,
@@ -53,7 +58,9 @@ func CreateOrg(ctx *context.APIContext, form api.CreateOrgOption) {
Location: form.Location,
IsActive: true,
Type: models.UserTypeOrganization,
Visibility: visibility,
}
if err := models.CreateOrganization(org, u); err != nil {
if models.IsErrUserAlreadyExist(err) ||
models.IsErrNameReserved(err) ||
+29 -8
View File
@@ -74,7 +74,8 @@ import (
"code.gitea.io/gitea/routers/api/v1/user"
"github.com/go-macaron/binding"
"gopkg.in/macaron.v1"
"github.com/go-macaron/cors"
macaron "gopkg.in/macaron.v1"
)
func sudo() macaron.Handler {
@@ -500,6 +501,12 @@ func RegisterRoutes(m *macaron.Macaron) {
m.Get("/swagger", misc.Swagger) //Render V1 by default
}
var handlers []macaron.Handler
if setting.EnableCORS {
handlers = append(handlers, cors.CORS(setting.CORSConfig))
}
handlers = append(handlers, securityHeaders(), context.APIContexter(), sudo())
m.Group("/v1", func() {
// Miscellaneous
if setting.API.EnableSwagger {
@@ -601,7 +608,8 @@ func RegisterRoutes(m *macaron.Macaron) {
m.Group("/:username/:reponame", func() {
m.Combo("").Get(reqAnyRepoReader(), repo.Get).
Delete(reqToken(), reqOwner(), repo.Delete)
Delete(reqToken(), reqOwner(), repo.Delete).
Patch(reqToken(), reqAdmin(), bind(api.EditRepoOption{}), repo.Edit)
m.Group("/hooks", func() {
m.Combo("").Get(repo.ListHooks).
Post(bind(api.CreateHookOption{}), repo.CreateHook)
@@ -743,6 +751,7 @@ func RegisterRoutes(m *macaron.Macaron) {
Post(reqToken(), bind(api.CreateStatusOption{}), repo.NewCommitStatus)
}, reqRepoReader(models.UnitTypeCode))
m.Group("/commits/:ref", func() {
// TODO: Add m.Get("") for single commit (https://developer.github.com/v3/repos/commits/#get-a-single-commit)
m.Get("/status", repo.GetCombinedCommitStatusByRef)
m.Get("/statuses", repo.GetCommitStatusesByRef)
}, reqRepoReader(models.UnitTypeCode))
@@ -754,9 +763,11 @@ func RegisterRoutes(m *macaron.Macaron) {
m.Get("/refs/*", repo.GetGitRefs)
m.Get("/trees/:sha", context.RepoRef(), repo.GetTree)
m.Get("/blobs/:sha", context.RepoRef(), repo.GetBlob)
m.Get("/tags/:sha", context.RepoRef(), repo.GetTag)
}, reqRepoReader(models.UnitTypeCode))
m.Group("/contents", func() {
m.Get("/*", repo.GetFileContents)
m.Get("", repo.GetContentsList)
m.Get("/*", repo.GetContents)
m.Group("/*", func() {
m.Post("", bind(api.CreateFileOptions{}), repo.CreateFile)
m.Put("", bind(api.UpdateFileOptions{}), repo.UpdateFile)
@@ -787,14 +798,14 @@ func RegisterRoutes(m *macaron.Macaron) {
Delete(reqToken(), reqOrgMembership(), org.ConcealMember)
})
m.Combo("/teams", reqToken(), reqOrgMembership()).Get(org.ListTeams).
Post(bind(api.CreateTeamOption{}), org.CreateTeam)
Post(reqOrgOwnership(), bind(api.CreateTeamOption{}), org.CreateTeam)
m.Group("/hooks", func() {
m.Combo("").Get(org.ListHooks).
Post(bind(api.CreateHookOption{}), org.CreateHook)
m.Combo("/:id").Get(org.GetHook).
Patch(reqOrgOwnership(), bind(api.EditHookOption{}), org.EditHook).
Delete(reqOrgOwnership(), org.DeleteHook)
}, reqToken(), reqOrgMembership())
Patch(bind(api.EditHookOption{}), org.EditHook).
Delete(org.DeleteHook)
}, reqToken(), reqOrgOwnership())
}, orgAssignment(true))
m.Group("/teams/:teamid", func() {
m.Combo("").Get(org.GetTeam).
@@ -841,5 +852,15 @@ func RegisterRoutes(m *macaron.Macaron) {
m.Group("/topics", func() {
m.Get("/search", repo.TopicSearch)
})
}, context.APIContexter(), sudo())
}, handlers...)
}
func securityHeaders() macaron.Handler {
return func(ctx *macaron.Context) {
ctx.Resp.Before(func(w macaron.ResponseWriter) {
// CORB: https://www.chromium.org/Home/chromium-security/corb-for-developers
// http://stackoverflow.com/a/3146618/244009
w.Header().Set("x-content-type-options", "nosniff")
})
}
}
+73 -28
View File
@@ -6,6 +6,7 @@ package convert
import (
"fmt"
"time"
"code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/git"
@@ -26,7 +27,7 @@ func ToEmail(email *models.EmailAddress) *api.Email {
}
}
// ToBranch convert a commit and branch to an api.Branch
// ToBranch convert a git.Commit and git.Branch to an api.Branch
func ToBranch(repo *models.Repository, b *git.Branch, c *git.Commit) *api.Branch {
return &api.Branch{
Name: b.Name,
@@ -34,23 +35,18 @@ func ToBranch(repo *models.Repository, b *git.Branch, c *git.Commit) *api.Branch
}
}
// ToTag convert a tag to an api.Tag
// ToTag convert a git.Tag to an api.Tag
func ToTag(repo *models.Repository, t *git.Tag) *api.Tag {
return &api.Tag{
Name: t.Name,
Commit: struct {
SHA string `json:"sha"`
URL string `json:"url"`
}{
SHA: t.ID.String(),
URL: util.URLJoin(repo.Link(), "commit", t.ID.String()),
},
ZipballURL: util.URLJoin(repo.Link(), "archive", t.Name+".zip"),
TarballURL: util.URLJoin(repo.Link(), "archive", t.Name+".tar.gz"),
Name: t.Name,
ID: t.ID.String(),
Commit: ToCommitMeta(repo, t),
ZipballURL: util.URLJoin(repo.HTMLURL(), "archive", t.Name+".zip"),
TarballURL: util.URLJoin(repo.HTMLURL(), "archive", t.Name+".tar.gz"),
}
}
// ToCommit convert a commit to api.PayloadCommit
// ToCommit convert a git.Commit to api.PayloadCommit
func ToCommit(repo *models.Repository, c *git.Commit) *api.PayloadCommit {
authorUsername := ""
if author, err := models.GetUserByEmail(c.Author.Email); err == nil {
@@ -66,17 +62,10 @@ func ToCommit(repo *models.Repository, c *git.Commit) *api.PayloadCommit {
log.Error("GetUserByEmail: %v", err)
}
verif := models.ParseCommitWithSignature(c)
var signature, payload string
if c.Signature != nil {
signature = c.Signature.Signature
payload = c.Signature.Payload
}
return &api.PayloadCommit{
ID: c.ID.String(),
Message: c.Message(),
URL: util.URLJoin(repo.Link(), "commit", c.ID.String()),
URL: util.URLJoin(repo.HTMLURL(), "commit", c.ID.String()),
Author: &api.PayloadUser{
Name: c.Author.Name,
Email: c.Author.Email,
@@ -87,13 +76,24 @@ func ToCommit(repo *models.Repository, c *git.Commit) *api.PayloadCommit {
Email: c.Committer.Email,
UserName: committerUsername,
},
Timestamp: c.Author.When,
Verification: &api.PayloadCommitVerification{
Verified: verif.Verified,
Reason: verif.Reason,
Signature: signature,
Payload: payload,
},
Timestamp: c.Author.When,
Verification: ToVerification(c),
}
}
// ToVerification convert a git.Commit.Signature to an api.PayloadCommitVerification
func ToVerification(c *git.Commit) *api.PayloadCommitVerification {
verif := models.ParseCommitWithSignature(c)
var signature, payload string
if c.Signature != nil {
signature = c.Signature.Signature
payload = c.Signature.Payload
}
return &api.PayloadCommitVerification{
Verified: verif.Verified,
Reason: verif.Reason,
Signature: signature,
Payload: payload,
}
}
@@ -213,6 +213,7 @@ func ToOrganization(org *models.User) *api.Organization {
Description: org.Description,
Website: org.Website,
Location: org.Location,
Visibility: org.Visibility.String(),
}
}
@@ -236,9 +237,53 @@ func ToUser(user *models.User, signed, admin bool) *api.User {
AvatarURL: user.AvatarLink(),
FullName: markup.Sanitize(user.FullName),
IsAdmin: user.IsAdmin,
LastLogin: user.LastLoginUnix.AsTime(),
Created: user.CreatedUnix.AsTime(),
}
if signed && (!user.KeepEmailPrivate || admin) {
result.Email = user.Email
}
return result
}
// ToAnnotatedTag convert git.Tag to api.AnnotatedTag
func ToAnnotatedTag(repo *models.Repository, t *git.Tag, c *git.Commit) *api.AnnotatedTag {
return &api.AnnotatedTag{
Tag: t.Name,
SHA: t.ID.String(),
Object: ToAnnotatedTagObject(repo, c),
Message: t.Message,
URL: util.URLJoin(repo.APIURL(), "git/tags", t.ID.String()),
Tagger: ToCommitUser(t.Tagger),
Verification: ToVerification(c),
}
}
// ToAnnotatedTagObject convert a git.Commit to an api.AnnotatedTagObject
func ToAnnotatedTagObject(repo *models.Repository, commit *git.Commit) *api.AnnotatedTagObject {
return &api.AnnotatedTagObject{
SHA: commit.ID.String(),
Type: string(git.ObjectCommit),
URL: util.URLJoin(repo.APIURL(), "git/commits", commit.ID.String()),
}
}
// ToCommitUser convert a git.Signature to an api.CommitUser
func ToCommitUser(sig *git.Signature) *api.CommitUser {
return &api.CommitUser{
Identity: api.Identity{
Name: sig.Name,
Email: sig.Email,
},
Date: sig.When.UTC().Format(time.RFC3339),
}
}
// ToCommitMeta convert a git.Tag to an api.CommitMeta
func ToCommitMeta(repo *models.Repository, tag *git.Tag) *api.CommitMeta {
return &api.CommitMeta{
SHA: tag.Object.String(),
// TODO: Add the /commits API endpoint and use it here (https://developer.github.com/v3/repos/commits/#get-a-single-commit)
URL: util.URLJoin(repo.APIURL(), "git/commits", tag.ID.String()),
}
}
+22 -5
View File
@@ -5,6 +5,7 @@
package misc
import (
"net/http"
"strings"
api "code.gitea.io/gitea/modules/structs"
@@ -42,7 +43,7 @@ func Markdown(ctx *context.APIContext, form api.MarkdownOption) {
}
if len(form.Text) == 0 {
ctx.Write([]byte(""))
_, _ = ctx.Write([]byte(""))
return
}
@@ -63,12 +64,24 @@ func Markdown(ctx *context.APIContext, form api.MarkdownOption) {
meta = ctx.Repo.Repository.ComposeMetas()
}
if form.Wiki {
ctx.Write([]byte(markdown.RenderWiki(md, urlPrefix, meta)))
_, err := ctx.Write([]byte(markdown.RenderWiki(md, urlPrefix, meta)))
if err != nil {
ctx.Error(http.StatusInternalServerError, "", err)
return
}
} else {
ctx.Write(markdown.Render(md, urlPrefix, meta))
_, err := ctx.Write(markdown.Render(md, urlPrefix, meta))
if err != nil {
ctx.Error(http.StatusInternalServerError, "", err)
return
}
}
default:
ctx.Write(markdown.RenderRaw([]byte(form.Text), "", false))
_, err := ctx.Write(markdown.RenderRaw([]byte(form.Text), "", false))
if err != nil {
ctx.Error(http.StatusInternalServerError, "", err)
return
}
}
}
@@ -98,5 +111,9 @@ func MarkdownRaw(ctx *context.APIContext) {
ctx.Error(422, "", err)
return
}
ctx.Write(markdown.RenderRaw(body, "", false))
_, err = ctx.Write(markdown.RenderRaw(body, "", false))
if err != nil {
ctx.Error(http.StatusInternalServerError, "", err)
return
}
}
+12 -2
View File
@@ -90,6 +90,11 @@ func Create(ctx *context.APIContext, form api.CreateOrgOption) {
return
}
visibility := api.VisibleTypePublic
if form.Visibility != "" {
visibility = api.VisibilityModes[form.Visibility]
}
org := &models.User{
Name: form.UserName,
FullName: form.FullName,
@@ -98,6 +103,7 @@ func Create(ctx *context.APIContext, form api.CreateOrgOption) {
Location: form.Location,
IsActive: true,
Type: models.UserTypeOrganization,
Visibility: visibility,
}
if err := models.CreateOrganization(org, ctx.User); err != nil {
if models.IsErrUserAlreadyExist(err) ||
@@ -153,6 +159,7 @@ func Edit(ctx *context.APIContext, form api.EditOrgOption) {
// required: true
// - name: body
// in: body
// required: true
// schema:
// "$ref": "#/definitions/EditOrgOption"
// responses:
@@ -163,8 +170,11 @@ func Edit(ctx *context.APIContext, form api.EditOrgOption) {
org.Description = form.Description
org.Website = form.Website
org.Location = form.Location
if err := models.UpdateUserCols(org, "full_name", "description", "website", "location"); err != nil {
ctx.Error(500, "UpdateUser", err)
if form.Visibility != "" {
org.Visibility = api.VisibilityModes[form.Visibility]
}
if err := models.UpdateUserCols(org, "full_name", "description", "website", "location", "visibility"); err != nil {
ctx.Error(500, "EditOrganization", err)
return
}
+58 -14
View File
@@ -181,7 +181,7 @@ func CreateFile(ctx *context.APIContext, apiOpts api.CreateFileOptions) {
// required: true
// - name: body
// in: body
// description: "'content' must be base64 encoded\n\n 'author' and 'committer' are optional (if only one is given, it will be used for the other, otherwise the authenticated user will be used)\n\n If 'branch' is not given, default branch will be used\n\n 'sha' is the SHA for the file that already exists\n\n 'new_branch' (optional) will make a new branch from 'branch' before creating the file"
// required: true
// schema:
// "$ref": "#/definitions/CreateFileOptions"
// responses:
@@ -204,6 +204,11 @@ func CreateFile(ctx *context.APIContext, apiOpts api.CreateFileOptions) {
Email: apiOpts.Author.Email,
},
}
if opts.Message == "" {
opts.Message = ctx.Tr("repo.editor.add", opts.TreePath)
}
if fileResponse, err := createOrUpdateFile(ctx, opts); err != nil {
ctx.Error(http.StatusInternalServerError, "CreateFile", err)
} else {
@@ -238,7 +243,7 @@ func UpdateFile(ctx *context.APIContext, apiOpts api.UpdateFileOptions) {
// required: true
// - name: body
// in: body
// description: "'content' must be base64 encoded\n\n 'sha' is the SHA for the file that already exists\n\n 'author' and 'committer' are optional (if only one is given, it will be used for the other, otherwise the authenticated user will be used)\n\n If 'branch' is not given, default branch will be used\n\n 'new_branch' (optional) will make a new branch from 'branch' before updating the file"
// required: true
// schema:
// "$ref": "#/definitions/UpdateFileOptions"
// responses:
@@ -264,6 +269,10 @@ func UpdateFile(ctx *context.APIContext, apiOpts api.UpdateFileOptions) {
},
}
if opts.Message == "" {
opts.Message = ctx.Tr("repo.editor.update", opts.TreePath)
}
if fileResponse, err := createOrUpdateFile(ctx, opts); err != nil {
ctx.Error(http.StatusInternalServerError, "UpdateFile", err)
} else {
@@ -316,7 +325,7 @@ func DeleteFile(ctx *context.APIContext, apiOpts api.DeleteFileOptions) {
// required: true
// - name: body
// in: body
// description: "'sha' is the SHA for the file to be deleted\n\n 'author' and 'committer' are optional (if only one is given, it will be used for the other, otherwise the authenticated user will be used)\n\n If 'branch' is not given, default branch will be used\n\n 'new_branch' (optional) will make a new branch from 'branch' before deleting the file"
// required: true
// schema:
// "$ref": "#/definitions/DeleteFileOptions"
// responses:
@@ -346,6 +355,10 @@ func DeleteFile(ctx *context.APIContext, apiOpts api.DeleteFileOptions) {
},
}
if opts.Message == "" {
opts.Message = ctx.Tr("repo.editor.delete", opts.TreePath)
}
if fileResponse, err := repofiles.DeleteRepoFile(ctx.Repo.Repository, ctx.User, opts); err != nil {
ctx.Error(http.StatusInternalServerError, "DeleteFile", err)
} else {
@@ -353,11 +366,11 @@ func DeleteFile(ctx *context.APIContext, apiOpts api.DeleteFileOptions) {
}
}
// GetFileContents Get the contents of a fle in a repository
func GetFileContents(ctx *context.APIContext) {
// swagger:operation GET /repos/{owner}/{repo}/contents/{filepath} repository repoGetFileContents
// GetContents Get the metadata and contents (if a file) of an entry in a repository, or a list of entries if a dir
func GetContents(ctx *context.APIContext) {
// swagger:operation GET /repos/{owner}/{repo}/contents/{filepath} repository repoGetContents
// ---
// summary: Gets the contents of a file or directory in a repository
// summary: Gets the metadata and contents (if a file) of an entry in a repository, or a list of entries if a dir
// produces:
// - application/json
// parameters:
@@ -373,20 +386,20 @@ func GetFileContents(ctx *context.APIContext) {
// required: true
// - name: filepath
// in: path
// description: path of the file to delete
// description: path of the dir, file, symlink or submodule in the repo
// type: string
// required: true
// - name: ref
// in: query
// description: "The name of the commit/branch/tag. Default the repositorys default branch (usually master)"
// required: false
// type: string
// required: false
// responses:
// "200":
// "$ref": "#/responses/FileContentResponse"
// "$ref": "#/responses/ContentsResponse"
if !CanReadFiles(ctx.Repo) {
ctx.Error(http.StatusInternalServerError, "GetFileContents", models.ErrUserDoesNotHaveAccessToRepo{
ctx.Error(http.StatusInternalServerError, "GetContentsOrList", models.ErrUserDoesNotHaveAccessToRepo{
UserID: ctx.User.ID,
RepoName: ctx.Repo.Repository.LowerName,
})
@@ -396,9 +409,40 @@ func GetFileContents(ctx *context.APIContext) {
treePath := ctx.Params("*")
ref := ctx.QueryTrim("ref")
if fileContents, err := repofiles.GetFileContents(ctx.Repo.Repository, treePath, ref); err != nil {
ctx.Error(http.StatusInternalServerError, "GetFileContents", err)
if fileList, err := repofiles.GetContentsOrList(ctx.Repo.Repository, treePath, ref); err != nil {
ctx.Error(http.StatusInternalServerError, "GetContentsOrList", err)
} else {
ctx.JSON(http.StatusOK, fileContents)
ctx.JSON(http.StatusOK, fileList)
}
}
// GetContentsList Get the metadata of all the entries of the root dir
func GetContentsList(ctx *context.APIContext) {
// swagger:operation GET /repos/{owner}/{repo}/contents repository repoGetContentsList
// ---
// summary: Gets the metadata of all the entries of the root dir
// produces:
// - application/json
// parameters:
// - name: owner
// in: path
// description: owner of the repo
// type: string
// required: true
// - name: repo
// in: path
// description: name of the repo
// type: string
// required: true
// - name: ref
// in: query
// description: "The name of the commit/branch/tag. Default the repositorys default branch (usually master)"
// type: string
// required: false
// responses:
// "200":
// "$ref": "#/responses/ContentsListResponse"
// same as GetContents(), this function is here because swagger fails if path is empty in GetContents() interface
GetContents(ctx)
}
+1 -2
View File
@@ -100,8 +100,7 @@ func getGitRefsInternal(ctx *context.APIContext, filter string) {
Object: &api.GitObject{
SHA: refs[i].Object.String(),
Type: refs[i].Type,
// TODO: Add commit/tag info URL
//URL: ctx.Repo.Repository.APIURL() + "/git/" + refs[i].Type + "s/" + refs[i].Object.String(),
URL: ctx.Repo.Repository.APIURL() + "/git/" + refs[i].Type + "s/" + refs[i].Object.String(),
},
}
}
+7 -3
View File
@@ -125,9 +125,10 @@ func CreateLabel(ctx *context.APIContext, form api.CreateLabelOption) {
// "201":
// "$ref": "#/responses/Label"
label := &models.Label{
Name: form.Name,
Color: form.Color,
RepoID: ctx.Repo.Repository.ID,
Name: form.Name,
Color: form.Color,
RepoID: ctx.Repo.Repository.ID,
Description: form.Description,
}
if err := models.NewLabel(label); err != nil {
ctx.Error(500, "NewLabel", err)
@@ -185,6 +186,9 @@ func EditLabel(ctx *context.APIContext, form api.EditLabelOption) {
if form.Color != nil {
label.Color = *form.Color
}
if form.Description != nil {
label.Description = *form.Description
}
if err := models.UpdateLabel(label); err != nil {
ctx.ServerError("UpdateLabel", err)
return
+6 -2
View File
@@ -14,7 +14,7 @@ import (
api "code.gitea.io/gitea/modules/structs"
)
// ListMilestones list all the opened milestones for a repository
// ListMilestones list milestones for a repository
func ListMilestones(ctx *context.APIContext) {
// swagger:operation GET /repos/{owner}/{repo}/milestones issue issueGetMilestonesList
// ---
@@ -32,10 +32,14 @@ func ListMilestones(ctx *context.APIContext) {
// description: name of the repo
// type: string
// required: true
// - name: state
// in: query
// description: Milestone state, Recognised values are open, closed and all. Defaults to "open"
// type: string
// responses:
// "200":
// "$ref": "#/responses/MilestoneList"
milestones, err := models.GetMilestonesByRepoID(ctx.Repo.Repository.ID)
milestones, err := models.GetMilestonesByRepoID(ctx.Repo.Repository.ID, api.StateType(ctx.Query("state")))
if err != nil {
ctx.Error(500, "GetMilestonesByRepoID", err)
return
+26 -11
View File
@@ -15,6 +15,7 @@ import (
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/notification"
"code.gitea.io/gitea/modules/pull"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/util"
)
@@ -188,7 +189,7 @@ func CreatePullRequest(ctx *context.APIContext, form api.CreatePullRequestOption
)
// Get repo/branch information
headUser, headRepo, headGitRepo, prInfo, baseBranch, headBranch := parseCompareInfo(ctx, form)
headUser, headRepo, headGitRepo, compareInfo, baseBranch, headBranch := parseCompareInfo(ctx, form)
if ctx.Written() {
return
}
@@ -240,7 +241,7 @@ func CreatePullRequest(ctx *context.APIContext, form api.CreatePullRequestOption
milestoneID = milestone.ID
}
patch, err := headGitRepo.GetPatch(prInfo.MergeBase, headBranch)
patch, err := headGitRepo.GetPatch(compareInfo.MergeBase, headBranch)
if err != nil {
ctx.Error(500, "GetPatch", err)
return
@@ -251,9 +252,15 @@ func CreatePullRequest(ctx *context.APIContext, form api.CreatePullRequestOption
deadlineUnix = util.TimeStamp(form.Deadline.Unix())
}
maxIndex, err := models.GetMaxIndexOfIssue(repo.ID)
if err != nil {
ctx.ServerError("GetPatch", err)
return
}
prIssue := &models.Issue{
RepoID: repo.ID,
Index: repo.NextIssueIndex(),
Index: maxIndex + 1,
Title: form.Title,
PosterID: ctx.User.ID,
Poster: ctx.User,
@@ -271,7 +278,7 @@ func CreatePullRequest(ctx *context.APIContext, form api.CreatePullRequestOption
BaseBranch: baseBranch,
HeadRepo: headRepo,
BaseRepo: repo,
MergeBase: prInfo.MergeBase,
MergeBase: compareInfo.MergeBase,
Type: models.PullRequestGitea,
}
@@ -347,7 +354,11 @@ func EditPullRequest(ctx *context.APIContext, form api.EditPullRequestOption) {
return
}
pr.LoadIssue()
err = pr.LoadIssue()
if err != nil {
ctx.Error(http.StatusInternalServerError, "LoadIssue", err)
return
}
issue := pr.Issue
issue.Repo = ctx.Repo.Repository
@@ -541,7 +552,11 @@ func MergePullRequest(ctx *context.APIContext, form auth.MergePullRequestForm) {
return
}
pr.LoadIssue()
err = pr.LoadIssue()
if err != nil {
ctx.Error(http.StatusInternalServerError, "LoadIssue", err)
return
}
pr.Issue.Repo = ctx.Repo.Repository
if ctx.IsSigned {
@@ -581,7 +596,7 @@ func MergePullRequest(ctx *context.APIContext, form auth.MergePullRequestForm) {
message += "\n\n" + form.MergeMessageField
}
if err := pr.Merge(ctx.User, ctx.Repo.GitRepo, models.MergeStyle(form.Do), message); err != nil {
if err := pull.Merge(pr, ctx.User, ctx.Repo.GitRepo, models.MergeStyle(form.Do), message); err != nil {
if models.IsErrInvalidMergeStyle(err) {
ctx.Status(405)
return
@@ -594,7 +609,7 @@ func MergePullRequest(ctx *context.APIContext, form auth.MergePullRequestForm) {
ctx.Status(200)
}
func parseCompareInfo(ctx *context.APIContext, form api.CreatePullRequestOption) (*models.User, *models.Repository, *git.Repository, *git.PullRequestInfo, string, string) {
func parseCompareInfo(ctx *context.APIContext, form api.CreatePullRequestOption) (*models.User, *models.Repository, *git.Repository, *git.CompareInfo, string, string) {
baseRepo := ctx.Repo.Repository
// Get compared branches information
@@ -706,11 +721,11 @@ func parseCompareInfo(ctx *context.APIContext, form api.CreatePullRequestOption)
return nil, nil, nil, nil, "", ""
}
prInfo, err := headGitRepo.GetPullRequestInfo(models.RepoPath(baseRepo.Owner.Name, baseRepo.Name), baseBranch, headBranch)
compareInfo, err := headGitRepo.GetCompareInfo(models.RepoPath(baseRepo.Owner.Name, baseRepo.Name), baseBranch, headBranch)
if err != nil {
ctx.Error(500, "GetPullRequestInfo", err)
ctx.Error(500, "GetCompareInfo", err)
return nil, nil, nil, nil, "", ""
}
return headUser, headRepo, headGitRepo, prInfo, baseBranch, headBranch
return headUser, headRepo, headGitRepo, compareInfo, baseBranch, headBranch
}
+4 -16
View File
@@ -5,13 +5,12 @@
package repo
import (
"errors"
"net/http"
"strings"
"code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/upload"
api "code.gitea.io/gitea/modules/structs"
)
@@ -177,20 +176,9 @@ func CreateReleaseAttachment(ctx *context.APIContext) {
}
// Check if the filetype is allowed by the settings
fileType := http.DetectContentType(buf)
allowedTypes := strings.Split(setting.AttachmentAllowedTypes, ",")
allowed := false
for _, t := range allowedTypes {
t := strings.Trim(t, " ")
if t == "*/*" || t == fileType {
allowed = true
break
}
}
if !allowed {
ctx.Error(400, "DetectContentType", errors.New("File type is not allowed"))
err = upload.VerifyAllowedContentType(buf, strings.Split(setting.AttachmentAllowedTypes, ","))
if err != nil {
ctx.Error(400, "DetectContentType", err)
return
}
+282 -36
View File
@@ -56,6 +56,15 @@ func Search(ctx *context.APIContext) {
// description: search only for repos that the user with the given id owns or contributes to
// type: integer
// format: int64
// - name: starredBy
// in: query
// description: search only for repos that the user with the given id has starred
// type: integer
// format: int64
// - name: private
// in: query
// description: include private repositories this user has access to (defaults to true)
// type: boolean
// - name: page
// in: query
// description: page number of results to return (1-based)
@@ -96,6 +105,10 @@ func Search(ctx *context.APIContext) {
PageSize: convert.ToCorrectPageSize(ctx.QueryInt("limit")),
TopicOnly: ctx.QueryBool("topic"),
Collaborate: util.OptionalBoolNone,
Private: ctx.IsSigned && (ctx.Query("private") == "" || ctx.QueryBool("private")),
UserIsAdmin: ctx.IsUserSiteAdmin(),
UserID: ctx.Data["SignedUserID"].(int64),
StarredByID: ctx.QueryInt64("starredBy"),
}
if ctx.QueryBool("exclusive") {
@@ -140,42 +153,6 @@ func Search(ctx *context.APIContext) {
}
var err error
if opts.OwnerID > 0 {
var repoOwner *models.User
if ctx.User != nil && ctx.User.ID == opts.OwnerID {
repoOwner = ctx.User
} else {
repoOwner, err = models.GetUserByID(opts.OwnerID)
if err != nil {
ctx.JSON(500, api.SearchError{
OK: false,
Error: err.Error(),
})
return
}
}
if repoOwner.IsOrganization() {
opts.Collaborate = util.OptionalBoolFalse
}
// Check visibility.
if ctx.IsSigned {
if ctx.User.ID == repoOwner.ID {
opts.Private = true
} else if repoOwner.IsOrganization() {
opts.Private, err = repoOwner.IsOwnedBy(ctx.User.ID)
if err != nil {
ctx.JSON(500, api.SearchError{
OK: false,
Error: err.Error(),
})
return
}
}
}
}
repos, count, err := models.SearchRepositoryByName(opts)
if err != nil {
ctx.JSON(500, api.SearchError{
@@ -263,6 +240,10 @@ func Create(ctx *context.APIContext, opt api.CreateRepoOption) {
// responses:
// "201":
// "$ref": "#/responses/Repository"
// "409":
// description: The repository with the same name already exists.
// "422":
// "$ref": "#/responses/validationError"
if ctx.User.IsOrganization() {
// Shouldn't reach this condition, but just in case.
ctx.Error(422, "", "not allowed creating repository for organization")
@@ -523,6 +504,271 @@ func GetByID(ctx *context.APIContext) {
ctx.JSON(200, repo.APIFormat(perm.AccessMode))
}
// Edit edit repository properties
func Edit(ctx *context.APIContext, opts api.EditRepoOption) {
// swagger:operation PATCH /repos/{owner}/{repo} repository repoEdit
// ---
// summary: Edit a repository's properties. Only fields that are set will be changed.
// produces:
// - application/json
// parameters:
// - name: owner
// in: path
// description: owner of the repo to edit
// type: string
// required: true
// - name: repo
// in: path
// description: name of the repo to edit
// type: string
// required: true
// required: true
// - name: body
// in: body
// description: "Properties of a repo that you can edit"
// schema:
// "$ref": "#/definitions/EditRepoOption"
// responses:
// "200":
// "$ref": "#/responses/Repository"
// "403":
// "$ref": "#/responses/forbidden"
// "422":
// "$ref": "#/responses/validationError"
if err := updateBasicProperties(ctx, opts); err != nil {
return
}
if err := updateRepoUnits(ctx, opts); err != nil {
return
}
if opts.Archived != nil {
if err := updateRepoArchivedState(ctx, opts); err != nil {
return
}
}
ctx.JSON(http.StatusOK, ctx.Repo.Repository.APIFormat(ctx.Repo.AccessMode))
}
// updateBasicProperties updates the basic properties of a repo: Name, Description, Website and Visibility
func updateBasicProperties(ctx *context.APIContext, opts api.EditRepoOption) error {
owner := ctx.Repo.Owner
repo := ctx.Repo.Repository
oldRepoName := repo.Name
newRepoName := repo.Name
if opts.Name != nil {
newRepoName = *opts.Name
}
// Check if repository name has been changed and not just a case change
if repo.LowerName != strings.ToLower(newRepoName) {
if err := models.ChangeRepositoryName(ctx.Repo.Owner, repo.Name, newRepoName); err != nil {
switch {
case models.IsErrRepoAlreadyExist(err):
ctx.Error(http.StatusUnprocessableEntity, fmt.Sprintf("repo name is already taken [name: %s]", newRepoName), err)
case models.IsErrNameReserved(err):
ctx.Error(http.StatusUnprocessableEntity, fmt.Sprintf("repo name is reserved [name: %s]", newRepoName), err)
case models.IsErrNamePatternNotAllowed(err):
ctx.Error(http.StatusUnprocessableEntity, fmt.Sprintf("repo name's pattern is not allowed [name: %s, pattern: %s]", newRepoName, err.(models.ErrNamePatternNotAllowed).Pattern), err)
default:
ctx.Error(http.StatusUnprocessableEntity, "ChangeRepositoryName", err)
}
return err
}
err := models.NewRepoRedirect(ctx.Repo.Owner.ID, repo.ID, repo.Name, newRepoName)
if err != nil {
ctx.Error(http.StatusUnprocessableEntity, "NewRepoRedirect", err)
return err
}
if err := models.RenameRepoAction(ctx.User, oldRepoName, repo); err != nil {
log.Error("RenameRepoAction: %v", err)
ctx.Error(http.StatusInternalServerError, "RenameRepoActions", err)
return err
}
log.Trace("Repository name changed: %s/%s -> %s", ctx.Repo.Owner.Name, repo.Name, newRepoName)
}
// Update the name in the repo object for the response
repo.Name = newRepoName
repo.LowerName = strings.ToLower(newRepoName)
if opts.Description != nil {
repo.Description = *opts.Description
}
if opts.Website != nil {
repo.Website = *opts.Website
}
visibilityChanged := false
if opts.Private != nil {
// Visibility of forked repository is forced sync with base repository.
if repo.IsFork {
*opts.Private = repo.BaseRepo.IsPrivate
}
visibilityChanged = repo.IsPrivate != *opts.Private
// when ForcePrivate enabled, you could change public repo to private, but only admin users can change private to public
if visibilityChanged && setting.Repository.ForcePrivate && !*opts.Private && !ctx.User.IsAdmin {
err := fmt.Errorf("cannot change private repository to public")
ctx.Error(http.StatusUnprocessableEntity, "Force Private enabled", err)
return err
}
repo.IsPrivate = *opts.Private
}
if err := models.UpdateRepository(repo, visibilityChanged); err != nil {
ctx.Error(http.StatusInternalServerError, "UpdateRepository", err)
return err
}
log.Trace("Repository basic settings updated: %s/%s", owner.Name, repo.Name)
return nil
}
// updateRepoUnits updates repo units: Issue settings, Wiki settings, PR settings
func updateRepoUnits(ctx *context.APIContext, opts api.EditRepoOption) error {
owner := ctx.Repo.Owner
repo := ctx.Repo.Repository
var units []models.RepoUnit
for _, tp := range models.MustRepoUnits {
units = append(units, models.RepoUnit{
RepoID: repo.ID,
Type: tp,
Config: new(models.UnitConfig),
})
}
if opts.HasIssues != nil {
if *opts.HasIssues {
// We don't currently allow setting individual issue settings through the API,
// only can enable/disable issues, so when enabling issues,
// we either get the existing config which means it was already enabled,
// or create a new config since it doesn't exist.
unit, err := repo.GetUnit(models.UnitTypeIssues)
var config *models.IssuesConfig
if err != nil {
// Unit type doesn't exist so we make a new config file with default values
config = &models.IssuesConfig{
EnableTimetracker: true,
AllowOnlyContributorsToTrackTime: true,
EnableDependencies: true,
}
} else {
config = unit.IssuesConfig()
}
units = append(units, models.RepoUnit{
RepoID: repo.ID,
Type: models.UnitTypeIssues,
Config: config,
})
}
}
if opts.HasWiki != nil {
if *opts.HasWiki {
// We don't currently allow setting individual wiki settings through the API,
// only can enable/disable the wiki, so when enabling the wiki,
// we either get the existing config which means it was already enabled,
// or create a new config since it doesn't exist.
config := &models.UnitConfig{}
units = append(units, models.RepoUnit{
RepoID: repo.ID,
Type: models.UnitTypeWiki,
Config: config,
})
}
}
if opts.HasPullRequests != nil {
if *opts.HasPullRequests {
// We do allow setting individual PR settings through the API, so
// we get the config settings and then set them
// if those settings were provided in the opts.
unit, err := repo.GetUnit(models.UnitTypePullRequests)
var config *models.PullRequestsConfig
if err != nil {
// Unit type doesn't exist so we make a new config file with default values
config = &models.PullRequestsConfig{
IgnoreWhitespaceConflicts: false,
AllowMerge: true,
AllowRebase: true,
AllowRebaseMerge: true,
AllowSquash: true,
}
} else {
config = unit.PullRequestsConfig()
}
if opts.IgnoreWhitespaceConflicts != nil {
config.IgnoreWhitespaceConflicts = *opts.IgnoreWhitespaceConflicts
}
if opts.AllowMerge != nil {
config.AllowMerge = *opts.AllowMerge
}
if opts.AllowRebase != nil {
config.AllowRebase = *opts.AllowRebase
}
if opts.AllowRebaseMerge != nil {
config.AllowRebaseMerge = *opts.AllowRebaseMerge
}
if opts.AllowSquash != nil {
config.AllowSquash = *opts.AllowSquash
}
units = append(units, models.RepoUnit{
RepoID: repo.ID,
Type: models.UnitTypePullRequests,
Config: config,
})
}
}
if err := models.UpdateRepositoryUnits(repo, units); err != nil {
ctx.Error(http.StatusInternalServerError, "UpdateRepositoryUnits", err)
return err
}
log.Trace("Repository advanced settings updated: %s/%s", owner.Name, repo.Name)
return nil
}
// updateRepoArchivedState updates repo's archive state
func updateRepoArchivedState(ctx *context.APIContext, opts api.EditRepoOption) error {
repo := ctx.Repo.Repository
// archive / un-archive
if opts.Archived != nil {
if repo.IsMirror {
err := fmt.Errorf("repo is a mirror, cannot archive/un-archive")
ctx.Error(http.StatusUnprocessableEntity, err.Error(), err)
return err
}
if *opts.Archived {
if err := repo.SetArchiveRepoState(*opts.Archived); err != nil {
log.Error("Tried to archive a repo: %s", err)
ctx.Error(http.StatusInternalServerError, "ArchiveRepoState", err)
return err
}
log.Trace("Repository was archived: %s/%s", ctx.Repo.Owner.Name, repo.Name)
} else {
if err := repo.SetArchiveRepoState(*opts.Archived); err != nil {
log.Error("Tried to un-archive a repo: %s", err)
ctx.Error(http.StatusInternalServerError, "ArchiveRepoState", err)
return err
}
log.Trace("Repository was un-archived: %s/%s", ctx.Repo.Owner.Name, repo.Name)
}
}
return nil
}
// Delete one repository
func Delete(ctx *context.APIContext) {
// swagger:operation DELETE /repos/{owner}/{repo} repository repoDelete
+82
View File
@@ -0,0 +1,82 @@
// 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 repo
import (
"net/http"
"testing"
"code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/context"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/test"
"github.com/stretchr/testify/assert"
)
func TestRepoEdit(t *testing.T) {
models.PrepareTestEnv(t)
ctx := test.MockContext(t, "user2/repo1")
test.LoadRepo(t, ctx, 1)
test.LoadUser(t, ctx, 2)
ctx.Repo.Owner = ctx.User
description := "new description"
website := "http://wwww.newwebsite.com"
private := true
hasIssues := false
hasWiki := false
defaultBranch := "master"
hasPullRequests := true
ignoreWhitespaceConflicts := true
allowMerge := false
allowRebase := false
allowRebaseMerge := false
allowSquashMerge := false
archived := true
opts := api.EditRepoOption{
Name: &ctx.Repo.Repository.Name,
Description: &description,
Website: &website,
Private: &private,
HasIssues: &hasIssues,
HasWiki: &hasWiki,
DefaultBranch: &defaultBranch,
HasPullRequests: &hasPullRequests,
IgnoreWhitespaceConflicts: &ignoreWhitespaceConflicts,
AllowMerge: &allowMerge,
AllowRebase: &allowRebase,
AllowRebaseMerge: &allowRebaseMerge,
AllowSquash: &allowSquashMerge,
Archived: &archived,
}
Edit(&context.APIContext{Context: ctx, Org: nil}, opts)
assert.EqualValues(t, http.StatusOK, ctx.Resp.Status())
models.AssertExistsAndLoadBean(t, &models.Repository{
ID: 1,
}, models.Cond("name = ? AND is_archived = 1", *opts.Name))
}
func TestRepoEditNameChange(t *testing.T) {
models.PrepareTestEnv(t)
ctx := test.MockContext(t, "user2/repo1")
test.LoadRepo(t, ctx, 1)
test.LoadUser(t, ctx, 2)
ctx.Repo.Owner = ctx.User
name := "newname"
opts := api.EditRepoOption{
Name: &name,
}
Edit(&context.APIContext{Context: ctx, Org: nil}, opts)
assert.EqualValues(t, http.StatusOK, ctx.Resp.Status())
models.AssertExistsAndLoadBean(t, &models.Repository{
ID: 1,
}, models.Cond("name = ?", opts.Name))
}
+5 -8
View File
@@ -9,6 +9,7 @@ import (
"code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/repofiles"
api "code.gitea.io/gitea/modules/structs"
)
@@ -57,17 +58,12 @@ func NewCommitStatus(ctx *context.APIContext, form api.CreateStatusOption) {
Description: form.Description,
Context: form.Context,
}
if err := models.NewCommitStatus(ctx.Repo.Repository, ctx.User, sha, status); err != nil {
ctx.Error(500, "NewCommitStatus", err)
if err := repofiles.CreateCommitStatus(ctx.Repo.Repository, ctx.User, sha, status); err != nil {
ctx.Error(500, "CreateCommitStatus", err)
return
}
newStatus, err := models.GetCommitStatus(ctx.Repo.Repository, sha, status)
if err != nil {
ctx.Error(500, "GetCommitStatus", err)
return
}
ctx.JSON(201, newStatus.APIFormat())
ctx.JSON(201, status.APIFormat())
}
// GetCommitStatuses returns all statuses for any given commit hash
@@ -140,6 +136,7 @@ func getCommitStatuses(ctx *context.APIContext, sha string) {
statuses, err := models.GetCommitStatuses(repo, sha, page)
if err != nil {
ctx.Error(500, "GetCommitStatuses", fmt.Errorf("GetCommitStatuses[%s, %s, %d]: %v", repo.FullName(), sha, page, err))
return
}
apiStatuses := make([]*api.Status, 0, len(statuses))
+45
View File
@@ -7,6 +7,7 @@ package repo
import (
"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/routers/api/v1/convert"
"net/http"
api "code.gitea.io/gitea/modules/structs"
)
@@ -45,3 +46,47 @@ func ListTags(ctx *context.APIContext) {
ctx.JSON(200, &apiTags)
}
// GetTag get the tag of a repository.
func GetTag(ctx *context.APIContext) {
// swagger:operation GET /repos/{owner}/{repo}/git/tags/{sha} repository GetTag
// ---
// summary: Gets the tag of a repository.
// produces:
// - application/json
// parameters:
// - name: owner
// in: path
// description: owner of the repo
// type: string
// required: true
// - name: repo
// in: path
// description: name of the repo
// type: string
// required: true
// - name: sha
// in: path
// description: sha of the tag
// type: string
// required: true
// responses:
// "200":
// "$ref": "#/responses/AnnotatedTag"
sha := ctx.Params("sha")
if len(sha) == 0 {
ctx.Error(http.StatusBadRequest, "", "SHA not provided")
return
}
if tag, err := ctx.Repo.GitRepo.GetAnnotatedTag(sha); err != nil {
ctx.Error(http.StatusBadRequest, "GetTag", err)
} else {
commit, err := tag.Commit()
if err != nil {
ctx.Error(http.StatusBadRequest, "GetTag", err)
}
ctx.JSON(http.StatusOK, convert.ToAnnotatedTag(ctx.Repo.Repository, tag, commit))
}
}
+2
View File
@@ -82,6 +82,8 @@ type swaggerParameterBodies struct {
// in:body
CreateRepoOption api.CreateRepoOption
// in:body
EditRepoOption api.EditRepoOption
// in:body
CreateForkOption api.CreateForkOption
// in:body
+26 -5
View File
@@ -38,11 +38,25 @@ type swaggerResponseBranchList struct {
// TagList
// swagger:response TagList
type swaggerReponseTagList struct {
type swaggerResponseTagList struct {
// in:body
Body []api.Tag `json:"body"`
}
// Tag
// swagger:response Tag
type swaggerResponseTag struct {
// in:body
Body api.Tag `json:"body"`
}
// AnnotatedTag
// swagger:response AnnotatedTag
type swaggerResponseAnnotatedTag struct {
// in:body
Body api.AnnotatedTag `json:"body"`
}
// Reference
// swagger:response Reference
type swaggerResponseReference struct {
@@ -183,11 +197,18 @@ type swaggerFileResponse struct {
Body api.FileResponse `json:"body"`
}
// FileContentResponse
// swagger:response FileContentResponse
type swaggerFileContentResponse struct {
// ContentsResponse
// swagger:response ContentsResponse
type swaggerContentsResponse struct {
//in: body
Body api.FileContentResponse `json:"body"`
Body api.ContentsResponse `json:"body"`
}
// ContentsListResponse
// swagger:response ContentsListResponse
type swaggerContentsListResponse struct {
// in:body
Body []api.ContentsResponse `json:"body"`
}
// FileDeleteResponse
+10 -3
View File
@@ -36,6 +36,9 @@ func ListAccessTokens(ctx *context.APIContext) {
apiTokens := make([]*api.AccessToken, len(tokens))
for i := range tokens {
if tokens[i].Name == "drone" {
tokens[i].Name = "drone-legacy-use-oauth2-instead"
}
apiTokens[i] = &api.AccessToken{
ID: tokens[i].ID,
Name: tokens[i].Name,
@@ -76,14 +79,18 @@ func CreateAccessToken(ctx *context.APIContext, form api.CreateAccessTokenOption
UID: ctx.User.ID,
Name: form.Name,
}
if t.Name == "drone" {
t.Name = "drone-legacy-use-oauth2-instead"
}
if err := models.NewAccessToken(t); err != nil {
ctx.Error(500, "NewAccessToken", err)
return
}
ctx.JSON(201, &api.AccessToken{
Name: t.Name,
Token: t.Token,
ID: t.ID,
Name: t.Name,
Token: t.Token,
ID: t.ID,
TokenLastEight: t.TokenLastEight,
})
}
-5
View File
@@ -9,14 +9,9 @@ import (
"code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/routers/api/v1/convert"
)
func composePublicGPGKeysAPILink() string {
return setting.AppURL + "api/v1/user/gpg_keys/"
}
func listGPGKeys(ctx *context.APIContext, uid int64) {
keys, err := models.ListGPGKeys(uid)
if err != nil {
+1
View File
@@ -98,6 +98,7 @@ func addHook(ctx *context.APIContext, form *api.CreateHookOption, orgID, repoID
URL: form.Config["url"],
ContentType: models.ToHookContentType(form.Config["content_type"]),
Secret: form.Config["secret"],
HTTPMethod: "POST",
HookEvent: &models.HookEvent{
ChooseEvents: true,
HookEvents: models.HookEvents{