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 @@
{{$externalTracker := (.Repository.MustGetUnit $.UnitTypeExternalTracker)}} {{$externalTrackerStyle := $externalTracker.ExternalTrackerConfig.ExternalTrackerStyle}} - +
- +
+
+
+ + +
+
+ +
+ + +

{{.i18n.Tr "repo.settings.tracker_issue_style.regexp_pattern_desc" | Str2html}}