From e2e64aeec57ca333618a07d2277b6519d66f3871 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Bj=C3=B8rn=20Erik=20Pedersen?= Date: Fri, 5 Dec 2025 11:33:55 +0100 Subject: [PATCH] Fix server rebuilds on editing content with Chinese terms Fixes #14240 --- commands/server.go | 7 ++++++ common/paths/path_test.go | 22 ++++++++++++++++ hugolib/rebuild_test.go | 23 +++++++++++++++++ main_test.go | 4 +-- .../server__edit_content_with_chinese_tag.txt | 25 +++++++++++++++++++ 5 files changed, 79 insertions(+), 2 deletions(-) create mode 100644 testscripts/commands/server__edit_content_with_chinese_tag.txt diff --git a/commands/server.go b/commands/server.go index ba0f35e99..1f5c58463 100644 --- a/commands/server.go +++ b/commands/server.go @@ -49,6 +49,7 @@ import ( "github.com/fsnotify/fsnotify" "github.com/gohugoio/hugo/common/herrors" "github.com/gohugoio/hugo/common/hugo" + "github.com/gohugoio/hugo/common/paths" "github.com/gohugoio/hugo/langs" "github.com/gohugoio/hugo/tpl/tplimpl" @@ -370,6 +371,12 @@ func (f *fileServer) createEndpoint(i int) (*http.ServeMux, net.Listener, string if f.c.fastRenderMode && f.c.errState.buildErr() == nil { if isNavigation(requestURI, r) { + // See issue 14240. + // Hugo escapes the URL paths when generating them, + // that may not be the case when we receive it back from the browser. + // PathEscape will escape if it is not already escaped. + requestURI = paths.PathEscape(requestURI) + if !f.c.visitedURLs.Contains(requestURI) { // If not already on stack, re-render that single page. if err := f.c.partialReRender(requestURI); err != nil { diff --git a/common/paths/path_test.go b/common/paths/path_test.go index ebfd5acd7..3fa4c03b2 100644 --- a/common/paths/path_test.go +++ b/common/paths/path_test.go @@ -347,3 +347,25 @@ func BenchmarkAddLeadingSlash(b *testing.B) { } }) } + +func TestPathEscape(t *testing.T) { + c := qt.New(t) + + for _, this := range []struct { + input string + expected string + }{ + {"/tags/欢迎", "/tags/%E6%AC%A2%E8%BF%8E"}, + {"/path with spaces", "/path%20with%20spaces"}, + {"/simple-path", "/simple-path"}, + {"/path/with/slash", "/path/with/slash"}, + {"/path/with special&chars", "/path/with%20special&chars"}, + } { + in := this.input + for range 2 { + result := PathEscape(in) + c.Assert(result, qt.Equals, this.expected, qt.Commentf("input: %q", this.input)) + in = result // test idempotency + } + } +} diff --git a/hugolib/rebuild_test.go b/hugolib/rebuild_test.go index 751547f12..07ce1580d 100644 --- a/hugolib/rebuild_test.go +++ b/hugolib/rebuild_test.go @@ -2051,6 +2051,29 @@ tags: ["tag1"] b.AssertFileContent("public/tags/index.html", "All. Tag1|$") } +func TestRebuildPageWithChineseTag14240(t *testing.T) { + t.Parallel() + + files := ` +-- hugo.toml -- +baseURL = "https://example.com" +disableLiveReload = true +-- layouts/all.html -- +All. {{ range .Pages }}{{ .RelPermalink }}: {{ .Title }}|{{ end }}$ +-- content/p1.md -- +--- +title: "P1" +tags: ["欢迎"] +--- + +` + b := TestRunning(t, files) + + b.AssertFileContent("public/tags/欢迎/index.html", "All. /p1/: P1|$") + b.EditFileReplaceAll("content/p1.md", "P1", "P1 edit").Build() + b.AssertFileContent("public/tags/欢迎/index.html", "All. /p1/: P1 edit|$") +} + func TestRebuildEditNonReferencedResourceIssue13748(t *testing.T) { t.Parallel() diff --git a/main_test.go b/main_test.go index 9e85c7ece..34c86034b 100644 --- a/main_test.go +++ b/main_test.go @@ -248,7 +248,7 @@ var commonTestScriptsParam = testscript.Params{ // httpget checks that a HTTP resource's body matches (if it compiles as a regexp) or contains all of the strings given as arguments. "httpget": func(ts *testscript.TestScript, neg bool, args []string) { if len(args) < 2 { - ts.Fatalf("usage: httpgrep URL STRING...") + ts.Fatalf("usage: httpget URL STRING...") } tryget := func() error { @@ -375,7 +375,7 @@ var commonTestScriptsParam = testscript.Params{ // The server will write a .ready file when ready. // We wait for that. readyFilename := ts.MkAbs(".ready") - limit := time.Now().Add(5 * time.Second) + limit := time.Now().Add(10 * time.Second) for { _, err := os.Stat(readyFilename) if err != nil { diff --git a/testscripts/commands/server__edit_content_with_chinese_tag.txt b/testscripts/commands/server__edit_content_with_chinese_tag.txt new file mode 100644 index 000000000..4020a54ac --- /dev/null +++ b/testscripts/commands/server__edit_content_with_chinese_tag.txt @@ -0,0 +1,25 @@ +# Issue 14240. + +hugo server & + +waitServer + +httpget ${HUGOTEST_BASEURL_0}tags/%E6%AC%A2%E8%BF%8E/ '_P1_' + +replace $WORK/content/p1.md 'P1' 'P1 New' + +httpget ${HUGOTEST_BASEURL_0}tags/%E6%AC%A2%E8%BF%8E/ '_P1 New_' + +stopServer +! stderr . + +-- hugo.toml -- +baseURL = "https://example.com" +disableLiveReload = true +-- layouts/all.html -- +All. {{ range .Pages }}_{{ .Title }}_{{ end }}$ +-- content/p1.md -- +--- +title: "P1" +tags: ["欢迎"] +--- -- 2.39.5