models/action.go: add action reopen for #462

- models/issue.go: format comment type names
This commit is contained in:
Unknwon 2015-02-06 20:47:21 -05:00
parent afccd0a3ee
commit 5a99e9a37b
3 changed files with 84 additions and 45 deletions

View file

@ -42,12 +42,19 @@ var (
var ( var (
// Same as Github. See https://help.github.com/articles/closing-issues-via-commit-messages // Same as Github. See https://help.github.com/articles/closing-issues-via-commit-messages
IssueCloseKeywords = []string{"close", "closes", "closed", "fix", "fixes", "fixed", "resolve", "resolves", "resolved"} IssueCloseKeywords = []string{"close", "closes", "closed", "fix", "fixes", "fixed", "resolve", "resolves", "resolved"}
IssueCloseKeywordsPat *regexp.Regexp IssueReopenKeywords = []string{"reopen", "reopens", "reopened"}
IssueCloseKeywordsPat, IssueReopenKeywordsPat *regexp.Regexp
IssueReferenceKeywordsPat *regexp.Regexp IssueReferenceKeywordsPat *regexp.Regexp
) )
func assembleKeywordsPattern(words []string) string {
return fmt.Sprintf(`(?i)(?:%s) \S+`, strings.Join(words, "|"))
}
func init() { func init() {
IssueCloseKeywordsPat = regexp.MustCompile(fmt.Sprintf(`(?i)(?:%s) \S+`, strings.Join(IssueCloseKeywords, "|"))) IssueCloseKeywordsPat = regexp.MustCompile(assembleKeywordsPattern(IssueCloseKeywords))
IssueReopenKeywordsPat = regexp.MustCompile(assembleKeywordsPattern(IssueReopenKeywords))
IssueReferenceKeywordsPat = regexp.MustCompile(`(?i)(?:)(^| )\S+`) IssueReferenceKeywordsPat = regexp.MustCompile(`(?i)(?:)(^| )\S+`)
} }
@ -112,11 +119,9 @@ func (a Action) GetIssueInfos() []string {
func updateIssuesCommit(userId, repoId int64, repoUserName, repoName string, commits []*base.PushCommit) error { func updateIssuesCommit(userId, repoId int64, repoUserName, repoName string, commits []*base.PushCommit) error {
for _, c := range commits { for _, c := range commits {
references := IssueReferenceKeywordsPat.FindAllString(c.Message, -1)
// FIXME: should not be a reference when it comes with action. // FIXME: should not be a reference when it comes with action.
// e.g. fixes #1 will not have duplicated reference message. // e.g. fixes #1 will not have duplicated reference message.
for _, ref := range references { for _, ref := range IssueReferenceKeywordsPat.FindAllString(c.Message, -1) {
ref := ref[strings.IndexByte(ref, byte(' '))+1:] ref := ref[strings.IndexByte(ref, byte(' '))+1:]
ref = strings.TrimRightFunc(ref, func(c rune) bool { ref = strings.TrimRightFunc(ref, func(c rune) bool {
return !unicode.IsDigit(c) return !unicode.IsDigit(c)
@ -137,22 +142,18 @@ func updateIssuesCommit(userId, repoId int64, repoUserName, repoName string, com
} }
issue, err := GetIssueByRef(ref) issue, err := GetIssueByRef(ref)
if err != nil { if err != nil {
return err return err
} }
url := fmt.Sprintf("%s/%s/%s/commit/%s", setting.AppSubUrl, repoUserName, repoName, c.Sha1) url := fmt.Sprintf("%s/%s/%s/commit/%s", setting.AppSubUrl, repoUserName, repoName, c.Sha1)
message := fmt.Sprintf(`<a href="%s">%s</a>`, url, c.Message) message := fmt.Sprintf(`<a href="%s">%s</a>`, url, c.Message)
if _, err = CreateComment(userId, issue.RepoId, issue.Id, 0, 0, COMMENT_TYPE_COMMIT, message, nil); err != nil {
if _, err = CreateComment(userId, issue.RepoId, issue.Id, 0, 0, COMMIT, message, nil); err != nil {
return err return err
} }
} }
closes := IssueCloseKeywordsPat.FindAllString(c.Message, -1) for _, ref := range IssueCloseKeywordsPat.FindAllString(c.Message, -1) {
for _, ref := range closes {
ref := ref[strings.IndexByte(ref, byte(' '))+1:] ref := ref[strings.IndexByte(ref, byte(' '))+1:]
ref = strings.TrimRightFunc(ref, func(c rune) bool { ref = strings.TrimRightFunc(ref, func(c rune) bool {
return !unicode.IsDigit(c) return !unicode.IsDigit(c)
@ -173,7 +174,6 @@ func updateIssuesCommit(userId, repoId int64, repoUserName, repoName string, com
} }
issue, err := GetIssueByRef(ref) issue, err := GetIssueByRef(ref)
if err != nil { if err != nil {
return err return err
} }
@ -182,7 +182,6 @@ func updateIssuesCommit(userId, repoId int64, repoUserName, repoName string, com
if issue.IsClosed { if issue.IsClosed {
continue continue
} }
issue.IsClosed = true issue.IsClosed = true
if err = UpdateIssue(issue); err != nil { if err = UpdateIssue(issue); err != nil {
@ -196,14 +195,60 @@ func updateIssuesCommit(userId, repoId int64, repoUserName, repoName string, com
} }
// If commit happened in the referenced repository, it means the issue can be closed. // If commit happened in the referenced repository, it means the issue can be closed.
if _, err = CreateComment(userId, repoId, issue.Id, 0, 0, CLOSE, "", nil); err != nil { if _, err = CreateComment(userId, repoId, issue.Id, 0, 0, COMMENT_TYPE_CLOSE, "", nil); err != nil {
return err return err
} }
} }
} }
for _, ref := range IssueReopenKeywordsPat.FindAllString(c.Message, -1) {
ref := ref[strings.IndexByte(ref, byte(' '))+1:]
ref = strings.TrimRightFunc(ref, func(c rune) bool {
return !unicode.IsDigit(c)
})
if len(ref) == 0 {
continue
} }
// Add repo name if missing
if ref[0] == '#' {
ref = fmt.Sprintf("%s/%s%s", repoUserName, repoName, ref)
} else if strings.Contains(ref, "/") == false {
// We don't support User#ID syntax yet
// return ErrNotImplemented
continue
}
issue, err := GetIssueByRef(ref)
if err != nil {
return err
}
if issue.RepoId == repoId {
if !issue.IsClosed {
continue
}
issue.IsClosed = false
if err = UpdateIssue(issue); err != nil {
return err
} else if err = UpdateIssueUserPairsByStatus(issue.Id, issue.IsClosed); err != nil {
return err
}
if err = ChangeMilestoneIssueStats(issue); err != nil {
return err
}
// If commit happened in the referenced repository, it means the issue can be closed.
if _, err = CreateComment(userId, repoId, issue.Id, 0, 0, COMMENT_TYPE_REOPEN, "", nil); err != nil {
return err
}
}
}
}
return nil return nil
} }

View file

@ -859,22 +859,16 @@ type CommentType int
const ( const (
// Plain comment, can be associated with a commit (CommitId > 0) and a line (Line > 0) // Plain comment, can be associated with a commit (CommitId > 0) and a line (Line > 0)
COMMENT CommentType = iota COMMENT_TYPE_COMMENT CommentType = iota
COMMENT_TYPE_REOPEN
// Reopen action COMMENT_TYPE_CLOSE
REOPEN
// Close action
CLOSE
// Reference from another issue
ISSUE
// References.
COMMENT_TYPE_ISSUE
// Reference from some commit (not part of a pull request) // Reference from some commit (not part of a pull request)
COMMIT COMMENT_TYPE_COMMIT
// Reference from some pull request // Reference from some pull request
PULL COMMENT_TYPE_PULL
) )
// Comment represents a comment in commit and issue page. // Comment represents a comment in commit and issue page.
@ -908,7 +902,7 @@ func CreateComment(userId, repoId, issueId, commitId, line int64, cmtType Commen
// Check comment type. // Check comment type.
switch cmtType { switch cmtType {
case COMMENT: case COMMENT_TYPE_COMMENT:
rawSql := "UPDATE `issue` SET num_comments = num_comments + 1 WHERE id = ?" rawSql := "UPDATE `issue` SET num_comments = num_comments + 1 WHERE id = ?"
if _, err := sess.Exec(rawSql, issueId); err != nil { if _, err := sess.Exec(rawSql, issueId); err != nil {
sess.Rollback() sess.Rollback()
@ -929,13 +923,13 @@ func CreateComment(userId, repoId, issueId, commitId, line int64, cmtType Commen
return nil, err return nil, err
} }
} }
case REOPEN: case COMMENT_TYPE_REOPEN:
rawSql := "UPDATE `repository` SET num_closed_issues = num_closed_issues - 1 WHERE id = ?" rawSql := "UPDATE `repository` SET num_closed_issues = num_closed_issues - 1 WHERE id = ?"
if _, err := sess.Exec(rawSql, repoId); err != nil { if _, err := sess.Exec(rawSql, repoId); err != nil {
sess.Rollback() sess.Rollback()
return nil, err return nil, err
} }
case CLOSE: case COMMENT_TYPE_CLOSE:
rawSql := "UPDATE `repository` SET num_closed_issues = num_closed_issues + 1 WHERE id = ?" rawSql := "UPDATE `repository` SET num_closed_issues = num_closed_issues + 1 WHERE id = ?"
if _, err := sess.Exec(rawSql, repoId); err != nil { if _, err := sess.Exec(rawSql, repoId); err != nil {
sess.Rollback() sess.Rollback()

View file

@ -424,7 +424,7 @@ func ViewIssue(ctx *middleware.Context) {
} }
comments[i].Poster = u comments[i].Poster = u
if comments[i].Type == models.COMMENT { if comments[i].Type == models.COMMENT_TYPE_COMMENT {
comments[i].Content = string(base.RenderMarkdown([]byte(comments[i].Content), ctx.Repo.RepoLink)) comments[i].Content = string(base.RenderMarkdown([]byte(comments[i].Content), ctx.Repo.RepoLink))
} }
} }
@ -774,9 +774,9 @@ func Comment(ctx *middleware.Context) {
} }
} }
cmtType := models.CLOSE cmtType := models.COMMENT_TYPE_CLOSE
if !issue.IsClosed { if !issue.IsClosed {
cmtType = models.REOPEN cmtType = models.COMMENT_TYPE_REOPEN
} }
if _, err = models.CreateComment(ctx.User.Id, ctx.Repo.Repository.Id, issue.Id, 0, 0, cmtType, "", nil); err != nil { if _, err = models.CreateComment(ctx.User.Id, ctx.Repo.Repository.Id, issue.Id, 0, 0, cmtType, "", nil); err != nil {
@ -795,7 +795,7 @@ func Comment(ctx *middleware.Context) {
if len(content) > 0 || len(ctx.Req.MultipartForm.File["attachments"]) > 0 { if len(content) > 0 || len(ctx.Req.MultipartForm.File["attachments"]) > 0 {
switch ctx.Params(":action") { switch ctx.Params(":action") {
case "new": case "new":
if comment, err = models.CreateComment(ctx.User.Id, ctx.Repo.Repository.Id, issue.Id, 0, 0, models.COMMENT, content, nil); err != nil { 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) send(500, nil, err)
return return
} }