Configurable close and reopen keywords for PRs (#8120)

* Add settings for CloseKeywords and ReopenKeywords

* Fix and improve tests

* Use sync.Once() for initialization

* Fix unintended exported function
This commit is contained in:
guillep2k
2019-10-30 09:43:59 -03:00
committed by Lauris BH
parent ac6accef09
commit f9944c0e69
5 changed files with 199 additions and 77 deletions

View File

@ -69,6 +69,10 @@ MAX_FILES = 5
[repository.pull-request]
; List of prefixes used in Pull Request title to mark them as Work In Progress
WORK_IN_PROGRESS_PREFIXES=WIP:,[WIP]
; List of keywords used in Pull Request comments to automatically close a related issue
CLOSE_KEYWORDS=close,closes,closed,fix,fixes,fixed,resolve,resolves,resolved
; List of keywords used in Pull Request comments to automatically reopen a related issue
REOPEN_KEYWORDS=reopen,reopens,reopened
[repository.issue]
; List of reasons why a Pull Request or Issue can be locked

View File

@ -71,6 +71,10 @@ Values containing `#` or `;` must be quoted using `` ` `` or `"""`.
- `WORK_IN_PROGRESS_PREFIXES`: **WIP:,\[WIP\]**: List of prefixes used in Pull Request
title to mark them as Work In Progress
- `CLOSE_KEYWORDS`: **close**, **closes**, **closed**, **fix**, **fixes**, **fixed**, **resolve**, **resolves**, **resolved**: List of
keywords used in Pull Request comments to automatically close a related issue
- `REOPEN_KEYWORDS`: **reopen**, **reopens**, **reopened**: List of keywords used in Pull Request comments to automatically reopen
a related issue
### Repository - Issue (`repository.issue`)

View File

@ -11,6 +11,7 @@ import (
"strings"
"sync"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/markup/mdstripper"
"code.gitea.io/gitea/modules/setting"
)
@ -35,12 +36,8 @@ var (
// e.g. gogits/gogs#12345
crossReferenceIssueNumericPattern = regexp.MustCompile(`(?:\s|^|\(|\[)([0-9a-zA-Z-_\.]+/[0-9a-zA-Z-_\.]+#[0-9]+)(?:\s|$|\)|\]|\.(\s|$))`)
// 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"}
issueReopenKeywords = []string{"reopen", "reopens", "reopened"}
issueCloseKeywordsPat, issueReopenKeywordsPat *regexp.Regexp
issueKeywordsOnce sync.Once
giteaHostInit sync.Once
giteaHost string
@ -107,13 +104,40 @@ type RefSpan struct {
End int
}
func makeKeywordsPat(keywords []string) *regexp.Regexp {
return regexp.MustCompile(`(?i)(?:\s|^|\(|\[)(` + strings.Join(keywords, `|`) + `):? $`)
func makeKeywordsPat(words []string) *regexp.Regexp {
acceptedWords := parseKeywords(words)
if len(acceptedWords) == 0 {
// Never match
return nil
}
return regexp.MustCompile(`(?i)(?:\s|^|\(|\[)(` + strings.Join(acceptedWords, `|`) + `):? $`)
}
func init() {
issueCloseKeywordsPat = makeKeywordsPat(issueCloseKeywords)
issueReopenKeywordsPat = makeKeywordsPat(issueReopenKeywords)
func parseKeywords(words []string) []string {
acceptedWords := make([]string, 0, 5)
wordPat := regexp.MustCompile(`^[\pL]+$`)
for _, word := range words {
word = strings.ToLower(strings.TrimSpace(word))
// Accept Unicode letter class runes (a-z, á, à, ä, )
if wordPat.MatchString(word) {
acceptedWords = append(acceptedWords, word)
} else {
log.Info("Invalid keyword: %s", word)
}
}
return acceptedWords
}
func newKeywords() {
issueKeywordsOnce.Do(func() {
// Delay initialization until after the settings module is initialized
doNewKeywords(setting.Repository.PullRequest.CloseKeywords, setting.Repository.PullRequest.ReopenKeywords)
})
}
func doNewKeywords(close []string, reopen []string) {
issueCloseKeywordsPat = makeKeywordsPat(close)
issueReopenKeywordsPat = makeKeywordsPat(reopen)
}
// getGiteaHostName returns a normalized string with the local host name, with no scheme or port information
@ -310,13 +334,19 @@ func getCrossReference(content []byte, start, end int, fromLink bool) *rawRefere
}
func findActionKeywords(content []byte, start int) (XRefAction, *RefSpan) {
m := issueCloseKeywordsPat.FindSubmatchIndex(content[:start])
if m != nil {
return XRefActionCloses, &RefSpan{Start: m[2], End: m[3]}
newKeywords()
var m []int
if issueCloseKeywordsPat != nil {
m = issueCloseKeywordsPat.FindSubmatchIndex(content[:start])
if m != nil {
return XRefActionCloses, &RefSpan{Start: m[2], End: m[3]}
}
}
m = issueReopenKeywordsPat.FindSubmatchIndex(content[:start])
if m != nil {
return XRefActionReopens, &RefSpan{Start: m[2], End: m[3]}
if issueReopenKeywordsPat != nil {
m = issueReopenKeywordsPat.FindSubmatchIndex(content[:start])
if m != nil {
return XRefActionReopens, &RefSpan{Start: m[2], End: m[3]}
}
}
return XRefActionNone, nil
}

File diff suppressed because it is too large Load Diff

View File

@ -59,6 +59,8 @@ var (
// Pull request settings
PullRequest struct {
WorkInProgressPrefixes []string
CloseKeywords []string
ReopenKeywords []string
} `ini:"repository.pull-request"`
// Issue Setting
@ -122,8 +124,14 @@ var (
// Pull request settings
PullRequest: struct {
WorkInProgressPrefixes []string
CloseKeywords []string
ReopenKeywords []string
}{
WorkInProgressPrefixes: []string{"WIP:", "[WIP]"},
// Same as GitHub. See
// https://help.github.com/articles/closing-issues-via-commit-messages
CloseKeywords: strings.Split("close,closes,closed,fix,fixes,fixed,resolve,resolves,resolved", ","),
ReopenKeywords: strings.Split("reopen,reopens,reopened", ","),
},
// Issue settings