]> git.maquefel.me Git - brevno-suite/hugo/commitdiff
Fix server rebuilds on editing content with Chinese terms
authorBjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com>
Fri, 5 Dec 2025 10:33:55 +0000 (11:33 +0100)
committerBjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com>
Fri, 5 Dec 2025 14:53:48 +0000 (15:53 +0100)
Fixes #14240

commands/server.go
common/paths/path_test.go
hugolib/rebuild_test.go
main_test.go
testscripts/commands/server__edit_content_with_chinese_tag.txt [new file with mode: 0644]

index ba0f35e9965fe2d368a3ad5af6fbb5c41eef73c6..1f5c584634c6355b901a67b1b497da02b15e06b7 100644 (file)
@@ -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 {
index ebfd5acd7c5f2f6acfdb6ce6b982e602a0c06867..3fa4c03b2805d266faa50056a5856cebd4c8e91c 100644 (file)
@@ -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
+               }
+       }
+}
index 751547f1224063455a8964a8d6caedb711ab212f..07ce1580d9cb0628e139effff25b528409473bf5 100644 (file)
@@ -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()
 
index 9e85c7ececf2a3b6cfbfe3dba6e32086517d0dfc..34c86034bc092d6e3933c5f41a08312d97230fff 100644 (file)
@@ -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 (file)
index 0000000..4020a54
--- /dev/null
@@ -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: ["欢迎"]
+---