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:
Gregorius Bima Kharisma Wicaksana
2025-12-28 00:05:24 +07:00
committed by GitHub
parent 19e1997ee2
commit 83527d3f8a
2 changed files with 80 additions and 6 deletions
+56
View File
@@ -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