diff --git a/models/repo.go b/models/repo.go
index 3c4908b0d..175073d33 100644
--- a/models/repo.go
+++ b/models/repo.go
@@ -527,10 +527,13 @@ func (repo *Repository) ComposeMetas() map[string]string {
"format": unit.ExternalTrackerConfig().ExternalTrackerFormat,
"user": repo.MustOwner().Name,
"repo": repo.Name,
+ "regexp": unit.ExternalTrackerConfig().ExternalTrackerRegexpPattern,
}
switch unit.ExternalTrackerConfig().ExternalTrackerStyle {
case markup.IssueNameStyleAlphanumeric:
repo.ExternalMetas["style"] = markup.IssueNameStyleAlphanumeric
+ case markup.IssueNameStyleRegexp:
+ repo.ExternalMetas["style"] = markup.IssueNameStyleRegexp
default:
repo.ExternalMetas["style"] = markup.IssueNameStyleNumeric
}
diff --git a/models/repo_test.go b/models/repo_test.go
index 752ffc2dd..73d0acae6 100644
--- a/models/repo_test.go
+++ b/models/repo_test.go
@@ -46,6 +46,9 @@ func TestRepo(t *testing.T) {
externalTracker.ExternalTrackerConfig().ExternalTrackerStyle = markup.IssueNameStyleNumeric
testSuccess(markup.IssueNameStyleNumeric)
+
+ externalTracker.ExternalTrackerConfig().ExternalTrackerStyle = markup.IssueNameStyleRegexp
+ testSuccess(markup.IssueNameStyleRegexp)
}
func TestGetRepositoryCount(t *testing.T) {
diff --git a/models/repo_unit.go b/models/repo_unit.go
index 1e1778356..2d343e7b0 100644
--- a/models/repo_unit.go
+++ b/models/repo_unit.go
@@ -54,9 +54,10 @@ func (cfg *ExternalWikiConfig) ToDB() ([]byte, error) {
// ExternalTrackerConfig describes external tracker config
type ExternalTrackerConfig struct {
- ExternalTrackerURL string
- ExternalTrackerFormat string
- ExternalTrackerStyle string
+ ExternalTrackerURL string
+ ExternalTrackerFormat string
+ ExternalTrackerStyle string
+ ExternalTrackerRegexpPattern string
}
// FromDB fills up a ExternalTrackerConfig from serialized format.
diff --git a/modules/auth/repo_form.go b/modules/auth/repo_form.go
index eea5859ff..fab2de785 100644
--- a/modules/auth/repo_form.go
+++ b/modules/auth/repo_form.go
@@ -106,6 +106,7 @@ type RepoSettingForm struct {
ExternalTrackerURL string
TrackerURLFormat string
TrackerIssueStyle string
+ ExternalTrackerRegexpPattern string
EnablePulls bool
PullsIgnoreWhitespace bool
PullsAllowMerge bool
diff --git a/modules/markup/html.go b/modules/markup/html.go
index a4ef86de2..e737d0eea 100644
--- a/modules/markup/html.go
+++ b/modules/markup/html.go
@@ -25,6 +25,7 @@ import (
const (
IssueNameStyleNumeric = "numeric"
IssueNameStyleAlphanumeric = "alphanumeric"
+ IssueNameStyleRegexp = "regexp"
)
var (
@@ -543,28 +544,52 @@ func issueIndexPatternProcessor(ctx *postProcessCtx, node *html.Node) {
// default to numeric pattern, unless alphanumeric is requested.
pattern := issueNumericPattern
- if ctx.metas["style"] == IssueNameStyleAlphanumeric {
+ switch ctx.metas["style"] {
+ case IssueNameStyleAlphanumeric:
pattern = issueAlphanumericPattern
+ case IssueNameStyleRegexp:
+ var err error
+ pattern, err = regexp.Compile(ctx.metas["regexp"])
+ if err != nil {
+ return
+ }
}
match := pattern.FindStringSubmatchIndex(node.Data)
- if match == nil {
+ if match == nil || len(match) < 4 {
return
}
- id := node.Data[match[2]:match[3]]
+
+ var index string
+ var content string
+ var start int
+ var end int
+ switch ctx.metas["style"] {
+ case IssueNameStyleAlphanumeric:
+ content = node.Data[match[2]:match[3]]
+ index = content
+ start = match[2]
+ end = match[3]
+ case IssueNameStyleRegexp:
+ index = node.Data[match[2]:match[3]]
+ content = node.Data[match[0]:match[1]]
+ start = match[0]
+ end = match[1]
+ default:
+ content = node.Data[match[2]:match[3]]
+ index = content[1:]
+ start = match[2]
+ end = match[3]
+ }
+
var link *html.Node
if ctx.metas == nil {
- link = createLink(util.URLJoin(prefix, "issues", id[1:]), id)
+ link = createLink(util.URLJoin(prefix, "issues", index), content)
} else {
- // Support for external issue tracker
- if ctx.metas["style"] == IssueNameStyleAlphanumeric {
- ctx.metas["index"] = id
- } else {
- ctx.metas["index"] = id[1:]
- }
- link = createLink(com.Expand(ctx.metas["format"], ctx.metas), id)
+ ctx.metas["index"] = index
+ link = createLink(com.Expand(ctx.metas["format"], ctx.metas), content)
}
- replaceContent(node, match[2], match[3], link)
+ replaceContent(node, start, end, link)
}
func crossReferenceIssueIndexPatternProcessor(ctx *postProcessCtx, node *html.Node) {
diff --git a/modules/markup/html_internal_test.go b/modules/markup/html_internal_test.go
index ff07bab91..526b6814e 100644
--- a/modules/markup/html_internal_test.go
+++ b/modules/markup/html_internal_test.go
@@ -53,6 +53,13 @@ var alphanumericMetas = map[string]string{
"style": IssueNameStyleAlphanumeric,
}
+var regexpMetas = map[string]string{
+ "format": "https://someurl.com/{user}/{repo}/{index}",
+ "user": "someUser",
+ "repo": "someRepo",
+ "style": IssueNameStyleRegexp,
+}
+
func TestRender_IssueIndexPattern(t *testing.T) {
// numeric: render inputs without valid mentions
test := func(s string) {
@@ -160,6 +167,40 @@ func TestRender_IssueIndexPattern4(t *testing.T) {
test("test issue ABCDEFGHIJ-1234567890", "test issue %s", "ABCDEFGHIJ-1234567890")
}
+func TestRender_IssueIndexPattern5(t *testing.T) {
+ test := func(s, expectedFmt string, pattern string, ids []string, names []string) {
+ metas := regexpMetas
+ metas["regexp"] = pattern
+ links := make([]interface{}, len(ids))
+ for i, id := range ids {
+ links[i] = link(util.URLJoin("https://someurl.com/someUser/someRepo/", id), names[i])
+ }
+
+ expected := fmt.Sprintf(expectedFmt, links...)
+ testRenderIssueIndexPattern(t, s, expected, &postProcessCtx{metas: metas})
+ }
+
+ test("abc ISSUE-123 def", "abc %s def", "ISSUE-(\\d+)",
+ []string{"123"},
+ []string{"ISSUE-123"},
+ )
+
+ test("abc (ISSUE 123) def", "abc %s def",
+ "\\(ISSUE (\\d+)\\)",
+ []string{"123"},
+ []string{"(ISSUE 123)"},
+ )
+
+ test("abc (ISSUE 123) def (TASK 456) ghi", "abc %s def %s ghi", "\\((?:ISSUE|TASK) (\\d+)\\)",
+ []string{"123", "456"},
+ []string{"(ISSUE 123)", "(TASK 456)"},
+ )
+
+ metas := regexpMetas
+ metas["regexp"] = "no matches"
+ testRenderIssueIndexPattern(t, "will not match", "will not match", &postProcessCtx{metas: metas})
+}
+
func testRenderIssueIndexPattern(t *testing.T, input, expected string, ctx *postProcessCtx) {
if ctx == nil {
ctx = new(postProcessCtx)
diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini
index 72e79cf90..b06b19df5 100644
--- a/options/locale/locale_en-US.ini
+++ b/options/locale/locale_en-US.ini
@@ -974,6 +974,9 @@ settings.tracker_url_format = External Issue Tracker URL Format
settings.tracker_issue_style = External Issue Tracker Number Format
settings.tracker_issue_style.numeric = Numeric
settings.tracker_issue_style.alphanumeric = Alphanumeric
+settings.tracker_issue_style.regexp = Regular Expression
+settings.tracker_issue_style.regexp_pattern = Regular Expression Pattern
+settings.tracker_issue_style.regexp_pattern_desc = The first captured group will be used in place of {index}
.
settings.tracker_url_format_desc = Use the placeholders {user}
, {repo}
and {index}
for the username, repository name and issue index.
settings.enable_timetracker = Enable Time Tracking
settings.allow_only_contributors_to_track_time = Let Only Contributors Track Time
diff --git a/public/js/index.js b/public/js/index.js
index 696b63e77..ea1bf76c9 100644
--- a/public/js/index.js
+++ b/public/js/index.js
@@ -501,6 +501,15 @@ function initRepository() {
if (typeof $(this).data('context') !== 'undefined') $($(this).data('context')).addClass('disabled');
}
});
+ $('.enable-system-pick').change(function () {
+ if ($(this).data('context') && $(this).data('target')) {
+ if ($(this).data('context') === this.value) {
+ $($(this).data('target')).removeClass('disabled')
+ } else {
+ $($(this).data('target')).addClass('disabled')
+ }
+ }
+ })
}
// Labels
diff --git a/routers/repo/setting.go b/routers/repo/setting.go
index fa3bd434d..ef147f456 100644
--- a/routers/repo/setting.go
+++ b/routers/repo/setting.go
@@ -190,9 +190,10 @@ func SettingsPost(ctx *context.Context, form auth.RepoSettingForm) {
RepoID: repo.ID,
Type: models.UnitTypeExternalTracker,
Config: &models.ExternalTrackerConfig{
- ExternalTrackerURL: form.ExternalTrackerURL,
- ExternalTrackerFormat: form.TrackerURLFormat,
- ExternalTrackerStyle: form.TrackerIssueStyle,
+ ExternalTrackerURL: form.ExternalTrackerURL,
+ ExternalTrackerFormat: form.TrackerURLFormat,
+ ExternalTrackerStyle: form.TrackerIssueStyle,
+ ExternalTrackerRegexpPattern: form.ExternalTrackerRegexpPattern,
},
})
} else {
diff --git a/templates/repo/settings/options.tmpl b/templates/repo/settings/options.tmpl
index 744fa8206..b3acee409 100644
--- a/templates/repo/settings/options.tmpl
+++ b/templates/repo/settings/options.tmpl
@@ -183,16 +183,27 @@
{{.i18n.Tr "repo.settings.tracker_issue_style.regexp_pattern_desc" | Str2html}}