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
+73 -7
View File
@@ -1,4 +1,5 @@
// 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.
@@ -6,6 +7,7 @@ package admin
import (
"fmt"
"net/url"
"os"
"runtime"
"strings"
@@ -18,6 +20,8 @@ import (
"code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/cron"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/process"
"code.gitea.io/gitea/modules/setting"
)
@@ -124,6 +128,7 @@ const (
reinitMissingRepository
syncExternalUsers
gitFsck
deleteGeneratedRepositoryAvatars
)
// Dashboard show admin panel dashboard
@@ -166,6 +171,9 @@ func Dashboard(ctx *context.Context) {
case gitFsck:
success = ctx.Tr("admin.dashboard.git_fsck_started")
go models.GitFsck()
case deleteGeneratedRepositoryAvatars:
success = ctx.Tr("admin.dashboard.delete_generated_repository_avatars_success")
err = models.RemoveRandomAvatars()
}
if err != nil {
@@ -197,6 +205,63 @@ func SendTestMail(ctx *context.Context) {
ctx.Redirect(setting.AppSubURL + "/admin/config")
}
func shadownPasswordKV(cfgItem, splitter string) string {
fields := strings.Split(cfgItem, splitter)
for i := 0; i < len(fields); i++ {
if strings.HasPrefix(fields[i], "password=") {
fields[i] = "password=******"
break
}
}
return strings.Join(fields, splitter)
}
func shadownURL(provider, cfgItem string) string {
u, err := url.Parse(cfgItem)
if err != nil {
log.Error("shodowPassword %v failed: %v", provider, err)
return cfgItem
}
if u.User != nil {
atIdx := strings.Index(cfgItem, "@")
if atIdx > 0 {
colonIdx := strings.LastIndex(cfgItem[:atIdx], ":")
if colonIdx > 0 {
return cfgItem[:colonIdx+1] + "******" + cfgItem[atIdx:]
}
}
}
return cfgItem
}
func shadowPassword(provider, cfgItem string) string {
switch provider {
case "redis":
return shadownPasswordKV(cfgItem, ",")
case "mysql":
//root:@tcp(localhost:3306)/macaron?charset=utf8
atIdx := strings.Index(cfgItem, "@")
if atIdx > 0 {
colonIdx := strings.Index(cfgItem[:atIdx], ":")
if colonIdx > 0 {
return cfgItem[:colonIdx+1] + "******" + cfgItem[atIdx:]
}
}
return cfgItem
case "postgres":
// user=jiahuachen dbname=macaron port=5432 sslmode=disable
if !strings.HasPrefix(cfgItem, "postgres://") {
return shadownPasswordKV(cfgItem, " ")
}
// postgres://pqgotest:password@localhost/pqgotest?sslmode=verify-full
// Notice: use shadwonURL
}
// "couchbase"
return shadownURL(provider, cfgItem)
}
// Config show admin config page
func Config(ctx *context.Context) {
ctx.Data["Title"] = ctx.Tr("admin.config")
@@ -210,7 +275,7 @@ func Config(ctx *context.Context) {
ctx.Data["DisableRouterLog"] = setting.DisableRouterLog
ctx.Data["RunUser"] = setting.RunUser
ctx.Data["RunMode"] = strings.Title(macaron.Env)
ctx.Data["GitVersion"] = setting.Git.Version
ctx.Data["GitVersion"], _ = git.BinVersion()
ctx.Data["RepoRootPath"] = setting.RepoRootPath
ctx.Data["CustomRootPath"] = setting.CustomPath
ctx.Data["StaticRootPath"] = setting.StaticRootPath
@@ -220,6 +285,7 @@ func Config(ctx *context.Context) {
ctx.Data["ReverseProxyAuthEmail"] = setting.ReverseProxyAuthEmail
ctx.Data["SSH"] = setting.SSH
ctx.Data["LFS"] = setting.LFS
ctx.Data["Service"] = setting.Service
ctx.Data["DbCfg"] = models.DbCfg
@@ -233,10 +299,14 @@ func Config(ctx *context.Context) {
ctx.Data["CacheAdapter"] = setting.CacheService.Adapter
ctx.Data["CacheInterval"] = setting.CacheService.Interval
ctx.Data["CacheConn"] = setting.CacheService.Conn
ctx.Data["CacheConn"] = shadowPassword(setting.CacheService.Adapter, setting.CacheService.Conn)
ctx.Data["CacheItemTTL"] = setting.CacheService.TTL
ctx.Data["SessionConfig"] = setting.SessionConfig
sessionCfg := setting.SessionConfig
sessionCfg.ProviderConfig = shadowPassword(sessionCfg.Provider, sessionCfg.ProviderConfig)
ctx.Data["SessionConfig"] = sessionCfg
ctx.Data["DisableGravatar"] = setting.DisableGravatar
ctx.Data["EnableFederatedAvatar"] = setting.EnableFederatedAvatar
@@ -256,10 +326,6 @@ func Config(ctx *context.Context) {
}
ctx.Data["EnvVars"] = envVars
type logger struct {
Mode, Config string
}
ctx.Data["Loggers"] = setting.LogDescriptions
ctx.Data["RedirectMacaronLog"] = setting.RedirectMacaronLog
ctx.Data["EnableAccessLog"] = setting.EnableAccessLog
+69
View File
@@ -0,0 +1,69 @@
// 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 admin
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestShadowPassword(t *testing.T) {
var kases = []struct {
Provider string
CfgItem string
Result string
}{
{
Provider: "redis",
CfgItem: "network=tcp,addr=:6379,password=gitea,db=0,pool_size=100,idle_timeout=180",
Result: "network=tcp,addr=:6379,password=******,db=0,pool_size=100,idle_timeout=180",
},
{
Provider: "mysql",
CfgItem: "root:@tcp(localhost:3306)/gitea?charset=utf8",
Result: "root:******@tcp(localhost:3306)/gitea?charset=utf8",
},
{
Provider: "mysql",
CfgItem: "/gitea?charset=utf8",
Result: "/gitea?charset=utf8",
},
{
Provider: "mysql",
CfgItem: "user:mypassword@/dbname",
Result: "user:******@/dbname",
},
{
Provider: "postgres",
CfgItem: "user=pqgotest dbname=pqgotest sslmode=verify-full",
Result: "user=pqgotest dbname=pqgotest sslmode=verify-full",
},
{
Provider: "postgres",
CfgItem: "user=pqgotest password= dbname=pqgotest sslmode=verify-full",
Result: "user=pqgotest password=****** dbname=pqgotest sslmode=verify-full",
},
{
Provider: "postgres",
CfgItem: "postgres://user:pass@hostname/dbname",
Result: "postgres://user:******@hostname/dbname",
},
{
Provider: "couchbase",
CfgItem: "http://dev-couchbase.example.com:8091/",
Result: "http://dev-couchbase.example.com:8091/",
},
{
Provider: "couchbase",
CfgItem: "http://user:the_password@dev-couchbase.example.com:8091/",
Result: "http://user:******@dev-couchbase.example.com:8091/",
},
}
for _, k := range kases {
assert.EqualValues(t, k.Result, shadowPassword(k.Provider, k.CfgItem))
}
}
+1 -1
View File
@@ -17,7 +17,7 @@ import (
"code.gitea.io/gitea/modules/setting"
"github.com/Unknwon/com"
"github.com/go-xorm/core"
"xorm.io/core"
)
const (
+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{
+6 -4
View File
@@ -5,7 +5,6 @@
package routers
import (
"path"
"strings"
"time"
@@ -19,6 +18,7 @@ import (
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/mailer"
"code.gitea.io/gitea/modules/markup"
"code.gitea.io/gitea/modules/markup/external"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/ssh"
@@ -41,7 +41,7 @@ func checkRunMode() {
func NewServices() {
setting.NewServices()
mailer.NewContext()
cache.NewContext()
_ = cache.NewContext()
}
// In case of problems connecting to DB, retry connection. Eg, PGSQL in Docker Container on Synology
@@ -65,6 +65,9 @@ func initDBEngine() (err error) {
// GlobalInit is for global configuration reload-able.
func GlobalInit() {
setting.NewContext()
if err := git.Init(); err != nil {
log.Fatal("Git module init failed: %v", err)
}
setting.CheckLFSVersion()
log.Trace("AppPath: %s", setting.AppPath)
log.Trace("AppWorkPath: %s", setting.AppWorkPath)
@@ -75,6 +78,7 @@ func GlobalInit() {
if setting.InstallLock {
highlight.NewContext()
external.RegisterParsers()
markup.Init()
if err := initDBEngine(); err == nil {
log.Info("ORM engine initialization successful!")
@@ -86,7 +90,6 @@ func GlobalInit() {
log.Fatal("Failed to initialize OAuth2 support: %v", err)
}
models.LoadRepoConfig()
models.NewRepoContext()
// Booting long running goroutines.
@@ -98,7 +101,6 @@ func GlobalInit() {
models.InitSyncMirrors()
models.InitDeliverHooks()
models.InitTestPullRequests()
log.NewGitLogger(path.Join(setting.LogRootPath, "http.log"))
}
if models.EnableSQLite3 {
log.Info("SQLite3 Supported")
+39 -12
View File
@@ -57,6 +57,7 @@ func Install(ctx *context.Context) {
form.DbPasswd = models.DbCfg.Passwd
form.DbName = models.DbCfg.Name
form.DbPath = models.DbCfg.Path
form.Charset = models.DbCfg.Charset
ctx.Data["CurDbOption"] = "MySQL"
switch models.DbCfg.Type {
@@ -150,6 +151,7 @@ func InstallPost(ctx *context.Context, form auth.InstallForm) {
models.DbCfg.Passwd = form.DbPasswd
models.DbCfg.Name = form.DbName
models.DbCfg.SSLMode = form.SSLMode
models.DbCfg.Charset = form.Charset
models.DbCfg.Path = form.DbPath
if (models.DbCfg.Type == "sqlite3") &&
@@ -213,18 +215,42 @@ func InstallPost(ctx *context.Context, form auth.InstallForm) {
return
}
// Check admin password.
if len(form.AdminName) > 0 && len(form.AdminPasswd) == 0 {
ctx.Data["Err_Admin"] = true
ctx.Data["Err_AdminPasswd"] = true
ctx.RenderWithErr(ctx.Tr("install.err_empty_admin_password"), tplInstall, form)
return
}
if form.AdminPasswd != form.AdminConfirmPasswd {
ctx.Data["Err_Admin"] = true
ctx.Data["Err_AdminPasswd"] = true
ctx.RenderWithErr(ctx.Tr("form.password_not_match"), tplInstall, form)
return
// Check admin user creation
if len(form.AdminName) > 0 {
// Ensure AdminName is valid
if err := models.IsUsableUsername(form.AdminName); err != nil {
ctx.Data["Err_Admin"] = true
ctx.Data["Err_AdminName"] = true
if models.IsErrNameReserved(err) {
ctx.RenderWithErr(ctx.Tr("install.err_admin_name_is_reserved"), tplInstall, form)
return
} else if models.IsErrNamePatternNotAllowed(err) {
ctx.RenderWithErr(ctx.Tr("install.err_admin_name_pattern_not_allowed"), tplInstall, form)
return
}
ctx.RenderWithErr(ctx.Tr("install.err_admin_name_is_invalid"), tplInstall, form)
return
}
// Check Admin email
if len(form.AdminEmail) == 0 {
ctx.Data["Err_Admin"] = true
ctx.Data["Err_AdminEmail"] = true
ctx.RenderWithErr(ctx.Tr("install.err_empty_admin_email"), tplInstall, form)
return
}
// Check admin password.
if len(form.AdminPasswd) == 0 {
ctx.Data["Err_Admin"] = true
ctx.Data["Err_AdminPasswd"] = true
ctx.RenderWithErr(ctx.Tr("install.err_empty_admin_password"), tplInstall, form)
return
}
if form.AdminPasswd != form.AdminConfirmPasswd {
ctx.Data["Err_Admin"] = true
ctx.Data["Err_AdminPasswd"] = true
ctx.RenderWithErr(ctx.Tr("form.password_not_match"), tplInstall, form)
return
}
}
if form.AppURL[len(form.AppURL)-1] != '/' {
@@ -245,6 +271,7 @@ func InstallPost(ctx *context.Context, form auth.InstallForm) {
cfg.Section("database").Key("USER").SetValue(models.DbCfg.User)
cfg.Section("database").Key("PASSWD").SetValue(models.DbCfg.Passwd)
cfg.Section("database").Key("SSL_MODE").SetValue(models.DbCfg.SSLMode)
cfg.Section("database").Key("CHARSET").SetValue(models.DbCfg.Charset)
cfg.Section("database").Key("PATH").SetValue(models.DbCfg.Path)
cfg.Section("").Key("APP_NAME").SetValue(form.AppName)
+5 -1
View File
@@ -5,6 +5,8 @@
package routers
import (
"crypto/subtle"
"github.com/prometheus/client_golang/prometheus/promhttp"
"code.gitea.io/gitea/modules/context"
@@ -22,7 +24,9 @@ func Metrics(ctx *context.Context) {
ctx.Error(401)
return
}
if header != "Bearer "+setting.Metrics.Token {
got := []byte(header)
want := []byte("Bearer " + setting.Metrics.Token)
if subtle.ConstantTimeCompare(got, want) != 1 {
ctx.Error(401)
return
}
+1
View File
@@ -39,6 +39,7 @@ func Settings(ctx *context.Context) {
func SettingsPost(ctx *context.Context, form auth.UpdateOrgSettingForm) {
ctx.Data["Title"] = ctx.Tr("org.settings")
ctx.Data["PageIsSettingsOptions"] = true
ctx.Data["CurrentVisibility"] = structs.VisibleType(ctx.Org.Organization.Visibility)
if ctx.HasError() {
ctx.HTML(200, tplSettingsOptions)
+6 -1
View File
@@ -6,6 +6,7 @@
package org
import (
"net/http"
"path"
"strings"
@@ -291,7 +292,11 @@ func EditTeamPost(ctx *context.Context, form auth.CreateTeamForm) {
Type: tp,
})
}
models.UpdateTeamUnits(t, units)
err := models.UpdateTeamUnits(t, units)
if err != nil {
ctx.Error(http.StatusInternalServerError, "LoadIssue", err.Error())
return
}
}
if ctx.HasError() {
-52
View File
@@ -1,52 +0,0 @@
// 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 private
import (
"code.gitea.io/gitea/models"
macaron "gopkg.in/macaron.v1"
)
// GetProtectedBranchBy get protected branch information
func GetProtectedBranchBy(ctx *macaron.Context) {
repoID := ctx.ParamsInt64(":id")
branchName := ctx.Params("*")
protectBranch, err := models.GetProtectedBranchBy(repoID, branchName)
if err != nil {
ctx.JSON(500, map[string]interface{}{
"err": err.Error(),
})
return
} else if protectBranch != nil {
ctx.JSON(200, protectBranch)
} else {
ctx.JSON(200, &models.ProtectedBranch{
ID: 0,
})
}
}
// CanUserPush returns if user push
func CanUserPush(ctx *macaron.Context) {
pbID := ctx.ParamsInt64(":pbid")
userID := ctx.ParamsInt64(":userid")
protectBranch, err := models.GetProtectedBranchByID(pbID)
if err != nil {
ctx.JSON(500, map[string]interface{}{
"err": err.Error(),
})
return
} else if protectBranch != nil {
ctx.JSON(200, map[string]interface{}{
"can_push": protectBranch.CanUserPush(userID),
})
} else {
ctx.JSON(200, map[string]interface{}{
"can_push": false,
})
}
}
+235
View File
@@ -0,0 +1,235 @@
// 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 private includes all internal routes. The package name internal is ideal but Golang is not allowed, so we use private as package name instead.
package private
import (
"fmt"
"net/http"
"os"
"strings"
"code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/private"
"code.gitea.io/gitea/modules/repofiles"
"code.gitea.io/gitea/modules/util"
macaron "gopkg.in/macaron.v1"
)
// HookPreReceive checks whether a individual commit is acceptable
func HookPreReceive(ctx *macaron.Context) {
ownerName := ctx.Params(":owner")
repoName := ctx.Params(":repo")
oldCommitID := ctx.QueryTrim("old")
newCommitID := ctx.QueryTrim("new")
refFullName := ctx.QueryTrim("ref")
userID := ctx.QueryInt64("userID")
gitObjectDirectory := ctx.QueryTrim("gitObjectDirectory")
gitAlternativeObjectDirectories := ctx.QueryTrim("gitAlternativeObjectDirectories")
prID := ctx.QueryInt64("prID")
branchName := strings.TrimPrefix(refFullName, git.BranchPrefix)
repo, err := models.GetRepositoryByOwnerAndName(ownerName, repoName)
if err != nil {
log.Error("Unable to get repository: %s/%s Error: %v", ownerName, repoName, err)
ctx.JSON(http.StatusInternalServerError, map[string]interface{}{
"err": err.Error(),
})
return
}
repo.OwnerName = ownerName
protectBranch, err := models.GetProtectedBranchBy(repo.ID, branchName)
if err != nil {
log.Error("Unable to get protected branch: %s in %-v Error: %v", branchName, repo, err)
ctx.JSON(500, map[string]interface{}{
"err": err.Error(),
})
return
}
if protectBranch != nil && protectBranch.IsProtected() {
// check and deletion
if newCommitID == git.EmptySHA {
log.Warn("Forbidden: Branch: %s in %-v is protected from deletion", branchName, repo)
ctx.JSON(http.StatusForbidden, map[string]interface{}{
"err": fmt.Sprintf("branch %s is protected from deletion", branchName),
})
return
}
// detect force push
if git.EmptySHA != oldCommitID {
env := append(os.Environ(),
private.GitAlternativeObjectDirectories+"="+gitAlternativeObjectDirectories,
private.GitObjectDirectory+"="+gitObjectDirectory,
private.GitQuarantinePath+"="+gitObjectDirectory,
)
output, err := git.NewCommand("rev-list", "--max-count=1", oldCommitID, "^"+newCommitID).RunInDirWithEnv(repo.RepoPath(), env)
if err != nil {
log.Error("Unable to detect force push between: %s and %s in %-v Error: %v", oldCommitID, newCommitID, repo, err)
ctx.JSON(http.StatusInternalServerError, map[string]interface{}{
"err": fmt.Sprintf("Fail to detect force push: %v", err),
})
return
} else if len(output) > 0 {
log.Warn("Forbidden: Branch: %s in %-v is protected from force push", branchName, repo)
ctx.JSON(http.StatusForbidden, map[string]interface{}{
"err": fmt.Sprintf("branch %s is protected from force push", branchName),
})
return
}
}
canPush := protectBranch.CanUserPush(userID)
if !canPush && prID > 0 {
pr, err := models.GetPullRequestByID(prID)
if err != nil {
log.Error("Unable to get PullRequest %d Error: %v", prID, err)
ctx.JSON(http.StatusInternalServerError, map[string]interface{}{
"err": fmt.Sprintf("Unable to get PullRequest %d Error: %v", prID, err),
})
return
}
if !protectBranch.HasEnoughApprovals(pr) {
log.Warn("Forbidden: User %d cannot push to protected branch: %s in %-v and pr #%d does not have enough approvals", userID, branchName, repo, pr.Index)
ctx.JSON(http.StatusForbidden, map[string]interface{}{
"err": fmt.Sprintf("protected branch %s can not be pushed to and pr #%d does not have enough approvals", branchName, prID),
})
return
}
} else if !canPush {
log.Warn("Forbidden: User %d cannot push to protected branch: %s in %-v", userID, branchName, repo)
ctx.JSON(http.StatusForbidden, map[string]interface{}{
"err": fmt.Sprintf("protected branch %s can not be pushed to", branchName),
})
return
}
}
ctx.PlainText(http.StatusOK, []byte("ok"))
}
// HookPostReceive updates services and users
func HookPostReceive(ctx *macaron.Context) {
ownerName := ctx.Params(":owner")
repoName := ctx.Params(":repo")
oldCommitID := ctx.Query("old")
newCommitID := ctx.Query("new")
refFullName := ctx.Query("ref")
userID := ctx.QueryInt64("userID")
userName := ctx.Query("username")
branch := refFullName
if strings.HasPrefix(refFullName, git.BranchPrefix) {
branch = strings.TrimPrefix(refFullName, git.BranchPrefix)
} else if strings.HasPrefix(refFullName, git.TagPrefix) {
branch = strings.TrimPrefix(refFullName, git.TagPrefix)
}
// Only trigger activity updates for changes to branches or
// tags. Updates to other refs (eg, refs/notes, refs/changes,
// or other less-standard refs spaces are ignored since there
// may be a very large number of them).
if strings.HasPrefix(refFullName, git.BranchPrefix) || strings.HasPrefix(refFullName, git.TagPrefix) {
repo, err := models.GetRepositoryByOwnerAndName(ownerName, repoName)
if err != nil {
log.Error("Failed to get repository: %s/%s Error: %v", ownerName, repoName, err)
ctx.JSON(http.StatusInternalServerError, map[string]interface{}{
"err": fmt.Sprintf("Failed to get repository: %s/%s Error: %v", ownerName, repoName, err),
})
return
}
if err := repofiles.PushUpdate(repo, branch, models.PushUpdateOptions{
RefFullName: refFullName,
OldCommitID: oldCommitID,
NewCommitID: newCommitID,
PusherID: userID,
PusherName: userName,
RepoUserName: ownerName,
RepoName: repoName,
}); err != nil {
log.Error("Failed to Update: %s/%s Branch: %s Error: %v", ownerName, repoName, branch, err)
ctx.JSON(http.StatusInternalServerError, map[string]interface{}{
"err": fmt.Sprintf("Failed to Update: %s/%s Branch: %s Error: %v", ownerName, repoName, branch, err),
})
return
}
}
if newCommitID != git.EmptySHA && strings.HasPrefix(refFullName, git.BranchPrefix) {
repo, err := models.GetRepositoryByOwnerAndName(ownerName, repoName)
if err != nil {
log.Error("Failed to get repository: %s/%s Error: %v", ownerName, repoName, err)
ctx.JSON(http.StatusInternalServerError, map[string]interface{}{
"err": fmt.Sprintf("Failed to get repository: %s/%s Error: %v", ownerName, repoName, err),
})
return
}
repo.OwnerName = ownerName
pullRequestAllowed := repo.AllowsPulls()
if !pullRequestAllowed {
ctx.JSON(http.StatusOK, map[string]interface{}{
"message": false,
})
return
}
baseRepo := repo
if repo.IsFork {
if err := repo.GetBaseRepo(); err != nil {
log.Error("Failed to get Base Repository of Forked repository: %-v Error: %v", repo, err)
ctx.JSON(http.StatusInternalServerError, map[string]interface{}{
"err": fmt.Sprintf("Failed to get Base Repository of Forked repository: %-v Error: %v", repo, err),
})
return
}
baseRepo = repo.BaseRepo
}
if !repo.IsFork && branch == baseRepo.DefaultBranch {
ctx.JSON(http.StatusOK, map[string]interface{}{
"message": false,
})
return
}
pr, err := models.GetUnmergedPullRequest(repo.ID, baseRepo.ID, branch, baseRepo.DefaultBranch)
if err != nil && !models.IsErrPullRequestNotExist(err) {
log.Error("Failed to get active PR in: %-v Branch: %s to: %-v Branch: %s Error: %v", repo, branch, baseRepo, baseRepo.DefaultBranch, err)
ctx.JSON(http.StatusInternalServerError, map[string]interface{}{
"err": fmt.Sprintf(
"Failed to get active PR in: %-v Branch: %s to: %-v Branch: %s Error: %v", repo, branch, baseRepo, baseRepo.DefaultBranch, err),
})
return
}
if pr == nil {
if repo.IsFork {
branch = fmt.Sprintf("%s:%s", repo.OwnerName, branch)
}
ctx.JSON(http.StatusOK, map[string]interface{}{
"message": true,
"create": true,
"branch": branch,
"url": fmt.Sprintf("%s/compare/%s...%s", baseRepo.HTMLURL(), util.PathEscapeSegments(baseRepo.DefaultBranch), util.PathEscapeSegments(branch)),
})
} else {
ctx.JSON(http.StatusOK, map[string]interface{}{
"message": true,
"create": false,
"branch": branch,
"url": fmt.Sprintf("%s/pulls/%d", baseRepo.HTMLURL(), pr.Index),
})
}
return
}
ctx.JSON(http.StatusOK, map[string]interface{}{
"message": false,
})
}
+5 -14
View File
@@ -76,19 +76,10 @@ func CheckUnitUser(ctx *macaron.Context) {
// These APIs will be invoked by internal commands for example `gitea serv` and etc.
func RegisterRoutes(m *macaron.Macaron) {
m.Group("/", func() {
m.Get("/ssh/:id", GetPublicKeyByID)
m.Get("/ssh/:id/user", GetUserByKeyID)
m.Post("/ssh/:id/update", UpdatePublicKey)
m.Post("/repositories/:repoid/keys/:keyid/update", UpdateDeployKey)
m.Get("/repositories/:repoid/user/:userid/checkunituser", CheckUnitUser)
m.Get("/repositories/:repoid/has-keys/:keyid", HasDeployKey)
m.Get("/repositories/:repoid/keys/:keyid", GetDeployKey)
m.Get("/repositories/:repoid/wiki/init", InitWiki)
m.Post("/push/update", PushUpdate)
m.Get("/protectedbranch/:pbid/:userid", CanUserPush)
m.Get("/repo/:owner/:repo", GetRepositoryByOwnerAndName)
m.Get("/branch/:id/*", GetProtectedBranchBy)
m.Get("/repository/:rid", GetRepository)
m.Get("/active-pull-request", GetActivePullRequest)
m.Post("/ssh/:id/update/:repoid", UpdatePublicKeyInRepo)
m.Get("/hook/pre-receive/:owner/:repo", HookPreReceive)
m.Get("/hook/post-receive/:owner/:repo", HookPostReceive)
m.Get("/serv/none/:keyid", ServNoCommand)
m.Get("/serv/command/:keyid/:owner/:repo", ServCommand)
}, CheckInternalToken)
}
+14 -70
View File
@@ -12,12 +12,23 @@ import (
macaron "gopkg.in/macaron.v1"
)
// UpdateDeployKey update deploy key updates
func UpdateDeployKey(ctx *macaron.Context) {
// UpdatePublicKeyInRepo update public key and deploy key updates
func UpdatePublicKeyInRepo(ctx *macaron.Context) {
keyID := ctx.ParamsInt64(":id")
repoID := ctx.ParamsInt64(":repoid")
keyID := ctx.ParamsInt64(":keyid")
if err := models.UpdatePublicKeyUpdated(keyID); err != nil {
ctx.JSON(500, map[string]interface{}{
"err": err.Error(),
})
return
}
deployKey, err := models.GetDeployKeyByRepo(keyID, repoID)
if err != nil {
if models.IsErrDeployKeyNotExist(err) {
ctx.PlainText(200, []byte("success"))
return
}
ctx.JSON(500, map[string]interface{}{
"err": err.Error(),
})
@@ -30,73 +41,6 @@ func UpdateDeployKey(ctx *macaron.Context) {
})
return
}
ctx.PlainText(200, []byte("success"))
}
// UpdatePublicKey update publick key updates
func UpdatePublicKey(ctx *macaron.Context) {
keyID := ctx.ParamsInt64(":id")
if err := models.UpdatePublicKeyUpdated(keyID); err != nil {
ctx.JSON(500, map[string]interface{}{
"err": err.Error(),
})
return
}
ctx.PlainText(200, []byte("success"))
}
//GetPublicKeyByID chainload to models.GetPublicKeyByID
func GetPublicKeyByID(ctx *macaron.Context) {
keyID := ctx.ParamsInt64(":id")
key, err := models.GetPublicKeyByID(keyID)
if err != nil {
ctx.JSON(500, map[string]interface{}{
"err": err.Error(),
})
return
}
ctx.JSON(200, key)
}
//GetUserByKeyID chainload to models.GetUserByKeyID
func GetUserByKeyID(ctx *macaron.Context) {
keyID := ctx.ParamsInt64(":id")
user, err := models.GetUserByKeyID(keyID)
if err != nil {
ctx.JSON(500, map[string]interface{}{
"err": err.Error(),
})
return
}
ctx.JSON(200, user)
}
//GetDeployKey chainload to models.GetDeployKey
func GetDeployKey(ctx *macaron.Context) {
repoID := ctx.ParamsInt64(":repoid")
keyID := ctx.ParamsInt64(":keyid")
dKey, err := models.GetDeployKeyByRepo(keyID, repoID)
if err != nil {
if models.IsErrDeployKeyNotExist(err) {
ctx.JSON(404, []byte("not found"))
return
}
ctx.JSON(500, map[string]interface{}{
"err": err.Error(),
})
return
}
ctx.JSON(200, dKey)
}
//HasDeployKey chainload to models.HasDeployKey
func HasDeployKey(ctx *macaron.Context) {
repoID := ctx.ParamsInt64(":repoid")
keyID := ctx.ParamsInt64(":keyid")
if models.HasDeployKey(keyID, repoID) {
ctx.PlainText(200, []byte("success"))
return
}
ctx.PlainText(404, []byte("not found"))
}
+10 -1
View File
@@ -11,6 +11,7 @@ import (
"code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/repofiles"
macaron "gopkg.in/macaron.v1"
)
@@ -32,7 +33,15 @@ func PushUpdate(ctx *macaron.Context) {
return
}
err := models.PushUpdate(branch, opt)
repo, err := models.GetRepositoryByOwnerAndName(opt.RepoUserName, opt.RepoName)
if err != nil {
ctx.JSON(500, map[string]interface{}{
"err": err.Error(),
})
return
}
err = repofiles.PushUpdate(repo, branch, opt)
if err != nil {
if models.IsErrUserNotExist(err) {
ctx.Error(404)
-83
View File
@@ -1,83 +0,0 @@
// 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 private
import (
"net/http"
"code.gitea.io/gitea/models"
macaron "gopkg.in/macaron.v1"
)
// GetRepository return the default branch of a repository
func GetRepository(ctx *macaron.Context) {
repoID := ctx.ParamsInt64(":rid")
repository, err := models.GetRepositoryByID(repoID)
repository.MustOwnerName()
allowPulls := repository.AllowsPulls()
// put it back to nil because json unmarshal can't unmarshal it
repository.Units = nil
if err != nil {
ctx.JSON(http.StatusInternalServerError, map[string]interface{}{
"err": err.Error(),
})
return
}
if repository.IsFork {
repository.GetBaseRepo()
if err != nil {
ctx.JSON(http.StatusInternalServerError, map[string]interface{}{
"err": err.Error(),
})
return
}
repository.BaseRepo.MustOwnerName()
allowPulls = repository.BaseRepo.AllowsPulls()
// put it back to nil because json unmarshal can't unmarshal it
repository.BaseRepo.Units = nil
}
ctx.JSON(http.StatusOK, struct {
Repository *models.Repository
AllowPullRequest bool
}{
Repository: repository,
AllowPullRequest: allowPulls,
})
}
// GetActivePullRequest return an active pull request when it exists or an empty object
func GetActivePullRequest(ctx *macaron.Context) {
baseRepoID := ctx.QueryInt64("baseRepoID")
headRepoID := ctx.QueryInt64("headRepoID")
baseBranch := ctx.QueryTrim("baseBranch")
if len(baseBranch) == 0 {
ctx.JSON(http.StatusInternalServerError, map[string]interface{}{
"err": "QueryTrim failed",
})
return
}
headBranch := ctx.QueryTrim("headBranch")
if len(headBranch) == 0 {
ctx.JSON(http.StatusInternalServerError, map[string]interface{}{
"err": "QueryTrim failed",
})
return
}
pr, err := models.GetUnmergedPullRequest(headRepoID, baseRepoID, headBranch, baseBranch)
if err != nil && !models.IsErrPullRequestNotExist(err) {
ctx.JSON(http.StatusInternalServerError, map[string]interface{}{
"err": err.Error(),
})
return
}
ctx.JSON(http.StatusOK, pr)
}
+284
View File
@@ -0,0 +1,284 @@
// 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 private includes all internal routes. The package name internal is ideal but Golang is not allowed, so we use private as package name instead.
package private
import (
"fmt"
"net/http"
"strings"
"code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/private"
"code.gitea.io/gitea/modules/setting"
macaron "gopkg.in/macaron.v1"
)
// ServNoCommand returns information about the provided keyid
func ServNoCommand(ctx *macaron.Context) {
keyID := ctx.ParamsInt64(":keyid")
if keyID <= 0 {
ctx.JSON(http.StatusBadRequest, map[string]interface{}{
"err": fmt.Sprintf("Bad key id: %d", keyID),
})
}
results := private.KeyAndOwner{}
key, err := models.GetPublicKeyByID(keyID)
if err != nil {
if models.IsErrKeyNotExist(err) {
ctx.JSON(http.StatusUnauthorized, map[string]interface{}{
"err": fmt.Sprintf("Cannot find key: %d", keyID),
})
return
}
log.Error("Unable to get public key: %d Error: %v", keyID, err)
ctx.JSON(http.StatusInternalServerError, map[string]interface{}{
"err": err.Error(),
})
return
}
results.Key = key
if key.Type == models.KeyTypeUser {
user, err := models.GetUserByID(key.OwnerID)
if err != nil {
if models.IsErrUserNotExist(err) {
ctx.JSON(http.StatusUnauthorized, map[string]interface{}{
"err": fmt.Sprintf("Cannot find owner with id: %d for key: %d", key.OwnerID, keyID),
})
return
}
log.Error("Unable to get owner with id: %d for public key: %d Error: %v", key.OwnerID, keyID, err)
ctx.JSON(http.StatusInternalServerError, map[string]interface{}{
"err": err.Error(),
})
return
}
results.Owner = user
}
ctx.JSON(http.StatusOK, &results)
}
// ServCommand returns information about the provided keyid
func ServCommand(ctx *macaron.Context) {
// Although we provide the verbs we don't need them at present they're just for logging purposes
keyID := ctx.ParamsInt64(":keyid")
ownerName := ctx.Params(":owner")
repoName := ctx.Params(":repo")
mode := models.AccessMode(ctx.QueryInt("mode"))
// Set the basic parts of the results to return
results := private.ServCommandResults{
RepoName: repoName,
OwnerName: ownerName,
KeyID: keyID,
}
// Now because we're not translating things properly let's just default some Engish strings here
modeString := "read"
if mode > models.AccessModeRead {
modeString = "write to"
}
// The default unit we're trying to look at is code
unitType := models.UnitTypeCode
// Unless we're a wiki...
if strings.HasSuffix(repoName, ".wiki") {
// in which case we need to look at the wiki
unitType = models.UnitTypeWiki
// And we'd better munge the reponame and tell downstream we're looking at a wiki
results.IsWiki = true
results.RepoName = repoName[:len(repoName)-5]
}
// Now get the Repository and set the results section
repo, err := models.GetRepositoryByOwnerAndName(results.OwnerName, results.RepoName)
if err != nil {
if models.IsErrRepoNotExist(err) {
ctx.JSON(http.StatusNotFound, map[string]interface{}{
"results": results,
"type": "ErrRepoNotExist",
"err": fmt.Sprintf("Cannot find repository %s/%s", results.OwnerName, results.RepoName),
})
return
}
log.Error("Unable to get repository: %s/%s Error: %v", results.OwnerName, results.RepoName, err)
ctx.JSON(http.StatusInternalServerError, map[string]interface{}{
"results": results,
"type": "InternalServerError",
"err": fmt.Sprintf("Unable to get repository: %s/%s %v", results.OwnerName, results.RepoName, err),
})
return
}
repo.OwnerName = ownerName
results.RepoID = repo.ID
// We can shortcut at this point if the repo is a mirror
if mode > models.AccessModeRead && repo.IsMirror {
ctx.JSON(http.StatusUnauthorized, map[string]interface{}{
"results": results,
"type": "ErrMirrorReadOnly",
"err": fmt.Sprintf("Mirror Repository %s/%s is read-only", results.OwnerName, results.RepoName),
})
return
}
// Get the Public Key represented by the keyID
key, err := models.GetPublicKeyByID(keyID)
if err != nil {
if models.IsErrKeyNotExist(err) {
ctx.JSON(http.StatusUnauthorized, map[string]interface{}{
"results": results,
"type": "ErrKeyNotExist",
"err": fmt.Sprintf("Cannot find key: %d", keyID),
})
return
}
log.Error("Unable to get public key: %d Error: %v", keyID, err)
ctx.JSON(http.StatusInternalServerError, map[string]interface{}{
"results": results,
"type": "InternalServerError",
"err": fmt.Sprintf("Unable to get key: %d Error: %v", keyID, err),
})
return
}
results.KeyName = key.Name
results.KeyID = key.ID
results.UserID = key.OwnerID
// Deploy Keys have ownerID set to 0 therefore we can't use the owner
// So now we need to check if the key is a deploy key
// We'll keep hold of the deploy key here for permissions checking
var deployKey *models.DeployKey
var user *models.User
if key.Type == models.KeyTypeDeploy {
results.IsDeployKey = true
var err error
deployKey, err = models.GetDeployKeyByRepo(key.ID, repo.ID)
if err != nil {
if models.IsErrDeployKeyNotExist(err) {
ctx.JSON(http.StatusUnauthorized, map[string]interface{}{
"results": results,
"type": "ErrDeployKeyNotExist",
"err": fmt.Sprintf("Public (Deploy) Key: %d:%s is not authorized to %s %s/%s.", key.ID, key.Name, modeString, results.OwnerName, results.RepoName),
})
return
}
log.Error("Unable to get deploy for public (deploy) key: %d in %-v Error: %v", key.ID, repo, err)
ctx.JSON(http.StatusInternalServerError, map[string]interface{}{
"results": results,
"type": "InternalServerError",
"err": fmt.Sprintf("Unable to get Deploy Key for Public Key: %d:%s in %s/%s.", key.ID, key.Name, results.OwnerName, results.RepoName),
})
return
}
results.KeyName = deployKey.Name
// FIXME: Deploy keys aren't really the owner of the repo pushing changes
// however we don't have good way of representing deploy keys in hook.go
// so for now use the owner of the repository
results.UserName = results.OwnerName
results.UserID = repo.OwnerID
} else {
// Get the user represented by the Key
var err error
user, err = models.GetUserByID(key.OwnerID)
if err != nil {
if models.IsErrUserNotExist(err) {
ctx.JSON(http.StatusUnauthorized, map[string]interface{}{
"results": results,
"type": "ErrUserNotExist",
"err": fmt.Sprintf("Public Key: %d:%s owner %d does not exist.", key.ID, key.Name, key.OwnerID),
})
return
}
log.Error("Unable to get owner: %d for public key: %d:%s Error: %v", key.OwnerID, key.ID, key.Name, err)
ctx.JSON(http.StatusInternalServerError, map[string]interface{}{
"results": results,
"type": "InternalServerError",
"err": fmt.Sprintf("Unable to get Owner: %d for Deploy Key: %d:%s in %s/%s.", key.OwnerID, key.ID, key.Name, ownerName, repoName),
})
return
}
results.UserName = user.Name
}
// Don't allow pushing if the repo is archived
if mode > models.AccessModeRead && repo.IsArchived {
ctx.JSON(http.StatusUnauthorized, map[string]interface{}{
"results": results,
"type": "ErrRepoIsArchived",
"err": fmt.Sprintf("Repo: %s/%s is archived.", results.OwnerName, results.RepoName),
})
return
}
// Permissions checking:
if mode > models.AccessModeRead || repo.IsPrivate || setting.Service.RequireSignInView {
if key.Type == models.KeyTypeDeploy {
if deployKey.Mode < mode {
ctx.JSON(http.StatusUnauthorized, map[string]interface{}{
"results": results,
"type": "ErrUnauthorized",
"err": fmt.Sprintf("Deploy Key: %d:%s is not authorized to %s %s/%s.", key.ID, key.Name, modeString, results.OwnerName, results.RepoName),
})
return
}
} else {
perm, err := models.GetUserRepoPermission(repo, user)
if err != nil {
log.Error("Unable to get permissions for %-v with key %d in %-v Error: %v", user, key.ID, repo, err)
ctx.JSON(http.StatusInternalServerError, map[string]interface{}{
"results": results,
"type": "InternalServerError",
"err": fmt.Sprintf("Unable to get permissions for user %d:%s with key %d in %s/%s Error: %v", user.ID, user.Name, key.ID, results.OwnerName, results.RepoName, err),
})
return
}
userMode := perm.UnitAccessMode(unitType)
if userMode < mode {
ctx.JSON(http.StatusUnauthorized, map[string]interface{}{
"results": results,
"type": "ErrUnauthorized",
"err": fmt.Sprintf("User: %d:%s with Key: %d:%s is not authorized to %s %s/%s.", user.ID, user.Name, key.ID, key.Name, modeString, ownerName, repoName),
})
return
}
}
}
// Finally if we're trying to touch the wiki we should init it
if results.IsWiki {
if err = repo.InitWiki(); err != nil {
log.Error("Failed to initialize the wiki in %-v Error: %v", repo, err)
ctx.JSON(http.StatusInternalServerError, map[string]interface{}{
"results": results,
"type": "InternalServerError",
"err": fmt.Sprintf("Failed to initialize the wiki in %s/%s Error: %v", ownerName, repoName, err),
})
return
}
}
log.Debug("Serv Results:\nIsWiki: %t\nIsDeployKey: %t\nKeyID: %d\tKeyName: %s\nUserName: %s\nUserID: %d\nOwnerName: %s\nRepoName: %s\nRepoID: %d",
results.IsWiki,
results.IsDeployKey,
results.KeyID,
results.KeyName,
results.UserName,
results.UserID,
results.OwnerName,
results.RepoName,
results.RepoID)
ctx.JSON(http.StatusOK, results)
// We will update the keys in a different call.
}
-34
View File
@@ -1,34 +0,0 @@
// 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 private
import (
"code.gitea.io/gitea/models"
macaron "gopkg.in/macaron.v1"
)
// InitWiki initilizes wiki via repo id
func InitWiki(ctx *macaron.Context) {
repoID := ctx.ParamsInt64("repoid")
repo, err := models.GetRepositoryByID(repoID)
if err != nil {
ctx.JSON(500, map[string]interface{}{
"err": err.Error(),
})
return
}
err = repo.InitWiki()
if err != nil {
ctx.JSON(500, map[string]interface{}{
"err": err.Error(),
})
return
}
ctx.Status(202)
}
+4 -15
View File
@@ -6,13 +6,13 @@ package repo
import (
"fmt"
"net/http"
"strings"
"code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/upload"
)
func renderAttachmentSettings(ctx *context.Context) {
@@ -42,21 +42,10 @@ func UploadAttachment(ctx *context.Context) {
if n > 0 {
buf = buf[:n]
}
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 {
log.Info("Attachment with type %s blocked from upload", fileType)
ctx.Error(400, ErrFileTypeForbidden.Error())
err = upload.VerifyAllowedContentType(buf, strings.Split(setting.AttachmentAllowedTypes, ","))
if err != nil {
ctx.Error(400, err.Error())
return
}
+4 -4
View File
@@ -131,14 +131,14 @@ func RefBlame(ctx *context.Context) {
ctx.Data["FileSize"] = blob.Size()
ctx.Data["FileName"] = blob.Name()
blameReader, err := models.CreateBlameReader(models.RepoPath(userName, repoName), commitID, fileName)
blameReader, err := git.CreateBlameReader(models.RepoPath(userName, repoName), commitID, fileName)
if err != nil {
ctx.NotFound("CreateBlameReader", err)
return
}
defer blameReader.Close()
blameParts := make([]models.BlamePart, 0)
blameParts := make([]git.BlamePart, 0)
for {
blamePart, err := blameReader.NextPart()
@@ -189,10 +189,10 @@ func RefBlame(ctx *context.Context) {
ctx.HTML(200, tplBlame)
}
func renderBlame(ctx *context.Context, blameParts []models.BlamePart, commitNames map[string]models.UserCommit) {
func renderBlame(ctx *context.Context, blameParts []git.BlamePart, commitNames map[string]models.UserCommit) {
repoLink := ctx.Repo.RepoLink
var lines = make([]string, 0, 0)
var lines = make([]string, 0)
var commitInfo bytes.Buffer
var lineNumbers bytes.Buffer
+38 -21
View File
@@ -24,13 +24,14 @@ const (
// Branch contains the branch information
type Branch struct {
Name string
Commit *git.Commit
IsProtected bool
IsDeleted bool
DeletedBranch *models.DeletedBranch
CommitsAhead int
CommitsBehind int
Name string
Commit *git.Commit
IsProtected bool
IsDeleted bool
DeletedBranch *models.DeletedBranch
CommitsAhead int
CommitsBehind int
LatestPullRequest *models.PullRequest
}
// Branches render repository branch page
@@ -131,15 +132,18 @@ func deleteBranch(ctx *context.Context, branchName string) error {
}
// Don't return error below this
if err := models.PushUpdate(branchName, models.PushUpdateOptions{
RefFullName: git.BranchPrefix + branchName,
OldCommitID: commit.ID.String(),
NewCommitID: git.EmptySHA,
PusherID: ctx.User.ID,
PusherName: ctx.User.Name,
RepoUserName: ctx.Repo.Owner.Name,
RepoName: ctx.Repo.Repository.Name,
}); err != nil {
if err := repofiles.PushUpdate(
ctx.Repo.Repository,
branchName,
models.PushUpdateOptions{
RefFullName: git.BranchPrefix + branchName,
OldCommitID: commit.ID.String(),
NewCommitID: git.EmptySHA,
PusherID: ctx.User.ID,
PusherName: ctx.User.Name,
RepoUserName: ctx.Repo.Owner.Name,
RepoName: ctx.Repo.Repository.Name,
}); err != nil {
log.Error("Update: %v", err)
}
@@ -178,12 +182,25 @@ func loadBranches(ctx *context.Context) []*Branch {
return nil
}
pr, err := models.GetLatestPullRequestByHeadInfo(ctx.Repo.Repository.ID, branchName)
if err != nil {
ctx.ServerError("GetLatestPullRequestByHeadInfo", err)
return nil
}
if pr != nil {
if err := pr.LoadIssue(); err != nil {
ctx.ServerError("pr.LoadIssue", err)
return nil
}
}
branches[i] = &Branch{
Name: branchName,
Commit: commit,
IsProtected: isProtected,
CommitsAhead: divergence.Ahead,
CommitsBehind: divergence.Behind,
Name: branchName,
Commit: commit,
IsProtected: isProtected,
CommitsAhead: divergence.Ahead,
CommitsBehind: divergence.Behind,
LatestPullRequest: pr,
}
}
+17 -55
View File
@@ -15,12 +15,13 @@ import (
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/templates"
)
const (
tplCommits base.TplName = "repo/commits"
tplGraph base.TplName = "repo/graph"
tplDiff base.TplName = "repo/diff/page"
tplCommits base.TplName = "repo/commits"
tplGraph base.TplName = "repo/graph"
tplCommitPage base.TplName = "repo/commit_page"
)
// RefCommits render commits page
@@ -246,12 +247,24 @@ func Diff(ctx *context.Context) {
ctx.Data["Parents"] = parents
ctx.Data["DiffNotAvailable"] = diff.NumFiles() == 0
ctx.Data["SourcePath"] = setting.AppSubURL + "/" + path.Join(userName, repoName, "src", "commit", commitID)
note := &git.Note{}
err = git.GetNote(ctx.Repo.GitRepo, commitID, note)
if err == nil {
ctx.Data["Note"] = string(templates.ToUTF8WithFallback(note.Message))
ctx.Data["NoteCommit"] = note.Commit
ctx.Data["NoteAuthor"] = models.ValidateCommitWithEmail(note.Commit)
}
if commit.ParentCount() > 0 {
ctx.Data["BeforeSourcePath"] = setting.AppSubURL + "/" + path.Join(userName, repoName, "src", "commit", parents[0])
}
ctx.Data["RawPath"] = setting.AppSubURL + "/" + path.Join(userName, repoName, "raw", "commit", commitID)
ctx.Data["BranchName"], err = commit.GetBranchName()
ctx.HTML(200, tplDiff)
if err != nil {
ctx.ServerError("commit.GetBranchName", err)
}
ctx.HTML(200, tplCommitPage)
}
// RawDiff dumps diff results of repository in given commit ID to io.Writer
@@ -266,54 +279,3 @@ func RawDiff(ctx *context.Context) {
return
}
}
// CompareDiff show different from one commit to another commit
func CompareDiff(ctx *context.Context) {
ctx.Data["IsRepoToolbarCommits"] = true
ctx.Data["IsDiffCompare"] = true
userName := ctx.Repo.Owner.Name
repoName := ctx.Repo.Repository.Name
beforeCommitID := ctx.Params(":before")
afterCommitID := ctx.Params(":after")
commit, err := ctx.Repo.GitRepo.GetCommit(afterCommitID)
if err != nil {
ctx.NotFound("GetCommit", err)
return
}
diff, err := models.GetDiffRange(models.RepoPath(userName, repoName), beforeCommitID,
afterCommitID, setting.Git.MaxGitDiffLines,
setting.Git.MaxGitDiffLineCharacters, setting.Git.MaxGitDiffFiles)
if err != nil {
ctx.NotFound("GetDiffRange", err)
return
}
commits, err := commit.CommitsBeforeUntil(beforeCommitID)
if err != nil {
ctx.ServerError("CommitsBeforeUntil", err)
return
}
commits = models.ValidateCommitsWithEmails(commits)
commits = models.ParseCommitsWithSignature(commits)
commits = models.ParseCommitsWithStatus(commits, ctx.Repo.Repository)
ctx.Data["CommitRepoLink"] = ctx.Repo.RepoLink
ctx.Data["Commits"] = commits
ctx.Data["CommitCount"] = commits.Len()
ctx.Data["BeforeCommitID"] = beforeCommitID
ctx.Data["AfterCommitID"] = afterCommitID
ctx.Data["Username"] = userName
ctx.Data["Reponame"] = repoName
ctx.Data["IsImageFile"] = commit.IsImageFile
ctx.Data["Title"] = "Comparing " + base.ShortSha(beforeCommitID) + "..." + base.ShortSha(afterCommitID) + " · " + userName + "/" + repoName
ctx.Data["Commit"] = commit
ctx.Data["Diff"] = diff
ctx.Data["DiffNotAvailable"] = diff.NumFiles() == 0
ctx.Data["SourcePath"] = setting.AppSubURL + "/" + path.Join(userName, repoName, "src", "commit", afterCommitID)
ctx.Data["BeforeSourcePath"] = setting.AppSubURL + "/" + path.Join(userName, repoName, "src", "commit", beforeCommitID)
ctx.Data["RawPath"] = setting.AppSubURL + "/" + path.Join(userName, repoName, "raw", "commit", afterCommitID)
ctx.Data["RequireHighlightJS"] = true
ctx.HTML(200, tplDiff)
}
+337
View File
@@ -0,0 +1,337 @@
// 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 (
"path"
"strings"
"code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
)
const (
tplCompare base.TplName = "repo/diff/compare"
)
// ParseCompareInfo parse compare info between two commit for preparing comparing references
func ParseCompareInfo(ctx *context.Context) (*models.User, *models.Repository, *git.Repository, *git.CompareInfo, string, string) {
baseRepo := ctx.Repo.Repository
// Get compared branches information
// format: <base branch>...[<head repo>:]<head branch>
// base<-head: master...head:feature
// same repo: master...feature
var (
headUser *models.User
headBranch string
isSameRepo bool
infoPath string
err error
)
infoPath = ctx.Params("*")
infos := strings.Split(infoPath, "...")
if len(infos) != 2 {
log.Trace("ParseCompareInfo[%d]: not enough compared branches information %s", baseRepo.ID, infos)
ctx.NotFound("CompareAndPullRequest", nil)
return nil, nil, nil, nil, "", ""
}
baseBranch := infos[0]
ctx.Data["BaseBranch"] = baseBranch
// If there is no head repository, it means compare between same repository.
headInfos := strings.Split(infos[1], ":")
if len(headInfos) == 1 {
isSameRepo = true
headUser = ctx.Repo.Owner
headBranch = headInfos[0]
} else if len(headInfos) == 2 {
headUser, err = models.GetUserByName(headInfos[0])
if err != nil {
if models.IsErrUserNotExist(err) {
ctx.NotFound("GetUserByName", nil)
} else {
ctx.ServerError("GetUserByName", err)
}
return nil, nil, nil, nil, "", ""
}
headBranch = headInfos[1]
isSameRepo = headUser.ID == ctx.Repo.Owner.ID
} else {
ctx.NotFound("CompareAndPullRequest", nil)
return nil, nil, nil, nil, "", ""
}
ctx.Data["HeadUser"] = headUser
ctx.Data["HeadBranch"] = headBranch
ctx.Repo.PullRequest.SameRepo = isSameRepo
// Check if base branch is valid.
baseIsCommit := ctx.Repo.GitRepo.IsCommitExist(baseBranch)
baseIsBranch := ctx.Repo.GitRepo.IsBranchExist(baseBranch)
baseIsTag := ctx.Repo.GitRepo.IsTagExist(baseBranch)
if !baseIsCommit && !baseIsBranch && !baseIsTag {
// Check if baseBranch is short sha commit hash
if baseCommit, _ := ctx.Repo.GitRepo.GetCommit(baseBranch); baseCommit != nil {
baseBranch = baseCommit.ID.String()
ctx.Data["BaseBranch"] = baseBranch
baseIsCommit = true
} else {
ctx.NotFound("IsRefExist", nil)
return nil, nil, nil, nil, "", ""
}
}
ctx.Data["BaseIsCommit"] = baseIsCommit
ctx.Data["BaseIsBranch"] = baseIsBranch
ctx.Data["BaseIsTag"] = baseIsTag
// Check if current user has fork of repository or in the same repository.
headRepo, has := models.HasForkedRepo(headUser.ID, baseRepo.ID)
if !has && !isSameRepo {
ctx.Data["PageIsComparePull"] = false
}
var headGitRepo *git.Repository
if isSameRepo {
headRepo = ctx.Repo.Repository
headGitRepo = ctx.Repo.GitRepo
ctx.Data["BaseName"] = headUser.Name
} else {
headGitRepo, err = git.OpenRepository(models.RepoPath(headUser.Name, headRepo.Name))
ctx.Data["BaseName"] = baseRepo.OwnerName
if err != nil {
ctx.ServerError("OpenRepository", err)
return nil, nil, nil, nil, "", ""
}
}
// user should have permission to read baseRepo's codes and pulls, NOT headRepo's
permBase, err := models.GetUserRepoPermission(baseRepo, ctx.User)
if err != nil {
ctx.ServerError("GetUserRepoPermission", err)
return nil, nil, nil, nil, "", ""
}
if !permBase.CanRead(models.UnitTypeCode) {
if log.IsTrace() {
log.Trace("Permission Denied: User: %-v cannot read code in Repo: %-v\nUser in baseRepo has Permissions: %-+v",
ctx.User,
baseRepo,
permBase)
}
ctx.NotFound("ParseCompareInfo", nil)
return nil, nil, nil, nil, "", ""
}
// user should have permission to read headrepo's codes
permHead, err := models.GetUserRepoPermission(headRepo, ctx.User)
if err != nil {
ctx.ServerError("GetUserRepoPermission", err)
return nil, nil, nil, nil, "", ""
}
if !permHead.CanRead(models.UnitTypeCode) {
if log.IsTrace() {
log.Trace("Permission Denied: User: %-v cannot read code in Repo: %-v\nUser in headRepo has Permissions: %-+v",
ctx.User,
headRepo,
permHead)
}
ctx.NotFound("ParseCompareInfo", nil)
return nil, nil, nil, nil, "", ""
}
// Check if head branch is valid.
headIsCommit := ctx.Repo.GitRepo.IsCommitExist(headBranch)
headIsBranch := headGitRepo.IsBranchExist(headBranch)
headIsTag := headGitRepo.IsTagExist(headBranch)
if !headIsCommit && !headIsBranch && !headIsTag {
// Check if headBranch is short sha commit hash
if headCommit, _ := ctx.Repo.GitRepo.GetCommit(headBranch); headCommit != nil {
headBranch = headCommit.ID.String()
ctx.Data["HeadBranch"] = headBranch
headIsCommit = true
} else {
ctx.NotFound("IsRefExist", nil)
return nil, nil, nil, nil, "", ""
}
}
ctx.Data["HeadIsCommit"] = headIsCommit
ctx.Data["HeadIsBranch"] = headIsBranch
ctx.Data["HeadIsTag"] = headIsTag
// Treat as pull request if both references are branches
if ctx.Data["PageIsComparePull"] == nil {
ctx.Data["PageIsComparePull"] = headIsBranch && baseIsBranch
}
if ctx.Data["PageIsComparePull"] == true && !permBase.CanReadIssuesOrPulls(true) {
if log.IsTrace() {
log.Trace("Permission Denied: User: %-v cannot create/read pull requests in Repo: %-v\nUser in baseRepo has Permissions: %-+v",
ctx.User,
baseRepo,
permBase)
}
ctx.NotFound("ParseCompareInfo", nil)
return nil, nil, nil, nil, "", ""
}
compareInfo, err := headGitRepo.GetCompareInfo(models.RepoPath(baseRepo.Owner.Name, baseRepo.Name), baseBranch, headBranch)
if err != nil {
ctx.ServerError("GetCompareInfo", err)
return nil, nil, nil, nil, "", ""
}
ctx.Data["BeforeCommitID"] = compareInfo.MergeBase
return headUser, headRepo, headGitRepo, compareInfo, baseBranch, headBranch
}
// PrepareCompareDiff renders compare diff page
func PrepareCompareDiff(
ctx *context.Context,
headUser *models.User,
headRepo *models.Repository,
headGitRepo *git.Repository,
compareInfo *git.CompareInfo,
baseBranch, headBranch string) bool {
var (
repo = ctx.Repo.Repository
err error
title string
)
// Get diff information.
ctx.Data["CommitRepoLink"] = headRepo.Link()
headCommitID := headBranch
if ctx.Data["HeadIsCommit"] == false {
if ctx.Data["HeadIsTag"] == true {
headCommitID, err = headGitRepo.GetTagCommitID(headBranch)
} else {
headCommitID, err = headGitRepo.GetBranchCommitID(headBranch)
}
if err != nil {
ctx.ServerError("GetRefCommitID", err)
return false
}
}
ctx.Data["AfterCommitID"] = headCommitID
if headCommitID == compareInfo.MergeBase {
ctx.Data["IsNothingToCompare"] = true
return true
}
diff, err := models.GetDiffRange(models.RepoPath(headUser.Name, headRepo.Name),
compareInfo.MergeBase, headCommitID, setting.Git.MaxGitDiffLines,
setting.Git.MaxGitDiffLineCharacters, setting.Git.MaxGitDiffFiles)
if err != nil {
ctx.ServerError("GetDiffRange", err)
return false
}
ctx.Data["Diff"] = diff
ctx.Data["DiffNotAvailable"] = diff.NumFiles() == 0
headCommit, err := headGitRepo.GetCommit(headCommitID)
if err != nil {
ctx.ServerError("GetCommit", err)
return false
}
compareInfo.Commits = models.ValidateCommitsWithEmails(compareInfo.Commits)
compareInfo.Commits = models.ParseCommitsWithSignature(compareInfo.Commits)
compareInfo.Commits = models.ParseCommitsWithStatus(compareInfo.Commits, headRepo)
ctx.Data["Commits"] = compareInfo.Commits
ctx.Data["CommitCount"] = compareInfo.Commits.Len()
if ctx.Data["CommitCount"] == 0 {
ctx.Data["PageIsComparePull"] = false
}
if compareInfo.Commits.Len() == 1 {
c := compareInfo.Commits.Front().Value.(models.SignCommitWithStatuses)
title = strings.TrimSpace(c.UserCommit.Summary())
body := strings.Split(strings.TrimSpace(c.UserCommit.Message()), "\n")
if len(body) > 1 {
ctx.Data["content"] = strings.Join(body[1:], "\n")
}
} else {
title = headBranch
}
ctx.Data["title"] = title
ctx.Data["Username"] = headUser.Name
ctx.Data["Reponame"] = headRepo.Name
ctx.Data["IsImageFile"] = headCommit.IsImageFile
headTarget := path.Join(headUser.Name, repo.Name)
ctx.Data["SourcePath"] = setting.AppSubURL + "/" + path.Join(headTarget, "src", "commit", headCommitID)
ctx.Data["BeforeSourcePath"] = setting.AppSubURL + "/" + path.Join(headTarget, "src", "commit", compareInfo.MergeBase)
ctx.Data["RawPath"] = setting.AppSubURL + "/" + path.Join(headTarget, "raw", "commit", headCommitID)
return false
}
// CompareDiff show different from one commit to another commit
func CompareDiff(ctx *context.Context) {
headUser, headRepo, headGitRepo, compareInfo, baseBranch, headBranch := ParseCompareInfo(ctx)
if ctx.Written() {
return
}
nothingToCompare := PrepareCompareDiff(ctx, headUser, headRepo, headGitRepo, compareInfo, baseBranch, headBranch)
if ctx.Written() {
return
}
if ctx.Data["PageIsComparePull"] == true {
pr, err := models.GetUnmergedPullRequest(headRepo.ID, ctx.Repo.Repository.ID, headBranch, baseBranch)
if err != nil {
if !models.IsErrPullRequestNotExist(err) {
ctx.ServerError("GetUnmergedPullRequest", err)
return
}
} else {
ctx.Data["HasPullRequest"] = true
ctx.Data["PullRequest"] = pr
ctx.HTML(200, tplCompareDiff)
return
}
if !nothingToCompare {
// Setup information for new form.
RetrieveRepoMetas(ctx, ctx.Repo.Repository)
if ctx.Written() {
return
}
}
headBranches, err := headGitRepo.GetBranches()
if err != nil {
ctx.ServerError("GetBranches", err)
return
}
ctx.Data["HeadBranches"] = headBranches
}
beforeCommitID := ctx.Data["BeforeCommitID"].(string)
afterCommitID := ctx.Data["AfterCommitID"].(string)
ctx.Data["Title"] = "Comparing " + base.ShortSha(beforeCommitID) + "..." + base.ShortSha(afterCommitID)
ctx.Data["IsRepoToolbarCommits"] = true
ctx.Data["IsDiffCompare"] = true
ctx.Data["RequireHighlightJS"] = true
ctx.Data["RequireTribute"] = true
ctx.Data["PullRequestWorkInProgressPrefixes"] = setting.Repository.PullRequest.WorkInProgressPrefixes
setTemplateIfExists(ctx, pullRequestTemplateKey, pullRequestTemplateCandidates)
renderAttachmentSettings(ctx)
ctx.HTML(200, tplCompare)
}
+16 -4
View File
@@ -15,6 +15,7 @@ import (
"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/lfs"
"code.gitea.io/gitea/modules/log"
)
// ServeData download file from io.Reader
@@ -39,8 +40,11 @@ func ServeData(ctx *context.Context, name string, reader io.Reader) error {
ctx.Resp.Header().Set("Content-Disposition", fmt.Sprintf(`attachment; filename="%s"`, name))
}
ctx.Resp.Write(buf)
_, err := io.Copy(ctx.Resp, reader)
_, err := ctx.Resp.Write(buf)
if err != nil {
return err
}
_, err = io.Copy(ctx.Resp, reader)
return err
}
@@ -50,7 +54,11 @@ func ServeBlob(ctx *context.Context, blob *git.Blob) error {
if err != nil {
return err
}
defer dataRc.Close()
defer func() {
if err = dataRc.Close(); err != nil {
log.Error("ServeBlob: Close: %v", err)
}
}()
return ServeData(ctx, ctx.Repo.TreePath, dataRc)
}
@@ -61,7 +69,11 @@ func ServeBlobOrLFS(ctx *context.Context, blob *git.Blob) error {
if err != nil {
return err
}
defer dataRc.Close()
defer func() {
if err = dataRc.Close(); err != nil {
log.Error("ServeBlobOrLFS: Close: %v", err)
}
}()
if meta, _ := lfs.ReadPointerFile(dataRc); meta != nil {
meta, _ = ctx.Repo.Repository.GetLFSMetaObjectByOid(meta.Oid)
+5 -24
View File
@@ -7,7 +7,6 @@ package repo
import (
"fmt"
"io/ioutil"
"net/http"
"path"
"strings"
@@ -20,6 +19,7 @@ import (
"code.gitea.io/gitea/modules/repofiles"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/templates"
"code.gitea.io/gitea/modules/upload"
"code.gitea.io/gitea/modules/util"
)
@@ -118,9 +118,7 @@ func editFile(ctx *context.Context, isNewFile bool) {
d, _ := ioutil.ReadAll(dataRc)
buf = append(buf, d...)
if content, err := templates.ToUTF8WithErr(buf); err != nil {
if err != nil {
log.Error("ToUTF8WithErr: %v", err)
}
log.Error("ToUTF8WithErr: %v", err)
ctx.Data["FileContent"] = string(buf)
} else {
ctx.Data["FileContent"] = content
@@ -235,16 +233,12 @@ func editFilePost(ctx *context.Context, form auth.EditRepoFileForm, isNewFile bo
switch fileErr.Type {
case git.EntryModeSymlink:
ctx.RenderWithErr(ctx.Tr("repo.editor.file_is_a_symlink", fileErr.Path), tplEditFile, &form)
break
case git.EntryModeTree:
ctx.RenderWithErr(ctx.Tr("repo.editor.filename_is_a_directory", fileErr.Path), tplEditFile, &form)
break
case git.EntryModeBlob:
ctx.RenderWithErr(ctx.Tr("repo.editor.directory_is_a_file", fileErr.Path), tplEditFile, &form)
break
default:
ctx.Error(500, err.Error())
break
}
} else {
ctx.Error(500, err.Error())
@@ -403,16 +397,12 @@ func DeleteFilePost(ctx *context.Context, form auth.DeleteRepoFileForm) {
switch fileErr.Type {
case git.EntryModeSymlink:
ctx.RenderWithErr(ctx.Tr("repo.editor.file_is_a_symlink", fileErr.Path), tplEditFile, &form)
break
case git.EntryModeTree:
ctx.RenderWithErr(ctx.Tr("repo.editor.filename_is_a_directory", fileErr.Path), tplEditFile, &form)
break
case git.EntryModeBlob:
ctx.RenderWithErr(ctx.Tr("repo.editor.directory_is_a_file", fileErr.Path), tplEditFile, &form)
break
default:
ctx.ServerError("DeleteRepoFile", err)
break
}
} else {
ctx.ServerError("DeleteRepoFile", err)
@@ -604,20 +594,11 @@ func UploadFileToServer(ctx *context.Context) {
if n > 0 {
buf = buf[:n]
}
fileType := http.DetectContentType(buf)
if len(setting.Repository.Upload.AllowedTypes) > 0 {
allowed := false
for _, t := range setting.Repository.Upload.AllowedTypes {
t := strings.Trim(t, " ")
if t == "*/*" || t == fileType {
allowed = true
break
}
}
if !allowed {
ctx.Error(400, ErrFileTypeForbidden.Error())
err = upload.VerifyAllowedContentType(buf, setting.Repository.Upload.AllowedTypes)
if err != nil {
ctx.Error(400, err.Error())
return
}
}
+52 -41
View File
@@ -22,6 +22,7 @@ import (
"code.gitea.io/gitea/modules/auth"
"code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/util"
@@ -206,10 +207,8 @@ func HTTP(ctx *context.Context) {
if err = models.UpdateAccessToken(token); err != nil {
ctx.ServerError("UpdateAccessToken", err)
}
} else {
if !models.IsErrAccessTokenNotExist(err) && !models.IsErrAccessTokenEmpty(err) {
log.Error("GetAccessTokenBySha: %v", err)
}
} else if !models.IsErrAccessTokenNotExist(err) && !models.IsErrAccessTokenEmpty(err) {
log.Error("GetAccessTokenBySha: %v", err)
}
if authUser == nil {
@@ -332,32 +331,24 @@ type route struct {
}
var routes = []route{
{regexp.MustCompile("(.*?)/git-upload-pack$"), "POST", serviceUploadPack},
{regexp.MustCompile("(.*?)/git-receive-pack$"), "POST", serviceReceivePack},
{regexp.MustCompile("(.*?)/info/refs$"), "GET", getInfoRefs},
{regexp.MustCompile("(.*?)/HEAD$"), "GET", getTextFile},
{regexp.MustCompile("(.*?)/objects/info/alternates$"), "GET", getTextFile},
{regexp.MustCompile("(.*?)/objects/info/http-alternates$"), "GET", getTextFile},
{regexp.MustCompile("(.*?)/objects/info/packs$"), "GET", getInfoPacks},
{regexp.MustCompile("(.*?)/objects/info/[^/]*$"), "GET", getTextFile},
{regexp.MustCompile("(.*?)/objects/[0-9a-f]{2}/[0-9a-f]{38}$"), "GET", getLooseObject},
{regexp.MustCompile("(.*?)/objects/pack/pack-[0-9a-f]{40}\\.pack$"), "GET", getPackFile},
{regexp.MustCompile("(.*?)/objects/pack/pack-[0-9a-f]{40}\\.idx$"), "GET", getIdxFile},
}
// FIXME: use process module
func gitCommand(dir string, args ...string) []byte {
cmd := exec.Command("git", args...)
cmd.Dir = dir
out, err := cmd.Output()
if err != nil {
log.GitLogger.Error(fmt.Sprintf("%v - %s", err, out))
}
return out
{regexp.MustCompile(`(.*?)/git-upload-pack$`), "POST", serviceUploadPack},
{regexp.MustCompile(`(.*?)/git-receive-pack$`), "POST", serviceReceivePack},
{regexp.MustCompile(`(.*?)/info/refs$`), "GET", getInfoRefs},
{regexp.MustCompile(`(.*?)/HEAD$`), "GET", getTextFile},
{regexp.MustCompile(`(.*?)/objects/info/alternates$`), "GET", getTextFile},
{regexp.MustCompile(`(.*?)/objects/info/http-alternates$`), "GET", getTextFile},
{regexp.MustCompile(`(.*?)/objects/info/packs$`), "GET", getInfoPacks},
{regexp.MustCompile(`(.*?)/objects/info/[^/]*$`), "GET", getTextFile},
{regexp.MustCompile(`(.*?)/objects/[0-9a-f]{2}/[0-9a-f]{38}$`), "GET", getLooseObject},
{regexp.MustCompile(`(.*?)/objects/pack/pack-[0-9a-f]{40}\.pack$`), "GET", getPackFile},
{regexp.MustCompile(`(.*?)/objects/pack/pack-[0-9a-f]{40}\.idx$`), "GET", getIdxFile},
}
func getGitConfig(option, dir string) string {
out := string(gitCommand(dir, "config", option))
out, err := git.NewCommand("config", option).RunInDir(dir)
if err != nil {
log.Error("%v - %s", err, out)
}
return out[0 : len(out)-1]
}
@@ -393,7 +384,12 @@ func hasAccess(service string, h serviceHandler, checkContentType bool) bool {
}
func serviceRPC(h serviceHandler, service string) {
defer h.r.Body.Close()
defer func() {
if err := h.r.Body.Close(); err != nil {
log.Error("serviceRPC: Close: %v", err)
}
}()
if !hasAccess(service, h, true) {
h.w.WriteHeader(http.StatusUnauthorized)
@@ -409,7 +405,7 @@ func serviceRPC(h serviceHandler, service string) {
if h.r.Header.Get("Content-Encoding") == "gzip" {
reqBody, err = gzip.NewReader(reqBody)
if err != nil {
log.GitLogger.Error("Fail to create gzip reader: %v", err)
log.Error("Fail to create gzip reader: %v", err)
h.w.WriteHeader(http.StatusInternalServerError)
return
}
@@ -419,7 +415,7 @@ func serviceRPC(h serviceHandler, service string) {
h.environ = append(h.environ, "SSH_ORIGINAL_COMMAND="+service)
var stderr bytes.Buffer
cmd := exec.Command("git", service, "--stateless-rpc", h.dir)
cmd := exec.Command(git.GitExecutable, service, "--stateless-rpc", h.dir)
cmd.Dir = h.dir
if service == "receive-pack" {
cmd.Env = append(os.Environ(), h.environ...)
@@ -428,7 +424,7 @@ func serviceRPC(h serviceHandler, service string) {
cmd.Stdin = reqBody
cmd.Stderr = &stderr
if err := cmd.Run(); err != nil {
log.GitLogger.Error("Fail to serve RPC(%s): %v - %v", service, err, stderr)
log.Error("Fail to serve RPC(%s): %v - %v", service, err, stderr)
return
}
}
@@ -450,7 +446,11 @@ func getServiceType(r *http.Request) string {
}
func updateServerInfo(dir string) []byte {
return gitCommand(dir, "update-server-info")
out, err := git.NewCommand("update-server-info").RunInDirBytes(dir)
if err != nil {
log.Error(fmt.Sprintf("%v - %s", err, string(out)))
}
return out
}
func packetWrite(str string) []byte {
@@ -465,13 +465,16 @@ func getInfoRefs(h serviceHandler) {
h.setHeaderNoCache()
if hasAccess(getServiceType(h.r), h, false) {
service := getServiceType(h.r)
refs := gitCommand(h.dir, service, "--stateless-rpc", "--advertise-refs", ".")
refs, err := git.NewCommand(service, "--stateless-rpc", "--advertise-refs", ".").RunInDirBytes(h.dir)
if err != nil {
log.Error(fmt.Sprintf("%v - %s", err, string(refs)))
}
h.w.Header().Set("Content-Type", fmt.Sprintf("application/x-git-%s-advertisement", service))
h.w.WriteHeader(http.StatusOK)
h.w.Write(packetWrite("# service=git-" + service + "\n"))
h.w.Write([]byte("0000"))
h.w.Write(refs)
_, _ = h.w.Write(packetWrite("# service=git-" + service + "\n"))
_, _ = h.w.Write([]byte("0000"))
_, _ = h.w.Write(refs)
} else {
updateServerInfo(h.dir)
h.sendFile("text/plain; charset=utf-8")
@@ -524,16 +527,25 @@ func HTTPBackend(ctx *context.Context, cfg *serviceConfig) http.HandlerFunc {
if m := route.reg.FindStringSubmatch(r.URL.Path); m != nil {
if setting.Repository.DisableHTTPGit {
w.WriteHeader(http.StatusForbidden)
w.Write([]byte("Interacting with repositories by HTTP protocol is not allowed"))
_, err := w.Write([]byte("Interacting with repositories by HTTP protocol is not allowed"))
if err != nil {
log.Error(err.Error())
}
return
}
if route.method != r.Method {
if r.Proto == "HTTP/1.1" {
w.WriteHeader(http.StatusMethodNotAllowed)
w.Write([]byte("Method Not Allowed"))
_, err := w.Write([]byte("Method Not Allowed"))
if err != nil {
log.Error(err.Error())
}
} else {
w.WriteHeader(http.StatusBadRequest)
w.Write([]byte("Bad Request"))
_, err := w.Write([]byte("Bad Request"))
if err != nil {
log.Error(err.Error())
}
}
return
}
@@ -541,7 +553,7 @@ func HTTPBackend(ctx *context.Context, cfg *serviceConfig) http.HandlerFunc {
file := strings.Replace(r.URL.Path, m[1]+"/", "", 1)
dir, err := getGitRepoPath(m[1])
if err != nil {
log.GitLogger.Error(err.Error())
log.Error(err.Error())
ctx.NotFound("HTTPBackend", err)
return
}
@@ -552,6 +564,5 @@ func HTTPBackend(ctx *context.Context, cfg *serviceConfig) http.HandlerFunc {
}
ctx.NotFound("HTTPBackend", nil)
return
}
}
+20 -10
View File
@@ -24,6 +24,7 @@ import (
"code.gitea.io/gitea/modules/markup/markdown"
"code.gitea.io/gitea/modules/notification"
"code.gitea.io/gitea/modules/setting"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/util"
"github.com/Unknwon/com"
@@ -40,8 +41,6 @@ const (
)
var (
// ErrFileTypeForbidden not allowed file type error
ErrFileTypeForbidden = errors.New("File type is not allowed")
// ErrTooManyFiles upload too many files
ErrTooManyFiles = errors.New("Maximum number of files to upload exceeded")
// IssueTemplateCandidates issue templates
@@ -305,7 +304,7 @@ func Issues(ctx *context.Context) {
var err error
// Get milestones.
ctx.Data["Milestones"], err = models.GetMilestonesByRepoID(ctx.Repo.Repository.ID)
ctx.Data["Milestones"], err = models.GetMilestonesByRepoID(ctx.Repo.Repository.ID, api.StateType(ctx.Query("state")))
if err != nil {
ctx.ServerError("GetAllRepoMilestones", err)
return
@@ -425,12 +424,14 @@ func NewIssue(ctx *context.Context) {
ctx.Data["BodyQuery"] = body
milestoneID := ctx.QueryInt64("milestone")
milestone, err := models.GetMilestoneByID(milestoneID)
if err != nil {
log.Error("GetMilestoneByID: %d: %v", milestoneID, err)
} else {
ctx.Data["milestone_id"] = milestoneID
ctx.Data["Milestone"] = milestone
if milestoneID > 0 {
milestone, err := models.GetMilestoneByID(milestoneID)
if err != nil {
log.Error("GetMilestoneByID: %d: %v", milestoneID, err)
} else {
ctx.Data["milestone_id"] = milestoneID
ctx.Data["Milestone"] = milestone
}
}
setTemplateIfExists(ctx, issueTemplateKey, IssueTemplateCandidates)
@@ -942,7 +943,15 @@ func ViewIssue(ctx *context.Context) {
// Get Dependencies
ctx.Data["BlockedByDependencies"], err = issue.BlockedByDependencies()
if err != nil {
ctx.ServerError("BlockedByDependencies", err)
return
}
ctx.Data["BlockingDependencies"], err = issue.BlockingDependencies()
if err != nil {
ctx.ServerError("BlockingDependencies", err)
return
}
ctx.Data["Participants"] = participants
ctx.Data["NumParticipants"] = len(participants)
@@ -1223,7 +1232,8 @@ func NewComment(ctx *context.Context, form auth.CreateCommentForm) {
if form.Status == "reopen" && issue.IsPull {
pull := issue.PullRequest
pr, err := models.GetUnmergedPullRequest(pull.HeadRepoID, pull.BaseRepoID, pull.HeadBranch, pull.BaseBranch)
var err error
pr, err = models.GetUnmergedPullRequest(pull.HeadRepoID, pull.BaseRepoID, pull.HeadBranch, pull.BaseBranch)
if err != nil {
if !models.IsErrPullRequestNotExist(err) {
ctx.ServerError("GetUnmergedPullRequest", err)
-1
View File
@@ -129,7 +129,6 @@ func DeleteLabel(ctx *context.Context) {
ctx.JSON(200, map[string]interface{}{
"redirect": ctx.Repo.RepoLink + "/labels",
})
return
}
// UpdateIssueLabel change issue's labels
+1 -2
View File
@@ -19,7 +19,6 @@ import (
const (
tplMilestone base.TplName = "repo/issue/milestones"
tplMilestoneNew base.TplName = "repo/issue/milestone_new"
tplMilestoneEdit base.TplName = "repo/issue/milestone_edit"
tplMilestoneIssues base.TplName = "repo/issue/milestone_issues"
)
@@ -57,7 +56,7 @@ func Milestones(ctx *context.Context) {
return
}
if ctx.Repo.Repository.IsTimetrackerEnabled() {
if miles.LoadTotalTrackedTimes(); err != nil {
if err := miles.LoadTotalTrackedTimes(); err != nil {
ctx.ServerError("LoadTotalTrackedTimes", err)
return
}
+53 -281
View File
@@ -8,6 +8,7 @@ package repo
import (
"container/list"
"crypto/subtle"
"fmt"
"io"
"path"
@@ -20,6 +21,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"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/util"
@@ -28,7 +30,7 @@ import (
const (
tplFork base.TplName = "repo/pulls/fork"
tplComparePull base.TplName = "repo/pulls/compare"
tplCompareDiff base.TplName = "repo/diff/compare"
tplPullCommits base.TplName = "repo/pulls/commits"
tplPullFiles base.TplName = "repo/pulls/files"
@@ -280,13 +282,13 @@ func setMergeTarget(ctx *context.Context, pull *models.PullRequest) {
}
// PrepareMergedViewPullInfo show meta information for a merged pull request view page
func PrepareMergedViewPullInfo(ctx *context.Context, issue *models.Issue) *git.PullRequestInfo {
func PrepareMergedViewPullInfo(ctx *context.Context, issue *models.Issue) *git.CompareInfo {
pull := issue.PullRequest
setMergeTarget(ctx, pull)
ctx.Data["HasMerged"] = true
prInfo, err := ctx.Repo.GitRepo.GetPullRequestInfo(ctx.Repo.Repository.RepoPath(),
compareInfo, err := ctx.Repo.GitRepo.GetCompareInfo(ctx.Repo.Repository.RepoPath(),
pull.MergeBase, pull.GetGitRefName())
if err != nil {
@@ -298,16 +300,16 @@ func PrepareMergedViewPullInfo(ctx *context.Context, issue *models.Issue) *git.P
return nil
}
ctx.ServerError("GetPullRequestInfo", err)
ctx.ServerError("GetCompareInfo", err)
return nil
}
ctx.Data["NumCommits"] = prInfo.Commits.Len()
ctx.Data["NumFiles"] = prInfo.NumFiles
return prInfo
ctx.Data["NumCommits"] = compareInfo.Commits.Len()
ctx.Data["NumFiles"] = compareInfo.NumFiles
return compareInfo
}
// PrepareViewPullInfo show meta information for a pull request preview page
func PrepareViewPullInfo(ctx *context.Context, issue *models.Issue) *git.PullRequestInfo {
func PrepareViewPullInfo(ctx *context.Context, issue *models.Issue) *git.CompareInfo {
repo := ctx.Repo.Repository
pull := issue.PullRequest
@@ -320,15 +322,37 @@ func PrepareViewPullInfo(ctx *context.Context, issue *models.Issue) *git.PullReq
setMergeTarget(ctx, pull)
var headGitRepo *git.Repository
var headBranchExist bool
// HeadRepo may be missing
if pull.HeadRepo != nil {
headGitRepo, err = git.OpenRepository(pull.HeadRepo.RepoPath())
if err != nil {
ctx.ServerError("OpenRepository", err)
return nil
}
headBranchExist = headGitRepo.IsBranchExist(pull.HeadBranch)
if headBranchExist {
sha, err := headGitRepo.GetBranchCommitID(pull.HeadBranch)
if err != nil {
ctx.ServerError("GetBranchCommitID", err)
return nil
}
commitStatuses, err := models.GetLatestCommitStatus(repo, sha, 0)
if err != nil {
ctx.ServerError("GetLatestCommitStatus", err)
return nil
}
if len(commitStatuses) > 0 {
ctx.Data["LatestCommitStatuses"] = commitStatuses
ctx.Data["LatestCommitStatus"] = models.CalcCommitStatus(commitStatuses)
}
}
}
if pull.HeadRepo == nil || !headGitRepo.IsBranchExist(pull.HeadBranch) {
if pull.HeadRepo == nil || !headBranchExist {
ctx.Data["IsPullRequestBroken"] = true
ctx.Data["HeadTarget"] = "deleted"
ctx.Data["NumCommits"] = 0
@@ -336,7 +360,7 @@ func PrepareViewPullInfo(ctx *context.Context, issue *models.Issue) *git.PullReq
return nil
}
prInfo, err := headGitRepo.GetPullRequestInfo(models.RepoPath(repo.Owner.Name, repo.Name),
compareInfo, err := headGitRepo.GetCompareInfo(models.RepoPath(repo.Owner.Name, repo.Name),
pull.BaseBranch, pull.HeadBranch)
if err != nil {
if strings.Contains(err.Error(), "fatal: Not a valid object name") {
@@ -347,7 +371,7 @@ func PrepareViewPullInfo(ctx *context.Context, issue *models.Issue) *git.PullReq
return nil
}
ctx.ServerError("GetPullRequestInfo", err)
ctx.ServerError("GetCompareInfo", err)
return nil
}
@@ -361,9 +385,9 @@ func PrepareViewPullInfo(ctx *context.Context, issue *models.Issue) *git.PullReq
ctx.Data["ConflictedFiles"] = pull.ConflictedFiles
}
ctx.Data["NumCommits"] = prInfo.Commits.Len()
ctx.Data["NumFiles"] = prInfo.NumFiles
return prInfo
ctx.Data["NumCommits"] = compareInfo.Commits.Len()
ctx.Data["NumFiles"] = compareInfo.NumFiles
return compareInfo
}
// ViewPullCommits show commits for a pull request
@@ -596,7 +620,7 @@ func MergePullRequest(ctx *context.Context, form auth.MergePullRequestForm) {
return
}
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.Flash.Error(ctx.Tr("repo.pulls.invalid_merge_option"))
ctx.Redirect(ctx.Repo.RepoLink + "/pulls/" + com.ToStr(pr.Index))
@@ -628,266 +652,6 @@ func stopTimerIfAvailable(user *models.User, issue *models.Issue) error {
return nil
}
// ParseCompareInfo parse compare info between two commit for preparing pull request
func ParseCompareInfo(ctx *context.Context) (*models.User, *models.Repository, *git.Repository, *git.PullRequestInfo, string, string) {
baseRepo := ctx.Repo.Repository
// Get compared branches information
// format: <base branch>...[<head repo>:]<head branch>
// base<-head: master...head:feature
// same repo: master...feature
var (
headUser *models.User
headBranch string
isSameRepo bool
infoPath string
err error
)
infoPath = ctx.Params("*")
infos := strings.Split(infoPath, "...")
if len(infos) != 2 {
log.Trace("ParseCompareInfo[%d]: not enough compared branches information %s", baseRepo.ID, infos)
ctx.NotFound("CompareAndPullRequest", nil)
return nil, nil, nil, nil, "", ""
}
baseBranch := infos[0]
ctx.Data["BaseBranch"] = baseBranch
// If there is no head repository, it means pull request between same repository.
headInfos := strings.Split(infos[1], ":")
if len(headInfos) == 1 {
isSameRepo = true
headUser = ctx.Repo.Owner
headBranch = headInfos[0]
} else if len(headInfos) == 2 {
headUser, err = models.GetUserByName(headInfos[0])
if err != nil {
if models.IsErrUserNotExist(err) {
ctx.NotFound("GetUserByName", nil)
} else {
ctx.ServerError("GetUserByName", err)
}
return nil, nil, nil, nil, "", ""
}
headBranch = headInfos[1]
isSameRepo = headUser.ID == ctx.Repo.Owner.ID
} else {
ctx.NotFound("CompareAndPullRequest", nil)
return nil, nil, nil, nil, "", ""
}
ctx.Data["HeadUser"] = headUser
ctx.Data["HeadBranch"] = headBranch
ctx.Repo.PullRequest.SameRepo = isSameRepo
// Check if base branch is valid.
if !ctx.Repo.GitRepo.IsBranchExist(baseBranch) {
ctx.NotFound("IsBranchExist", nil)
return nil, nil, nil, nil, "", ""
}
// Check if current user has fork of repository or in the same repository.
headRepo, has := models.HasForkedRepo(headUser.ID, baseRepo.ID)
if !has && !isSameRepo {
log.Trace("ParseCompareInfo[%d]: does not have fork or in same repository", baseRepo.ID)
ctx.NotFound("ParseCompareInfo", nil)
return nil, nil, nil, nil, "", ""
}
var headGitRepo *git.Repository
if isSameRepo {
headRepo = ctx.Repo.Repository
headGitRepo = ctx.Repo.GitRepo
ctx.Data["BaseName"] = headUser.Name
} else {
headGitRepo, err = git.OpenRepository(models.RepoPath(headUser.Name, headRepo.Name))
ctx.Data["BaseName"] = baseRepo.OwnerName
if err != nil {
ctx.ServerError("OpenRepository", err)
return nil, nil, nil, nil, "", ""
}
}
// user should have permission to read baseRepo's codes and pulls, NOT headRepo's
permBase, err := models.GetUserRepoPermission(baseRepo, ctx.User)
if err != nil {
ctx.ServerError("GetUserRepoPermission", err)
return nil, nil, nil, nil, "", ""
}
if !permBase.CanReadIssuesOrPulls(true) || !permBase.CanRead(models.UnitTypeCode) {
if log.IsTrace() {
log.Trace("Permission Denied: User: %-v cannot create/read pull requests or cannot read code in Repo: %-v\nUser in baseRepo has Permissions: %-+v",
ctx.User,
baseRepo,
permBase)
}
ctx.NotFound("ParseCompareInfo", nil)
return nil, nil, nil, nil, "", ""
}
// user should have permission to read headrepo's codes
permHead, err := models.GetUserRepoPermission(headRepo, ctx.User)
if err != nil {
ctx.ServerError("GetUserRepoPermission", err)
return nil, nil, nil, nil, "", ""
}
if !permHead.CanRead(models.UnitTypeCode) {
if log.IsTrace() {
log.Trace("Permission Denied: User: %-v cannot read code requests in Repo: %-v\nUser in headRepo has Permissions: %-+v",
ctx.User,
headRepo,
permHead)
}
ctx.NotFound("ParseCompareInfo", nil)
return nil, nil, nil, nil, "", ""
}
// Check if head branch is valid.
if !headGitRepo.IsBranchExist(headBranch) {
ctx.NotFound("IsBranchExist", nil)
return nil, nil, nil, nil, "", ""
}
headBranches, err := headGitRepo.GetBranches()
if err != nil {
ctx.ServerError("GetBranches", err)
return nil, nil, nil, nil, "", ""
}
ctx.Data["HeadBranches"] = headBranches
prInfo, err := headGitRepo.GetPullRequestInfo(models.RepoPath(baseRepo.Owner.Name, baseRepo.Name), baseBranch, headBranch)
if err != nil {
ctx.ServerError("GetPullRequestInfo", err)
return nil, nil, nil, nil, "", ""
}
ctx.Data["BeforeCommitID"] = prInfo.MergeBase
return headUser, headRepo, headGitRepo, prInfo, baseBranch, headBranch
}
// PrepareCompareDiff render pull request preview diff page
func PrepareCompareDiff(
ctx *context.Context,
headUser *models.User,
headRepo *models.Repository,
headGitRepo *git.Repository,
prInfo *git.PullRequestInfo,
baseBranch, headBranch string) bool {
var (
repo = ctx.Repo.Repository
err error
title string
)
// Get diff information.
ctx.Data["CommitRepoLink"] = headRepo.Link()
headCommitID, err := headGitRepo.GetBranchCommitID(headBranch)
if err != nil {
ctx.ServerError("GetBranchCommitID", err)
return false
}
ctx.Data["AfterCommitID"] = headCommitID
if headCommitID == prInfo.MergeBase {
ctx.Data["IsNothingToCompare"] = true
return true
}
diff, err := models.GetDiffRange(models.RepoPath(headUser.Name, headRepo.Name),
prInfo.MergeBase, headCommitID, setting.Git.MaxGitDiffLines,
setting.Git.MaxGitDiffLineCharacters, setting.Git.MaxGitDiffFiles)
if err != nil {
ctx.ServerError("GetDiffRange", err)
return false
}
ctx.Data["Diff"] = diff
ctx.Data["DiffNotAvailable"] = diff.NumFiles() == 0
headCommit, err := headGitRepo.GetCommit(headCommitID)
if err != nil {
ctx.ServerError("GetCommit", err)
return false
}
prInfo.Commits = models.ValidateCommitsWithEmails(prInfo.Commits)
prInfo.Commits = models.ParseCommitsWithSignature(prInfo.Commits)
prInfo.Commits = models.ParseCommitsWithStatus(prInfo.Commits, headRepo)
ctx.Data["Commits"] = prInfo.Commits
ctx.Data["CommitCount"] = prInfo.Commits.Len()
if prInfo.Commits.Len() == 1 {
c := prInfo.Commits.Front().Value.(models.SignCommitWithStatuses)
title = strings.TrimSpace(c.UserCommit.Summary())
body := strings.Split(strings.TrimSpace(c.UserCommit.Message()), "\n")
if len(body) > 1 {
ctx.Data["content"] = strings.Join(body[1:], "\n")
}
} else {
title = headBranch
}
ctx.Data["title"] = title
ctx.Data["Username"] = headUser.Name
ctx.Data["Reponame"] = headRepo.Name
ctx.Data["IsImageFile"] = headCommit.IsImageFile
headTarget := path.Join(headUser.Name, repo.Name)
ctx.Data["SourcePath"] = setting.AppSubURL + "/" + path.Join(headTarget, "src", "commit", headCommitID)
ctx.Data["BeforeSourcePath"] = setting.AppSubURL + "/" + path.Join(headTarget, "src", "commit", prInfo.MergeBase)
ctx.Data["RawPath"] = setting.AppSubURL + "/" + path.Join(headTarget, "raw", "commit", headCommitID)
return false
}
// CompareAndPullRequest render pull request preview page
func CompareAndPullRequest(ctx *context.Context) {
ctx.Data["Title"] = ctx.Tr("repo.pulls.compare_changes")
ctx.Data["PageIsComparePull"] = true
ctx.Data["IsDiffCompare"] = true
ctx.Data["RequireHighlightJS"] = true
ctx.Data["RequireTribute"] = true
ctx.Data["PullRequestWorkInProgressPrefixes"] = setting.Repository.PullRequest.WorkInProgressPrefixes
setTemplateIfExists(ctx, pullRequestTemplateKey, pullRequestTemplateCandidates)
renderAttachmentSettings(ctx)
headUser, headRepo, headGitRepo, prInfo, baseBranch, headBranch := ParseCompareInfo(ctx)
if ctx.Written() {
return
}
pr, err := models.GetUnmergedPullRequest(headRepo.ID, ctx.Repo.Repository.ID, headBranch, baseBranch)
if err != nil {
if !models.IsErrPullRequestNotExist(err) {
ctx.ServerError("GetUnmergedPullRequest", err)
return
}
} else {
ctx.Data["HasPullRequest"] = true
ctx.Data["PullRequest"] = pr
ctx.HTML(200, tplComparePull)
return
}
nothingToCompare := PrepareCompareDiff(ctx, headUser, headRepo, headGitRepo, prInfo, baseBranch, headBranch)
if ctx.Written() {
return
}
if !nothingToCompare {
// Setup information for new form.
RetrieveRepoMetas(ctx, ctx.Repo.Repository)
if ctx.Written() {
return
}
}
ctx.HTML(200, tplComparePull)
}
// CompareAndPullRequestPost response for creating pull request
func CompareAndPullRequestPost(ctx *context.Context, form auth.CreateIssueForm) {
ctx.Data["Title"] = ctx.Tr("repo.pulls.compare_changes")
@@ -926,7 +690,7 @@ func CompareAndPullRequestPost(ctx *context.Context, form auth.CreateIssueForm)
return
}
ctx.HTML(200, tplComparePull)
ctx.HTML(200, tplCompareDiff)
return
}
@@ -936,7 +700,7 @@ func CompareAndPullRequestPost(ctx *context.Context, form auth.CreateIssueForm)
return
}
ctx.RenderWithErr(ctx.Tr("repo.issues.new.title_empty"), tplComparePull, form)
ctx.RenderWithErr(ctx.Tr("repo.issues.new.title_empty"), tplCompareDiff, form)
return
}
@@ -946,9 +710,15 @@ func CompareAndPullRequestPost(ctx *context.Context, form auth.CreateIssueForm)
return
}
maxIndex, err := models.GetMaxIndexOfIssue(repo.ID)
if err != nil {
ctx.ServerError("GetPatch", err)
return
}
pullIssue := &models.Issue{
RepoID: repo.ID,
Index: repo.NextIssueIndex(),
Index: maxIndex + 1,
Title: form.Title,
PosterID: ctx.User.ID,
Poster: ctx.User,
@@ -1002,7 +772,9 @@ func TriggerTask(ctx *context.Context) {
if ctx.Written() {
return
}
if secret != base.EncodeMD5(owner.Salt) {
got := []byte(base.EncodeMD5(owner.Salt))
want := []byte(secret)
if subtle.ConstantTimeCompare(got, want) != 1 {
ctx.Error(404)
log.Trace("TriggerTask [%s/%s]: invalid secret", owner.Name, repo.Name)
return
@@ -1047,10 +819,10 @@ func CleanUpPullRequest(ctx *context.Context) {
// Forked repository has already been deleted
ctx.NotFound("CleanUpPullRequest", nil)
return
} else if pr.GetBaseRepo(); err != nil {
} else if err = pr.GetBaseRepo(); err != nil {
ctx.ServerError("GetBaseRepo", err)
return
} else if pr.HeadRepo.GetOwner(); err != nil {
} else if err = pr.HeadRepo.GetOwner(); err != nil {
ctx.ServerError("HeadRepo.GetOwner", err)
return
}
+64 -2
View File
@@ -7,11 +7,14 @@ package repo
import (
"errors"
"fmt"
"io/ioutil"
"net/url"
"regexp"
"strings"
"time"
"github.com/Unknwon/com"
"mvdan.cc/xurls/v2"
"code.gitea.io/gitea/models"
@@ -246,7 +249,7 @@ func SettingsPost(ctx *context.Context, form auth.RepoSettingForm) {
ctx.Redirect(repo.Link() + "/settings")
return
}
if len(form.TrackerURLFormat) != 0 && !validation.IsValidExternalURL(form.TrackerURLFormat) {
if len(form.TrackerURLFormat) != 0 && !validation.IsValidExternalTrackerURLFormat(form.TrackerURLFormat) {
ctx.Flash.Error(ctx.Tr("repo.settings.tracker_url_format_error"))
ctx.Redirect(repo.Link() + "/settings")
return
@@ -416,7 +419,10 @@ func SettingsPost(ctx *context.Context, form auth.RepoSettingForm) {
return
}
repo.DeleteWiki()
err := repo.DeleteWiki()
if err != nil {
log.Error("Delete Wiki: %v", err.Error())
}
log.Trace("Repository wiki deleted: %s/%s", ctx.Repo.Owner.Name, repo.Name)
ctx.Flash.Success(ctx.Tr("repo.settings.wiki_deletion_success"))
@@ -727,3 +733,59 @@ func init() {
panic(err)
}
}
// UpdateAvatarSetting update repo's avatar
func UpdateAvatarSetting(ctx *context.Context, form auth.AvatarForm) error {
ctxRepo := ctx.Repo.Repository
if form.Avatar == nil {
// No avatar is uploaded and we not removing it here.
// No random avatar generated here.
// Just exit, no action.
if !com.IsFile(ctxRepo.CustomAvatarPath()) {
log.Trace("No avatar was uploaded for repo: %d. Default icon will appear instead.", ctxRepo.ID)
}
return nil
}
r, err := form.Avatar.Open()
if err != nil {
return fmt.Errorf("Avatar.Open: %v", err)
}
defer r.Close()
if form.Avatar.Size > setting.AvatarMaxFileSize {
return errors.New(ctx.Tr("settings.uploaded_avatar_is_too_big"))
}
data, err := ioutil.ReadAll(r)
if err != nil {
return fmt.Errorf("ioutil.ReadAll: %v", err)
}
if !base.IsImageFile(data) {
return errors.New(ctx.Tr("settings.uploaded_avatar_not_a_image"))
}
if err = ctxRepo.UploadAvatar(data); err != nil {
return fmt.Errorf("UploadAvatar: %v", err)
}
return nil
}
// SettingsAvatar save new POSTed repository avatar
func SettingsAvatar(ctx *context.Context, form auth.AvatarForm) {
form.Source = auth.AvatarLocal
if err := UpdateAvatarSetting(ctx, form); err != nil {
ctx.Flash.Error(err.Error())
} else {
ctx.Flash.Success(ctx.Tr("repo.settings.update_avatar_success"))
}
ctx.Redirect(ctx.Repo.RepoLink + "/settings")
}
// SettingsDeleteAvatar delete repository avatar
func SettingsDeleteAvatar(ctx *context.Context) {
if err := ctx.Repo.Repository.DeleteAvatar(); err != nil {
ctx.Flash.Error(fmt.Sprintf("DeleteAvatar: %v", err))
}
ctx.Redirect(ctx.Repo.RepoLink + "/settings")
}
+1 -3
View File
@@ -294,9 +294,7 @@ func renderFile(ctx *context.Context, entry *git.TreeEntry, treeLink, rawLink st
// Building code view blocks with line number on server side.
var fileContent string
if content, err := templates.ToUTF8WithErr(buf); err != nil {
if err != nil {
log.Error("ToUTF8WithErr: %v", err)
}
log.Error("ToUTF8WithErr: %v", err)
fileContent = string(buf)
} else {
fileContent = content
+12 -3
View File
@@ -197,12 +197,20 @@ func WebHooksNewPost(ctx *context.Context, form auth.NewWebhookForm) {
}
// GogsHooksNewPost response for creating webhook
func GogsHooksNewPost(ctx *context.Context, form auth.NewGogshookForm) {
func GogsHooksNewPost(ctx *context.Context, form auth.NewWebhookForm) {
newGenericWebhookPost(ctx, form, models.GOGS)
}
func newGenericWebhookPost(ctx *context.Context, form auth.NewWebhookForm, kind models.HookTaskType) {
ctx.Data["Title"] = ctx.Tr("repo.settings.add_webhook")
ctx.Data["PageIsSettingsHooks"] = true
ctx.Data["PageIsSettingsHooksNew"] = true
ctx.Data["Webhook"] = models.Webhook{HookEvent: &models.HookEvent{}}
ctx.Data["HookType"] = "gogs"
ctx.Data["HookType"] = "gitea"
if kind == models.GOGS {
ctx.Data["HookType"] = "gogs"
}
orCtx, err := getOrgRepoCtx(ctx)
if err != nil {
@@ -228,7 +236,7 @@ func GogsHooksNewPost(ctx *context.Context, form auth.NewGogshookForm) {
Secret: form.Secret,
HookEvent: ParseHookEvent(form.WebhookForm),
IsActive: form.Active,
HookTaskType: models.GOGS,
HookTaskType: kind,
OrgID: orCtx.OrgID,
}
if err := w.UpdateEvent(); err != nil {
@@ -564,6 +572,7 @@ func WebHooksEditPost(ctx *context.Context, form auth.NewWebhookForm) {
w.Secret = form.Secret
w.HookEvent = ParseHookEvent(form.WebhookForm)
w.IsActive = form.Active
w.HTTPMethod = form.HTTPMethod
if err := w.UpdateEvent(); err != nil {
ctx.ServerError("UpdateEvent", err)
return
+195 -71
View File
@@ -23,10 +23,11 @@ import (
)
const (
tplWikiStart base.TplName = "repo/wiki/start"
tplWikiView base.TplName = "repo/wiki/view"
tplWikiNew base.TplName = "repo/wiki/new"
tplWikiPages base.TplName = "repo/wiki/pages"
tplWikiStart base.TplName = "repo/wiki/start"
tplWikiView base.TplName = "repo/wiki/view"
tplWikiRevision base.TplName = "repo/wiki/revision"
tplWikiNew base.TplName = "repo/wiki/new"
tplWikiPages base.TplName = "repo/wiki/pages"
)
// MustEnableWiki check if wiki is enabled, if external then redirect
@@ -107,18 +108,20 @@ func wikiContentsByEntry(ctx *context.Context, entry *git.TreeEntry) []byte {
// wikiContentsByName returns the contents of a wiki page, along with a boolean
// indicating whether the page exists. Writes to ctx if an error occurs.
func wikiContentsByName(ctx *context.Context, commit *git.Commit, wikiName string) ([]byte, bool) {
entry, err := findEntryForFile(commit, models.WikiNameToFilename(wikiName))
if err != nil {
func wikiContentsByName(ctx *context.Context, commit *git.Commit, wikiName string) ([]byte, *git.TreeEntry, string, bool) {
var entry *git.TreeEntry
var err error
pageFilename := models.WikiNameToFilename(wikiName)
if entry, err = findEntryForFile(commit, pageFilename); err != nil {
ctx.ServerError("findEntryForFile", err)
return nil, false
return nil, nil, "", false
} else if entry == nil {
return nil, false
return nil, nil, "", true
}
return wikiContentsByEntry(ctx, entry), true
return wikiContentsByEntry(ctx, entry), entry, pageFilename, false
}
func renderWikiPage(ctx *context.Context, isViewPage bool) (*git.Repository, *git.TreeEntry) {
func renderViewPage(ctx *context.Context) (*git.Repository, *git.TreeEntry) {
wikiRepo, commit, err := findWikiRepoCommit(ctx)
if err != nil {
if !git.IsErrNotExist(err) {
@@ -128,88 +131,176 @@ func renderWikiPage(ctx *context.Context, isViewPage bool) (*git.Repository, *gi
}
// Get page list.
if isViewPage {
entries, err := commit.ListEntries()
if err != nil {
ctx.ServerError("ListEntries", err)
return nil, nil
}
pages := make([]PageMeta, 0, len(entries))
for _, entry := range entries {
if !entry.IsRegular() {
continue
}
wikiName, err := models.WikiFilenameToName(entry.Name())
if err != nil {
if models.IsErrWikiInvalidFileName(err) {
continue
}
ctx.ServerError("WikiFilenameToName", err)
return nil, nil
} else if wikiName == "_Sidebar" || wikiName == "_Footer" {
continue
}
pages = append(pages, PageMeta{
Name: wikiName,
SubURL: models.WikiNameToSubURL(wikiName),
})
}
ctx.Data["Pages"] = pages
entries, err := commit.ListEntries()
if err != nil {
ctx.ServerError("ListEntries", err)
return nil, nil
}
pages := make([]PageMeta, 0, len(entries))
for _, entry := range entries {
if !entry.IsRegular() {
continue
}
wikiName, err := models.WikiFilenameToName(entry.Name())
if err != nil {
if models.IsErrWikiInvalidFileName(err) {
continue
}
ctx.ServerError("WikiFilenameToName", err)
return nil, nil
} else if wikiName == "_Sidebar" || wikiName == "_Footer" {
continue
}
pages = append(pages, PageMeta{
Name: wikiName,
SubURL: models.WikiNameToSubURL(wikiName),
})
}
ctx.Data["Pages"] = pages
// get requested pagename
pageName := models.NormalizeWikiName(ctx.Params(":page"))
if len(pageName) == 0 {
pageName = "Home"
}
ctx.Data["PageURL"] = models.WikiNameToSubURL(pageName)
ctx.Data["old_title"] = pageName
ctx.Data["Title"] = pageName
ctx.Data["title"] = pageName
ctx.Data["RequireHighlightJS"] = true
pageFilename := models.WikiNameToFilename(pageName)
var entry *git.TreeEntry
if entry, err = findEntryForFile(commit, pageFilename); err != nil {
ctx.ServerError("findEntryForFile", err)
return nil, nil
} else if entry == nil {
//lookup filename in wiki - get filecontent, gitTree entry , real filename
data, entry, pageFilename, noEntry := wikiContentsByName(ctx, commit, pageName)
if noEntry {
ctx.Redirect(ctx.Repo.RepoLink + "/wiki/_pages")
}
if entry == nil || ctx.Written() {
return nil, nil
}
data := wikiContentsByEntry(ctx, entry)
sidebarContent, _, _, _ := wikiContentsByName(ctx, commit, "_Sidebar")
if ctx.Written() {
return nil, nil
}
if isViewPage {
sidebarContent, sidebarPresent := wikiContentsByName(ctx, commit, "_Sidebar")
if ctx.Written() {
return nil, nil
}
footerContent, footerPresent := wikiContentsByName(ctx, commit, "_Footer")
if ctx.Written() {
return nil, nil
}
metas := ctx.Repo.Repository.ComposeMetas()
ctx.Data["content"] = markdown.RenderWiki(data, ctx.Repo.RepoLink, metas)
ctx.Data["sidebarPresent"] = sidebarPresent
ctx.Data["sidebarContent"] = markdown.RenderWiki(sidebarContent, ctx.Repo.RepoLink, metas)
ctx.Data["footerPresent"] = footerPresent
ctx.Data["footerContent"] = markdown.RenderWiki(footerContent, ctx.Repo.RepoLink, metas)
} else {
ctx.Data["content"] = string(data)
ctx.Data["sidebarPresent"] = false
ctx.Data["sidebarContent"] = ""
ctx.Data["footerPresent"] = false
ctx.Data["footerContent"] = ""
footerContent, _, _, _ := wikiContentsByName(ctx, commit, "_Footer")
if ctx.Written() {
return nil, nil
}
metas := ctx.Repo.Repository.ComposeMetas()
ctx.Data["content"] = markdown.RenderWiki(data, ctx.Repo.RepoLink, metas)
ctx.Data["sidebarPresent"] = sidebarContent != nil
ctx.Data["sidebarContent"] = markdown.RenderWiki(sidebarContent, ctx.Repo.RepoLink, metas)
ctx.Data["footerPresent"] = footerContent != nil
ctx.Data["footerContent"] = markdown.RenderWiki(footerContent, ctx.Repo.RepoLink, metas)
// get commit count - wiki revisions
commitsCount, _ := wikiRepo.FileCommitsCount("master", pageFilename)
ctx.Data["CommitCount"] = commitsCount
return wikiRepo, entry
}
func renderRevisionPage(ctx *context.Context) (*git.Repository, *git.TreeEntry) {
wikiRepo, commit, err := findWikiRepoCommit(ctx)
if err != nil {
if !git.IsErrNotExist(err) {
ctx.ServerError("GetBranchCommit", err)
}
return nil, nil
}
// get requested pagename
pageName := models.NormalizeWikiName(ctx.Params(":page"))
if len(pageName) == 0 {
pageName = "Home"
}
ctx.Data["PageURL"] = models.WikiNameToSubURL(pageName)
ctx.Data["old_title"] = pageName
ctx.Data["Title"] = pageName
ctx.Data["title"] = pageName
ctx.Data["RequireHighlightJS"] = true
//lookup filename in wiki - get filecontent, gitTree entry , real filename
data, entry, pageFilename, noEntry := wikiContentsByName(ctx, commit, pageName)
if noEntry {
ctx.Redirect(ctx.Repo.RepoLink + "/wiki/_pages")
}
if entry == nil || ctx.Written() {
return nil, nil
}
ctx.Data["content"] = string(data)
ctx.Data["sidebarPresent"] = false
ctx.Data["sidebarContent"] = ""
ctx.Data["footerPresent"] = false
ctx.Data["footerContent"] = ""
// get commit count - wiki revisions
commitsCount, _ := wikiRepo.FileCommitsCount("master", pageFilename)
ctx.Data["CommitCount"] = commitsCount
// get page
page := ctx.QueryInt("page")
if page <= 1 {
page = 1
}
// get Commit Count
commitsHistory, err := wikiRepo.CommitsByFileAndRange("master", pageFilename, page)
if err != nil {
ctx.ServerError("CommitsByFileAndRange", err)
return nil, nil
}
commitsHistory = models.ValidateCommitsWithEmails(commitsHistory)
commitsHistory = models.ParseCommitsWithSignature(commitsHistory)
ctx.Data["Commits"] = commitsHistory
pager := context.NewPagination(int(commitsCount), git.CommitsRangeSize, page, 5)
pager.SetDefaultParams(ctx)
ctx.Data["Page"] = pager
return wikiRepo, entry
}
func renderEditPage(ctx *context.Context) {
_, commit, err := findWikiRepoCommit(ctx)
if err != nil {
if !git.IsErrNotExist(err) {
ctx.ServerError("GetBranchCommit", err)
}
return
}
// get requested pagename
pageName := models.NormalizeWikiName(ctx.Params(":page"))
if len(pageName) == 0 {
pageName = "Home"
}
ctx.Data["PageURL"] = models.WikiNameToSubURL(pageName)
ctx.Data["old_title"] = pageName
ctx.Data["Title"] = pageName
ctx.Data["title"] = pageName
ctx.Data["RequireHighlightJS"] = true
//lookup filename in wiki - get filecontent, gitTree entry , real filename
data, entry, _, noEntry := wikiContentsByName(ctx, commit, pageName)
if noEntry {
ctx.Redirect(ctx.Repo.RepoLink + "/wiki/_pages")
}
if entry == nil || ctx.Written() {
return
}
ctx.Data["content"] = string(data)
ctx.Data["sidebarPresent"] = false
ctx.Data["sidebarContent"] = ""
ctx.Data["footerPresent"] = false
ctx.Data["footerContent"] = ""
}
// Wiki renders single wiki page
func Wiki(ctx *context.Context) {
ctx.Data["PageIsWiki"] = true
@@ -221,7 +312,7 @@ func Wiki(ctx *context.Context) {
return
}
wikiRepo, entry := renderWikiPage(ctx, true)
wikiRepo, entry := renderViewPage(ctx)
if ctx.Written() {
return
}
@@ -247,6 +338,39 @@ func Wiki(ctx *context.Context) {
ctx.HTML(200, tplWikiView)
}
// WikiRevision renders file revision list of wiki page
func WikiRevision(ctx *context.Context) {
ctx.Data["PageIsWiki"] = true
ctx.Data["CanWriteWiki"] = ctx.Repo.CanWrite(models.UnitTypeWiki) && !ctx.Repo.Repository.IsArchived
if !ctx.Repo.Repository.HasWiki() {
ctx.Data["Title"] = ctx.Tr("repo.wiki")
ctx.HTML(200, tplWikiStart)
return
}
wikiRepo, entry := renderRevisionPage(ctx)
if ctx.Written() {
return
}
if entry == nil {
ctx.Data["Title"] = ctx.Tr("repo.wiki")
ctx.HTML(200, tplWikiStart)
return
}
// Get last change information.
wikiPath := entry.Name()
lastCommit, err := wikiRepo.GetCommitByPath(wikiPath)
if err != nil {
ctx.ServerError("GetCommitByPath", err)
return
}
ctx.Data["Author"] = lastCommit.Author
ctx.HTML(200, tplWikiRevision)
}
// WikiPages render wiki pages list page
func WikiPages(ctx *context.Context) {
if !ctx.Repo.Repository.HasWiki() {
@@ -399,7 +523,7 @@ func EditWiki(ctx *context.Context) {
return
}
renderWikiPage(ctx, false)
renderEditPage(ctx)
if ctx.Written() {
return
}
+47 -28
View File
@@ -47,19 +47,6 @@ import (
macaron "gopkg.in/macaron.v1"
)
/*func giteaLogger(l *log.LoggerAsWriter) macaron.Handler {
return func(ctx *macaron.Context) {
start := time.Now()
l.Log(fmt.Sprintf("[Macaron] Started %s %s for %s", ctx.Req.Method, ctx.Req.RequestURI, ctx.RemoteAddr()))
ctx.Next()
rw := ctx.Resp.(macaron.ResponseWriter)
l.Log(fmt.Sprintf("[Macaron] Completed %s %s %v %s in %v", ctx.Req.Method, ctx.Req.RequestURI, rw.Status(), http.StatusText(rw.Status()), time.Since(start)))
}
}*/
type routerLoggerOptions struct {
Ctx *macaron.Context
Identity *string
@@ -83,17 +70,38 @@ func setupAccessLogger(m *macaron.Macaron) {
rw := ctx.Resp.(macaron.ResponseWriter)
buf := bytes.NewBuffer([]byte{})
logTemplate.Execute(buf, routerLoggerOptions{
err := logTemplate.Execute(buf, routerLoggerOptions{
Ctx: ctx,
Identity: &identity,
Start: &start,
ResponseWriter: &rw,
})
if err != nil {
log.Error("Could not set up macaron access logger: %v", err.Error())
}
logger.SendLog(log.INFO, "", "", 0, buf.String(), "")
err = logger.SendLog(log.INFO, "", "", 0, buf.String(), "")
if err != nil {
log.Error("Could not set up macaron access logger: %v", err.Error())
}
})
}
// RouterHandler is a macaron handler that will log the routing to the default gitea log
func RouterHandler(level log.Level) func(ctx *macaron.Context) {
return func(ctx *macaron.Context) {
start := time.Now()
_ = log.GetLogger("router").Log(0, level, "Started %s %s for %s", log.ColoredMethod(ctx.Req.Method), ctx.Req.RequestURI, ctx.RemoteAddr())
rw := ctx.Resp.(macaron.ResponseWriter)
ctx.Next()
status := rw.Status()
_ = log.GetLogger("router").Log(0, level, "Completed %s %s %v %s in %v", log.ColoredMethod(ctx.Req.Method), ctx.Req.RequestURI, log.ColoredStatus(status), log.ColoredStatus(status, http.StatusText(rw.Status())), log.ColoredTime(time.Since(start)))
}
}
// NewMacaron initializes Macaron instance.
func NewMacaron() *macaron.Macaron {
gob.Register(&u2f.Challenge{})
@@ -102,7 +110,9 @@ func NewMacaron() *macaron.Macaron {
loggerAsWriter := log.NewLoggerAsWriter("INFO", log.GetLogger("macaron"))
m = macaron.NewWithLogger(loggerAsWriter)
if !setting.DisableRouterLog && setting.RouterLogLevel != log.NONE {
log.SetupRouterLogger(m, setting.RouterLogLevel)
if log.GetLogger("router").GetLevel() <= setting.RouterLogLevel {
m.Use(RouterHandler(setting.RouterLogLevel))
}
}
} else {
m = macaron.New()
@@ -142,6 +152,14 @@ func NewMacaron() *macaron.Macaron {
ExpiresAfter: time.Hour * 6,
},
))
m.Use(public.StaticHandler(
setting.RepositoryAvatarUploadPath,
&public.Options{
Prefix: "repo-avatars",
SkipLogging: setting.DisableRouterLog,
ExpiresAfter: time.Hour * 6,
},
))
m.Use(templates.HTMLRenderer())
models.InitMailRender(templates.Mailer())
@@ -418,14 +436,14 @@ func RegisterRoutes(m *macaron.Macaron) {
m.Post("/delete", admin.DeleteDefaultWebhook)
m.Get("/:type/new", repo.WebhooksNew)
m.Post("/gitea/new", bindIgnErr(auth.NewWebhookForm{}), repo.WebHooksNewPost)
m.Post("/gogs/new", bindIgnErr(auth.NewGogshookForm{}), repo.GogsHooksNewPost)
m.Post("/gogs/new", bindIgnErr(auth.NewWebhookForm{}), repo.GogsHooksNewPost)
m.Post("/slack/new", bindIgnErr(auth.NewSlackHookForm{}), repo.SlackHooksNewPost)
m.Post("/discord/new", bindIgnErr(auth.NewDiscordHookForm{}), repo.DiscordHooksNewPost)
m.Post("/dingtalk/new", bindIgnErr(auth.NewDingtalkHookForm{}), repo.DingtalkHooksNewPost)
m.Post("/msteams/new", bindIgnErr(auth.NewMSTeamsHookForm{}), repo.MSTeamsHooksNewPost)
m.Get("/:id", repo.WebHooksEdit)
m.Post("/gitea/:id", bindIgnErr(auth.NewWebhookForm{}), repo.WebHooksEditPost)
m.Post("/gogs/:id", bindIgnErr(auth.NewGogshookForm{}), repo.GogsHooksEditPost)
m.Post("/gogs/:id", bindIgnErr(auth.NewWebhookForm{}), repo.GogsHooksEditPost)
m.Post("/slack/:id", bindIgnErr(auth.NewSlackHookForm{}), repo.SlackHooksEditPost)
m.Post("/discord/:id", bindIgnErr(auth.NewDiscordHookForm{}), repo.DiscordHooksEditPost)
m.Post("/dingtalk/:id", bindIgnErr(auth.NewDingtalkHookForm{}), repo.DingtalkHooksEditPost)
@@ -557,7 +575,7 @@ func RegisterRoutes(m *macaron.Macaron) {
m.Post("/delete", org.DeleteWebhook)
m.Get("/:type/new", repo.WebhooksNew)
m.Post("/gitea/new", bindIgnErr(auth.NewWebhookForm{}), repo.WebHooksNewPost)
m.Post("/gogs/new", bindIgnErr(auth.NewGogshookForm{}), repo.GogsHooksNewPost)
m.Post("/gogs/new", bindIgnErr(auth.NewWebhookForm{}), repo.GogsHooksNewPost)
m.Post("/slack/new", bindIgnErr(auth.NewSlackHookForm{}), repo.SlackHooksNewPost)
m.Post("/discord/new", bindIgnErr(auth.NewDiscordHookForm{}), repo.DiscordHooksNewPost)
m.Post("/dingtalk/new", bindIgnErr(auth.NewDingtalkHookForm{}), repo.DingtalkHooksNewPost)
@@ -596,6 +614,9 @@ func RegisterRoutes(m *macaron.Macaron) {
m.Group("/settings", func() {
m.Combo("").Get(repo.Settings).
Post(bindIgnErr(auth.RepoSettingForm{}), repo.SettingsPost)
m.Post("/avatar", binding.MultipartForm(auth.AvatarForm{}), repo.SettingsAvatar)
m.Post("/avatar/delete", repo.SettingsDeleteAvatar)
m.Group("/collaboration", func() {
m.Combo("").Get(repo.Collaboration).Post(repo.CollaborationPost)
m.Post("/access_mode", repo.ChangeCollaborationAccessMode)
@@ -612,7 +633,7 @@ func RegisterRoutes(m *macaron.Macaron) {
m.Post("/delete", repo.DeleteWebhook)
m.Get("/:type/new", repo.WebhooksNew)
m.Post("/gitea/new", bindIgnErr(auth.NewWebhookForm{}), repo.WebHooksNewPost)
m.Post("/gogs/new", bindIgnErr(auth.NewGogshookForm{}), repo.GogsHooksNewPost)
m.Post("/gogs/new", bindIgnErr(auth.NewWebhookForm{}), repo.GogsHooksNewPost)
m.Post("/slack/new", bindIgnErr(auth.NewSlackHookForm{}), repo.SlackHooksNewPost)
m.Post("/discord/new", bindIgnErr(auth.NewDiscordHookForm{}), repo.DiscordHooksNewPost)
m.Post("/dingtalk/new", bindIgnErr(auth.NewDingtalkHookForm{}), repo.DingtalkHooksNewPost)
@@ -646,7 +667,7 @@ func RegisterRoutes(m *macaron.Macaron) {
})
}, reqSignIn, context.RepoAssignment(), reqRepoAdmin, context.UnitTypes(), context.RepoRef())
m.Get("/:username/:reponame/action/:action", reqSignIn, context.RepoAssignment(), context.UnitTypes(), context.RepoMustNotBeArchived(), repo.Action)
m.Get("/:username/:reponame/action/:action", reqSignIn, context.RepoAssignment(), context.UnitTypes(), repo.Action)
m.Group("/:username/:reponame", func() {
m.Group("/issues", func() {
@@ -704,9 +725,9 @@ func RegisterRoutes(m *macaron.Macaron) {
m.Group("/milestone", func() {
m.Get("/:id", repo.MilestoneIssuesAndPulls)
}, reqRepoIssuesOrPullsReader, context.RepoRef())
m.Combo("/compare/*", context.RepoMustNotBeArchived(), reqRepoCodeReader, reqRepoPullsReader, repo.MustAllowPulls, repo.SetEditorconfigIfExists).
Get(repo.SetDiffViewStyle, repo.CompareAndPullRequest).
Post(bindIgnErr(auth.CreateIssueForm{}), repo.CompareAndPullRequestPost)
m.Combo("/compare/*", repo.MustBeNotEmpty, reqRepoCodeReader, repo.SetEditorconfigIfExists).
Get(repo.SetDiffViewStyle, repo.CompareDiff).
Post(context.RepoMustNotBeArchived(), reqRepoPullsReader, repo.MustAllowPulls, bindIgnErr(auth.CreateIssueForm{}), repo.CompareAndPullRequestPost)
m.Group("", func() {
m.Group("", func() {
@@ -783,6 +804,7 @@ func RegisterRoutes(m *macaron.Macaron) {
m.Group("/wiki", func() {
m.Get("/?:page", repo.Wiki)
m.Get("/_pages", repo.WikiPages)
m.Get("/:page/_revision", repo.WikiRevision)
m.Group("", func() {
m.Combo("/_new").Get(repo.NewWiki).
@@ -878,9 +900,6 @@ func RegisterRoutes(m *macaron.Macaron) {
}, context.RepoRef(), reqRepoCodeReader)
m.Get("/commit/:sha([a-f0-9]{7,40})\\.:ext(patch|diff)",
repo.MustBeNotEmpty, reqRepoCodeReader, repo.RawDiff)
m.Get("/compare/:before([a-z0-9]{40})\\.\\.\\.:after([a-z0-9]{40})", repo.SetEditorconfigIfExists,
repo.SetDiffViewStyle, repo.MustBeNotEmpty, reqRepoCodeReader, repo.CompareDiff)
}, ignSignIn, context.RepoAssignment(), context.UnitTypes())
m.Group("/:username/:reponame", func() {
m.Get("/stars", repo.Stars)
@@ -906,7 +925,7 @@ func RegisterRoutes(m *macaron.Macaron) {
m.Post("/", lfs.PostLockHandler)
m.Post("/verify", lfs.VerifyLockHandler)
m.Post("/:lid/unlock", lfs.UnLockHandler)
}, context.RepoAssignment())
})
m.Any("/*", func(ctx *context.Context) {
ctx.NotFound("", nil)
})
+133 -58
View File
@@ -71,14 +71,20 @@ func AutoSignIn(ctx *context.Context) (bool, error) {
return false, nil
}
if val, _ := ctx.GetSuperSecureCookie(
base.EncodeMD5(u.Rands+u.Passwd), setting.CookieRememberName); val != u.Name {
if val, ok := ctx.GetSuperSecureCookie(
base.EncodeMD5(u.Rands+u.Passwd), setting.CookieRememberName); !ok || val != u.Name {
return false, nil
}
isSucceed = true
ctx.Session.Set("uid", u.ID)
ctx.Session.Set("uname", u.Name)
err = ctx.Session.Set("uid", u.ID)
if err != nil {
return false, err
}
err = ctx.Session.Set("uname", u.Name)
if err != nil {
return false, err
}
ctx.SetCookie(setting.CSRFCookieName, "", -1, setting.AppSubURL, "", setting.SessionConfig.Secure, true)
return true, nil
}
@@ -191,8 +197,16 @@ func SignInPost(ctx *context.Context, form auth.SignInForm) {
}
// User needs to use 2FA, save data and redirect to 2FA page.
ctx.Session.Set("twofaUid", u.ID)
ctx.Session.Set("twofaRemember", form.Remember)
err = ctx.Session.Set("twofaUid", u.ID)
if err != nil {
ctx.ServerError("UserSignIn", err)
return
}
err = ctx.Session.Set("twofaRemember", form.Remember)
if err != nil {
ctx.ServerError("UserSignIn", err)
return
}
regs, err := models.GetU2FRegistrationsByUID(u.ID)
if err == nil && len(regs) > 0 {
@@ -383,6 +397,10 @@ func U2FChallenge(ctx *context.Context) {
return
}
challenge, err := u2f.NewChallenge(setting.U2F.AppID, setting.U2F.TrustedFacets)
if err != nil {
ctx.ServerError("u2f.NewChallenge", err)
return
}
if err = ctx.Session.Set("u2fChallenge", challenge); err != nil {
ctx.ServerError("UserSignIn", err)
return
@@ -462,16 +480,22 @@ func handleSignInFull(ctx *context.Context, u *models.User, remember bool, obeyR
setting.CookieRememberName, u.Name, days, setting.AppSubURL, "", setting.SessionConfig.Secure, true)
}
ctx.Session.Delete("openid_verified_uri")
ctx.Session.Delete("openid_signin_remember")
ctx.Session.Delete("openid_determined_email")
ctx.Session.Delete("openid_determined_username")
ctx.Session.Delete("twofaUid")
ctx.Session.Delete("twofaRemember")
ctx.Session.Delete("u2fChallenge")
ctx.Session.Delete("linkAccount")
ctx.Session.Set("uid", u.ID)
ctx.Session.Set("uname", u.Name)
_ = ctx.Session.Delete("openid_verified_uri")
_ = ctx.Session.Delete("openid_signin_remember")
_ = ctx.Session.Delete("openid_determined_email")
_ = ctx.Session.Delete("openid_determined_username")
_ = ctx.Session.Delete("twofaUid")
_ = ctx.Session.Delete("twofaRemember")
_ = ctx.Session.Delete("u2fChallenge")
_ = ctx.Session.Delete("linkAccount")
err := ctx.Session.Set("uid", u.ID)
if err != nil {
log.Error(fmt.Sprintf("Error setting session: %v", err))
}
err = ctx.Session.Set("uname", u.Name)
if err != nil {
log.Error(fmt.Sprintf("Error setting session: %v", err))
}
// Language setting of the user overwrites the one previously set
// If the user does not have a locale set, we save the current one.
@@ -563,7 +587,10 @@ func handleOAuth2SignIn(u *models.User, gothUser goth.User, ctx *context.Context
if u == nil {
// no existing user is found, request attach or new account
ctx.Session.Set("linkAccountGothUser", gothUser)
err = ctx.Session.Set("linkAccountGothUser", gothUser)
if err != nil {
log.Error(fmt.Sprintf("Error setting session: %v", err))
}
ctx.Redirect(setting.AppSubURL + "/user/link_account")
return
}
@@ -573,8 +600,14 @@ func handleOAuth2SignIn(u *models.User, gothUser goth.User, ctx *context.Context
_, err = models.GetTwoFactorByUID(u.ID)
if err != nil {
if models.IsErrTwoFactorNotEnrolled(err) {
ctx.Session.Set("uid", u.ID)
ctx.Session.Set("uname", u.Name)
err = ctx.Session.Set("uid", u.ID)
if err != nil {
log.Error(fmt.Sprintf("Error setting session: %v", err))
}
err = ctx.Session.Set("uname", u.Name)
if err != nil {
log.Error(fmt.Sprintf("Error setting session: %v", err))
}
// Clear whatever CSRF has right now, force to generate a new one
ctx.SetCookie(setting.CSRFCookieName, "", -1, setting.AppSubURL, "", setting.SessionConfig.Secure, true)
@@ -600,8 +633,14 @@ func handleOAuth2SignIn(u *models.User, gothUser goth.User, ctx *context.Context
}
// User needs to use 2FA, save data and redirect to 2FA page.
ctx.Session.Set("twofaUid", u.ID)
ctx.Session.Set("twofaRemember", false)
err = ctx.Session.Set("twofaUid", u.ID)
if err != nil {
log.Error(fmt.Sprintf("Error setting session: %v", err))
}
err = ctx.Session.Set("twofaRemember", false)
if err != nil {
log.Error(fmt.Sprintf("Error setting session: %v", err))
}
// If U2F is enrolled -> Redirect to U2F instead
regs, err := models.GetU2FRegistrationsByUID(u.ID)
@@ -658,9 +697,10 @@ func oAuth2UserLoginCallback(loginSource *models.LoginSource, request *http.Requ
// LinkAccount shows the page where the user can decide to login or create a new account
func LinkAccount(ctx *context.Context) {
ctx.Data["DisablePassword"] = !setting.Service.RequireExternalRegistrationCaptcha || setting.Service.AllowOnlyExternalRegistration
ctx.Data["Title"] = ctx.Tr("link_account")
ctx.Data["LinkAccountMode"] = true
ctx.Data["EnableCaptcha"] = setting.Service.EnableCaptcha
ctx.Data["EnableCaptcha"] = setting.Service.EnableCaptcha && setting.Service.RequireExternalRegistrationCaptcha
ctx.Data["CaptchaType"] = setting.Service.CaptchaType
ctx.Data["RecaptchaURL"] = setting.Service.RecaptchaURL
ctx.Data["RecaptchaSitekey"] = setting.Service.RecaptchaSitekey
@@ -707,10 +747,11 @@ func LinkAccount(ctx *context.Context) {
// LinkAccountPostSignIn handle the coupling of external account with another account using signIn
func LinkAccountPostSignIn(ctx *context.Context, signInForm auth.SignInForm) {
ctx.Data["DisablePassword"] = setting.Service.AllowOnlyExternalRegistration
ctx.Data["Title"] = ctx.Tr("link_account")
ctx.Data["LinkAccountMode"] = true
ctx.Data["LinkAccountModeSignIn"] = true
ctx.Data["EnableCaptcha"] = setting.Service.EnableCaptcha
ctx.Data["EnableCaptcha"] = setting.Service.EnableCaptcha && setting.Service.RequireExternalRegistrationCaptcha
ctx.Data["RecaptchaURL"] = setting.Service.RecaptchaURL
ctx.Data["CaptchaType"] = setting.Service.CaptchaType
ctx.Data["RecaptchaSitekey"] = setting.Service.RecaptchaSitekey
@@ -760,9 +801,18 @@ func LinkAccountPostSignIn(ctx *context.Context, signInForm auth.SignInForm) {
}
// User needs to use 2FA, save data and redirect to 2FA page.
ctx.Session.Set("twofaUid", u.ID)
ctx.Session.Set("twofaRemember", signInForm.Remember)
ctx.Session.Set("linkAccount", true)
err = ctx.Session.Set("twofaUid", u.ID)
if err != nil {
log.Error(fmt.Sprintf("Error setting session: %v", err))
}
err = ctx.Session.Set("twofaRemember", signInForm.Remember)
if err != nil {
log.Error(fmt.Sprintf("Error setting session: %v", err))
}
err = ctx.Session.Set("linkAccount", true)
if err != nil {
log.Error(fmt.Sprintf("Error setting session: %v", err))
}
// If U2F is enrolled -> Redirect to U2F instead
regs, err := models.GetU2FRegistrationsByUID(u.ID)
@@ -776,10 +826,13 @@ func LinkAccountPostSignIn(ctx *context.Context, signInForm auth.SignInForm) {
// LinkAccountPostRegister handle the creation of a new account for an external account using signUp
func LinkAccountPostRegister(ctx *context.Context, cpt *captcha.Captcha, form auth.RegisterForm) {
// TODO Make insecure passwords optional for local accounts also,
// once email-based Second-Factor Auth is available
ctx.Data["DisablePassword"] = !setting.Service.RequireExternalRegistrationCaptcha || setting.Service.AllowOnlyExternalRegistration
ctx.Data["Title"] = ctx.Tr("link_account")
ctx.Data["LinkAccountMode"] = true
ctx.Data["LinkAccountModeRegister"] = true
ctx.Data["EnableCaptcha"] = setting.Service.EnableCaptcha
ctx.Data["EnableCaptcha"] = setting.Service.EnableCaptcha && setting.Service.RequireExternalRegistrationCaptcha
ctx.Data["RecaptchaURL"] = setting.Service.RecaptchaURL
ctx.Data["CaptchaType"] = setting.Service.CaptchaType
ctx.Data["RecaptchaSitekey"] = setting.Service.RecaptchaSitekey
@@ -806,14 +859,18 @@ func LinkAccountPostRegister(ctx *context.Context, cpt *captcha.Captcha, form au
return
}
if setting.Service.EnableCaptcha && setting.Service.CaptchaType == setting.ImageCaptcha && !cpt.VerifyReq(ctx.Req) {
ctx.Data["Err_Captcha"] = true
ctx.RenderWithErr(ctx.Tr("form.captcha_incorrect"), tplLinkAccount, &form)
return
}
if setting.Service.EnableCaptcha && setting.Service.RequireExternalRegistrationCaptcha {
var valid bool
switch setting.Service.CaptchaType {
case setting.ImageCaptcha:
valid = cpt.VerifyReq(ctx.Req)
case setting.ReCaptcha:
valid, _ = recaptcha.Verify(form.GRecaptchaResponse)
default:
ctx.ServerError("Unknown Captcha Type", fmt.Errorf("Unknown Captcha Type: %s", setting.Service.CaptchaType))
return
}
if setting.Service.EnableCaptcha && setting.Service.CaptchaType == setting.ReCaptcha {
valid, _ := recaptcha.Verify(form.GRecaptchaResponse)
if !valid {
ctx.Data["Err_Captcha"] = true
ctx.RenderWithErr(ctx.Tr("form.captcha_incorrect"), tplLinkAccount, &form)
@@ -821,15 +878,24 @@ func LinkAccountPostRegister(ctx *context.Context, cpt *captcha.Captcha, form au
}
}
if (len(strings.TrimSpace(form.Password)) > 0 || len(strings.TrimSpace(form.Retype)) > 0) && form.Password != form.Retype {
ctx.Data["Err_Password"] = true
ctx.RenderWithErr(ctx.Tr("form.password_not_match"), tplLinkAccount, &form)
return
}
if len(strings.TrimSpace(form.Password)) > 0 && len(form.Password) < setting.MinPasswordLength {
ctx.Data["Err_Password"] = true
ctx.RenderWithErr(ctx.Tr("auth.password_too_short", setting.MinPasswordLength), tplLinkAccount, &form)
return
if setting.Service.AllowOnlyExternalRegistration || !setting.Service.RequireExternalRegistrationPassword {
// In models.User an empty password is classed as not set, so we set form.Password to empty.
// Eventually the database should be changed to indicate "Second Factor"-enabled accounts
// (accounts that do not introduce the security vulnerabilities of a password).
// If a user decides to circumvent second-factor security, and purposefully create a password,
// they can still do so using the "Recover Account" option.
form.Password = ""
} else {
if (len(strings.TrimSpace(form.Password)) > 0 || len(strings.TrimSpace(form.Retype)) > 0) && form.Password != form.Retype {
ctx.Data["Err_Password"] = true
ctx.RenderWithErr(ctx.Tr("form.password_not_match"), tplLinkAccount, &form)
return
}
if len(strings.TrimSpace(form.Password)) > 0 && len(form.Password) < setting.MinPasswordLength {
ctx.Data["Err_Password"] = true
ctx.RenderWithErr(ctx.Tr("auth.password_too_short", setting.MinPasswordLength), tplLinkAccount, &form)
return
}
}
loginSource, err := models.GetActiveOAuth2LoginSourceByName(gothUser.(goth.User).Provider)
@@ -897,11 +963,11 @@ func LinkAccountPostRegister(ctx *context.Context, cpt *captcha.Captcha, form au
}
func handleSignOut(ctx *context.Context) {
ctx.Session.Delete("uid")
ctx.Session.Delete("uname")
ctx.Session.Delete("socialId")
ctx.Session.Delete("socialName")
ctx.Session.Delete("socialEmail")
_ = ctx.Session.Delete("uid")
_ = ctx.Session.Delete("uname")
_ = ctx.Session.Delete("socialId")
_ = ctx.Session.Delete("socialName")
_ = ctx.Session.Delete("socialEmail")
ctx.SetCookie(setting.CookieUserName, "", -1, setting.AppSubURL, "", setting.SessionConfig.Secure, true)
ctx.SetCookie(setting.CookieRememberName, "", -1, setting.AppSubURL, "", setting.SessionConfig.Secure, true)
ctx.SetCookie(setting.CSRFCookieName, "", -1, setting.AppSubURL, "", setting.SessionConfig.Secure, true)
@@ -952,14 +1018,18 @@ func SignUpPost(ctx *context.Context, cpt *captcha.Captcha, form auth.RegisterFo
return
}
if setting.Service.EnableCaptcha && setting.Service.CaptchaType == setting.ImageCaptcha && !cpt.VerifyReq(ctx.Req) {
ctx.Data["Err_Captcha"] = true
ctx.RenderWithErr(ctx.Tr("form.captcha_incorrect"), tplSignUp, &form)
return
}
if setting.Service.EnableCaptcha {
var valid bool
switch setting.Service.CaptchaType {
case setting.ImageCaptcha:
valid = cpt.VerifyReq(ctx.Req)
case setting.ReCaptcha:
valid, _ = recaptcha.Verify(form.GRecaptchaResponse)
default:
ctx.ServerError("Unknown Captcha Type", fmt.Errorf("Unknown Captcha Type: %s", setting.Service.CaptchaType))
return
}
if setting.Service.EnableCaptcha && setting.Service.CaptchaType == setting.ReCaptcha {
valid, _ := recaptcha.Verify(form.GRecaptchaResponse)
if !valid {
ctx.Data["Err_Captcha"] = true
ctx.RenderWithErr(ctx.Tr("form.captcha_incorrect"), tplSignUp, &form)
@@ -1086,8 +1156,14 @@ func Activate(ctx *context.Context) {
log.Trace("User activated: %s", user.Name)
ctx.Session.Set("uid", user.ID)
ctx.Session.Set("uname", user.Name)
err = ctx.Session.Set("uid", user.ID)
if err != nil {
log.Error(fmt.Sprintf("Error setting session: %v", err))
}
err = ctx.Session.Set("uname", user.Name)
if err != nil {
log.Error(fmt.Sprintf("Error setting session: %v", err))
}
ctx.Flash.Success(ctx.Tr("auth.account_activated"))
ctx.Redirect(setting.AppSubURL + "/")
return
@@ -1113,7 +1189,6 @@ func ActivateEmail(ctx *context.Context) {
}
ctx.Redirect(setting.AppSubURL + "/user/settings/email")
return
}
// ForgotPasswd render the forget pasword page
+40 -18
View File
@@ -126,7 +126,10 @@ func SignInOpenIDPost(ctx *context.Context, form auth.SignInOpenIDForm) {
url += "&openid.sreg.optional=nickname%2Cemail"
log.Trace("Form-passed openid-remember: %t", form.Remember)
ctx.Session.Set("openid_signin_remember", form.Remember)
err = ctx.Session.Set("openid_signin_remember", form.Remember)
if err != nil {
log.Error("SignInOpenIDPost: Could not set session: %v", err.Error())
}
ctx.Redirect(url)
}
@@ -152,7 +155,7 @@ func signInOpenIDVerify(ctx *context.Context) {
/* Now we should seek for the user and log him in, or prompt
* to register if not found */
u, _ := models.GetUserByOpenID(id)
u, err := models.GetUserByOpenID(id)
if err != nil {
if !models.IsErrUserNotExist(err) {
ctx.RenderWithErr(err.Error(), tplSignInOpenID, &auth.SignInOpenIDForm{
@@ -160,6 +163,7 @@ func signInOpenIDVerify(ctx *context.Context) {
})
return
}
log.Error("signInOpenIDVerify: %v", err)
}
if u != nil {
log.Trace("User exists, logging in")
@@ -191,7 +195,7 @@ func signInOpenIDVerify(ctx *context.Context) {
log.Trace("User has email=" + email + " and nickname=" + nickname)
if email != "" {
u, _ = models.GetUserByEmail(email)
u, err = models.GetUserByEmail(email)
if err != nil {
if !models.IsErrUserNotExist(err) {
ctx.RenderWithErr(err.Error(), tplSignInOpenID, &auth.SignInOpenIDForm{
@@ -199,6 +203,7 @@ func signInOpenIDVerify(ctx *context.Context) {
})
return
}
log.Error("signInOpenIDVerify: %v", err)
}
if u != nil {
log.Trace("Local user " + u.LowerName + " has OpenID provided email " + email)
@@ -220,15 +225,24 @@ func signInOpenIDVerify(ctx *context.Context) {
}
}
ctx.Session.Set("openid_verified_uri", id)
err = ctx.Session.Set("openid_verified_uri", id)
if err != nil {
log.Error("signInOpenIDVerify: Could not set session: %v", err.Error())
}
ctx.Session.Set("openid_determined_email", email)
err = ctx.Session.Set("openid_determined_email", email)
if err != nil {
log.Error("signInOpenIDVerify: Could not set session: %v", err.Error())
}
if u != nil {
nickname = u.LowerName
}
ctx.Session.Set("openid_determined_username", nickname)
err = ctx.Session.Set("openid_determined_username", nickname)
if err != nil {
log.Error("signInOpenIDVerify: Could not set session: %v", err.Error())
}
if u != nil || !setting.Service.EnableOpenIDSignUp {
ctx.Redirect(setting.AppSubURL + "/user/openid/connect")
@@ -343,15 +357,23 @@ func RegisterOpenIDPost(ctx *context.Context, cpt *captcha.Captcha, form auth.Si
ctx.Data["RecaptchaSitekey"] = setting.Service.RecaptchaSitekey
ctx.Data["OpenID"] = oid
if setting.Service.EnableCaptcha && setting.Service.CaptchaType == setting.ImageCaptcha && !cpt.VerifyReq(ctx.Req) {
ctx.Data["Err_Captcha"] = true
ctx.RenderWithErr(ctx.Tr("form.captcha_incorrect"), tplSignUpOID, &form)
return
}
if setting.Service.EnableCaptcha {
var valid bool
switch setting.Service.CaptchaType {
case setting.ImageCaptcha:
valid = cpt.VerifyReq(ctx.Req)
case setting.ReCaptcha:
err := ctx.Req.ParseForm()
if err != nil {
ctx.ServerError("", err)
return
}
valid, _ = recaptcha.Verify(form.GRecaptchaResponse)
default:
ctx.ServerError("Unknown Captcha Type", fmt.Errorf("Unknown Captcha Type: %s", setting.Service.CaptchaType))
return
}
if setting.Service.EnableCaptcha && setting.Service.CaptchaType == setting.ReCaptcha {
ctx.Req.ParseForm()
valid, _ := recaptcha.Verify(form.GRecaptchaResponse)
if !valid {
ctx.Data["Err_Captcha"] = true
ctx.RenderWithErr(ctx.Tr("form.captcha_incorrect"), tplSignUpOID, &form)
@@ -359,11 +381,11 @@ func RegisterOpenIDPost(ctx *context.Context, cpt *captcha.Captcha, form auth.Si
}
}
len := setting.MinPasswordLength
if len < 256 {
len = 256
length := setting.MinPasswordLength
if length < 256 {
length = 256
}
password, err := generate.GetRandomString(len)
password, err := generate.GetRandomString(length)
if err != nil {
ctx.RenderWithErr(err.Error(), tplSignUpOID, form)
return
+14 -44
View File
@@ -499,50 +499,20 @@ func showOrgProfile(ctx *context.Context) {
count int64
err error
)
if ctx.IsSigned && !ctx.User.IsAdmin {
env, err := org.AccessibleReposEnv(ctx.User.ID)
if err != nil {
ctx.ServerError("AccessibleReposEnv", err)
return
}
env.SetSort(orderBy)
if len(keyword) != 0 {
env.AddKeyword(keyword)
}
repos, err = env.Repos(page, setting.UI.User.RepoPagingNum)
if err != nil {
ctx.ServerError("env.Repos", err)
return
}
count, err = env.CountRepos()
if err != nil {
ctx.ServerError("env.CountRepos", err)
return
}
} else {
showPrivate := ctx.IsSigned && ctx.User.IsAdmin
if len(keyword) == 0 {
repos, err = models.GetUserRepositories(org.ID, showPrivate, page, setting.UI.User.RepoPagingNum, orderBy.String())
if err != nil {
ctx.ServerError("GetRepositories", err)
return
}
count = models.CountUserRepositories(org.ID, showPrivate)
} else {
repos, count, err = models.SearchRepositoryByName(&models.SearchRepoOptions{
Keyword: keyword,
OwnerID: org.ID,
OrderBy: orderBy,
Private: showPrivate,
Page: page,
IsProfile: true,
PageSize: setting.UI.User.RepoPagingNum,
})
if err != nil {
ctx.ServerError("SearchRepositoryByName", err)
return
}
}
repos, count, err = models.SearchRepositoryByName(&models.SearchRepoOptions{
Keyword: keyword,
OwnerID: org.ID,
OrderBy: orderBy,
Private: ctx.IsSigned,
UserIsAdmin: ctx.IsUserSiteAdmin(),
UserID: ctx.Data["SignedUserID"].(int64),
Page: page,
IsProfile: true,
PageSize: setting.UI.User.RepoPagingNum,
})
if err != nil {
ctx.ServerError("SearchRepositoryByName", err)
return
}
if err := org.GetMembers(); err != nil {
+29 -7
View File
@@ -7,12 +7,10 @@ package user
import (
"encoding/base64"
"fmt"
"github.com/go-macaron/binding"
"net/url"
"strings"
"github.com/dgrijalva/jwt-go"
"github.com/go-macaron/binding"
"code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/auth"
"code.gitea.io/gitea/modules/base"
@@ -20,6 +18,8 @@ import (
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/util"
"github.com/dgrijalva/jwt-go"
)
const (
@@ -164,6 +164,14 @@ func newAccessTokenResponse(grant *models.OAuth2Grant) (*AccessTokenResponse, *A
func AuthorizeOAuth(ctx *context.Context, form auth.AuthorizationForm) {
errs := binding.Errors{}
errs = form.Validate(ctx.Context, errs)
if len(errs) > 0 {
errstring := ""
for _, e := range errs {
errstring += e.Error() + "\n"
}
ctx.ServerError("AuthorizeOAuth: Validate: ", fmt.Errorf("errors occurred during validation: %s", errstring))
return
}
app, err := models.GetOAuth2ApplicationByClientID(form.ClientID)
if err != nil {
@@ -221,7 +229,6 @@ func AuthorizeOAuth(ctx *context.Context, form auth.AuthorizationForm) {
}, form.RedirectURI)
return
}
break
case "":
break
default:
@@ -262,9 +269,24 @@ func AuthorizeOAuth(ctx *context.Context, form auth.AuthorizationForm) {
ctx.Data["ApplicationUserLink"] = "<a href=\"" + setting.AppURL + app.User.LowerName + "\">@" + app.User.Name + "</a>"
ctx.Data["ApplicationRedirectDomainHTML"] = "<strong>" + form.RedirectURI + "</strong>"
// TODO document SESSION <=> FORM
ctx.Session.Set("client_id", app.ClientID)
ctx.Session.Set("redirect_uri", form.RedirectURI)
ctx.Session.Set("state", form.State)
err = ctx.Session.Set("client_id", app.ClientID)
if err != nil {
handleServerError(ctx, form.State, form.RedirectURI)
log.Error(err.Error())
return
}
err = ctx.Session.Set("redirect_uri", form.RedirectURI)
if err != nil {
handleServerError(ctx, form.State, form.RedirectURI)
log.Error(err.Error())
return
}
err = ctx.Session.Set("state", form.State)
if err != nil {
handleServerError(ctx, form.State, form.RedirectURI)
log.Error(err.Error())
return
}
ctx.HTML(200, tplGrantAccess)
}
+33 -64
View File
@@ -20,7 +20,6 @@ import (
const (
tplFollowers base.TplName = "user/meta/followers"
tplStars base.TplName = "user/meta/stars"
)
// GetUserByName get user by name
@@ -170,74 +169,44 @@ func Profile(ctx *context.Context) {
}
case "stars":
ctx.Data["PageIsProfileStarList"] = true
if len(keyword) == 0 {
repos, err = ctxUser.GetStarredRepos(showPrivate, page, setting.UI.User.RepoPagingNum, orderBy.String())
if err != nil {
ctx.ServerError("GetStarredRepos", err)
return
}
count, err = ctxUser.GetStarredRepoCount(showPrivate)
if err != nil {
ctx.ServerError("GetStarredRepoCount", err)
return
}
} else {
repos, count, err = models.SearchRepositoryByName(&models.SearchRepoOptions{
Keyword: keyword,
OwnerID: ctxUser.ID,
OrderBy: orderBy,
Private: showPrivate,
Page: page,
PageSize: setting.UI.User.RepoPagingNum,
Starred: true,
Collaborate: util.OptionalBoolFalse,
TopicOnly: topicOnly,
})
if err != nil {
ctx.ServerError("SearchRepositoryByName", err)
return
}
repos, count, err = models.SearchRepositoryByName(&models.SearchRepoOptions{
Keyword: keyword,
OrderBy: orderBy,
Private: ctx.IsSigned,
UserIsAdmin: ctx.IsUserSiteAdmin(),
UserID: ctx.Data["SignedUserID"].(int64),
Page: page,
PageSize: setting.UI.User.RepoPagingNum,
StarredByID: ctxUser.ID,
Collaborate: util.OptionalBoolFalse,
TopicOnly: topicOnly,
})
if err != nil {
ctx.ServerError("SearchRepositoryByName", err)
return
}
total = int(count)
default:
if len(keyword) == 0 {
repos, err = models.GetUserRepositories(ctxUser.ID, showPrivate, page, setting.UI.User.RepoPagingNum, orderBy.String())
if err != nil {
ctx.ServerError("GetRepositories", err)
return
}
if showPrivate {
total = ctxUser.NumRepos
} else {
count, err := models.GetPublicRepositoryCount(ctxUser)
if err != nil {
ctx.ServerError("GetPublicRepositoryCount", err)
return
}
total = int(count)
}
} else {
repos, count, err = models.SearchRepositoryByName(&models.SearchRepoOptions{
Keyword: keyword,
OwnerID: ctxUser.ID,
OrderBy: orderBy,
Private: showPrivate,
Page: page,
IsProfile: true,
PageSize: setting.UI.User.RepoPagingNum,
Collaborate: util.OptionalBoolFalse,
TopicOnly: topicOnly,
})
if err != nil {
ctx.ServerError("SearchRepositoryByName", err)
return
}
total = int(count)
repos, count, err = models.SearchRepositoryByName(&models.SearchRepoOptions{
Keyword: keyword,
OwnerID: ctxUser.ID,
OrderBy: orderBy,
Private: ctx.IsSigned,
UserIsAdmin: ctx.IsUserSiteAdmin(),
UserID: ctx.Data["SignedUserID"].(int64),
Page: page,
IsProfile: true,
PageSize: setting.UI.User.RepoPagingNum,
Collaborate: util.OptionalBoolFalse,
TopicOnly: topicOnly,
})
if err != nil {
ctx.ServerError("SearchRepositoryByName", err)
return
}
total = int(count)
}
ctx.Data["Repos"] = repos
ctx.Data["Total"] = total
+7 -5
View File
@@ -127,6 +127,10 @@ func UpdateAvatarSetting(ctx *context.Context, form auth.AvatarForm, ctxUser *mo
}
defer fr.Close()
if form.Avatar.Size > setting.AvatarMaxFileSize {
return errors.New(ctx.Tr("settings.uploaded_avatar_is_too_big"))
}
data, err := ioutil.ReadAll(fr)
if err != nil {
return fmt.Errorf("ioutil.ReadAll: %v", err)
@@ -137,13 +141,11 @@ func UpdateAvatarSetting(ctx *context.Context, form auth.AvatarForm, ctxUser *mo
if err = ctxUser.UploadAvatar(data); err != nil {
return fmt.Errorf("UploadAvatar: %v", err)
}
} else {
} else if ctxUser.UseCustomAvatar && !com.IsFile(ctxUser.CustomAvatarPath()) {
// No avatar is uploaded but setting has been changed to enable,
// generate a random one when needed.
if ctxUser.UseCustomAvatar && !com.IsFile(ctxUser.CustomAvatarPath()) {
if err := ctxUser.GenerateRandomAvatar(); err != nil {
log.Error("GenerateRandomAvatar[%d]: %v", ctxUser.ID, err)
}
if err := ctxUser.GenerateRandomAvatar(); err != nil {
log.Error("GenerateRandomAvatar[%d]: %v", ctxUser.ID, err)
}
}
+27 -5
View File
@@ -73,12 +73,18 @@ func twofaGenerateSecretAndQr(ctx *context.Context) bool {
uri := ctx.Session.Get("twofaUri")
if uri != nil {
otpKey, err = otp.NewKeyFromURL(uri.(string))
if err != nil {
ctx.ServerError("SettingsTwoFactor: NewKeyFromURL: ", err)
return false
}
}
// Filter unsafe character ':' in issuer
issuer := strings.Replace(setting.AppName+" ("+setting.Domain+")", ":", "", -1)
if otpKey == nil {
err = nil // clear the error, in case the URL was invalid
otpKey, err = totp.Generate(totp.GenerateOpts{
SecretSize: 40,
Issuer: setting.AppName + " (" + strings.TrimRight(setting.AppURL, "/") + ")",
Issuer: issuer,
AccountName: ctx.User.Name,
})
if err != nil {
@@ -101,8 +107,16 @@ func twofaGenerateSecretAndQr(ctx *context.Context) bool {
}
ctx.Data["QrUri"] = template.URL("data:image/png;base64," + base64.StdEncoding.EncodeToString(imgBytes.Bytes()))
ctx.Session.Set("twofaSecret", otpKey.Secret())
ctx.Session.Set("twofaUri", otpKey.String())
err = ctx.Session.Set("twofaSecret", otpKey.Secret())
if err != nil {
ctx.ServerError("SettingsTwoFactor", err)
return false
}
err = ctx.Session.Set("twofaUri", otpKey.String())
if err != nil {
ctx.ServerError("SettingsTwoFactor", err)
return false
}
return true
}
@@ -182,8 +196,16 @@ func EnrollTwoFactorPost(ctx *context.Context, form auth.TwoFactorAuthForm) {
return
}
ctx.Session.Delete("twofaSecret")
ctx.Session.Delete("twofaUri")
err = ctx.Session.Delete("twofaSecret")
if err != nil {
ctx.ServerError("SettingsTwoFactor", err)
return
}
err = ctx.Session.Delete("twofaUri")
if err != nil {
ctx.ServerError("SettingsTwoFactor", err)
return
}
ctx.Flash.Success(ctx.Tr("settings.twofa_enrolled", token))
ctx.Redirect(setting.AppSubURL + "/user/settings/security")
}
+5 -2
View File
@@ -42,7 +42,11 @@ func U2FRegister(ctx *context.Context, form auth.U2FRegistrationForm) {
return
}
}
ctx.Session.Set("u2fName", form.Name)
err = ctx.Session.Set("u2fName", form.Name)
if err != nil {
ctx.ServerError("", err)
return
}
ctx.JSON(200, u2f.NewWebRegisterRequest(challenge, regs.ToRegistrations()))
}
@@ -95,5 +99,4 @@ func U2FDelete(ctx *context.Context, form auth.U2FDeleteForm) {
ctx.JSON(200, map[string]interface{}{
"redirect": setting.AppSubURL + "/user/settings/security",
})
return
}