Skip to content
4 changes: 4 additions & 0 deletions models/repo.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
3 changes: 3 additions & 0 deletions models/repo_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
7 changes: 4 additions & 3 deletions models/repo_unit.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
1 change: 1 addition & 0 deletions modules/auth/repo_form.go
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,7 @@ type RepoSettingForm struct {
ExternalTrackerURL string
TrackerURLFormat string
TrackerIssueStyle string
ExternalTrackerRegexpPattern string
EnablePulls bool
PullsIgnoreWhitespace bool
PullsAllowMerge bool
Expand Down
46 changes: 34 additions & 12 deletions modules/markup/html.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import (
const (
IssueNameStyleNumeric = "numeric"
IssueNameStyleAlphanumeric = "alphanumeric"
IssueNameStyleRegexp = "regexp"
)

var (
Expand Down Expand Up @@ -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) {
Expand Down
41 changes: 41 additions & 0 deletions modules/markup/html_internal_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -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)
Expand Down
3 changes: 3 additions & 0 deletions options/locale/locale_en-US.ini
Original file line number Diff line number Diff line change
Expand Up @@ -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 <code>{index}</code>.
settings.tracker_url_format_desc = Use the placeholders <code>{user}</code>, <code>{repo}</code> and <code>{index}</code> 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
Expand Down
9 changes: 9 additions & 0 deletions public/js/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
7 changes: 4 additions & 3 deletions routers/repo/setting.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
15 changes: 13 additions & 2 deletions templates/repo/settings/options.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -203,16 +203,27 @@
<div class="ui radio checkbox">
{{$externalTracker := (.Repository.MustGetUnit $.UnitTypeExternalTracker)}}
{{$externalTrackerStyle := $externalTracker.ExternalTrackerConfig.ExternalTrackerStyle}}
<input class="hidden" tabindex="0" name="tracker_issue_style" type="radio" value="numeric" {{if $externalTrackerStyle}}{{if eq $externalTrackerStyle "numeric"}}checked=""{{end}}{{end}}/>
<input class="hidden enable-system-pick" tabindex="0" name="tracker_issue_style" type="radio" value="numeric" data-context="regexp" data-target="#tracker_regexp_pattern_box" {{if $externalTrackerStyle}}{{if eq $externalTrackerStyle "numeric"}}checked=""{{end}}{{end}}/>
<label>{{.i18n.Tr "repo.settings.tracker_issue_style.numeric"}} <span class="ui light grey text">(#1234)</span></label>
</div>
</div>
<div class="field">
<div class="ui radio checkbox">
<input class="hidden" tabindex="0" name="tracker_issue_style" type="radio" value="alphanumeric" {{if $externalTrackerStyle}}{{if eq $externalTracker.ExternalTrackerConfig.ExternalTrackerStyle "alphanumeric"}}checked=""{{end}}{{end}} />
<input class="hidden enable-system-pick" tabindex="0" name="tracker_issue_style" type="radio" value="alphanumeric" data-context="regexp" data-target="#tracker_regexp_pattern_box" {{if $externalTrackerStyle}}{{if eq $externalTracker.ExternalTrackerConfig.ExternalTrackerStyle "alphanumeric"}}checked=""{{end}}{{end}} />
<label>{{.i18n.Tr "repo.settings.tracker_issue_style.alphanumeric"}} <span class="ui light grey text">(ABC-123, DEFG-234)</span></label>
</div>
</div>
<div class="field">
<div class="ui radio checkbox">
<input class="hidden enable-system-pick" tabindex="0" name="tracker_issue_style" type="radio" value="regexp" data-context="regexp" data-target="#tracker_regexp_pattern_box" {{if $externalTrackerStyle}}{{if eq $externalTracker.ExternalTrackerConfig.ExternalTrackerStyle "regexp"}}checked=""{{end}}{{end}} />
<label>{{.i18n.Tr "repo.settings.tracker_issue_style.regexp"}} <span class="ui light grey text">((?:TASK|ISSUE) (\d+))</span></label>
</div>
</div>
</div>
<div class="field {{if ne $externalTracker.ExternalTrackerConfig.ExternalTrackerStyle "regexp"}}disabled{{end}}" id="tracker_regexp_pattern_box">
<label for="external_tracker_regexp_pattern">{{.i18n.Tr "repo.settings.tracker_issue_style.regexp_pattern"}}</label>
<input id="external_tracker_regexp_pattern" name="external_tracker_regexp_pattern" value="{{(.Repository.MustGetUnit $.UnitTypeExternalTracker).ExternalTrackerConfig.ExternalTrackerRegexpPattern}}">
<p class="help">{{.i18n.Tr "repo.settings.tracker_issue_style.regexp_pattern_desc" | Str2html}}</p>
</div>
</div>
</div>
Expand Down