func checkPullInfo(ctx *middleware.Context) *models.Issue { issue, err := models.GetIssueByIndex(ctx.Repo.Repository.ID, ctx.ParamsInt64(":index")) if err != nil { if models.IsErrIssueNotExist(err) { ctx.Handle(404, "GetIssueByIndex", err) } else { ctx.Handle(500, "GetIssueByIndex", err) } return nil } ctx.Data["Title"] = issue.Name ctx.Data["Issue"] = issue if !issue.IsPull { ctx.Handle(404, "ViewPullCommits", nil) return nil } if err = issue.GetPoster(); err != nil { ctx.Handle(500, "GetPoster", err) return nil } if ctx.IsSigned { // Update issue-user. if err = issue.ReadBy(ctx.User.Id); err != nil { ctx.Handle(500, "ReadBy", err) return nil } } return issue }
func UpdateIssue(ctx *middleware.Context, params martini.Params, form auth.CreateIssueForm) { if !ctx.Repo.IsOwner { ctx.Handle(404, "issue.UpdateIssue", nil) return } index, err := base.StrTo(params["index"]).Int() if err != nil { ctx.Handle(404, "issue.UpdateIssue", err) return } issue, err := models.GetIssueByIndex(ctx.Repo.Repository.Id, int64(index)) if err != nil { if err == models.ErrIssueNotExist { ctx.Handle(404, "issue.UpdateIssue", err) } else { ctx.Handle(200, "issue.UpdateIssue", err) } return } issue.Name = form.IssueName issue.MilestoneId = form.MilestoneId issue.AssigneeId = form.AssigneeId issue.Labels = form.Labels issue.Content = form.Content if err = models.UpdateIssue(issue); err != nil { ctx.Handle(200, "issue.UpdateIssue", err) return } ctx.Data["Title"] = issue.Name ctx.Data["Issue"] = issue }
func DeleteIssueLabel(ctx *context.APIContext) { if !ctx.Repo.IsWriter() { ctx.Status(403) return } issue, err := models.GetIssueByIndex(ctx.Repo.Repository.ID, ctx.ParamsInt64(":index")) if err != nil { if models.IsErrIssueNotExist(err) { ctx.Status(404) } else { ctx.Error(500, "GetIssueByIndex", err) } return } label, err := models.GetLabelInRepoByID(ctx.Repo.Repository.ID, ctx.ParamsInt64(":id")) if err != nil { if models.IsErrLabelNotExist(err) { ctx.Error(422, "", err) } else { ctx.Error(500, "GetLabelInRepoByID", err) } return } if err := models.DeleteIssueLabel(issue, label); err != nil { ctx.Error(500, "DeleteIssueLabel", err) return } ctx.Status(204) }
func GetIssue(ctx *context.APIContext) { issue, err := models.GetIssueByIndex(ctx.Repo.Repository.ID, ctx.ParamsInt64(":index")) if err != nil { if models.IsErrIssueNotExist(err) { ctx.Status(404) } else { ctx.Error(500, "GetIssueByIndex", err) } return } ctx.JSON(200, issue.APIFormat()) }
func getActionIssue(ctx *middleware.Context) *models.Issue { issue, err := models.GetIssueByIndex(ctx.Repo.Repository.ID, ctx.ParamsInt64(":index")) if err != nil { if models.IsErrIssueNotExist(err) { ctx.Error(404, "GetIssueByIndex") } else { ctx.Handle(500, "GetIssueByIndex", err) } return nil } return issue }
func ViewIssue(ctx *middleware.Context, params martini.Params) { index, err := base.StrTo(params["index"]).Int() if err != nil { ctx.Handle(404, "issue.ViewIssue", err) return } issue, err := models.GetIssueByIndex(ctx.Repo.Repository.Id, int64(index)) if err != nil { if err == models.ErrIssueNotExist { ctx.Handle(404, "issue.ViewIssue", err) } else { ctx.Handle(200, "issue.ViewIssue", err) } return } // Get posters. u, err := models.GetUserById(issue.PosterId) if err != nil { ctx.Handle(200, "issue.ViewIssue(get poster): %v", err) return } issue.Poster = u issue.RenderedContent = string(base.RenderMarkdown([]byte(issue.Content), ctx.Repo.RepoLink)) // Get comments. comments, err := models.GetIssueComments(issue.Id) if err != nil { ctx.Handle(200, "issue.ViewIssue(get comments): %v", err) return } // Get posters. for i := range comments { u, err := models.GetUserById(comments[i].PosterId) if err != nil { ctx.Handle(200, "issue.ViewIssue(get poster): %v", err) return } comments[i].Poster = u comments[i].Content = string(base.RenderMarkdown([]byte(comments[i].Content), ctx.Repo.RepoLink)) } ctx.Data["Title"] = issue.Name ctx.Data["Issue"] = issue ctx.Data["Comments"] = comments ctx.Data["IsIssueOwner"] = ctx.Repo.IsOwner || (ctx.IsSigned && issue.PosterId == ctx.User.Id) ctx.Data["IsRepoToolbarIssues"] = true ctx.Data["IsRepoToolbarIssuesList"] = false ctx.HTML(200, "issue/view") }
func CreateIssueComment(ctx *context.APIContext, form api.CreateIssueCommentOption) { issue, err := models.GetIssueByIndex(ctx.Repo.Repository.ID, ctx.ParamsInt64(":index")) if err != nil { ctx.Error(500, "GetIssueByIndex", err) return } comment, err := models.CreateIssueComment(ctx.User, ctx.Repo.Repository, issue, form.Body, nil) if err != nil { ctx.Error(500, "CreateIssueComment", err) return } ctx.JSON(201, comment.APIFormat()) }
func ListIssueLabels(ctx *context.APIContext) { issue, err := models.GetIssueByIndex(ctx.Repo.Repository.ID, ctx.ParamsInt64(":index")) if err != nil { if models.IsErrIssueNotExist(err) { ctx.Status(404) } else { ctx.Error(500, "GetIssueByIndex", err) } return } apiLabels := make([]*api.Label, len(issue.Labels)) for i := range issue.Labels { apiLabels[i] = convert.ToLabel(issue.Labels[i]) } ctx.JSON(200, &apiLabels) }
func UpdateIssue(ctx *middleware.Context, form auth.CreateIssueForm) { idx := com.StrTo(ctx.Params(":index")).MustInt64() if idx <= 0 { ctx.Error(404) return } issue, err := models.GetIssueByIndex(ctx.Repo.Repository.Id, idx) if err != nil { if err == models.ErrIssueNotExist { ctx.Handle(404, "issue.UpdateIssue", err) } else { ctx.Handle(500, "issue.UpdateIssue(GetIssueByIndex)", err) } return } if ctx.User.Id != issue.PosterId && !ctx.Repo.IsOwner() { ctx.Error(403) return } issue.Name = form.IssueName //issue.MilestoneId = form.MilestoneId //issue.AssigneeId = form.AssigneeId //issue.LabelIds = form.Labels issue.Content = form.Content // try get content from text, ignore conflict with preview ajax if form.Content == "" { issue.Content = ctx.Query("text") } if err = models.UpdateIssue(issue); err != nil { ctx.Handle(500, "issue.UpdateIssue(UpdateIssue)", err) return } ctx.JSON(200, map[string]interface{}{ "ok": true, "title": issue.Name, "content": string(base.RenderMarkdown([]byte(issue.Content), ctx.Repo.RepoLink)), }) }
func ViewIssue(ctx *middleware.Context, params martini.Params) { index, err := base.StrTo(params["index"]).Int() if err != nil { ctx.Handle(404, "issue.ViewIssue", err) return } issue, err := models.GetIssueByIndex(ctx.Repo.Repository.Id, int64(index)) if err != nil { if err == models.ErrIssueNotExist { ctx.Handle(404, "issue.ViewIssue", err) } else { ctx.Handle(200, "issue.ViewIssue", err) } return } ctx.Data["Title"] = issue.Name ctx.Data["Issue"] = issue ctx.HTML(200, "issue/view") }
func AddIssueLabels(ctx *context.APIContext, form api.IssueLabelsOption) { if !ctx.Repo.IsWriter() { ctx.Status(403) return } issue, err := models.GetIssueByIndex(ctx.Repo.Repository.ID, ctx.ParamsInt64(":index")) if err != nil { if models.IsErrIssueNotExist(err) { ctx.Status(404) } else { ctx.Error(500, "GetIssueByIndex", err) } return } labels, err := models.GetLabelsInRepoByIDs(ctx.Repo.Repository.ID, form.Labels) if err != nil { ctx.Error(500, "GetLabelsInRepoByIDs", err) return } if err = issue.AddLabels(labels); err != nil { ctx.Error(500, "AddLabels", err) return } labels, err = models.GetLabelsByIssueID(issue.ID) if err != nil { ctx.Error(500, "GetLabelsByIssueID", err) return } apiLabels := make([]*api.Label, len(labels)) for i := range labels { apiLabels[i] = convert.ToLabel(labels[i]) } ctx.JSON(200, &apiLabels) }
func UpdateIssue(ctx *middleware.Context, params martini.Params, form auth.CreateIssueForm) { index, err := base.StrTo(params["index"]).Int() if err != nil { ctx.Handle(404, "issue.UpdateIssue", err) return } issue, err := models.GetIssueByIndex(ctx.Repo.Repository.Id, int64(index)) if err != nil { if err == models.ErrIssueNotExist { ctx.Handle(404, "issue.UpdateIssue", err) } else { ctx.Handle(200, "issue.UpdateIssue(get issue)", err) } return } if ctx.User.Id != issue.PosterId && !ctx.Repo.IsOwner { ctx.Handle(404, "issue.UpdateIssue", nil) return } issue.Name = form.IssueName issue.MilestoneId = form.MilestoneId issue.AssigneeId = form.AssigneeId issue.Labels = form.Labels issue.Content = form.Content if err = models.UpdateIssue(issue); err != nil { ctx.Handle(200, "issue.UpdateIssue(update issue)", err) return } ctx.JSON(200, map[string]interface{}{ "ok": true, "title": issue.Name, "content": string(base.RenderMarkdown([]byte(issue.Content), ctx.Repo.RepoLink)), }) }
func ClearIssueLabels(ctx *context.APIContext) { if !ctx.Repo.IsWriter() { ctx.Status(403) return } issue, err := models.GetIssueByIndex(ctx.Repo.Repository.ID, ctx.ParamsInt64(":index")) if err != nil { if models.IsErrIssueNotExist(err) { ctx.Status(404) } else { ctx.Error(500, "GetIssueByIndex", err) } return } if err := issue.ClearLabels(); err != nil { ctx.Error(500, "ClearLabels", err) return } ctx.Status(204) }
func UpdateAssignee(ctx *middleware.Context) { if !ctx.Repo.IsOwner { ctx.Error(403) return } idx, err := base.StrTo(ctx.Query("issue")).Int64() if err != nil { ctx.Error(404) return } issue, err := models.GetIssueByIndex(ctx.Repo.Repository.Id, idx) if err != nil { if err == models.ErrIssueNotExist { ctx.Handle(404, "issue.UpdateAssignee", err) } else { ctx.Handle(500, "issue.UpdateAssignee(GetIssueByIndex)", err) } return } aid, _ := base.StrTo(ctx.Query("assigneeid")).Int64() // Not check for invalid assignne id and give responsibility to owners. issue.AssigneeId = aid if err = models.UpdateIssueUserPairByAssignee(aid, issue.Id); err != nil { ctx.Handle(500, "issue.UpdateAssignee(UpdateIssueUserPairByAssignee): %v", err) return } else if err = models.UpdateIssue(issue); err != nil { ctx.Handle(500, "issue.UpdateAssignee(UpdateIssue)", err) return } ctx.JSON(200, map[string]interface{}{ "ok": true, }) }
func ViewIssue(ctx *middleware.Context) { ctx.Data["RequireDropzone"] = true renderAttachmentSettings(ctx) issue, err := models.GetIssueByIndex(ctx.Repo.Repository.ID, ctx.ParamsInt64(":index")) if err != nil { if models.IsErrIssueNotExist(err) { ctx.Handle(404, "GetIssueByIndex", err) } else { ctx.Handle(500, "GetIssueByIndex", err) } return } ctx.Data["Title"] = issue.Name // Make sure type and URL matches. if ctx.Params(":type") == "issues" && issue.IsPull { ctx.Redirect(ctx.Repo.RepoLink + "/pulls/" + com.ToStr(issue.Index)) return } else if ctx.Params(":type") == "pulls" && !issue.IsPull { ctx.Redirect(ctx.Repo.RepoLink + "/issues/" + com.ToStr(issue.Index)) return } if issue.IsPull { if err = issue.GetPullRequest(); err != nil { ctx.Handle(500, "GetPullRequest", err) return } ctx.Data["PageIsPullList"] = true ctx.Data["PageIsPullConversation"] = true ctx.Data["HasForkedRepo"] = ctx.IsSigned && ctx.User.HasForkedRepo(ctx.Repo.Repository.ID) } else { MustEnableIssues(ctx) if ctx.Written() { return } ctx.Data["PageIsIssueList"] = true } if err = issue.GetPoster(); err != nil { ctx.Handle(500, "GetPoster", err) return } issue.RenderedContent = string(base.RenderMarkdown([]byte(issue.Content), ctx.Repo.RepoLink, ctx.Repo.Repository.ComposeMetas())) repo := ctx.Repo.Repository // Get more information if it's a pull request. if issue.IsPull { if issue.HasMerged { ctx.Data["DisableStatusChange"] = issue.HasMerged PrepareMergedViewPullInfo(ctx, issue) } else { PrepareViewPullInfo(ctx, issue) } if ctx.Written() { return } } // Metas. // Check labels. if err = issue.GetLabels(); err != nil { ctx.Handle(500, "GetLabels", err) return } labelIDMark := make(map[int64]bool) for i := range issue.Labels { labelIDMark[issue.Labels[i].ID] = true } labels, err := models.GetLabelsByRepoID(repo.ID) if err != nil { ctx.Handle(500, "GetLabelsByRepoID: %v", err) return } hasSelected := false for i := range labels { if labelIDMark[labels[i].ID] { labels[i].IsChecked = true hasSelected = true } } ctx.Data["HasSelectedLabel"] = hasSelected ctx.Data["Labels"] = labels // Check milestone and assignee. if ctx.Repo.IsAdmin() { RetrieveRepoMilestonesAndAssignees(ctx, repo) if ctx.Written() { return } } if ctx.IsSigned { // Update issue-user. if err = issue.ReadBy(ctx.User.Id); err != nil { ctx.Handle(500, "ReadBy", err) return } } var ( tag models.CommentTag ok bool marked = make(map[int64]models.CommentTag) comment *models.Comment ) // Render comments. for _, comment = range issue.Comments { if comment.Type == models.COMMENT_TYPE_COMMENT { comment.RenderedContent = string(base.RenderMarkdown([]byte(comment.Content), ctx.Repo.RepoLink, ctx.Repo.Repository.ComposeMetas())) // Check tag. tag, ok = marked[comment.PosterID] if ok { comment.ShowTag = tag continue } if repo.IsOwnedBy(comment.PosterID) || (repo.Owner.IsOrganization() && repo.Owner.IsOwnedBy(comment.PosterID)) { comment.ShowTag = models.COMMENT_TAG_OWNER } else if comment.Poster.IsAdminOfRepo(repo) { comment.ShowTag = models.COMMENT_TAG_ADMIN } else if comment.PosterID == issue.PosterID { comment.ShowTag = models.COMMENT_TAG_POSTER } marked[comment.PosterID] = comment.ShowTag } } ctx.Data["Issue"] = issue ctx.Data["IsIssueOwner"] = ctx.Repo.IsAdmin() || (ctx.IsSigned && issue.IsPoster(ctx.User.Id)) ctx.Data["SignInLink"] = setting.AppSubUrl + "/user/login" ctx.HTML(200, ISSUE_VIEW) }
func ViewIssue(ctx *middleware.Context, params martini.Params) { idx, _ := base.StrTo(params["index"]).Int64() if idx == 0 { ctx.Handle(404, "issue.ViewIssue", nil) return } issue, err := models.GetIssueByIndex(ctx.Repo.Repository.Id, idx) if err != nil { if err == models.ErrIssueNotExist { ctx.Handle(404, "issue.ViewIssue(GetIssueByIndex)", err) } else { ctx.Handle(500, "issue.ViewIssue(GetIssueByIndex)", err) } return } // Update assignee. if ctx.Repo.IsOwner { aid, _ := base.StrTo(ctx.Query("assignneid")).Int64() if aid > 0 { // Not check for invalid assignne id and give responsibility to owners. issue.AssigneeId = aid if err = models.UpdateIssueUserPairByAssignee(aid, issue.Id); err != nil { ctx.Handle(500, "issue.ViewIssue(UpdateIssueUserPairByAssignee): %v", err) return } ctx.Redirect(fmt.Sprintf("%s/issues/%d", ctx.Repo.RepoLink, issue.Index)) return } } if ctx.IsSigned { // Update issue-user. if err = models.UpdateIssueUserPairByRead(ctx.User.Id, issue.Id); err != nil { ctx.Handle(500, "issue.ViewIssue(UpdateIssueUserPairByRead): %v", err) return } } // Get poster and Assignee. if err = issue.GetPoster(); err != nil { ctx.Handle(500, "issue.ViewIssue(GetPoster): %v", err) return } else if err = issue.GetAssignee(); err != nil { ctx.Handle(500, "issue.ViewIssue(GetAssignee): %v", err) return } issue.RenderedContent = string(base.RenderMarkdown([]byte(issue.Content), ctx.Repo.RepoLink)) // Get comments. comments, err := models.GetIssueComments(issue.Id) if err != nil { ctx.Handle(500, "issue.ViewIssue(GetIssueComments): %v", err) return } // Get posters. for i := range comments { u, err := models.GetUserById(comments[i].PosterId) if err != nil { ctx.Handle(500, "issue.ViewIssue(GetUserById.2): %v", err) return } comments[i].Poster = u comments[i].Content = string(base.RenderMarkdown([]byte(comments[i].Content), ctx.Repo.RepoLink)) } ctx.Data["Title"] = issue.Name ctx.Data["Issue"] = issue ctx.Data["Comments"] = comments ctx.Data["IsIssueOwner"] = ctx.Repo.IsOwner || (ctx.IsSigned && issue.PosterId == ctx.User.Id) ctx.Data["IsRepoToolbarIssues"] = true ctx.Data["IsRepoToolbarIssuesList"] = false ctx.HTML(200, "issue/view") }
func NewComment(ctx *middleware.Context, form auth.CreateCommentForm) { issue, err := models.GetIssueByIndex(ctx.Repo.Repository.ID, ctx.ParamsInt64(":index")) if err != nil { if models.IsErrIssueNotExist(err) { ctx.Handle(404, "GetIssueByIndex", err) } else { ctx.Handle(500, "GetIssueByIndex", err) } return } var attachments []string if setting.AttachmentEnabled { attachments = form.Attachments } if ctx.HasError() { ctx.Flash.Error(ctx.Data["ErrorMsg"].(string)) ctx.Redirect(fmt.Sprintf("%s/issues/%d", ctx.Repo.RepoLink, issue.Index)) return } // Fix #321: Allow empty comments, as long as we have attachments. if len(form.Content) == 0 && len(attachments) == 0 { ctx.Redirect(fmt.Sprintf("%s/issues/%d", ctx.Repo.RepoLink, issue.Index)) return } comment, err := models.CreateIssueComment(ctx.User, ctx.Repo.Repository, issue, form.Content, attachments) if err != nil { ctx.Handle(500, "CreateIssueComment", err) return } // Update mentions. mentions := base.MentionPattern.FindAllString(comment.Content, -1) if len(mentions) > 0 { for i := range mentions { mentions[i] = mentions[i][1:] } if err := models.UpdateMentions(mentions, issue.ID); err != nil { ctx.Handle(500, "UpdateMentions", err) return } } // Mail watchers and mentions. if setting.Service.EnableNotifyMail { issue.Content = form.Content tos, err := mailer.SendIssueNotifyMail(ctx.User, ctx.Repo.Owner, ctx.Repo.Repository, issue) if err != nil { ctx.Handle(500, "SendIssueNotifyMail", err) return } tos = append(tos, ctx.User.LowerName) newTos := make([]string, 0, len(mentions)) for _, m := range mentions { if com.IsSliceContainsStr(tos, m) { continue } newTos = append(newTos, m) } if err = mailer.SendIssueMentionMail(ctx.Render, ctx.User, ctx.Repo.Owner, ctx.Repo.Repository, issue, models.GetUserEmailsByNames(newTos)); err != nil { ctx.Handle(500, "SendIssueMentionMail", err) return } } log.Trace("Comment created: %d/%d/%d", ctx.Repo.Repository.ID, issue.ID, comment.ID) // Check if issue owner/poster changes the status of issue. if (ctx.Repo.IsOwner() || (ctx.IsSigned && issue.IsPoster(ctx.User.Id))) && (form.Status == "reopen" || form.Status == "close") && !(issue.IsPull && issue.HasMerged) { issue.Repo = ctx.Repo.Repository if err = issue.ChangeStatus(ctx.User, form.Status == "close"); err != nil { ctx.Handle(500, "ChangeStatus", err) return } log.Trace("Issue[%d] status changed: %v", issue.ID, !issue.IsClosed) } ctx.Redirect(fmt.Sprintf("%s/issues/%d#%s", ctx.Repo.RepoLink, issue.Index, comment.HashTag())) }
func EditIssue(ctx *context.APIContext, form api.EditIssueOption) { issue, err := models.GetIssueByIndex(ctx.Repo.Repository.ID, ctx.ParamsInt64(":index")) if err != nil { if models.IsErrIssueNotExist(err) { ctx.Status(404) } else { ctx.Error(500, "GetIssueByIndex", err) } return } if !issue.IsPoster(ctx.User.ID) && !ctx.Repo.IsWriter() { ctx.Status(403) return } if len(form.Title) > 0 { issue.Title = form.Title } if form.Body != nil { issue.Content = *form.Body } 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 } if err = models.UpdateIssueUserByAssignee(issue); err != nil { ctx.Error(500, "UpdateIssueUserByAssignee", err) return } } if ctx.Repo.IsWriter() && form.Milestone != nil && issue.MilestoneID != *form.Milestone { oldMilestoneID := issue.MilestoneID issue.MilestoneID = *form.Milestone if err = models.ChangeMilestoneAssign(issue, oldMilestoneID); err != nil { ctx.Error(500, "ChangeMilestoneAssign", err) return } } if err = models.UpdateIssue(issue); err != nil { ctx.Error(500, "UpdateIssue", err) return } if form.State != nil { if err = issue.ChangeStatus(ctx.User, ctx.Repo.Repository, api.STATE_CLOSED == api.StateType(*form.State)); err != nil { ctx.Error(500, "ChangeStatus", err) return } } // Refetch from database to assign some automatic values issue, err = models.GetIssueByID(issue.ID) if err != nil { ctx.Error(500, "GetIssueByID", err) return } ctx.JSON(201, issue.APIFormat()) }
func ViewIssue(ctx *middleware.Context, params martini.Params) { idx, _ := base.StrTo(params["index"]).Int64() if idx == 0 { ctx.Handle(404, "issue.ViewIssue", nil) return } issue, err := models.GetIssueByIndex(ctx.Repo.Repository.Id, idx) if err != nil { if err == models.ErrIssueNotExist { ctx.Handle(404, "issue.ViewIssue(GetIssueByIndex)", err) } else { ctx.Handle(500, "issue.ViewIssue(GetIssueByIndex)", err) } return } // Get assigned milestone. if issue.MilestoneId > 0 { ctx.Data["Milestone"], err = models.GetMilestoneById(issue.MilestoneId) if err != nil { if err == models.ErrMilestoneNotExist { log.Warn("issue.ViewIssue(GetMilestoneById): %v", err) } else { ctx.Handle(500, "issue.ViewIssue(GetMilestoneById)", err) return } } } // Get all milestones. ctx.Data["OpenMilestones"], err = models.GetMilestones(ctx.Repo.Repository.Id, false) if err != nil { ctx.Handle(500, "issue.ViewIssue(GetMilestones.1): %v", err) return } ctx.Data["ClosedMilestones"], err = models.GetMilestones(ctx.Repo.Repository.Id, true) if err != nil { ctx.Handle(500, "issue.ViewIssue(GetMilestones.2): %v", err) return } // Get all collaborators. ctx.Data["Collaborators"], err = models.GetCollaborators(strings.TrimPrefix(ctx.Repo.RepoLink, "/")) if err != nil { ctx.Handle(500, "issue.CreateIssue(GetCollaborators)", err) return } if ctx.IsSigned { // Update issue-user. if err = models.UpdateIssueUserPairByRead(ctx.User.Id, issue.Id); err != nil { ctx.Handle(500, "issue.ViewIssue(UpdateIssueUserPairByRead): %v", err) return } } // Get poster and Assignee. if err = issue.GetPoster(); err != nil { ctx.Handle(500, "issue.ViewIssue(GetPoster): %v", err) return } else if err = issue.GetAssignee(); err != nil { ctx.Handle(500, "issue.ViewIssue(GetAssignee): %v", err) return } issue.RenderedContent = string(base.RenderMarkdown([]byte(issue.Content), ctx.Repo.RepoLink)) // Get comments. comments, err := models.GetIssueComments(issue.Id) if err != nil { ctx.Handle(500, "issue.ViewIssue(GetIssueComments): %v", err) return } // Get posters. for i := range comments { u, err := models.GetUserById(comments[i].PosterId) if err != nil { ctx.Handle(500, "issue.ViewIssue(GetUserById.2): %v", err) return } comments[i].Poster = u comments[i].Content = string(base.RenderMarkdown([]byte(comments[i].Content), ctx.Repo.RepoLink)) } ctx.Data["Title"] = issue.Name ctx.Data["Issue"] = issue ctx.Data["Comments"] = comments ctx.Data["IsIssueOwner"] = ctx.Repo.IsOwner || (ctx.IsSigned && issue.PosterId == ctx.User.Id) ctx.Data["IsRepoToolbarIssues"] = true ctx.Data["IsRepoToolbarIssuesList"] = false ctx.HTML(200, "issue/view") }
func ViewIssue(ctx *middleware.Context) { ctx.Data["PageIsIssueList"] = true ctx.Data["RequireDropzone"] = true renderAttachmentSettings(ctx) issue, err := models.GetIssueByIndex(ctx.Repo.Repository.ID, ctx.ParamsInt64(":index")) if err != nil { if models.IsErrIssueNotExist(err) { ctx.Handle(404, "GetIssueByIndex", err) } else { ctx.Handle(500, "GetIssueByIndex", err) } return } ctx.Data["Title"] = issue.Name if err = issue.GetPoster(); err != nil { ctx.Handle(500, "GetPoster", err) return } issue.RenderedContent = string(base.RenderMarkdown([]byte(issue.Content), ctx.Repo.RepoLink)) repo := ctx.Repo.Repository // Metas. // Check labels. if err = issue.GetLabels(); err != nil { ctx.Handle(500, "GetLabels", err) return } labelIDMark := make(map[int64]bool) for i := range issue.Labels { labelIDMark[issue.Labels[i].ID] = true } labels, err := models.GetLabelsByRepoID(repo.ID) if err != nil { ctx.Handle(500, "GetLabelsByRepoID: %v", err) return } hasSelected := false for i := range labels { if labelIDMark[labels[i].ID] { labels[i].IsChecked = true hasSelected = true } } ctx.Data["HasSelectedLabel"] = hasSelected ctx.Data["Labels"] = labels // Check milestone and assignee. if ctx.Repo.IsAdmin() { ctx.Data["OpenMilestones"], err = models.GetMilestones(repo.ID, -1, false) if err != nil { ctx.Handle(500, "GetMilestones: %v", err) return } ctx.Data["ClosedMilestones"], err = models.GetMilestones(repo.ID, -1, true) if err != nil { ctx.Handle(500, "GetMilestones: %v", err) return } ctx.Data["Assignees"], err = repo.GetAssignees() if err != nil { ctx.Handle(500, "GetAssignees: %v", err) return } } if ctx.IsSigned { // Update issue-user. if err = issue.ReadBy(ctx.User.Id); err != nil { ctx.Handle(500, "ReadBy", err) return } } var ( tag models.CommentTag ok bool marked = make(map[int64]models.CommentTag) comment *models.Comment ) // Render comments. for _, comment = range issue.Comments { if comment.Type == models.COMMENT_TYPE_COMMENT { comment.RenderedContent = string(base.RenderMarkdown([]byte(comment.Content), ctx.Repo.RepoLink)) // Check tag. tag, ok = marked[comment.PosterID] if ok { comment.ShowTag = tag continue } if repo.IsOwnedBy(comment.PosterID) || (repo.Owner.IsOrganization() && repo.Owner.IsOwnedBy(comment.PosterID)) { comment.ShowTag = models.COMMENT_TAG_OWNER } else if comment.Poster.IsAdminOfRepo(repo) { comment.ShowTag = models.COMMENT_TAG_ADMIN } else if comment.PosterID == issue.PosterID { comment.ShowTag = models.COMMENT_TAG_POSTER } marked[comment.PosterID] = comment.ShowTag } } ctx.Data["Issue"] = issue ctx.Data["IsIssueOwner"] = ctx.Repo.IsAdmin() || (ctx.IsSigned && issue.IsPoster(ctx.User.Id)) ctx.HTML(200, ISSUE_VIEW) }
func Comment(ctx *middleware.Context) { send := func(status int, data interface{}, err error) { if err != nil { log.Error(4, "issue.Comment(?): %s", err) ctx.JSON(status, map[string]interface{}{ "ok": false, "status": status, "error": err.Error(), }) } else { ctx.JSON(status, map[string]interface{}{ "ok": true, "status": status, "data": data, }) } } index := com.StrTo(ctx.Query("issueIndex")).MustInt64() if index == 0 { ctx.Error(404) return } issue, err := models.GetIssueByIndex(ctx.Repo.Repository.Id, index) if err != nil { if err == models.ErrIssueNotExist { send(404, nil, err) } else { send(200, nil, err) } return } // Check if issue owner changes the status of issue. var newStatus string if ctx.Repo.IsOwner() || issue.PosterId == ctx.User.Id { newStatus = ctx.Query("change_status") } if len(newStatus) > 0 { if (strings.Contains(newStatus, "Reopen") && issue.IsClosed) || (strings.Contains(newStatus, "Close") && !issue.IsClosed) { issue.IsClosed = !issue.IsClosed if err = models.UpdateIssue(issue); err != nil { send(500, nil, err) return } else if err = models.UpdateIssueUserPairsByStatus(issue.ID, issue.IsClosed); err != nil { send(500, nil, err) return } if err = issue.GetLabels(); err != nil { send(500, nil, err) return } for _, label := range issue.Labels { if issue.IsClosed { label.NumClosedIssues++ } else { label.NumClosedIssues-- } if err = models.UpdateLabel(label); err != nil { send(500, nil, err) return } } // Change open/closed issue counter for the associated milestone if issue.MilestoneId > 0 { if err = models.ChangeMilestoneIssueStats(issue); err != nil { send(500, nil, err) } } cmtType := models.COMMENT_TYPE_CLOSE if !issue.IsClosed { cmtType = models.COMMENT_TYPE_REOPEN } if _, err = models.CreateComment(ctx.User.Id, ctx.Repo.Repository.Id, issue.ID, 0, 0, cmtType, "", nil); err != nil { send(200, nil, err) return } log.Trace("%s Issue(%d) status changed: %v", ctx.Req.RequestURI, issue.ID, !issue.IsClosed) } } var comment *models.Comment var ms []string content := ctx.Query("content") // Fix #321. Allow empty comments, as long as we have attachments. if len(content) > 0 || len(ctx.Req.MultipartForm.File["attachments"]) > 0 { switch ctx.Params(":action") { case "new": if comment, err = models.CreateComment(ctx.User.Id, ctx.Repo.Repository.Id, issue.ID, 0, 0, models.COMMENT_TYPE_COMMENT, content, nil); err != nil { send(500, nil, err) return } // Update mentions. ms = base.MentionPattern.FindAllString(issue.Content, -1) if len(ms) > 0 { for i := range ms { ms[i] = ms[i][1:] } if err := models.UpdateMentions(ms, issue.ID); err != nil { send(500, nil, err) return } } log.Trace("%s Comment created: %d", ctx.Req.RequestURI, issue.ID) default: ctx.Handle(404, "issue.Comment", err) return } } if comment != nil { uploadFiles(ctx, issue.ID, comment.Id) } // Notify watchers. act := &models.Action{ ActUserID: ctx.User.Id, ActUserName: ctx.User.LowerName, ActEmail: ctx.User.Email, OpType: models.COMMENT_ISSUE, Content: fmt.Sprintf("%d|%s", issue.Index, strings.Split(content, "\n")[0]), RepoID: ctx.Repo.Repository.Id, RepoUserName: ctx.Repo.Owner.LowerName, RepoName: ctx.Repo.Repository.LowerName, IsPrivate: ctx.Repo.Repository.IsPrivate, } if err = models.NotifyWatchers(act); err != nil { send(500, nil, err) return } // Mail watchers and mentions. if setting.Service.EnableNotifyMail { issue.Content = content tos, err := mailer.SendIssueNotifyMail(ctx.User, ctx.Repo.Owner, ctx.Repo.Repository, issue) if err != nil { send(500, nil, err) return } tos = append(tos, ctx.User.LowerName) newTos := make([]string, 0, len(ms)) for _, m := range ms { if com.IsSliceContainsStr(tos, m) { continue } newTos = append(newTos, m) } if err = mailer.SendIssueMentionMail(ctx.Render, ctx.User, ctx.Repo.Owner, ctx.Repo.Repository, issue, models.GetUserEmailsByNames(newTos)); err != nil { send(500, nil, err) return } } send(200, fmt.Sprintf("%s/issues/%d", ctx.Repo.RepoLink, index), nil) }
func UpdateIssueLabel(ctx *middleware.Context) { if !ctx.Repo.IsOwner() { ctx.Error(403) return } idx := com.StrTo(ctx.Params(":index")).MustInt64() if idx <= 0 { ctx.Error(404) return } issue, err := models.GetIssueByIndex(ctx.Repo.Repository.Id, idx) if err != nil { if err == models.ErrIssueNotExist { ctx.Handle(404, "issue.UpdateIssueLabel(GetIssueByIndex)", err) } else { ctx.Handle(500, "issue.UpdateIssueLabel(GetIssueByIndex)", err) } return } isAttach := ctx.Query("action") == "attach" labelStrId := ctx.Query("id") labelId := com.StrTo(labelStrId).MustInt64() label, err := models.GetLabelById(labelId) if err != nil { if err == models.ErrLabelNotExist { ctx.Handle(404, "issue.UpdateIssueLabel(GetLabelById)", err) } else { ctx.Handle(500, "issue.UpdateIssueLabel(GetLabelById)", err) } return } isHad := strings.Contains(issue.LabelIds, "$"+labelStrId+"|") isNeedUpdate := false if isAttach { if !isHad { issue.LabelIds += "$" + labelStrId + "|" isNeedUpdate = true } } else { if isHad { issue.LabelIds = strings.Replace(issue.LabelIds, "$"+labelStrId+"|", "", -1) isNeedUpdate = true } } if isNeedUpdate { if err = models.UpdateIssue(issue); err != nil { ctx.Handle(500, "issue.UpdateIssueLabel(UpdateIssue)", err) return } if isAttach { label.NumIssues++ if issue.IsClosed { label.NumClosedIssues++ } } else { label.NumIssues-- if issue.IsClosed { label.NumClosedIssues-- } } if err = models.UpdateLabel(label); err != nil { ctx.Handle(500, "issue.UpdateIssueLabel(UpdateLabel)", err) return } } ctx.JSON(200, map[string]interface{}{ "ok": true, }) }
func ViewIssue(ctx *middleware.Context) { ctx.Data["AttachmentsEnabled"] = setting.AttachmentEnabled idx := com.StrTo(ctx.Params(":index")).MustInt64() if idx == 0 { ctx.Handle(404, "issue.ViewIssue", nil) return } issue, err := models.GetIssueByIndex(ctx.Repo.Repository.Id, idx) if err != nil { if err == models.ErrIssueNotExist { ctx.Handle(404, "issue.ViewIssue(GetIssueByIndex)", err) } else { ctx.Handle(500, "issue.ViewIssue(GetIssueByIndex)", err) } return } // Get labels. if err = issue.GetLabels(); err != nil { ctx.Handle(500, "issue.ViewIssue(GetLabels)", err) return } labels, err := models.GetLabels(ctx.Repo.Repository.Id) if err != nil { ctx.Handle(500, "issue.ViewIssue(GetLabels.2)", err) return } checkLabels(issue.Labels, labels) ctx.Data["Labels"] = labels // Get assigned milestone. if issue.MilestoneId > 0 { ctx.Data["Milestone"], err = models.GetMilestoneById(issue.MilestoneId) if err != nil { if err == models.ErrMilestoneNotExist { log.Warn("issue.ViewIssue(GetMilestoneById): %v", err) } else { ctx.Handle(500, "issue.ViewIssue(GetMilestoneById)", err) return } } } // Get all milestones. ctx.Data["OpenMilestones"], err = models.GetMilestones(ctx.Repo.Repository.Id, false) if err != nil { ctx.Handle(500, "issue.ViewIssue(GetMilestones.1): %v", err) return } ctx.Data["ClosedMilestones"], err = models.GetMilestones(ctx.Repo.Repository.Id, true) if err != nil { ctx.Handle(500, "issue.ViewIssue(GetMilestones.2): %v", err) return } // Get all collaborators. ctx.Data["Collaborators"], err = ctx.Repo.Repository.GetCollaborators() if err != nil { ctx.Handle(500, "issue.CreateIssue(GetCollaborators)", err) return } if ctx.IsSigned { // Update issue-user. if err = models.UpdateIssueUserPairByRead(ctx.User.Id, issue.ID); err != nil { ctx.Handle(500, "issue.ViewIssue(UpdateIssueUserPairByRead): %v", err) return } } // Get poster and Assignee. if err = issue.GetPoster(); err != nil { ctx.Handle(500, "issue.ViewIssue(GetPoster): %v", err) return } else if err = issue.GetAssignee(); err != nil { ctx.Handle(500, "issue.ViewIssue(GetAssignee): %v", err) return } issue.RenderedContent = string(base.RenderMarkdown([]byte(issue.Content), ctx.Repo.RepoLink)) // Get comments. comments, err := models.GetIssueComments(issue.ID) if err != nil { ctx.Handle(500, "issue.ViewIssue(GetIssueComments): %v", err) return } // Get posters. for i := range comments { u, err := models.GetUserById(comments[i].PosterId) if err != nil { ctx.Handle(500, "issue.ViewIssue(GetUserById.2): %v", err) return } comments[i].Poster = u if comments[i].Type == models.COMMENT_TYPE_COMMENT { comments[i].Content = string(base.RenderMarkdown([]byte(comments[i].Content), ctx.Repo.RepoLink)) } } ctx.Data["AllowedTypes"] = setting.AttachmentAllowedTypes ctx.Data["Title"] = issue.Name ctx.Data["Issue"] = issue ctx.Data["Comments"] = comments ctx.Data["IsIssueOwner"] = ctx.Repo.IsOwner() || (ctx.IsSigned && issue.PosterId == ctx.User.Id) ctx.Data["IsRepoToolbarIssues"] = true ctx.Data["IsRepoToolbarIssuesList"] = false ctx.HTML(200, ISSUE_VIEW) }
func Comment(ctx *middleware.Context, params martini.Params) { index, err := base.StrTo(ctx.Query("issueIndex")).Int64() if err != nil { ctx.Handle(404, "issue.Comment(get index)", err) return } issue, err := models.GetIssueByIndex(ctx.Repo.Repository.Id, index) if err != nil { if err == models.ErrIssueNotExist { ctx.Handle(404, "issue.Comment", err) } else { ctx.Handle(200, "issue.Comment(get issue)", err) } return } // Check if issue owner changes the status of issue. var newStatus string if ctx.Repo.IsOwner || issue.PosterId == ctx.User.Id { newStatus = ctx.Query("change_status") } if len(newStatus) > 0 { if (strings.Contains(newStatus, "Reopen") && issue.IsClosed) || (strings.Contains(newStatus, "Close") && !issue.IsClosed) { issue.IsClosed = !issue.IsClosed if err = models.UpdateIssue(issue); err != nil { ctx.Handle(500, "issue.Comment(UpdateIssue)", err) return } else if err = models.UpdateIssueUserPairsByStatus(issue.Id, issue.IsClosed); err != nil { ctx.Handle(500, "issue.Comment(UpdateIssueUserPairsByStatus)", err) return } cmtType := models.IT_CLOSE if !issue.IsClosed { cmtType = models.IT_REOPEN } if err = models.CreateComment(ctx.User.Id, ctx.Repo.Repository.Id, issue.Id, 0, 0, cmtType, ""); err != nil { ctx.Handle(200, "issue.Comment(create status change comment)", err) return } log.Trace("%s Issue(%d) status changed: %v", ctx.Req.RequestURI, issue.Id, !issue.IsClosed) } } var ms []string content := ctx.Query("content") if len(content) > 0 { switch params["action"] { case "new": if err = models.CreateComment(ctx.User.Id, ctx.Repo.Repository.Id, issue.Id, 0, 0, models.IT_PLAIN, content); err != nil { ctx.Handle(500, "issue.Comment(create comment)", err) return } // Update mentions. ms = base.MentionPattern.FindAllString(issue.Content, -1) if len(ms) > 0 { for i := range ms { ms[i] = ms[i][1:] } ids := models.GetUserIdsByNames(ms) if err := models.UpdateIssueUserPairsByMentions(ids, issue.Id); err != nil { ctx.Handle(500, "issue.CreateIssue(UpdateIssueUserPairsByMentions)", err) return } } log.Trace("%s Comment created: %d", ctx.Req.RequestURI, issue.Id) default: ctx.Handle(404, "issue.Comment", err) return } } // Notify watchers. act := &models.Action{ ActUserId: ctx.User.Id, ActUserName: ctx.User.LowerName, ActEmail: ctx.User.Email, OpType: models.OP_COMMENT_ISSUE, Content: fmt.Sprintf("%d|%s", issue.Index, strings.Split(content, "\n")[0]), RepoId: ctx.Repo.Repository.Id, RepoUserName: ctx.Repo.Owner.LowerName, RepoName: ctx.Repo.Repository.LowerName, } if err = models.NotifyWatchers(act); err != nil { ctx.Handle(500, "issue.CreateIssue(NotifyWatchers)", err) return } // Mail watchers and mentions. if setting.Service.EnableNotifyMail { issue.Content = content tos, err := mailer.SendIssueNotifyMail(ctx.User, ctx.Repo.Owner, ctx.Repo.Repository, issue) if err != nil { ctx.Handle(500, "issue.Comment(SendIssueNotifyMail)", err) return } tos = append(tos, ctx.User.LowerName) newTos := make([]string, 0, len(ms)) for _, m := range ms { if com.IsSliceContainsStr(tos, m) { continue } newTos = append(newTos, m) } if err = mailer.SendIssueMentionMail(ctx.Render, ctx.User, ctx.Repo.Owner, ctx.Repo.Repository, issue, models.GetUserEmailsByNames(newTos)); err != nil { ctx.Handle(500, "issue.Comment(SendIssueMentionMail)", err) return } } ctx.Redirect(fmt.Sprintf("%s/issues/%d", ctx.Repo.RepoLink, index)) }
func Comment(ctx *middleware.Context, params martini.Params) { index, err := base.StrTo(ctx.Query("issueIndex")).Int64() if err != nil { ctx.Handle(404, "issue.Comment(get index)", err) return } issue, err := models.GetIssueByIndex(ctx.Repo.Repository.Id, index) if err != nil { if err == models.ErrIssueNotExist { ctx.Handle(404, "issue.Comment", err) } else { ctx.Handle(200, "issue.Comment(get issue)", err) } return } // TODO: check collaborators // Check if issue owner changes the status of issue. var newStatus string if ctx.Repo.IsOwner || issue.PosterId == ctx.User.Id { newStatus = ctx.Query("change_status") } if len(newStatus) > 0 { if (strings.Contains(newStatus, "Reopen") && issue.IsClosed) || (strings.Contains(newStatus, "Close") && !issue.IsClosed) { issue.IsClosed = !issue.IsClosed if err = models.UpdateIssue(issue); err != nil { ctx.Handle(200, "issue.Comment(update issue status)", err) return } cmtType := models.IT_CLOSE if !issue.IsClosed { cmtType = models.IT_REOPEN } if err = models.CreateComment(ctx.User.Id, ctx.Repo.Repository.Id, issue.Id, 0, 0, cmtType, ""); err != nil { ctx.Handle(200, "issue.Comment(create status change comment)", err) return } log.Trace("%s Issue(%d) status changed: %v", ctx.Req.RequestURI, issue.Id, !issue.IsClosed) } } content := ctx.Query("content") if len(content) > 0 { switch params["action"] { case "new": if err = models.CreateComment(ctx.User.Id, ctx.Repo.Repository.Id, issue.Id, 0, 0, models.IT_PLAIN, content); err != nil { ctx.Handle(500, "issue.Comment(create comment)", err) return } log.Trace("%s Comment created: %d", ctx.Req.RequestURI, issue.Id) default: ctx.Handle(404, "issue.Comment", err) return } } ctx.Redirect(fmt.Sprintf("%s/issues/%d", ctx.Repo.RepoLink, index)) }
func NewComment(ctx *middleware.Context, form auth.CreateCommentForm) { issue, err := models.GetIssueByIndex(ctx.Repo.Repository.ID, ctx.ParamsInt64(":index")) if err != nil { if models.IsErrIssueNotExist(err) { ctx.Handle(404, "GetIssueByIndex", err) } else { ctx.Handle(500, "GetIssueByIndex", err) } return } if issue.IsPull { if err = issue.GetPullRequest(); err != nil { ctx.Handle(500, "GetPullRequest", err) return } } var attachments []string if setting.AttachmentEnabled { attachments = form.Attachments } if ctx.HasError() { ctx.Flash.Error(ctx.Data["ErrorMsg"].(string)) ctx.Redirect(fmt.Sprintf("%s/issues/%d", ctx.Repo.RepoLink, issue.Index)) return } var comment *models.Comment defer func() { // Check if issue admin/poster changes the status of issue. if (ctx.Repo.IsAdmin() || (ctx.IsSigned && issue.IsPoster(ctx.User.Id))) && (form.Status == "reopen" || form.Status == "close") && !(issue.IsPull && issue.HasMerged) { // Duplication and conflict check should apply to reopen pull request. var pr *models.PullRequest if form.Status == "reopen" && issue.IsPull { pull := issue.PullRequest pr, err = models.GetUnmergedPullRequest(pull.HeadRepoID, pull.BaseRepoID, pull.HeadBranch, pull.BaseBranch) if err != nil { if !models.IsErrPullRequestNotExist(err) { ctx.Handle(500, "GetUnmergedPullRequest", err) return } } // Regenerate patch and test conflict. if pr == nil { if err = issue.UpdatePatch(); err != nil { ctx.Handle(500, "UpdatePatch", err) return } issue.AddToTaskQueue() } } if pr != nil { ctx.Flash.Info(ctx.Tr("repo.pulls.open_unmerged_pull_exists", pr.Index)) } else { issue.Repo = ctx.Repo.Repository if err = issue.ChangeStatus(ctx.User, form.Status == "close"); err != nil { log.Error(4, "ChangeStatus: %v", err) } else { log.Trace("Issue[%d] status changed to closed: %v", issue.ID, issue.IsClosed) } } } // Redirect to comment hashtag if there is any actual content. typeName := "issues" if issue.IsPull { typeName = "pulls" } if comment != nil { ctx.Redirect(fmt.Sprintf("%s/%s/%d#%s", ctx.Repo.RepoLink, typeName, issue.Index, comment.HashTag())) } else { ctx.Redirect(fmt.Sprintf("%s/%s/%d", ctx.Repo.RepoLink, typeName, issue.Index)) } }() // Fix #321: Allow empty comments, as long as we have attachments. if len(form.Content) == 0 && len(attachments) == 0 { return } comment, err = models.CreateIssueComment(ctx.User, ctx.Repo.Repository, issue, form.Content, attachments) if err != nil { ctx.Handle(500, "CreateIssueComment", err) return } notifyWatchersAndMentions(ctx, &models.Issue{ ID: issue.ID, Index: issue.Index, Name: issue.Name, Content: form.Content, }) if ctx.Written() { return } log.Trace("Comment created: %d/%d/%d", ctx.Repo.Repository.ID, issue.ID, comment.ID) }
func NewComment(ctx *middleware.Context, form auth.CreateCommentForm) { issue, err := models.GetIssueByIndex(ctx.Repo.Repository.ID, ctx.ParamsInt64(":index")) if err != nil { if models.IsErrIssueNotExist(err) { ctx.Handle(404, "GetIssueByIndex", err) } else { ctx.Handle(500, "GetIssueByIndex", err) } return } if issue.IsPull { if err = issue.GetPullRequest(); err != nil { ctx.Handle(500, "GetPullRequest", err) return } } var attachments []string if setting.AttachmentEnabled { attachments = form.Attachments } if ctx.HasError() { ctx.Flash.Error(ctx.Data["ErrorMsg"].(string)) ctx.Redirect(fmt.Sprintf("%s/issues/%d", ctx.Repo.RepoLink, issue.Index)) return } var comment *models.Comment defer func() { // Check if issue owner/poster changes the status of issue. if (ctx.Repo.IsOwner() || (ctx.IsSigned && issue.IsPoster(ctx.User.Id))) && (form.Status == "reopen" || form.Status == "close") && !(issue.IsPull && issue.HasMerged) { // Duplication and conflict check should apply to reopen pull request. var pr *models.PullRequest if form.Status == "reopen" && issue.IsPull { pull := issue.PullRequest pr, err = models.GetUnmergedPullRequest(pull.HeadRepoID, pull.BaseRepoID, pull.HeadBranch, pull.BaseBranch) if err != nil { if !models.IsErrPullRequestNotExist(err) { ctx.Handle(500, "GetUnmergedPullRequest", err) return } } // Regenerate patch and test conflict. if pr == nil { if err = issue.UpdatePatch(); err != nil { ctx.Handle(500, "UpdatePatch", err) return } issue.AddToTaskQueue() } } if pr != nil { ctx.Flash.Info(ctx.Tr("repo.pulls.open_unmerged_pull_exists", pr.Index)) } else { issue.Repo = ctx.Repo.Repository if err = issue.ChangeStatus(ctx.User, form.Status == "close"); err != nil { log.Error(4, "ChangeStatus: %v", err) } else { log.Trace("Issue[%d] status changed to closed: %v", issue.ID, issue.IsClosed) } } } // Redirect to comment hashtag if there is any actual content. typeName := "issues" if issue.IsPull { typeName = "pulls" } if comment != nil { ctx.Redirect(fmt.Sprintf("%s/%s/%d#%s", ctx.Repo.RepoLink, typeName, issue.Index, comment.HashTag())) } else { ctx.Redirect(fmt.Sprintf("%s/%s/%d", ctx.Repo.RepoLink, typeName, issue.Index)) } }() // Fix #321: Allow empty comments, as long as we have attachments. if len(form.Content) == 0 && len(attachments) == 0 { return } comment, err = models.CreateIssueComment(ctx.User, ctx.Repo.Repository, issue, form.Content, attachments) if err != nil { ctx.Handle(500, "CreateIssueComment", err) return } // Update mentions. mentions := base.MentionPattern.FindAllString(comment.Content, -1) if len(mentions) > 0 { for i := range mentions { mentions[i] = mentions[i][1:] } if err := models.UpdateMentions(mentions, issue.ID); err != nil { ctx.Handle(500, "UpdateMentions", err) return } } // Mail watchers and mentions. if setting.Service.EnableNotifyMail { tos, err := mailer.SendIssueNotifyMail(ctx.User, ctx.Repo.Owner, ctx.Repo.Repository, &models.Issue{ Index: issue.Index, Name: issue.Name, Content: form.Content, }) if err != nil { ctx.Handle(500, "SendIssueNotifyMail", err) return } tos = append(tos, ctx.User.LowerName) newTos := make([]string, 0, len(mentions)) for _, m := range mentions { if com.IsSliceContainsStr(tos, m) { continue } newTos = append(newTos, m) } if err = mailer.SendIssueMentionMail(ctx.Render, ctx.User, ctx.Repo.Owner, ctx.Repo.Repository, issue, models.GetUserEmailsByNames(newTos)); err != nil { ctx.Handle(500, "SendIssueMentionMail", err) return } } log.Trace("Comment created: %d/%d/%d", ctx.Repo.Repository.ID, issue.ID, comment.ID) }