Only turn links to current instance into hash links (#36237)

Given the following markdown:

```
a832c723cd
19fe6cdf81
a832c723cd
```

Previously, all links would turn into hash link, even ones to external
sites:

<img width="849" height="89" alt="Screenshot 2025-12-23 at 19 19 13"
src="https://github.com/user-attachments/assets/2ad35a18-4542-40a4-a838-7ab8ac8bc047"
/>

After this change, only links to the current instance, as identified by
`setting.AppURL` are turned into hash links:

<img width="850" height="87" alt="Screenshot 2025-12-23 at 19 18 56"
src="https://github.com/user-attachments/assets/2c49a5b2-426c-4a82-a610-9b9da8dcfff9"
/>

There is still one notable [difference with
GitHub](https://github.com/silverwind/symlink-test/issues/20#issuecomment-3687535938)
where the second link should render like `user/repo@<hash>`, not
`<hash>` as currently, I would like to fix that here as well.

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
silverwind
2026-02-16 11:27:49 +01:00
committed by GitHub
parent a0160694b9
commit 0e99932530
4 changed files with 38 additions and 9 deletions

View File

@@ -8,6 +8,7 @@ import (
"strings"
"code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/httplib"
"code.gitea.io/gitea/modules/references"
"code.gitea.io/gitea/modules/util"
@@ -121,6 +122,11 @@ func fullHashPatternProcessor(ctx *RenderContext, node *html.Node) {
if ret.QueryHash != "" {
text += " (" + ret.QueryHash + ")"
}
// only turn commit links to the current instance into hash link
if !httplib.IsCurrentGiteaSiteURL(ctx, ret.FullURL) {
node = node.NextSibling
continue
}
replaceContent(node, ret.PosStart, ret.PosEnd, createCodeLink(ret.FullURL, text, "commit"))
node = node.NextSibling.NextSibling
}
@@ -167,6 +173,12 @@ func comparePatternProcessor(ctx *RenderContext, node *html.Node) {
}
}
// only turn compare links to the current instance into hash link
if !httplib.IsCurrentGiteaSiteURL(ctx, urlFull) {
node = node.NextSibling
continue
}
text := text1 + textDots + text2
if hash != "" {
text += " (" + hash + ")"

View File

@@ -299,7 +299,7 @@ func TestRender_AutoLink(t *testing.T) {
// render other commit URLs
tmp = "https://external-link.gitea.io/go-gitea/gitea/commit/d8a994ef243349f321568f9e36d5c3f444b99cae#diff-2"
test(tmp, "<a href=\""+tmp+"\" class=\"commit\"><code>d8a994ef24 (diff-2)</code></a>")
test(tmp, "<a href=\""+tmp+"\">"+tmp+"</a>")
}
func TestRender_FullIssueURLs(t *testing.T) {

View File

@@ -71,6 +71,7 @@ func TestRender_Commits(t *testing.T) {
}
func TestRender_CrossReferences(t *testing.T) {
defer testModule.MockVariableValue(&setting.AppURL, markup.TestAppURL)()
defer testModule.MockVariableValue(&markup.RenderBehaviorForTesting.DisableAdditionalAttributes, true)()
test := func(input, expected string) {
rctx := markup.NewTestRenderContext(markup.TestAppURL, localMetas).WithRelativePath("a.md")
@@ -98,17 +99,17 @@ func TestRender_CrossReferences(t *testing.T) {
util.URLJoin(markup.TestAppURL, "gogitea", "some-repo-name", "issues", "12345"),
`<p><a href="`+util.URLJoin(markup.TestAppURL, "gogitea", "some-repo-name", "issues", "12345")+`" class="ref-issue" rel="nofollow">gogitea/some-repo-name#12345</a></p>`)
inputURL := "https://host/a/b/commit/0123456789012345678901234567890123456789/foo.txt?a=b#L2-L3"
inputURL := setting.AppURL + "a/b/commit/0123456789012345678901234567890123456789/foo.txt?a=b#L2-L3"
test(
inputURL,
`<p><a href="`+inputURL+`" rel="nofollow"><code>0123456789/foo.txt (L2-L3)</code></a></p>`)
inputURL = "https://example.com/repo/owner/archive/0123456789012345678901234567890123456789.tar.gz"
inputURL = setting.AppURL + "repo/owner/archive/0123456789012345678901234567890123456789.tar.gz"
test(
inputURL,
`<p><a href="`+inputURL+`" rel="nofollow"><code>0123456789.tar.gz</code></a></p>`)
inputURL = "https://example.com/owner/repo/commit/0123456789012345678901234567890123456789.patch?key=val"
inputURL = setting.AppURL + "owner/repo/commit/0123456789012345678901234567890123456789.patch?key=val"
test(
inputURL,
`<p><a href="`+inputURL+`" rel="nofollow"><code>0123456789.patch</code></a></p>`)
@@ -575,13 +576,15 @@ func TestFuzz(t *testing.T) {
}
func TestIssue18471(t *testing.T) {
data := `http://domain/org/repo/compare/783b039...da951ce`
defer testModule.MockVariableValue(&setting.AppURL, markup.TestAppURL)()
data := markup.TestAppURL + `org/repo/compare/783b039...da951ce`
var res strings.Builder
err := markup.PostProcessDefault(markup.NewTestRenderContext(localMetas), strings.NewReader(data), &res)
assert.NoError(t, err)
assert.Equal(t, `<a href="http://domain/org/repo/compare/783b039...da951ce" class="compare"><code>783b039...da951ce</code></a>`, res.String())
assert.Equal(t, `<a href="`+markup.TestAppURL+`org/repo/compare/783b039...da951ce" class="compare"><code>783b039...da951ce</code></a>`, res.String())
}
func TestIsFullURL(t *testing.T) {

View File

@@ -483,6 +483,9 @@ foo: bar
}
func TestRenderLinks(t *testing.T) {
defer test.MockVariableValue(&setting.AppURL, AppURL)()
defer test.MockVariableValue(&markup.RenderBehaviorForTesting.DisableAdditionalAttributes, true)()
input := ` space @mention-user${SPACE}${SPACE}
/just/a/path.bin
https://example.com/file.bin
@@ -520,9 +523,9 @@ mail@domain.com
<a href="https://example.com/image.jpg" target="_blank" rel="nofollow noopener"><img src="https://example.com/image.jpg" alt="remote image"/></a>
<a href="/image.jpg" rel="nofollow"><img src="/image.jpg" title="local image" alt="local image"/></a>
<a href="https://example.com/image.jpg" rel="nofollow"><img src="https://example.com/image.jpg" title="remote link" alt="remote link"/></a>
<a href="https://example.com/user/repo/compare/88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb#hash" rel="nofollow"><code>88fc37a3c0...12fc37a3c0 (hash)</code></a>
<a href="https://example.com/user/repo/compare/88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb#hash" rel="nofollow">https://example.com/user/repo/compare/88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb#hash</a>
com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb pare
<a href="https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb" rel="nofollow"><code>88fc37a3c0</code></a>
<a href="https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb" rel="nofollow">https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb</a>
com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb mit
<span class="emoji" aria-label="thumbs up">👍</span>
<a href="mailto:mail@domain.com" rel="nofollow">mail@domain.com</a>
@@ -530,10 +533,21 @@ com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb mit
#123
space</p>
`
defer test.MockVariableValue(&markup.RenderBehaviorForTesting.DisableAdditionalAttributes, true)()
result, err := markdown.RenderString(markup.NewTestRenderContext(localMetas), input)
assert.NoError(t, err)
assert.Equal(t, expected, string(result))
t.Run("LocalCommitAndCompare", func(t *testing.T) {
input := `http://localhost:3000/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb
http://localhost:3000/user/repo/compare/88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb#hash`
expected := `<p><a href="http://localhost:3000/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb" rel="nofollow"><code>88fc37a3c0</code></a>
<a href="http://localhost:3000/user/repo/compare/88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb#hash" rel="nofollow"><code>88fc37a3c0...12fc37a3c0 (hash)</code></a></p>
`
result, err := markdown.RenderString(markup.NewTestRenderContext(localMetas), input)
assert.NoError(t, err)
assert.Equal(t, expected, string(result))
})
}
func TestMarkdownLink(t *testing.T) {