Support closing keywords with URL references (#36221)
## Summary This PR adds support for closing keywords (`closes`, `fixes`, `reopens`, etc.) with full URL references in markdown links. **Before:** - `closes #123` ✅ works - `closes org/repo#123` ✅ works - `Closes [this issue](https://gitea.io/user/repo/issues/123)` ❌ didn't work - `Fixes [#456](https://gitea.io/org/project/issues/456)` ❌ didn't work **After:** All of the above now work correctly. ## Problem When users reference issues using full URLs in markdown links (e.g., `Closes [this issue](https://gitea.io/user/repo/issues/123)`), the closing keywords were not detected. This was because the URL processing code explicitly stated: ```go // Note: closing/reopening keywords not supported with URLs ``` Both methods of writing the reference render the same in the UI, so users expected the closing keywords to behave the same. ## Solution The fix works by: 1. Passing the original (unstripped) content to `findAllIssueReferencesBytes` 2. When processing URL links from markdown, finding the URL position in the original content 3. For markdown links `[text](url)`, finding the opening bracket `[` position 4. Using that position to detect closing keywords before the link ## Testing Added test cases for: - `Closes [this issue](url)` - single URL with closing keyword - `This fixes [#456](url)` - keyword in middle of text - `Reopens [PR](url)` - reopen keyword with pull request URL - Multiple URLs where only one has a closing keyword All existing tests continue to pass. Fixes #27549
This commit is contained in:
committed by
GitHub
parent
19e1997ee2
commit
83527d3f8a
@@ -227,6 +227,62 @@ func TestFindAllIssueReferences(t *testing.T) {
|
||||
|
||||
testFixtures(t, fixtures, "default")
|
||||
|
||||
// Test closing/reopening keywords with URLs (issue #27549)
|
||||
// Uses the same AppURL as testFixtures (https://gitea.com:3000/)
|
||||
urlFixtures := []testFixture{
|
||||
{
|
||||
"Closes [this issue](https://gitea.com:3000/user/repo/issues/123)",
|
||||
[]testResult{
|
||||
{123, "user", "repo", "123", false, XRefActionCloses, nil, &RefSpan{Start: 0, End: 6}, ""},
|
||||
},
|
||||
},
|
||||
{
|
||||
"This fixes [#456](https://gitea.com:3000/org/project/issues/456)",
|
||||
[]testResult{
|
||||
{456, "org", "project", "456", false, XRefActionCloses, nil, &RefSpan{Start: 5, End: 10}, ""},
|
||||
},
|
||||
},
|
||||
{
|
||||
"Reopens [PR](https://gitea.com:3000/owner/repo/pulls/789)",
|
||||
[]testResult{
|
||||
{789, "owner", "repo", "789", true, XRefActionReopens, nil, &RefSpan{Start: 0, End: 7}, ""},
|
||||
},
|
||||
},
|
||||
{
|
||||
"See [issue](https://gitea.com:3000/user/repo/issues/100) but closes [another](https://gitea.com:3000/user/repo/issues/200)",
|
||||
[]testResult{
|
||||
{100, "user", "repo", "100", false, XRefActionNone, nil, nil, ""},
|
||||
{200, "user", "repo", "200", false, XRefActionCloses, nil, &RefSpan{Start: 61, End: 67}, ""},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
testFixtures(t, urlFixtures, "url-keywords")
|
||||
|
||||
// Test bare URLs (not markdown links) with closing keywords
|
||||
// These use FindAllIssueReferences (non-markdown) which converts full URLs to short refs first
|
||||
setting.AppURL = "https://gitea.com:3000/"
|
||||
bareURLTests := []struct {
|
||||
name string
|
||||
input string
|
||||
expected XRefAction
|
||||
}{
|
||||
{"Fixes bare URL", "Fixes https://gitea.com:3000/org/project/issues/456", XRefActionCloses},
|
||||
{"Fixes with colon", "Fixes: https://gitea.com:3000/org/project/issues/456", XRefActionCloses},
|
||||
{"Closes bare URL", "Closes https://gitea.com:3000/user/repo/issues/123", XRefActionCloses},
|
||||
{"Closes with colon", "Closes: https://gitea.com:3000/user/repo/issues/123", XRefActionCloses},
|
||||
}
|
||||
|
||||
for _, tt := range bareURLTests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
refs := FindAllIssueReferences(tt.input)
|
||||
assert.Len(t, refs, 1, "Expected 1 reference for: %s", tt.input)
|
||||
if len(refs) > 0 {
|
||||
assert.Equal(t, tt.expected, refs[0].Action, "Expected action %v for: %s", tt.expected, tt.input)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
type alnumFixture struct {
|
||||
input string
|
||||
issue string
|
||||
|
||||
Reference in New Issue
Block a user