Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Configurable close and reopen keywords for PRs #8120

Merged
merged 15 commits into from
Oct 30, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions custom/conf/app.ini.sample
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 4 additions & 0 deletions docs/content/doc/advanced/config-cheat-sheet.en-us.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`)

Expand Down
62 changes: 46 additions & 16 deletions modules/references/references.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
}
Loading