Repository: gohugoio/hugo (language: go)

Issue:
Improve resources.Copy to prevent nil pointer when using unusual construct
Reference: <https://discourse.gohugo.io/t/nil-pointer-evaluating-resource-resource-relpermalink-when-using-resources-copy/49030/14>

Error:

> Error: error building site: render: failed to render pages: render of "page" failed: "/home/jmooring/code/hugo-testing/layouts/_default/single.html:4:5": execute of template failed: template: _default/single.html:4:5: executing "_default/single.html" at <$r.RelPermalink>: nil pointer evaluating resource.Resource.RelPermalink

Notes:

1. I have no idea if this a bug or an enhancement. 
2. If it's a bug, it's been there forever; this isn't anything new.
3. The construct below is unusual. The example reported in the forum topic makes sense, while the test case below is simply a minimal reproducer.
4. The OP has at least two ways to get around this.

Minimal reproducible example:

```text
git clone --single-branch -b hugo-forum-topic-49030 https://github.com/jmooring/hugo-testing hugo-forum-topic-49030
cd hugo-forum-topic-49030
hugo
```

Test case:

```go
func TestFoo(t *testing.T) {
	t.Parallel()

	files := `
-- hugo.toml --
disableKinds = ['home','section','rss','sitemap','taxonomy','term']
-- assets/a.txt --
This was assets/a.txt
-- content/p1.md --
---
title: p1
---
{{< sc >}}
-- layouts/shortcodes/sc.html --
{{ $noop := resources.Get "/p1/b.txt" }}
-- layouts/_default/single.html --
{{ $noop := .Content }}
{{ $r := resources.Get "a.txt" }}
{{ $r = resources.Copy "/p1/b.txt" $r }}
{{ $r.RelPermalink }}
{{/* In the above, no errors you remove line 1. */}}
`

	b, err := hugolib.TestE(t, files)

	b.Assert(err, qt.IsNil)
	b.AssertFileContent("public/p1/b.txt", "This was assets/a.txt")
}

```

In the above, `resources.Get "/p1/b.txt"` is not a typographical error. Again, the example reported in the forum topic makes sense; the above is a minimal reproducer.

My best guess is that we cache a nil resource when calling the shortcode, and resources.Copy doesn't overwrite it because... it's cached.


Discussion / hints:

Fix patch:
diff --git a/resources/resource_factories/create/create.go b/resources/resource_factories/create/create.go
index 061ecf1e7f0..4725cf390b3 100644
--- a/resources/resource_factories/create/create.go
+++ b/resources/resource_factories/create/create.go
@@ -57,7 +57,7 @@ func New(rs *resources.Spec) *Client {
 
 // Copy copies r to the new targetPath.
 func (c *Client) Copy(r resource.Resource, targetPath string) (resource.Resource, error) {
-	key := dynacache.CleanKey(targetPath)
+	key := dynacache.CleanKey(targetPath) + "__copy"
 	return c.rs.ResourceCache.GetOrCreate(key, func() (resource.Resource, error) {
 		return resources.Copy(r, targetPath), nil
 	})
@@ -66,7 +66,7 @@ func (c *Client) Copy(r resource.Resource, targetPath string) (resource.Resource
 // Get creates a new Resource by opening the given pathname in the assets filesystem.
 func (c *Client) Get(pathname string) (resource.Resource, error) {
 	pathname = path.Clean(pathname)
-	key := dynacache.CleanKey(pathname)
+	key := dynacache.CleanKey(pathname) + "__get"
 
 	return c.rs.ResourceCache.GetOrCreate(key, func() (resource.Resource, error) {
 		// The resource file will not be read before it gets used (e.g. in .Content),


Test patch:
diff --git a/resources/resources_integration_test.go b/resources/resources_integration_test.go
index a0b35196a66..ba580b4dce1 100644
--- a/resources/resources_integration_test.go
+++ b/resources/resources_integration_test.go
@@ -227,3 +227,50 @@ eventDate: 2023-11-01T07:00:00-08:00
 
 	b.AssertFileContent("public/index.html", "2023-11|p9|p8|p7|2023-10|p6|p5|p4|2023-09|p3|p2|p1|")
 }
+
+// Issue 10412
+func TestImageTransformThenCopy(t *testing.T) {
+	t.Parallel()
+
+	files := `
+-- hugo.toml --
+disableKinds = ['page','rss','section','sitemap','taxonomy','term']
+-- assets/pixel.png --
+iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==
+-- layouts/index.html --
+{{- with resources.Get "pixel.png" }}
+  {{- with .Resize "200x" | resources.Copy "pixel.png" }}
+    <img src="{{ .RelPermalink }}" width="{{ .Width }}" height="{{ .Height }}">|{{ .Key }}
+  {{- end }}
+{{- end }}
+`
+
+	b := hugolib.Test(t, files)
+
+	b.AssertFileExists("public/pixel.png", true)
+	b.AssertFileContent("public/index.html",
+		`<img src="/pixel.png" width="200" height="200">|/pixel.png`,
+	)
+}
+
+// Issue 12310
+func TestUseDifferentCacheKeyForResourceCopy(t *testing.T) {
+	t.Parallel()
+
+	files := `
+-- hugo.toml --
+disableKinds = ['page','section','rss','sitemap','taxonomy','term']
+-- assets/a.txt --
+This was assets/a.txt
+-- layouts/index.html --
+{{ $nilResource := resources.Get "/p1/b.txt" }}
+{{ $r := resources.Get "a.txt" }}
+{{ $r = resources.Copy "/p1/b.txt" $r }}
+{{ $r.RelPermalink }}
+`
+
+	b, err := hugolib.TestE(t, files)
+
+	b.Assert(err, qt.IsNil)
+	b.AssertFileContent("public/p1/b.txt", "This was assets/a.txt")
+}