From 6808b576496467ee9825fb332276031b480c1a5f Mon Sep 17 00:00:00 2001 From: Alexander Beyn Date: Sun, 17 Jun 2018 12:21:42 -0700 Subject: [PATCH 01/10] Implement custom regular expression for external issue tracking. Signed-off-by: Alexander Beyn --- models/repo.go | 4 +++ models/repo_test.go | 4 +++ models/repo_unit.go | 7 +++-- modules/markup/html.go | 37 ++++++++++++++++------ modules/markup/html_internal_test.go | 46 ++++++++++++++++++++++++++++ modules/references/references.go | 20 +++++++++++- options/locale/locale_en-US.ini | 3 ++ routers/web/repo/setting.go | 7 +++-- services/forms/repo_form.go | 1 + templates/repo/settings/options.tmpl | 15 +++++++-- web_src/js/features/repo-legacy.js | 9 ++++++ 11 files changed, 134 insertions(+), 19 deletions(-) diff --git a/models/repo.go b/models/repo.go index f44fc763a56ec..78930c716745f 100644 --- a/models/repo.go +++ b/models/repo.go @@ -492,9 +492,13 @@ func (repo *Repository) ComposeMetas() map[string]string { switch unit.ExternalTrackerConfig().ExternalTrackerStyle { case markup.IssueNameStyleAlphanumeric: metas["style"] = markup.IssueNameStyleAlphanumeric + case markup.IssueNameStyleRegexp: + metas["style"] = markup.IssueNameStyleRegexp default: metas["style"] = markup.IssueNameStyleNumeric } + metas["format"] = unit.ExternalTrackerConfig().ExternalTrackerFormat + metas["regexp"] = unit.ExternalTrackerConfig().ExternalTrackerRegexpPattern } repo.MustOwner() diff --git a/models/repo_test.go b/models/repo_test.go index 425e8c01913e3..567dd0b65d271 100644 --- a/models/repo_test.go +++ b/models/repo_test.go @@ -57,6 +57,9 @@ func TestMetas(t *testing.T) { externalTracker.ExternalTrackerConfig().ExternalTrackerStyle = markup.IssueNameStyleNumeric testSuccess(markup.IssueNameStyleNumeric) + externalTracker.ExternalTrackerConfig().ExternalTrackerStyle = markup.IssueNameStyleRegexp + testSuccess(markup.IssueNameStyleRegexp) + repo, err := GetRepositoryByID(3) assert.NoError(t, err) @@ -65,6 +68,7 @@ func TestMetas(t *testing.T) { assert.Contains(t, metas, "teams") assert.Equal(t, "user3", metas["org"]) assert.Equal(t, ",owners,team1,", metas["teams"]) + } func TestGetRepositoryCount(t *testing.T) { diff --git a/models/repo_unit.go b/models/repo_unit.go index 4dac15366b95d..47acb28a03f01 100644 --- a/models/repo_unit.go +++ b/models/repo_unit.go @@ -60,9 +60,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/markup/html.go b/modules/markup/html.go index 746830720da90..4cb5f93cbbdc1 100644 --- a/modules/markup/html.go +++ b/modules/markup/html.go @@ -33,6 +33,7 @@ import ( const ( IssueNameStyleNumeric = "numeric" IssueNameStyleAlphanumeric = "alphanumeric" + IssueNameStyleRegexp = "regexp" ) var ( @@ -806,19 +807,35 @@ func issueIndexPatternProcessor(ctx *RenderContext, node *html.Node) { ) next := node.NextSibling + + _, exttrack := ctx.Metas["format"] + notNumericStyle := ctx.Metas["style"] != IssueNameStyleNumeric + foundNumeric, refNumeric := references.FindRenderizableReferenceNumeric(node.Data, exttrack && notNumericStyle) + for node != nil && node != next { - _, exttrack := ctx.Metas["format"] - alphanum := ctx.Metas["style"] == IssueNameStyleAlphanumeric + switch ctx.Metas["style"] { + case IssueNameStyleNumeric: + found = foundNumeric + ref = refNumeric + case IssueNameStyleAlphanumeric: + found, ref := references.FindRenderizableReferenceAlphanumeric(node.Data) + case IssueNameStyleRegexp: + // TODO: Compile only once, at regexp definition time + pattern, err := regexp.Compile(ctx.metas["regexp"]) + if err == nil { + return + } + found, ref := references.FindRenderizableReferenceRegexp(node.Data, pattern) + } // Repos with external issue trackers might still need to reference local PRs // We need to concern with the first one that shows up in the text, whichever it is - found, ref = references.FindRenderizableReferenceNumeric(node.Data, exttrack && alphanum) - if exttrack && alphanum { - if found2, ref2 := references.FindRenderizableReferenceAlphanumeric(node.Data); found2 { - if !found || ref2.RefLocation.Start < ref.RefLocation.Start { - found = true - ref = ref2 - } + if exttrack && notNumericStyle { + // If numeric (PR) was found and it was BEFORE the notNumeric + // pattern, use that + if foundNumeric && refNumeric.RefLocation.Start < ref.RefLocation.Start { + found = foundNumeric + ref = refNumeric } } if !found { @@ -853,7 +870,7 @@ func issueIndexPatternProcessor(ctx *RenderContext, node *html.Node) { // Decorate action keywords if actionable var keyword *html.Node - if references.IsXrefActionable(ref, exttrack, alphanum) { + if references.IsXrefActionable(ref, exttrack) { keyword = createKeyword(node.Data[ref.ActionLocation.Start:ref.ActionLocation.End]) } else { keyword = &html.Node{ diff --git a/modules/markup/html_internal_test.go b/modules/markup/html_internal_test.go index f9ef90744b173..677c08d6a4647 100644 --- a/modules/markup/html_internal_test.go +++ b/modules/markup/html_internal_test.go @@ -52,6 +52,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", @@ -195,6 +202,45 @@ func TestRender_IssueIndexPattern4(t *testing.T) { test("test issue ABCDEFGHIJ-1234567890", "test issue %s", "ABCDEFGHIJ-1234567890") } +func TestRender_IssueIndexPattern5(t *testing.T) { + setting.AppURL = AppURL + setting.AppSubURL = AppSubURL + + // regexp: render inputs without valid mentions + 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 *RenderContext) { if ctx.URLPrefix == "" { ctx.URLPrefix = AppSubURL diff --git a/modules/references/references.go b/modules/references/references.go index ef859abcc79b9..4cf79b6475c43 100644 --- a/modules/references/references.go +++ b/modules/references/references.go @@ -340,6 +340,24 @@ func FindRenderizableReferenceNumeric(content string, prOnly bool) (bool, *Rende } } +// FindRenderizableReferenceRegexp returns the first regexp unvalidated references found in a string. +func FindRenderizableReferenceRegexp(content string, pattern *Regexp) (bool, *RenderizableReference) { + match := pattern.FindStringSubmatchIndex(content) + if match == nil { + return false, nil + } + + action, location := findActionKeywords([]byte(content), match[2]) + + return true, &RenderizableReference{ + Issue: string(content[match[2]:match[3]]), + RefLocation: &RefSpan{Start: match[0], End: match[1]}, + Action: action, + ActionLocation: location, + IsPull: false, + } +} + // FindRenderizableReferenceAlphanumeric returns the first alphanumeric unvalidated references found in a string. func FindRenderizableReferenceAlphanumeric(content string) (bool, *RenderizableReference) { match := issueAlphanumericPattern.FindStringSubmatchIndex(content) @@ -537,7 +555,7 @@ func findActionKeywords(content []byte, start int) (XRefAction, *RefSpan) { } // IsXrefActionable returns true if the xref action is actionable (i.e. produces a result when resolved) -func IsXrefActionable(ref *RenderizableReference, extTracker bool, alphaNum bool) bool { +func IsXrefActionable(ref *RenderizableReference, extTracker bool) bool { if extTracker { // External issues cannot be automatically closed return false diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini index 2632531e2fb54..cda0b3fca401f 100644 --- a/options/locale/locale_en-US.ini +++ b/options/locale/locale_en-US.ini @@ -1690,6 +1690,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/routers/web/repo/setting.go b/routers/web/repo/setting.go index b104ede005221..dc7fe08c498ff 100644 --- a/routers/web/repo/setting.go +++ b/routers/web/repo/setting.go @@ -395,9 +395,10 @@ func SettingsPost(ctx *context.Context) { RepoID: repo.ID, Type: unit_model.TypeExternalTracker, Config: &models.ExternalTrackerConfig{ - ExternalTrackerURL: form.ExternalTrackerURL, - ExternalTrackerFormat: form.TrackerURLFormat, - ExternalTrackerStyle: form.TrackerIssueStyle, + ExternalTrackerURL: form.ExternalTrackerURL, + ExternalTrackerFormat: form.TrackerURLFormat, + ExternalTrackerStyle: form.TrackerIssueStyle, + ExternalTrackerRegexpPattern: form.ExternalTrackerRegexpPattern, }, }) deleteUnitTypes = append(deleteUnitTypes, unit_model.TypeIssues) diff --git a/services/forms/repo_form.go b/services/forms/repo_form.go index 7c61be5e2221d..176eba9d20110 100644 --- a/services/forms/repo_form.go +++ b/services/forms/repo_form.go @@ -140,6 +140,7 @@ type RepoSettingForm struct { ExternalTrackerURL string TrackerURLFormat string TrackerIssueStyle string + ExternalTrackerRegexpPattern string EnableCloseIssuesViaCommitInAnyBranch bool EnableProjects bool EnablePulls bool diff --git a/templates/repo/settings/options.tmpl b/templates/repo/settings/options.tmpl index 211b7da8e7cfb..5c0882a1d6170 100644 --- a/templates/repo/settings/options.tmpl +++ b/templates/repo/settings/options.tmpl @@ -361,16 +361,27 @@
{{$externalTracker := (.Repository.MustGetUnit $.UnitTypeExternalTracker)}} {{$externalTrackerStyle := $externalTracker.ExternalTrackerConfig.ExternalTrackerStyle}} - +
- +
+
+
+ + +
+
+ +
+ + +

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

diff --git a/web_src/js/features/repo-legacy.js b/web_src/js/features/repo-legacy.js index f4a8c0cf3e9fe..3d38d6456df1c 100644 --- a/web_src/js/features/repo-legacy.js +++ b/web_src/js/features/repo-legacy.js @@ -309,6 +309,15 @@ export 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 From fc1401aaf7c09e268efcabc8c0a5d63f00d7a0c0 Mon Sep 17 00:00:00 2001 From: Sandro Santilli Date: Fri, 12 Nov 2021 13:12:47 +0100 Subject: [PATCH 02/10] Fix syntax/style --- modules/markup/html.go | 8 ++++---- modules/markup/html_internal_test.go | 22 +++++++++------------- modules/references/references.go | 2 +- services/forms/repo_form.go | 2 +- web_src/js/features/repo-legacy.js | 16 ++++++++-------- 5 files changed, 23 insertions(+), 27 deletions(-) diff --git a/modules/markup/html.go b/modules/markup/html.go index 4cb5f93cbbdc1..11c9932b5386f 100644 --- a/modules/markup/html.go +++ b/modules/markup/html.go @@ -33,7 +33,7 @@ import ( const ( IssueNameStyleNumeric = "numeric" IssueNameStyleAlphanumeric = "alphanumeric" - IssueNameStyleRegexp = "regexp" + IssueNameStyleRegexp = "regexp" ) var ( @@ -818,14 +818,14 @@ func issueIndexPatternProcessor(ctx *RenderContext, node *html.Node) { found = foundNumeric ref = refNumeric case IssueNameStyleAlphanumeric: - found, ref := references.FindRenderizableReferenceAlphanumeric(node.Data) + found, ref = references.FindRenderizableReferenceAlphanumeric(node.Data) case IssueNameStyleRegexp: // TODO: Compile only once, at regexp definition time - pattern, err := regexp.Compile(ctx.metas["regexp"]) + pattern, err := regexp.Compile(ctx.Metas["regexp"]) if err == nil { return } - found, ref := references.FindRenderizableReferenceRegexp(node.Data, pattern) + found, ref = references.FindRenderizableReferenceRegexp(node.Data, pattern) } // Repos with external issue trackers might still need to reference local PRs diff --git a/modules/markup/html_internal_test.go b/modules/markup/html_internal_test.go index 677c08d6a4647..f8abe903970a4 100644 --- a/modules/markup/html_internal_test.go +++ b/modules/markup/html_internal_test.go @@ -207,40 +207,36 @@ func TestRender_IssueIndexPattern5(t *testing.T) { setting.AppSubURL = AppSubURL // regexp: render inputs without valid mentions - 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]) + test := func(s, expectedFmt string, pattern string, names []string) { + links := make([]interface{}, len(names)) + for i, name := range names { + // TODO: rename alphanumIssueLink to externalIssueLink + links[i] = alphanumIssueLink("https://someurl.com/someUser/someRepo/", "ref-issue ref-external-issue", name) } expected := fmt.Sprintf(expectedFmt, links...) - testRenderIssueIndexPattern(t, s, expected, &postProcessCtx{metas: metas}) + testRenderIssueIndexPattern(t, s, expected, &RenderContext{Metas: regexpMetas}) } - test("abc ISSUE-123 def", "abc %s def", "ISSUE-(\\d+)", - []string{"123"}, + test("abc ISSUE-123 def", "abc %s def", + "ISSUE-(\\d+)", []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}) + testRenderIssueIndexPattern(t, "will not match", "will not match", &RenderContext{Metas: regexpMetas}) } - func testRenderIssueIndexPattern(t *testing.T, input, expected string, ctx *RenderContext) { if ctx.URLPrefix == "" { ctx.URLPrefix = AppSubURL diff --git a/modules/references/references.go b/modules/references/references.go index 4cf79b6475c43..2c2b2b1ead16c 100644 --- a/modules/references/references.go +++ b/modules/references/references.go @@ -341,7 +341,7 @@ func FindRenderizableReferenceNumeric(content string, prOnly bool) (bool, *Rende } // FindRenderizableReferenceRegexp returns the first regexp unvalidated references found in a string. -func FindRenderizableReferenceRegexp(content string, pattern *Regexp) (bool, *RenderizableReference) { +func FindRenderizableReferenceRegexp(content string, pattern *regexp.Regexp) (bool, *RenderizableReference) { match := pattern.FindStringSubmatchIndex(content) if match == nil { return false, nil diff --git a/services/forms/repo_form.go b/services/forms/repo_form.go index 176eba9d20110..55de4e87ba3b2 100644 --- a/services/forms/repo_form.go +++ b/services/forms/repo_form.go @@ -140,7 +140,7 @@ type RepoSettingForm struct { ExternalTrackerURL string TrackerURLFormat string TrackerIssueStyle string - ExternalTrackerRegexpPattern string + ExternalTrackerRegexpPattern string EnableCloseIssuesViaCommitInAnyBranch bool EnableProjects bool EnablePulls bool diff --git a/web_src/js/features/repo-legacy.js b/web_src/js/features/repo-legacy.js index 3d38d6456df1c..3ab6d7074689f 100644 --- a/web_src/js/features/repo-legacy.js +++ b/web_src/js/features/repo-legacy.js @@ -309,15 +309,15 @@ export 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') - } + $('.enable-system-pick').on('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 From ce82e7c6c78c51345f8807c65733d4a0bab2a533 Mon Sep 17 00:00:00 2001 From: techknowlogick Date: Tue, 23 Apr 2019 20:59:25 -0400 Subject: [PATCH 03/10] Update repo.go --- models/repo.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/models/repo.go b/models/repo.go index 78930c716745f..9d058927eb9f2 100644 --- a/models/repo.go +++ b/models/repo.go @@ -497,8 +497,6 @@ func (repo *Repository) ComposeMetas() map[string]string { default: metas["style"] = markup.IssueNameStyleNumeric } - metas["format"] = unit.ExternalTrackerConfig().ExternalTrackerFormat - metas["regexp"] = unit.ExternalTrackerConfig().ExternalTrackerRegexpPattern } repo.MustOwner() From 7ac7a0e89cc309707ff1dac90fc7b172e3f02871 Mon Sep 17 00:00:00 2001 From: Sandro Santilli Date: Fri, 12 Nov 2021 18:58:06 +0100 Subject: [PATCH 04/10] Set metas['regexp'] --- models/repo.go | 1 + 1 file changed, 1 insertion(+) diff --git a/models/repo.go b/models/repo.go index 9d058927eb9f2..d0426b9de7d82 100644 --- a/models/repo.go +++ b/models/repo.go @@ -494,6 +494,7 @@ func (repo *Repository) ComposeMetas() map[string]string { metas["style"] = markup.IssueNameStyleAlphanumeric case markup.IssueNameStyleRegexp: metas["style"] = markup.IssueNameStyleRegexp + metas["regexp"] = unit.ExternalTrackerConfig().ExternalTrackerRegexpPattern default: metas["style"] = markup.IssueNameStyleNumeric } From 777dc4106ad6b0dcde653fbe1b48ba8121e4596d Mon Sep 17 00:00:00 2001 From: Sandro Santilli Date: Fri, 12 Nov 2021 19:17:38 +0100 Subject: [PATCH 05/10] gofmt --- modules/markup/html.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/markup/html.go b/modules/markup/html.go index 11c9932b5386f..432b90c7907ee 100644 --- a/modules/markup/html.go +++ b/modules/markup/html.go @@ -33,7 +33,7 @@ import ( const ( IssueNameStyleNumeric = "numeric" IssueNameStyleAlphanumeric = "alphanumeric" - IssueNameStyleRegexp = "regexp" + IssueNameStyleRegexp = "regexp" ) var ( From 68b718b0c47732b21661488b6cf7d130eae2f000 Mon Sep 17 00:00:00 2001 From: wxiaoguang Date: Thu, 9 Jun 2022 23:40:15 +0800 Subject: [PATCH 06/10] fix some tests --- modules/markup/html.go | 30 ++++++++++++++-------------- modules/markup/html_internal_test.go | 9 +++++++-- modules/references/references.go | 4 ++-- 3 files changed, 24 insertions(+), 19 deletions(-) diff --git a/modules/markup/html.go b/modules/markup/html.go index 755efb8e3fa47..658f3b244349f 100644 --- a/modules/markup/html.go +++ b/modules/markup/html.go @@ -817,21 +817,22 @@ func issueIndexPatternProcessor(ctx *RenderContext, node *html.Node) { next := node.NextSibling - _, exttrack := ctx.Metas["format"] - notNumericStyle := ctx.Metas["style"] != IssueNameStyleNumeric - foundNumeric, refNumeric := references.FindRenderizableReferenceNumeric(node.Data, exttrack && notNumericStyle) - for node != nil && node != next { + _, hasExtTrackFormat := ctx.Metas["format"] + + // Repos with external issue trackers might still need to reference local PRs + // We need to concern with the first one that shows up in the text, whichever it is + isNumericStyle := ctx.Metas["style"] == "" || ctx.Metas["style"] == IssueNameStyleNumeric + foundNumeric, refNumeric := references.FindRenderizableReferenceNumeric(node.Data, hasExtTrackFormat && !isNumericStyle) + switch ctx.Metas["style"] { - case IssueNameStyleNumeric: - found = foundNumeric - ref = refNumeric + case "", IssueNameStyleNumeric: + found, ref = foundNumeric, refNumeric case IssueNameStyleAlphanumeric: found, ref = references.FindRenderizableReferenceAlphanumeric(node.Data) case IssueNameStyleRegexp: - // TODO: Compile only once, at regexp definition time - pattern, err := regexp.Compile(ctx.Metas["regexp"]) - if err == nil { + pattern, err := regexp.Compile(ctx.Metas["regexp"]) // TODO: Compile only once, at regexp definition time + if err != nil { return } found, ref = references.FindRenderizableReferenceRegexp(node.Data, pattern) @@ -839,9 +840,8 @@ func issueIndexPatternProcessor(ctx *RenderContext, node *html.Node) { // Repos with external issue trackers might still need to reference local PRs // We need to concern with the first one that shows up in the text, whichever it is - if exttrack && notNumericStyle { - // If numeric (PR) was found and it was BEFORE the notNumeric - // pattern, use that + if hasExtTrackFormat && !isNumericStyle { + // If numeric (PR) was found, and it was BEFORE the non-numeric pattern, use that if foundNumeric && refNumeric.RefLocation.Start < ref.RefLocation.Start { found = foundNumeric ref = refNumeric @@ -853,7 +853,7 @@ func issueIndexPatternProcessor(ctx *RenderContext, node *html.Node) { var link *html.Node reftext := node.Data[ref.RefLocation.Start:ref.RefLocation.End] - if exttrack && !ref.IsPull { + if hasExtTrackFormat && !ref.IsPull { ctx.Metas["index"] = ref.Issue res, err := vars.Expand(ctx.Metas["format"], ctx.Metas) @@ -886,7 +886,7 @@ func issueIndexPatternProcessor(ctx *RenderContext, node *html.Node) { // Decorate action keywords if actionable var keyword *html.Node - if references.IsXrefActionable(ref, exttrack) { + if references.IsXrefActionable(ref, hasExtTrackFormat) { keyword = createKeyword(node.Data[ref.ActionLocation.Start:ref.ActionLocation.End]) } else { keyword = &html.Node{ diff --git a/modules/markup/html_internal_test.go b/modules/markup/html_internal_test.go index 73c89a79bc664..2d4a18d287a50 100644 --- a/modules/markup/html_internal_test.go +++ b/modules/markup/html_internal_test.go @@ -211,8 +211,13 @@ func TestRender_IssueIndexPattern5(t *testing.T) { links[i] = externalIssueLink("https://someurl.com/someUser/someRepo/", "ref-issue ref-external-issue", name) } + metas := map[string]string{} + for k, v := range regexpMetas { + metas[k] = v + } + metas["regexp"] = pattern expected := fmt.Sprintf(expectedFmt, links...) - testRenderIssueIndexPattern(t, s, expected, &RenderContext{Metas: regexpMetas}) + testRenderIssueIndexPattern(t, s, expected, &RenderContext{Metas: metas}) } test("abc ISSUE-123 def", "abc %s def", @@ -242,7 +247,7 @@ func testRenderIssueIndexPattern(t *testing.T, input, expected string, ctx *Rend var buf strings.Builder err := postProcess(ctx, []processor{issueIndexPatternProcessor}, strings.NewReader(input), &buf) assert.NoError(t, err) - assert.Equal(t, expected, buf.String()) + assert.Equal(t, expected, buf.String(), "input=%q", input) } func TestRender_AutoLink(t *testing.T) { diff --git a/modules/references/references.go b/modules/references/references.go index 22e04bcbb559f..7f5086d093e5f 100644 --- a/modules/references/references.go +++ b/modules/references/references.go @@ -354,14 +354,14 @@ func FindRenderizableReferenceNumeric(content string, prOnly bool) (bool, *Rende // FindRenderizableReferenceRegexp returns the first regexp unvalidated references found in a string. func FindRenderizableReferenceRegexp(content string, pattern *regexp.Regexp) (bool, *RenderizableReference) { match := pattern.FindStringSubmatchIndex(content) - if match == nil { + if len(match) < 4 { return false, nil } action, location := findActionKeywords([]byte(content), match[2]) return true, &RenderizableReference{ - Issue: string(content[match[2]:match[3]]), + Issue: content[match[2]:match[3]], RefLocation: &RefSpan{Start: match[0], End: match[1]}, Action: action, ActionLocation: location, From 0b88fbc63cab0a1442594cd8793da6e8e9e2c774 Mon Sep 17 00:00:00 2001 From: wxiaoguang Date: Fri, 10 Jun 2022 00:08:17 +0800 Subject: [PATCH 07/10] fix more tests --- models/repo/fork.go | 1 + models/repo/main_test.go | 4 ++-- modules/markup/html_internal_test.go | 25 ++++++++++++------------- 3 files changed, 15 insertions(+), 15 deletions(-) diff --git a/models/repo/fork.go b/models/repo/fork.go index 938bbae17e16b..b54c61c425fd7 100644 --- a/models/repo/fork.go +++ b/models/repo/fork.go @@ -9,6 +9,7 @@ import ( "code.gitea.io/gitea/models/db" user_model "code.gitea.io/gitea/models/user" + "xorm.io/builder" ) diff --git a/models/repo/main_test.go b/models/repo/main_test.go index eb04aa8227d8d..f6d704ca65874 100644 --- a/models/repo/main_test.go +++ b/models/repo/main_test.go @@ -8,12 +8,12 @@ import ( "path/filepath" "testing" + "code.gitea.io/gitea/models/unittest" + _ "code.gitea.io/gitea/models" // register table model _ "code.gitea.io/gitea/models/perm/access" // register table model _ "code.gitea.io/gitea/models/repo" // register table model _ "code.gitea.io/gitea/models/user" // register table model - - "code.gitea.io/gitea/models/unittest" ) func TestMain(m *testing.M) { diff --git a/modules/markup/html_internal_test.go b/modules/markup/html_internal_test.go index 2d4a18d287a50..ca154ac86b6c7 100644 --- a/modules/markup/html_internal_test.go +++ b/modules/markup/html_internal_test.go @@ -205,37 +205,36 @@ func TestRender_IssueIndexPattern5(t *testing.T) { setting.AppURL = TestAppURL // regexp: render inputs without valid mentions - test := func(s, expectedFmt, pattern string, names []string) { - links := make([]interface{}, len(names)) - for i, name := range names { - links[i] = externalIssueLink("https://someurl.com/someUser/someRepo/", "ref-issue ref-external-issue", name) + test := func(s, expectedFmt, pattern string, ids, 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), "ref-issue ref-external-issue", names[i]) } - metas := map[string]string{} - for k, v := range regexpMetas { - metas[k] = v - } - metas["regexp"] = pattern expected := fmt.Sprintf(expectedFmt, links...) testRenderIssueIndexPattern(t, s, expected, &RenderContext{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{"(ISSUE 123)", "(TASK 456)"}, + test("abc ISSUE-123 def", "abc %s def", + "(ISSUE-(\\d+))", + []string{"ISSUE-123"}, + []string{"ISSUE-123"}, ) - metas := regexpMetas - metas["regexp"] = "no matches" testRenderIssueIndexPattern(t, "will not match", "will not match", &RenderContext{Metas: regexpMetas}) } From 60217b4996fae5476399bc8e5004736754b34761 Mon Sep 17 00:00:00 2001 From: wxiaoguang Date: Fri, 10 Jun 2022 00:37:05 +0800 Subject: [PATCH 08/10] refactor frontend --- templates/repo/settings/options.tmpl | 14 +++++++------- web_src/js/features/repo-legacy.js | 12 ++++-------- 2 files changed, 11 insertions(+), 15 deletions(-) diff --git a/templates/repo/settings/options.tmpl b/templates/repo/settings/options.tmpl index cee65fe4e8038..67a98aff43cda 100644 --- a/templates/repo/settings/options.tmpl +++ b/templates/repo/settings/options.tmpl @@ -361,24 +361,24 @@
{{$externalTracker := (.Repository.MustGetUnit $.UnitTypeExternalTracker)}} {{$externalTrackerStyle := $externalTracker.ExternalTrackerConfig.ExternalTrackerStyle}} - - + +
- - + +
- - + +
-
+

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

diff --git a/web_src/js/features/repo-legacy.js b/web_src/js/features/repo-legacy.js index 7c098adb3672c..6cdde6a1e4c27 100644 --- a/web_src/js/features/repo-legacy.js +++ b/web_src/js/features/repo-legacy.js @@ -462,14 +462,10 @@ export function initRepository() { if (typeof $(this).data('context') !== 'undefined') $($(this).data('context')).addClass('disabled'); } }); - $('.enable-system-pick').on('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'); - } - } + const $trackerIssueStyleRadios = $('.js-tracker-issue-style'); + $trackerIssueStyleRadios.on('change input', () => { + const checkedVal = $trackerIssueStyleRadios.filter(':checked').val(); + $('#tracker-issue-style-regex-box').toggleClass('disabled', checkedVal !== 'regexp'); }); } From 2639ced2078e120ab8c1f322986ea33576623cce Mon Sep 17 00:00:00 2001 From: wxiaoguang Date: Fri, 10 Jun 2022 01:24:26 +0800 Subject: [PATCH 09/10] use LRU cache for regexp --- modules/markup/html.go | 3 +- modules/regexplru/regexplru.go | 45 +++++++++++++++++++++++++++++ modules/regexplru/regexplru_test.go | 27 +++++++++++++++++ 3 files changed, 74 insertions(+), 1 deletion(-) create mode 100644 modules/regexplru/regexplru.go create mode 100644 modules/regexplru/regexplru_test.go diff --git a/modules/markup/html.go b/modules/markup/html.go index 658f3b244349f..69d9ba3ef2a46 100644 --- a/modules/markup/html.go +++ b/modules/markup/html.go @@ -20,6 +20,7 @@ import ( "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/markup/common" "code.gitea.io/gitea/modules/references" + "code.gitea.io/gitea/modules/regexplru" "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/templates/vars" "code.gitea.io/gitea/modules/util" @@ -831,7 +832,7 @@ func issueIndexPatternProcessor(ctx *RenderContext, node *html.Node) { case IssueNameStyleAlphanumeric: found, ref = references.FindRenderizableReferenceAlphanumeric(node.Data) case IssueNameStyleRegexp: - pattern, err := regexp.Compile(ctx.Metas["regexp"]) // TODO: Compile only once, at regexp definition time + pattern, err := regexplru.GetCompiled(ctx.Metas["regexp"]) if err != nil { return } diff --git a/modules/regexplru/regexplru.go b/modules/regexplru/regexplru.go new file mode 100644 index 0000000000000..97c7cff4c1009 --- /dev/null +++ b/modules/regexplru/regexplru.go @@ -0,0 +1,45 @@ +// Copyright 2022 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package regexplru + +import ( + "regexp" + + "code.gitea.io/gitea/modules/log" + + lru "github.com/hashicorp/golang-lru" +) + +var lruCache *lru.Cache + +func init() { + var err error + lruCache, err = lru.New(1000) + if err != nil { + log.Fatal("failed to new LRU cache, err: %v", err) + } +} + +// GetCompiled works like regexp.Compile, the compiled expr or error is stored in LRU cache +func GetCompiled(expr string) (r *regexp.Regexp, err error) { + v, ok := lruCache.Get(expr) + if !ok { + r, err = regexp.Compile(expr) + if err != nil { + lruCache.Add(expr, err) + return nil, err + } + lruCache.Add(expr, r) + } else { + r, ok = v.(*regexp.Regexp) + if !ok { + if err, ok = v.(error); ok { + return nil, err + } + panic("impossible") + } + } + return r, nil +} diff --git a/modules/regexplru/regexplru_test.go b/modules/regexplru/regexplru_test.go new file mode 100644 index 0000000000000..041f0dcfb9278 --- /dev/null +++ b/modules/regexplru/regexplru_test.go @@ -0,0 +1,27 @@ +// Copyright 2022 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package regexplru + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestRegexpLru(t *testing.T) { + r, err := GetCompiled("a") + assert.NoError(t, err) + assert.True(t, r.MatchString("a")) + + r, err = GetCompiled("a") + assert.NoError(t, err) + assert.True(t, r.MatchString("a")) + + assert.EqualValues(t, 1, lruCache.Len()) + + _, err = GetCompiled("(") + assert.Error(t, err) + assert.EqualValues(t, 2, lruCache.Len()) +} From 20eb0d15607d1e887d13cdd2960da0237466c8ea Mon Sep 17 00:00:00 2001 From: 6543 <6543@obermui.de> Date: Thu, 9 Jun 2022 20:28:03 +0200 Subject: [PATCH 10/10] Update modules/markup/html_internal_test.go --- modules/markup/html_internal_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/markup/html_internal_test.go b/modules/markup/html_internal_test.go index ca154ac86b6c7..25b0f7b7a5ef1 100644 --- a/modules/markup/html_internal_test.go +++ b/modules/markup/html_internal_test.go @@ -21,7 +21,7 @@ const ( TestRepoURL = TestAppURL + TestOrgRepo + "/" ) -// alphanumLink an HTML link to an alphanumeric-style issue +// externalIssueLink an HTML link to an alphanumeric-style issue func externalIssueLink(baseURL, class, name string) string { return link(util.URLJoin(baseURL, name), class, name) }