diff --git a/models/repo.go b/models/repo.go
index 8819debd4bcd0..f67b70fa0a19f 100644
--- a/models/repo.go
+++ b/models/repo.go
@@ -519,9 +519,13 @@ func (repo *Repository) ComposeMetas() map[string]string {
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
}
+ repo.ExternalMetas["format"] = unit.ExternalTrackerConfig().ExternalTrackerFormat
+ repo.ExternalMetas["regexp"] = unit.ExternalTrackerConfig().ExternalTrackerRegexpPattern
}
return repo.ExternalMetas
diff --git a/models/repo_test.go b/models/repo_test.go
index 02cb5ab993530..0477fc290330a 100644
--- a/models/repo_test.go
+++ b/models/repo_test.go
@@ -52,6 +52,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 430f5a242ff55..577a9618a462a 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 0333c3c92614a..ecae5bd642820 100644
--- a/modules/auth/repo_form.go
+++ b/modules/auth/repo_form.go
@@ -115,6 +115,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 dbfc8dbe85f71..1d55abe7283be 100644
--- a/modules/markup/html.go
+++ b/modules/markup/html.go
@@ -26,6 +26,7 @@ import (
const (
IssueNameStyleNumeric = "numeric"
IssueNameStyleAlphanumeric = "alphanumeric"
+ IssueNameStyleRegexp = "regexp"
)
var (
@@ -552,29 +553,50 @@ 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, content string
+ var start, 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 _, ok := ctx.metas["format"]; ok {
- // 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)
} else {
- link = createLink(util.URLJoin(setting.AppURL, ctx.metas["user"], ctx.metas["repo"], "issues", id[1:]), id)
+ link = createLink(util.URLJoin(setting.AppURL, ctx.metas["user"], ctx.metas["repo"], "issues", index), 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 10bc4973cd525..338c9e8463db8 100644
--- a/modules/markup/html_internal_test.go
+++ b/modules/markup/html_internal_test.go
@@ -48,6 +48,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,
+}
+
// these values should match the Repo const above
var localMetas = map[string]string{
"user": "gogits",
@@ -164,6 +171,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 71c76fd9b6ada..f63fe3642dd39 100644
--- a/options/locale/locale_en-US.ini
+++ b/options/locale/locale_en-US.ini
@@ -1137,6 +1137,9 @@ settings.tracker_url_format_error = The external issue tracker URL format is not
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 28023e1061bbf..c03bafa5086b9 100644
--- a/public/js/index.js
+++ b/public/js/index.js
@@ -722,6 +722,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 757295069e90c..6dee267101cb8 100644
--- a/routers/repo/setting.go
+++ b/routers/repo/setting.go
@@ -258,9 +258,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 c6d715acbee88..e1eff030ec06a 100644
--- a/templates/repo/settings/options.tmpl
+++ b/templates/repo/settings/options.tmpl
@@ -203,16 +203,27 @@
{{.i18n.Tr "repo.settings.tracker_issue_style.regexp_pattern_desc" | Str2html}}