Merge branch 'master' into feat/approval-new

# Conflicts:
#	models/issue_comment.go
#	models/migrations/migrations.go
#	models/migrations/v64.go
#	models/models.go
#	public/css/index.css
This commit is contained in:
Jonas Franz
2018-05-13 22:14:34 +02:00
95 changed files with 4325 additions and 927 deletions
+31 -34
View File
@@ -178,25 +178,22 @@ func CreateIssue(ctx *context.APIContext, form api.CreateIssueOption) {
DeadlineUnix: deadlineUnix,
}
if ctx.Repo.IsWriter() {
if len(form.Assignee) > 0 {
assignee, err := models.GetUserByName(form.Assignee)
if err != nil {
if models.IsErrUserNotExist(err) {
ctx.Error(422, "", fmt.Sprintf("Assignee does not exist: [name: %s]", form.Assignee))
} else {
ctx.Error(500, "GetUserByName", err)
}
return
}
issue.AssigneeID = assignee.ID
// Get all assignee IDs
assigneeIDs, err := models.MakeIDsFromAPIAssigneesToAdd(form.Assignee, form.Assignees)
if err != nil {
if models.IsErrUserNotExist(err) {
ctx.Error(422, "", fmt.Sprintf("Assignee does not exist: [name: %s]", err))
} else {
ctx.Error(500, "AddAssigneeByName", err)
}
issue.MilestoneID = form.Milestone
} else {
form.Labels = nil
return
}
if err := models.NewIssue(ctx.Repo.Repository, issue, form.Labels, nil); err != nil {
if err := models.NewIssue(ctx.Repo.Repository, issue, form.Labels, assigneeIDs, nil); err != nil {
if models.IsErrUserDoesNotHaveAccessToRepo(err) {
ctx.Error(400, "UserDoesNotHaveAccessToRepo", err)
return
}
ctx.Error(500, "NewIssue", err)
return
}
@@ -209,7 +206,6 @@ func CreateIssue(ctx *context.APIContext, form api.CreateIssueOption) {
}
// Refetch from database to assign some automatic values
var err error
issue, err = models.GetIssueByID(issue.ID)
if err != nil {
ctx.Error(500, "GetIssueByID", err)
@@ -272,6 +268,7 @@ func EditIssue(ctx *context.APIContext, form api.EditIssueOption) {
issue.Content = *form.Body
}
// Update the deadline
var deadlineUnix util.TimeStamp
if form.Deadline != nil && !form.Deadline.IsZero() {
deadlineUnix = util.TimeStamp(form.Deadline.Unix())
@@ -282,28 +279,28 @@ func EditIssue(ctx *context.APIContext, form api.EditIssueOption) {
return
}
if ctx.Repo.IsWriter() && form.Assignee != nil &&
(issue.Assignee == nil || issue.Assignee.LowerName != strings.ToLower(*form.Assignee)) {
if len(*form.Assignee) == 0 {
issue.AssigneeID = 0
} else {
assignee, err := models.GetUserByName(*form.Assignee)
if err != nil {
if models.IsErrUserNotExist(err) {
ctx.Error(422, "", fmt.Sprintf("assignee does not exist: [name: %s]", *form.Assignee))
} else {
ctx.Error(500, "GetUserByName", err)
}
return
}
issue.AssigneeID = assignee.ID
// Add/delete assignees
// Deleting is done the Github way (quote from their api documentation):
// https://developer.github.com/v3/issues/#edit-an-issue
// "assignees" (array): Logins for Users to assign to this issue.
// Pass one or more user logins to replace the set of assignees on this Issue.
// Send an empty array ([]) to clear all assignees from the Issue.
if ctx.Repo.IsWriter() && (form.Assignees != nil || form.Assignee != nil) {
oneAssignee := ""
if form.Assignee != nil {
oneAssignee = *form.Assignee
}
if err = models.UpdateIssueUserByAssignee(issue); err != nil {
ctx.Error(500, "UpdateIssueUserByAssignee", err)
err = models.UpdateAPIAssignee(issue, oneAssignee, form.Assignees, ctx.User)
if err != nil {
ctx.Error(500, "UpdateAPIAssignee", err)
return
}
}
if ctx.Repo.IsWriter() && form.Milestone != nil &&
issue.MilestoneID != *form.Milestone {
oldMilestoneID := issue.MilestoneID
+1 -1
View File
@@ -15,7 +15,7 @@ import (
// ListIssueComments list all the comments of an issue
func ListIssueComments(ctx *context.APIContext) {
// swagger:operation GET /repos/{owner}/{repo}/issue/{index}/comments issue issueGetComments
// swagger:operation GET /repos/{owner}/{repo}/issues/{index}/comments issue issueGetComments
// ---
// summary: List all comments on an issue
// produces:
+4 -4
View File
@@ -58,7 +58,7 @@ func ListIssueLabels(ctx *context.APIContext) {
// AddIssueLabels add labels for an issue
func AddIssueLabels(ctx *context.APIContext, form api.IssueLabelsOption) {
// swagger:operation POST /repos/{owner}/{repo}/issue/{index}/labels issue issueAddLabel
// swagger:operation POST /repos/{owner}/{repo}/issues/{index}/labels issue issueAddLabel
// ---
// summary: Add a label to an issue
// consumes:
@@ -129,7 +129,7 @@ func AddIssueLabels(ctx *context.APIContext, form api.IssueLabelsOption) {
// DeleteIssueLabel delete a label for an issue
func DeleteIssueLabel(ctx *context.APIContext) {
// swagger:operation DELETE /repos/{owner}/{repo}/issue/{index}/labels/{id} issue issueRemoveLabel
// swagger:operation DELETE /repos/{owner}/{repo}/issues/{index}/labels/{id} issue issueRemoveLabel
// ---
// summary: Remove a label from an issue
// produces:
@@ -193,7 +193,7 @@ func DeleteIssueLabel(ctx *context.APIContext) {
// ReplaceIssueLabels replace labels for an issue
func ReplaceIssueLabels(ctx *context.APIContext, form api.IssueLabelsOption) {
// swagger:operation PUT /repos/{owner}/{repo}/issue/{index}/labels issue issueReplaceLabels
// swagger:operation PUT /repos/{owner}/{repo}/issues/{index}/labels issue issueReplaceLabels
// ---
// summary: Replace an issue's labels
// consumes:
@@ -264,7 +264,7 @@ func ReplaceIssueLabels(ctx *context.APIContext, form api.IssueLabelsOption) {
// ClearIssueLabels delete all the labels for an issue
func ClearIssueLabels(ctx *context.APIContext) {
// swagger:operation DELETE /repos/{owner}/{repo}/issue/{index}/labels issue issueClearLabels
// swagger:operation DELETE /repos/{owner}/{repo}/issues/{index}/labels issue issueClearLabels
// ---
// summary: Remove all labels from an issue
// produces:
+34 -39
View File
@@ -211,26 +211,6 @@ func CreatePullRequest(ctx *context.APIContext, form api.CreatePullRequestOption
milestoneID = milestone.ID
}
if len(form.Assignee) > 0 {
assigneeUser, err := models.GetUserByName(form.Assignee)
if err != nil {
if models.IsErrUserNotExist(err) {
ctx.Error(422, "", fmt.Sprintf("assignee does not exist: [name: %s]", form.Assignee))
} else {
ctx.Error(500, "GetUserByName", err)
}
return
}
assignee, err := repo.GetAssigneeByID(assigneeUser.ID)
if err != nil {
ctx.Error(500, "GetAssigneeByID", err)
return
}
assigneeID = assignee.ID
}
patch, err := headGitRepo.GetPatch(prInfo.MergeBase, headBranch)
if err != nil {
ctx.Error(500, "GetPatch", err)
@@ -266,7 +246,22 @@ func CreatePullRequest(ctx *context.APIContext, form api.CreatePullRequestOption
Type: models.PullRequestGitea,
}
if err := models.NewPullRequest(repo, prIssue, labelIDs, []string{}, pr, patch); err != nil {
// Get all assignee IDs
assigneeIDs, err := models.MakeIDsFromAPIAssigneesToAdd(form.Assignee, form.Assignees)
if err != nil {
if models.IsErrUserNotExist(err) {
ctx.Error(422, "", fmt.Sprintf("Assignee does not exist: [name: %s]", err))
} else {
ctx.Error(500, "AddAssigneeByName", err)
}
return
}
if err := models.NewPullRequest(repo, prIssue, labelIDs, []string{}, pr, patch, assigneeIDs); err != nil {
if models.IsErrUserDoesNotHaveAccessToRepo(err) {
ctx.Error(400, "UserDoesNotHaveAccessToRepo", err)
return
}
ctx.Error(500, "NewPullRequest", err)
return
} else if err := pr.PushToBaseRepo(); err != nil {
@@ -335,6 +330,7 @@ func EditPullRequest(ctx *context.APIContext, form api.EditPullRequestOption) {
issue.Content = form.Body
}
// Update Deadline
var deadlineUnix util.TimeStamp
if form.Deadline != nil && !form.Deadline.IsZero() {
deadlineUnix = util.TimeStamp(form.Deadline.Unix())
@@ -345,28 +341,27 @@ func EditPullRequest(ctx *context.APIContext, form api.EditPullRequestOption) {
return
}
if ctx.Repo.IsWriter() && len(form.Assignee) > 0 &&
(issue.Assignee == nil || issue.Assignee.LowerName != strings.ToLower(form.Assignee)) {
if len(form.Assignee) == 0 {
issue.AssigneeID = 0
} else {
assignee, err := models.GetUserByName(form.Assignee)
if err != nil {
if models.IsErrUserNotExist(err) {
ctx.Error(422, "", fmt.Sprintf("assignee does not exist: [name: %s]", form.Assignee))
} else {
ctx.Error(500, "GetUserByName", err)
}
return
}
issue.AssigneeID = assignee.ID
}
// Add/delete assignees
if err = models.UpdateIssueUserByAssignee(issue); err != nil {
ctx.Error(500, "UpdateIssueUserByAssignee", err)
// Deleting is done the Github way (quote from their api documentation):
// https://developer.github.com/v3/issues/#edit-an-issue
// "assignees" (array): Logins for Users to assign to this issue.
// Pass one or more user logins to replace the set of assignees on this Issue.
// Send an empty array ([]) to clear all assignees from the Issue.
if ctx.Repo.IsWriter() && (form.Assignees != nil || len(form.Assignee) > 0) {
err = models.UpdateAPIAssignee(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)
}
return
}
}
if ctx.Repo.IsWriter() && form.Milestone != 0 &&
issue.MilestoneID != form.Milestone {
oldMilestoneID := issue.MilestoneID
+2
View File
@@ -112,6 +112,7 @@ func Install(ctx *context.Context) {
form.EnableOpenIDSignIn = setting.Service.EnableOpenIDSignIn
form.EnableOpenIDSignUp = setting.Service.EnableOpenIDSignUp
form.DisableRegistration = setting.Service.DisableRegistration
form.AllowOnlyExternalRegistration = setting.Service.AllowOnlyExternalRegistration
form.EnableCaptcha = setting.Service.EnableCaptcha
form.RequireSignInView = setting.Service.RequireSignInView
form.DefaultKeepEmailPrivate = setting.Service.DefaultKeepEmailPrivate
@@ -304,6 +305,7 @@ func InstallPost(ctx *context.Context, form auth.InstallForm) {
cfg.Section("openid").Key("ENABLE_OPENID_SIGNIN").SetValue(com.ToStr(form.EnableOpenIDSignIn))
cfg.Section("openid").Key("ENABLE_OPENID_SIGNUP").SetValue(com.ToStr(form.EnableOpenIDSignUp))
cfg.Section("service").Key("DISABLE_REGISTRATION").SetValue(com.ToStr(form.DisableRegistration))
cfg.Section("service").Key("ALLOW_ONLY_EXTERNAL_REGISTRATION").SetValue(com.ToStr(form.AllowOnlyExternalRegistration))
cfg.Section("service").Key("ENABLE_CAPTCHA").SetValue(com.ToStr(form.EnableCaptcha))
cfg.Section("service").Key("REQUIRE_SIGNIN_VIEW").SetValue(com.ToStr(form.RequireSignInView))
cfg.Section("service").Key("DEFAULT_KEEP_EMAIL_PRIVATE").SetValue(com.ToStr(form.DefaultKeepEmailPrivate))
+46 -24
View File
@@ -364,7 +364,7 @@ func NewIssue(ctx *context.Context) {
}
// ValidateRepoMetas check and returns repository's meta informations
func ValidateRepoMetas(ctx *context.Context, form auth.CreateIssueForm) ([]int64, int64, int64) {
func ValidateRepoMetas(ctx *context.Context, form auth.CreateIssueForm) ([]int64, []int64, int64) {
var (
repo = ctx.Repo.Repository
err error
@@ -372,11 +372,11 @@ func ValidateRepoMetas(ctx *context.Context, form auth.CreateIssueForm) ([]int64
labels := RetrieveRepoMetas(ctx, ctx.Repo.Repository)
if ctx.Written() {
return nil, 0, 0
return nil, nil, 0
}
if !ctx.Repo.IsWriter() {
return nil, 0, 0
return nil, nil, 0
}
var labelIDs []int64
@@ -385,7 +385,7 @@ func ValidateRepoMetas(ctx *context.Context, form auth.CreateIssueForm) ([]int64
if len(form.LabelIDs) > 0 {
labelIDs, err = base.StringsToInt64s(strings.Split(form.LabelIDs, ","))
if err != nil {
return nil, 0, 0
return nil, nil, 0
}
labelIDMark := base.Int64sToMap(labelIDs)
@@ -407,23 +407,35 @@ func ValidateRepoMetas(ctx *context.Context, form auth.CreateIssueForm) ([]int64
ctx.Data["Milestone"], err = repo.GetMilestoneByID(milestoneID)
if err != nil {
ctx.ServerError("GetMilestoneByID", err)
return nil, 0, 0
return nil, nil, 0
}
ctx.Data["milestone_id"] = milestoneID
}
// Check assignee.
assigneeID := form.AssigneeID
if assigneeID > 0 {
ctx.Data["Assignee"], err = repo.GetAssigneeByID(assigneeID)
// Check assignees
var assigneeIDs []int64
if len(form.AssigneeIDs) > 0 {
assigneeIDs, err = base.StringsToInt64s(strings.Split(form.AssigneeIDs, ","))
if err != nil {
ctx.ServerError("GetAssigneeByID", err)
return nil, 0, 0
return nil, nil, 0
}
// Check if the passed assignees actually exists and has write access to the repo
for _, aID := range assigneeIDs {
_, err = repo.GetUserIfHasWriteAccess(aID)
if err != nil {
ctx.ServerError("GetUserIfHasWriteAccess", err)
return nil, nil, 0
}
}
ctx.Data["assignee_id"] = assigneeID
}
return labelIDs, milestoneID, assigneeID
// Keep the old assignee id thingy for compatibility reasons
if form.AssigneeID > 0 {
assigneeIDs = append(assigneeIDs, form.AssigneeID)
}
return labelIDs, assigneeIDs, milestoneID
}
// NewIssuePost response for creating new issue
@@ -440,7 +452,7 @@ func NewIssuePost(ctx *context.Context, form auth.CreateIssueForm) {
attachments []string
)
labelIDs, milestoneID, assigneeID := ValidateRepoMetas(ctx, form)
labelIDs, assigneeIDs, milestoneID := ValidateRepoMetas(ctx, form)
if ctx.Written() {
return
}
@@ -460,11 +472,14 @@ func NewIssuePost(ctx *context.Context, form auth.CreateIssueForm) {
PosterID: ctx.User.ID,
Poster: ctx.User,
MilestoneID: milestoneID,
AssigneeID: assigneeID,
Content: form.Content,
Ref: form.Ref,
}
if err := models.NewIssue(repo, issue, labelIDs, attachments); err != nil {
if err := models.NewIssue(repo, issue, labelIDs, assigneeIDs, attachments); err != nil {
if models.IsErrUserDoesNotHaveAccessToRepo(err) {
ctx.Error(400, "UserDoesNotHaveAccessToRepo", err.Error())
return
}
ctx.ServerError("NewIssue", err)
return
}
@@ -702,8 +717,8 @@ func ViewIssue(ctx *context.Context) {
comment.Milestone = ghostMilestone
}
} else if comment.Type == models.CommentTypeAssignees {
if err = comment.LoadAssignees(); err != nil {
ctx.ServerError("LoadAssignees", err)
if err = comment.LoadAssigneeUser(); err != nil {
ctx.ServerError("LoadAssigneeUser", err)
return
}
} else if comment.Type == models.CommentTypeCode || comment.Type == models.CommentTypeReview {
@@ -917,13 +932,20 @@ func UpdateIssueAssignee(ctx *context.Context) {
}
assigneeID := ctx.QueryInt64("id")
action := ctx.Query("action")
for _, issue := range issues {
if issue.AssigneeID == assigneeID {
continue
}
if err := issue.ChangeAssignee(ctx.User, assigneeID); err != nil {
ctx.ServerError("ChangeAssignee", err)
return
switch action {
case "clear":
if err := models.DeleteNotPassedAssignee(issue, ctx.User, []*models.User{}); err != nil {
ctx.ServerError("ClearAssignees", err)
return
}
default:
if err := issue.ChangeAssignee(ctx.User, assigneeID); err != nil {
ctx.ServerError("ChangeAssignee", err)
return
}
}
}
ctx.JSON(200, map[string]interface{}{
+7 -3
View File
@@ -785,7 +785,7 @@ func CompareAndPullRequestPost(ctx *context.Context, form auth.CreateIssueForm)
return
}
labelIDs, milestoneID, assigneeID := ValidateRepoMetas(ctx, form)
labelIDs, assigneeIDs, milestoneID := ValidateRepoMetas(ctx, form)
if ctx.Written() {
return
}
@@ -821,7 +821,6 @@ func CompareAndPullRequestPost(ctx *context.Context, form auth.CreateIssueForm)
PosterID: ctx.User.ID,
Poster: ctx.User,
MilestoneID: milestoneID,
AssigneeID: assigneeID,
IsPull: true,
Content: form.Content,
}
@@ -838,7 +837,12 @@ func CompareAndPullRequestPost(ctx *context.Context, form auth.CreateIssueForm)
}
// FIXME: check error in the case two people send pull request at almost same time, give nice error prompt
// instead of 500.
if err := models.NewPullRequest(repo, pullIssue, labelIDs, attachments, pullRequest, patch); err != nil {
if err := models.NewPullRequest(repo, pullIssue, labelIDs, attachments, pullRequest, patch, assigneeIDs); err != nil {
if models.IsErrUserDoesNotHaveAccessToRepo(err) {
ctx.Error(400, "UserDoesNotHaveAccessToRepo", err.Error())
return
}
ctx.ServerError("NewPullRequest", err)
return
} else if err := pullRequest.PushToBaseRepo(); err != nil {
+5 -1
View File
@@ -21,7 +21,11 @@ func TopicPost(ctx *context.Context) {
return
}
topics := strings.Split(ctx.Query("topics"), ",")
var topics = make([]string, 0)
var topicsStr = strings.TrimSpace(ctx.Query("topics"))
if len(topicsStr) > 0 {
topics = strings.Split(topicsStr, ",")
}
err := models.SaveTopics(ctx.Repo.Repository.ID, topics...)
if err != nil {
+2 -1
View File
@@ -741,7 +741,8 @@ func SignUpPost(ctx *context.Context, cpt *captcha.Captcha, form auth.RegisterFo
ctx.Data["EnableCaptcha"] = setting.Service.EnableCaptcha
if setting.Service.DisableRegistration {
//Permission denied if DisableRegistration or AllowOnlyExternalRegistration options are true
if !setting.Service.ShowRegistrationButton {
ctx.Error(403)
return
}