Skip to content

Commit 24c38e5

Browse files
authored
Merge branch 'main' into feature/workflow-graph
2 parents fbc6abf + 83527d3 commit 24c38e5

File tree

2 files changed

+80
-6
lines changed

2 files changed

+80
-6
lines changed

modules/references/references.go

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -248,7 +248,7 @@ func FindAllIssueReferencesMarkdown(content string) []IssueReference {
248248

249249
func findAllIssueReferencesMarkdown(content string) []*rawReference {
250250
bcontent, links := mdstripper.StripMarkdownBytes([]byte(content))
251-
return findAllIssueReferencesBytes(bcontent, links)
251+
return findAllIssueReferencesBytes(bcontent, links, []byte(content))
252252
}
253253

254254
func convertFullHTMLReferencesToShortRefs(re *regexp.Regexp, contentBytes *[]byte) {
@@ -326,7 +326,7 @@ func FindAllIssueReferences(content string) []IssueReference {
326326
} else {
327327
log.Debug("No GiteaIssuePullPattern pattern")
328328
}
329-
return rawToIssueReferenceList(findAllIssueReferencesBytes(contentBytes, []string{}))
329+
return rawToIssueReferenceList(findAllIssueReferencesBytes(contentBytes, []string{}, nil))
330330
}
331331

332332
// FindRenderizableReferenceNumeric returns the first unvalidated reference found in a string.
@@ -406,7 +406,8 @@ func FindRenderizableReferenceAlphanumeric(content string) *RenderizableReferenc
406406
}
407407

408408
// FindAllIssueReferencesBytes returns a list of unvalidated references found in a byte slice.
409-
func findAllIssueReferencesBytes(content []byte, links []string) []*rawReference {
409+
// originalContent is optional and used to detect closing/reopening keywords for URL references.
410+
func findAllIssueReferencesBytes(content []byte, links []string, originalContent []byte) []*rawReference {
410411
ret := make([]*rawReference, 0, 10)
411412
pos := 0
412413

@@ -470,10 +471,27 @@ func findAllIssueReferencesBytes(content []byte, links []string) []*rawReference
470471
default:
471472
continue
472473
}
473-
// Note: closing/reopening keywords not supported with URLs
474-
bytes := []byte(parts[1] + "/" + parts[2] + sep + parts[4])
475-
if ref := getCrossReference(bytes, 0, len(bytes), true, false); ref != nil {
474+
refBytes := []byte(parts[1] + "/" + parts[2] + sep + parts[4])
475+
if ref := getCrossReference(refBytes, 0, len(refBytes), true, false); ref != nil {
476476
ref.refLocation = nil
477+
// Detect closing/reopening keywords by finding the URL position in original content
478+
if originalContent != nil {
479+
if idx := bytes.Index(originalContent, []byte(link)); idx > 0 {
480+
// For markdown links [text](url), find the opening bracket before the URL
481+
// to properly detect keywords like "closes [text](url)"
482+
searchStart := idx
483+
if idx >= 2 && originalContent[idx-1] == '(' {
484+
// Find the matching '[' for this markdown link
485+
bracketIdx := bytes.LastIndex(originalContent[:idx-1], []byte{'['})
486+
if bracketIdx >= 0 {
487+
searchStart = bracketIdx
488+
}
489+
}
490+
action, location := findActionKeywords(originalContent, searchStart)
491+
ref.action = action
492+
ref.actionLocation = location
493+
}
494+
}
477495
ret = append(ret, ref)
478496
}
479497
}

modules/references/references_test.go

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -227,6 +227,62 @@ func TestFindAllIssueReferences(t *testing.T) {
227227

228228
testFixtures(t, fixtures, "default")
229229

230+
// Test closing/reopening keywords with URLs (issue #27549)
231+
// Uses the same AppURL as testFixtures (https://gitea.com:3000/)
232+
urlFixtures := []testFixture{
233+
{
234+
"Closes [this issue](https://gitea.com:3000/user/repo/issues/123)",
235+
[]testResult{
236+
{123, "user", "repo", "123", false, XRefActionCloses, nil, &RefSpan{Start: 0, End: 6}, ""},
237+
},
238+
},
239+
{
240+
"This fixes [#456](https://gitea.com:3000/org/project/issues/456)",
241+
[]testResult{
242+
{456, "org", "project", "456", false, XRefActionCloses, nil, &RefSpan{Start: 5, End: 10}, ""},
243+
},
244+
},
245+
{
246+
"Reopens [PR](https://gitea.com:3000/owner/repo/pulls/789)",
247+
[]testResult{
248+
{789, "owner", "repo", "789", true, XRefActionReopens, nil, &RefSpan{Start: 0, End: 7}, ""},
249+
},
250+
},
251+
{
252+
"See [issue](https://gitea.com:3000/user/repo/issues/100) but closes [another](https://gitea.com:3000/user/repo/issues/200)",
253+
[]testResult{
254+
{100, "user", "repo", "100", false, XRefActionNone, nil, nil, ""},
255+
{200, "user", "repo", "200", false, XRefActionCloses, nil, &RefSpan{Start: 61, End: 67}, ""},
256+
},
257+
},
258+
}
259+
260+
testFixtures(t, urlFixtures, "url-keywords")
261+
262+
// Test bare URLs (not markdown links) with closing keywords
263+
// These use FindAllIssueReferences (non-markdown) which converts full URLs to short refs first
264+
setting.AppURL = "https://gitea.com:3000/"
265+
bareURLTests := []struct {
266+
name string
267+
input string
268+
expected XRefAction
269+
}{
270+
{"Fixes bare URL", "Fixes https://gitea.com:3000/org/project/issues/456", XRefActionCloses},
271+
{"Fixes with colon", "Fixes: https://gitea.com:3000/org/project/issues/456", XRefActionCloses},
272+
{"Closes bare URL", "Closes https://gitea.com:3000/user/repo/issues/123", XRefActionCloses},
273+
{"Closes with colon", "Closes: https://gitea.com:3000/user/repo/issues/123", XRefActionCloses},
274+
}
275+
276+
for _, tt := range bareURLTests {
277+
t.Run(tt.name, func(t *testing.T) {
278+
refs := FindAllIssueReferences(tt.input)
279+
assert.Len(t, refs, 1, "Expected 1 reference for: %s", tt.input)
280+
if len(refs) > 0 {
281+
assert.Equal(t, tt.expected, refs[0].Action, "Expected action %v for: %s", tt.expected, tt.input)
282+
}
283+
})
284+
}
285+
230286
type alnumFixture struct {
231287
input string
232288
issue string

0 commit comments

Comments
 (0)