Merge remote-tracking branch 'upstream/master' into team-grant-all-repos

This commit is contained in:
David Svantesson
2019-10-25 19:47:54 +00:00
889 changed files with 25916 additions and 8092 deletions
+9 -1
View File
@@ -12,6 +12,7 @@ import (
"code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/password"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/routers"
"code.gitea.io/gitea/services/mailer"
@@ -94,7 +95,10 @@ func NewUserPost(ctx *context.Context, form auth.AdminCreateUserForm) {
u.LoginName = form.LoginName
}
}
if !password.IsComplexEnough(form.Password) {
ctx.RenderWithErr(ctx.Tr("form.password_complexity"), tplUserNew, &form)
return
}
if err := models.CreateUser(u); err != nil {
switch {
case models.IsErrUserAlreadyExist(err):
@@ -201,6 +205,10 @@ func EditUserPost(ctx *context.Context, form auth.AdminEditUserForm) {
ctx.ServerError("UpdateUser", err)
return
}
if !password.IsComplexEnough(form.Password) {
ctx.RenderWithErr(ctx.Tr("form.password_complexity"), tplUserEdit, &form)
return
}
u.HashPassword(form.Password)
}
+2 -2
View File
@@ -34,7 +34,7 @@ func TestNewUserPost_MustChangePassword(t *testing.T) {
LoginName: "local",
UserName: username,
Email: email,
Password: "xxxxxxxx",
Password: "abc123ABC!=$",
SendNotify: false,
MustChangePassword: true,
}
@@ -71,7 +71,7 @@ func TestNewUserPost_MustChangePasswordFalse(t *testing.T) {
LoginName: "local",
UserName: username,
Email: email,
Password: "xxxxxxxx",
Password: "abc123ABC!=$",
SendNotify: false,
MustChangePassword: false,
}
+13 -1
View File
@@ -6,9 +6,12 @@
package admin
import (
"errors"
"code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/password"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/routers/api/v1/convert"
"code.gitea.io/gitea/routers/api/v1/user"
@@ -73,7 +76,11 @@ func CreateUser(ctx *context.APIContext, form api.CreateUserOption) {
if ctx.Written() {
return
}
if !password.IsComplexEnough(form.Password) {
err := errors.New("PasswordComplexity")
ctx.Error(400, "PasswordComplexity", err)
return
}
if err := models.CreateUser(u); err != nil {
if models.IsErrUserAlreadyExist(err) ||
models.IsErrEmailAlreadyUsed(err) ||
@@ -131,6 +138,11 @@ func EditUser(ctx *context.APIContext, form api.EditUserOption) {
}
if len(form.Password) > 0 {
if !password.IsComplexEnough(form.Password) {
err := errors.New("PasswordComplexity")
ctx.Error(400, "PasswordComplexity", err)
return
}
var err error
if u.Salt, err = models.GetUserSalt(); err != nil {
ctx.Error(500, "UpdateUser", err)
+2
View File
@@ -507,6 +507,7 @@ func RegisterRoutes(m *macaron.Macaron) {
m.Get("/swagger", misc.Swagger)
}
m.Get("/version", misc.Version)
m.Get("/signing-key.gpg", misc.SigningKey)
m.Post("/markdown", bind(api.MarkdownOption{}), misc.Markdown)
m.Post("/markdown/raw", misc.MarkdownRaw)
@@ -771,6 +772,7 @@ func RegisterRoutes(m *macaron.Macaron) {
m.Delete("", bind(api.DeleteFileOptions{}), repo.DeleteFile)
}, reqRepoWriter(models.UnitTypeCode), reqToken())
}, reqRepoReader(models.UnitTypeCode))
m.Get("/signing-key.gpg", misc.SigningKey)
m.Group("/topics", func() {
m.Combo("").Get(repo.ListTopics).
Put(reqToken(), reqAdmin(), bind(api.RepoTopicOptions{}), repo.UpdateTopics)
+17 -12
View File
@@ -12,6 +12,7 @@ import (
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/markup"
"code.gitea.io/gitea/modules/structs"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/util"
@@ -84,17 +85,21 @@ func ToCommit(repo *models.Repository, c *git.Commit) *api.PayloadCommit {
// 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
commitVerification := &api.PayloadCommitVerification{
Verified: verif.Verified,
Reason: verif.Reason,
}
if c.Signature != nil {
signature = c.Signature.Signature
payload = c.Signature.Payload
commitVerification.Signature = c.Signature.Signature
commitVerification.Payload = c.Signature.Payload
}
return &api.PayloadCommitVerification{
Verified: verif.Verified,
Reason: verif.Reason,
Signature: signature,
Payload: payload,
if verif.SigningUser != nil {
commitVerification.Signer = &structs.PayloadUser{
Name: verif.SigningUser.Name,
Email: verif.SigningUser.Email,
}
}
return commitVerification
}
// ToPublicKey convert models.PublicKey to api.PublicKey
@@ -233,12 +238,9 @@ func ToTeam(team *models.Team) *api.Team {
// ToUser convert models.User to api.User
func ToUser(user *models.User, signed, authed bool) *api.User {
result := &api.User{
ID: user.ID,
UserName: user.Name,
AvatarURL: user.AvatarLink(),
FullName: markup.Sanitize(user.FullName),
IsAdmin: user.IsAdmin,
LastLogin: user.LastLoginUnix.AsTime(),
Created: user.CreatedUnix.AsTime(),
}
// hide primary email if API caller isn't user itself or an admin
@@ -246,8 +248,11 @@ func ToUser(user *models.User, signed, authed bool) *api.User {
result.Email = ""
} else if user.KeepEmailPrivate && !authed {
result.Email = user.GetEmail()
} else {
} else { // only user himself and admin could visit these information
result.ID = user.ID
result.Email = user.Email
result.IsAdmin = user.IsAdmin
result.LastLogin = user.LastLoginUnix.AsTime()
}
return result
}
+62
View File
@@ -0,0 +1,62 @@
package misc
import (
"fmt"
"net/http"
"code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/log"
)
// SigningKey returns the public key of the default signing key if it exists
func SigningKey(ctx *context.Context) {
// swagger:operation GET /signing-key.gpg miscellaneous getSigningKey
// ---
// summary: Get default signing-key.gpg
// produces:
// - text/plain
// responses:
// "200":
// description: "GPG armored public key"
// schema:
// type: string
// swagger:operation GET /repos/{owner}/{repo}/signing-key.gpg repository repoSigningKey
// ---
// summary: Get signing-key.gpg for given repository
// produces:
// - text/plain
// 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
// responses:
// "200":
// description: "GPG armored public key"
// schema:
// type: string
path := ""
if ctx.Repo != nil && ctx.Repo.Repository != nil {
path = ctx.Repo.Repository.RepoPath()
}
content, err := models.PublicSigningKey(path)
if err != nil {
ctx.ServerError("gpg export", err)
return
}
_, err = ctx.Write([]byte(content))
if err != nil {
log.Error("Error writing key content %v", err)
ctx.Error(http.StatusInternalServerError, fmt.Sprintf("%v", err))
}
}
+1 -1
View File
@@ -136,7 +136,7 @@ func GetEditorconfig(ctx *context.APIContext) {
}
fileName := ctx.Params("filename")
def := ec.GetDefinitionForFilename(fileName)
def, err := ec.GetDefinitionForFilename(fileName)
if def == nil {
ctx.NotFound(err)
return
+27 -3
View File
@@ -213,12 +213,31 @@ func CreateIssue(ctx *context.APIContext, form api.CreateIssueOption) {
}
return
}
// Check if the passed assignees is assignable
for _, aID := range assigneeIDs {
assignee, err := models.GetUserByID(aID)
if err != nil {
ctx.Error(500, "GetUserByID", err)
return
}
valid, err := models.CanBeAssigned(assignee, ctx.Repo.Repository, false)
if err != nil {
ctx.Error(500, "canBeAssigned", err)
return
}
if !valid {
ctx.Error(422, "canBeAssigned", models.ErrUserDoesNotHaveAccessToRepo{UserID: aID, RepoName: ctx.Repo.Repository.Name})
return
}
}
} else {
// setting labels is not allowed if user is not a writer
form.Labels = make([]int64, 0)
}
if err := issue_service.NewIssue(ctx.Repo.Repository, issue, form.Labels, assigneeIDs, nil); err != nil {
if err := issue_service.NewIssue(ctx.Repo.Repository, issue, form.Labels, nil); err != nil {
if models.IsErrUserDoesNotHaveAccessToRepo(err) {
ctx.Error(400, "UserDoesNotHaveAccessToRepo", err)
return
@@ -227,6 +246,11 @@ func CreateIssue(ctx *context.APIContext, form api.CreateIssueOption) {
return
}
if err := issue_service.AddAssignees(issue, ctx.User, assigneeIDs); err != nil {
ctx.ServerError("AddAssignees", err)
return
}
notification.NotifyNewIssue(issue)
if form.Closed {
@@ -336,9 +360,9 @@ func EditIssue(ctx *context.APIContext, form api.EditIssueOption) {
oneAssignee = *form.Assignee
}
err = models.UpdateAPIAssignee(issue, oneAssignee, form.Assignees, ctx.User)
err = issue_service.UpdateAssignees(issue, oneAssignee, form.Assignees, ctx.User)
if err != nil {
ctx.Error(500, "UpdateAPIAssignee", err)
ctx.Error(500, "UpdateAssignees", err)
return
}
}
+3 -2
View File
@@ -9,6 +9,7 @@ import (
"code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/context"
api "code.gitea.io/gitea/modules/structs"
issue_service "code.gitea.io/gitea/services/issue"
)
// ListIssueLabels list all the labels of an issue
@@ -116,7 +117,7 @@ func AddIssueLabels(ctx *context.APIContext, form api.IssueLabelsOption) {
return
}
if err = issue.AddLabels(ctx.User, labels); err != nil {
if err = issue_service.AddLabels(issue, ctx.User, labels); err != nil {
ctx.Error(500, "AddLabels", err)
return
}
@@ -314,7 +315,7 @@ func ClearIssueLabels(ctx *context.APIContext) {
return
}
if err := issue.ClearLabels(ctx.User); err != nil {
if err := issue_service.ClearLabels(issue, ctx.User); err != nil {
ctx.Error(500, "ClearLabels", err)
return
}
+36 -13
View File
@@ -17,6 +17,7 @@ import (
"code.gitea.io/gitea/modules/notification"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/timeutil"
issue_service "code.gitea.io/gitea/services/issue"
milestone_service "code.gitea.io/gitea/services/milestone"
pull_service "code.gitea.io/gitea/services/pull"
)
@@ -190,7 +191,7 @@ func CreatePullRequest(ctx *context.APIContext, form api.CreatePullRequestOption
)
// Get repo/branch information
headUser, headRepo, headGitRepo, compareInfo, baseBranch, headBranch := parseCompareInfo(ctx, form)
_, headRepo, headGitRepo, compareInfo, baseBranch, headBranch := parseCompareInfo(ctx, form)
if ctx.Written() {
return
}
@@ -265,15 +266,14 @@ func CreatePullRequest(ctx *context.APIContext, form api.CreatePullRequestOption
DeadlineUnix: deadlineUnix,
}
pr := &models.PullRequest{
HeadRepoID: headRepo.ID,
BaseRepoID: repo.ID,
HeadUserName: headUser.Name,
HeadBranch: headBranch,
BaseBranch: baseBranch,
HeadRepo: headRepo,
BaseRepo: repo,
MergeBase: compareInfo.MergeBase,
Type: models.PullRequestGitea,
HeadRepoID: headRepo.ID,
BaseRepoID: repo.ID,
HeadBranch: headBranch,
BaseBranch: baseBranch,
HeadRepo: headRepo,
BaseRepo: repo,
MergeBase: compareInfo.MergeBase,
Type: models.PullRequestGitea,
}
// Get all assignee IDs
@@ -286,8 +286,26 @@ func CreatePullRequest(ctx *context.APIContext, form api.CreatePullRequestOption
}
return
}
// Check if the passed assignees is assignable
for _, aID := range assigneeIDs {
assignee, err := models.GetUserByID(aID)
if err != nil {
ctx.Error(500, "GetUserByID", err)
return
}
if err := pull_service.NewPullRequest(repo, prIssue, labelIDs, []string{}, pr, patch, assigneeIDs); err != nil {
valid, err := models.CanBeAssigned(assignee, repo, true)
if err != nil {
ctx.Error(500, "canBeAssigned", err)
return
}
if !valid {
ctx.Error(422, "canBeAssigned", models.ErrUserDoesNotHaveAccessToRepo{UserID: aID, RepoName: repo.Name})
return
}
}
if err := pull_service.NewPullRequest(repo, prIssue, labelIDs, []string{}, pr, patch); err != nil {
if models.IsErrUserDoesNotHaveAccessToRepo(err) {
ctx.Error(400, "UserDoesNotHaveAccessToRepo", err)
return
@@ -299,6 +317,11 @@ func CreatePullRequest(ctx *context.APIContext, form api.CreatePullRequestOption
return
}
if err := issue_service.AddAssignees(prIssue, ctx.User, assigneeIDs); err != nil {
ctx.ServerError("AddAssignees", err)
return
}
notification.NotifyNewPullRequest(pr)
log.Trace("Pull request created: %d/%d", repo.ID, prIssue.ID)
@@ -388,12 +411,12 @@ func EditPullRequest(ctx *context.APIContext, form api.EditPullRequestOption) {
// Send an empty array ([]) to clear all assignees from the Issue.
if ctx.Repo.CanWrite(models.UnitTypePullRequests) && (form.Assignees != nil || len(form.Assignee) > 0) {
err = models.UpdateAPIAssignee(issue, form.Assignee, form.Assignees, ctx.User)
err = issue_service.UpdateAssignees(issue, form.Assignee, form.Assignees, ctx.User)
if err != nil {
if models.IsErrUserNotExist(err) {
ctx.Error(422, "", fmt.Sprintf("Assignee does not exist: [name: %s]", err))
} else {
ctx.Error(500, "UpdateAPIAssignee", err)
ctx.Error(500, "UpdateAssignees", err)
}
return
}
+23 -14
View File
@@ -8,6 +8,7 @@ package repo
import (
"fmt"
"net/http"
"net/url"
"strings"
"code.gitea.io/gitea/models"
@@ -17,6 +18,7 @@ import (
"code.gitea.io/gitea/modules/migrations"
"code.gitea.io/gitea/modules/notification"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/structs"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/modules/validation"
@@ -397,21 +399,28 @@ func Migrate(ctx *context.APIContext, form auth.MigrateRepoForm) {
return
}
var gitServiceType = structs.PlainGitService
u, err := url.Parse(remoteAddr)
if err == nil && strings.EqualFold(u.Host, "github.com") {
gitServiceType = structs.GithubService
}
var opts = migrations.MigrateOptions{
RemoteURL: remoteAddr,
Name: form.RepoName,
Description: form.Description,
Private: form.Private || setting.Repository.ForcePrivate,
Mirror: form.Mirror,
AuthUsername: form.AuthUsername,
AuthPassword: form.AuthPassword,
Wiki: form.Wiki,
Issues: form.Issues,
Milestones: form.Milestones,
Labels: form.Labels,
Comments: true,
PullRequests: form.PullRequests,
Releases: form.Releases,
CloneAddr: remoteAddr,
RepoName: form.RepoName,
Description: form.Description,
Private: form.Private || setting.Repository.ForcePrivate,
Mirror: form.Mirror,
AuthUsername: form.AuthUsername,
AuthPassword: form.AuthPassword,
Wiki: form.Wiki,
Issues: form.Issues,
Milestones: form.Milestones,
Labels: form.Labels,
Comments: true,
PullRequests: form.PullRequests,
Releases: form.Releases,
GitServiceType: gitServiceType,
}
if opts.Mirror {
opts.Issues = false
+1 -1
View File
@@ -52,7 +52,7 @@ func ListUserRepos(ctx *context.APIContext) {
if ctx.Written() {
return
}
private := ctx.IsSigned && (ctx.User.ID == user.ID || ctx.User.IsAdmin)
private := ctx.IsSigned
listUserRepos(ctx, user, private)
}
+1 -1
View File
@@ -104,7 +104,7 @@ func GetInfo(ctx *context.APIContext) {
return
}
ctx.JSON(200, convert.ToUser(u, ctx.IsSigned, ctx.User.ID == u.ID || ctx.User.IsAdmin))
ctx.JSON(200, convert.ToUser(u, ctx.IsSigned, ctx.User != nil && (ctx.User.ID == u.ID || ctx.User.IsAdmin)))
}
// GetAuthenticatedUser get current user's information
+1 -1
View File
@@ -273,7 +273,7 @@ func ExploreOrganizations(ctx *context.Context) {
// ExploreCode render explore code page
func ExploreCode(ctx *context.Context) {
if !setting.Indexer.RepoIndexerEnabled {
ctx.Redirect("/explore", 302)
ctx.Redirect(setting.AppSubURL+"/explore", 302)
return
}
+17 -6
View File
@@ -18,8 +18,10 @@ import (
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/markup"
"code.gitea.io/gitea/modules/markup/external"
"code.gitea.io/gitea/modules/notification"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/ssh"
"code.gitea.io/gitea/modules/task"
"code.gitea.io/gitea/services/mailer"
mirror_service "code.gitea.io/gitea/services/mirror"
@@ -43,6 +45,7 @@ func NewServices() {
setting.NewServices()
mailer.NewContext()
_ = cache.NewContext()
notification.NewContext()
}
// In case of problems connecting to DB, retry connection. Eg, PGSQL in Docker Container on Synology
@@ -95,21 +98,29 @@ func GlobalInit() {
// Booting long running goroutines.
cron.NewContext()
if err := issue_indexer.InitIssueIndexer(false); err != nil {
log.Fatal("Failed to initialize issue indexer: %v", err)
}
issue_indexer.InitIssueIndexer(false)
models.InitRepoIndexer()
mirror_service.InitSyncMirrors()
models.InitDeliverHooks()
models.InitTestPullRequests()
if err := task.Init(); err != nil {
log.Fatal("Failed to initialize task scheduler: %v", err)
}
}
if setting.EnableSQLite3 {
log.Info("SQLite3 Supported")
}
checkRunMode()
if setting.InstallLock && setting.SSH.StartBuiltinServer {
ssh.Listen(setting.SSH.ListenHost, setting.SSH.ListenPort, setting.SSH.ServerCiphers, setting.SSH.ServerKeyExchanges, setting.SSH.ServerMACs)
log.Info("SSH server started on %s:%d. Cipher list (%v), key exchange algorithms (%v), MACs (%v)", setting.SSH.ListenHost, setting.SSH.ListenPort, setting.SSH.ServerCiphers, setting.SSH.ServerKeyExchanges, setting.SSH.ServerMACs)
// Now because Install will re-run GlobalInit once it has set InstallLock
// we can't tell if the ssh port will remain unused until that's done.
// However, see FIXME comment in install.go
if setting.InstallLock {
if setting.SSH.StartBuiltinServer {
ssh.Listen(setting.SSH.ListenHost, setting.SSH.ListenPort, setting.SSH.ServerCiphers, setting.SSH.ServerKeyExchanges, setting.SSH.ServerMACs)
log.Info("SSH server started on %s:%d. Cipher list (%v), key exchange algorithms (%v), MACs (%v)", setting.SSH.ListenHost, setting.SSH.ListenPort, setting.SSH.ServerCiphers, setting.SSH.ServerKeyExchanges, setting.SSH.ServerMACs)
} else {
ssh.Unused()
}
}
}
+7 -1
View File
@@ -20,9 +20,9 @@ import (
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/user"
"github.com/go-xorm/xorm"
"github.com/unknwon/com"
"gopkg.in/ini.v1"
"xorm.io/xorm"
)
const (
@@ -386,6 +386,12 @@ func InstallPost(ctx *context.Context, form auth.InstallForm) {
}
log.Info("First-time run install finished!")
// FIXME: This isn't really enough to completely take account of new configuration
// We should really be restarting:
// - On windows this is probably just a simple restart
// - On linux we can't just use graceful.RestartProcess() everything that was passed in on LISTEN_FDS
// (active or not) needs to be passed out and everything new passed out too.
// This means we need to prevent the cleanup goroutine from running prior to the second GlobalInit
ctx.Flash.Success(ctx.Tr("install.install_success"))
ctx.Redirect(form.AppURL + "user/login")
}
+5 -4
View File
@@ -47,10 +47,11 @@ func CreatePost(ctx *context.Context, form auth.CreateOrgForm) {
}
org := &models.User{
Name: form.OrgName,
IsActive: true,
Type: models.UserTypeOrganization,
Visibility: form.Visibility,
Name: form.OrgName,
IsActive: true,
Type: models.UserTypeOrganization,
Visibility: form.Visibility,
RepoAdminChangeTeamAccess: form.RepoAdminChangeTeamAccess,
}
if err := models.CreateOrganization(org, ctx.User); err != nil {
+7 -1
View File
@@ -33,6 +33,7 @@ func HookPreReceive(ctx *macaron.Context) {
gitAlternativeObjectDirectories := ctx.QueryTrim("gitAlternativeObjectDirectories")
gitQuarantinePath := ctx.QueryTrim("gitQuarantinePath")
prID := ctx.QueryInt64("prID")
isDeployKey := ctx.QueryBool("isDeployKey")
branchName := strings.TrimPrefix(refFullName, git.BranchPrefix)
repo, err := models.GetRepositoryByOwnerAndName(ownerName, repoName)
@@ -95,7 +96,12 @@ func HookPreReceive(ctx *macaron.Context) {
}
}
canPush := protectBranch.CanUserPush(userID)
canPush := false
if isDeployKey {
canPush = protectBranch.WhitelistDeployKeys
} else {
canPush = protectBranch.CanUserPush(userID)
}
if !canPush && prID > 0 {
pr, err := models.GetPullRequestByID(prID)
if err != nil {
+9
View File
@@ -119,6 +119,15 @@ func ServCommand(ctx *macaron.Context) {
repo.OwnerName = ownerName
results.RepoID = repo.ID
if repo.IsBeingCreated() {
ctx.JSON(http.StatusInternalServerError, map[string]interface{}{
"results": results,
"type": "InternalServerError",
"err": "Repository is being created, you could retry after it finished",
})
return
}
// 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{}{
+22
View File
@@ -63,3 +63,25 @@ func UploadAttachment(ctx *context.Context) {
"uuid": attach.UUID,
})
}
// DeleteAttachment response for deleting issue's attachment
func DeleteAttachment(ctx *context.Context) {
file := ctx.Query("file")
attach, err := models.GetAttachmentByUUID(file)
if !ctx.IsSigned || (ctx.User.ID != attach.UploaderID) {
ctx.Error(403)
return
}
if err != nil {
ctx.Error(400, err.Error())
return
}
err = models.DeleteAttachment(attach, true)
if err != nil {
ctx.Error(500, fmt.Sprintf("DeleteAttachment: %v", err))
return
}
ctx.JSON(200, map[string]string{
"uuid": attach.UUID,
})
}
+4
View File
@@ -28,6 +28,7 @@ type Branch struct {
Commit *git.Commit
IsProtected bool
IsDeleted bool
IsIncluded bool
DeletedBranch *models.DeletedBranch
CommitsAhead int
CommitsBehind int
@@ -203,10 +204,13 @@ func loadBranches(ctx *context.Context) []*Branch {
}
}
isIncluded := divergence.Ahead == 0 && ctx.Repo.Repository.DefaultBranch != branchName
branches[i] = &Branch{
Name: branchName,
Commit: commit,
IsProtected: isProtected,
IsIncluded: isIncluded,
CommitsAhead: divergence.Ahead,
CommitsBehind: divergence.Behind,
LatestPullRequest: pr,
+4 -2
View File
@@ -91,7 +91,9 @@ func Graph(ctx *context.Context) {
return
}
graph, err := models.GetCommitGraph(ctx.Repo.GitRepo)
page := ctx.QueryInt("page")
graph, err := models.GetCommitGraph(ctx.Repo.GitRepo, page)
if err != nil {
ctx.ServerError("GetCommitGraph", err)
return
@@ -103,8 +105,8 @@ func Graph(ctx *context.Context) {
ctx.Data["CommitCount"] = commitsCount
ctx.Data["Branch"] = ctx.Repo.BranchName
ctx.Data["RequireGitGraph"] = true
ctx.Data["Page"] = context.NewPagination(int(commitsCount), setting.UI.GraphMaxCommitNum, page, 5)
ctx.HTML(200, tplGraph)
}
// SearchCommits render commits filtered by keyword
+5
View File
@@ -84,6 +84,11 @@ func ServeBlobOrLFS(ctx *context.Context, blob *git.Blob) error {
if err != nil {
return err
}
defer func() {
if err = lfsDataRc.Close(); err != nil {
log.Error("ServeBlobOrLFS: Close: %v", err)
}
}()
return ServeData(ctx, ctx.Repo.TreePath, lfsDataRc)
}
+1
View File
@@ -263,6 +263,7 @@ func HTTP(ctx *context.Context) {
models.EnvPusherName + "=" + authUser.Name,
models.EnvPusherID + fmt.Sprintf("=%d", authUser.ID),
models.ProtectedBranchRepoID + fmt.Sprintf("=%d", repo.ID),
models.EnvIsDeployKey + "=false",
}
if !authUser.KeepEmailPrivate {
+143 -13
View File
@@ -34,6 +34,8 @@ import (
)
const (
tplAttachment base.TplName = "repo/issue/view_content/attachments"
tplIssues base.TplName = "repo/issue/list"
tplIssueNew base.TplName = "repo/issue/new"
tplIssueView base.TplName = "repo/issue/view"
@@ -501,21 +503,21 @@ func ValidateRepoMetas(ctx *context.Context, form auth.CreateIssueForm, isPull b
return nil, nil, 0
}
// Check if the passed assignees actually exists and has write access to the repo
// Check if the passed assignees actually exists and is assignable
for _, aID := range assigneeIDs {
user, err := models.GetUserByID(aID)
assignee, err := models.GetUserByID(aID)
if err != nil {
ctx.ServerError("GetUserByID", err)
return nil, nil, 0
}
perm, err := models.GetUserRepoPermission(repo, user)
valid, err := models.CanBeAssigned(assignee, repo, isPull)
if err != nil {
ctx.ServerError("GetUserRepoPermission", err)
ctx.ServerError("canBeAssigned", err)
return nil, nil, 0
}
if !perm.CanWriteIssuesOrPulls(isPull) {
ctx.ServerError("CanWriteIssuesOrPulls", fmt.Errorf("No permission for %s", user.Name))
if !valid {
ctx.ServerError("canBeAssigned", models.ErrUserDoesNotHaveAccessToRepo{UserID: aID, RepoName: repo.Name})
return nil, nil, 0
}
}
@@ -572,7 +574,7 @@ func NewIssuePost(ctx *context.Context, form auth.CreateIssueForm) {
Content: form.Content,
Ref: form.Ref,
}
if err := issue_service.NewIssue(repo, issue, labelIDs, assigneeIDs, attachments); err != nil {
if err := issue_service.NewIssue(repo, issue, labelIDs, attachments); err != nil {
if models.IsErrUserDoesNotHaveAccessToRepo(err) {
ctx.Error(400, "UserDoesNotHaveAccessToRepo", err.Error())
return
@@ -581,6 +583,11 @@ func NewIssuePost(ctx *context.Context, form auth.CreateIssueForm) {
return
}
if err := issue_service.AddAssignees(issue, ctx.User, assigneeIDs); err != nil {
log.Error("AddAssignees: %v", err)
ctx.Flash.Error(ctx.Tr("issues.assignee.error"))
}
notification.NotifyNewIssue(issue)
log.Trace("Issue created: %d/%d", repo.ID, issue.ID)
@@ -1044,7 +1051,7 @@ func UpdateIssueTitle(ctx *context.Context) {
}
oldTitle := issue.Title
if err := issue.ChangeTitle(ctx.User, title); err != nil {
if err := issue_service.ChangeTitle(issue, ctx.User, title); err != nil {
ctx.ServerError("ChangeTitle", err)
return
}
@@ -1074,8 +1081,14 @@ func UpdateIssueContent(ctx *context.Context) {
return
}
files := ctx.QueryStrings("files[]")
if err := updateAttachments(issue, files); err != nil {
ctx.ServerError("UpdateAttachments", err)
}
ctx.JSON(200, map[string]interface{}{
"content": string(markdown.Render([]byte(issue.Content), ctx.Query("context"), ctx.Repo.Repository.ComposeMetas())),
"content": string(markdown.Render([]byte(issue.Content), ctx.Query("context"), ctx.Repo.Repository.ComposeMetas())),
"attachments": attachmentsHTML(ctx, issue.Attachments),
})
}
@@ -1104,7 +1117,7 @@ func UpdateIssueMilestone(ctx *context.Context) {
})
}
// UpdateIssueAssignee change issue's assignee
// UpdateIssueAssignee change issue's or pull's assignee
func UpdateIssueAssignee(ctx *context.Context) {
issues := getActionIssues(ctx)
if ctx.Written() {
@@ -1122,10 +1135,29 @@ func UpdateIssueAssignee(ctx *context.Context) {
return
}
default:
if err := issue.ChangeAssignee(ctx.User, assigneeID); err != nil {
ctx.ServerError("ChangeAssignee", err)
assignee, err := models.GetUserByID(assigneeID)
if err != nil {
ctx.ServerError("GetUserByID", err)
return
}
valid, err := models.CanBeAssigned(assignee, issue.Repo, issue.IsPull)
if err != nil {
ctx.ServerError("canBeAssigned", err)
return
}
if !valid {
ctx.ServerError("canBeAssigned", models.ErrUserDoesNotHaveAccessToRepo{UserID: assigneeID, RepoName: issue.Repo.Name})
return
}
removed, comment, err := issue.ToggleAssignee(ctx.User, assigneeID)
if err != nil {
ctx.ServerError("ToggleAssignee", err)
return
}
notification.NotifyIssueChangeAssignee(ctx.User, issue, assignee, removed, comment)
}
}
ctx.JSON(200, map[string]interface{}{
@@ -1325,6 +1357,13 @@ func UpdateCommentContent(ctx *context.Context) {
return
}
if comment.Type == models.CommentTypeComment {
if err := comment.LoadAttachments(); err != nil {
ctx.ServerError("LoadAttachments", err)
return
}
}
if !ctx.IsSigned || (ctx.User.ID != comment.PosterID && !ctx.Repo.CanWriteIssuesOrPulls(comment.Issue.IsPull)) {
ctx.Error(403)
return
@@ -1346,10 +1385,16 @@ func UpdateCommentContent(ctx *context.Context) {
return
}
files := ctx.QueryStrings("files[]")
if err := updateAttachments(comment, files); err != nil {
ctx.ServerError("UpdateAttachments", err)
}
notification.NotifyUpdateComment(ctx.User, comment, oldContent)
ctx.JSON(200, map[string]interface{}{
"content": string(markdown.Render([]byte(comment.Content), ctx.Query("context"), ctx.Repo.Repository.ComposeMetas())),
"content": string(markdown.Render([]byte(comment.Content), ctx.Query("context"), ctx.Repo.Repository.ComposeMetas())),
"attachments": attachmentsHTML(ctx, comment.Attachments),
})
}
@@ -1603,3 +1648,88 @@ func filterXRefComments(ctx *context.Context, issue *models.Issue) error {
}
return nil
}
// GetIssueAttachments returns attachments for the issue
func GetIssueAttachments(ctx *context.Context) {
issue := GetActionIssue(ctx)
var attachments = make([]*api.Attachment, len(issue.Attachments))
for i := 0; i < len(issue.Attachments); i++ {
attachments[i] = issue.Attachments[i].APIFormat()
}
ctx.JSON(200, attachments)
}
// GetCommentAttachments returns attachments for the comment
func GetCommentAttachments(ctx *context.Context) {
comment, err := models.GetCommentByID(ctx.ParamsInt64(":id"))
if err != nil {
ctx.NotFoundOrServerError("GetCommentByID", models.IsErrCommentNotExist, err)
return
}
var attachments = make([]*api.Attachment, 0)
if comment.Type == models.CommentTypeComment {
if err := comment.LoadAttachments(); err != nil {
ctx.ServerError("LoadAttachments", err)
return
}
for i := 0; i < len(comment.Attachments); i++ {
attachments = append(attachments, comment.Attachments[i].APIFormat())
}
}
ctx.JSON(200, attachments)
}
func updateAttachments(item interface{}, files []string) error {
var attachments []*models.Attachment
switch content := item.(type) {
case *models.Issue:
attachments = content.Attachments
case *models.Comment:
attachments = content.Attachments
default:
return fmt.Errorf("Unknow Type")
}
for i := 0; i < len(attachments); i++ {
if util.IsStringInSlice(attachments[i].UUID, files) {
continue
}
if err := models.DeleteAttachment(attachments[i], true); err != nil {
return err
}
}
var err error
if len(files) > 0 {
switch content := item.(type) {
case *models.Issue:
err = content.UpdateAttachments(files)
case *models.Comment:
err = content.UpdateAttachments(files)
default:
return fmt.Errorf("Unknow Type")
}
if err != nil {
return err
}
}
switch content := item.(type) {
case *models.Issue:
content.Attachments, err = models.GetAttachmentsByIssueID(content.ID)
case *models.Comment:
content.Attachments, err = models.GetAttachmentsByCommentID(content.ID)
default:
return fmt.Errorf("Unknow Type")
}
return err
}
func attachmentsHTML(ctx *context.Context, attachments []*models.Attachment) string {
attachHTML, err := ctx.HTMLString(string(tplAttachment), map[string]interface{}{
"ctx": ctx.Data,
"Attachments": attachments,
})
if err != nil {
ctx.ServerError("attachmentsHTML.HTMLString", err)
return ""
}
return attachHTML
}
+4 -3
View File
@@ -10,6 +10,7 @@ import (
"code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/log"
issue_service "code.gitea.io/gitea/services/issue"
)
const (
@@ -132,7 +133,7 @@ func UpdateIssueLabel(ctx *context.Context) {
switch action := ctx.Query("action"); action {
case "clear":
for _, issue := range issues {
if err := issue.ClearLabels(ctx.User); err != nil {
if err := issue_service.ClearLabels(issue, ctx.User); err != nil {
ctx.ServerError("ClearLabels", err)
return
}
@@ -161,14 +162,14 @@ func UpdateIssueLabel(ctx *context.Context) {
if action == "attach" {
for _, issue := range issues {
if err = issue.AddLabel(ctx.User, label); err != nil {
if err = issue_service.AddLabel(issue, ctx.User, label); err != nil {
ctx.ServerError("AddLabel", err)
return
}
}
} else {
for _, issue := range issues {
if err = issue.RemoveLabel(ctx.User, label); err != nil {
if err = issue_service.RemoveLabel(issue, ctx.User, label); err != nil {
ctx.ServerError("RemoveLabel", err)
return
}
+27 -18
View File
@@ -24,6 +24,7 @@ import (
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/services/gitdiff"
issue_service "code.gitea.io/gitea/services/issue"
pull_service "code.gitea.io/gitea/services/pull"
"github.com/unknwon/com"
@@ -242,6 +243,10 @@ func checkPullInfo(ctx *context.Context) *models.Issue {
ctx.ServerError("LoadPoster", err)
return nil
}
if err := issue.LoadRepo(); err != nil {
ctx.ServerError("LoadRepo", err)
return nil
}
ctx.Data["Title"] = fmt.Sprintf("#%d - %s", issue.Index, issue.Title)
ctx.Data["Issue"] = issue
@@ -272,12 +277,12 @@ func checkPullInfo(ctx *context.Context) *models.Issue {
}
func setMergeTarget(ctx *context.Context, pull *models.PullRequest) {
if ctx.Repo.Owner.Name == pull.HeadUserName {
if ctx.Repo.Owner.Name == pull.MustHeadUserName() {
ctx.Data["HeadTarget"] = pull.HeadBranch
} else if pull.HeadRepo == nil {
ctx.Data["HeadTarget"] = pull.HeadUserName + ":" + pull.HeadBranch
ctx.Data["HeadTarget"] = pull.MustHeadUserName() + ":" + pull.HeadBranch
} else {
ctx.Data["HeadTarget"] = pull.HeadUserName + "/" + pull.HeadRepo.Name + ":" + pull.HeadBranch
ctx.Data["HeadTarget"] = pull.MustHeadUserName() + "/" + pull.HeadRepo.Name + ":" + pull.HeadBranch
}
ctx.Data["BaseTarget"] = pull.BaseBranch
}
@@ -440,7 +445,7 @@ func ViewPullCommits(ctx *context.Context) {
ctx.NotFound("ViewPullCommits", nil)
return
}
ctx.Data["Username"] = pull.HeadUserName
ctx.Data["Username"] = pull.MustHeadUserName()
ctx.Data["Reponame"] = pull.HeadRepo.Name
commits = prInfo.Commits
}
@@ -512,7 +517,7 @@ func ViewPullFiles(ctx *context.Context) {
return
}
headRepoPath := models.RepoPath(pull.HeadUserName, pull.HeadRepo.Name)
headRepoPath := pull.HeadRepo.RepoPath()
headGitRepo, err := git.OpenRepository(headRepoPath)
if err != nil {
@@ -531,8 +536,8 @@ func ViewPullFiles(ctx *context.Context) {
endCommitID = headCommitID
gitRepo = headGitRepo
headTarget = path.Join(pull.HeadUserName, pull.HeadRepo.Name)
ctx.Data["Username"] = pull.HeadUserName
headTarget = path.Join(pull.MustHeadUserName(), pull.HeadRepo.Name)
ctx.Data["Username"] = pull.MustHeadUserName()
ctx.Data["Reponame"] = pull.HeadRepo.Name
}
@@ -754,20 +759,19 @@ func CompareAndPullRequestPost(ctx *context.Context, form auth.CreateIssueForm)
Content: form.Content,
}
pullRequest := &models.PullRequest{
HeadRepoID: headRepo.ID,
BaseRepoID: repo.ID,
HeadUserName: headUser.Name,
HeadBranch: headBranch,
BaseBranch: baseBranch,
HeadRepo: headRepo,
BaseRepo: repo,
MergeBase: prInfo.MergeBase,
Type: models.PullRequestGitea,
HeadRepoID: headRepo.ID,
BaseRepoID: repo.ID,
HeadBranch: headBranch,
BaseBranch: baseBranch,
HeadRepo: headRepo,
BaseRepo: repo,
MergeBase: prInfo.MergeBase,
Type: models.PullRequestGitea,
}
// FIXME: check error in the case two people send pull request at almost same time, give nice error prompt
// instead of 500.
if err := pull_service.NewPullRequest(repo, pullIssue, labelIDs, attachments, pullRequest, patch, assigneeIDs); err != nil {
if err := pull_service.NewPullRequest(repo, pullIssue, labelIDs, attachments, pullRequest, patch); err != nil {
if models.IsErrUserDoesNotHaveAccessToRepo(err) {
ctx.Error(400, "UserDoesNotHaveAccessToRepo", err.Error())
return
@@ -779,6 +783,11 @@ func CompareAndPullRequestPost(ctx *context.Context, form auth.CreateIssueForm)
return
}
if err := issue_service.AddAssignees(pullIssue, ctx.User, assigneeIDs); err != nil {
log.Error("AddAssignees: %v", err)
ctx.Flash.Error(ctx.Tr("issues.assignee.error"))
}
notification.NotifyNewPullRequest(pullRequest)
log.Trace("Pull request created: %d/%d", repo.ID, pullIssue.ID)
@@ -820,7 +829,7 @@ func TriggerTask(ctx *context.Context) {
log.Trace("TriggerTask '%s/%s' by %s", repo.Name, branch, pusher.Name)
go models.HookQueue.Add(repo.ID)
go models.AddTestPullRequestTask(pusher, repo.ID, branch, true)
go pull_service.AddTestPullRequestTask(pusher, repo.ID, branch, true)
ctx.Status(202)
}
+2 -2
View File
@@ -157,7 +157,7 @@ func SubmitReview(ctx *context.Context, form auth.SubmitReviewForm) {
return
}
// No current review. Create a new one!
if review, err = models.CreateReview(models.CreateReviewOptions{
if review, err = pull_service.CreateReview(models.CreateReviewOptions{
Type: reviewType,
Issue: issue,
Reviewer: ctx.User,
@@ -169,7 +169,7 @@ func SubmitReview(ctx *context.Context, form auth.SubmitReviewForm) {
} else {
review.Content = form.Content
review.Type = reviewType
if err = models.UpdateReview(review); err != nil {
if err = pull_service.UpdateReview(review); err != nil {
ctx.ServerError("UpdateReview", err)
return
}
+62 -41
View File
@@ -19,6 +19,7 @@ import (
"code.gitea.io/gitea/modules/migrations"
"code.gitea.io/gitea/modules/notification"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/task"
"code.gitea.io/gitea/modules/util"
"github.com/unknwon/com"
@@ -133,8 +134,6 @@ func Create(ctx *context.Context) {
func handleCreateError(ctx *context.Context, owner *models.User, err error, name string, tpl base.TplName, form interface{}) {
switch {
case migrations.IsRateLimitError(err):
ctx.RenderWithErr(ctx.Tr("form.visit_rate_limit"), tpl, form)
case models.IsErrReachLimitOfRepo(err):
ctx.RenderWithErr(ctx.Tr("repo.form.reach_limit_of_creation", owner.MaxCreationLimit()), tpl, form)
case models.IsErrRepoAlreadyExist(err):
@@ -221,6 +220,40 @@ func Migrate(ctx *context.Context) {
ctx.HTML(200, tplMigrate)
}
func handleMigrateError(ctx *context.Context, owner *models.User, err error, name string, tpl base.TplName, form *auth.MigrateRepoForm) {
switch {
case migrations.IsRateLimitError(err):
ctx.RenderWithErr(ctx.Tr("form.visit_rate_limit"), tpl, form)
case migrations.IsTwoFactorAuthError(err):
ctx.RenderWithErr(ctx.Tr("form.2fa_auth_required"), tpl, form)
case models.IsErrReachLimitOfRepo(err):
ctx.RenderWithErr(ctx.Tr("repo.form.reach_limit_of_creation", owner.MaxCreationLimit()), tpl, form)
case models.IsErrRepoAlreadyExist(err):
ctx.Data["Err_RepoName"] = true
ctx.RenderWithErr(ctx.Tr("form.repo_name_been_taken"), tpl, form)
case models.IsErrNameReserved(err):
ctx.Data["Err_RepoName"] = true
ctx.RenderWithErr(ctx.Tr("repo.form.name_reserved", err.(models.ErrNameReserved).Name), tpl, form)
case models.IsErrNamePatternNotAllowed(err):
ctx.Data["Err_RepoName"] = true
ctx.RenderWithErr(ctx.Tr("repo.form.name_pattern_not_allowed", err.(models.ErrNamePatternNotAllowed).Pattern), tpl, form)
default:
remoteAddr, _ := form.ParseRemoteAddr(owner)
err = util.URLSanitizedError(err, remoteAddr)
if strings.Contains(err.Error(), "Authentication failed") ||
strings.Contains(err.Error(), "Bad credentials") ||
strings.Contains(err.Error(), "could not read Username") {
ctx.Data["Err_Auth"] = true
ctx.RenderWithErr(ctx.Tr("form.auth_failed", err.Error()), tpl, form)
} else if strings.Contains(err.Error(), "fatal:") {
ctx.Data["Err_CloneAddr"] = true
ctx.RenderWithErr(ctx.Tr("repo.migrate.failed", err.Error()), tpl, form)
} else {
ctx.ServerError(name, err)
}
}
}
// MigratePost response for migrating from external git repository
func MigratePost(ctx *context.Context, form auth.MigrateRepoForm) {
ctx.Data["Title"] = ctx.Tr("new_migrate")
@@ -258,8 +291,8 @@ func MigratePost(ctx *context.Context, form auth.MigrateRepoForm) {
}
var opts = migrations.MigrateOptions{
RemoteURL: remoteAddr,
Name: form.RepoName,
CloneAddr: remoteAddr,
RepoName: form.RepoName,
Description: form.Description,
Private: form.Private || setting.Repository.ForcePrivate,
Mirror: form.Mirror,
@@ -282,47 +315,19 @@ func MigratePost(ctx *context.Context, form auth.MigrateRepoForm) {
opts.Releases = false
}
repo, err := migrations.MigrateRepository(ctx.User, ctxUser.Name, opts)
if err == nil {
notification.NotifyCreateRepository(ctx.User, ctxUser, repo)
log.Trace("Repository migrated [%d]: %s/%s successfully", repo.ID, ctxUser.Name, form.RepoName)
ctx.Redirect(setting.AppSubURL + "/" + ctxUser.Name + "/" + form.RepoName)
err = models.CheckCreateRepository(ctx.User, ctxUser, opts.RepoName)
if err != nil {
handleMigrateError(ctx, ctxUser, err, "MigratePost", tplMigrate, &form)
return
}
switch {
case models.IsErrReachLimitOfRepo(err):
ctx.RenderWithErr(ctx.Tr("repo.form.reach_limit_of_creation", ctxUser.MaxCreationLimit()), tplMigrate, &form)
case models.IsErrNameReserved(err):
ctx.Data["Err_RepoName"] = true
ctx.RenderWithErr(ctx.Tr("repo.form.name_reserved", err.(models.ErrNameReserved).Name), tplMigrate, &form)
case models.IsErrRepoAlreadyExist(err):
ctx.Data["Err_RepoName"] = true
ctx.RenderWithErr(ctx.Tr("form.repo_name_been_taken"), tplMigrate, &form)
case models.IsErrNamePatternNotAllowed(err):
ctx.Data["Err_RepoName"] = true
ctx.RenderWithErr(ctx.Tr("repo.form.name_pattern_not_allowed", err.(models.ErrNamePatternNotAllowed).Pattern), tplMigrate, &form)
case migrations.IsRateLimitError(err):
ctx.RenderWithErr(ctx.Tr("form.visit_rate_limit"), tplMigrate, &form)
case migrations.IsTwoFactorAuthError(err):
ctx.Data["Err_Auth"] = true
ctx.RenderWithErr(ctx.Tr("form.2fa_auth_required"), tplMigrate, &form)
default:
// remoteAddr may contain credentials, so we sanitize it
err = util.URLSanitizedError(err, remoteAddr)
if strings.Contains(err.Error(), "Authentication failed") ||
strings.Contains(err.Error(), "Bad credentials") ||
strings.Contains(err.Error(), "could not read Username") {
ctx.Data["Err_Auth"] = true
ctx.RenderWithErr(ctx.Tr("form.auth_failed", err.Error()), tplMigrate, &form)
} else if strings.Contains(err.Error(), "fatal:") {
ctx.Data["Err_CloneAddr"] = true
ctx.RenderWithErr(ctx.Tr("repo.migrate.failed", err.Error()), tplMigrate, &form)
} else {
ctx.ServerError("MigratePost", err)
}
err = task.MigrateRepository(ctx.User, ctxUser, opts)
if err == nil {
ctx.Redirect(setting.AppSubURL + "/" + ctxUser.Name + "/" + opts.RepoName)
return
}
handleMigrateError(ctx, ctxUser, err, "MigratePost", tplMigrate, &form)
}
// Action response for actions to a repository
@@ -460,3 +465,19 @@ func Download(ctx *context.Context) {
ctx.ServeFile(archivePath, ctx.Repo.Repository.Name+"-"+refName+ext)
}
// Status returns repository's status
func Status(ctx *context.Context) {
task, err := models.GetMigratingTask(ctx.Repo.Repository.ID)
if err != nil {
ctx.JSON(500, map[string]interface{}{
"err": err,
})
return
}
ctx.JSON(200, map[string]interface{}{
"status": ctx.Repo.Repository.Status,
"err": task.Errors,
})
}
+3 -2
View File
@@ -117,9 +117,9 @@ func SettingsProtectedBranch(c *context.Context) {
}
}
users, err := c.Repo.Repository.GetWriters()
users, err := c.Repo.Repository.GetReaders()
if err != nil {
c.ServerError("Repo.Repository.GetWriters", err)
c.ServerError("Repo.Repository.GetReaders", err)
return
}
c.Data["Users"] = users
@@ -213,6 +213,7 @@ func SettingsProtectedBranchPost(ctx *context.Context, f auth.ProtectBranchForm)
protectBranch.EnableStatusCheck = f.EnableStatusCheck
protectBranch.StatusCheckContexts = f.StatusCheckContexts
protectBranch.WhitelistDeployKeys = f.WhitelistDeployKeys
protectBranch.RequiredApprovals = f.RequiredApprovals
if strings.TrimSpace(f.ApprovalsWhitelistUsers) != "" {
+46
View File
@@ -11,6 +11,7 @@ import (
"fmt"
gotemplate "html/template"
"io/ioutil"
"net/url"
"path"
"strings"
@@ -31,6 +32,7 @@ const (
tplRepoHome base.TplName = "repo/home"
tplWatchers base.TplName = "repo/watchers"
tplForks base.TplName = "repo/forks"
tplMigrating base.TplName = "repo/migrating"
)
func renderDirectory(ctx *context.Context, treeLink string) {
@@ -304,6 +306,8 @@ func renderFile(ctx *context.Context, entry *git.TreeEntry, treeLink, rawLink st
var output bytes.Buffer
lines := strings.Split(fileContent, "\n")
ctx.Data["NumLines"] = len(lines)
//Remove blank line at the end of file
if len(lines) > 0 && lines[len(lines)-1] == "" {
lines = lines[:len(lines)-1]
@@ -342,6 +346,20 @@ func renderFile(ctx *context.Context, entry *git.TreeEntry, treeLink, rawLink st
ctx.Data["IsAudioFile"] = true
case base.IsImageFile(buf):
ctx.Data["IsImageFile"] = true
default:
if fileSize >= setting.UI.MaxDisplayFileSize {
ctx.Data["IsFileTooLarge"] = true
break
}
if markupType := markup.Type(blob.Name()); markupType != "" {
d, _ := ioutil.ReadAll(dataRc)
buf = append(buf, d...)
ctx.Data["IsMarkup"] = true
ctx.Data["MarkupType"] = markupType
ctx.Data["FileContent"] = string(markup.Render(blob.Name(), buf, path.Dir(treeLink), ctx.Repo.Repository.ComposeMetas()))
}
}
if ctx.Repo.CanEnableEditor() {
@@ -354,9 +372,37 @@ func renderFile(ctx *context.Context, entry *git.TreeEntry, treeLink, rawLink st
}
}
func safeURL(address string) string {
u, err := url.Parse(address)
if err != nil {
return address
}
u.User = nil
return u.String()
}
// Home render repository home page
func Home(ctx *context.Context) {
if len(ctx.Repo.Units) > 0 {
if ctx.Repo.Repository.IsBeingCreated() {
task, err := models.GetMigratingTask(ctx.Repo.Repository.ID)
if err != nil {
ctx.ServerError("models.GetMigratingTask", err)
return
}
cfg, err := task.MigrateConfig()
if err != nil {
ctx.ServerError("task.MigrateConfig", err)
return
}
ctx.Data["Repo"] = ctx.Repo
ctx.Data["MigrateTask"] = task
ctx.Data["CloneAddr"] = safeURL(cfg.CloneAddr)
ctx.HTML(200, tplMigrating)
return
}
var firstUnit *models.Unit
for _, repoUnit := range ctx.Repo.Units {
if repoUnit.Type == models.UnitTypeCode {
+12
View File
@@ -8,6 +8,7 @@ package repo
import (
"fmt"
"io/ioutil"
"net/url"
"path/filepath"
"strings"
@@ -68,11 +69,22 @@ func findEntryForFile(commit *git.Commit, target string) (*git.TreeEntry, error)
if err != nil {
return nil, err
}
// The longest name should be checked first
for _, entry := range entries {
if entry.IsRegular() && entry.Name() == target {
return entry, nil
}
}
// Then the unescaped, shortest alternative
var unescapedTarget string
if unescapedTarget, err = url.QueryUnescape(target); err != nil {
return nil, err
}
for _, entry := range entries {
if entry.IsRegular() && entry.Name() == unescapedTarget {
return entry, nil
}
}
return nil, nil
}
+11 -6
View File
@@ -139,14 +139,14 @@ func NewMacaron() *macaron.Macaron {
m.Use(public.Custom(
&public.Options{
SkipLogging: setting.DisableRouterLog,
ExpiresAfter: time.Hour * 6,
ExpiresAfter: setting.StaticCacheTime,
},
))
m.Use(public.Static(
&public.Options{
Directory: path.Join(setting.StaticRootPath, "public"),
SkipLogging: setting.DisableRouterLog,
ExpiresAfter: time.Hour * 6,
ExpiresAfter: setting.StaticCacheTime,
},
))
m.Use(public.StaticHandler(
@@ -154,7 +154,7 @@ func NewMacaron() *macaron.Macaron {
&public.Options{
Prefix: "avatars",
SkipLogging: setting.DisableRouterLog,
ExpiresAfter: time.Hour * 6,
ExpiresAfter: setting.StaticCacheTime,
},
))
m.Use(public.StaticHandler(
@@ -162,7 +162,7 @@ func NewMacaron() *macaron.Macaron {
&public.Options{
Prefix: "repo-avatars",
SkipLogging: setting.DisableRouterLog,
ExpiresAfter: time.Hour * 6,
ExpiresAfter: setting.StaticCacheTime,
},
))
@@ -513,8 +513,9 @@ func RegisterRoutes(m *macaron.Macaron) {
})
}, ignSignIn)
m.Group("", func() {
m.Post("/attachments", repo.UploadAttachment)
m.Group("/attachments", func() {
m.Post("", repo.UploadAttachment)
m.Post("/delete", repo.DeleteAttachment)
}, reqSignIn)
m.Group("/:username", func() {
@@ -710,6 +711,7 @@ func RegisterRoutes(m *macaron.Macaron) {
m.Post("/reactions/:action", bindIgnErr(auth.ReactionForm{}), repo.ChangeIssueReaction)
m.Post("/lock", reqRepoIssueWriter, bindIgnErr(auth.IssueLockForm{}), repo.LockIssue)
m.Post("/unlock", reqRepoIssueWriter, repo.UnlockIssue)
m.Get("/attachments", repo.GetIssueAttachments)
}, context.RepoMustNotBeArchived())
m.Post("/labels", reqRepoIssuesOrPullsWriter, repo.UpdateIssueLabel)
@@ -721,6 +723,7 @@ func RegisterRoutes(m *macaron.Macaron) {
m.Post("", repo.UpdateCommentContent)
m.Post("/delete", repo.DeleteComment)
m.Post("/reactions/:action", bindIgnErr(auth.ReactionForm{}), repo.ChangeCommentReaction)
m.Get("/attachments", repo.GetCommentAttachments)
}, context.RepoMustNotBeArchived())
m.Group("/labels", func() {
m.Post("/new", bindIgnErr(auth.CreateLabelForm{}), repo.NewLabel)
@@ -845,6 +848,8 @@ func RegisterRoutes(m *macaron.Macaron) {
m.Get("/archive/*", repo.MustBeNotEmpty, reqRepoCodeReader, repo.Download)
m.Get("/status", reqRepoCodeReader, repo.Status)
m.Group("/branches", func() {
m.Get("", repo.Branches)
}, repo.MustBeNotEmpty, context.RepoRef(), reqRepoCodeReader)
+60 -45
View File
@@ -17,10 +17,12 @@ import (
"code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/password"
"code.gitea.io/gitea/modules/recaptcha"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/timeutil"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/services/externalaccount"
"code.gitea.io/gitea/services/mailer"
"gitea.com/macaron/captcha"
@@ -277,7 +279,7 @@ func TwoFactorPost(ctx *context.Context, form auth.TwoFactorAuthForm) {
return
}
err = models.LinkAccountToUser(u, gothUser.(goth.User))
err = externalaccount.LinkAccountToUser(u, gothUser.(goth.User))
if err != nil {
ctx.ServerError("UserSignIn", err)
return
@@ -452,7 +454,7 @@ func U2FSign(ctx *context.Context, signResp u2f.SignResponse) {
return
}
err = models.LinkAccountToUser(user, gothUser.(goth.User))
err = externalaccount.LinkAccountToUser(user, gothUser.(goth.User))
if err != nil {
ctx.ServerError("UserSignIn", err)
return
@@ -601,36 +603,42 @@ func handleOAuth2SignIn(u *models.User, gothUser goth.User, ctx *context.Context
// Instead, redirect them to the 2FA authentication page.
_, err = models.GetTwoFactorByUID(u.ID)
if err != nil {
if models.IsErrTwoFactorNotEnrolled(err) {
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.Domain, setting.SessionConfig.Secure, true)
// Register last login
u.SetLastLogin()
if err := models.UpdateUserCols(u, "last_login_unix"); err != nil {
ctx.ServerError("UpdateUserCols", err)
return
}
if redirectTo := ctx.GetCookie("redirect_to"); len(redirectTo) > 0 {
ctx.SetCookie("redirect_to", "", -1, setting.AppSubURL, "", setting.SessionConfig.Secure, true)
ctx.RedirectToFirst(redirectTo)
return
}
ctx.Redirect(setting.AppSubURL + "/")
} else {
if !models.IsErrTwoFactorNotEnrolled(err) {
ctx.ServerError("UserSignIn", err)
return
}
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.Domain, setting.SessionConfig.Secure, true)
// Register last login
u.SetLastLogin()
if err := models.UpdateUserCols(u, "last_login_unix"); err != nil {
ctx.ServerError("UpdateUserCols", err)
return
}
// update external user information
if err := models.UpdateExternalUser(u, gothUser); err != nil {
log.Error("UpdateExternalUser failed: %v", err)
}
if redirectTo := ctx.GetCookie("redirect_to"); len(redirectTo) > 0 {
ctx.SetCookie("redirect_to", "", -1, setting.AppSubURL, "", setting.SessionConfig.Secure, true)
ctx.RedirectToFirst(redirectTo)
return
}
ctx.Redirect(setting.AppSubURL + "/")
return
}
@@ -675,7 +683,7 @@ func oAuth2UserLoginCallback(loginSource *models.LoginSource, request *http.Requ
}
if hasUser {
return user, goth.User{}, nil
return user, gothUser, nil
}
// search in external linked users
@@ -689,7 +697,7 @@ func oAuth2UserLoginCallback(loginSource *models.LoginSource, request *http.Requ
}
if hasUser {
user, err = models.GetUserByID(externalLoginUser.UserID)
return user, goth.User{}, err
return user, gothUser, err
}
// no user found to login
@@ -789,16 +797,18 @@ func LinkAccountPostSignIn(ctx *context.Context, signInForm auth.SignInForm) {
// Instead, redirect them to the 2FA authentication page.
_, err = models.GetTwoFactorByUID(u.ID)
if err != nil {
if models.IsErrTwoFactorNotEnrolled(err) {
err = models.LinkAccountToUser(u, gothUser.(goth.User))
if err != nil {
ctx.ServerError("UserLinkAccount", err)
} else {
handleSignIn(ctx, u, signInForm.Remember)
}
} else {
if !models.IsErrTwoFactorNotEnrolled(err) {
ctx.ServerError("UserLinkAccount", err)
return
}
err = externalaccount.LinkAccountToUser(u, gothUser.(goth.User))
if err != nil {
ctx.ServerError("UserLinkAccount", err)
return
}
handleSignIn(ctx, u, signInForm.Remember)
return
}
@@ -947,6 +957,11 @@ func LinkAccountPostRegister(ctx *context.Context, cpt *captcha.Captcha, form au
}
}
// update external user information
if err := models.UpdateExternalUser(u, gothUser.(goth.User)); err != nil {
log.Error("UpdateExternalUser failed: %v", err)
}
// Send confirmation email
if setting.Service.RegisterEmailConfirm && u.ID > 1 {
mailer.SendActivateAccountMail(ctx.Locale, u)
@@ -1320,6 +1335,11 @@ func ResetPasswdPost(ctx *context.Context) {
ctx.Data["Err_Password"] = true
ctx.RenderWithErr(ctx.Tr("auth.password_too_short", setting.MinPasswordLength), tplResetPassword, nil)
return
} else if !password.IsComplexEnough(passwd) {
ctx.Data["IsResetForm"] = true
ctx.Data["Err_Password"] = true
ctx.RenderWithErr(ctx.Tr("form.password_complexity"), tplResetPassword, nil)
return
}
var err error
@@ -1350,7 +1370,6 @@ func ResetPasswdPost(ctx *context.Context) {
func MustChangePassword(ctx *context.Context) {
ctx.Data["Title"] = ctx.Tr("auth.must_change_password")
ctx.Data["ChangePasscodeLink"] = setting.AppSubURL + "/user/settings/change_password"
ctx.HTML(200, tplMustChangePassword)
}
@@ -1358,16 +1377,12 @@ func MustChangePassword(ctx *context.Context) {
// account was created by an admin
func MustChangePasswordPost(ctx *context.Context, cpt *captcha.Captcha, form auth.MustChangePasswordForm) {
ctx.Data["Title"] = ctx.Tr("auth.must_change_password")
ctx.Data["ChangePasscodeLink"] = setting.AppSubURL + "/user/settings/change_password"
if ctx.HasError() {
ctx.HTML(200, tplMustChangePassword)
return
}
u := ctx.User
// Make sure only requests for users who are eligible to change their password via
// this method passes through
if !u.MustChangePassword {
+4 -1
View File
@@ -13,6 +13,7 @@ import (
"code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/password"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/timeutil"
"code.gitea.io/gitea/services/mailer"
@@ -27,7 +28,6 @@ func Account(ctx *context.Context) {
ctx.Data["Title"] = ctx.Tr("settings")
ctx.Data["PageIsSettingsAccount"] = true
ctx.Data["Email"] = ctx.User.Email
ctx.Data["EmailNotificationsPreference"] = ctx.User.EmailNotifications()
loadAccountData(ctx)
@@ -52,6 +52,8 @@ func AccountPost(ctx *context.Context, form auth.ChangePasswordForm) {
ctx.Flash.Error(ctx.Tr("settings.password_incorrect"))
} else if form.Password != form.Retype {
ctx.Flash.Error(ctx.Tr("form.password_not_match"))
} else if !password.IsComplexEnough(form.Password) {
ctx.Flash.Error(ctx.Tr("form.password_complexity"))
} else {
var err error
if ctx.User.Salt, err = models.GetUserSalt(); err != nil {
@@ -227,4 +229,5 @@ func loadAccountData(ctx *context.Context) {
return
}
ctx.Data["Emails"] = emails
ctx.Data["EmailNotificationsPreference"] = ctx.User.EmailNotifications()
}
+49 -20
View File
@@ -19,36 +19,65 @@ import (
func TestChangePassword(t *testing.T) {
oldPassword := "password"
setting.MinPasswordLength = 6
var pcALL = []string{"lower", "upper", "digit", "spec"}
var pcLUN = []string{"lower", "upper", "digit"}
var pcLU = []string{"lower", "upper"}
for _, req := range []struct {
OldPassword string
NewPassword string
Retype string
Message string
OldPassword string
NewPassword string
Retype string
Message string
PasswordComplexity []string
}{
{
OldPassword: oldPassword,
NewPassword: "123456",
Retype: "123456",
Message: "",
OldPassword: oldPassword,
NewPassword: "Qwerty123456-",
Retype: "Qwerty123456-",
Message: "",
PasswordComplexity: pcALL,
},
{
OldPassword: oldPassword,
NewPassword: "12345",
Retype: "12345",
Message: "auth.password_too_short",
OldPassword: oldPassword,
NewPassword: "12345",
Retype: "12345",
Message: "auth.password_too_short",
PasswordComplexity: pcALL,
},
{
OldPassword: "12334",
NewPassword: "123456",
Retype: "123456",
Message: "settings.password_incorrect",
OldPassword: "12334",
NewPassword: "123456",
Retype: "123456",
Message: "settings.password_incorrect",
PasswordComplexity: pcALL,
},
{
OldPassword: oldPassword,
NewPassword: "123456",
Retype: "12345",
Message: "form.password_not_match",
OldPassword: oldPassword,
NewPassword: "123456",
Retype: "12345",
Message: "form.password_not_match",
PasswordComplexity: pcALL,
},
{
OldPassword: oldPassword,
NewPassword: "Qwerty",
Retype: "Qwerty",
Message: "form.password_complexity",
PasswordComplexity: pcALL,
},
{
OldPassword: oldPassword,
NewPassword: "Qwerty",
Retype: "Qwerty",
Message: "form.password_complexity",
PasswordComplexity: pcLUN,
},
{
OldPassword: oldPassword,
NewPassword: "QWERTY",
Retype: "QWERTY",
Message: "form.password_complexity",
PasswordComplexity: pcLU,
},
} {
models.PrepareTestEnv(t)