From da3e192eafc04b17f8fdea55f310ff4760b805ed Mon Sep 17 00:00:00 2001 From: Nicolas Date: Fri, 29 May 2026 06:34:37 +0200 Subject: [PATCH] fix(actions): keep action run title clickable when commit subject is a URL (#37867) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - When a commit subject is a bare URL, `linkProcessor` wrapped it in its own `` to that URL. Because HTML cannot nest anchors, the wrapping default link (the action run / commit link) was lost and the action title became unclickable — clicking it sent the user to the URL from the commit message instead of the action log. - Drop `linkProcessor` from `PostProcessCommitMessageSubject` so the whole subject stays wrapped in the default link. URLs in subjects now render as text inside that link; URLs in commit bodies are unaffected. Fixes #37865 --------- Co-authored-by: Claude Opus 4.7 Co-authored-by: Lunny Xiao Co-authored-by: Giteabot --- modules/markup/html.go | 24 +++++++++++++++++++++--- modules/templates/util_render_test.go | 12 ++++++++++++ 2 files changed, 33 insertions(+), 3 deletions(-) diff --git a/modules/markup/html.go b/modules/markup/html.go index a635ce219b..4943bdf4a5 100644 --- a/modules/markup/html.go +++ b/modules/markup/html.go @@ -175,16 +175,25 @@ var emojiProcessors = []processor{ emojiProcessor, } +// isBareURLSubject reports whether the (HTML-escaped) commit subject content +// is entirely a single URL, ignoring leading/trailing whitespace. +func isBareURLSubject(content string) bool { + s := strings.TrimSpace(html.UnescapeString(content)) + if s == "" { + return false + } + m := common.GlobalVars().LinkRegex.FindStringIndex(s) + return m != nil && m[0] == 0 && m[1] == len(s) +} + // PostProcessCommitMessageSubject will use the same logic as PostProcess and // PostProcessCommitMessage, but will disable the shortLinkProcessor and -// emailAddressProcessor, will add a defaultLinkProcessor if defaultLink is set, -// which changes every text node into a link to the passed default link. +// emailAddressProcessor, and wraps the whole subject in defaultLink. func PostProcessCommitMessageSubject(ctx *RenderContext, defaultLink, content string) (string, error) { procs := []processor{ fullIssuePatternProcessor, comparePatternProcessor, fullHashPatternProcessor, - linkProcessor, mentionProcessor, issueIndexPatternProcessor, commitCrossReferencePatternProcessor, @@ -192,6 +201,15 @@ func PostProcessCommitMessageSubject(ctx *RenderContext, defaultLink, content st emojiShortCodeProcessor, emojiProcessor, } + // When the whole subject is a bare URL, linkProcessor would turn it into + // a competing anchor and hijack the surrounding defaultLink wrapper, leaving + // the subject visually unclickable. Match GitHub: render such subjects as + // plain text inside defaultLink. Partial URLs inside larger text still become + // their own links (nested anchors aren't legal HTML, so the outer defaultLink + // naturally breaks on that span, same as on GitHub). + if !isBareURLSubject(content) { + procs = append(procs, linkProcessor) + } procs = append(procs, func(ctx *RenderContext, node *html.Node) { ch := &html.Node{Parent: node, Type: html.TextNode, Data: node.Data} node.Type = html.ElementNode diff --git a/modules/templates/util_render_test.go b/modules/templates/util_render_test.go index f732be014a..be1190cc49 100644 --- a/modules/templates/util_render_test.go +++ b/modules/templates/util_render_test.go @@ -140,6 +140,18 @@ com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb mit assert.EqualValues(t, expected, newTestRenderUtils(t).RenderCommitMessageLinkSubject(testInput(), "https://example.com/link", mockRepo)) }) + t.Run("RenderCommitMessageLinkSubjectURLOnly", func(t *testing.T) { + // a bare URL in the subject must not hijack the default link + expected := `https://example.com/file.bin` + assert.EqualValues(t, expected, newTestRenderUtils(t).RenderCommitMessageLinkSubject("https://example.com/file.bin", "https://example.com/link", mockRepo)) + }) + + t.Run("RenderCommitMessageLinkSubjectPartialURL", func(t *testing.T) { + // a URL embedded in larger subject text still becomes its own link + expected := `see https://example.com/x here` + assert.EqualValues(t, expected, newTestRenderUtils(t).RenderCommitMessageLinkSubject("see https://example.com/x here", "https://example.com/link", mockRepo)) + }) + t.Run("RenderIssueTitle", func(t *testing.T) { defer test.MockVariableValue(&markup.RenderBehaviorForTesting.DisableAdditionalAttributes, true)() expected := ` space @mention-user