Repository: regclient/regclient
Pull request #910 (2025-02-18 19:35:48)

Chore: Modernize Go to the 1.22 specs
<!--

Commits must be signed indicating your agreement to the [DCO](https://developercertificate.org/).
See [DCO missing](https://github.com/src-d/guide/blob/master/developer-community/fix-DCO.md) for steps to fix a missing signoff.

-->

### Fixes issue

<!-- If this is a bug fix, include "fixes #xxxx", or "closes #xxxx" -->

### Describe the change

Modernize Go to the 1.22 specs:

- Use "any" instead of an empty interface.
- Use range over an integer for for loops.
- Remove shadow variables in loops now that Go no longer reuses the variable.
- Use "slices.Contains", "slices.Delete", "slices.Equal", "slices.Index", "slices.SortFunc".
- Use "cmp.Or", "min", and "max".
- Use "fmt.Appendf" instead of "Sprintf" for generating a byte slice.
- Use "errors.Join" or "fmt.Errorf" with multiple "%w" for multiple errors.

Additionally, use modern regclient features:

- Use "ref.SetTag", "ref.SetDigest", and "ref.AddDigest".
- Call "regclient.ManifestGet" using "WithManifestDesc" instead of setting the digest on the reference.

<!-- Include the type of change: bug fix, new feature, breaking change, documentation update -->
<!-- Describe what was changed, why the change was made, and how it was implemented -->

### How to verify it

There are very few user visible changes (at most some error messages may be improved).

```shell
make test
```

Given the scope of the changes, there is potential for introducing bugs, but it's also likely quite a few other bugs have been resolved by using language built-in features.
<!-- Include steps that can be taken to verify the change -->

### Changelog text

- Chore: Modernize Go to the 1.22 specs.
- Feat: Add `ref.AddDigest` method that does not unset the tag.
<!-- If the release changelog should have an entry for this, include it here -->

### Please verify and check that the pull request fulfills the following requirements

<!-- Mark the following with an [X] to verify they are included -->

- [X] Tests have been added or not applicable
- [X] Documentation has been added, updated, or not applicable
- [X] Changes have been rebased to main
- [X] Multiple commits to the same code have been squashed

<!-- markdownlint-disable-file MD041 -->



Patch:
diff --git a/cmd/regbot/internal/go2lua/export.go b/cmd/regbot/internal/go2lua/export.go
index a452a2c..c306530 100644
--- a/cmd/regbot/internal/go2lua/export.go
+++ b/cmd/regbot/internal/go2lua/export.go
@@ -9,7 +9,7 @@ import (
 )
 
 // Export takes an input Go interface and converts it to a Lua value
-func Export(ls *lua.LState, v interface{}) lua.LValue {
+func Export(ls *lua.LState, v any) lua.LValue {
 	return exportReflect(ls, reflect.ValueOf(v))
 }
 
@@ -30,13 +30,13 @@ func exportReflect(ls *lua.LState, v reflect.Value) lua.LValue {
 		return lua.LString(v.String())
 	case reflect.Array:
 		lTab := ls.NewTable()
-		for i := 0; i < v.Len(); i++ {
+		for i := range v.Len() {
 			lTab.RawSetInt(i+1, exportReflect(ls, v.Index(i)))
 		}
 		return lTab
 	case reflect.Slice:
 		lTab := ls.NewTable()
-		for i := 0; i < v.Len(); i++ {
+		for i := range v.Len() {
 			lTab.RawSetInt(i+1, exportReflect(ls, v.Index(i)))
 		}
 		return lTab
@@ -55,7 +55,7 @@ func exportReflect(ls *lua.LState, v reflect.Value) lua.LValue {
 		vType := v.Type()
 		lTab := ls.NewTable()
 		foundExported := false
-		for i := 0; i < vType.NumField(); i++ {
+		for i := range vType.NumField() {
 			field := vType.Field(i)
 			// skip unexported fields
 			if !v.FieldByName(field.Name).CanInterface() {
diff --git a/cmd/regbot/internal/go2lua/import.go b/cmd/regbot/internal/go2lua/import.go
index 6b379c7..a887949 100644
--- a/cmd/regbot/internal/go2lua/import.go
+++ b/cmd/regbot/internal/go2lua/import.go
@@ -1,6 +1,7 @@
 package go2lua
 
 import (
+	"errors"
 	"fmt"
 	"reflect"
 	"runtime/debug"
@@ -11,7 +12,7 @@ import (
 
 // Import takes a Lua value and copies matching values into the provided Go interface.
 // By providing the orig interface, values that cannot be imported from Lua will be copied from orig.
-func Import(ls *lua.LState, lv lua.LValue, v, orig interface{}) (err error) {
+func Import(ls *lua.LState, lv lua.LValue, v, orig any) (err error) {
 	defer func() {
 		if r := recover(); r != nil {
 			err = fmt.Errorf("go2lua import panic: %v\n%s", r, string(debug.Stack()))
@@ -63,7 +64,7 @@ func importReflect(ls *lua.LState, lv lua.LValue, v, orig reflect.Value) error {
 	case reflect.Array:
 		// If we have an array, and lua is the expected table, iterate and recursively import contents.
 		if lvi, ok := lv.(*lua.LTable); ok {
-			for i := 0; i < v.Len(); i++ {
+			for i := range v.Len() {
 				// Orig is also iterated on if it has a matching type and length
 				var origI reflect.Value
 				if orig.IsValid() && orig.Type() == v.Type() && orig.Len() < i {
@@ -80,7 +81,7 @@ func importReflect(ls *lua.LState, lv lua.LValue, v, orig reflect.Value) error {
 		// Slice follows the same pattern as array, except the slice is first created with the desired size.
 		if lvi, ok := lv.(*lua.LTable); ok {
 			newV := reflect.MakeSlice(v.Type(), lvi.Len(), lvi.Len())
-			for i := 0; i < newV.Len(); i++ {
+			for i := range newV.Len() {
 				var origI reflect.Value
 				if orig.IsValid() && orig.Type() == v.Type() && orig.Len() > i {
 					origI = orig.Index(i)
@@ -94,8 +95,7 @@ func importReflect(ls *lua.LState, lv lua.LValue, v, orig reflect.Value) error {
 		}
 		return nil
 	case reflect.Map:
-		// TODO: with go 1.20, switch to error list and return errors.Join
-		var retErr error
+		var errs []error
 		if lvi, ok := lv.(*lua.LTable); ok {
 			newV := reflect.MakeMap(v.Type())
 			lvi.ForEach(func(lvtKey, lvtElem lua.LValue) {
@@ -103,7 +103,7 @@ func importReflect(ls *lua.LState, lv lua.LValue, v, orig reflect.Value) error {
 				newElem := reflect.Indirect(reflect.New(v.Type().Elem()))
 				err := importReflect(ls, lvtKey, newKey, reflect.Value{})
 				if err != nil {
-					retErr = err
+					errs = append(errs, err)
 				}
 				var origElem reflect.Value
 				if orig.IsValid() && orig.Type() == v.Type() {
@@ -111,18 +111,18 @@ func importReflect(ls *lua.LState, lv lua.LValue, v, orig reflect.Value) error {
 				}
 				err = importReflect(ls, lvtElem, newElem, origElem)
 				if err != nil {
-					retErr = err
+					errs = append(errs, err)
 				}
 				newV.SetMapIndex(newKey, newElem)
 			})
 			v.Set(newV)
 		}
-		return retErr
+		return errors.Join(errs...)
 	case reflect.Struct:
 		foundExported := false
 		if lvi, ok := lv.(*lua.LTable); ok {
 			vType := v.Type()
-			for i := 0; i < vType.NumField(); i++ {
+			for i := range vType.NumField() {
 				field := vType.Field(i)
 				// skip unexported fields
 				if !v.FieldByName(field.Name).CanInterface() {
diff --git a/cmd/regbot/root.go b/cmd/regbot/root.go
index 295be9e..d8bef56 100644
--- a/cmd/regbot/root.go
+++ b/cmd/regbot/root.go
@@ -135,7 +135,6 @@ func (rootOpts *rootCmd) runOnce(cmd *cobra.Command, args []string) error {
 	var wg sync.WaitGroup
 	var mainErr error
 	for _, s := range rootOpts.conf.Scripts {
-		s := s
 		if rootOpts.conf.Defaults.Parallel > 0 {
 			wg.Add(1)
 			go func() {
@@ -174,7 +173,6 @@ func (rootOpts *rootCmd) runServer(cmd *cobra.Command, args []string) error {
 		cron.SkipIfStillRunning(cron.DefaultLogger),
 	))
 	for _, s := range rootOpts.conf.Scripts {
-		s := s
 		sched := s.Schedule
 		if sched == "" && s.Interval != 0 {
 			sched = "@every " + s.Interval.String()
@@ -310,7 +308,7 @@ func (rootOpts *rootCmd) process(ctx context.Context, s ConfigScript) error {
 		rootOpts.log.Warn("Error running script",
 			slog.String("script", s.Name),
 			slog.String("error", err.Error()))
-		return ErrScriptFailed
+		return fmt.Errorf("%w%.0w", err, ErrScriptFailed)
 	}
 	rootOpts.log.Debug("Finished script",
 		slog.String("script", s.Name))
diff --git a/cmd/regbot/sandbox/manifest.go b/cmd/regbot/sandbox/manifest.go
index 7db159c..27e50a3 100644
--- a/cmd/regbot/sandbox/manifest.go
+++ b/cmd/regbot/sandbox/manifest.go
@@ -7,6 +7,7 @@ import (
 
 	lua "github.com/yuin/gopher-lua"
 
+	"github.com/regclient/regclient"
 	"github.com/regclient/regclient/cmd/regbot/internal/go2lua"
 	"github.com/regclient/regclient/types/manifest"
 	"github.com/regclient/regclient/types/platform"
@@ -104,8 +105,7 @@ func (s *Sandbox) manifestDelete(ls *lua.LState) int {
 	m := s.checkManifest(ls, 1, true, true)
 	r := m.r
 	if r.Digest == "" {
-		d := m.m.GetDescriptor()
-		r.Digest = d.Digest.String()
+		r = r.AddDigest(m.m.GetDescriptor().Digest.String())
 	}
 	s.log.Info("Delete manifest",
 		slog.String("script", s.name),
@@ -290,8 +290,7 @@ func (s *Sandbox) rcManifestGet(r ref.Ref, list bool, pStr string) (manifest.Man
 		if err != nil {
 			return m, err
 		}
-		r.Digest = desc.Digest.String()
-		m, err = s.rc.ManifestGet(s.ctx, r)
+		m, err = s.rc.ManifestGet(s.ctx, r, regclient.WithManifestDesc(*desc))
 		if err != nil {
 			return m, err
 		}
diff --git a/cmd/regbot/sandbox/reference.go b/cmd/regbot/sandbox/reference.go
index 7c43cab..64f9ced 100644
--- a/cmd/regbot/sandbox/reference.go
+++ b/cmd/regbot/sandbox/reference.go
@@ -106,7 +106,7 @@ func (s *Sandbox) referenceString(ls *lua.LState) int {
 func (s *Sandbox) referenceGetSetDigest(ls *lua.LState) int {
 	r := s.checkReference(ls, 1)
 	if ls.GetTop() == 2 {
-		r.r.Digest = ls.CheckString(2)
+		r.r = r.r.SetDigest(ls.CheckString(2))
 		return 0
 	}
 	ls.Push(lua.LString(r.r.Digest))
@@ -116,7 +116,7 @@ func (s *Sandbox) referenceGetSetDigest(ls *lua.LState) int {
 func (s *Sandbox) referenceGetSetTag(ls *lua.LState) int {
 	r := s.checkReference(ls, 1)
 	if ls.GetTop() == 2 {
-		r.r.Tag = ls.CheckString(2)
+		r.r = r.r.SetTag(ls.CheckString(2))
 		return 0
 	}
 	ls.Push(lua.LString(r.r.Tag))
diff --git a/cmd/regbot/sandbox/sandbox.go b/cmd/regbot/sandbox/sandbox.go
index 55b47b3..475af89 100644
--- a/cmd/regbot/sandbox/sandbox.go
+++ b/cmd/regbot/sandbox/sandbox.go
@@ -3,6 +3,7 @@ package sandbox
 
 import (
 	"context"
+	"fmt"
 	"log/slog"
 	"os"
 
@@ -138,7 +139,7 @@ func (s *Sandbox) RunScript(script string) (err error) {
 			s.log.Error("Runtime error from script",
 				slog.String("script", s.name),
 				slog.Any("error", r))
-			err = ErrScriptFailed
+			err = fmt.Errorf("%w: %v", ErrScriptFailed, r)
 		}
 	}()
 	return s.ls.DoString(script)
@@ -160,7 +161,7 @@ func (s *Sandbox) sandboxLog(ls *lua.LState) int {
 // wrapUserData creates a userdata -> wrapped table -> userdata metatable
 // structure. This allows references to a struct to resolve for read access,
 // while providing access to only the desired methods on the userdata.
-func wrapUserData(ls *lua.LState, udVal interface{}, wrapVal interface{}, udType string) (lua.LValue, error) {
+func wrapUserData(ls *lua.LState, udVal any, wrapVal any, udType string) (lua.LValue, error) {
 	ud := ls.NewUserData()
 	ud.Value = udVal
 	udTypeMT, ok := (ls.GetTypeMetatable(udType)).(*lua.LTable)
diff --git a/cmd/regctl/artifact.go b/cmd/regctl/artifact.go
index 0f6f213..6798883 100644
--- a/cmd/regctl/artifact.go
+++ b/cmd/regctl/artifact.go
@@ -9,6 +9,7 @@ import (
 	"os"
 	"path"
 	"path/filepath"
+	"slices"
 	"strings"
 
 	// crypto libraries included for go-digest
@@ -382,8 +383,7 @@ func (artifactOpts *artifactCmd) runArtifactGet(cmd *cobra.Command, args []strin
 		if err != nil {
 			return fmt.Errorf("no matching artifacts found in index: %w", err)
 		}
-		r.Digest = d.Digest.String()
-		m, err = rc.ManifestGet(ctx, r)
+		m, err = rc.ManifestGet(ctx, r, regclient.WithManifestDesc(d))
 		if err != nil {
 			return err
 		}
@@ -434,41 +434,23 @@ func (artifactOpts *artifactCmd) runArtifactGet(cmd *cobra.Command, args []strin
 	// filter by media-type if defined
 	if len(artifactOpts.artifactFileMT) > 0 {
 		for i := len(layers) - 1; i >= 0; i-- {
-			found := false
-			for _, mt := range artifactOpts.artifactFileMT {
-				if layers[i].MediaType == mt {
-					found = true
-					break
-				}
-			}
-			if !found {
-				// remove from slice
-				layers = append(layers[:i], layers[i+1:]...)
+			if !slices.Contains(artifactOpts.artifactFileMT, layers[i].MediaType) {
+				layers = slices.Delete(layers, i, i+1)
 			}
 		}
 	}
 	// filter by filename if defined
 	if len(artifactOpts.artifactFile) > 0 {
 		for i := len(layers) - 1; i >= 0; i-- {
-			found := false
 			af, ok := layers[i].Annotations[ociAnnotTitle]
-			if ok {
-				for _, f := range artifactOpts.artifactFile {
-					if af == f {
-						found = true
-						break
-					}
-				}
-			}
-			if !found {
-				// remove from slice
-				layers = append(layers[:i], layers[i+1:]...)
+			if !ok || !slices.Contains(artifactOpts.artifactFile, af) {
+				layers = slices.Delete(layers, i, i+1)
 			}
 		}
 	}
 
 	if len(layers) == 0 {
-		return fmt.Errorf("no matching layers found in the artifact, verify media-type and filename")
+		return fmt.Errorf("no matching layers found in the artifact, verify media-type and filename%.0w", errs.ErrNotFound)
 	}
 
 	if artifactOpts.outputDir != "" {
@@ -639,7 +621,7 @@ func (artifactOpts *artifactCmd) runArtifactList(cmd *cobra.Command, args []stri
 			return fmt.Errorf("failed to compute fallback tag: %w", err)
 		}
 		for _, t := range tl.Tags {
-			if strings.HasPrefix(t, prefix.Tag) && !sliceHasStr(rl.Tags, t) {
+			if strings.HasPrefix(t, prefix.Tag) && !slices.Contains(rl.Tags, t) {
 				rTag := rl.Subject.SetTag(t)
 				mh, err := rc.ManifestHead(ctx, rTag, regclient.WithManifestRequireDigest())
 				if err != nil {
@@ -973,8 +955,7 @@ func (artifactOpts *artifactCmd) runArtifactPut(cmd *cobra.Command, args []strin
 	}
 
 	if artifactOpts.byDigest || artifactOpts.index || rArt.IsZero() {
-		r.Tag = ""
-		r.Digest = mm.GetDescriptor().Digest.String()
+		r = r.SetDigest(mm.GetDescriptor().Digest.String())
 	}
 
 	// push manifest
@@ -1123,13 +1104,11 @@ func (artifactOpts *artifactCmd) treeAddResult(ctx context.Context, rc *regclien
 		return nil, err
 	}
 	tr.Manifest = m
-	if r.Digest == "" {
-		r.Digest = m.GetDescriptor().Digest.String()
-	}
+	r = r.AddDigest(m.GetDescriptor().Digest.String())
 
 	// track already seen manifests
 	dig := m.GetDescriptor().Digest.String()
-	if sliceHasStr(seen, dig) {
+	if slices.Contains(seen, dig) {
 		return &tr, fmt.Errorf("%w, already processed %s", ErrLoopEncountered, dig)
 	}
 	seen = append(seen, dig)
@@ -1199,7 +1178,7 @@ func (artifactOpts *artifactCmd) treeAddResult(ctx context.Context, rc *regclien
 			return &tr, fmt.Errorf("failed to compute fallback tag: %w", err)
 		}
 		for _, t := range tags {
-			if strings.HasPrefix(t, prefix.Tag) && !sliceHasStr(rl.Tags, t) {
+			if strings.HasPrefix(t, prefix.Tag) && !slices.Contains(rl.Tags, t) {
 				rTag := r.SetTag(t)
 				tReferrer, err := artifactOpts.treeAddResult(ctx, rc, rTag, seen, rOpts, tags)
 				if tReferrer != nil {
@@ -1216,15 +1195,6 @@ func (artifactOpts *artifactCmd) treeAddResult(ctx context.Context, rc *regclien
 	return &tr, nil
 }
 
-func sliceHasStr(list []string, search string) bool {
-	for _, el := range list {
-		if el == search {
-			return true
-		}
-	}
-	return false
-}
-
 type treeResult struct {
 	Ref          ref.Ref            `json:"reference"`
 	Manifest     manifest.Manifest  `json:"manifest"`
@@ -1240,7 +1210,7 @@ func (tr *treeResult) MarshalPretty() ([]byte, error) {
 	if err != nil {
 		return nil, err
 	}
-	return []byte(fmt.Sprintf("Ref: %s\nDigest: %s", tr.Ref.CommonName(), mp)), nil
+	return fmt.Appendf(nil, "Ref: %s\nDigest: %s", tr.Ref.CommonName(), mp), nil
 }
 
 func (tr *treeResult) marshalPretty(indent string) ([]byte, error) {
diff --git a/cmd/regctl/completion.go b/cmd/regctl/completion.go
index 2a91ebc..df19a30 100644
--- a/cmd/regctl/completion.go
+++ b/cmd/regctl/completion.go
@@ -71,7 +71,7 @@ func (rootOpts *rootCmd) completeArgTag(cmd *cobra.Command, args []string, toCom
 	}
 	for _, tag := range tags {
 		resultRef, _ := ref.New(input)
-		resultRef.Tag = tag
+		resultRef = resultRef.SetTag(tag)
 		resultCN := resultRef.CommonName()
 		if strings.HasPrefix(resultCN, toComplete) {
 			result = append(result, resultCN)
diff --git a/cmd/regctl/image.go b/cmd/regctl/image.go
index 77819fc..e5c9c02 100644
--- a/cmd/regctl/image.go
+++ b/cmd/regctl/image.go
@@ -3,6 +3,7 @@ package main
 import (
 	"archive/tar"
 	"bytes"
+	"cmp"
 	"context"
 	"encoding/json"
 	"errors"
@@ -11,7 +12,7 @@ import (
 	"log/slog"
 	"os"
 	"regexp"
-	"sort"
+	"slices"
 	"strconv"
 	"strings"
 	"sync"
@@ -1081,7 +1082,7 @@ func (imageOpts *imageCmd) runImageCopy(cmd *cobra.Command, args []string) error
 		if err != nil {
 			return err
 		}
-		rSrc = rSrc.SetDigest(m.GetDescriptor().Digest.String())
+		rSrc = rSrc.AddDigest(m.GetDescriptor().Digest.String())
 	}
 	imageOpts.rootOpts.log.Debug("Image copy",
 		slog.String("source", rSrc.CommonName()),
@@ -1223,13 +1224,18 @@ func (ip *imageProgress) display(final bool) {
 	for k := range ip.entries {
 		keys = append(keys, k)
 	}
-	sort.Slice(keys, func(a, b int) bool {
-		if ip.entries[keys[a]].state != ip.entries[keys[b]].state {
-			return ip.entries[keys[a]].state > ip.entries[keys[b]].state
-		} else if ip.entries[keys[a]].state != types.CallbackActive {
-			return ip.entries[keys[a]].last.Before(ip.entries[keys[b]].last)
+	slices.SortFunc(keys, func(a, b string) int {
+		// show finished entries at the top, queued entries on the bottom
+		if ip.entries[a].state > ip.entries[b].state {
+			return -1
+		} else if ip.entries[a].state < ip.entries[b].state {
+			return 1
+		} else if ip.entries[a].state != types.CallbackActive {
+			// sort inactive entries by finish time
+			return ip.entries[a].last.Compare(ip.entries[b].last)
 		} else {
-			return ip.entries[keys[a]].cur > ip.entries[keys[b]].cur
+			// sort bytes sent descending
+			return cmp.Compare(ip.entries[a].cur, ip.entries[b].cur) * -1
 		}
 	})
 	startCount, startLimit := 0, 2
@@ -1276,15 +1282,14 @@ func (ip *imageProgress) display(final bool) {
 		}
 	}
 	// show stats summary
-	ip.asciiOut.Add([]byte(fmt.Sprintf("Manifests: %d/%d | Blobs: %s copied, %s skipped",
+	ip.asciiOut.Add(fmt.Appendf(nil, "Manifests: %d/%d | Blobs: %s copied, %s skipped",
 		manifestFinished, manifestTotal,
 		units.HumanSize(float64(sum)),
-		units.HumanSize(float64(skipped)))))
+		units.HumanSize(float64(skipped))))
 	if queued > 0 {
-		ip.asciiOut.Add([]byte(fmt.Sprintf(", %s queued",
-			units.HumanSize(float64(queued)))))
+		ip.asciiOut.Add(fmt.Appendf(nil, ", %s queued", units.HumanSize(float64(queued))))
 	}
-	ip.asciiOut.Add([]byte(fmt.Sprintf(" | Elapsed: %ds\n", int64(time.Since(ip.start).Seconds()))))
+	ip.asciiOut.Add(fmt.Appendf(nil, " | Elapsed: %ds\n", int64(time.Since(ip.start).Seconds())))
 	ip.asciiOut.Flush()
 	if !final {
 		ip.asciiOut.Return()
@@ -1406,8 +1411,7 @@ func (imageOpts *imageCmd) runImageCreate(cmd *cobra.Command, args []string) err
 
 	// push the image
 	if imageOpts.byDigest {
-		r.Tag = ""
-		r.Digest = mm.GetDescriptor().Digest.String()
+		r = r.SetDigest(mm.GetDescriptor().Digest.String())
 	}
 	err = rc.ManifestPut(ctx, r, mm)
 	if err != nil {
@@ -1457,7 +1461,7 @@ func (imageOpts *imageCmd) runImageExport(cmd *cobra.Command, args []string) err
 		if err != nil {
 			return err
 		}
-		r = r.SetDigest(m.GetDescriptor().Digest.String())
+		r = r.AddDigest(m.GetDescriptor().Digest.String())
 	}
 	if imageOpts.exportCompress {
 		opts = append(opts, regclient.ImageWithExportCompress())
@@ -1660,8 +1664,7 @@ func (imageOpts *imageCmd) runImageMod(cmd *cobra.Command, args []string) error
 	} else if imageOpts.replace {
 		rTgt = rSrc
 	} else {
-		rTgt = rSrc
-		rTgt.Tag = ""
+		rTgt = rSrc.SetTag("")
 	}
 	imageOpts.modOpts = append(imageOpts.modOpts, mod.WithRefTgt(rTgt))
 	rc := imageOpts.rootOpts.newRegClient()
diff --git a/cmd/regctl/index.go b/cmd/regctl/index.go
index f4468b1..51f99fb 100644
--- a/cmd/regctl/index.go
+++ b/cmd/regctl/index.go
@@ -3,6 +3,7 @@ package main
 import (
 	"context"
 	"fmt"
+	"slices"
 	"strings"
 
 	"github.com/opencontainers/go-digest"
@@ -191,8 +192,8 @@ func (indexOpts *indexCmd) runIndexAdd(cmd *cobra.Command, args []string) error
 	}
 
 	// push the index
-	if r.Tag == "" && r.Digest != "" {
-		r.Digest = m.GetDescriptor().Digest.String()
+	if r.Digest != "" {
+		r = r.AddDigest(m.GetDescriptor().Digest.String())
 	}
 	err = rc.ManifestPut(ctx, r, m)
 	if err != nil {
@@ -301,8 +302,7 @@ func (indexOpts *indexCmd) runIndexCreate(cmd *cobra.Command, args []string) err
 
 	// push the index
 	if indexOpts.byDigest {
-		r.Tag = ""
-		r.Digest = mm.GetDescriptor().Digest.String()
+		r = r.SetDigest(mm.GetDescriptor().Digest.String())
 	}
 	err = rc.ManifestPut(ctx, r, mm)
 	if err != nil {
@@ -357,11 +357,7 @@ func (indexOpts *indexCmd) runIndexDelete(cmd *cobra.Command, args []string) err
 		i := len(curDesc) - 1
 		for i >= 0 {
 			if curDesc[i].Digest.String() == dig {
-				if i < len(curDesc)-1 {
-					curDesc = append(curDesc[:i], curDesc[i+1:]...)
-				} else {
-					curDesc = curDesc[:i]
-				}
+				curDesc = slices.Delete(curDesc, i, i+1)
 			}
 			i--
 		}
@@ -374,11 +370,7 @@ func (indexOpts *indexCmd) runIndexDelete(cmd *cobra.Command, args []string) err
 		i := len(curDesc) - 1
 		for i >= 0 {
 			if curDesc[i].Platform != nil && platform.Match(plat, *curDesc[i].Platform) {
-				if i < len(curDesc)-1 {
-					curDesc = append(curDesc[:i], curDesc[i+1:]...)
-				} else {
-					curDesc = curDesc[:i]
-				}
+				curDesc = slices.Delete(curDesc, i, i+1)
 			}
 			i--
 		}
@@ -391,8 +383,8 @@ func (indexOpts *indexCmd) runIndexDelete(cmd *cobra.Command, args []string) err
 	}
 
 	// push the index
-	if r.Tag == "" && r.Digest != "" {
-		r.Digest = m.GetDescriptor().Digest.String()
+	if r.Digest != "" {
+		r = r.AddDigest(m.GetDescriptor().Digest.String())
 	}
 	err = rc.ManifestPut(ctx, r, m)
 	if err != nil {
@@ -556,11 +548,7 @@ func indexDescListRmDup(dl []descriptor.Descriptor) []descriptor.Descriptor {
 		j := len(dl) - 1
 		for j > i {
 			if dl[i].Equal(dl[j]) {
-				if j < len(dl)-1 {
-					dl = append(dl[:j], dl[j+1:]...)
-				} else {
-					dl = dl[:j]
-				}
+				dl = slices.Delete(dl, j, j+1)
 			}
 			j--
 		}
diff --git a/cmd/regctl/manifest.go b/cmd/regctl/manifest.go
index 62df253..286e514 100644
--- a/cmd/regctl/manifest.go
+++ b/cmd/regctl/manifest.go
@@ -195,10 +195,10 @@ func (manifestOpts *manifestCmd) runManifestDelete(cmd *cobra.Command, args []st
 		if err != nil {
 			return err
 		}
-		r.Digest = manifest.GetDigest(m).String()
+		r = r.AddDigest(manifest.GetDigest(m).String())
 		manifestOpts.rootOpts.log.Debug("Forced dereference of tag",
-			slog.String("tag", r.Tag),
-			slog.String("digest", r.Digest))
+			slog.String("orig", args[0]),
+			slog.String("resolved", r.CommonName()))
 	}
 
 	manifestOpts.rootOpts.log.Debug("Manifest delete",
@@ -391,8 +391,7 @@ func (manifestOpts *manifestCmd) runManifestPut(cmd *cobra.Command, args []strin
 		return err
 	}
 	if manifestOpts.byDigest {
-		r.Tag = ""
-		r.Digest = rcM.GetDescriptor().Digest.String()
+		r = r.SetDigest(rcM.GetDescriptor().Digest.String())
 	}
 
 	err = rc.ManifestPut(ctx, r, rcM)
diff --git a/cmd/regsync/root.go b/cmd/regsync/root.go
index 39e05ea..cd7bf94 100644
--- a/cmd/regsync/root.go
+++ b/cmd/regsync/root.go
@@ -7,6 +7,7 @@ import (
 	"log/slog"
 	"os"
 	"regexp"
+	"slices"
 	"strings"
 	"sync"
 	"time"
@@ -196,7 +197,6 @@ func (rootOpts *rootCmd) runOnce(cmd *cobra.Command, args []string) error {
 	var wg sync.WaitGroup
 	var mainErr error
 	for _, s := range rootOpts.conf.Sync {
-		s := s
 		if rootOpts.conf.Defaults.Parallel > 0 {
 			wg.Add(1)
 			go func() {
@@ -236,7 +236,6 @@ func (rootOpts *rootCmd) runServer(cmd *cobra.Command, args []string) error {
 		cron.SkipIfStillRunning(cron.DefaultLogger),
 	))
 	for _, s := range rootOpts.conf.Sync {
-		s := s
 		sched := s.Schedule
 		if sched == "" && s.Interval != 0 {
 			sched = "@every " + s.Interval.String()
@@ -535,7 +534,7 @@ func (rootOpts *rootCmd) processRepo(ctx context.Context, s ConfigSync, src, tgt
 		for sI >= 0 && tI >= 0 {
 			switch strings.Compare(sTagList[sI], tTagList[tI]) {
 			case 0:
-				sTagList = append(sTagList[:sI], sTagList[sI+1:]...)
+				sTagList = slices.Delete(sTagList, sI, sI+1)
 				sI--
 				tI--
 			case -1:
@@ -628,14 +627,7 @@ func (rootOpts *rootCmd) processRef(ctx context.Context, s ConfigSync, src, tgt
 
 	// skip when source manifest is an unsupported type
 	smt := manifest.GetMediaType(mSrc)
-	found := false
-	for _, mt := range s.MediaTypes {
-		if mt == smt {
-			found = true
-			break
-		}
-	}
-	if !found {
+	if !slices.Contains(s.MediaTypes, smt) {
 		rootOpts.log.Info("Skipping unsupported media type",
 			slog.String("ref", src.CommonName()),
 			slog.String("mediaType", manifest.GetMediaType(mSrc)),
@@ -767,7 +759,7 @@ func (rootOpts *rootCmd) processRef(ctx context.Context, s ConfigSync, src, tgt
 			}
 		} else {
 			// else parse backup string as just a tag
-			backupRef.Tag = backupStr
+			backupRef = backupRef.SetTag(backupStr)
 		}
 		defer rootOpts.rc.Close(ctx, backupRef)
 		// run copy from tgt ref to backup ref
diff --git a/config/host.go b/config/host.go
index 77ae57b..f30d9d9 100644
--- a/config/host.go
+++ b/config/host.go
@@ -6,6 +6,7 @@ import (
 	"fmt"
 	"io"
 	"log/slog"
+	"slices"
 	"strings"
 	"time"
 
@@ -411,7 +412,7 @@ func (host *Host) Merge(newHost Host, log *slog.Logger) error {
 	}
 
 	if len(newHost.Mirrors) > 0 {
-		if len(host.Mirrors) > 0 && !stringSliceEq(host.Mirrors, newHost.Mirrors) {
+		if len(host.Mirrors) > 0 && !slices.Equal(host.Mirrors, newHost.Mirrors) {
 			log.Warn("Changing mirror settings for registry",
 				slog.Any("orig", host.Mirrors),
 				slog.Any("new", newHost.Mirrors),
@@ -510,15 +511,3 @@ func copyMapString(src map[string]string) map[string]string {
 	}
 	return copy
 }
-
-func stringSliceEq(a, b []string) bool {
-	if len(a) != len(b) {
-		return false
-	}
-	for i, v := range a {
-		if v != b[i] {
-			return false
-		}
-	}
-	return true
-}
diff --git a/image.go b/image.go
index 9f2cf88..696c3be 100644
--- a/image.go
+++ b/image.go
@@ -2,6 +2,7 @@ package regclient
 
 import (
 	"archive/tar"
+	"cmp"
 	"compress/gzip"
 	"context"
 	"encoding/json"
@@ -11,6 +12,7 @@ import (
 	"log/slog"
 	"net/url"
 	"path/filepath"
+	"slices"
 	"strings"
 	"sync"
 	"time"
@@ -325,8 +327,7 @@ func (rc *RegClient) ImageCheckBase(ctx context.Context, r ref.Ref, opts ...Imag
 		if err != nil {
 			return err
 		}
-		rp := r
-		rp.Digest = d.Digest.String()
+		rp := r.AddDigest(d.Digest.String())
 		m, err = rc.ManifestGet(ctx, rp)
 		if err != nil {
 			return err
@@ -342,9 +343,8 @@ func (rc *RegClient) ImageCheckBase(ctx context.Context, r ref.Ref, opts ...Imag
 		if err != nil {
 			return err
 		}
-		rp := r
 		for _, d := range dl {
-			rp.Digest = d.Digest.String()
+			rp := r.AddDigest(d.Digest.String())
 			optP := append(opts, ImageWithPlatform(d.Platform.String()))
 			err = rc.ImageCheckBase(ctx, rp, optP...)
 			if err != nil {
@@ -374,9 +374,7 @@ func (rc *RegClient) ImageCheckBase(ctx context.Context, r ref.Ref, opts ...Imag
 		if err != nil {
 			return err
 		}
-		rp := baseR
-		rp.Digest = d.Digest.String()
-		baseM, err = rc.ManifestGet(ctx, rp)
+		baseM, err = rc.ManifestGet(ctx, baseR, WithManifestDesc(*d))
 		if err != nil {
 			return err
 		}
@@ -656,7 +654,6 @@ func (rc *RegClient) imageCopyOpt(ctx context.Context, refSrc ref.Ref, refTgt re
 					continue
 				}
 			}
-			dEntry := dEntry
 			waitCount++
 			go func() {
 				var err error
@@ -737,7 +734,6 @@ func (rc *RegClient) imageCopyOpt(ctx context.Context, refSrc ref.Ref, refTgt re
 				continue
 			}
 			waitCount++
-			layerSrc := layerSrc
 			go func() {
 				rc.slog.Info("Copy layer",
 					slog.String("source", refSrc.Reference),
@@ -830,7 +826,6 @@ func (rc *RegClient) imageCopyOpt(ctx context.Context, refSrc ref.Ref, refTgt re
 			}
 			referrerSrc := referrerSrc.SetDigest(rDesc.Digest.String())
 			referrerTgt := referrerTgt.SetDigest(rDesc.Digest.String())
-			rDesc := rDesc
 			waitCount++
 			go func() {
 				err := rc.imageCopyOpt(ctx, referrerSrc, referrerTgt, rDesc, true, parentsNew, opt)
@@ -886,19 +881,11 @@ func (rc *RegClient) imageCopyOpt(ctx context.Context, refSrc ref.Ref, refTgt re
 		for _, tag := range opt.tagList {
 			if strings.HasPrefix(tag, prefix) {
 				// skip referrers that were copied above
-				found := false
-				for _, referrerTag := range referrerTags {
-					if referrerTag == tag {
-						found = true
-						break
-					}
-				}
-				if found {
+				if slices.Contains(referrerTags, tag) {
 					continue
 				}
 				refTagSrc := refSrc.SetTag(tag)
 				refTagTgt := refTgt.SetTag(tag)
-				tag := tag
 				waitCount++
 				go func() {
 					err := rc.imageCopyOpt(ctx, refTagSrc, refTagTgt, descriptor.Descriptor{}, false, parentsNew, opt)
@@ -1121,12 +1108,7 @@ func (rc *RegClient) ImageExport(ctx context.Context, r ref.Ref, outStream io.Wr
 			return err
 		}
 		refTag := opt.exportRef.ToReg()
-		if refTag.Digest != "" {
-			refTag.Digest = ""
-		}
-		if refTag.Tag == "" {
-			refTag.Tag = "latest"
-		}
+		refTag = refTag.SetTag(cmp.Or(refTag.Tag, "latest"))
 		dockerManifest := dockerTarManifest{
 			RepoTags:     []string{refTag.CommonName()},
 			Config:       tarOCILayoutDescPath(conf),
@@ -1384,14 +1366,9 @@ func (rc *RegClient) imageImportDockerAddLayerHandlers(ctx context.Context, r re
 		tags := []string{}
 		for i, entry := range trd.dockerManifestList {
 			tags = append(tags, entry.RepoTags...)
-			for _, tag := range entry.RepoTags {
-				if tag == trd.name {
-					index = i
-					found = true
-					break
-				}
-			}
-			if found {
+			if slices.Contains(entry.RepoTags, trd.name) {
+				index = i
+				found = true
 				break
 			}
 		}
@@ -1674,8 +1651,7 @@ func (rc *RegClient) imageImportOCIHandleManifest(ctx context.Context, r ref.Ref
 	// add a finish func to push the manifest, this gets skipped for the index.json
 	if push {
 		trd.finish = append(trd.finish, func() error {
-			mRef := r
-			mRef.Digest = string(m.GetDescriptor().Digest)
+			mRef := r.SetDigest(m.GetDescriptor().Digest.String())
 			_, err := rc.ManifestHead(ctx, mRef)
 			if err == nil {
 				return nil
@@ -1706,10 +1682,8 @@ func (rc *RegClient) imageImportOCIPushManifests(_ context.Context, _ ref.Ref, t
 func imagePlatformInList(target *platform.Platform, list []string) (bool, error) {
 	// special case for an unset platform
 	if target == nil || target.OS == "" {
-		for _, entry := range list {
-			if entry == "" {
-				return true, nil
-			}
+		if slices.Contains(list, "") {
+			return true, nil
 		}
 		return false, nil
 	}
@@ -1818,10 +1792,8 @@ func (trd *tarReadData) tarReadAll(rs io.ReadSeeker) error {
 }
 
 func (trd *tarReadData) linkAdd(src, tgt string) bool {
-	for _, entry := range trd.links[tgt] {
-		if entry == src {
-			return false
-		}
+	if slices.Contains(trd.links[tgt], src) {
+		return false
 	}
 	trd.links[tgt] = append(trd.links[tgt], src)
 	return true
@@ -1839,7 +1811,7 @@ func (trd *tarReadData) linkList(tgt string) ([]string, error) {
 }
 
 // tarReadFileJSON reads the current tar entry and unmarshals json into provided interface.
-func (trd *tarReadData) tarReadFileJSON(data interface{}) error {
+func (trd *tarReadData) tarReadFileJSON(data any) error {
 	b, err := io.ReadAll(trd.tr)
 	if err != nil {
 		return err
@@ -1895,7 +1867,7 @@ func (td *tarWriteData) tarWriteHeader(filename string, size int64) error {
 	return td.tw.WriteHeader(&header)
 }
 
-func (td *tarWriteData) tarWriteFileJSON(filename string, data interface{}) error {
+func (td *tarWriteData) tarWriteFileJSON(filename string, data any) error {
 	dataJSON, err := json.Marshal(data)
 	if err != nil {
 		return err
diff --git a/internal/ascii/progress.go b/internal/ascii/progress.go
index c5a7f2d..ae8ebe5 100644
--- a/internal/ascii/progress.go
+++ b/internal/ascii/progress.go
@@ -42,15 +42,11 @@ func (p *ProgressBar) Generate(pct float64, pre, post string) []byte {
 		pct = 1
 	}
 	curWidth := p.Width - (len(pre) + len(post) + 2)
-	if curWidth < p.Min {
-		curWidth = p.Min
-	} else if curWidth > p.Max {
-		curWidth = p.Max
-	}
+	curWidth = min(max(curWidth, p.Min), p.Max)
 	buf := make([]byte, curWidth)
 
 	doneLen := int(float64(curWidth) * pct)
-	for i := 0; i < doneLen; i++ {
+	for i := range doneLen {
 		buf[i] = p.Done
 	}
 	if doneLen < curWidth {
@@ -59,5 +55,5 @@ func (p *ProgressBar) Generate(pct float64, pre, post string) []byte {
 	for i := doneLen + 1; i < curWidth; i++ {
 		buf[i] = p.Pending
 	}
-	return []byte(fmt.Sprintf("%s%c%s%c%s\n", pre, p.Start, buf, p.End, post))
+	return fmt.Appendf(nil, "%s%c%s%c%s\n", pre, p.Start, buf, p.End, post)
 }
diff --git a/internal/auth/auth.go b/internal/auth/auth.go
index b8f5066..2799bfc 100644
--- a/internal/auth/auth.go
+++ b/internal/auth/auth.go
@@ -36,7 +36,7 @@ const (
 )
 
 func init() {
-	for c := 0; c < 256; c++ {
+	for c := range 256 {
 		charLUs[c] = 0
 		if strings.ContainsRune(" \t\r\n", rune(c)) {
 			charLUs[c] |= isSpace
diff --git a/internal/httplink/httplink.go b/internal/httplink/httplink.go
index b747cfd..8638122 100644
--- a/internal/httplink/httplink.go
+++ b/internal/httplink/httplink.go
@@ -25,7 +25,7 @@ const (
 )
 
 func init() {
-	for c := 0; c < 256; c++ {
+	for c := range 256 {
 		charLUs[c] = 0
 		if strings.ContainsRune(" \t\r\n", rune(c)) {
 			charLUs[c] |= isSpace
diff --git a/internal/pqueue/pqueue.go b/internal/pqueue/pqueue.go
index 4d493f2..2eea096 100644
--- a/internal/pqueue/pqueue.go
+++ b/internal/pqueue/pqueue.go
@@ -4,6 +4,7 @@ package pqueue
 import (
 	"context"
 	"fmt"
+	"slices"
 	"sync"
 )
 
@@ -63,18 +64,11 @@ func (q *Queue[T]) Acquire(ctx context.Context, e T) (func(), error) {
 	case <-ctx.Done():
 		// context abort, remove queued entry
 		q.mu.Lock()
-		for i := range q.queued {
-			if q.queued[i] == &e {
-				if len(q.queued) >= i+1 {
-					q.queued = q.queued[:i]
-					q.wait = q.wait[:i]
-				} else {
-					q.queued = append(q.queued[:i], q.queued[i+1:]...)
-					q.wait = append(q.wait[:i], q.wait[i+1:]...)
-				}
-				q.mu.Unlock()
-				return nil, ctx.Err()
-			}
+		if i := slices.Index(q.queued, &e); i >= 0 {
+			q.queued = slices.Delete(q.queued, i, i+1)
+			q.wait = slices.Delete(q.wait, i, i+1)
+			q.mu.Unlock()
+			return nil, ctx.Err()
 		}
 		q.mu.Unlock()
 		// queued entry found, assume race condition with context and entry being released, release next entry
@@ -113,15 +107,8 @@ func (q *Queue[T]) release(prev *T) {
 	q.mu.Lock()
 	defer q.mu.Unlock()
 	// remove prev entry from active list
-	for i := range q.active {
-		if q.active[i] == prev {
-			if i == len(q.active)+1 {
-				q.active = q.active[:i]
-			} else {
-				q.active = append(q.active[:i], q.active[i+1:]...)
-			}
-			break
-		}
+	if i := slices.Index(q.active, prev); i >= 0 {
+		q.active = slices.Delete(q.active, i, i+1)
 	}
 	// skip checks when at limit or nothing queued
 	if len(q.queued) == 0 {
@@ -140,23 +127,13 @@ func (q *Queue[T]) release(prev *T) {
 	if q.next != nil && len(q.queued) > 1 {
 		i = q.next(q.queued, q.active)
 		// validate response
-		if i < 0 {
-			i = 0
-		}
-		if i >= len(q.queued) {
-			i = len(q.queued) - 1
-		}
+		i = max(min(i, len(q.queued)-1), 0)
 	}
 	// release queued entry, move to active list, and remove from queued/wait lists
 	close(*q.wait[i])
 	q.active = append(q.active, q.queued[i])
-	if i == len(q.queued)-1 {
-		q.queued = q.queued[:i]
-		q.wait = q.wait[:i]
-	} else {
-		q.queued = append(q.queued[:i], q.queued[i+1:]...)
-		q.wait = append(q.wait[:i], q.wait[i+1:]...)
-	}
+	q.queued = slices.Delete(q.queued, i, i+1)
+	q.wait = slices.Delete(q.wait, i, i+1)
 }
 
 // releaseFn is a convenience wrapper around [release].
@@ -191,11 +168,7 @@ func AcquireMulti[T any](ctx context.Context, e T, qList ...*Queue[T]) (context.
 	// delete nil entries
 	for i := len(qList) - 1; i >= 0; i-- {
 		if qList[i] == nil {
-			if i == len(qList)-1 {
-				qList = qList[:i]
-			} else {
-				qList = append(qList[:i], qList[i+1:]...)
-			}
+			qList = slices.Delete(qList, i, i+1)
 		}
 	}
 	// empty/nil list is a noop
@@ -276,11 +249,9 @@ func (q *Queue[T]) checkContext(ctx context.Context) (bool, error) {
 	if qCtxVal.qList == nil {
 		return false, nil
 	}
-	for _, cur := range qCtxVal.qList {
-		if cur == q {
-			// instance already locked
-			return true, nil
-		}
+	if slices.Contains(qCtxVal.qList, q) {
+		// instance already locked
+		return true, nil
 	}
 	return true, fmt.Errorf("cannot acquire new locks during a transaction")
 }
diff --git a/internal/reghttp/http.go b/internal/reghttp/http.go
index e461a9c..fb9f057 100644
--- a/internal/reghttp/http.go
+++ b/internal/reghttp/http.go
@@ -16,6 +16,7 @@ import (
 	"os"
 	"path/filepath"
 	"regexp"
+	"slices"
 	"sort"
 	"strconv"
 	"strings"
@@ -528,7 +529,7 @@ func (resp *Resp) next() error {
 		}
 		err = loopErr
 		if dropHost {
-			hosts = append(hosts[:curHost], hosts[curHost+1:]...)
+			hosts = slices.Delete(hosts, curHost, curHost+1)
 		} else if !retryHost {
 			curHost++
 		}
@@ -643,9 +644,7 @@ func (resp *Resp) backoffGet() time.Time {
 	defer ch.mu.Unlock()
 	if ch.backoffCur > 0 {
 		delay := c.delayInit << ch.backoffCur
-		if delay > c.delayMax {
-			delay = c.delayMax
-		}
+		delay = min(delay, c.delayMax)
 		next := ch.backoffLast.Add(delay)
 		now := time.Now()
 		if now.After(next) {
diff --git a/internal/reqresp/reqresp.go b/internal/reqresp/reqresp.go
index 085fed3..4843982 100644
--- a/internal/reqresp/reqresp.go
+++ b/internal/reqresp/reqresp.go
@@ -8,6 +8,7 @@ import (
 	"math/rand"
 	"net/http"
 	"regexp"
+	"slices"
 	"sync"
 	"testing"
 
@@ -66,14 +67,7 @@ func strMapMatch(a, b map[string][]string) bool {
 			return false
 		}
 		for _, ave := range av {
-			found := false
-			for _, bve := range bv {
-				if ave == bve {
-					found = true
-					break
-				}
-			}
-			if !found {
+			if !slices.Contains(bv, ave) {
 				return false
 			}
 		}
@@ -81,18 +75,6 @@ func strMapMatch(a, b map[string][]string) bool {
 	return true
 }
 
-func stateMatch(state string, list []string) bool {
-	if len(list) == 0 {
-		return true
-	}
-	for _, entry := range list {
-		if entry == state {
-			return true
-		}
-	}
-	return false
-}
-
 func (r *rrHandler) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
 	reqBody, err := io.ReadAll(req.Body)
 	if err != nil {
@@ -105,7 +87,7 @@ func (r *rrHandler) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
 	defer r.mu.Unlock()
 	for i, rr := range r.rrs {
 		reqMatch := rr.ReqEntry
-		if !stateMatch(r.state, reqMatch.IfState) ||
+		if (len(reqMatch.IfState) > 0 && !slices.Contains(reqMatch.IfState, r.state)) ||
 			reqMatch.Method != req.Method ||
 			(reqMatch.PathRE != nil && !reqMatch.PathRE.MatchString(req.URL.Path)) ||
 			(reqMatch.Path != "" && reqMatch.Path != req.URL.Path) ||
@@ -129,7 +111,7 @@ func (r *rrHandler) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
 
 		// for single use test cases, delete this entry
 		if reqMatch.DelOnUse {
-			r.rrs = append(r.rrs[:i], r.rrs[i+1:]...)
+			r.rrs = slices.Delete(r.rrs, i, i+1)
 		}
 		// update current state
 		if reqMatch.SetState != "" {
diff --git a/internal/timejson/timejson.go b/internal/timejson/timejson.go
index 3257089..c5aad64 100644
--- a/internal/timejson/timejson.go
+++ b/internal/timejson/timejson.go
@@ -20,7 +20,7 @@ func (d Duration) MarshalJSON() ([]byte, error) {
 
 // UnmarshalJSON converts json to a duration
 func (d *Duration) UnmarshalJSON(b []byte) error {
-	var v interface{}
+	var v any
 	if err := json.Unmarshal(b, &v); err != nil {
 		return err
 	}
diff --git a/manifest.go b/manifest.go
index 285f72e..ef871eb 100644
--- a/manifest.go
+++ b/manifest.go
@@ -99,7 +99,7 @@ func (rc *RegClient) ManifestGet(ctx context.Context, r ref.Ref, opts ...Manifes
 		fn(&opt)
 	}
 	if opt.d.Digest != "" {
-		r.Digest = opt.d.Digest.String()
+		r = r.AddDigest(opt.d.Digest.String())
 		data, err := opt.d.GetData()
 		if err == nil {
 			return manifest.New(
diff --git a/mod/config.go b/mod/config.go
index 046a452..f8cc6c1 100644
--- a/mod/config.go
+++ b/mod/config.go
@@ -4,6 +4,7 @@ import (
 	"context"
 	"fmt"
 	"regexp"
+	"slices"
 	"strconv"
 	"strings"
 	"time"
@@ -29,7 +30,7 @@ func WithBuildArgRm(arg string, value *regexp.Regexp) Opts {
 			for i := len(oc.History) - 1; i >= 0; i-- {
 				if argexp.MatchString(oc.History[i].CreatedBy) && oc.History[i].EmptyLayer {
 					// delete empty build arg history entry
-					oc.History = append(oc.History[:i], oc.History[i+1:]...)
+					oc.History = slices.Delete(oc.History, i, i+1)
 					changed = true
 				} else if match := runexp.FindStringSubmatch(oc.History[i].CreatedBy); len(match) == 4 {
 					// delete arg from run steps
@@ -61,7 +62,7 @@ func WithConfigCmd(cmd []string) Opts {
 	return func(dc *dagConfig, dm *dagManifest) error {
 		dc.stepsOCIConfig = append(dc.stepsOCIConfig, func(ctx context.Context, rc *regclient.RegClient, rSrc, rTgt ref.Ref, doc *dagOCIConfig) error {
 			oc := doc.oc.GetConfig()
-			if eqStrSlice(cmd, oc.Config.Cmd) {
+			if slices.Equal(cmd, oc.Config.Cmd) {
 				return nil
 			}
 			oc.Config.Cmd = cmd
@@ -112,7 +113,7 @@ func WithConfigEntrypoint(entrypoint []string) Opts {
 	return func(dc *dagConfig, dm *dagManifest) error {
 		dc.stepsOCIConfig = append(dc.stepsOCIConfig, func(ctx context.Context, rc *regclient.RegClient, rSrc, rTgt ref.Ref, doc *dagOCIConfig) error {
 			oc := doc.oc.GetConfig()
-			if eqStrSlice(entrypoint, oc.Config.Entrypoint) {
+			if slices.Equal(entrypoint, oc.Config.Entrypoint) {
 				return nil
 			}
 			oc.Config.Entrypoint = entrypoint
@@ -320,11 +321,7 @@ func WithEnv(name, value string) Opts {
 				found = true
 				if value == "" {
 					// delete an entry
-					if i < len(oc.Config.Env)-1 {
-						oc.Config.Env = append(oc.Config.Env[:i], oc.Config.Env[i+1:]...)
-					} else {
-						oc.Config.Env = oc.Config.Env[:i]
-					}
+					oc.Config.Env = slices.Delete(oc.Config.Env, i, i+1)
 					changed = true
 				} else if len(kvSplit) < 2 || value != kvSplit[1] {
 					// change an entry
diff --git a/mod/dag.go b/mod/dag.go
index 5c3c919..6d16dd3 100644
--- a/mod/dag.go
+++ b/mod/dag.go
@@ -3,10 +3,12 @@ package mod
 import (
 	"archive/tar"
 	"bytes"
+	"cmp"
 	"context"
 	"errors"
 	"fmt"
 	"io"
+	"slices"
 
 	"github.com/opencontainers/go-digest"
 
@@ -96,7 +98,7 @@ func dagGet(ctx context.Context, rc *regclient.RegClient, rSrc ref.Ref, d descri
 		cd, err := mi.GetConfig()
 		if err != nil && !errors.Is(err, errs.ErrUnsupportedMediaType) {
 			return nil, err
-		} else if err == nil && inListStr(cd.MediaType, mtKnownConfig) {
+		} else if err == nil && slices.Contains(mtKnownConfig, cd.MediaType) {
 			oc, err := rc.BlobGetOCIConfig(ctx, rSrc, cd)
 			if err != nil {
 				return nil, err
@@ -202,7 +204,7 @@ func dagPut(ctx context.Context, rc *regclient.RegClient, mc dagConfig, rSrc, rT
 			if child.mod != deleted {
 				continue
 			}
-			ociI.Manifests = append(ociI.Manifests[:i], ociI.Manifests[i+1:]...)
+			ociI.Manifests = slices.Delete(ociI.Manifests, i, i+1)
 			changed = true
 		}
 		err = manifest.OCIIndexToAny(ociI, &om)
@@ -315,12 +317,12 @@ func dagPut(ctx context.Context, rc *regclient.RegClient, mc dagConfig, rSrc, rT
 				}
 				continue
 			}
-			ociM.Layers = append(ociM.Layers[:i], ociM.Layers[i+1:]...)
+			ociM.Layers = slices.Delete(ociM.Layers, i, i+1)
 			if oc.RootFS.DiffIDs != nil && len(oc.RootFS.DiffIDs) >= i+1 {
-				oc.RootFS.DiffIDs = append(oc.RootFS.DiffIDs[:i], oc.RootFS.DiffIDs[i+1:]...)
+				oc.RootFS.DiffIDs = slices.Delete(oc.RootFS.DiffIDs, i, i+1)
 			}
 			if iConfig >= 0 {
-				oc.History = append(oc.History[:iConfig], oc.History[iConfig+1:]...)
+				oc.History = slices.Delete(oc.History, iConfig, iConfig+1)
 				iConfig--
 			}
 			changed = true
@@ -435,18 +437,10 @@ func dagPut(ctx context.Context, rc *regclient.RegClient, mc dagConfig, rSrc, rT
 		rPut := rTgt
 		if !dm.top {
 			mpOpts = append(mpOpts, regclient.WithManifestChild())
-			rPut.Tag = ""
-		}
-		if rPut.Tag == "" {
-			// push by digest
-			if dm.newDesc.Digest != "" {
-				rPut.Digest = dm.newDesc.Digest.String()
-			} else {
-				rPut.Digest = dm.origDesc.Digest.String()
-			}
-		} else {
-			// push by tag
-			rPut.Digest = ""
+			rPut = rPut.SetDigest(cmp.Or(dm.newDesc.Digest.String(), dm.origDesc.Digest.String()))
+		} else if rPut.Tag == "" || rPut.Digest != "" {
+			// update digest, prefer newDesc if set
+			rPut = rPut.AddDigest(cmp.Or(dm.newDesc.Digest.String(), dm.origDesc.Digest.String()))
 		}
 		err = rc.ManifestPut(ctx, rPut, dm.m, mpOpts...)
 		if err != nil {
diff --git a/mod/manifest.go b/mod/manifest.go
index 6b99526..9b19b9d 100644
--- a/mod/manifest.go
+++ b/mod/manifest.go
@@ -3,6 +3,7 @@ package mod
 import (
 	"context"
 	"fmt"
+	"slices"
 	"strings"
 
 	"github.com/opencontainers/go-digest"
@@ -830,17 +831,14 @@ func rebaseAddStep(dc *dagConfig, rBaseOld, rBaseNew ref.Ref) error {
 		pruneNum := len(layersOld)
 		i := 0
 		for pruneNum > 0 {
+			if i >= len(dm.layers) {
+				break
+			}
 			if dm.layers[i].mod == added {
 				i++
 				continue
 			}
-			if i == 0 {
-				dm.layers = dm.layers[1:]
-			} else if i >= len(dm.layers)-1 {
-				dm.layers = dm.layers[:i]
-			} else {
-				dm.layers = append(dm.layers[:i], dm.layers[i+1:]...)
-			}
+			dm.layers = slices.Delete(dm.layers, i, i+1)
 			pruneNum--
 		}
 		layers = layers[len(layersOld):]
diff --git a/mod/mod.go b/mod/mod.go
index 20f7619..773b20e 100644
--- a/mod/mod.go
+++ b/mod/mod.go
@@ -8,6 +8,7 @@ import (
 	"fmt"
 	"io"
 	"os"
+	"slices"
 	"time"
 
 	"github.com/klauspost/compress/zstd"
@@ -65,9 +66,7 @@ func Apply(ctx context.Context, rc *regclient.RegClient, rSrc ref.Ref, opts ...O
 	dm.top = true
 
 	// load the options
-	rTgt := rSrc
-	rTgt.Tag = ""
-	rTgt.Digest = ""
+	rTgt := rSrc.SetTag("")
 	dc := dagConfig{
 		stepsManifest:  []func(context.Context, *regclient.RegClient, ref.Ref, ref.Ref, *dagManifest) error{},
 		stepsOCIConfig: []func(context.Context, *regclient.RegClient, ref.Ref, ref.Ref, *dagOCIConfig) error{},
@@ -147,7 +146,7 @@ func Apply(ctx context.Context, rc *regclient.RegClient, rSrc ref.Ref, opts ...O
 				}
 			}
 			// changes for files within layers require extracting the tar and then repackaging it
-			if len(dc.stepsLayerFile) > 0 && inListStr(dl.desc.MediaType, mtKnownTar) {
+			if len(dc.stepsLayerFile) > 0 && slices.Contains(mtKnownTar, dl.desc.MediaType) {
 				if dl.mod == deleted {
 					return dl, nil
 				}
@@ -343,8 +342,8 @@ func Apply(ctx context.Context, rc *regclient.RegClient, rSrc ref.Ref, opts ...O
 	if err != nil {
 		return rTgt, err
 	}
-	if rTgt.Tag == "" {
-		rTgt.Digest = dm.m.GetDescriptor().Digest.String()
+	if rTgt.Tag == "" || rTgt.Digest != "" {
+		rTgt = rTgt.AddDigest(dm.m.GetDescriptor().Digest.String())
 	}
 	return rTgt, nil
 }
@@ -388,24 +387,3 @@ func WithDigestAlgo(algo digest.Algorithm) Opts {
 		return nil
 	}
 }
-
-func inListStr(str string, list []string) bool {
-	for _, s := range list {
-		if str == s {
-			return true
-		}
-	}
-	return false
-}
-
-func eqStrSlice(a, b []string) bool {
-	if len(a) != len(b) {
-		return false
-	}
-	for i := range a {
-		if a[i] != b[i] {
-			return false
-		}
-	}
-	return true
-}
diff --git a/pkg/template/printpretty.go b/pkg/template/printpretty.go
index ae36bf4..6d64ed9 100644
--- a/pkg/template/printpretty.go
+++ b/pkg/template/printpretty.go
@@ -9,7 +9,7 @@ type prettyPrinter interface {
 	MarshalPretty() ([]byte, error)
 }
 
-func printPretty(v interface{}) string {
+func printPretty(v any) string {
 	if pp, ok := v.(prettyPrinter); ok {
 		b, err := pp.MarshalPretty()
 		if err != nil {
diff --git a/pkg/template/template.go b/pkg/template/template.go
index a8eb40f..a1232c3 100644
--- a/pkg/template/template.go
+++ b/pkg/template/template.go
@@ -12,7 +12,7 @@ import (
 )
 
 var tmplFuncs = gotemplate.FuncMap{
-	"default": func(def, orig interface{}) interface{} {
+	"default": func(def, orig any) any {
 		if orig == nil || orig == reflect.Zero(reflect.TypeOf(orig)).Interface() {
 			return def
 		}
@@ -30,14 +30,14 @@ var tmplFuncs = gotemplate.FuncMap{
 		return strings.TrimSpace(string(b))
 	},
 	"join": strings.Join,
-	"json": func(v interface{}) string {
+	"json": func(v any) string {
 		buf := &bytes.Buffer{}
 		enc := json.NewEncoder(buf)
 		enc.SetEscapeHTML(false)
 		_ = enc.Encode(v)
 		return buf.String()
 	},
-	"jsonPretty": func(v interface{}) string {
+	"jsonPretty": func(v any) string {
 		buf := &bytes.Buffer{}
 		enc := json.NewEncoder(buf)
 		enc.SetEscapeHTML(false)
@@ -57,7 +57,7 @@ var tmplFuncs = gotemplate.FuncMap{
 type Opt func(*gotemplate.Template) (*gotemplate.Template, error)
 
 // Writer outputs a template to an io.Writer
-func Writer(out io.Writer, tmpl string, data interface{}, opts ...Opt) error {
+func Writer(out io.Writer, tmpl string, data any, opts ...Opt) error {
 	var err error
 	t := gotemplate.New("out").Funcs(tmplFuncs)
 	for _, opt := range opts {
@@ -74,7 +74,7 @@ func Writer(out io.Writer, tmpl string, data interface{}, opts ...Opt) error {
 }
 
 // String converts a template to a string
-func String(tmpl string, data interface{}, opts ...Opt) (string, error) {
+func String(tmpl string, data any, opts ...Opt) (string, error) {
 	var sb strings.Builder
 	err := Writer(&sb, tmpl, data)
 	if err != nil {
diff --git a/scheme/ocidir/manifest.go b/scheme/ocidir/manifest.go
index c4cf0ee..6d20ab7 100644
--- a/scheme/ocidir/manifest.go
+++ b/scheme/ocidir/manifest.go
@@ -10,6 +10,7 @@ import (
 	"log/slog"
 	"os"
 	"path"
+	"slices"
 
 	// crypto libraries included for go-digest
 	_ "crypto/sha256"
@@ -69,7 +70,7 @@ func (o *OCIDir) ManifestDelete(ctx context.Context, r ref.Ref, opts ...scheme.M
 		// remove matching entry from index
 		if r.Digest != "" && index.Manifests[i].Digest.String() == r.Digest {
 			changed = true
-			index.Manifests = append(index.Manifests[:i], index.Manifests[i+1:]...)
+			index.Manifests = slices.Delete(index.Manifests, i, i+1)
 		}
 	}
 	// push manifest back out
@@ -104,7 +105,7 @@ func (o *OCIDir) manifestGet(_ context.Context, r ref.Ref) (manifest.Manifest, e
 		return nil, fmt.Errorf("unable to read oci index: %w", err)
 	}
 	if r.Digest == "" && r.Tag == "" {
-		r.Tag = "latest"
+		r = r.SetTag("latest")
 	}
 	desc, err := indexGet(index, r)
 	if err != nil {
@@ -151,7 +152,7 @@ func (o *OCIDir) ManifestHead(ctx context.Context, r ref.Ref) (manifest.Manifest
 		return nil, fmt.Errorf("unable to read oci index: %w", err)
 	}
 	if r.Digest == "" && r.Tag == "" {
-		r.Tag = "latest"
+		r = r.SetTag("latest")
 	}
 	desc, err := indexGet(index, r)
 	if err != nil {
@@ -181,9 +182,9 @@ func (o *OCIDir) ManifestHead(ctx context.Context, r ref.Ref) (manifest.Manifest
 			return nil, err
 		}
 		mt := struct {
-			MediaType     string        `json:"mediaType,omitempty"`
-			SchemaVersion int           `json:"schemaVersion,omitempty"`
-			Signatures    []interface{} `json:"signatures,omitempty"`
+			MediaType     string `json:"mediaType,omitempty"`
+			SchemaVersion int    `json:"schemaVersion,omitempty"`
+			Signatures    []any  `json:"signatures,omitempty"`
 		}{}
 		err = json.Unmarshal(raw, &mt)
 		if err != nil {
@@ -218,7 +219,7 @@ func (o *OCIDir) manifestPut(ctx context.Context, r ref.Ref, m manifest.Manifest
 		opt(&config)
 	}
 	if !config.Child && r.Digest == "" && r.Tag == "" {
-		r.Tag = "latest"
+		r = r.SetTag("latest")
 	}
 	err := o.initIndex(r, true)
 	if err != nil {
@@ -232,9 +233,13 @@ func (o *OCIDir) manifestPut(ctx context.Context, r ref.Ref, m manifest.Manifest
 	if err != nil {
 		return fmt.Errorf("could not serialize manifest: %w", err)
 	}
-	if r.Tag == "" {
-		// force digest to match manifest value
-		r.Digest = desc.Digest.String()
+	if r.Digest != "" && desc.Digest.String() != r.Digest {
+		// Digest algorithm may have changed, try recreating the manifest with the provided ref.
+		// This will fail if the ref digest does not match the manifest.
+		m, err = manifest.New(manifest.WithRef(r), manifest.WithRaw(b))
+		if err != nil {
+			return fmt.Errorf("failed to rebuilding manifest with ref \"%s\": %w", r.CommonName(), err)
+		}
 	}
 	if r.Tag != "" {
 		desc.Annotations = map[string]string{
diff --git a/scheme/ocidir/ocidir.go b/scheme/ocidir/ocidir.go
index 2dcf189..9d59286 100644
--- a/scheme/ocidir/ocidir.go
+++ b/scheme/ocidir/ocidir.go
@@ -10,6 +10,7 @@ import (
 	"log/slog"
 	"os"
 	"path"
+	"slices"
 	"strings"
 	"sync"
 
@@ -335,7 +336,7 @@ func indexCreate() v1.Index {
 
 func indexGet(index v1.Index, r ref.Ref) (descriptor.Descriptor, error) {
 	if r.Digest == "" && r.Tag == "" {
-		r.Tag = "latest"
+		r = r.SetTag("latest")
 	}
 	if r.Digest != "" {
 		for _, im := range index.Manifests {
@@ -395,7 +396,7 @@ func indexSet(index *v1.Index, r ref.Ref, d descriptor.Descriptor) error {
 			// prune entries without any tag and a matching digest
 			// or entries with a matching tag
 			if (name == "" && index.Manifests[i].Digest == d.Digest) || (r.Tag != "" && name == r.Tag) {
-				index.Manifests = append(index.Manifests[:i], index.Manifests[i+1:]...)
+				index.Manifests = slices.Delete(index.Manifests, i, i+1)
 			}
 		}
 	} else {
diff --git a/scheme/ocidir/tag.go b/scheme/ocidir/tag.go
index fd34a32..741ebda 100644
--- a/scheme/ocidir/tag.go
+++ b/scheme/ocidir/tag.go
@@ -4,6 +4,7 @@ import (
 	"context"
 	"encoding/json"
 	"fmt"
+	"slices"
 	"sort"
 	"strings"
 
@@ -34,7 +35,7 @@ func (o *OCIDir) tagDelete(_ context.Context, r ref.Ref) error {
 	for i, desc := range index.Manifests {
 		if t, ok := desc.Annotations[aOCIRefName]; ok && t == r.Tag {
 			// remove matching entry from index
-			index.Manifests = append(index.Manifests[:i], index.Manifests[i+1:]...)
+			index.Manifests = slices.Delete(index.Manifests, i, i+1)
 			changed = true
 		}
 	}
@@ -63,14 +64,7 @@ func (o *OCIDir) TagList(ctx context.Context, r ref.Ref, opts ...scheme.TagOpts)
 			if i := strings.LastIndex(t, ":"); i >= 0 {
 				t = t[i+1:]
 			}
-			found := false
-			for _, cur := range tl {
-				if cur == t {
-					found = true
-					break
-				}
-			}
-			if !found {
+			if !slices.Contains(tl, t) {
 				tl = append(tl, t)
 			}
 		}
diff --git a/scheme/reg/blob.go b/scheme/reg/blob.go
index f477f88..bdc5ccf 100644
--- a/scheme/reg/blob.go
+++ b/scheme/reg/blob.go
@@ -266,16 +266,14 @@ func (reg *Reg) blobGetUploadURL(ctx context.Context, r ref.Ref, d descriptor.De
 				slog.String("err", err.Error()))
 		} else {
 			host := reg.hostGet(r.Registry)
+			reg.muHost.Lock()
 			if (host.BlobChunk > 0 && minSize > host.BlobChunk) || (host.BlobChunk <= 0 && minSize > reg.blobChunkSize) {
-				if minSize > reg.blobChunkLimit {
-					host.BlobChunk = reg.blobChunkLimit
-				} else {
-					host.BlobChunk = minSize
-				}
+				host.BlobChunk = min(minSize, reg.blobChunkLimit)
 				reg.slog.Debug("Registry requested min chunk size",
 					slog.Int64("size", host.BlobChunk),
 					slog.String("host", host.Name))
 			}
+			reg.muHost.Unlock()
 		}
 	}
 	// Extract the location into a new putURL based on whether it's relative, fqdn with a scheme, or without a scheme.
@@ -335,17 +333,14 @@ func (reg *Reg) blobMount(ctx context.Context, rTgt ref.Ref, d descriptor.Descri
 				slog.String("err", err.Error()))
 		} else {
 			host := reg.hostGet(rTgt.Registry)
+			reg.muHost.Lock()
 			if (host.BlobChunk > 0 && minSize > host.BlobChunk) || (host.BlobChunk <= 0 && minSize > reg.blobChunkSize) {
-				// TODO(bmitch): potential race condition, may need a lock before setting/using values in host
-				if minSize > reg.blobChunkLimit {
-					host.BlobChunk = reg.blobChunkLimit
-				} else {
-					host.BlobChunk = minSize
-				}
+				host.BlobChunk = min(minSize, reg.blobChunkLimit)
 				reg.slog.Debug("Registry requested min chunk size",
 					slog.Int64("size", host.BlobChunk),
 					slog.String("host", host.Name))
 			}
+			reg.muHost.Unlock()
 		}
 	}
 	// 201 indicates the blob mount succeeded
diff --git a/scheme/reg/tag.go b/scheme/reg/tag.go
index e24b54b..0ed66c8 100644
--- a/scheme/reg/tag.go
+++ b/scheme/reg/tag.go
@@ -181,9 +181,8 @@ func (reg *Reg) TagDelete(ctx context.Context, r ref.Ref) error {
 		return fmt.Errorf("failed sending dummy manifest to delete %s: %w", r.CommonName(), err)
 	}
 
-	r.Digest = tempManifest.GetDescriptor().Digest.String()
-
 	// delete manifest by digest
+	r = r.AddDigest(tempManifest.GetDescriptor().Digest.String())
 	reg.slog.Debug("Deleting dummy manifest",
 		slog.String("ref", r.Reference),
 		slog.String("digest", r.Digest))
diff --git a/types/manifest/docker1.go b/types/manifest/docker1.go
index e95aa43..9a99355 100644
--- a/types/manifest/docker1.go
+++ b/types/manifest/docker1.go
@@ -82,10 +82,10 @@ func (m *docker1SignedManifest) GetLayers() ([]descriptor.Descriptor, error) {
 	return dl, nil
 }
 
-func (m *docker1Manifest) GetOrig() interface{} {
+func (m *docker1Manifest) GetOrig() any {
 	return m.Manifest
 }
-func (m *docker1SignedManifest) GetOrig() interface{} {
+func (m *docker1SignedManifest) GetOrig() any {
 	return m.SignedManifest
 }
 
@@ -186,7 +186,7 @@ func (m *docker1SignedManifest) SetLayers(dl []descriptor.Descriptor) error {
 	return fmt.Errorf("set methods not supported for for media type %s%.0w", m.desc.MediaType, errs.ErrUnsupportedMediaType)
 }
 
-func (m *docker1Manifest) SetOrig(origIn interface{}) error {
+func (m *docker1Manifest) SetOrig(origIn any) error {
 	orig, ok := origIn.(schema1.Manifest)
 	if !ok {
 		return errs.ErrUnsupportedMediaType
@@ -211,7 +211,7 @@ func (m *docker1Manifest) SetOrig(origIn interface{}) error {
 	return nil
 }
 
-func (m *docker1SignedManifest) SetOrig(origIn interface{}) error {
+func (m *docker1SignedManifest) SetOrig(origIn any) error {
 	orig, ok := origIn.(schema1.SignedManifest)
 	if !ok {
 		return errs.ErrUnsupportedMediaType
diff --git a/types/manifest/docker2.go b/types/manifest/docker2.go
index 7d04bb7..780f121 100644
--- a/types/manifest/docker2.go
+++ b/types/manifest/docker2.go
@@ -88,10 +88,10 @@ func (m *docker2ManifestList) GetLayers() ([]descriptor.Descriptor, error) {
 	return []descriptor.Descriptor{}, fmt.Errorf("layers are not available for media type %s%.0w", m.desc.MediaType, errs.ErrUnsupportedMediaType)
 }
 
-func (m *docker2Manifest) GetOrig() interface{} {
+func (m *docker2Manifest) GetOrig() any {
 	return m.Manifest
 }
-func (m *docker2ManifestList) GetOrig() interface{} {
+func (m *docker2ManifestList) GetOrig() any {
 	return m.ManifestList
 }
 
@@ -229,7 +229,7 @@ func (m *docker2ManifestList) MarshalPretty() ([]byte, error) {
 		fmt.Fprintf(tw, "\t\n")
 		dRef := m.r
 		if dRef.Reference != "" {
-			dRef.Digest = d.Digest.String()
+			dRef = dRef.AddDigest(d.Digest.String())
 			fmt.Fprintf(tw, "  Name:\t%s\n", dRef.CommonName())
 		}
 		err := d.MarshalPrettyTW(tw, "  ")
@@ -294,7 +294,7 @@ func (m *docker2ManifestList) SetManifestList(dl []descriptor.Descriptor) error
 	return m.updateDesc()
 }
 
-func (m *docker2Manifest) SetOrig(origIn interface{}) error {
+func (m *docker2Manifest) SetOrig(origIn any) error {
 	orig, ok := origIn.(schema2.Manifest)
 	if !ok {
 		return errs.ErrUnsupportedMediaType
@@ -308,7 +308,7 @@ func (m *docker2Manifest) SetOrig(origIn interface{}) error {
 	return m.updateDesc()
 }
 
-func (m *docker2ManifestList) SetOrig(origIn interface{}) error {
+func (m *docker2ManifestList) SetOrig(origIn any) error {
 	orig, ok := origIn.(schema2.ManifestList)
 	if !ok {
 		return errs.ErrUnsupportedMediaType
diff --git a/types/manifest/manifest.go b/types/manifest/manifest.go
index dd2a640..4aa1791 100644
--- a/types/manifest/manifest.go
+++ b/types/manifest/manifest.go
@@ -31,14 +31,14 @@ import (
 // many calls are only supported by certain underlying media types.
 type Manifest interface {
 	GetDescriptor() descriptor.Descriptor
-	GetOrig() interface{}
+	GetOrig() any
 	GetRef() ref.Ref
 	IsList() bool
 	IsSet() bool
 	MarshalJSON() ([]byte, error)
 	RawBody() ([]byte, error)
 	RawHeaders() (http.Header, error)
-	SetOrig(interface{}) error
+	SetOrig(any) error
 
 	// Deprecated: GetConfig should be accessed using [Imager] interface.
 	GetConfig() (descriptor.Descriptor, error)
@@ -96,7 +96,7 @@ type manifestConfig struct {
 	r      ref.Ref
 	desc   descriptor.Descriptor
 	raw    []byte
-	orig   interface{}
+	orig   any
 	header http.Header
 }
 type Opts func(*manifestConfig)
@@ -156,7 +156,7 @@ func WithHeader(header http.Header) Opts {
 }
 
 // WithOrig provides the original manifest variable.
-func WithOrig(orig interface{}) Opts {
+func WithOrig(orig any) Opts {
 	return func(mc *manifestConfig) {
 		mc.orig = orig
 	}
@@ -275,7 +275,7 @@ func HasRateLimit(m Manifest) bool {
 }
 
 // OCIIndexFromAny converts manifest lists to an OCI index.
-func OCIIndexFromAny(orig interface{}) (v1.Index, error) {
+func OCIIndexFromAny(orig any) (v1.Index, error) {
 	ociI := v1.Index{
 		Versioned: v1.IndexSchemaVersion,
 		MediaType: mediatype.OCI1ManifestList,
@@ -293,8 +293,8 @@ func OCIIndexFromAny(orig interface{}) (v1.Index, error) {
 }
 
 // OCIIndexToAny converts from an OCI index back to the manifest list.
-func OCIIndexToAny(ociI v1.Index, origP interface{}) error {
-	// reflect is used to handle both *interface{} and *Manifest
+func OCIIndexToAny(ociI v1.Index, origP any) error {
+	// reflect is used to handle both *interface and *Manifest
 	rv := reflect.ValueOf(origP)
 	for rv.IsValid() && rv.Type().Kind() == reflect.Ptr {
 		rv = rv.Elem()
@@ -321,7 +321,7 @@ func OCIIndexToAny(ociI v1.Index, origP interface{}) error {
 }
 
 // OCIManifestFromAny converts an image manifest to an OCI manifest.
-func OCIManifestFromAny(orig interface{}) (v1.Manifest, error) {
+func OCIManifestFromAny(orig any) (v1.Manifest, error) {
 	ociM := v1.Manifest{
 		Versioned: v1.ManifestSchemaVersion,
 		MediaType: mediatype.OCI1Manifest,
@@ -341,8 +341,8 @@ func OCIManifestFromAny(orig interface{}) (v1.Manifest, error) {
 }
 
 // OCIManifestToAny converts an OCI manifest back to the image manifest.
-func OCIManifestToAny(ociM v1.Manifest, origP interface{}) error {
-	// reflect is used to handle both *interface{} and *Manifest
+func OCIManifestToAny(ociM v1.Manifest, origP any) error {
+	// reflect is used to handle both *interface and *Manifest
 	rv := reflect.ValueOf(origP)
 	for rv.IsValid() && rv.Type().Kind() == reflect.Ptr {
 		rv = rv.Elem()
@@ -372,7 +372,7 @@ func OCIManifestToAny(ociM v1.Manifest, origP interface{}) error {
 
 // FromOrig creates a new manifest from the original upstream manifest type.
 // This method should be used if you are creating a new manifest rather than pulling one from a registry.
-func fromOrig(c common, orig interface{}) (Manifest, error) {
+func fromOrig(c common, orig any) (Manifest, error) {
 	var mt string
 	var m Manifest
 	origDigest := c.desc.Digest
@@ -473,7 +473,7 @@ func fromCommon(c common) (Manifest, error) {
 			mt := struct {
 				MediaType     string                  `json:"mediaType,omitempty"`
 				SchemaVersion int                     `json:"schemaVersion,omitempty"`
-				Signatures    []interface{}           `json:"signatures,omitempty"`
+				Signatures    []any                   `json:"signatures,omitempty"`
 				Manifests     []descriptor.Descriptor `json:"manifests,omitempty"`
 				Layers        []descriptor.Descriptor `json:"layers,omitempty"`
 			}{}
diff --git a/types/manifest/oci1.go b/types/manifest/oci1.go
index 594a765..1c49b9f 100644
--- a/types/manifest/oci1.go
+++ b/types/manifest/oci1.go
@@ -115,13 +115,13 @@ func (m *oci1Artifact) GetLayers() ([]descriptor.Descriptor, error) {
 	return m.Blobs, nil
 }
 
-func (m *oci1Manifest) GetOrig() interface{} {
+func (m *oci1Manifest) GetOrig() any {
 	return m.Manifest
 }
-func (m *oci1Index) GetOrig() interface{} {
+func (m *oci1Index) GetOrig() any {
 	return m.Index
 }
-func (m *oci1Artifact) GetOrig() interface{} {
+func (m *oci1Artifact) GetOrig() any {
 	return m.ArtifactManifest
 }
 
@@ -301,7 +301,7 @@ func (m *oci1Index) MarshalPretty() ([]byte, error) {
 		fmt.Fprintf(tw, "\t\n")
 		dRef := m.r
 		if dRef.Reference != "" {
-			dRef.Digest = d.Digest.String()
+			dRef = dRef.AddDigest(d.Digest.String())
 			fmt.Fprintf(tw, "  Name:\t%s\n", dRef.CommonName())
 		}
 		err := d.MarshalPrettyTW(tw, "  ")
@@ -473,7 +473,7 @@ func (m *oci1Index) SetManifestList(dl []descriptor.Descriptor) error {
 	return m.updateDesc()
 }
 
-func (m *oci1Manifest) SetOrig(origIn interface{}) error {
+func (m *oci1Manifest) SetOrig(origIn any) error {
 	orig, ok := origIn.(v1.Manifest)
 	if !ok {
 		return errs.ErrUnsupportedMediaType
@@ -488,7 +488,7 @@ func (m *oci1Manifest) SetOrig(origIn interface{}) error {
 	return m.updateDesc()
 }
 
-func (m *oci1Index) SetOrig(origIn interface{}) error {
+func (m *oci1Index) SetOrig(origIn any) error {
 	orig, ok := origIn.(v1.Index)
 	if !ok {
 		return errs.ErrUnsupportedMediaType
@@ -525,7 +525,7 @@ func (m *oci1Index) SetSubject(d *descriptor.Descriptor) error {
 	return m.updateDesc()
 }
 
-func (m *oci1Artifact) SetOrig(origIn interface{}) error {
+func (m *oci1Artifact) SetOrig(origIn any) error {
 	orig, ok := origIn.(v1.ArtifactManifest)
 	if !ok {
 		return errs.ErrUnsupportedMediaType
diff --git a/types/ref/ref.go b/types/ref/ref.go
index cb1030c..8c96f13 100644
--- a/types/ref/ref.go
+++ b/types/ref/ref.go
@@ -161,6 +161,14 @@ func NewHost(parse string) (Ref, error) {
 	return ret, nil
 }
 
+// AddDigest returns a ref with the requested digest set.
+// The tag will NOT be unset and the reference value will be reset.
+func (r Ref) AddDigest(digest string) Ref {
+	r.Digest = digest
+	r.Reference = r.CommonName()
+	return r
+}
+
 // CommonName outputs a parsable name from a reference.
 func (r Ref) CommonName() string {
 	cn := ""
diff --git a/types/referrer/referrer.go b/types/referrer/referrer.go
index 5a7bc4f..6069e8f 100644
--- a/types/referrer/referrer.go
+++ b/types/referrer/referrer.go
@@ -4,6 +4,7 @@ package referrer
 import (
 	"bytes"
 	"fmt"
+	"slices"
 	"sort"
 	"text/tabwriter"
 
@@ -79,11 +80,7 @@ func (rl *ReferrerList) Delete(m manifest.Manifest) error {
 	found := false
 	for i := len(rlM.Manifests) - 1; i >= 0; i-- {
 		if rlM.Manifests[i].Digest == mDesc.Digest {
-			if i < len(rlM.Manifests)-1 {
-				rlM.Manifests = append(rlM.Manifests[:i], rlM.Manifests[i+1:]...)
-			} else {
-				rlM.Manifests = rlM.Manifests[:i]
-			}
+			rlM.Manifests = slices.Delete(rlM.Manifests, i, i+1)
 			found = true
 		}
 	}
diff --git a/types/repo/repolist.go b/types/repo/repolist.go
index a27aa25..6d04aba 100644
--- a/types/repo/repolist.go
+++ b/types/repo/repolist.go
@@ -21,7 +21,7 @@ type RepoList struct {
 type repoCommon struct {
 	host      string
 	mt        string
-	orig      interface{}
+	orig      any
 	rawHeader http.Header
 	rawBody   []byte
 }
@@ -92,7 +92,7 @@ type RepoRegistryList struct {
 	Repositories []string `json:"repositories"`
 }
 
-func (r repoCommon) GetOrig() interface{} {
+func (r repoCommon) GetOrig() any {
 	return r.orig
 }
 
diff --git a/types/tag/taglist.go b/types/tag/taglist.go
index 02661e2..2c72e42 100644
--- a/types/tag/taglist.go
+++ b/types/tag/taglist.go
@@ -29,7 +29,7 @@ type List struct {
 type tagCommon struct {
 	r         ref.Ref
 	mt        string
-	orig      interface{}
+	orig      any
 	rawHeader http.Header
 	rawBody   []byte
 	url       *url.URL
@@ -206,7 +206,7 @@ func (l *List) Append(add *List) error {
 }
 
 // GetOrig returns the underlying tag data structure if defined.
-func (t tagCommon) GetOrig() interface{} {
+func (t tagCommon) GetOrig() any {
 	return t.orig
 }
 
diff --git a/types/warning/warning.go b/types/warning/warning.go
index f14fb33..eb0609f 100644
--- a/types/warning/warning.go
+++ b/types/warning/warning.go
@@ -4,6 +4,7 @@ package warning
 import (
 	"context"
 	"log/slog"
+	"slices"
 	"sync"
 )
 
@@ -20,11 +21,8 @@ type Warning struct {
 func (w *Warning) Handle(ctx context.Context, slog *slog.Logger, msg string) {
 	w.mu.Lock()
 	defer w.mu.Unlock()
-	// dedup
-	for _, entry := range w.List {
-		if entry == msg {
-			return
-		}
+	if slices.Contains(w.List, msg) {
+		return
 	}
 	w.List = append(w.List, msg)
 	// handle new warning if hook defined


Test patch:
diff --git a/cmd/regctl/artifact_test.go b/cmd/regctl/artifact_test.go
index 32e3097..adda90b 100644
--- a/cmd/regctl/artifact_test.go
+++ b/cmd/regctl/artifact_test.go
@@ -40,6 +40,26 @@ func TestArtifactGet(t *testing.T) {
 			args:      []string{"artifact", "get", "ocidir://../../testdata/testrepo:a1"},
 			expectOut: "eggs",
 		},
+		{
+			name:      "By Manifest filter layer media type",
+			args:      []string{"artifact", "get", "ocidir://../../testdata/testrepo:a3", "--file-media-type", "application/example.layer.2"},
+			expectOut: "2",
+		},
+		{
+			name:      "By Manifest filter layer filename",
+			args:      []string{"artifact", "get", "ocidir://../../testdata/testrepo:a3", "--file", "layer3.txt"},
+			expectOut: "3",
+		},
+		{
+			name:      "By Manifest filter layer media type missing",
+			args:      []string{"artifact", "get", "ocidir://../../testdata/testrepo:a3", "--file-media-type", "application/example.missing"},
+			expectErr: errs.ErrNotFound,
+		},
+		{
+			name:      "By Manifest filter layer filename missing",
+			args:      []string{"artifact", "get", "ocidir://../../testdata/testrepo:a3", "--file", "missing.txt"},
+			expectErr: errs.ErrNotFound,
+		},
 		{
 			name:      "By Subject",
 			args:      []string{"artifact", "get", "--subject", "ocidir://../../testdata/testrepo:v2", "--filter-artifact-type", "application/example.sbom"},
diff --git a/cmd/regctl/manifest_test.go b/cmd/regctl/manifest_test.go
index 42604fd..51f59e1 100644
--- a/cmd/regctl/manifest_test.go
+++ b/cmd/regctl/manifest_test.go
@@ -69,5 +69,4 @@ func TestManifestHead(t *testing.T) {
 			}
 		})
 	}
-
 }
diff --git a/config/docker_test.go b/config/docker_test.go
index b48c106..fff54a5 100644
--- a/config/docker_test.go
+++ b/config/docker_test.go
@@ -23,7 +23,6 @@ func TestDocker(t *testing.T) {
 	}
 	hostMap := map[string]*Host{}
 	for _, h := range hosts {
-		h := h // shadow h for unique var/pointer
 		hostMap[h.Name] = &h
 	}
 	tt := []struct {
diff --git a/image_test.go b/image_test.go
index 3741118..cc27230 100644
--- a/image_test.go
+++ b/image_test.go
@@ -401,7 +401,6 @@ func TestCopy(t *testing.T) {
 		},
 	}
 	for _, tc := range tt {
-		tc := tc
 		t.Run(tc.name, func(t *testing.T) {
 			t.Parallel()
 			rSrc, err := ref.New(tc.src)
diff --git a/internal/pqueue/pqueue_test.go b/internal/pqueue/pqueue_test.go
index 5accbc5..3060eb7 100644
--- a/internal/pqueue/pqueue_test.go
+++ b/internal/pqueue/pqueue_test.go
@@ -63,14 +63,14 @@ func TestQueue(t *testing.T) {
 	}
 	// background acquire two more, which should block
 	for _, i := range []int{2, 3} {
-		go func(i int) {
+		go func() {
 			done, err := q.Acquire(ctx, eList[i])
 			if err != nil {
 				t.Errorf("failed to acquire queue %d: %v", i, err)
 			}
 			finished <- i
 			done()
-		}(i)
+		}()
 	}
 	// verify background jobs blocked
 	sleepMS(2)
diff --git a/internal/reghttp/http_test.go b/internal/reghttp/http_test.go
index 6e80bd8..e2318c1 100644
--- a/internal/reghttp/http_test.go
+++ b/internal/reghttp/http_test.go
@@ -1644,7 +1644,7 @@ func TestRegHttp(t *testing.T) {
 			Headers:    headers,
 		}
 		chResults := make(chan error)
-		for i := 0; i < count; i++ {
+		for range count {
 			go func() {
 				resp, err := hc.Do(ctxTimeout, getReq)
 				if err == nil {
@@ -1653,7 +1653,7 @@ func TestRegHttp(t *testing.T) {
 				chResults <- err
 			}()
 		}
-		for i := 0; i < count; i++ {
+		for range count {
 			err := <-chResults
 			if err == nil {
 				t.Errorf("unexpected success on get for missing manifest")
@@ -1670,7 +1670,7 @@ func TestRegHttp(t *testing.T) {
 		}
 		start := time.Now()
 		count := 10
-		for i := 0; i < count; i++ {
+		for range count {
 			resp, err := hc.Do(ctx, getReq)
 			if err != nil {
 				t.Fatalf("failed to run get: %v", err)
diff --git a/mod/mod_test.go b/mod/mod_test.go
index 273b7f5..8a572f5 100644
--- a/mod/mod_test.go
+++ b/mod/mod_test.go
@@ -24,7 +24,6 @@ import (
 	"github.com/regclient/regclient/scheme/reg"
 	"github.com/regclient/regclient/types/errs"
 	"github.com/regclient/regclient/types/manifest"
-	"github.com/regclient/regclient/types/mediatype"
 	"github.com/regclient/regclient/types/platform"
 	"github.com/regclient/regclient/types/ref"
 )
@@ -1090,17 +1089,3 @@ func TestMod(t *testing.T) {
 		})
 	}
 }
-
-func TestInList(t *testing.T) {
-	t.Parallel()
-	t.Run("match", func(t *testing.T) {
-		if !inListStr(mediatype.Docker2LayerGzip, mtKnownTar) {
-			t.Errorf("did not find docker layer in known tar list")
-		}
-	})
-	t.Run("mismatch", func(t *testing.T) {
-		if inListStr(mediatype.Docker2LayerGzip, mtKnownConfig) {
-			t.Errorf("found docker layer in known config list")
-		}
-	})
-}
diff --git a/pkg/archive/compress_test.go b/pkg/archive/compress_test.go
index 1649348..5aa3fa0 100644
--- a/pkg/archive/compress_test.go
+++ b/pkg/archive/compress_test.go
@@ -42,7 +42,6 @@ func TestRoundtrip(t *testing.T) {
 		},
 	}
 	for _, algo := range []CompressType{CompressNone, CompressGzip, CompressXz, CompressZstd} {
-		algo := algo
 		t.Run(algo.String(), func(t *testing.T) {
 			for _, tc := range tt {
 				tc := tc
diff --git a/referrer_test.go b/referrer_test.go
index 7b6acfe..11688bc 100644
--- a/referrer_test.go
+++ b/referrer_test.go
@@ -112,7 +112,6 @@ func TestReferrerList(t *testing.T) {
 		},
 	}
 	for _, tcServer := range ttServers {
-		tcServer := tcServer
 		t.Run(tcServer.name, func(t *testing.T) {
 			t.Parallel()
 			refTag, err := ref.New(fmt.Sprintf("%s/%s:%s", tcServer.reg, testRepo, testTag))
@@ -168,7 +167,6 @@ func TestReferrerList(t *testing.T) {
 				},
 			}
 			for _, tc := range tt {
-				tc := tc
 				t.Run(tc.name, func(t *testing.T) {
 					t.Parallel()
 					rl, err := rc.ReferrerList(ctx, tc.ref, tc.opts...)
diff --git a/scheme/ocidir/blob_test.go b/scheme/ocidir/blob_test.go
index 0fec754..b0e3c89 100644
--- a/scheme/ocidir/blob_test.go
+++ b/scheme/ocidir/blob_test.go
@@ -156,7 +156,7 @@ func TestBlob(t *testing.T) {
 	count := 5
 	var wg sync.WaitGroup
 	wg.Add(count)
-	for i := 0; i < 5; i++ {
+	for range 5 {
 		go func() {
 			defer wg.Done()
 			bRdr := bytes.NewReader(bBytes)
diff --git a/scheme/ocidir/manifest_test.go b/scheme/ocidir/manifest_test.go
index 641a891..abffd7b 100644
--- a/scheme/ocidir/manifest_test.go
+++ b/scheme/ocidir/manifest_test.go
@@ -10,6 +10,8 @@ import (
 	"path/filepath"
 	"testing"
 
+	"github.com/opencontainers/go-digest"
+
 	"github.com/regclient/regclient/internal/copyfs"
 	"github.com/regclient/regclient/types/manifest"
 	"github.com/regclient/regclient/types/mediatype"
@@ -47,6 +49,10 @@ func TestManifest(t *testing.T) {
 	if !ml.IsList() {
 		t.Errorf("expected manifest list")
 	}
+	mlb, err := ml.RawBody()
+	if err != nil {
+		t.Fatalf("failed to get body of manifest: %v", err)
+	}
 	mli, ok := ml.(manifest.Indexer)
 	if !ok {
 		t.Fatalf("manifest doesn't support index methods")
@@ -61,11 +67,18 @@ func TestManifest(t *testing.T) {
 	if err != nil {
 		t.Errorf("manifest head failed on child digest: %v", err)
 	}
-	rMissing := r.SetDigest("sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa")
+	digMissing := digest.Canonical.FromString("missing")
+	digSHA512 := digest.SHA512.FromBytes(mlb)
+	rMissing := r.SetDigest(digMissing.String())
+	rSHA512 := r.SetDigest(digSHA512.String())
 	_, err = o.ManifestHead(ctx, rMissing)
 	if err == nil {
 		t.Errorf("manifest head succeeded on missing digest: %s", rMissing.CommonName())
 	}
+	_, err = o.ManifestHead(ctx, rSHA512)
+	if err == nil {
+		t.Errorf("manifest head succeeded on alternate algorithm: %s", rSHA512.CommonName())
+	}
 	// image manifest
 	m, err := o.ManifestGet(ctx, r)
 	if err != nil {
@@ -201,6 +214,17 @@ func TestManifest(t *testing.T) {
 	if err != nil {
 		t.Errorf("failed pushing manifest: %v", err)
 	}
+	// push invalid digest
+	err = o.manifestPut(ctx, rMissing, ml)
+	if err == nil {
+		t.Errorf("succeeded pushing with invalid digest")
+	}
+	// push with alternate digest
+	err = o.manifestPut(ctx, rSHA512, ml)
+	if err != nil {
+		t.Errorf("failed pushing with alternate digest algorithm: %v", err)
+	}
+	// close and reopen
 	err = o.Close(ctx, r)
 	if err != nil {
 		t.Errorf("failed closing: %v", err)
diff --git a/scheme/ocidir/referrer_test.go b/scheme/ocidir/referrer_test.go
index 0389ae2..dab4389 100644
--- a/scheme/ocidir/referrer_test.go
+++ b/scheme/ocidir/referrer_test.go
@@ -158,12 +158,12 @@ func TestReferrer(t *testing.T) {
 		if err != nil {
 			t.Fatalf("Failed running ManifestPut on Manifest again: %v", err)
 		}
-		r.Digest = artifactBM.GetDescriptor().Digest.String()
+		r = r.AddDigest(artifactBM.GetDescriptor().Digest.String())
 		err = o.ManifestPut(ctx, r, artifactBM, scheme.WithManifestChild())
 		if err != nil {
 			t.Fatalf("Failed running ManifestPut on Artifact: %v", err)
 		}
-		r.Digest = artifactCM.GetDescriptor().Digest.String()
+		r = r.AddDigest(artifactCM.GetDescriptor().Digest.String())
 		err = o.ManifestPut(ctx, r, artifactCM, scheme.WithManifestChild())
 		if err != nil {
 			t.Fatalf("Failed running ManifestPut on Artifact: %v", err)
diff --git a/scheme/ocidir/tag_test.go b/scheme/ocidir/tag_test.go
index 2115b6f..187d75d 100644
--- a/scheme/ocidir/tag_test.go
+++ b/scheme/ocidir/tag_test.go
@@ -4,6 +4,7 @@ import (
 	"context"
 	"errors"
 	"path/filepath"
+	"slices"
 	"testing"
 
 	"github.com/regclient/regclient/internal/copyfs"
@@ -37,7 +38,7 @@ func TestTag(t *testing.T) {
 			t.Fatalf("failed to get tags: %v", err)
 		}
 		for _, exTag := range exTags {
-			if !inListStr(exTag, tlTags) {
+			if !slices.Contains(tlTags, exTag) {
 				t.Errorf("missing tag: %s", exTag)
 			}
 		}
@@ -67,23 +68,14 @@ func TestTag(t *testing.T) {
 			t.Errorf("failed to get tags: %v", err)
 		}
 		for _, keep := range keepTags {
-			if !inListStr(keep, tlTags) {
+			if !slices.Contains(tlTags, keep) {
 				t.Errorf("missing tag: %s", keep)
 			}
 		}
 		for _, rm := range rmTags {
-			if inListStr(rm, tlTags) {
+			if slices.Contains(tlTags, rm) {
 				t.Errorf("tag not removed: %s", rm)
 			}
 		}
 	})
 }
-
-func inListStr(str string, list []string) bool {
-	for _, s := range list {
-		if str == s {
-			return true
-		}
-	}
-	return false
-}
diff --git a/scheme/reg/repo_test.go b/scheme/reg/repo_test.go
index 40e3a40..5ae2262 100644
--- a/scheme/reg/repo_test.go
+++ b/scheme/reg/repo_test.go
@@ -94,7 +94,7 @@ func TestRepo(t *testing.T) {
 				},
 				RespEntry: reqresp.RespEntry{
 					Status: http.StatusOK,
-					Body:   []byte(fmt.Sprintf(`{"repositories":["%s"]}`, strings.Join(listRegistry[:partialLen], `","`))),
+					Body:   fmt.Appendf(nil, `{"repositories":["%s"]}`, strings.Join(listRegistry[:partialLen], `","`)),
 					Headers: http.Header{
 						"Content-Type": {"text/plain; charset=utf-8"},
 					},
@@ -111,7 +111,7 @@ func TestRepo(t *testing.T) {
 				},
 				RespEntry: reqresp.RespEntry{
 					Status: http.StatusOK,
-					Body:   []byte(fmt.Sprintf(`{"repositories":["%s"]}`, strings.Join(listRegistry[partialLen:], `","`))),
+					Body:   fmt.Appendf(nil, `{"repositories":["%s"]}`, strings.Join(listRegistry[partialLen:], `","`)),
 					Headers: http.Header{
 						"Content-Type": {"text/plain; charset=utf-8"},
 					},
@@ -125,7 +125,7 @@ func TestRepo(t *testing.T) {
 				},
 				RespEntry: reqresp.RespEntry{
 					Status: http.StatusOK,
-					Body:   []byte(fmt.Sprintf(`{"repositories":["%s"]}`, strings.Join(listRegistry, `","`))),
+					Body:   fmt.Appendf(nil, `{"repositories":["%s"]}`, strings.Join(listRegistry, `","`)),
 					Headers: http.Header{
 						"Content-Type": {"text/plain; charset=utf-8"},
 					},
diff --git a/scheme/reg/tag_test.go b/scheme/reg/tag_test.go
index c52bf16..4c70259 100644
--- a/scheme/reg/tag_test.go
+++ b/scheme/reg/tag_test.go
@@ -32,15 +32,15 @@ func TestTag(t *testing.T) {
 	repoPath2 := "/proj2"
 	pageLen := 2
 	listTagList := []string{"latest", "v1", "v1.1", "v1.1.1"}
-	listTagBody := []byte(fmt.Sprintf("{\"name\":\"%s\",\"tags\":[\"%s\"]}",
+	listTagBody := fmt.Appendf(nil, "{\"name\":\"%s\",\"tags\":[\"%s\"]}",
 		strings.TrimLeft(repoPath, "/"),
-		strings.Join(listTagList, "\",\"")))
-	listTagBody1 := []byte(fmt.Sprintf("{\"name\":\"%s\",\"tags\":[\"%s\"]}",
+		strings.Join(listTagList, "\",\""))
+	listTagBody1 := fmt.Appendf(nil, "{\"name\":\"%s\",\"tags\":[\"%s\"]}",
 		strings.TrimLeft(repoPath, "/"),
-		strings.Join(listTagList[:pageLen], "\",\"")))
-	listTagBody2 := []byte(fmt.Sprintf("{\"name\":\"%s\",\"tags\":[\"%s\"]}",
+		strings.Join(listTagList[:pageLen], "\",\""))
+	listTagBody2 := fmt.Appendf(nil, "{\"name\":\"%s\",\"tags\":[\"%s\"]}",
 		strings.TrimLeft(repoPath, "/"),
-		strings.Join(listTagList[pageLen:], "\",\"")))
+		strings.Join(listTagList[pageLen:], "\",\""))
 	missingRepo := "/missing"
 	delOCITag := "del-oci"
 	delFallbackTag := "del-fallback"
diff --git a/testdata/build-testrepo.sh b/testdata/build-testrepo.sh
index 1e4e054..82f6020 100755
--- a/testdata/build-testrepo.sh
+++ b/testdata/build-testrepo.sh
@@ -105,6 +105,14 @@ echo 64 arms | regctl artifact put \
   --artifact-type application/example.arms -m application/example.arms \
   --subject ocidir://testrepo:v2 --platform linux/arm64
 
+# another standalone artifact with multiple layers
+regctl artifact put \
+  --artifact-type application/example.layers \
+  -m application/example.layer.1 --file-title -f layer1.txt \
+  -m application/example.layer.2 --file-title -f layer2.txt \
+  -m application/example.layer.3 --file-title -f layer3.txt \
+  ocidir://testrepo:a3
+
 # include a external artifacts
 echo "bacon" | regctl artifact put \
   --artifact-type application/example.sbom -m application/example.sbom.breakfast \
diff --git a/testdata/testrepo/blobs/sha256/1121cfccd5913f0a63fec40a6ffd44ea64f9dc135c66634ba001d10bcf4302a2 b/testdata/testrepo/blobs/sha256/1121cfccd5913f0a63fec40a6ffd44ea64f9dc135c66634ba001d10bcf4302a2
new file mode 100644
index 0000000..00750ed
--- /dev/null
+++ b/testdata/testrepo/blobs/sha256/1121cfccd5913f0a63fec40a6ffd44ea64f9dc135c66634ba001d10bcf4302a2
@@ -0,0 +1,1 @@
+3
diff --git a/testdata/testrepo/blobs/sha256/2a25aa502b5155860847e5b3528b8839fee45514b3ed1dd67ed8c3d42efefd53 b/testdata/testrepo/blobs/sha256/2a25aa502b5155860847e5b3528b8839fee45514b3ed1dd67ed8c3d42efefd53
new file mode 100644
index 0000000..b5ea87f
--- /dev/null
+++ b/testdata/testrepo/blobs/sha256/2a25aa502b5155860847e5b3528b8839fee45514b3ed1dd67ed8c3d42efefd53
@@ -0,0 +1,1 @@
+{"schemaVersion":2,"mediaType":"application/vnd.oci.image.manifest.v1+json","artifactType":"application/example.layers","config":{"mediaType":"application/vnd.oci.empty.v1+json","digest":"sha256:44136fa355b3678a1146ad16f7e8649e94fb4fc21fe77e8310c060f61caaff8a","size":2},"layers":[{"mediaType":"application/example.layer.1","digest":"sha256:4355a46b19d348dc2f57c046f8ef63d4538ebb936000f3c9ee954a27460dd865","size":2,"annotations":{"org.opencontainers.image.title":"layer1.txt"}},{"mediaType":"application/example.layer.2","digest":"sha256:53c234e5e8472b6ac51c1ae1cab3fe06fad053beb8ebfd8977b010655bfdd3c3","size":2,"annotations":{"org.opencontainers.image.title":"layer2.txt"}},{"mediaType":"application/example.layer.3","digest":"sha256:1121cfccd5913f0a63fec40a6ffd44ea64f9dc135c66634ba001d10bcf4302a2","size":2,"annotations":{"org.opencontainers.image.title":"layer3.txt"}}]}
\ No newline at end of file
diff --git a/testdata/testrepo/blobs/sha256/4355a46b19d348dc2f57c046f8ef63d4538ebb936000f3c9ee954a27460dd865 b/testdata/testrepo/blobs/sha256/4355a46b19d348dc2f57c046f8ef63d4538ebb936000f3c9ee954a27460dd865
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/testdata/testrepo/blobs/sha256/4355a46b19d348dc2f57c046f8ef63d4538ebb936000f3c9ee954a27460dd865
@@ -0,0 +1,1 @@
+1
diff --git a/testdata/testrepo/blobs/sha256/53c234e5e8472b6ac51c1ae1cab3fe06fad053beb8ebfd8977b010655bfdd3c3 b/testdata/testrepo/blobs/sha256/53c234e5e8472b6ac51c1ae1cab3fe06fad053beb8ebfd8977b010655bfdd3c3
new file mode 100644
index 0000000..0cfbf08
--- /dev/null
+++ b/testdata/testrepo/blobs/sha256/53c234e5e8472b6ac51c1ae1cab3fe06fad053beb8ebfd8977b010655bfdd3c3
@@ -0,0 +1,1 @@
+2
diff --git a/testdata/testrepo/index.json b/testdata/testrepo/index.json
index 6509d29..c2e77f4 100644
--- a/testdata/testrepo/index.json
+++ b/testdata/testrepo/index.json
@@ -1,1 +1,1 @@
-{"schemaVersion":2,"mediaType":"application/vnd.oci.image.index.v1+json","manifests":[{"mediaType":"application/vnd.oci.image.index.v1+json","digest":"sha256:119b4a63feeda91d4874578e7883994fc45772dd912aa49ba380f87507f6ad07","size":964,"annotations":{"org.opencontainers.image.ref.name":"b1"}},{"mediaType":"application/vnd.oci.image.index.v1+json","digest":"sha256:87144634443f628331e98f5c8536a7c20a3aa4e26b1fc8676a0c27e10a545c20","size":964,"annotations":{"org.opencontainers.image.ref.name":"b2"}},{"mediaType":"application/vnd.oci.image.index.v1+json","digest":"sha256:caf692c7d56cfd226e5d04b0908b544e926ddf4f34ec888fe7646d39a3e63c28","size":964,"annotations":{"org.opencontainers.image.ref.name":"b3"}},{"mediaType":"application/vnd.oci.image.index.v1+json","digest":"sha256:190c9253f7a319f0d7f7b8cdd8c63894051be55aeb0c319555e5d075b229cf09","size":1262,"annotations":{"org.opencontainers.image.ref.name":"v1"}},{"mediaType":"application/vnd.oci.image.index.v1+json","digest":"sha256:dfae8f425735a5e3a72e40d6609e03079995511d48157c74d54801ff4430491e","size":934,"annotations":{"org.opencontainers.image.ref.name":"v2"}},{"mediaType":"application/vnd.oci.image.index.v1+json","digest":"sha256:6fe828b32b9b4572f32b16c1c0a4d675660b19ec207d010724309374252c2d6d","size":1153,"annotations":{"org.opencontainers.image.ref.name":"v3"}},{"mediaType":"application/vnd.oci.image.manifest.v1+json","digest":"sha256:43089316cfeec5c2f7897591f5925167afda21932cf71a1a1264684930e7b40a","size":394,"annotations":{"org.opencontainers.image.ref.name":"a-docker"}},{"mediaType":"application/vnd.oci.image.manifest.v1+json","digest":"sha256:0484e93c23cddf24a8400547119558312023295af241d4cd1eaf1b27145c5026","size":583,"annotations":{"org.opencontainers.image.ref.name":"a1"}},{"mediaType":"application/vnd.oci.image.index.v1+json","digest":"sha256:955b8a891713a806107edb6dd09410233a9e7926584b1d6fd8b7b5342296188b","size":482,"annotations":{"org.opencontainers.image.ref.name":"sha256-dfae8f425735a5e3a72e40d6609e03079995511d48157c74d54801ff4430491e"}},{"mediaType":"application/vnd.oci.image.manifest.v1+json","digest":"sha256:741132f956e196c3858dab17e50ea977056f2f1ce1ad2900f11f4c8ff2d4203b","size":588,"annotations":{"org.opencontainers.image.ref.name":"a2"}},{"mediaType":"application/vnd.oci.image.index.v1+json","digest":"sha256:41d51f3b861a4e941c3768285cdca0d1ffdad6cb25f4614d21ebe5bee700fb4b","size":282,"annotations":{"org.opencontainers.image.ref.name":"sha256-ee378b79279b57eb5ac1f3b892c9ad2a9be9d9ccabe1a29a9cbaed8cad182358"}},{"mediaType":"application/vnd.oci.image.index.v1+json","digest":"sha256:98291b121cf58a0e8e0731de7e31b8a5c81e39eb55706cb7710ad6c79d53e996","size":282,"annotations":{"org.opencontainers.image.ref.name":"sha256-36ed7f4ec4545a40ca043f60d76653ef3d2a76f58a051c0f3a256aaab26fb847"}},{"mediaType":"application/vnd.oci.image.index.v1+json","digest":"sha256:7ee385b1056dcaf891f34e1eb98b830bb2ad0d75d4c5a9207a2cb4439e9329ac","size":282,"annotations":{"org.opencontainers.image.ref.name":"sha256-6bed79d0800a0d3a1d0e0e8105a6a5f7f7758ce09e160a8f142574c418302467"}},{"mediaType":"application/vnd.oci.image.index.v1+json","digest":"sha256:df85221e1519ae86965f239606c33730d51602836dc45ad48e15b26383c648e4","size":499,"annotations":{"org.opencontainers.image.ref.name":"ai"}},{"mediaType":"application/vnd.oci.image.manifest.v1+json","digest":"sha256:905693978d033f94767ce99edd9a3b285853343c148a4057cf00d47c67f9fb83","size":417,"annotations":{"org.opencontainers.image.ref.name":"a-example"}},{"mediaType":"application/vnd.oci.image.index.v1+json","digest":"sha256:6fe828b32b9b4572f32b16c1c0a4d675660b19ec207d010724309374252c2d6d","size":1153,"annotations":{"org.opencontainers.image.ref.name":"sha256-190c9253f7a319f0d7f7b8cdd8c63894051be55aeb0c319555e5d075b229cf09.6fe828b32b9b4572.meta"}},{"mediaType":"application/vnd.oci.image.manifest.v1+json","digest":"sha256:8e54c6754f08d22f85c7552bb1951b228b8194d29b14a1639dbe50868da0273e","size":415,"annotations":{"org.opencontainers.image.ref.name":"child"}},{"mediaType":"application/vnd.oci.image.index.v1+json","digest":"sha256:d69399e05204fac05b0184eef72e984538cdc9c5854a6484e8852e4357c543cb","size":445,"annotations":{"org.opencontainers.image.ref.name":"loop"}},{"mediaType":"application/vnd.oci.image.index.v1+json","digest":"sha256:fdc0d9052a90792725e4fd8ba287fca2be6748d8b1e127ac7f82954449399912","size":279,"annotations":{"org.opencontainers.image.ref.name":"sha256-8e54c6754f08d22f85c7552bb1951b228b8194d29b14a1639dbe50868da0273e"}},{"mediaType":"application/vnd.oci.image.manifest.v1+json","digest":"sha256:0514ce64171e869a0b065fa1ce1b533e82808c9228d5b97ea6e3ef2e026d9aed","size":417,"annotations":{"org.opencontainers.image.ref.name":"mirror"}},{"mediaType":"application/vnd.oci.image.manifest.v1+json","digest":"sha256:0514ce64171e869a0b065fa1ce1b533e82808c9228d5b97ea6e3ef2e026d9aed","size":417,"annotations":{"org.opencontainers.image.ref.name":"sha256-0514ce64171e869a0b065fa1ce1b533e82808c9228d5b97ea6e3ef2e026d9aed"}}]}
\ No newline at end of file
+{"schemaVersion":2,"mediaType":"application/vnd.oci.image.index.v1+json","manifests":[{"mediaType":"application/vnd.oci.image.index.v1+json","digest":"sha256:119b4a63feeda91d4874578e7883994fc45772dd912aa49ba380f87507f6ad07","size":964,"annotations":{"org.opencontainers.image.ref.name":"b1"}},{"mediaType":"application/vnd.oci.image.index.v1+json","digest":"sha256:87144634443f628331e98f5c8536a7c20a3aa4e26b1fc8676a0c27e10a545c20","size":964,"annotations":{"org.opencontainers.image.ref.name":"b2"}},{"mediaType":"application/vnd.oci.image.index.v1+json","digest":"sha256:caf692c7d56cfd226e5d04b0908b544e926ddf4f34ec888fe7646d39a3e63c28","size":964,"annotations":{"org.opencontainers.image.ref.name":"b3"}},{"mediaType":"application/vnd.oci.image.index.v1+json","digest":"sha256:190c9253f7a319f0d7f7b8cdd8c63894051be55aeb0c319555e5d075b229cf09","size":1262,"annotations":{"org.opencontainers.image.ref.name":"v1"}},{"mediaType":"application/vnd.oci.image.index.v1+json","digest":"sha256:dfae8f425735a5e3a72e40d6609e03079995511d48157c74d54801ff4430491e","size":934,"annotations":{"org.opencontainers.image.ref.name":"v2"}},{"mediaType":"application/vnd.oci.image.index.v1+json","digest":"sha256:6fe828b32b9b4572f32b16c1c0a4d675660b19ec207d010724309374252c2d6d","size":1153,"annotations":{"org.opencontainers.image.ref.name":"v3"}},{"mediaType":"application/vnd.oci.image.manifest.v1+json","digest":"sha256:43089316cfeec5c2f7897591f5925167afda21932cf71a1a1264684930e7b40a","size":394,"annotations":{"org.opencontainers.image.ref.name":"a-docker"}},{"mediaType":"application/vnd.oci.image.manifest.v1+json","digest":"sha256:0484e93c23cddf24a8400547119558312023295af241d4cd1eaf1b27145c5026","size":583,"annotations":{"org.opencontainers.image.ref.name":"a1"}},{"mediaType":"application/vnd.oci.image.index.v1+json","digest":"sha256:955b8a891713a806107edb6dd09410233a9e7926584b1d6fd8b7b5342296188b","size":482,"annotations":{"org.opencontainers.image.ref.name":"sha256-dfae8f425735a5e3a72e40d6609e03079995511d48157c74d54801ff4430491e"}},{"mediaType":"application/vnd.oci.image.manifest.v1+json","digest":"sha256:741132f956e196c3858dab17e50ea977056f2f1ce1ad2900f11f4c8ff2d4203b","size":588,"annotations":{"org.opencontainers.image.ref.name":"a2"}},{"mediaType":"application/vnd.oci.image.index.v1+json","digest":"sha256:41d51f3b861a4e941c3768285cdca0d1ffdad6cb25f4614d21ebe5bee700fb4b","size":282,"annotations":{"org.opencontainers.image.ref.name":"sha256-ee378b79279b57eb5ac1f3b892c9ad2a9be9d9ccabe1a29a9cbaed8cad182358"}},{"mediaType":"application/vnd.oci.image.index.v1+json","digest":"sha256:98291b121cf58a0e8e0731de7e31b8a5c81e39eb55706cb7710ad6c79d53e996","size":282,"annotations":{"org.opencontainers.image.ref.name":"sha256-36ed7f4ec4545a40ca043f60d76653ef3d2a76f58a051c0f3a256aaab26fb847"}},{"mediaType":"application/vnd.oci.image.index.v1+json","digest":"sha256:7ee385b1056dcaf891f34e1eb98b830bb2ad0d75d4c5a9207a2cb4439e9329ac","size":282,"annotations":{"org.opencontainers.image.ref.name":"sha256-6bed79d0800a0d3a1d0e0e8105a6a5f7f7758ce09e160a8f142574c418302467"}},{"mediaType":"application/vnd.oci.image.index.v1+json","digest":"sha256:df85221e1519ae86965f239606c33730d51602836dc45ad48e15b26383c648e4","size":499,"annotations":{"org.opencontainers.image.ref.name":"ai"}},{"mediaType":"application/vnd.oci.image.manifest.v1+json","digest":"sha256:905693978d033f94767ce99edd9a3b285853343c148a4057cf00d47c67f9fb83","size":417,"annotations":{"org.opencontainers.image.ref.name":"a-example"}},{"mediaType":"application/vnd.oci.image.index.v1+json","digest":"sha256:6fe828b32b9b4572f32b16c1c0a4d675660b19ec207d010724309374252c2d6d","size":1153,"annotations":{"org.opencontainers.image.ref.name":"sha256-190c9253f7a319f0d7f7b8cdd8c63894051be55aeb0c319555e5d075b229cf09.6fe828b32b9b4572.meta"}},{"mediaType":"application/vnd.oci.image.manifest.v1+json","digest":"sha256:8e54c6754f08d22f85c7552bb1951b228b8194d29b14a1639dbe50868da0273e","size":415,"annotations":{"org.opencontainers.image.ref.name":"child"}},{"mediaType":"application/vnd.oci.image.index.v1+json","digest":"sha256:d69399e05204fac05b0184eef72e984538cdc9c5854a6484e8852e4357c543cb","size":445,"annotations":{"org.opencontainers.image.ref.name":"loop"}},{"mediaType":"application/vnd.oci.image.index.v1+json","digest":"sha256:fdc0d9052a90792725e4fd8ba287fca2be6748d8b1e127ac7f82954449399912","size":279,"annotations":{"org.opencontainers.image.ref.name":"sha256-8e54c6754f08d22f85c7552bb1951b228b8194d29b14a1639dbe50868da0273e"}},{"mediaType":"application/vnd.oci.image.manifest.v1+json","digest":"sha256:0514ce64171e869a0b065fa1ce1b533e82808c9228d5b97ea6e3ef2e026d9aed","size":417,"annotations":{"org.opencontainers.image.ref.name":"mirror"}},{"mediaType":"application/vnd.oci.image.manifest.v1+json","digest":"sha256:0514ce64171e869a0b065fa1ce1b533e82808c9228d5b97ea6e3ef2e026d9aed","size":417,"annotations":{"org.opencontainers.image.ref.name":"sha256-0514ce64171e869a0b065fa1ce1b533e82808c9228d5b97ea6e3ef2e026d9aed"}},{"mediaType":"application/vnd.oci.image.manifest.v1+json","digest":"sha256:2a25aa502b5155860847e5b3528b8839fee45514b3ed1dd67ed8c3d42efefd53","size":876,"annotations":{"org.opencontainers.image.ref.name":"a3"}}]}
\ No newline at end of file
diff --git a/types/blob/blob_test.go b/types/blob/blob_test.go
index b6e3839..b83df83 100644
--- a/types/blob/blob_test.go
+++ b/types/blob/blob_test.go
@@ -342,7 +342,7 @@ func TestReader(t *testing.T) {
 		go func() {
 			defer wg.Done()
 			out := make([]byte, exLen)
-			for i := 0; i < chunkCount-1; i++ {
+			for i := range chunkCount - 1 {
 				l, err := b.Read(out[i*int(chunkLen) : (i+1)*int(chunkLen)])
 				if l != int(chunkLen) {
 					t.Errorf("did not read enough bytes: expected %d, received %d", chunkLen, l)
diff --git a/types/ref/ref_test.go b/types/ref/ref_test.go
index 1f59700..837fedc 100644
--- a/types/ref/ref_test.go
+++ b/types/ref/ref_test.go
@@ -902,11 +902,12 @@ func TestIsSet(t *testing.T) {
 	}
 }
 
-func TestSet(t *testing.T) {
+func TestSetAndAdd(t *testing.T) {
 	t.Parallel()
 	rStr := "example.com/repo:v1"
 	rDigStr := "example.com/repo@" + testDigest
 	rTagStr := "example.com/repo:v2"
+	rAddStr := "example.com/repo:v2@" + testDigest
 	r, err := New(rStr)
 	if err != nil {
 		t.Fatalf("unexpected parse failure: %v", err)
@@ -931,6 +932,17 @@ func TestSet(t *testing.T) {
 	if r.Reference != rTagStr {
 		t.Errorf("SetTag reference mismatch, expected %s, received %s", rTagStr, r.Reference)
 	}
+	r = r.AddDigest(testDigest)
+	if r.Tag != "v2" {
+		t.Errorf("AddDigest tag mismatch, expected v2, received %s", r.Tag)
+	}
+	if r.Digest != testDigest {
+		t.Errorf("AddDigest digest mismatch, expected %s, received %s", testDigest, r.Digest)
+	}
+	if r.Reference != rAddStr {
+		t.Errorf("AddDigest reference mismatch, expected %s, received %s", rAddStr, r.Reference)
+	}
+
 }
 
 func TestToReg(t *testing.T) {
diff --git a/types/repo/repolist_test.go b/types/repo/repolist_test.go
index e0c5a34..6ed9609 100644
--- a/types/repo/repolist_test.go
+++ b/types/repo/repolist_test.go
@@ -15,7 +15,7 @@ func TestNew(t *testing.T) {
 	t.Parallel()
 	emptyRaw := []byte("{}")
 	registryList := []string{"library/alpine", "library/debian", "library/golang"}
-	registryRaw := []byte(fmt.Sprintf(`{"repositories":["%s"]}`, strings.Join(registryList, `","`)))
+	registryRaw := fmt.Appendf(nil, `{"repositories":["%s"]}`, strings.Join(registryList, `","`))
 	registryHost := "localhost:5000"
 	registryMT := "application/json; charset=utf-8"
 	registryHeaders := http.Header{
diff --git a/types/tag/taglist_test.go b/types/tag/taglist_test.go
index 9ead76e..8880d4e 100644
--- a/types/tag/taglist_test.go
+++ b/types/tag/taglist_test.go
@@ -24,8 +24,8 @@ func TestNew(t *testing.T) {
 	}
 	registryRef, _ := ref.New("localhost:5000/regclient/test")
 	registryRepoName := "regclient/test"
-	registryRaw := []byte(fmt.Sprintf(`{"name":"%s","tags":["%s"]}`, registryRepoName,
-		strings.Join(registryTags, `","`)))
+	registryRaw := fmt.Appendf(nil, `{"name":"%s","tags":["%s"]}`, registryRepoName,
+		strings.Join(registryTags, `","`))
 	registryMediaType := "application/json"
 	registryHeaders := http.Header{
 		"Content-Type": {registryMediaType},
@@ -104,11 +104,11 @@ func TestNew(t *testing.T) {
 	gcrTags := []string{"v0.3.0", "sha256-96ef6fb02c5a56901dc3c2e0ca34eec9ed926ab8d936ea30ec38f9ec9db017a5.sig"}
 	gcrRef, _ := ref.New("gcr.io/example/test")
 	gcrRepoName := "example/test"
-	gcrRaw := []byte(fmt.Sprintf(`{"child": ["%s"], "manifest":{%s}, "name":"%s", "tags":["%s"]}`,
+	gcrRaw := fmt.Appendf(nil, `{"child": ["%s"], "manifest":{%s}, "name":"%s", "tags":["%s"]}`,
 		strings.Join(gcrChild, `","`),
 		gcrManifestRaw,
 		gcrRepoName,
-		strings.Join(gcrTags, `","`)))
+		strings.Join(gcrTags, `","`))
 	gcrMediaType := "application/json"
 	gcrHeaders := http.Header{
 		"Content-Type": {gcrMediaType},