Repository: distribution/distribution
Pull request #4031 (2023-08-30 16:10:52)

deprecate reference package, migrate to github.com/distribution/reference
- relates to https://github.com/distribution/distribution/issues/3587

This integrates the new module, which was extracted from this repository at commit b9b19409cf458dcb9e1253ff44ba75bd0620faa6;

    # install filter-repo (https://github.com/newren/git-filter-repo/blob/main/INSTALL.md)
    brew install git-filter-repo

    # create a temporary clone of docker
    cd ~/Projects
    git clone https://github.com/distribution/distribution.git reference
    cd reference

    # commit taken from
    git rev-parse --verify HEAD
    b9b19409cf458dcb9e1253ff44ba75bd0620faa6

    # remove all code, except for general files, 'reference/', and rename to /
    git filter-repo \
      --path .github/workflows/codeql-analysis.yml \
      --path .github/workflows/fossa.yml \
      --path .golangci.yml \
      --path distribution-logo.svg \
      --path CODE-OF-CONDUCT.md \
      --path CONTRIBUTING.md \
      --path GOVERNANCE.md \
      --path README.md \
      --path LICENSE \
      --path MAINTAINERS \
      --path-glob 'reference/*.*' \
      --path-rename reference/:

    # initialize go.mod
    go mod init github.com/distribution/reference
    go mod tidy -go=1.20

milosgajdos: Wild! Yeah we need to tag this 🤔 
thaJeztah: > That was fast :)

😂 had to do similar moves a couple of times before, and now got the hang of it

> I guess we should tag up the reference repo as 1.0.0 before merging this.

Yes, we should probably look if the repository still needs some cleaning up before (I'm guessing we'd want to add a minimal README that describes the purpose of the package).

Even though this package has been used in production (so _technically_ v1.0.0 should be the correct version), perhaps we should tag a `v0.x.x` version first, to have some wiggle-room.

There are some deprecated types/functions in the package; I moved those as well, but those should definitely be removed at  some point (I guess; ideally before v1.0.0).

I also want to go the rounds across some repositories to see if things explode if they would already switch to the new module _even if they use a tagged version of distributions_ (i.e. any v2.x version).

I want to make sure that switching potential _types_ doesn't cause builds to fail.

And, finally, given that not much has _changed_ in the package (other than some performance improvements and bug fixes), we may want to consider switching the v2 branch to this package as well; that way v2 already would alias the types, which means there's no risk of Go (being a strong-typed language) from breaking things if a project happens to depend on both v3 and v2 in some form.



Jamstah: I'll take a look at the readme.

I agree that we should remove the deprecated functions before 1.0.0. How about now?

I plan to run round some repos and make some issues to make people aware.

I don't think we need to do the v2 stream. Repos will need to migrate at some point, and I don't think we need to support depending on v3 and v2 art the same time. Many projects will drop the distribution dependency and only need reference, I think.
thaJeztah: > I don't think we need to do the v2 stream. Repos will need to migrate at some point, and I don't think we need to support depending on v3 and v2 art the same time. Many projects will drop the distribution dependency and only need reference, I think.

Yes, I think we're _probably_ in the clear; v3 is already a different module (so a `v2.Foo` type would _already_ be incompatible with a `v3.Foo` - even if they're otherwise identical), so I don't expect much issues there beyond some projects (temporarily) depending on both during the transition.

thaJeztah: > I agree that we should remove the deprecated functions before 1.0.0. How about now?

Oh; forgot to answer this one; We need to check if those that are deprecated were already deprecated in the current (so: v2.8.x) release.

If not, then consumers may not have yet "received the message" (or more strictly; they haven't been deprecated yet, if no release was done with the deprecation in them).

I tend to follow a cycle of

- mark deprecated (in a release), but provide aliases (or at least a pointer to where things went)
- remove in the next release
- where "release" is a "non-patch release"
milosgajdos: So we are due to make a minor `2.8.x` release because of https://github.com/distribution/distribution/pull/4009

I wouldnt be against marking this package as deprecated in `2.8.x` before cutting a release and pointing folks in the new direction.
Jamstah: The only deprecated function in distribution/reference is `SplitHostname`. It is also deprecated in v2.8.2. I think that means we can just remove the code.

Although I agree we should tag a v0.0.1 (or 0.0.0) first, then remove it.
milosgajdos: I agree with @Jamstah, let's remove that one pesky func once tagged.

As for this
>    either adding a //Deprecated: line to the package in the package line, and to the functions that are deprecated
>    or backporting this PR (but larger change of course)

I am in favour or deprecating instead of another "laborious" backport to the branch which is essentially in maintenance mode 🤷‍♂️ 

Now going forward, we need to agree on the first tag 😄 because we now have 2 options OR I'm a bit confused.
* tag `0.0.1` as is i.e. the current commit on`main`
* tag `0.5.0` - @thaJeztah is there a reason for this specific tag?
thaJeztah: > is there a reason for this specific tag?

Nah, just something  I tend to use; "halfway there!" and it doesn't look as _unstable_ as `v0.0.1`.

SemVer itself suggests starting at v0.1.0 (allowing the `z` version to be bumped  for fixes (`vx.y.z`)) as a first tag, but given that this code was already used widely, I think it's not really a `v0.1.0`.
Jamstah: I'm happy with either, that one's just to say "This is the exact code that got copied in", and I still think we should immediately remove the deprecated code and tag 1.0.0.
thaJeztah: Opened https://github.com/distribution/distribution/pull/4032 to fix the "deprecation"; looks like that function was already marked deprecated in the 2.8 branch at least (but the format not correct, so not unlikely that linters and IDEs didn't pick it up)
Jamstah: @milosgajdos I think you're good to pick any version under 1.0.0 and tag the reference repo now.
milosgajdos: Ok pushed `v0.5.0` as agreed https://github.com/distribution/reference/releases/tag/v0.5.0
milosgajdos: Great I realized that `git` cli is silly and doesn't remove comments when signing tags before pushing 🫠 🤌
thaJeztah: Awww, that's cute! Perfect for a `v0.5.0`!

Patch:
diff --git a/blobs.go b/blobs.go
index b7583cc7..eac8bc93 100644
--- a/blobs.go
+++ b/blobs.go
@@ -8,7 +8,7 @@ import (
 	"net/http"
 	"time"
 
-	"github.com/distribution/distribution/v3/reference"
+	"github.com/distribution/reference"
 	"github.com/opencontainers/go-digest"
 	v1 "github.com/opencontainers/image-spec/specs-go/v1"
 )
diff --git a/go.mod b/go.mod
index 57cf8509..05f2a351 100644
--- a/go.mod
+++ b/go.mod
@@ -10,6 +10,7 @@ require (
 	github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.0.0
 	github.com/aws/aws-sdk-go v1.44.325
 	github.com/bshuster-repo/logrus-logstash-hook v1.0.0
+	github.com/distribution/reference v0.5.0
 	github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c
 	github.com/docker/go-metrics v0.0.1
 	github.com/docker/libtrust v0.0.0-20150114040149-fa567046d9b1
diff --git a/go.sum b/go.sum
index 0a8694e2..54d3626f 100644
--- a/go.sum
+++ b/go.sum
@@ -92,6 +92,8 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
 github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
 github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
+github.com/distribution/reference v0.5.0 h1:/FUIFXtfc/x2gpa5/VGfiGLuOIdYa1t65IKK2OFGvA0=
+github.com/distribution/reference v0.5.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
 github.com/dnaeon/go-vcr v1.2.0 h1:zHCHvJYTMh1N7xnV7zf1m1GPBF9Ad0Jk/whtQ1663qI=
 github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c h1:+pKlWGMw7gf6bQ+oDZB4KHQFypsfjYlq/C4rfL7D3g8=
 github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c/go.mod h1:Uw6UezgYA44ePAFQYUehOuCzmy5zmg/+nl2ZfMWGkpA=
diff --git a/notifications/bridge.go b/notifications/bridge.go
index ff5a7980..7404b021 100644
--- a/notifications/bridge.go
+++ b/notifications/bridge.go
@@ -6,8 +6,8 @@ import (
 
 	"github.com/distribution/distribution/v3"
 	"github.com/distribution/distribution/v3/context"
-	"github.com/distribution/distribution/v3/reference"
 	"github.com/distribution/distribution/v3/uuid"
+	"github.com/distribution/reference"
 	events "github.com/docker/go-events"
 	"github.com/opencontainers/go-digest"
 )
diff --git a/notifications/listener.go b/notifications/listener.go
index 57857e18..fad29341 100644
--- a/notifications/listener.go
+++ b/notifications/listener.go
@@ -8,7 +8,7 @@ import (
 	"github.com/distribution/distribution/v3"
 
 	dcontext "github.com/distribution/distribution/v3/context"
-	"github.com/distribution/distribution/v3/reference"
+	"github.com/distribution/reference"
 	"github.com/opencontainers/go-digest"
 )
 
diff --git a/reference/helpers_deprecated.go b/reference/helpers_deprecated.go
new file mode 100644
index 00000000..cbd11925
--- /dev/null
+++ b/reference/helpers_deprecated.go
@@ -0,0 +1,34 @@
+package reference
+
+import "github.com/distribution/reference"
+
+// IsNameOnly returns true if reference only contains a repo name.
+//
+// Deprecated: use [reference.IsNameOnly].
+func IsNameOnly(ref reference.Named) bool {
+	return reference.IsNameOnly(ref)
+}
+
+// FamiliarName returns the familiar name string
+// for the given named, familiarizing if needed.
+//
+// Deprecated: use [reference.FamiliarName].
+func FamiliarName(ref reference.Named) string {
+	return reference.FamiliarName(ref)
+}
+
+// FamiliarString returns the familiar string representation
+// for the given reference, familiarizing if needed.
+//
+// Deprecated: use [reference.FamiliarString].
+func FamiliarString(ref reference.Reference) string {
+	return reference.FamiliarString(ref)
+}
+
+// FamiliarMatch reports whether ref matches the specified pattern.
+// See [path.Match] for supported patterns.
+//
+// Deprecated: use [reference.FamiliarMatch].
+func FamiliarMatch(pattern string, ref reference.Reference) (bool, error) {
+	return reference.FamiliarMatch(pattern, ref)
+}
diff --git a/reference/normalize_deprecated.go b/reference/normalize_deprecated.go
new file mode 100644
index 00000000..ad2d8269
--- /dev/null
+++ b/reference/normalize_deprecated.go
@@ -0,0 +1,39 @@
+package reference
+
+import (
+	"github.com/distribution/reference"
+)
+
+// ParseNormalizedNamed parses a string into a named reference
+// transforming a familiar name from Docker UI to a fully
+// qualified reference. If the value may be an identifier
+// use ParseAnyReference.
+//
+// Deprecated: use [reference.ParseNormalizedNamed].
+func ParseNormalizedNamed(s string) (reference.Named, error) {
+	return reference.ParseNormalizedNamed(s)
+}
+
+// ParseDockerRef normalizes the image reference following the docker convention,
+// which allows for references to contain both a tag and a digest.
+//
+// Deprecated: use [reference.ParseDockerRef].
+func ParseDockerRef(ref string) (reference.Named, error) {
+	return reference.ParseDockerRef(ref)
+}
+
+// TagNameOnly adds the default tag "latest" to a reference if it only has
+// a repo name.
+//
+// Deprecated: use [reference.TagNameOnly].
+func TagNameOnly(ref reference.Named) reference.Named {
+	return reference.TagNameOnly(ref)
+}
+
+// ParseAnyReference parses a reference string as a possible identifier,
+// full digest, or familiar name.
+//
+// Deprecated: use [reference.ParseAnyReference].
+func ParseAnyReference(ref string) (reference.Reference, error) {
+	return reference.ParseAnyReference(ref)
+}
diff --git a/reference/reference_deprecated.go b/reference/reference_deprecated.go
new file mode 100644
index 00000000..5b732498
--- /dev/null
+++ b/reference/reference_deprecated.go
@@ -0,0 +1,172 @@
+// Package reference is deprecated, and has moved to github.com/distribution/reference.
+//
+// Deprecated: use github.com/distribution/reference instead.
+package reference
+
+import (
+	"github.com/distribution/reference"
+	"github.com/opencontainers/go-digest"
+)
+
+const (
+	// NameTotalLengthMax is the maximum total number of characters in a repository name.
+	//
+	// Deprecated: use [reference.NameTotalLengthMax].
+	NameTotalLengthMax = reference.NameTotalLengthMax
+)
+
+var (
+	// ErrReferenceInvalidFormat represents an error while trying to parse a string as a reference.
+	//
+	// Deprecated: use [reference.ErrReferenceInvalidFormat].
+	ErrReferenceInvalidFormat = reference.ErrReferenceInvalidFormat
+
+	// ErrTagInvalidFormat represents an error while trying to parse a string as a tag.
+	//
+	// Deprecated: use [reference.ErrTagInvalidFormat].
+	ErrTagInvalidFormat = reference.ErrTagInvalidFormat
+
+	// ErrDigestInvalidFormat represents an error while trying to parse a string as a tag.
+	//
+	// Deprecated: use [reference.ErrDigestInvalidFormat].
+	ErrDigestInvalidFormat = reference.ErrDigestInvalidFormat
+
+	// ErrNameContainsUppercase is returned for invalid repository names that contain uppercase characters.
+	//
+	// Deprecated: use [reference.ErrNameContainsUppercase].
+	ErrNameContainsUppercase = reference.ErrNameContainsUppercase
+
+	// ErrNameEmpty is returned for empty, invalid repository names.
+	//
+	// Deprecated: use [reference.ErrNameEmpty].
+	ErrNameEmpty = reference.ErrNameEmpty
+
+	// ErrNameTooLong is returned when a repository name is longer than NameTotalLengthMax.
+	//
+	// Deprecated: use [reference.ErrNameTooLong].
+	ErrNameTooLong = reference.ErrNameTooLong
+
+	// ErrNameNotCanonical is returned when a name is not canonical.
+	//
+	// Deprecated: use [reference.ErrNameNotCanonical].
+	ErrNameNotCanonical = reference.ErrNameNotCanonical
+)
+
+// Reference is an opaque object reference identifier that may include
+// modifiers such as a hostname, name, tag, and digest.
+//
+// Deprecated: use [reference.Reference].
+type Reference = reference.Reference
+
+// Field provides a wrapper type for resolving correct reference types when
+// working with encoding.
+//
+// Deprecated: use [reference.Field].
+type Field = reference.Field
+
+// AsField wraps a reference in a Field for encoding.
+//
+// Deprecated: use [reference.AsField].
+func AsField(ref reference.Reference) reference.Field {
+	return reference.AsField(ref)
+}
+
+// Named is an object with a full name
+//
+// Deprecated: use [reference.Named].
+type Named = reference.Named
+
+// Tagged is an object which has a tag
+//
+// Deprecated: use [reference.Tagged].
+type Tagged = reference.Tagged
+
+// NamedTagged is an object including a name and tag.
+//
+// Deprecated: use [reference.NamedTagged].
+type NamedTagged reference.NamedTagged
+
+// Digested is an object which has a digest
+// in which it can be referenced by
+//
+// Deprecated: use [reference.Digested].
+type Digested reference.Digested
+
+// Canonical reference is an object with a fully unique
+// name including a name with domain and digest
+//
+// Deprecated: use [reference.Canonical].
+type Canonical reference.Canonical
+
+// Domain returns the domain part of the [Named] reference.
+//
+// Deprecated: use [reference.Domain].
+func Domain(named reference.Named) string {
+	return reference.Domain(named)
+}
+
+// Path returns the name without the domain part of the [Named] reference.
+//
+// Deprecated: use [reference.Path].
+func Path(named reference.Named) (name string) {
+	return reference.Path(named)
+}
+
+// SplitHostname splits a named reference into a
+// hostname and name string. If no valid hostname is
+// found, the hostname is empty and the full value
+// is returned as name
+//
+// Deprecated: Use [reference.Domain] or [reference.Path].
+func SplitHostname(named reference.Named) (string, string) {
+	return reference.SplitHostname(named)
+}
+
+// Parse parses s and returns a syntactically valid Reference.
+// If an error was encountered it is returned, along with a nil Reference.
+//
+// Deprecated: use [reference.Parse].
+func Parse(s string) (reference.Reference, error) {
+	return reference.Parse(s)
+}
+
+// ParseNamed parses s and returns a syntactically valid reference implementing
+// the Named interface. The reference must have a name and be in the canonical
+// form, otherwise an error is returned.
+// If an error was encountered it is returned, along with a nil Reference.
+//
+// Deprecated: use [reference.ParseNamed].
+func ParseNamed(s string) (reference.Named, error) {
+	return reference.ParseNamed(s)
+}
+
+// WithName returns a named object representing the given string. If the input
+// is invalid ErrReferenceInvalidFormat will be returned.
+//
+// Deprecated: use [reference.WithName].
+func WithName(name string) (reference.Named, error) {
+	return reference.WithName(name)
+}
+
+// WithTag combines the name from "name" and the tag from "tag" to form a
+// reference incorporating both the name and the tag.
+//
+// Deprecated: use [reference.WithTag].
+func WithTag(name reference.Named, tag string) (reference.NamedTagged, error) {
+	return reference.WithTag(name, tag)
+}
+
+// WithDigest combines the name from "name" and the digest from "digest" to form
+// a reference incorporating both the name and the digest.
+//
+// Deprecated: use [reference.WithDigest].
+func WithDigest(name reference.Named, digest digest.Digest) (reference.Canonical, error) {
+	return reference.WithDigest(name, digest)
+}
+
+// TrimNamed removes any tag or digest from the named reference.
+//
+// Deprecated: use [reference.TrimNamed].
+func TrimNamed(ref reference.Named) reference.Named {
+	return reference.TrimNamed(ref)
+}
diff --git a/reference/regexp_deprecated.go b/reference/regexp_deprecated.go
new file mode 100644
index 00000000..4b9c1b58
--- /dev/null
+++ b/reference/regexp_deprecated.go
@@ -0,0 +1,50 @@
+package reference
+
+import (
+	"github.com/distribution/reference"
+)
+
+// DigestRegexp matches well-formed digests, including algorithm (e.g. "sha256:<encoded>").
+//
+// Deprecated: use [reference.DigestRegexp].
+var DigestRegexp = reference.DigestRegexp
+
+// DomainRegexp matches hostname or IP-addresses, optionally including a port
+// number. It defines the structure of potential domain components that may be
+// part of image names. This is purposely a subset of what is allowed by DNS to
+// ensure backwards compatibility with Docker image names. It may be a subset of
+// DNS domain name, an IPv4 address in decimal format, or an IPv6 address between
+// square brackets (excluding zone identifiers as defined by [RFC 6874] or special
+// addresses such as IPv4-Mapped).
+//
+// Deprecated: use [reference.DomainRegexp].
+//
+// [RFC 6874]: https://www.rfc-editor.org/rfc/rfc6874.
+var DomainRegexp = reference.DigestRegexp
+
+// IdentifierRegexp is the format for string identifier used as a
+// content addressable identifier using sha256. These identifiers
+// are like digests without the algorithm, since sha256 is used.
+//
+// Deprecated: use [reference.IdentifierRegexp].
+var IdentifierRegexp = reference.IdentifierRegexp
+
+// NameRegexp is the format for the name component of references, including
+// an optional domain and port, but without tag or digest suffix.
+//
+// Deprecated: use [reference.NameRegexp].
+var NameRegexp = reference.NameRegexp
+
+// ReferenceRegexp is the full supported format of a reference. The regexp
+// is anchored and has capturing groups for name, tag, and digest
+// components.
+//
+// Deprecated: use [reference.ReferenceRegexp].
+var ReferenceRegexp = reference.ReferenceRegexp
+
+// TagRegexp matches valid tag names. From [docker/docker:graph/tags.go].
+//
+// Deprecated: use [reference.TagRegexp].
+//
+// [docker/docker:graph/tags.go]: https://github.com/moby/moby/blob/v1.6.0/graph/tags.go#L26-L28
+var TagRegexp = reference.TagRegexp
diff --git a/reference/sort_deprecated.go b/reference/sort_deprecated.go
new file mode 100644
index 00000000..a73251b6
--- /dev/null
+++ b/reference/sort_deprecated.go
@@ -0,0 +1,10 @@
+package reference
+
+import "github.com/distribution/reference"
+
+// Sort sorts string references preferring higher information references.
+//
+// Deprecated: use [reference.Sort].
+func Sort(references []string) []string {
+	return reference.Sort(references)
+}
diff --git a/registry.go b/registry.go
index 658f2df0..d0deee65 100644
--- a/registry.go
+++ b/registry.go
@@ -3,7 +3,7 @@ package distribution
 import (
 	"context"
 
-	"github.com/distribution/distribution/v3/reference"
+	"github.com/distribution/reference"
 )
 
 // Scope defines the set of items that match a namespace.
diff --git a/registry/api/v2/descriptors.go b/registry/api/v2/descriptors.go
index 9d1cfdb8..d0e9620e 100644
--- a/registry/api/v2/descriptors.go
+++ b/registry/api/v2/descriptors.go
@@ -4,8 +4,8 @@ import (
 	"net/http"
 	"regexp"
 
-	"github.com/distribution/distribution/v3/reference"
 	"github.com/distribution/distribution/v3/registry/api/errcode"
+	"github.com/distribution/reference"
 	"github.com/opencontainers/go-digest"
 )
 
diff --git a/registry/api/v2/urls.go b/registry/api/v2/urls.go
index f4aa9095..1a76565a 100644
--- a/registry/api/v2/urls.go
+++ b/registry/api/v2/urls.go
@@ -6,7 +6,7 @@ import (
 	"net/url"
 	"strings"
 
-	"github.com/distribution/distribution/v3/reference"
+	"github.com/distribution/reference"
 	"github.com/gorilla/mux"
 )
 
diff --git a/registry/client/repository.go b/registry/client/repository.go
index 62f1c15d..58e763a4 100644
--- a/registry/client/repository.go
+++ b/registry/client/repository.go
@@ -15,11 +15,11 @@ import (
 	"time"
 
 	"github.com/distribution/distribution/v3"
-	"github.com/distribution/distribution/v3/reference"
 	v2 "github.com/distribution/distribution/v3/registry/api/v2"
 	"github.com/distribution/distribution/v3/registry/client/transport"
 	"github.com/distribution/distribution/v3/registry/storage/cache"
 	"github.com/distribution/distribution/v3/registry/storage/cache/memory"
+	"github.com/distribution/reference"
 	"github.com/opencontainers/go-digest"
 )
 
diff --git a/registry/handlers/app.go b/registry/handlers/app.go
index 04e3a070..7ea9f435 100644
--- a/registry/handlers/app.go
+++ b/registry/handlers/app.go
@@ -24,7 +24,6 @@ import (
 	"github.com/distribution/distribution/v3/health/checks"
 	prometheus "github.com/distribution/distribution/v3/metrics"
 	"github.com/distribution/distribution/v3/notifications"
-	"github.com/distribution/distribution/v3/reference"
 	"github.com/distribution/distribution/v3/registry/api/errcode"
 	v2 "github.com/distribution/distribution/v3/registry/api/v2"
 	"github.com/distribution/distribution/v3/registry/auth"
@@ -38,6 +37,7 @@ import (
 	"github.com/distribution/distribution/v3/registry/storage/driver/factory"
 	storagemiddleware "github.com/distribution/distribution/v3/registry/storage/driver/middleware"
 	"github.com/distribution/distribution/v3/version"
+	"github.com/distribution/reference"
 	events "github.com/docker/go-events"
 	"github.com/docker/go-metrics"
 	"github.com/gorilla/mux"
diff --git a/registry/handlers/blobupload.go b/registry/handlers/blobupload.go
index b7599a75..ee7626cb 100644
--- a/registry/handlers/blobupload.go
+++ b/registry/handlers/blobupload.go
@@ -8,10 +8,10 @@ import (
 
 	"github.com/distribution/distribution/v3"
 	dcontext "github.com/distribution/distribution/v3/context"
-	"github.com/distribution/distribution/v3/reference"
 	"github.com/distribution/distribution/v3/registry/api/errcode"
 	v2 "github.com/distribution/distribution/v3/registry/api/v2"
 	"github.com/distribution/distribution/v3/registry/storage"
+	"github.com/distribution/reference"
 	"github.com/gorilla/handlers"
 	"github.com/opencontainers/go-digest"
 )
diff --git a/registry/handlers/manifests.go b/registry/handlers/manifests.go
index e5815f3d..c5e2d064 100644
--- a/registry/handlers/manifests.go
+++ b/registry/handlers/manifests.go
@@ -12,11 +12,11 @@ import (
 	"github.com/distribution/distribution/v3/manifest/manifestlist"
 	"github.com/distribution/distribution/v3/manifest/ocischema"
 	"github.com/distribution/distribution/v3/manifest/schema2"
-	"github.com/distribution/distribution/v3/reference"
 	"github.com/distribution/distribution/v3/registry/api/errcode"
 	v2 "github.com/distribution/distribution/v3/registry/api/v2"
 	"github.com/distribution/distribution/v3/registry/auth"
 	"github.com/distribution/distribution/v3/registry/storage/driver"
+	"github.com/distribution/reference"
 	"github.com/gorilla/handlers"
 	"github.com/opencontainers/go-digest"
 	v1 "github.com/opencontainers/image-spec/specs-go/v1"
diff --git a/registry/proxy/proxyblobstore.go b/registry/proxy/proxyblobstore.go
index a03b7237..fd473e45 100644
--- a/registry/proxy/proxyblobstore.go
+++ b/registry/proxy/proxyblobstore.go
@@ -12,8 +12,8 @@ import (
 
 	"github.com/distribution/distribution/v3"
 	dcontext "github.com/distribution/distribution/v3/context"
-	"github.com/distribution/distribution/v3/reference"
 	"github.com/distribution/distribution/v3/registry/proxy/scheduler"
+	"github.com/distribution/reference"
 )
 
 type proxyBlobStore struct {
diff --git a/registry/proxy/proxymanifeststore.go b/registry/proxy/proxymanifeststore.go
index 5673e69b..fa60a07e 100644
--- a/registry/proxy/proxymanifeststore.go
+++ b/registry/proxy/proxymanifeststore.go
@@ -8,8 +8,8 @@ import (
 
 	"github.com/distribution/distribution/v3"
 	dcontext "github.com/distribution/distribution/v3/context"
-	"github.com/distribution/distribution/v3/reference"
 	"github.com/distribution/distribution/v3/registry/proxy/scheduler"
+	"github.com/distribution/reference"
 )
 
 type proxyManifestStore struct {
diff --git a/registry/proxy/proxyregistry.go b/registry/proxy/proxyregistry.go
index b67ba1a2..8420138f 100644
--- a/registry/proxy/proxyregistry.go
+++ b/registry/proxy/proxyregistry.go
@@ -11,7 +11,6 @@ import (
 	"github.com/distribution/distribution/v3"
 	"github.com/distribution/distribution/v3/configuration"
 	dcontext "github.com/distribution/distribution/v3/context"
-	"github.com/distribution/distribution/v3/reference"
 	"github.com/distribution/distribution/v3/registry/client"
 	"github.com/distribution/distribution/v3/registry/client/auth"
 	"github.com/distribution/distribution/v3/registry/client/auth/challenge"
@@ -19,6 +18,7 @@ import (
 	"github.com/distribution/distribution/v3/registry/proxy/scheduler"
 	"github.com/distribution/distribution/v3/registry/storage"
 	"github.com/distribution/distribution/v3/registry/storage/driver"
+	"github.com/distribution/reference"
 )
 
 var repositoryTTL = 24 * 7 * time.Hour
diff --git a/registry/proxy/scheduler/scheduler.go b/registry/proxy/scheduler/scheduler.go
index 8e5f4160..e492cf71 100644
--- a/registry/proxy/scheduler/scheduler.go
+++ b/registry/proxy/scheduler/scheduler.go
@@ -8,8 +8,8 @@ import (
 	"time"
 
 	dcontext "github.com/distribution/distribution/v3/context"
-	"github.com/distribution/distribution/v3/reference"
 	"github.com/distribution/distribution/v3/registry/storage/driver"
+	"github.com/distribution/reference"
 )
 
 // onTTLExpiryFunc is called when a repository's TTL expires
diff --git a/registry/storage/cache/memory/memory.go b/registry/storage/cache/memory/memory.go
index 81994712..8097939f 100644
--- a/registry/storage/cache/memory/memory.go
+++ b/registry/storage/cache/memory/memory.go
@@ -5,8 +5,8 @@ import (
 	"math"
 
 	"github.com/distribution/distribution/v3"
-	"github.com/distribution/distribution/v3/reference"
 	"github.com/distribution/distribution/v3/registry/storage/cache"
+	"github.com/distribution/reference"
 	"github.com/hashicorp/golang-lru/arc/v2"
 	"github.com/opencontainers/go-digest"
 )
diff --git a/registry/storage/cache/redis/redis.go b/registry/storage/cache/redis/redis.go
index 80286d71..e0f4192c 100644
--- a/registry/storage/cache/redis/redis.go
+++ b/registry/storage/cache/redis/redis.go
@@ -6,9 +6,9 @@ import (
 	"strconv"
 
 	"github.com/distribution/distribution/v3"
-	"github.com/distribution/distribution/v3/reference"
 	"github.com/distribution/distribution/v3/registry/storage/cache"
 	"github.com/distribution/distribution/v3/registry/storage/cache/metrics"
+	"github.com/distribution/reference"
 	"github.com/opencontainers/go-digest"
 	"github.com/redis/go-redis/v9"
 )
diff --git a/registry/storage/catalog.go b/registry/storage/catalog.go
index 21040a98..d9d75547 100644
--- a/registry/storage/catalog.go
+++ b/registry/storage/catalog.go
@@ -7,8 +7,8 @@ import (
 	"path"
 	"strings"
 
-	"github.com/distribution/distribution/v3/reference"
 	"github.com/distribution/distribution/v3/registry/storage/driver"
+	"github.com/distribution/reference"
 )
 
 // Returns a list, or partial list, of repositories in the registry.
@@ -27,14 +27,6 @@ func (reg *registry) Repositories(ctx context.Context, repos []string, last stri
 		return 0, err
 	}
 
-	startAfter := ""
-	if last != "" {
-		startAfter, err = pathFor(manifestsPathSpec{name: last})
-		if err != nil {
-			return 0, err
-		}
-	}
-
 	err = reg.blobStore.driver.Walk(ctx, root, func(fileInfo driver.FileInfo) error {
 		err := handleRepository(fileInfo, root, last, func(repoPath string) error {
 			repos[foundRepos] = repoPath
@@ -48,11 +40,11 @@ func (reg *registry) Repositories(ctx context.Context, repos []string, last stri
 		// if we've filled our slice, no need to walk any further
 		if foundRepos == len(repos) {
 			filledBuffer = true
-			return driver.ErrFilledBuffer
+			return driver.ErrSkipDir
 		}
 
 		return nil
-	}, driver.WithStartAfterHint(startAfter))
+	})
 
 	if err != nil {
 		return foundRepos, err
@@ -144,9 +136,8 @@ func compareReplaceInline(s1, s2 string, old, new byte) int {
 
 // handleRepository calls function fn with a repository path if fileInfo
 // has a path of a repository under root and that it is lexographically
-// after last. Otherwise, it will return ErrSkipDir or ErrFilledBuffer.
-// These should be used with Walk to do handling with repositories in a
-// storage.
+// after last. Otherwise, it will return ErrSkipDir. This should be used
+// with Walk to do handling with repositories in a storage.
 func handleRepository(fileInfo driver.FileInfo, root, last string, fn func(repoPath string) error) error {
 	filePath := fileInfo.Path()
 
diff --git a/registry/storage/driver/azure/azure.go b/registry/storage/driver/azure/azure.go
index c2cf9277..f2cb1101 100644
--- a/registry/storage/driver/azure/azure.go
+++ b/registry/storage/driver/azure/azure.go
@@ -385,8 +385,8 @@ func (d *driver) URLFor(ctx context.Context, path string, options map[string]int
 
 // Walk traverses a filesystem defined within driver, starting
 // from the given path, calling f on each file and directory
-func (d *driver) Walk(ctx context.Context, path string, f storagedriver.WalkFn, options ...func(*storagedriver.WalkOptions)) error {
-	return storagedriver.WalkFallback(ctx, d, path, f, options...)
+func (d *driver) Walk(ctx context.Context, path string, f storagedriver.WalkFn) error {
+	return storagedriver.WalkFallback(ctx, d, path, f)
 }
 
 // directDescendants will find direct descendants (blobs or virtual containers)
diff --git a/registry/storage/driver/base/base.go b/registry/storage/driver/base/base.go
index 4ea3394a..00007258 100644
--- a/registry/storage/driver/base/base.go
+++ b/registry/storage/driver/base/base.go
@@ -226,7 +226,7 @@ func (base *Base) URLFor(ctx context.Context, path string, options map[string]in
 }
 
 // Walk wraps Walk of underlying storage driver.
-func (base *Base) Walk(ctx context.Context, path string, f storagedriver.WalkFn, options ...func(*storagedriver.WalkOptions)) error {
+func (base *Base) Walk(ctx context.Context, path string, f storagedriver.WalkFn) error {
 	ctx, done := dcontext.WithTrace(ctx)
 	defer done("%s.Walk(%q)", base.Name(), path)
 
@@ -234,5 +234,5 @@ func (base *Base) Walk(ctx context.Context, path string, f storagedriver.WalkFn,
 		return storagedriver.InvalidPathError{Path: path, DriverName: base.StorageDriver.Name()}
 	}
 
-	return base.setDriverName(base.StorageDriver.Walk(ctx, path, f, options...))
+	return base.setDriverName(base.StorageDriver.Walk(ctx, path, f))
 }
diff --git a/registry/storage/driver/filesystem/driver.go b/registry/storage/driver/filesystem/driver.go
index ade7b283..de7ba096 100644
--- a/registry/storage/driver/filesystem/driver.go
+++ b/registry/storage/driver/filesystem/driver.go
@@ -290,8 +290,8 @@ func (d *driver) URLFor(ctx context.Context, path string, options map[string]int
 
 // Walk traverses a filesystem defined within driver, starting
 // from the given path, calling f on each file and directory
-func (d *driver) Walk(ctx context.Context, path string, f storagedriver.WalkFn, options ...func(*storagedriver.WalkOptions)) error {
-	return storagedriver.WalkFallback(ctx, d, path, f, options...)
+func (d *driver) Walk(ctx context.Context, path string, f storagedriver.WalkFn) error {
+	return storagedriver.WalkFallback(ctx, d, path, f)
 }
 
 // fullPath returns the absolute path of a key within the Driver's storage.
diff --git a/registry/storage/driver/gcs/gcs.go b/registry/storage/driver/gcs/gcs.go
index 81e1dde6..eb1d636b 100644
--- a/registry/storage/driver/gcs/gcs.go
+++ b/registry/storage/driver/gcs/gcs.go
@@ -849,8 +849,8 @@ func (d *driver) URLFor(ctx context.Context, path string, options map[string]int
 
 // Walk traverses a filesystem defined within driver, starting
 // from the given path, calling f on each file
-func (d *driver) Walk(ctx context.Context, path string, f storagedriver.WalkFn, options ...func(*storagedriver.WalkOptions)) error {
-	return storagedriver.WalkFallback(ctx, d, path, f, options...)
+func (d *driver) Walk(ctx context.Context, path string, f storagedriver.WalkFn) error {
+	return storagedriver.WalkFallback(ctx, d, path, f)
 }
 
 func startSession(client *http.Client, bucket string, name string) (uri string, err error) {
diff --git a/registry/storage/driver/inmemory/driver.go b/registry/storage/driver/inmemory/driver.go
index d9c30f38..6370ca63 100644
--- a/registry/storage/driver/inmemory/driver.go
+++ b/registry/storage/driver/inmemory/driver.go
@@ -244,8 +244,8 @@ func (d *driver) URLFor(ctx context.Context, path string, options map[string]int
 
 // Walk traverses a filesystem defined within driver, starting
 // from the given path, calling f on each file and directory
-func (d *driver) Walk(ctx context.Context, path string, f storagedriver.WalkFn, options ...func(*storagedriver.WalkOptions)) error {
-	return storagedriver.WalkFallback(ctx, d, path, f, options...)
+func (d *driver) Walk(ctx context.Context, path string, f storagedriver.WalkFn) error {
+	return storagedriver.WalkFallback(ctx, d, path, f)
 }
 
 type writer struct {
diff --git a/registry/storage/driver/s3-aws/s3.go b/registry/storage/driver/s3-aws/s3.go
index 46ee8a18..90de369b 100644
--- a/registry/storage/driver/s3-aws/s3.go
+++ b/registry/storage/driver/s3-aws/s3.go
@@ -1040,21 +1040,21 @@ func (d *driver) URLFor(ctx context.Context, path string, options map[string]int
 
 // Walk traverses a filesystem defined within driver, starting
 // from the given path, calling f on each file
-func (d *driver) Walk(ctx context.Context, from string, f storagedriver.WalkFn, options ...func(*storagedriver.WalkOptions)) error {
-	walkOptions := &storagedriver.WalkOptions{}
-	for _, o := range options {
-		o(walkOptions)
-	}
-
+func (d *driver) Walk(ctx context.Context, from string, f storagedriver.WalkFn) error {
 	var objectCount int64
-	if err := d.doWalk(ctx, &objectCount, from, walkOptions.StartAfterHint, f); err != nil {
+	if err := d.doWalk(ctx, &objectCount, from, f); err != nil {
 		return err
 	}
 
+	// S3 doesn't have the concept of empty directories, so it'll return path not found if there are no objects
+	if objectCount == 0 {
+		return storagedriver.PathNotFoundError{Path: from}
+	}
+
 	return nil
 }
 
-func (d *driver) doWalk(parentCtx context.Context, objectCount *int64, from string, startAfter string, f storagedriver.WalkFn) error {
+func (d *driver) doWalk(parentCtx context.Context, objectCount *int64, from string, f storagedriver.WalkFn) error {
 	var (
 		retError error
 		// the most recent directory walked for de-duping
@@ -1075,14 +1075,13 @@ func (d *driver) doWalk(parentCtx context.Context, objectCount *int64, from stri
 	}
 
 	listObjectsInput := &s3.ListObjectsV2Input{
-		Bucket:     aws.String(d.Bucket),
-		Prefix:     aws.String(d.s3Path(path)),
-		MaxKeys:    aws.Int64(listMax),
-		StartAfter: aws.String(d.s3Path(startAfter)),
+		Bucket:  aws.String(d.Bucket),
+		Prefix:  aws.String(d.s3Path(path)),
+		MaxKeys: aws.Int64(listMax),
 	}
 
 	ctx, done := dcontext.WithTrace(parentCtx)
-	defer done("s3aws.ListObjectsV2PagesWithContext(%s)", listObjectsInput)
+	defer done("s3aws.ListObjectsV2Pages(%s)", path)
 
 	// When the "delimiter" argument is omitted, the S3 list API will list all objects in the bucket
 	// recursively, omitting directory paths. Objects are listed in sorted, depth-first order so we
@@ -1134,10 +1133,11 @@ func (d *driver) doWalk(parentCtx context.Context, objectCount *int64, from stri
 
 			if err != nil {
 				if err == storagedriver.ErrSkipDir {
-					prevSkipDir = walkInfo.Path()
-					continue
-				}
-				if err == storagedriver.ErrFilledBuffer {
+					if walkInfo.IsDir() {
+						prevSkipDir = walkInfo.Path()
+						continue
+					}
+					// is file, stop gracefully
 					return false
 				}
 				retError = err
@@ -1187,7 +1187,7 @@ func directoryDiff(prev, current string) []string {
 	parent := current
 	for {
 		parent = filepath.Dir(parent)
-		if parent == "/" || parent == prev || strings.HasPrefix(prev+"/", parent+"/") {
+		if parent == "/" || parent == prev || strings.HasPrefix(prev, parent) {
 			break
 		}
 		paths = append(paths, parent)
diff --git a/registry/storage/driver/storagedriver.go b/registry/storage/driver/storagedriver.go
index b9ef6a14..e5d482c0 100644
--- a/registry/storage/driver/storagedriver.go
+++ b/registry/storage/driver/storagedriver.go
@@ -32,19 +32,6 @@ func (version Version) Minor() uint {
 // CurrentVersion is the current storage driver Version.
 const CurrentVersion Version = "0.1"
 
-// WalkOptions provides options to the walk function that may adjust its behaviour
-type WalkOptions struct {
-	// If StartAfterHint is set, the walk may start with the first item lexographically
-	// after the hint, but it is not guaranteed and drivers may start the walk from the path.
-	StartAfterHint string
-}
-
-func WithStartAfterHint(startAfterHint string) func(*WalkOptions) {
-	return func(s *WalkOptions) {
-		s.StartAfterHint = startAfterHint
-	}
-}
-
 // StorageDriver defines methods that a Storage Driver must implement for a
 // filesystem-like key/value object storage. Storage Drivers are automatically
 // registered via an internal registration mechanism, and generally created
@@ -101,9 +88,8 @@ type StorageDriver interface {
 	// from the given path, calling f on each file.
 	// If the returned error from the WalkFn is ErrSkipDir and fileInfo refers
 	// to a directory, the directory will not be entered and Walk
-	// will continue the traversal.
-	// If the returned error from the WalkFn is ErrFilledBuffer, processing stops.
-	Walk(ctx context.Context, path string, f WalkFn, options ...func(*WalkOptions)) error
+	// will continue the traversal.  If fileInfo refers to a normal file, processing stops
+	Walk(ctx context.Context, path string, f WalkFn) error
 }
 
 // FileWriter provides an abstraction for an opened writable file-like object in
diff --git a/registry/storage/driver/walk.go b/registry/storage/driver/walk.go
index 81463e58..c564d896 100644
--- a/registry/storage/driver/walk.go
+++ b/registry/storage/driver/walk.go
@@ -3,9 +3,7 @@ package driver
 import (
 	"context"
 	"errors"
-	"path/filepath"
 	"sort"
-	"strings"
 
 	"github.com/sirupsen/logrus"
 )
@@ -15,88 +13,30 @@ import (
 // as an error by any function.
 var ErrSkipDir = errors.New("skip this directory")
 
-// ErrFilledBuffer is used as a return value from onFileFunc to indicate
-// that the requested number of entries has been reached and the walk can
-// stop.
-var ErrFilledBuffer = errors.New("we have enough entries")
-
 // WalkFn is called once per file by Walk
 type WalkFn func(fileInfo FileInfo) error
 
 // WalkFallback traverses a filesystem defined within driver, starting
 // from the given path, calling f on each file. It uses the List method and Stat to drive itself.
-// If the returned error from the WalkFn is ErrSkipDir the directory will not be entered and Walk
-// will continue the traversal. If the returned error from the WalkFn is ErrFilledBuffer, the walk
-// stops.
-func WalkFallback(ctx context.Context, driver StorageDriver, from string, f WalkFn, options ...func(*WalkOptions)) error {
-	walkOptions := &WalkOptions{}
-	for _, o := range options {
-		o(walkOptions)
-	}
-
-	startAfterHint := walkOptions.StartAfterHint
-	// Ensure that we are checking the hint is contained within from by adding a "/".
-	// Add to both in case the hint and form are the same, which would still count.
-	rel, err := filepath.Rel(from, startAfterHint)
-	if err != nil || strings.HasPrefix(rel, "..") {
-		// The startAfterHint is outside from, so check if we even need to walk anything
-		// Replace any path separators with \x00 so that the sort works in a depth-first way
-		if strings.ReplaceAll(startAfterHint, "/", "\x00") < strings.ReplaceAll(from, "/", "\x00") {
-			_, err := doWalkFallback(ctx, driver, from, "", f)
-			return err
-		}
-	} else {
-		// The startAfterHint is within from.
-		// Walk up the tree until we hit from - we know it is contained.
-		// Ensure startAfterHint is never deeper than a child of the base
-		// directory so that doWalkFallback doesn't have to worry about
-		// depth-first comparisons
-		base := startAfterHint
-		for strings.HasPrefix(base, from) {
-			_, err = doWalkFallback(ctx, driver, base, startAfterHint, f)
-			switch err.(type) {
-			case nil:
-				// No error
-			case PathNotFoundError:
-				// dir doesn't exist, so nothing to walk
-			default:
-				return err
-			}
-			if base == from {
-				break
-			}
-			startAfterHint = base
-			base, _ = filepath.Split(startAfterHint)
-			if len(base) > 1 {
-				base = strings.TrimSuffix(base, "/")
-			}
-		}
-	}
-
-	return nil
+// If the returned error from the WalkFn is ErrSkipDir and fileInfo refers
+// to a directory, the directory will not be entered and Walk
+// will continue the traversal.  If fileInfo refers to a normal file, processing stops
+func WalkFallback(ctx context.Context, driver StorageDriver, from string, f WalkFn) error {
+	_, err := doWalkFallback(ctx, driver, from, f)
+	return err
 }
 
-// doWalkFallback performs a depth first walk using recursion.
-// from is the directory that this iteration of the function should walk.
-// startAfterHint is the child within from to start the walk after. It should only ever be a child of from, or the empty string.
-func doWalkFallback(ctx context.Context, driver StorageDriver, from string, startAfterHint string, f WalkFn) (bool, error) {
+func doWalkFallback(ctx context.Context, driver StorageDriver, from string, f WalkFn) (bool, error) {
 	children, err := driver.List(ctx, from)
 	if err != nil {
 		return false, err
 	}
-	sort.Strings(children)
+	sort.Stable(sort.StringSlice(children))
 	for _, child := range children {
-		// The startAfterHint has been sanitised in WalkFallback and will either be
-		// empty, or be suitable for an <= check for this _from_.
-		if child <= startAfterHint {
-			continue
-		}
-
 		// TODO(stevvooe): Calling driver.Stat for every entry is quite
 		// expensive when running against backends with a slow Stat
-		// implementation, such as GCS. This is very likely a serious
+		// implementation, such as s3. This is very likely a serious
 		// performance bottleneck.
-		// Those backends should have custom walk functions. See S3.
 		fileInfo, err := driver.Stat(ctx, child)
 		if err != nil {
 			switch err.(type) {
@@ -110,13 +50,14 @@ func doWalkFallback(ctx context.Context, driver StorageDriver, from string, star
 		}
 		err = f(fileInfo)
 		if err == nil && fileInfo.IsDir() {
-			if ok, err := doWalkFallback(ctx, driver, child, startAfterHint, f); err != nil || !ok {
+			if ok, err := doWalkFallback(ctx, driver, child, f); err != nil || !ok {
 				return ok, err
 			}
 		} else if err == ErrSkipDir {
-			// don't traverse into this directory
-		} else if err == ErrFilledBuffer {
-			return false, nil // no error but stop iteration
+			// noop for folders, will just skip
+			if !fileInfo.IsDir() {
+				return false, nil // no error but stop iteration
+			}
 		} else if err != nil {
 			return false, err
 		}
diff --git a/registry/storage/garbagecollect.go b/registry/storage/garbagecollect.go
index 661af53f..7895c9b5 100644
--- a/registry/storage/garbagecollect.go
+++ b/registry/storage/garbagecollect.go
@@ -5,8 +5,8 @@ import (
 	"fmt"
 
 	"github.com/distribution/distribution/v3"
-	"github.com/distribution/distribution/v3/reference"
 	"github.com/distribution/distribution/v3/registry/storage/driver"
+	"github.com/distribution/reference"
 	"github.com/opencontainers/go-digest"
 )
 
diff --git a/registry/storage/linkedblobstore.go b/registry/storage/linkedblobstore.go
index 48dc7722..0412bf43 100644
--- a/registry/storage/linkedblobstore.go
+++ b/registry/storage/linkedblobstore.go
@@ -10,9 +10,9 @@ import (
 
 	"github.com/distribution/distribution/v3"
 	dcontext "github.com/distribution/distribution/v3/context"
-	"github.com/distribution/distribution/v3/reference"
 	"github.com/distribution/distribution/v3/registry/storage/driver"
 	"github.com/distribution/distribution/v3/uuid"
+	"github.com/distribution/reference"
 	"github.com/opencontainers/go-digest"
 )
 
diff --git a/registry/storage/paths.go b/registry/storage/paths.go
index 8c424c6b..dab39424 100644
--- a/registry/storage/paths.go
+++ b/registry/storage/paths.go
@@ -24,7 +24,7 @@ const (
 // The path layout in the storage backend is roughly as follows:
 //
 //	<root>/v2
-//	├── blobs
+//	├── blob
 //	│   └── <algorithm>
 //	│       └── <split directory content addressable storage>
 //	└── repositories
@@ -79,8 +79,7 @@ const (
 //
 //	Manifests:
 //
-//	manifestsPathSpec:             <root>/v2/repositories/<name>/_manifests
-//	manifestRevisionsPathSpec:     <root>/v2/repositories/<name>/_manifests/revisions/
+//	manifestRevisionsPathSpec:      <root>/v2/repositories/<name>/_manifests/revisions/
 //	manifestRevisionPathSpec:      <root>/v2/repositories/<name>/_manifests/revisions/<algorithm>/<hex digest>/
 //	manifestRevisionLinkPathSpec:  <root>/v2/repositories/<name>/_manifests/revisions/<algorithm>/<hex digest>/link
 //
@@ -109,7 +108,7 @@ const (
 //	blobsPathSpec:                  <root>/v2/blobs/
 //	blobPathSpec:                   <root>/v2/blobs/<algorithm>/<first two hex bytes of digest>/<hex digest>
 //	blobDataPathSpec:               <root>/v2/blobs/<algorithm>/<first two hex bytes of digest>/<hex digest>/data
-//	blobMediaTypePathSpec:          <root>/v2/blobs/<algorithm>/<first two hex bytes of digest>/<hex digest>/data
+//	blobMediaTypePathSpec:               <root>/v2/blobs/<algorithm>/<first two hex bytes of digest>/<hex digest>/data
 //
 // For more information on the semantic meaning of each path and their
 // contents, please see the path spec documentation.
@@ -131,9 +130,6 @@ func pathFor(spec pathSpec) (string, error) {
 
 	switch v := spec.(type) {
 
-	case manifestsPathSpec:
-		return path.Join(append(repoPrefix, v.name, "_manifests")...), nil
-
 	case manifestRevisionsPathSpec:
 		return path.Join(append(repoPrefix, v.name, "_manifests", "revisions")...), nil
 
@@ -260,13 +256,6 @@ type pathSpec interface {
 	pathSpec()
 }
 
-// manifestPathSpec describes the directory path for a manifest.
-type manifestsPathSpec struct {
-	name string
-}
-
-func (manifestsPathSpec) pathSpec() {}
-
 // manifestRevisionsPathSpec describes the directory path for
 // a manifest revision.
 type manifestRevisionsPathSpec struct {
@@ -357,7 +346,7 @@ type layersPathSpec struct {
 
 func (layersPathSpec) pathSpec() {}
 
-// layerLinkPathSpec specifies a path for a blob link, which is a file with a
+// blobLinkPathSpec specifies a path for a blob link, which is a file with a
 // blob id. The blob link will contain a content addressable blob id reference
 // into the blob store. The format of the contents is as follows:
 //
@@ -414,7 +403,7 @@ type uploadDataPathSpec struct {
 
 func (uploadDataPathSpec) pathSpec() {}
 
-// uploadStartedAtPathSpec defines the path parameters for the file that stores the
+// uploadDataPathSpec defines the path parameters for the file that stores the
 // start time of an uploads. If it is missing, the upload is considered
 // unknown. Admittedly, the presence of this file is an ugly hack to make sure
 // we have a way to cleanup old or stalled uploads that doesn't rely on driver
diff --git a/registry/storage/registry.go b/registry/storage/registry.go
index 6c263e7c..49b604f2 100644
--- a/registry/storage/registry.go
+++ b/registry/storage/registry.go
@@ -5,9 +5,9 @@ import (
 	"regexp"
 
 	"github.com/distribution/distribution/v3"
-	"github.com/distribution/distribution/v3/reference"
 	"github.com/distribution/distribution/v3/registry/storage/cache"
 	storagedriver "github.com/distribution/distribution/v3/registry/storage/driver"
+	"github.com/distribution/reference"
 )
 
 // registry is the top-level implementation of Registry for use in the storage
diff --git a/vendor/github.com/distribution/reference/.gitattributes b/vendor/github.com/distribution/reference/.gitattributes
new file mode 100644
index 00000000..d207b180
--- /dev/null
+++ b/vendor/github.com/distribution/reference/.gitattributes
@@ -0,0 +1,1 @@
+*.go text eol=lf
diff --git a/vendor/github.com/distribution/reference/.gitignore b/vendor/github.com/distribution/reference/.gitignore
new file mode 100644
index 00000000..dc07e6b0
--- /dev/null
+++ b/vendor/github.com/distribution/reference/.gitignore
@@ -0,0 +1,2 @@
+# Cover profiles
+*.out
diff --git a/vendor/github.com/distribution/reference/.golangci.yml b/vendor/github.com/distribution/reference/.golangci.yml
new file mode 100644
index 00000000..793f0bb7
--- /dev/null
+++ b/vendor/github.com/distribution/reference/.golangci.yml
@@ -0,0 +1,18 @@
+linters:
+  enable:
+    - bodyclose
+    - dupword # Checks for duplicate words in the source code
+    - gofmt
+    - goimports
+    - ineffassign
+    - misspell
+    - revive
+    - staticcheck
+    - unconvert
+    - unused
+    - vet
+  disable:
+    - errcheck
+
+run:
+  deadline: 2m
diff --git a/vendor/github.com/distribution/reference/CODE-OF-CONDUCT.md b/vendor/github.com/distribution/reference/CODE-OF-CONDUCT.md
new file mode 100644
index 00000000..48f6704c
--- /dev/null
+++ b/vendor/github.com/distribution/reference/CODE-OF-CONDUCT.md
@@ -0,0 +1,5 @@
+# Code of Conduct
+
+We follow the [CNCF Code of Conduct](https://github.com/cncf/foundation/blob/main/code-of-conduct.md).
+
+Please contact the [CNCF Code of Conduct Committee](mailto:conduct@cncf.io) in order to report violations of the Code of Conduct.
diff --git a/vendor/github.com/distribution/reference/CONTRIBUTING.md b/vendor/github.com/distribution/reference/CONTRIBUTING.md
new file mode 100644
index 00000000..ab219466
--- /dev/null
+++ b/vendor/github.com/distribution/reference/CONTRIBUTING.md
@@ -0,0 +1,114 @@
+# Contributing to the reference library
+
+## Community help
+
+If you need help, please ask in the [#distribution](https://cloud-native.slack.com/archives/C01GVR8SY4R) channel on CNCF community slack.
+[Click here for an invite to the CNCF community slack](https://slack.cncf.io/)
+
+## Reporting security issues
+
+The maintainers take security seriously. If you discover a security
+issue, please bring it to their attention right away!
+
+Please **DO NOT** file a public issue, instead send your report privately to
+[cncf-distribution-security@lists.cncf.io](mailto:cncf-distribution-security@lists.cncf.io).
+
+## Reporting an issue properly
+
+By following these simple rules you will get better and faster feedback on your issue.
+
+ - search the bugtracker for an already reported issue
+
+### If you found an issue that describes your problem:
+
+ - please read other user comments first, and confirm this is the same issue: a given error condition might be indicative of different problems - you may also find a workaround in the comments
+ - please refrain from adding "same thing here" or "+1" comments
+ - you don't need to comment on an issue to get notified of updates: just hit the "subscribe" button
+ - comment if you have some new, technical and relevant information to add to the case
+ - __DO NOT__ comment on closed issues or merged PRs. If you think you have a related problem, open up a new issue and reference the PR or issue.
+
+### If you have not found an existing issue that describes your problem:
+
+ 1. create a new issue, with a succinct title that describes your issue:
+   - bad title: "It doesn't work with my docker"
+   - good title: "Private registry push fail: 400 error with E_INVALID_DIGEST"
+ 2. copy the output of (or similar for other container tools):
+   - `docker version`
+   - `docker info`
+   - `docker exec <registry-container> registry --version`
+ 3. copy the command line you used to launch your Registry
+ 4. restart your docker daemon in debug mode (add `-D` to the daemon launch arguments)
+ 5. reproduce your problem and get your docker daemon logs showing the error
+ 6. if relevant, copy your registry logs that show the error
+ 7. provide any relevant detail about your specific Registry configuration (e.g., storage backend used)
+ 8. indicate if you are using an enterprise proxy, Nginx, or anything else between you and your Registry
+
+## Contributing Code
+
+Contributions should be made via pull requests. Pull requests will be reviewed
+by one or more maintainers or reviewers and merged when acceptable.
+
+You should follow the basic GitHub workflow:
+
+ 1. Use your own [fork](https://help.github.com/en/articles/about-forks)
+ 2. Create your [change](https://github.com/containerd/project/blob/master/CONTRIBUTING.md#successful-changes)
+ 3. Test your code
+ 4. [Commit](https://github.com/containerd/project/blob/master/CONTRIBUTING.md#commit-messages) your work, always [sign your commits](https://github.com/containerd/project/blob/master/CONTRIBUTING.md#commit-messages)
+ 5. Push your change to your fork and create a [Pull Request](https://help.github.com/en/github/collaborating-with-issues-and-pull-requests/creating-a-pull-request-from-a-fork)
+
+Refer to [containerd's contribution guide](https://github.com/containerd/project/blob/master/CONTRIBUTING.md#successful-changes)
+for tips on creating a successful contribution.
+
+## Sign your work
+
+The sign-off is a simple line at the end of the explanation for the patch. Your
+signature certifies that you wrote the patch or otherwise have the right to pass
+it on as an open-source patch. The rules are pretty simple: if you can certify
+the below (from [developercertificate.org](http://developercertificate.org/)):
+
+```
+Developer Certificate of Origin
+Version 1.1
+
+Copyright (C) 2004, 2006 The Linux Foundation and its contributors.
+660 York Street, Suite 102,
+San Francisco, CA 94110 USA
+
+Everyone is permitted to copy and distribute verbatim copies of this
+license document, but changing it is not allowed.
+
+Developer's Certificate of Origin 1.1
+
+By making a contribution to this project, I certify that:
+
+(a) The contribution was created in whole or in part by me and I
+    have the right to submit it under the open source license
+    indicated in the file; or
+
+(b) The contribution is based upon previous work that, to the best
+    of my knowledge, is covered under an appropriate open source
+    license and I have the right under that license to submit that
+    work with modifications, whether created in whole or in part
+    by me, under the same open source license (unless I am
+    permitted to submit under a different license), as indicated
+    in the file; or
+
+(c) The contribution was provided directly to me by some other
+    person who certified (a), (b) or (c) and I have not modified
+    it.
+
+(d) I understand and agree that this project and the contribution
+    are public and that a record of the contribution (including all
+    personal information I submit with it, including my sign-off) is
+    maintained indefinitely and may be redistributed consistent with
+    this project or the open source license(s) involved.
+```
+
+Then you just add a line to every git commit message:
+
+    Signed-off-by: Joe Smith <joe.smith@email.com>
+
+Use your real name (sorry, no pseudonyms or anonymous contributions.)
+
+If you set your `user.name` and `user.email` git configs, you can sign your
+commit automatically with `git commit -s`.
diff --git a/vendor/github.com/distribution/reference/GOVERNANCE.md b/vendor/github.com/distribution/reference/GOVERNANCE.md
new file mode 100644
index 00000000..200045b0
--- /dev/null
+++ b/vendor/github.com/distribution/reference/GOVERNANCE.md
@@ -0,0 +1,144 @@
+# distribution/reference Project Governance
+
+Distribution [Code of Conduct](./CODE-OF-CONDUCT.md) can be found here.
+
+For specific guidance on practical contribution steps please
+see our [CONTRIBUTING.md](./CONTRIBUTING.md) guide.
+
+## Maintainership
+
+There are different types of maintainers, with different responsibilities, but
+all maintainers have 3 things in common:
+
+1) They share responsibility in the project's success.
+2) They have made a long-term, recurring time investment to improve the project.
+3) They spend that time doing whatever needs to be done, not necessarily what
+is the most interesting or fun.
+
+Maintainers are often under-appreciated, because their work is harder to appreciate.
+It's easy to appreciate a really cool and technically advanced feature. It's harder
+to appreciate the absence of bugs, the slow but steady improvement in stability,
+or the reliability of a release process. But those things distinguish a good
+project from a great one.
+
+## Reviewers
+
+A reviewer is a core role within the project.
+They share in reviewing issues and pull requests and their LGTM counts towards the
+required LGTM count to merge a code change into the project.
+
+Reviewers are part of the organization but do not have write access.
+Becoming a reviewer is a core aspect in the journey to becoming a maintainer.
+
+## Adding maintainers
+
+Maintainers are first and foremost contributors that have shown they are
+committed to the long term success of a project. Contributors wanting to become
+maintainers are expected to be deeply involved in contributing code, pull
+request review, and triage of issues in the project for more than three months.
+
+Just contributing does not make you a maintainer, it is about building trust
+with the current maintainers of the project and being a person that they can
+depend on and trust to make decisions in the best interest of the project.
+
+Periodically, the existing maintainers curate a list of contributors that have
+shown regular activity on the project over the prior months. From this list,
+maintainer candidates are selected and proposed in a pull request or a
+maintainers communication channel.
+
+After a candidate has been announced to the maintainers, the existing
+maintainers are given five business days to discuss the candidate, raise
+objections and cast their vote. Votes may take place on the communication
+channel or via pull request comment. Candidates must be approved by at least 66%
+of the current maintainers by adding their vote on the mailing list. The
+reviewer role has the same process but only requires 33% of current maintainers.
+Only maintainers of the repository that the candidate is proposed for are
+allowed to vote.
+
+If a candidate is approved, a maintainer will contact the candidate to invite
+the candidate to open a pull request that adds the contributor to the
+MAINTAINERS file. The voting process may take place inside a pull request if a
+maintainer has already discussed the candidacy with the candidate and a
+maintainer is willing to be a sponsor by opening the pull request. The candidate
+becomes a maintainer once the pull request is merged.
+
+## Stepping down policy
+
+Life priorities, interests, and passions can change. If you're a maintainer but
+feel you must remove yourself from the list, inform other maintainers that you
+intend to step down, and if possible, help find someone to pick up your work.
+At the very least, ensure your work can be continued where you left off.
+
+After you've informed other maintainers, create a pull request to remove
+yourself from the MAINTAINERS file.
+
+## Removal of inactive maintainers
+
+Similar to the procedure for adding new maintainers, existing maintainers can
+be removed from the list if they do not show significant activity on the
+project. Periodically, the maintainers review the list of maintainers and their
+activity over the last three months.
+
+If a maintainer has shown insufficient activity over this period, a neutral
+person will contact the maintainer to ask if they want to continue being
+a maintainer. If the maintainer decides to step down as a maintainer, they
+open a pull request to be removed from the MAINTAINERS file.
+
+If the maintainer wants to remain a maintainer, but is unable to perform the
+required duties they can be removed with a vote of at least 66% of the current
+maintainers. In this case, maintainers should first propose the change to
+maintainers via the maintainers communication channel, then open a pull request
+for voting. The voting period is five business days. The voting pull request
+should not come as a surpise to any maintainer and any discussion related to
+performance must not be discussed on the pull request.
+
+## How are decisions made?
+
+Docker distribution is an open-source project with an open design philosophy.
+This means that the repository is the source of truth for EVERY aspect of the
+project, including its philosophy, design, road map, and APIs. *If it's part of
+the project, it's in the repo. If it's in the repo, it's part of the project.*
+
+As a result, all decisions can be expressed as changes to the repository. An
+implementation change is a change to the source code. An API change is a change
+to the API specification. A philosophy change is a change to the philosophy
+manifesto, and so on.
+
+All decisions affecting distribution, big and small, follow the same 3 steps:
+
+* Step 1: Open a pull request. Anyone can do this.
+
+* Step 2: Discuss the pull request. Anyone can do this.
+
+* Step 3: Merge or refuse the pull request. Who does this depends on the nature
+of the pull request and which areas of the project it affects.
+
+## Helping contributors with the DCO
+
+The [DCO or `Sign your work`](./CONTRIBUTING.md#sign-your-work)
+requirement is not intended as a roadblock or speed bump.
+
+Some contributors are not as familiar with `git`, or have used a web
+based editor, and thus asking them to `git commit --amend -s` is not the best
+way forward.
+
+In this case, maintainers can update the commits based on clause (c) of the DCO.
+The most trivial way for a contributor to allow the maintainer to do this, is to
+add a DCO signature in a pull requests's comment, or a maintainer can simply
+note that the change is sufficiently trivial that it does not substantially
+change the existing contribution - i.e., a spelling change.
+
+When you add someone's DCO, please also add your own to keep a log.
+
+## I'm a maintainer. Should I make pull requests too?
+
+Yes. Nobody should ever push to master directly. All changes should be
+made through a pull request.
+
+## Conflict Resolution
+
+If you have a technical dispute that you feel has reached an impasse with a
+subset of the community, any contributor may open an issue, specifically
+calling for a resolution vote of the current core maintainers to resolve the
+dispute. The same voting quorums required (2/3) for adding and removing
+maintainers will apply to conflict resolution.
diff --git a/vendor/github.com/distribution/reference/LICENSE b/vendor/github.com/distribution/reference/LICENSE
new file mode 100644
index 00000000..e06d2081
--- /dev/null
+++ b/vendor/github.com/distribution/reference/LICENSE
@@ -0,0 +1,202 @@
+Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
+   APPENDIX: How to apply the Apache License to your work.
+
+      To apply the Apache License to your work, attach the following
+      boilerplate notice, with the fields enclosed by brackets "{}"
+      replaced with your own identifying information. (Don't include
+      the brackets!)  The text should be enclosed in the appropriate
+      comment syntax for the file format. We also recommend that a
+      file or class name and description of purpose be included on the
+      same "printed page" as the copyright notice for easier
+      identification within third-party archives.
+
+   Copyright {yyyy} {name of copyright owner}
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+
diff --git a/vendor/github.com/distribution/reference/MAINTAINERS b/vendor/github.com/distribution/reference/MAINTAINERS
new file mode 100644
index 00000000..9e0a60c8
--- /dev/null
+++ b/vendor/github.com/distribution/reference/MAINTAINERS
@@ -0,0 +1,26 @@
+# Distribution project maintainers & reviewers
+#
+# See GOVERNANCE.md for maintainer versus reviewer roles
+#
+# MAINTAINERS (cncf-distribution-maintainers@lists.cncf.io)
+# GitHub ID, Name, Email address
+"chrispat","Chris Patterson","chrispat@github.com"
+"clarkbw","Bryan Clark","clarkbw@github.com"
+"corhere","Cory Snider","csnider@mirantis.com"
+"deleteriousEffect","Hayley Swimelar","hswimelar@gitlab.com"
+"heww","He Weiwei","hweiwei@vmware.com"
+"joaodrp","João Pereira","jpereira@gitlab.com"
+"justincormack","Justin Cormack","justin.cormack@docker.com"
+"squizzi","Kyle Squizzato","ksquizzato@mirantis.com"
+"milosgajdos","Milos Gajdos","milosthegajdos@gmail.com"
+"sargun","Sargun Dhillon","sargun@sargun.me"
+"wy65701436","Wang Yan","wangyan@vmware.com"
+"stevelasker","Steve Lasker","steve.lasker@microsoft.com"
+#
+# REVIEWERS
+# GitHub ID, Name, Email address
+"dmcgowan","Derek McGowan","derek@mcgstyle.net"
+"stevvooe","Stephen Day","stevvooe@gmail.com"
+"thajeztah","Sebastiaan van Stijn","github@gone.nl"
+"DavidSpek", "David van der Spek", "vanderspek.david@gmail.com"
+"Jamstah", "James Hewitt", "james.hewitt@gmail.com"
diff --git a/vendor/github.com/distribution/reference/Makefile b/vendor/github.com/distribution/reference/Makefile
new file mode 100644
index 00000000..c78576b7
--- /dev/null
+++ b/vendor/github.com/distribution/reference/Makefile
@@ -0,0 +1,25 @@
+# Project packages.
+PACKAGES=$(shell go list ./...)
+
+# Flags passed to `go test`
+BUILDFLAGS ?= 
+TESTFLAGS ?= 
+
+.PHONY: all build test coverage
+.DEFAULT: all
+
+all: build
+
+build: ## no binaries to build, so just check compilation suceeds
+	go build ${BUILDFLAGS} ./...
+
+test: ## run tests
+	go test ${TESTFLAGS} ./...
+
+coverage: ## generate coverprofiles from the unit tests
+	rm -f coverage.txt
+	go test ${TESTFLAGS} -cover -coverprofile=cover.out ./...
+
+.PHONY: help
+help:
+	@awk 'BEGIN {FS = ":.*##"; printf "\nUsage:\n  make \033[36m\033[0m\n"} /^[a-zA-Z_\/%-]+:.*?##/ { printf "  \033[36m%-27s\033[0m %s\n", $$1, $$2 } /^##@/ { printf "\n\033[1m%s\033[0m\n", substr($$0, 5) } ' $(MAKEFILE_LIST)
diff --git a/vendor/github.com/distribution/reference/README.md b/vendor/github.com/distribution/reference/README.md
new file mode 100644
index 00000000..e2531e49
--- /dev/null
+++ b/vendor/github.com/distribution/reference/README.md
@@ -0,0 +1,30 @@
+# Distribution reference
+
+Go library to handle references to container images.
+
+<img src="/distribution-logo.svg" width="200px" />
+
+[![Build Status](https://github.com/distribution/reference/actions/workflows/test.yml/badge.svg?branch=main&event=push)](https://github.com/distribution/reference/actions?query=workflow%3ACI)
+[![GoDoc](https://img.shields.io/badge/go.dev-reference-007d9c?logo=go&logoColor=white&style=flat-square)](https://pkg.go.dev/github.com/distribution/reference)
+[![License: Apache-2.0](https://img.shields.io/badge/License-Apache--2.0-blue.svg)](LICENSE)
+[![codecov](https://codecov.io/gh/distribution/reference/branch/main/graph/badge.svg)](https://codecov.io/gh/distribution/reference)
+[![FOSSA Status](https://app.fossa.com/api/projects/custom%2B162%2Fgithub.com%2Fdistribution%2Freference.svg?type=shield)](https://app.fossa.com/projects/custom%2B162%2Fgithub.com%2Fdistribution%2Freference?ref=badge_shield)
+
+This repository contains a library for handling refrences to container images held in container registries. Please see [godoc](https://pkg.go.dev/github.com/distribution/reference) for details.
+
+## Contribution
+
+Please see [CONTRIBUTING.md](CONTRIBUTING.md) for details on how to contribute
+issues, fixes, and patches to this project.
+
+## Communication
+
+For async communication and long running discussions please use issues and pull requests on the github repo.
+This will be the best place to discuss design and implementation.
+
+For sync communication we have a #distribution channel in the [CNCF Slack](https://slack.cncf.io/)
+that everyone is welcome to join and chat about development.
+
+## Licenses
+
+The distribution codebase is released under the [Apache 2.0 license](LICENSE).
diff --git a/vendor/github.com/distribution/reference/SECURITY.md b/vendor/github.com/distribution/reference/SECURITY.md
new file mode 100644
index 00000000..aaf983c0
--- /dev/null
+++ b/vendor/github.com/distribution/reference/SECURITY.md
@@ -0,0 +1,7 @@
+# Security Policy
+
+## Reporting a Vulnerability
+
+The maintainers take security seriously. If you discover a security issue, please bring it to their attention right away!
+
+Please DO NOT file a public issue, instead send your report privately to cncf-distribution-security@lists.cncf.io.
diff --git a/vendor/github.com/distribution/reference/distribution-logo.svg b/vendor/github.com/distribution/reference/distribution-logo.svg
new file mode 100644
index 00000000..cc9f4073
--- /dev/null
+++ b/vendor/github.com/distribution/reference/distribution-logo.svg
@@ -0,0 +1,1 @@
+<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 564.27793 654.82002"><defs><style>.cls-1{fill:#416ba9;}.cls-2{fill:#74c3d5;}</style></defs><path class="cls-1" d="M17.48582,567.55941c0-3.66375,2.2168-4.91716,4.91609-4.91716H36.66877c18.70153,0,28.24476,9.06127,28.24476,33.64387,0,22.26771-7.51889,35.57065-27.95526,35.57065H22.40191c-2.69929,0-4.91609-1.25341-4.91609-4.91609Zm20.33987,49.741c7.61539,0,10.60365-6.94043,10.60365-20.72586,0-15.13429-3.85568-19.376-10.70015-19.376H33.97v40.10182Z"/><path class="cls-1" d="M72.43613,567.55941c0-3.66375,2.21681-4.91716,4.9161-4.91716h6.36251c2.69876,0,4.91609,1.25341,4.91609,4.91716v59.38127c0,3.66268-2.21733,4.91609-4.91609,4.91609H77.35223c-2.69929,0-4.9161-1.25341-4.9161-4.91609Z"/><path class="cls-1" d="M99.718,613.05875l5.78405-1.06042c4.33764-.77092,4.53063,1.15692,5.78352,3.85674a6.70953,6.70953,0,0,0,6.5555,3.95218c3.56672,0,6.26548-1.83134,6.26548-5.59158,0-12.82046-27.6663-11.08561-27.6663-34.31724,0-10.79718,7.80839-18.1236,20.43637-18.1236,11.08614,0,16.96615,4.8196,20.05091,12.918.96445,2.31277,1.44641,4.7231-2.98826,5.59052l-5.20559,1.06042c-3.95218.77092-4.33764-1.06042-6.073-3.66269a6.39679,6.39679,0,0,0-5.49456-3.08475c-3.18125,0-5.20559,1.83134-5.20559,4.7231,0,12.24252,27.56981,10.60418,27.56981,33.73931,0,12.532-8.77231,19.66545-22.36422,19.66545-11.47159,0-17.93007-4.43467-20.82236-14.07387C95.57286,616.24,95.1874,613.92617,99.718,613.05875Z"/><path class="cls-1" d="M154.86187,576.62068h-8.09735c-3.66321,0-4.91662-2.21734-4.91662-4.91716v-4.14411c0-2.69983,1.25341-4.91716,4.91662-4.91716H179.058c3.66321,0,4.91609,2.21733,4.91609,4.91716v4.14411c0,2.69982-1.25288,4.91716-4.91609,4.91716h-8.09788v50.32c0,3.66268-2.21681,4.91609-4.9161,4.91609h-6.266c-2.69876,0-4.9161-1.25341-4.9161-4.91609Z"/><path class="cls-1" d="M189.76022,567.55941c0-3.66375,2.21681-4.91716,4.9161-4.91716h16.2912c15.13481,0,23.03969,5.68808,23.03969,20.05144,0,8.38684-3.27775,13.68787-8.29034,16.77369l9.35076,27.76173c1.34938,4.04974-.96445,4.62766-3.37425,4.62766h-8.67581c-2.89176,0-4.53063-1.92783-5.30208-4.7231L211.546,605.92532h-5.68755v21.01536c0,3.66268-2.2168,4.91609-4.91609,4.91609h-6.266c-2.69929,0-4.9161-1.25341-4.9161-4.91609Zm20.14688,25.35246c4.14517,0,7.61592-1.6394,7.61592-8.57984,0-7.61486-3.37425-8.19384-7.22993-8.19384h-4.43467v16.77368Z"/><path class="cls-1" d="M241.91422,567.55941c0-3.66375,2.2168-4.91716,4.91609-4.91716h6.36251c2.69876,0,4.9161,1.25341,4.9161,4.91716v59.38127c0,3.66268-2.21734,4.91609-4.9161,4.91609h-6.36251c-2.69929,0-4.91609-1.25341-4.91609-4.91609Z"/><path class="cls-1" d="M267.75024,567.55941c0-3.66375,2.2168-4.91716,4.91609-4.91716h14.94182c16.77316,0,24.09958,5.49508,24.09958,18.31659,0,6.94044-3.08475,11.953-8.96477,14.5553v.193c6.74744,2.31384,10.41065,7.13343,10.41065,15.80977,0,16.58069-11.3751,20.33987-25.449,20.33987H272.66633c-2.69929,0-4.91609-1.25341-4.91609-4.91609Zm20.72533,22.5572c4.33817,0,6.84447-2.12083,6.84447-7.32642,0-4.91716-2.79579-6.36251-6.84447-6.36251h-4.9161v13.68893Zm.386,27.95473c5.88,0,7.90436-2.40926,7.90436-7.80785,0-5.88-3.18126-7.80786-7.80839-7.80786h-5.39806v15.61571Z"/><path class="cls-1" d="M320.09776,567.55941c0-3.66375,2.21733-4.91716,4.91662-4.91716h6.362c2.69929,0,4.91663,1.25341,4.91663,4.91716v43.282c0,5.78457,2.3133,8.38684,6.748,8.38684,4.43413,0,7.13343-2.60227,7.13343-8.38684v-43.282c0-3.66375,2.21733-4.91716,4.91609-4.91716H361.26c2.6993,0,4.91663,1.25341,4.91663,4.91716v41.06573c0,15.80871-7.13343,24.00256-23.42516,24.00256-16.0982,0-22.6537-8.19385-22.6537-24.00256Z"/><path class="cls-1" d="M384.59236,576.62068H376.495c-3.66322,0-4.91663-2.21734-4.91663-4.91716v-4.14411c0-2.69983,1.25341-4.91716,4.91663-4.91716h32.29343c3.66321,0,4.91609,2.21733,4.91609,4.91716v4.14411c0,2.69982-1.25288,4.91716-4.91609,4.91716H400.6911v50.32c0,3.66268-2.21734,4.91609-4.91663,4.91609h-6.266c-2.69929,0-4.9161-1.25341-4.9161-4.91609Z"/><path class="cls-1" d="M419.49071,567.55941c0-3.66375,2.21681-4.91716,4.9161-4.91716h6.36251c2.69876,0,4.91609,1.25341,4.91609,4.91716v59.38127c0,3.66268-2.21733,4.91609-4.91609,4.91609h-6.36251c-2.69929,0-4.9161-1.25341-4.9161-4.91609Z"/><path class="cls-1" d="M443.20536,597.34654c0-21.30485,5.59106-35.57171,24.48505-35.57171s24.48505,14.26686,24.48505,35.57171c0,22.07472-6.84394,35.37766-24.485,35.37766S443.20536,619.42126,443.20536,597.34654Zm32.48643,0c0-16.29226-2.12083-22.26877-8.00138-22.26877-5.88,0-8.00085,5.97651-8.00085,22.26877,0,17.06212,2.4098,22.17121,8.00085,22.17121C473.378,619.51775,475.69179,614.40866,475.69179,597.34654Z"/><path class="cls-1" d="M499.69859,567.55941c0-3.66375,2.2168-4.91716,4.9161-4.91716h6.941a7.56728,7.56728,0,0,1,6.84394,4.43467l13.207,29.88363h.19247V567.55941c0-3.66375,2.21733-4.91716,4.91662-4.91716h4.62714c2.69876,0,4.91609,1.25341,4.91609,4.91716v59.38127c0,3.66268-2.21733,4.91609-4.91609,4.91609h-5.68756c-2.98878,0-4.72363-1.83134-5.977-4.43466L514.15792,593.5863h-.19247v33.35438c0,3.66268-2.21733,4.91609-4.91663,4.91609h-4.43413c-2.6993,0-4.9161-1.25341-4.9161-4.91609Z"/><g id="F1L4Xu"><path class="cls-2" d="M461.28162,365.26377c14.626-14.99573,31.51073-27.88581,43.901-45.3001,23.6055-33.17709,21.5881-53.98611-9.08528-79.89549-5.54727-4.68571-8.18237-7.96252-5.187-15.251,2.57641-6.26934,3.76528-13.21711,4.83156-19.981,5.90314-37.44659-11.00082-60.70078-48.64828-63.33267-13.137-.91842-16.45015-5.43062-19.21754-16.935-9.30387-38.67663-34.59038-53.67167-74.13662-44.585-10.18443,2.3401-17.05741,2.2529-24.8593-6.77926-30.175-34.93317-59.58084-35.22646-91.95222-.348-9.64337,10.39025-15.6225,11.83918-28.62783,5.4055-32.90341-16.27713-59.8387-3.10046-69.78437,35.47712-4.6848,18.17164-11.36854,26.3489-30.24471,30.1582-31.33028,6.32259-44.78933,33.5759-35.57532,69.531,3.76252,14.68215,4.6826,23.43655-10.28992,33.26423-22.74913,14.93216-25.22052,35.33713-11.0953,59.39875a163.06863,163.06863,0,0,0,18.46085,24.68707c6.40258,7.28037,13.94521,13.55817,19.80434,21.482C65.25053,338.04809,43.116,321.28746,27.8598,297.29993c-17.05057-26.80876-13.40609-48.15088,13.33187-65.26212,13.64688-8.73344,14.87507-15.92347,10.65871-30.93464-12.7269-45.31048,5.12054-71.29348,52.09339-78.34017,9.12234-1.36851,13.24368-4.37134,14.93358-13.09156a120.37358,120.37358,0,0,1,6.91928-23.66458C140.26672,51.59355,166.89048,40.64934,200.93587,56.76c12.995,6.14933,20.16447,5.77862,30.68022-5.2998C268.21035,12.90788,301.23887,13.957,336.089,53.95609c7.03437,8.07364,13.32723,7.58749,21.51782,5.54845,49.26567-12.26462,82.34232,9.66738,86.80352,52.02378.85563,8.12375,4.89151,12.49189,13.59822,11.06745a9.08058,9.08058,0,0,1,2.06083.01162c48.42775,3.32745,67.90447,29.37216,55.65225,76.28488-3.68675,14.11627-5.266,23.35647,9.83168,33.3918,24.025,15.96919,26.43605,40.908,10.89058,67.22194C523.519,321.384,492.3391,349.49056,461.28162,365.26377Z"/><path class="cls-2" d="M222.21166,412.15994c-43.3335,8.71069-88.628,7.98395-130.77907,28.38523,94.59324,34.57588,265.86321,39.32494,381.28659.87883-20.80256-13.03877-43.25235-15.46757-64.96771-19.52875-21.46582-4.01455-43.21049-6.53754-65.22855-9.773,2.32155-9.0906,5.71941-13.26155,14.6818-11.53506,34.9755,6.73747,70.6065,10.43856,104.30053,22.96341,8.18218,3.04152,18.89124,4.40911,19.86455,15.15614,1.09622,12.10448-6.10692,24.09739-16.79915,25.67409-30.25013,4.46064-56.38559,26.36212-84.9133,26.506-28.47048.14371-52.82989-.52977-79.492,14.17476-20.18156,11.13047-46.332,2.83194-64.16335-15.45408-6.83775-7.01216-11.13794-6.37864-19.02114-2.85829-23.91278,10.67842-47.24546,8.5124-67.31614-8.79886-7.10279-6.12627-13.27536-7.629-22.06165-6.68907a44.08867,44.08867,0,0,1-27.74743-5.86122C89.8179,459.38315,82.0507,451.437,82.898,439.18832c.77048-11.13889,11.49627-12.35089,19.55689-15.40536,33.62848-12.74306,69.18754-16.82889,104.26785-23.14551C215.75685,399.01074,219.15407,403.38339,222.21166,412.15994Z"/></g><rect class="cls-1" x="157.9184" y="278.28554" width="63.75238" height="63.75238"/><rect class="cls-1" x="241.008" y="278.28554" width="63.75238" height="63.75238"/><rect class="cls-1" x="157.9184" y="361.37514" width="63.75238" height="63.75238"/><rect class="cls-1" x="367.09657" y="153.06724" width="63.75176" height="63.75177" transform="translate(-13.91822 336.28471) rotate(-45)"/><polygon class="cls-1" points="239.799 221.785 239.799 222.389 296.3 250.79 325.004 193.987 268.201 165.284 239.799 221.785"/><rect class="cls-1" x="341.49858" y="264.8759" width="63.75384" height="63.75383" transform="translate(-59.8512 496.2174) rotate(-63.19922)"/><rect class="cls-1" x="157.9184" y="195.19594" width="63.75238" height="63.75238"/><rect class="cls-1" x="241.008" y="361.37514" width="63.75238" height="63.75238"/><rect class="cls-1" x="324.39973" y="361.37514" width="63.75238" height="63.75238"/></svg>
\ No newline at end of file
diff --git a/reference/helpers.go b/vendor/github.com/distribution/reference/helpers.go
similarity index 100%
rename from reference/helpers.go
rename to vendor/github.com/distribution/reference/helpers.go
diff --git a/reference/normalize.go b/vendor/github.com/distribution/reference/normalize.go
similarity index 99%
rename from reference/normalize.go
rename to vendor/github.com/distribution/reference/normalize.go
index f2ce13ad..a30229d0 100644
--- a/reference/normalize.go
+++ b/vendor/github.com/distribution/reference/normalize.go
@@ -140,7 +140,7 @@ func splitDockerDomain(name string) (domain, remainder string) {
 }
 
 // familiarizeName returns a shortened version of the name familiar
-// to to the Docker UI. Familiar names have the default domain
+// to the Docker UI. Familiar names have the default domain
 // "docker.io" and "library/" repository prefix removed.
 // For example, "docker.io/library/redis" will have the familiar
 // name "redis" and "docker.io/dmcgowan/myapp" will be "dmcgowan/myapp".
diff --git a/reference/reference.go b/vendor/github.com/distribution/reference/reference.go
similarity index 100%
rename from reference/reference.go
rename to vendor/github.com/distribution/reference/reference.go
diff --git a/reference/regexp.go b/vendor/github.com/distribution/reference/regexp.go
similarity index 100%
rename from reference/regexp.go
rename to vendor/github.com/distribution/reference/regexp.go
diff --git a/reference/sort.go b/vendor/github.com/distribution/reference/sort.go
similarity index 100%
rename from reference/sort.go
rename to vendor/github.com/distribution/reference/sort.go
diff --git a/vendor/modules.txt b/vendor/modules.txt
index db082910..ace76b88 100644
--- a/vendor/modules.txt
+++ b/vendor/modules.txt
@@ -160,6 +160,9 @@ github.com/cyphar/filepath-securejoin
 # github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f
 ## explicit
 github.com/dgryski/go-rendezvous
+# github.com/distribution/reference v0.5.0
+## explicit; go 1.20
+github.com/distribution/reference
 # github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c
 ## explicit
 github.com/docker/go-events


Test patch:
diff --git a/notifications/bridge_test.go b/notifications/bridge_test.go
index e147fccb..4c3a4638 100644
--- a/notifications/bridge_test.go
+++ b/notifications/bridge_test.go
@@ -6,9 +6,9 @@ import (
 	"github.com/distribution/distribution/v3"
 	"github.com/distribution/distribution/v3/manifest"
 	"github.com/distribution/distribution/v3/manifest/schema2"
-	"github.com/distribution/distribution/v3/reference"
 	v2 "github.com/distribution/distribution/v3/registry/api/v2"
 	"github.com/distribution/distribution/v3/uuid"
+	"github.com/distribution/reference"
 	events "github.com/docker/go-events"
 	"github.com/opencontainers/go-digest"
 	v1 "github.com/opencontainers/image-spec/specs-go/v1"
diff --git a/notifications/listener_test.go b/notifications/listener_test.go
index 8d0c18a4..e33109be 100644
--- a/notifications/listener_test.go
+++ b/notifications/listener_test.go
@@ -8,11 +8,11 @@ import (
 	"github.com/distribution/distribution/v3"
 	dcontext "github.com/distribution/distribution/v3/context"
 	"github.com/distribution/distribution/v3/manifest/schema2"
-	"github.com/distribution/distribution/v3/reference"
 	"github.com/distribution/distribution/v3/registry/storage"
 	"github.com/distribution/distribution/v3/registry/storage/cache/memory"
 	"github.com/distribution/distribution/v3/registry/storage/driver/inmemory"
 	"github.com/distribution/distribution/v3/testutil"
+	"github.com/distribution/reference"
 	"github.com/opencontainers/go-digest"
 )
 
diff --git a/reference/fuzz_test.go b/reference/fuzz_test.go
deleted file mode 100644
index c64c54e2..00000000
--- a/reference/fuzz_test.go
+++ /dev/null
@@ -1,14 +0,0 @@
-package reference
-
-import (
-	"testing"
-)
-
-// fuzzParseNormalizedNamed implements a fuzzer
-// that targets ParseNormalizedNamed
-// nolint:deadcode
-func FuzzParseNormalizedNamed(f *testing.F) {
-	f.Fuzz(func(t *testing.T, data string) {
-		_, _ = ParseNormalizedNamed(data)
-	})
-}
diff --git a/reference/normalize_test.go b/reference/normalize_test.go
deleted file mode 100644
index a973927b..00000000
--- a/reference/normalize_test.go
+++ /dev/null
@@ -1,711 +0,0 @@
-package reference
-
-import (
-	"strconv"
-	"testing"
-
-	"github.com/opencontainers/go-digest"
-)
-
-func TestValidateReferenceName(t *testing.T) {
-	t.Parallel()
-	validRepoNames := []string{
-		"docker/docker",
-		"library/debian",
-		"debian",
-		"docker.io/docker/docker",
-		"docker.io/library/debian",
-		"docker.io/debian",
-		"index.docker.io/docker/docker",
-		"index.docker.io/library/debian",
-		"index.docker.io/debian",
-		"127.0.0.1:5000/docker/docker",
-		"127.0.0.1:5000/library/debian",
-		"127.0.0.1:5000/debian",
-		"192.168.0.1",
-		"192.168.0.1:80",
-		"192.168.0.1:8/debian",
-		"192.168.0.2:25000/debian",
-		"thisisthesongthatneverendsitgoesonandonandonthisisthesongthatnev",
-		"[fc00::1]:5000/docker",
-		"[fc00::1]:5000/docker/docker",
-		"[fc00:1:2:3:4:5:6:7]:5000/library/debian",
-
-		// This test case was moved from invalid to valid since it is valid input
-		// when specified with a hostname, it removes the ambiguity from about
-		// whether the value is an identifier or repository name
-		"docker.io/1a3f5e7d9c1b3a5f7e9d1c3b5a7f9e1d3c5b7a9f1e3d5d7c9b1a3f5e7d9c1b3a",
-		"Docker/docker",
-		"DOCKER/docker",
-	}
-	invalidRepoNames := []string{
-		"https://github.com/docker/docker",
-		"docker/Docker",
-		"-docker",
-		"-docker/docker",
-		"-docker.io/docker/docker",
-		"docker///docker",
-		"docker.io/docker/Docker",
-		"docker.io/docker///docker",
-		"[fc00::1]",
-		"[fc00::1]:5000",
-		"fc00::1:5000/debian",
-		"[fe80::1%eth0]:5000/debian",
-		"[2001:db8:3:4::192.0.2.33]:5000/debian",
-		"1a3f5e7d9c1b3a5f7e9d1c3b5a7f9e1d3c5b7a9f1e3d5d7c9b1a3f5e7d9c1b3a",
-	}
-
-	for _, name := range invalidRepoNames {
-		_, err := ParseNormalizedNamed(name)
-		if err == nil {
-			t.Fatalf("Expected invalid repo name for %q", name)
-		}
-	}
-
-	for _, name := range validRepoNames {
-		_, err := ParseNormalizedNamed(name)
-		if err != nil {
-			t.Fatalf("Error parsing repo name %s, got: %q", name, err)
-		}
-	}
-}
-
-func TestValidateRemoteName(t *testing.T) {
-	t.Parallel()
-	validRepositoryNames := []string{
-		// Sanity check.
-		"docker/docker",
-
-		// Allow 64-character non-hexadecimal names (hexadecimal names are forbidden).
-		"thisisthesongthatneverendsitgoesonandonandonthisisthesongthatnev",
-
-		// Allow embedded hyphens.
-		"docker-rules/docker",
-
-		// Allow multiple hyphens as well.
-		"docker---rules/docker",
-
-		// Username doc and image name docker being tested.
-		"doc/docker",
-
-		// single character names are now allowed.
-		"d/docker",
-		"jess/t",
-
-		// Consecutive underscores.
-		"dock__er/docker",
-	}
-	for _, repositoryName := range validRepositoryNames {
-		_, err := ParseNormalizedNamed(repositoryName)
-		if err != nil {
-			t.Errorf("Repository name should be valid: %v. Error: %v", repositoryName, err)
-		}
-	}
-
-	invalidRepositoryNames := []string{
-		// Disallow capital letters.
-		"docker/Docker",
-
-		// Only allow one slash.
-		"docker///docker",
-
-		// Disallow 64-character hexadecimal.
-		"1a3f5e7d9c1b3a5f7e9d1c3b5a7f9e1d3c5b7a9f1e3d5d7c9b1a3f5e7d9c1b3a",
-
-		// Disallow leading and trailing hyphens in namespace.
-		"-docker/docker",
-		"docker-/docker",
-		"-docker-/docker",
-
-		// Don't allow underscores everywhere (as opposed to hyphens).
-		"____/____",
-
-		"_docker/_docker",
-
-		// Disallow consecutive periods.
-		"dock..er/docker",
-		"dock_.er/docker",
-		"dock-.er/docker",
-
-		// No repository.
-		"docker/",
-
-		// namespace too long
-		"this_is_not_a_valid_namespace_because_its_lenth_is_greater_than_255_this_is_not_a_valid_namespace_because_its_lenth_is_greater_than_255_this_is_not_a_valid_namespace_because_its_lenth_is_greater_than_255_this_is_not_a_valid_namespace_because_its_lenth_is_greater_than_255/docker",
-	}
-	for _, repositoryName := range invalidRepositoryNames {
-		if _, err := ParseNormalizedNamed(repositoryName); err == nil {
-			t.Errorf("Repository name should be invalid: %v", repositoryName)
-		}
-	}
-}
-
-func TestParseRepositoryInfo(t *testing.T) {
-	t.Parallel()
-	type tcase struct {
-		RemoteName, FamiliarName, FullName, AmbiguousName, Domain string
-	}
-
-	tests := []tcase{
-		{
-			RemoteName:    "fooo/bar",
-			FamiliarName:  "fooo/bar",
-			FullName:      "docker.io/fooo/bar",
-			AmbiguousName: "index.docker.io/fooo/bar",
-			Domain:        "docker.io",
-		},
-		{
-			RemoteName:    "library/ubuntu",
-			FamiliarName:  "ubuntu",
-			FullName:      "docker.io/library/ubuntu",
-			AmbiguousName: "library/ubuntu",
-			Domain:        "docker.io",
-		},
-		{
-			RemoteName:    "nonlibrary/ubuntu",
-			FamiliarName:  "nonlibrary/ubuntu",
-			FullName:      "docker.io/nonlibrary/ubuntu",
-			AmbiguousName: "",
-			Domain:        "docker.io",
-		},
-		{
-			RemoteName:    "other/library",
-			FamiliarName:  "other/library",
-			FullName:      "docker.io/other/library",
-			AmbiguousName: "",
-			Domain:        "docker.io",
-		},
-		{
-			RemoteName:    "private/moonbase",
-			FamiliarName:  "127.0.0.1:8000/private/moonbase",
-			FullName:      "127.0.0.1:8000/private/moonbase",
-			AmbiguousName: "",
-			Domain:        "127.0.0.1:8000",
-		},
-		{
-			RemoteName:    "privatebase",
-			FamiliarName:  "127.0.0.1:8000/privatebase",
-			FullName:      "127.0.0.1:8000/privatebase",
-			AmbiguousName: "",
-			Domain:        "127.0.0.1:8000",
-		},
-		{
-			RemoteName:    "private/moonbase",
-			FamiliarName:  "example.com/private/moonbase",
-			FullName:      "example.com/private/moonbase",
-			AmbiguousName: "",
-			Domain:        "example.com",
-		},
-		{
-			RemoteName:    "privatebase",
-			FamiliarName:  "example.com/privatebase",
-			FullName:      "example.com/privatebase",
-			AmbiguousName: "",
-			Domain:        "example.com",
-		},
-		{
-			RemoteName:    "private/moonbase",
-			FamiliarName:  "example.com:8000/private/moonbase",
-			FullName:      "example.com:8000/private/moonbase",
-			AmbiguousName: "",
-			Domain:        "example.com:8000",
-		},
-		{
-			RemoteName:    "privatebasee",
-			FamiliarName:  "example.com:8000/privatebasee",
-			FullName:      "example.com:8000/privatebasee",
-			AmbiguousName: "",
-			Domain:        "example.com:8000",
-		},
-		{
-			RemoteName:    "library/ubuntu-12.04-base",
-			FamiliarName:  "ubuntu-12.04-base",
-			FullName:      "docker.io/library/ubuntu-12.04-base",
-			AmbiguousName: "index.docker.io/library/ubuntu-12.04-base",
-			Domain:        "docker.io",
-		},
-		{
-			RemoteName:    "library/foo",
-			FamiliarName:  "foo",
-			FullName:      "docker.io/library/foo",
-			AmbiguousName: "docker.io/foo",
-			Domain:        "docker.io",
-		},
-		{
-			RemoteName:    "library/foo/bar",
-			FamiliarName:  "library/foo/bar",
-			FullName:      "docker.io/library/foo/bar",
-			AmbiguousName: "",
-			Domain:        "docker.io",
-		},
-		{
-			RemoteName:    "store/foo/bar",
-			FamiliarName:  "store/foo/bar",
-			FullName:      "docker.io/store/foo/bar",
-			AmbiguousName: "",
-			Domain:        "docker.io",
-		},
-		{
-			RemoteName:    "bar",
-			FamiliarName:  "Foo/bar",
-			FullName:      "Foo/bar",
-			AmbiguousName: "",
-			Domain:        "Foo",
-		},
-		{
-			RemoteName:    "bar",
-			FamiliarName:  "FOO/bar",
-			FullName:      "FOO/bar",
-			AmbiguousName: "",
-			Domain:        "FOO",
-		},
-	}
-
-	for i, tc := range tests {
-		tc := tc
-		refStrings := []string{tc.FamiliarName, tc.FullName}
-		if tc.AmbiguousName != "" {
-			refStrings = append(refStrings, tc.AmbiguousName)
-		}
-
-		for _, r := range refStrings {
-			r := r
-			t.Run(strconv.Itoa(i)+"/"+r, func(t *testing.T) {
-				t.Parallel()
-				named, err := ParseNormalizedNamed(r)
-				if err != nil {
-					t.Fatalf("ref=%s: %v", r, err)
-				}
-				t.Run("FamiliarName", func(t *testing.T) {
-					if expected, actual := tc.FamiliarName, FamiliarName(named); expected != actual {
-						t.Errorf("Invalid familiar name for %q. Expected %q, got %q", named, expected, actual)
-					}
-				})
-				t.Run("FullName", func(t *testing.T) {
-					if expected, actual := tc.FullName, named.String(); expected != actual {
-						t.Errorf("Invalid canonical reference for %q. Expected %q, got %q", named, expected, actual)
-					}
-				})
-				t.Run("Domain", func(t *testing.T) {
-					if expected, actual := tc.Domain, Domain(named); expected != actual {
-						t.Errorf("Invalid domain for %q. Expected %q, got %q", named, expected, actual)
-					}
-				})
-				t.Run("RemoteName", func(t *testing.T) {
-					if expected, actual := tc.RemoteName, Path(named); expected != actual {
-						t.Errorf("Invalid remoteName for %q. Expected %q, got %q", named, expected, actual)
-					}
-				})
-			})
-		}
-	}
-}
-
-func TestParseReferenceWithTagAndDigest(t *testing.T) {
-	t.Parallel()
-	shortRef := "busybox:latest@sha256:86e0e091d0da6bde2456dbb48306f3956bbeb2eae1b5b9a43045843f69fe4aaa"
-	ref, err := ParseNormalizedNamed(shortRef)
-	if err != nil {
-		t.Fatal(err)
-	}
-	if expected, actual := "docker.io/library/"+shortRef, ref.String(); actual != expected {
-		t.Fatalf("Invalid parsed reference for %q: expected %q, got %q", ref, expected, actual)
-	}
-
-	if _, isTagged := ref.(NamedTagged); !isTagged {
-		t.Fatalf("Reference from %q should support tag", ref)
-	}
-	if _, isCanonical := ref.(Canonical); !isCanonical {
-		t.Fatalf("Reference from %q should support digest", ref)
-	}
-	if expected, actual := shortRef, FamiliarString(ref); actual != expected {
-		t.Fatalf("Invalid parsed reference for %q: expected %q, got %q", ref, expected, actual)
-	}
-}
-
-func TestInvalidReferenceComponents(t *testing.T) {
-	t.Parallel()
-	if _, err := ParseNormalizedNamed("-foo"); err == nil {
-		t.Fatal("Expected WithName to detect invalid name")
-	}
-	ref, err := ParseNormalizedNamed("busybox")
-	if err != nil {
-		t.Fatal(err)
-	}
-	if _, err := WithTag(ref, "-foo"); err == nil {
-		t.Fatal("Expected WithName to detect invalid tag")
-	}
-	if _, err := WithDigest(ref, digest.Digest("foo")); err == nil {
-		t.Fatal("Expected WithDigest to detect invalid digest")
-	}
-}
-
-func equalReference(r1, r2 Reference) bool {
-	switch v1 := r1.(type) {
-	case digestReference:
-		if v2, ok := r2.(digestReference); ok {
-			return v1 == v2
-		}
-	case repository:
-		if v2, ok := r2.(repository); ok {
-			return v1 == v2
-		}
-	case taggedReference:
-		if v2, ok := r2.(taggedReference); ok {
-			return v1 == v2
-		}
-	case canonicalReference:
-		if v2, ok := r2.(canonicalReference); ok {
-			return v1 == v2
-		}
-	case reference:
-		if v2, ok := r2.(reference); ok {
-			return v1 == v2
-		}
-	}
-	return false
-}
-
-func TestParseAnyReference(t *testing.T) {
-	t.Parallel()
-	tests := []struct {
-		Reference  string
-		Equivalent string
-		Expected   Reference
-	}{
-		{
-			Reference:  "redis",
-			Equivalent: "docker.io/library/redis",
-		},
-		{
-			Reference:  "redis:latest",
-			Equivalent: "docker.io/library/redis:latest",
-		},
-		{
-			Reference:  "docker.io/library/redis:latest",
-			Equivalent: "docker.io/library/redis:latest",
-		},
-		{
-			Reference:  "redis@sha256:dbcc1c35ac38df41fd2f5e4130b32ffdb93ebae8b3dbe638c23575912276fc9c",
-			Equivalent: "docker.io/library/redis@sha256:dbcc1c35ac38df41fd2f5e4130b32ffdb93ebae8b3dbe638c23575912276fc9c",
-		},
-		{
-			Reference:  "docker.io/library/redis@sha256:dbcc1c35ac38df41fd2f5e4130b32ffdb93ebae8b3dbe638c23575912276fc9c",
-			Equivalent: "docker.io/library/redis@sha256:dbcc1c35ac38df41fd2f5e4130b32ffdb93ebae8b3dbe638c23575912276fc9c",
-		},
-		{
-			Reference:  "dmcgowan/myapp",
-			Equivalent: "docker.io/dmcgowan/myapp",
-		},
-		{
-			Reference:  "dmcgowan/myapp:latest",
-			Equivalent: "docker.io/dmcgowan/myapp:latest",
-		},
-		{
-			Reference:  "docker.io/mcgowan/myapp:latest",
-			Equivalent: "docker.io/mcgowan/myapp:latest",
-		},
-		{
-			Reference:  "dmcgowan/myapp@sha256:dbcc1c35ac38df41fd2f5e4130b32ffdb93ebae8b3dbe638c23575912276fc9c",
-			Equivalent: "docker.io/dmcgowan/myapp@sha256:dbcc1c35ac38df41fd2f5e4130b32ffdb93ebae8b3dbe638c23575912276fc9c",
-		},
-		{
-			Reference:  "docker.io/dmcgowan/myapp@sha256:dbcc1c35ac38df41fd2f5e4130b32ffdb93ebae8b3dbe638c23575912276fc9c",
-			Equivalent: "docker.io/dmcgowan/myapp@sha256:dbcc1c35ac38df41fd2f5e4130b32ffdb93ebae8b3dbe638c23575912276fc9c",
-		},
-		{
-			Reference:  "dbcc1c35ac38df41fd2f5e4130b32ffdb93ebae8b3dbe638c23575912276fc9c",
-			Expected:   digestReference("sha256:dbcc1c35ac38df41fd2f5e4130b32ffdb93ebae8b3dbe638c23575912276fc9c"),
-			Equivalent: "sha256:dbcc1c35ac38df41fd2f5e4130b32ffdb93ebae8b3dbe638c23575912276fc9c",
-		},
-		{
-			Reference:  "sha256:dbcc1c35ac38df41fd2f5e4130b32ffdb93ebae8b3dbe638c23575912276fc9c",
-			Expected:   digestReference("sha256:dbcc1c35ac38df41fd2f5e4130b32ffdb93ebae8b3dbe638c23575912276fc9c"),
-			Equivalent: "sha256:dbcc1c35ac38df41fd2f5e4130b32ffdb93ebae8b3dbe638c23575912276fc9c",
-		},
-		{
-			Reference:  "dbcc1c35ac38df41fd2f5e4130b32ffdb93ebae8b3dbe638c23575912276fc9",
-			Equivalent: "docker.io/library/dbcc1c35ac38df41fd2f5e4130b32ffdb93ebae8b3dbe638c23575912276fc9",
-		},
-		{
-			Reference:  "dbcc1",
-			Equivalent: "docker.io/library/dbcc1",
-		},
-	}
-
-	for _, tc := range tests {
-		tc := tc
-		t.Run(tc.Reference, func(t *testing.T) {
-			t.Parallel()
-			var ref Reference
-			var err error
-			ref, err = ParseAnyReference(tc.Reference)
-			if err != nil {
-				t.Fatalf("Error parsing reference %s: %v", tc.Reference, err)
-			}
-			if ref.String() != tc.Equivalent {
-				t.Fatalf("Unexpected string: %s, expected %s", ref.String(), tc.Equivalent)
-			}
-
-			expected := tc.Expected
-			if expected == nil {
-				expected, err = Parse(tc.Equivalent)
-				if err != nil {
-					t.Fatalf("Error parsing reference %s: %v", tc.Equivalent, err)
-				}
-			}
-			if !equalReference(ref, expected) {
-				t.Errorf("Unexpected reference %#v, expected %#v", ref, expected)
-			}
-		})
-	}
-}
-
-func TestNormalizedSplitHostname(t *testing.T) {
-	t.Parallel()
-	tests := []struct {
-		input  string
-		domain string
-		name   string
-	}{
-		{
-			input:  "test.com/foo",
-			domain: "test.com",
-			name:   "foo",
-		},
-		{
-			input:  "test_com/foo",
-			domain: "docker.io",
-			name:   "test_com/foo",
-		},
-		{
-			input:  "docker/migrator",
-			domain: "docker.io",
-			name:   "docker/migrator",
-		},
-		{
-			input:  "test.com:8080/foo",
-			domain: "test.com:8080",
-			name:   "foo",
-		},
-		{
-			input:  "test-com:8080/foo",
-			domain: "test-com:8080",
-			name:   "foo",
-		},
-		{
-			input:  "foo",
-			domain: "docker.io",
-			name:   "library/foo",
-		},
-		{
-			input:  "xn--n3h.com/foo",
-			domain: "xn--n3h.com",
-			name:   "foo",
-		},
-		{
-			input:  "xn--n3h.com:18080/foo",
-			domain: "xn--n3h.com:18080",
-			name:   "foo",
-		},
-		{
-			input:  "docker.io/foo",
-			domain: "docker.io",
-			name:   "library/foo",
-		},
-		{
-			input:  "docker.io/library/foo",
-			domain: "docker.io",
-			name:   "library/foo",
-		},
-		{
-			input:  "docker.io/library/foo/bar",
-			domain: "docker.io",
-			name:   "library/foo/bar",
-		},
-	}
-	for _, tc := range tests {
-		tc := tc
-		t.Run(tc.input, func(t *testing.T) {
-			t.Parallel()
-			named, err := ParseNormalizedNamed(tc.input)
-			if err != nil {
-				t.Errorf("error parsing name: %s", err)
-			}
-			domain, name := SplitHostname(named) //nolint:staticcheck // Ignore SA1019: SplitHostname is deprecated.
-			if domain != tc.domain {
-				t.Errorf("unexpected domain: got %q, expected %q", domain, tc.domain)
-			}
-			if name != tc.name {
-				t.Errorf("unexpected name: got %q, expected %q", name, tc.name)
-			}
-		})
-	}
-}
-
-func TestMatchError(t *testing.T) {
-	t.Parallel()
-	named, err := ParseAnyReference("foo")
-	if err != nil {
-		t.Fatal(err)
-	}
-	_, err = FamiliarMatch("[-x]", named)
-	if err == nil {
-		t.Fatalf("expected an error, got nothing")
-	}
-}
-
-func TestMatch(t *testing.T) {
-	t.Parallel()
-	tests := []struct {
-		reference string
-		pattern   string
-		expected  bool
-	}{
-		{
-			reference: "foo",
-			pattern:   "foo/**/ba[rz]",
-			expected:  false,
-		},
-		{
-			reference: "foo/any/bat",
-			pattern:   "foo/**/ba[rz]",
-			expected:  false,
-		},
-		{
-			reference: "foo/a/bar",
-			pattern:   "foo/**/ba[rz]",
-			expected:  true,
-		},
-		{
-			reference: "foo/b/baz",
-			pattern:   "foo/**/ba[rz]",
-			expected:  true,
-		},
-		{
-			reference: "foo/c/baz:tag",
-			pattern:   "foo/**/ba[rz]",
-			expected:  true,
-		},
-		{
-			reference: "foo/c/baz:tag",
-			pattern:   "foo/*/baz:tag",
-			expected:  true,
-		},
-		{
-			reference: "foo/c/baz:tag",
-			pattern:   "foo/c/baz:tag",
-			expected:  true,
-		},
-		{
-			reference: "example.com/foo/c/baz:tag",
-			pattern:   "*/foo/c/baz",
-			expected:  true,
-		},
-		{
-			reference: "example.com/foo/c/baz:tag",
-			pattern:   "example.com/foo/c/baz",
-			expected:  true,
-		},
-	}
-	for _, tc := range tests {
-		tc := tc
-		t.Run(tc.reference, func(t *testing.T) {
-			t.Parallel()
-			named, err := ParseAnyReference(tc.reference)
-			if err != nil {
-				t.Fatal(err)
-			}
-			actual, err := FamiliarMatch(tc.pattern, named)
-			if err != nil {
-				t.Fatal(err)
-			}
-			if actual != tc.expected {
-				t.Fatalf("expected %s match %s to be %v, was %v", tc.reference, tc.pattern, tc.expected, actual)
-			}
-		})
-	}
-}
-
-func TestParseDockerRef(t *testing.T) {
-	t.Parallel()
-	tests := []struct {
-		name     string
-		input    string
-		expected string
-	}{
-		{
-			name:     "nothing",
-			input:    "busybox",
-			expected: "docker.io/library/busybox:latest",
-		},
-		{
-			name:     "tag only",
-			input:    "busybox:latest",
-			expected: "docker.io/library/busybox:latest",
-		},
-		{
-			name:     "digest only",
-			input:    "busybox@sha256:e6693c20186f837fc393390135d8a598a96a833917917789d63766cab6c59582",
-			expected: "docker.io/library/busybox@sha256:e6693c20186f837fc393390135d8a598a96a833917917789d63766cab6c59582",
-		},
-		{
-			name:     "path only",
-			input:    "library/busybox",
-			expected: "docker.io/library/busybox:latest",
-		},
-		{
-			name:     "hostname only",
-			input:    "docker.io/busybox",
-			expected: "docker.io/library/busybox:latest",
-		},
-		{
-			name:     "no tag",
-			input:    "docker.io/library/busybox",
-			expected: "docker.io/library/busybox:latest",
-		},
-		{
-			name:     "no path",
-			input:    "docker.io/busybox:latest",
-			expected: "docker.io/library/busybox:latest",
-		},
-		{
-			name:     "no hostname",
-			input:    "library/busybox:latest",
-			expected: "docker.io/library/busybox:latest",
-		},
-		{
-			name:     "full reference with tag",
-			input:    "docker.io/library/busybox:latest",
-			expected: "docker.io/library/busybox:latest",
-		},
-		{
-			name:     "gcr reference without tag",
-			input:    "gcr.io/library/busybox",
-			expected: "gcr.io/library/busybox:latest",
-		},
-		{
-			name:     "both tag and digest",
-			input:    "gcr.io/library/busybox:latest@sha256:e6693c20186f837fc393390135d8a598a96a833917917789d63766cab6c59582",
-			expected: "gcr.io/library/busybox@sha256:e6693c20186f837fc393390135d8a598a96a833917917789d63766cab6c59582",
-		},
-	}
-	for _, tc := range tests {
-		tc := tc
-		t.Run(tc.name, func(t *testing.T) {
-			t.Parallel()
-			normalized, err := ParseDockerRef(tc.input)
-			if err != nil {
-				t.Fatal(err)
-			}
-			output := normalized.String()
-			if output != tc.expected {
-				t.Fatalf("expected %q to be parsed as %v, got %v", tc.input, tc.expected, output)
-			}
-			_, err = Parse(output)
-			if err != nil {
-				t.Fatalf("%q should be a valid reference, but got an error: %v", output, err)
-			}
-		})
-	}
-}
diff --git a/reference/reference_test.go b/reference/reference_test.go
deleted file mode 100644
index d5a897c7..00000000
--- a/reference/reference_test.go
+++ /dev/null
@@ -1,751 +0,0 @@
-package reference
-
-import (
-	_ "crypto/sha256"
-	_ "crypto/sha512"
-	"encoding/json"
-	"strings"
-	"testing"
-
-	"github.com/opencontainers/go-digest"
-)
-
-func TestReferenceParse(t *testing.T) {
-	t.Parallel()
-	// tests is a unified set of testcases for
-	// testing the parsing of references
-	tests := []struct {
-		// input is the repository name or name component testcase
-		input string
-		// err is the error expected from Parse, or nil
-		err error
-		// repository is the string representation for the reference
-		repository string
-		// domain is the domain expected in the reference
-		domain string
-		// tag is the tag for the reference
-		tag string
-		// digest is the digest for the reference (enforces digest reference)
-		digest string
-	}{
-		{
-			input:      "test_com",
-			repository: "test_com",
-		},
-		{
-			input:      "test.com:tag",
-			repository: "test.com",
-			tag:        "tag",
-		},
-		{
-			input:      "test.com:5000",
-			repository: "test.com",
-			tag:        "5000",
-		},
-		{
-			input:      "test.com/repo:tag",
-			domain:     "test.com",
-			repository: "test.com/repo",
-			tag:        "tag",
-		},
-		{
-			input:      "test:5000/repo",
-			domain:     "test:5000",
-			repository: "test:5000/repo",
-		},
-		{
-			input:      "test:5000/repo:tag",
-			domain:     "test:5000",
-			repository: "test:5000/repo",
-			tag:        "tag",
-		},
-		{
-			input:      "test:5000/repo@sha256:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
-			domain:     "test:5000",
-			repository: "test:5000/repo",
-			digest:     "sha256:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
-		},
-		{
-			input:      "test:5000/repo:tag@sha256:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
-			domain:     "test:5000",
-			repository: "test:5000/repo",
-			tag:        "tag",
-			digest:     "sha256:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
-		},
-		{
-			input:      "test:5000/repo",
-			domain:     "test:5000",
-			repository: "test:5000/repo",
-		},
-		{
-			input: "",
-			err:   ErrNameEmpty,
-		},
-		{
-			input: ":justtag",
-			err:   ErrReferenceInvalidFormat,
-		},
-		{
-			input: "@sha256:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
-			err:   ErrReferenceInvalidFormat,
-		},
-		{
-			input: "repo@sha256:ffffffffffffffffffffffffffffffffff",
-			err:   digest.ErrDigestInvalidLength,
-		},
-		{
-			input: "validname@invaliddigest:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
-			err:   digest.ErrDigestUnsupported,
-		},
-		{
-			input: "Uppercase:tag",
-			err:   ErrNameContainsUppercase,
-		},
-		// FIXME "Uppercase" is incorrectly handled as a domain-name here, therefore passes.
-		// See https://github.com/distribution/distribution/pull/1778, and https://github.com/docker/docker/pull/20175
-		// {
-		//	input: "Uppercase/lowercase:tag",
-		//	err:   ErrNameContainsUppercase,
-		// },
-		{
-			input: "test:5000/Uppercase/lowercase:tag",
-			err:   ErrNameContainsUppercase,
-		},
-		{
-			input:      "lowercase:Uppercase",
-			repository: "lowercase",
-			tag:        "Uppercase",
-		},
-		{
-			input: strings.Repeat("a/", 128) + "a:tag",
-			err:   ErrNameTooLong,
-		},
-		{
-			input:      strings.Repeat("a/", 127) + "a:tag-puts-this-over-max",
-			domain:     "a",
-			repository: strings.Repeat("a/", 127) + "a",
-			tag:        "tag-puts-this-over-max",
-		},
-		{
-			input: "aa/asdf$$^/aa",
-			err:   ErrReferenceInvalidFormat,
-		},
-		{
-			input:      "sub-dom1.foo.com/bar/baz/quux",
-			domain:     "sub-dom1.foo.com",
-			repository: "sub-dom1.foo.com/bar/baz/quux",
-		},
-		{
-			input:      "sub-dom1.foo.com/bar/baz/quux:some-long-tag",
-			domain:     "sub-dom1.foo.com",
-			repository: "sub-dom1.foo.com/bar/baz/quux",
-			tag:        "some-long-tag",
-		},
-		{
-			input:      "b.gcr.io/test.example.com/my-app:test.example.com",
-			domain:     "b.gcr.io",
-			repository: "b.gcr.io/test.example.com/my-app",
-			tag:        "test.example.com",
-		},
-		{
-			input:      "xn--n3h.com/myimage:xn--n3h.com", // ☃.com in punycode
-			domain:     "xn--n3h.com",
-			repository: "xn--n3h.com/myimage",
-			tag:        "xn--n3h.com",
-		},
-		{
-			input:      "xn--7o8h.com/myimage:xn--7o8h.com@sha512:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", // 🐳.com in punycode
-			domain:     "xn--7o8h.com",
-			repository: "xn--7o8h.com/myimage",
-			tag:        "xn--7o8h.com",
-			digest:     "sha512:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
-		},
-		{
-			input:      "foo_bar.com:8080",
-			repository: "foo_bar.com",
-			tag:        "8080",
-		},
-		{
-			input:      "foo/foo_bar.com:8080",
-			domain:     "foo",
-			repository: "foo/foo_bar.com",
-			tag:        "8080",
-		},
-		{
-			input:      "192.168.1.1",
-			repository: "192.168.1.1",
-		},
-		{
-			input:      "192.168.1.1:tag",
-			repository: "192.168.1.1",
-			tag:        "tag",
-		},
-		{
-			input:      "192.168.1.1:5000",
-			repository: "192.168.1.1",
-			tag:        "5000",
-		},
-		{
-			input:      "192.168.1.1/repo",
-			domain:     "192.168.1.1",
-			repository: "192.168.1.1/repo",
-		},
-		{
-			input:      "192.168.1.1:5000/repo",
-			domain:     "192.168.1.1:5000",
-			repository: "192.168.1.1:5000/repo",
-		},
-		{
-			input:      "192.168.1.1:5000/repo:5050",
-			domain:     "192.168.1.1:5000",
-			repository: "192.168.1.1:5000/repo",
-			tag:        "5050",
-		},
-		{
-			input: "[2001:db8::1]",
-			err:   ErrReferenceInvalidFormat,
-		},
-		{
-			input: "[2001:db8::1]:5000",
-			err:   ErrReferenceInvalidFormat,
-		},
-		{
-			input: "[2001:db8::1]:tag",
-			err:   ErrReferenceInvalidFormat,
-		},
-		{
-			input:      "[2001:db8::1]/repo",
-			domain:     "[2001:db8::1]",
-			repository: "[2001:db8::1]/repo",
-		},
-		{
-			input:      "[2001:db8:1:2:3:4:5:6]/repo:tag",
-			domain:     "[2001:db8:1:2:3:4:5:6]",
-			repository: "[2001:db8:1:2:3:4:5:6]/repo",
-			tag:        "tag",
-		},
-		{
-			input:      "[2001:db8::1]:5000/repo",
-			domain:     "[2001:db8::1]:5000",
-			repository: "[2001:db8::1]:5000/repo",
-		},
-		{
-			input:      "[2001:db8::1]:5000/repo:tag",
-			domain:     "[2001:db8::1]:5000",
-			repository: "[2001:db8::1]:5000/repo",
-			tag:        "tag",
-		},
-		{
-			input:      "[2001:db8::1]:5000/repo@sha256:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
-			domain:     "[2001:db8::1]:5000",
-			repository: "[2001:db8::1]:5000/repo",
-			digest:     "sha256:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
-		},
-		{
-			input:      "[2001:db8::1]:5000/repo:tag@sha256:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
-			domain:     "[2001:db8::1]:5000",
-			repository: "[2001:db8::1]:5000/repo",
-			tag:        "tag",
-			digest:     "sha256:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
-		},
-		{
-			input:      "[2001:db8::]:5000/repo",
-			domain:     "[2001:db8::]:5000",
-			repository: "[2001:db8::]:5000/repo",
-		},
-		{
-			input:      "[::1]:5000/repo",
-			domain:     "[::1]:5000",
-			repository: "[::1]:5000/repo",
-		},
-		{
-			input: "[fe80::1%eth0]:5000/repo",
-			err:   ErrReferenceInvalidFormat,
-		},
-		{
-			input: "[fe80::1%@invalidzone]:5000/repo",
-			err:   ErrReferenceInvalidFormat,
-		},
-	}
-	for _, tc := range tests {
-		tc := tc
-		t.Run(tc.input, func(t *testing.T) {
-			t.Parallel()
-			repo, err := Parse(tc.input)
-			if tc.err != nil {
-				if err == nil {
-					t.Errorf("missing expected error: %v", tc.err)
-				} else if tc.err != err {
-					t.Errorf("mismatched error: got %v, expected %v", err, tc.err)
-				}
-				return
-			} else if err != nil {
-				t.Errorf("unexpected parse error: %v", err)
-				return
-			}
-			if repo.String() != tc.input {
-				t.Errorf("mismatched repo: got %q, expected %q", repo.String(), tc.input)
-			}
-
-			if named, ok := repo.(Named); ok {
-				if named.Name() != tc.repository {
-					t.Errorf("unexpected repository: got %q, expected %q", named.Name(), tc.repository)
-				}
-				domain, _ := SplitHostname(named)
-				if domain != tc.domain {
-					t.Errorf("unexpected domain: got %q, expected %q", domain, tc.domain)
-				}
-			} else if tc.repository != "" || tc.domain != "" {
-				t.Errorf("expected named type, got %T", repo)
-			}
-
-			tagged, ok := repo.(Tagged)
-			if tc.tag != "" {
-				if ok {
-					if tagged.Tag() != tc.tag {
-						t.Errorf("unexpected tag: got %q, expected %q", tagged.Tag(), tc.tag)
-					}
-				} else {
-					t.Errorf("expected tagged type, got %T", repo)
-				}
-			} else if ok {
-				t.Errorf("unexpected tagged type")
-			}
-
-			digested, ok := repo.(Digested)
-			if tc.digest != "" {
-				if ok {
-					if digested.Digest().String() != tc.digest {
-						t.Errorf("unexpected digest: got %q, expected %q", digested.Digest().String(), tc.digest)
-					}
-				} else {
-					t.Errorf("expected digested type, got %T", repo)
-				}
-			} else if ok {
-				t.Errorf("unexpected digested type")
-			}
-		})
-	}
-}
-
-// TestWithNameFailure tests cases where WithName should fail. Cases where it
-// should succeed are covered by TestSplitHostname, below.
-func TestWithNameFailure(t *testing.T) {
-	t.Parallel()
-	tests := []struct {
-		input string
-		err   error
-	}{
-		{
-			input: "",
-			err:   ErrNameEmpty,
-		},
-		{
-			input: ":justtag",
-			err:   ErrReferenceInvalidFormat,
-		},
-		{
-			input: "@sha256:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
-			err:   ErrReferenceInvalidFormat,
-		},
-		{
-			input: "validname@invaliddigest:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
-			err:   ErrReferenceInvalidFormat,
-		},
-		{
-			input: strings.Repeat("a/", 128) + "a:tag",
-			err:   ErrNameTooLong,
-		},
-		{
-			input: "aa/asdf$$^/aa",
-			err:   ErrReferenceInvalidFormat,
-		},
-	}
-	for _, tc := range tests {
-		tc := tc
-		t.Run(tc.input, func(t *testing.T) {
-			t.Parallel()
-			_, err := WithName(tc.input)
-			if err == nil {
-				t.Errorf("no error parsing name. expected: %s", tc.err)
-			}
-		})
-	}
-}
-
-func TestSplitHostname(t *testing.T) {
-	t.Parallel()
-	tests := []struct {
-		input  string
-		domain string
-		name   string
-	}{
-		{
-			input:  "test.com/foo",
-			domain: "test.com",
-			name:   "foo",
-		},
-		{
-			input:  "test_com/foo",
-			domain: "",
-			name:   "test_com/foo",
-		},
-		{
-			input:  "test:8080/foo",
-			domain: "test:8080",
-			name:   "foo",
-		},
-		{
-			input:  "test.com:8080/foo",
-			domain: "test.com:8080",
-			name:   "foo",
-		},
-		{
-			input:  "test-com:8080/foo",
-			domain: "test-com:8080",
-			name:   "foo",
-		},
-		{
-			input:  "xn--n3h.com:18080/foo",
-			domain: "xn--n3h.com:18080",
-			name:   "foo",
-		},
-	}
-	for _, tc := range tests {
-		tc := tc
-		t.Run(tc.input, func(t *testing.T) {
-			t.Parallel()
-			named, err := WithName(tc.input)
-			if err != nil {
-				t.Errorf("error parsing name: %s", err)
-			}
-			domain, name := SplitHostname(named)
-			if domain != tc.domain {
-				t.Errorf("unexpected domain: got %q, expected %q", domain, tc.domain)
-			}
-			if name != tc.name {
-				t.Errorf("unexpected name: got %q, expected %q", name, tc.name)
-			}
-		})
-	}
-}
-
-type serializationType struct {
-	Description string
-	Field       Field
-}
-
-func TestSerialization(t *testing.T) {
-	t.Parallel()
-	tests := []struct {
-		description string
-		input       string
-		name        string
-		tag         string
-		digest      string
-		err         error
-	}{
-		{
-			description: "empty value",
-			err:         ErrNameEmpty,
-		},
-		{
-			description: "just a name",
-			input:       "example.com:8000/named",
-			name:        "example.com:8000/named",
-		},
-		{
-			description: "name with a tag",
-			input:       "example.com:8000/named:tagged",
-			name:        "example.com:8000/named",
-			tag:         "tagged",
-		},
-		{
-			description: "name with digest",
-			input:       "other.com/named@sha256:1234567890098765432112345667890098765432112345667890098765432112",
-			name:        "other.com/named",
-			digest:      "sha256:1234567890098765432112345667890098765432112345667890098765432112",
-		},
-	}
-	for _, tc := range tests {
-		tc := tc
-		t.Run(tc.description, func(t *testing.T) {
-			t.Parallel()
-			m := map[string]string{
-				"Description": tc.description,
-				"Field":       tc.input,
-			}
-			b, err := json.Marshal(m)
-			if err != nil {
-				t.Errorf("error marshalling: %v", err)
-			}
-			st := serializationType{}
-
-			if err := json.Unmarshal(b, &st); err != nil {
-				if tc.err == nil {
-					t.Errorf("error unmarshalling: %v", err)
-				}
-				if err != tc.err {
-					t.Errorf("wrong error, expected %v, got %v", tc.err, err)
-				}
-
-				return
-			} else if tc.err != nil {
-				t.Errorf("expected error unmarshalling: %v", tc.err)
-			}
-
-			if st.Description != tc.description {
-				t.Errorf("wrong description, expected %q, got %q", tc.description, st.Description)
-			}
-
-			ref := st.Field.Reference()
-
-			if named, ok := ref.(Named); ok {
-				if named.Name() != tc.name {
-					t.Errorf("unexpected repository: got %q, expected %q", named.Name(), tc.name)
-				}
-			} else if tc.name != "" {
-				t.Errorf("expected named type, got %T", ref)
-			}
-
-			tagged, ok := ref.(Tagged)
-			if tc.tag != "" {
-				if ok {
-					if tagged.Tag() != tc.tag {
-						t.Errorf("unexpected tag: got %q, expected %q", tagged.Tag(), tc.tag)
-					}
-				} else {
-					t.Errorf("expected tagged type, got %T", ref)
-				}
-			} else if ok {
-				t.Errorf("unexpected tagged type")
-			}
-
-			digested, ok := ref.(Digested)
-			if tc.digest != "" {
-				if ok {
-					if digested.Digest().String() != tc.digest {
-						t.Errorf("unexpected digest: got %q, expected %q", digested.Digest().String(), tc.digest)
-					}
-				} else {
-					t.Errorf("expected digested type, got %T", ref)
-				}
-			} else if ok {
-				t.Errorf("unexpected digested type")
-			}
-
-			st = serializationType{
-				Description: tc.description,
-				Field:       AsField(ref),
-			}
-
-			b2, err := json.Marshal(st)
-			if err != nil {
-				t.Errorf("error marshing serialization type: %v", err)
-			}
-
-			if string(b) != string(b2) {
-				t.Errorf("unexpected serialized value: expected %q, got %q", string(b), string(b2))
-			}
-
-			// Ensure st.Field is not implementing "Reference" directly, getting
-			// around the Reference type system
-			var fieldInterface interface{} = st.Field
-			if _, ok := fieldInterface.(Reference); ok {
-				t.Errorf("field should not implement Reference interface")
-			}
-		})
-	}
-}
-
-func TestWithTag(t *testing.T) {
-	t.Parallel()
-	tests := []struct {
-		name     string
-		digest   digest.Digest
-		tag      string
-		combined string
-	}{
-		{
-			name:     "test.com/foo",
-			tag:      "tag",
-			combined: "test.com/foo:tag",
-		},
-		{
-			name:     "foo",
-			tag:      "tag2",
-			combined: "foo:tag2",
-		},
-		{
-			name:     "test.com:8000/foo",
-			tag:      "tag4",
-			combined: "test.com:8000/foo:tag4",
-		},
-		{
-			name:     "test.com:8000/foo",
-			tag:      "TAG5",
-			combined: "test.com:8000/foo:TAG5",
-		},
-		{
-			name:     "test.com:8000/foo",
-			digest:   "sha256:1234567890098765432112345667890098765",
-			tag:      "TAG5",
-			combined: "test.com:8000/foo:TAG5@sha256:1234567890098765432112345667890098765",
-		},
-	}
-	for _, tc := range tests {
-		tc := tc
-		t.Run(tc.combined, func(t *testing.T) {
-			t.Parallel()
-			named, err := WithName(tc.name)
-			if err != nil {
-				t.Errorf("error parsing name: %s", err)
-			}
-			if tc.digest != "" {
-				canonical, err := WithDigest(named, tc.digest)
-				if err != nil {
-					t.Errorf("error adding digest")
-				}
-				named = canonical
-			}
-
-			tagged, err := WithTag(named, tc.tag)
-			if err != nil {
-				t.Errorf("WithTag failed: %s", err)
-			}
-			if tagged.String() != tc.combined {
-				t.Errorf("unexpected: got %q, expected %q", tagged.String(), tc.combined)
-			}
-		})
-	}
-}
-
-func TestWithDigest(t *testing.T) {
-	t.Parallel()
-	tests := []struct {
-		name     string
-		digest   digest.Digest
-		tag      string
-		combined string
-	}{
-		{
-			name:     "test.com/foo",
-			digest:   "sha256:1234567890098765432112345667890098765",
-			combined: "test.com/foo@sha256:1234567890098765432112345667890098765",
-		},
-		{
-			name:     "foo",
-			digest:   "sha256:1234567890098765432112345667890098765",
-			combined: "foo@sha256:1234567890098765432112345667890098765",
-		},
-		{
-			name:     "test.com:8000/foo",
-			digest:   "sha256:1234567890098765432112345667890098765",
-			combined: "test.com:8000/foo@sha256:1234567890098765432112345667890098765",
-		},
-		{
-			name:     "test.com:8000/foo",
-			digest:   "sha256:1234567890098765432112345667890098765",
-			tag:      "latest",
-			combined: "test.com:8000/foo:latest@sha256:1234567890098765432112345667890098765",
-		},
-	}
-	for _, tc := range tests {
-		tc := tc
-		t.Run(tc.combined, func(t *testing.T) {
-			t.Parallel()
-			named, err := WithName(tc.name)
-			if err != nil {
-				t.Errorf("error parsing name: %s", err)
-			}
-			if tc.tag != "" {
-				tagged, err := WithTag(named, tc.tag)
-				if err != nil {
-					t.Errorf("error adding tag")
-				}
-				named = tagged
-			}
-			digested, err := WithDigest(named, tc.digest)
-			if err != nil {
-				t.Errorf("WithDigest failed: %s", err)
-			}
-			if digested.String() != tc.combined {
-				t.Errorf("unexpected: got %q, expected %q", digested.String(), tc.combined)
-			}
-		})
-	}
-}
-
-func TestParseNamed(t *testing.T) {
-	t.Parallel()
-	tests := []struct {
-		input  string
-		domain string
-		name   string
-		err    error
-	}{
-		{
-			input:  "test.com/foo",
-			domain: "test.com",
-			name:   "foo",
-		},
-		{
-			input:  "test:8080/foo",
-			domain: "test:8080",
-			name:   "foo",
-		},
-		{
-			input: "test_com/foo",
-			err:   ErrNameNotCanonical,
-		},
-		{
-			input: "test.com",
-			err:   ErrNameNotCanonical,
-		},
-		{
-			input: "foo",
-			err:   ErrNameNotCanonical,
-		},
-		{
-			input: "library/foo",
-			err:   ErrNameNotCanonical,
-		},
-		{
-			input:  "docker.io/library/foo",
-			domain: "docker.io",
-			name:   "library/foo",
-		},
-		// Ambiguous case, parser will add "library/" to foo
-		{
-			input: "docker.io/foo",
-			err:   ErrNameNotCanonical,
-		},
-	}
-	for _, tc := range tests {
-		tc := tc
-		t.Run(tc.input, func(t *testing.T) {
-			t.Parallel()
-			named, err := ParseNamed(tc.input)
-			if err != nil && tc.err == nil {
-				t.Errorf("error parsing name: %s", err)
-				return
-			} else if err == nil && tc.err != nil {
-				t.Errorf("parsing succeeded: expected error %v", tc.err)
-				return
-			} else if err != tc.err {
-				t.Errorf("unexpected error %v, expected %v", err, tc.err)
-				return
-			} else if err != nil {
-				return
-			}
-
-			domain, name := SplitHostname(named)
-			if domain != tc.domain {
-				t.Errorf("unexpected domain: got %q, expected %q", domain, tc.domain)
-			}
-			if name != tc.name {
-				t.Errorf("unexpected name: got %q, expected %q", name, tc.name)
-			}
-		})
-	}
-}
diff --git a/reference/regexp_bench_test.go b/reference/regexp_bench_test.go
deleted file mode 100644
index 12cb6077..00000000
--- a/reference/regexp_bench_test.go
+++ /dev/null
@@ -1,270 +0,0 @@
-package reference
-
-import (
-	"strings"
-	"testing"
-)
-
-func BenchmarkParse(b *testing.B) {
-	tests := []regexpMatch{
-		{
-			input: "",
-			match: false,
-		},
-		{
-			input: "short",
-			match: true,
-		},
-		{
-			input: "simple/name",
-			match: true,
-		},
-		{
-			input: "library/ubuntu",
-			match: true,
-		},
-		{
-			input: "docker/stevvooe/app",
-			match: true,
-		},
-		{
-			input: "aa/aa/aa/aa/aa/aa/aa/aa/aa/bb/bb/bb/bb/bb/bb",
-			match: true,
-		},
-		{
-			input: "aa/aa/bb/bb/bb",
-			match: true,
-		},
-		{
-			input: "a/a/a/a",
-			match: true,
-		},
-		{
-			input: "a/a/a/a/",
-			match: false,
-		},
-		{
-			input: "a//a/a",
-			match: false,
-		},
-		{
-			input: "a",
-			match: true,
-		},
-		{
-			input: "a/aa",
-			match: true,
-		},
-		{
-			input: "a/aa/a",
-			match: true,
-		},
-		{
-			input: "foo.com",
-			match: true,
-		},
-		{
-			input: "foo.com/",
-			match: false,
-		},
-		{
-			input: "foo.com:8080/bar",
-			match: true,
-		},
-		{
-			input: "foo.com:http/bar",
-			match: false,
-		},
-		{
-			input: "foo.com/bar",
-			match: true,
-		},
-		{
-			input: "foo.com/bar/baz",
-			match: true,
-		},
-		{
-			input: "localhost:8080/bar",
-			match: true,
-		},
-		{
-			input: "sub-dom1.foo.com/bar/baz/quux",
-			match: true,
-		},
-		{
-			input: "blog.foo.com/bar/baz",
-			match: true,
-		},
-		{
-			input: "a^a",
-			match: false,
-		},
-		{
-			input: "aa/asdf$$^/aa",
-			match: false,
-		},
-		{
-			input: "asdf$$^/aa",
-			match: false,
-		},
-		{
-			input: "aa-a/a",
-			match: true,
-		},
-		{
-			input: strings.Repeat("a/", 128) + "a",
-			match: true,
-		},
-		{
-			input: "a-/a/a/a",
-			match: false,
-		},
-		{
-			input: "foo.com/a-/a/a",
-			match: false,
-		},
-		{
-			input: "-foo/bar",
-			match: false,
-		},
-		{
-			input: "foo/bar-",
-			match: false,
-		},
-		{
-			input: "foo-/bar",
-			match: false,
-		},
-		{
-			input: "foo/-bar",
-			match: false,
-		},
-		{
-			input: "_foo/bar",
-			match: false,
-		},
-		{
-			input: "foo_bar",
-			match: true,
-		},
-		{
-			input: "foo_bar.com",
-			match: true,
-		},
-		{
-			input: "foo_bar.com:8080",
-			match: false,
-		},
-		{
-			input: "foo_bar.com:8080/app",
-			match: false,
-		},
-		{
-			input: "foo.com/foo_bar",
-			match: true,
-		},
-		{
-			input: "____/____",
-			match: false,
-		},
-		{
-			input: "_docker/_docker",
-			match: false,
-		},
-		{
-			input: "docker_/docker_",
-			match: false,
-		},
-		{
-			input: "b.gcr.io/test.example.com/my-app",
-			match: true,
-		},
-		{
-			input: "xn--n3h.com/myimage", // ☃.com in punycode
-			match: true,
-		},
-		{
-			input: "xn--7o8h.com/myimage", // 🐳.com in punycode
-			match: true,
-		},
-		{
-			input: "example.com/xn--7o8h.com/myimage", // 🐳.com in punycode
-			match: true,
-		},
-		{
-			input: "example.com/some_separator__underscore/myimage",
-			match: true,
-		},
-		{
-			input: "example.com/__underscore/myimage",
-			match: false,
-		},
-		{
-			input: "example.com/..dots/myimage",
-			match: false,
-		},
-		{
-			input: "example.com/.dots/myimage",
-			match: false,
-		},
-		{
-			input: "example.com/nodouble..dots/myimage",
-			match: false,
-		},
-		{
-			input: "example.com/nodouble..dots/myimage",
-			match: false,
-		},
-		{
-			input: "docker./docker",
-			match: false,
-		},
-		{
-			input: ".docker/docker",
-			match: false,
-		},
-		{
-			input: "docker-/docker",
-			match: false,
-		},
-		{
-			input: "-docker/docker",
-			match: false,
-		},
-		{
-			input: "do..cker/docker",
-			match: false,
-		},
-		{
-			input: "do__cker:8080/docker",
-			match: false,
-		},
-		{
-			input: "do__cker/docker",
-			match: true,
-		},
-		{
-			input: "b.gcr.io/test.example.com/my-app",
-			match: true,
-		},
-		{
-			input: "registry.io/foo/project--id.module--name.ver---sion--name",
-			match: true,
-		},
-		{
-			input: "Asdf.com/foo/bar", // uppercase character in hostname
-			match: true,
-		},
-		{
-			input: "Foo/FarB", // uppercase characters in remote name
-			match: false,
-		},
-	}
-
-	b.ReportAllocs()
-	for i := 0; i < b.N; i++ {
-		for _, tc := range tests {
-			_, _ = Parse(tc.input)
-		}
-	}
-}
diff --git a/reference/regexp_test.go b/reference/regexp_test.go
deleted file mode 100644
index ca4680d3..00000000
--- a/reference/regexp_test.go
+++ /dev/null
@@ -1,589 +0,0 @@
-package reference
-
-import (
-	"regexp"
-	"strings"
-	"testing"
-)
-
-type regexpMatch struct {
-	input string
-	match bool
-	subs  []string
-}
-
-func checkRegexp(t *testing.T, r *regexp.Regexp, m regexpMatch) {
-	t.Helper()
-	matches := r.FindStringSubmatch(m.input)
-	if m.match && matches != nil {
-		if len(matches) != (r.NumSubexp()+1) || matches[0] != m.input {
-			t.Fatalf("Bad match result %#v for %q", matches, m.input)
-		}
-		if len(matches) < (len(m.subs) + 1) {
-			t.Errorf("Expected %d sub matches, only have %d for %q", len(m.subs), len(matches)-1, m.input)
-		}
-		for i := range m.subs {
-			if m.subs[i] != matches[i+1] {
-				t.Errorf("Unexpected submatch %d: %q, expected %q for %q", i+1, matches[i+1], m.subs[i], m.input)
-			}
-		}
-	} else if m.match {
-		t.Errorf("Expected match for %q", m.input)
-	} else if matches != nil {
-		t.Errorf("Unexpected match for %q", m.input)
-	}
-}
-
-func TestDomainRegexp(t *testing.T) {
-	t.Parallel()
-	tests := []struct {
-		input string
-		match bool
-	}{
-		{
-			input: "test.com",
-			match: true,
-		},
-		{
-			input: "test.com:10304",
-			match: true,
-		},
-		{
-			input: "test.com:http",
-			match: false,
-		},
-		{
-			input: "localhost",
-			match: true,
-		},
-		{
-			input: "localhost:8080",
-			match: true,
-		},
-		{
-			input: "a",
-			match: true,
-		},
-		{
-			input: "a.b",
-			match: true,
-		},
-		{
-			input: "ab.cd.com",
-			match: true,
-		},
-		{
-			input: "a-b.com",
-			match: true,
-		},
-		{
-			input: "-ab.com",
-			match: false,
-		},
-		{
-			input: "ab-.com",
-			match: false,
-		},
-		{
-			input: "ab.c-om",
-			match: true,
-		},
-		{
-			input: "ab.-com",
-			match: false,
-		},
-		{
-			input: "ab.com-",
-			match: false,
-		},
-		{
-			input: "0101.com",
-			match: true, // TODO(dmcgowan): valid if this should be allowed
-		},
-		{
-			input: "001a.com",
-			match: true,
-		},
-		{
-			input: "b.gbc.io:443",
-			match: true,
-		},
-		{
-			input: "b.gbc.io",
-			match: true,
-		},
-		{
-			input: "xn--n3h.com", // ☃.com in punycode
-			match: true,
-		},
-		{
-			input: "Asdf.com", // uppercase character
-			match: true,
-		},
-		{
-			input: "192.168.1.1:75050", // ipv4
-			match: true,
-		},
-		{
-			input: "192.168.1.1:750050", // port with more than 5 digits, it will fail on validation
-			match: true,
-		},
-		{
-			input: "[fd00:1:2::3]:75050", // ipv6 compressed
-			match: true,
-		},
-		{
-			input: "[fd00:1:2::3]75050", // ipv6 wrong port separator
-			match: false,
-		},
-		{
-			input: "[fd00:1:2::3]::75050", // ipv6 wrong port separator
-			match: false,
-		},
-		{
-			input: "[fd00:1:2::3%eth0]:75050", // ipv6 with zone
-			match: false,
-		},
-		{
-			input: "[fd00123123123]:75050", // ipv6 wrong format, will fail in validation
-			match: true,
-		},
-		{
-			input: "[2001:0db8:85a3:0000:0000:8a2e:0370:7334]:75050", // ipv6 long format
-			match: true,
-		},
-		{
-			input: "[2001:0db8:85a3:0000:0000:8a2e:0370:7334]:750505", // ipv6 long format and invalid port, it will fail in validation
-			match: true,
-		},
-		{
-			input: "fd00:1:2::3:75050", // bad ipv6 without square brackets
-			match: false,
-		},
-	}
-	r := regexp.MustCompile(`^` + DomainRegexp.String() + `$`)
-	for _, tc := range tests {
-		tc := tc
-		t.Run(tc.input, func(t *testing.T) {
-			t.Parallel()
-			match := r.MatchString(tc.input)
-			if match != tc.match {
-				t.Errorf("Expected match=%t, got %t", tc.match, match)
-			}
-		})
-	}
-}
-
-func TestFullNameRegexp(t *testing.T) {
-	t.Parallel()
-	if anchoredNameRegexp.NumSubexp() != 2 {
-		t.Fatalf("anchored name regexp should have two submatches: %v, %v != 2",
-			anchoredNameRegexp, anchoredNameRegexp.NumSubexp())
-	}
-
-	tests := []regexpMatch{
-		{
-			input: "",
-			match: false,
-		},
-		{
-			input: "short",
-			match: true,
-			subs:  []string{"", "short"},
-		},
-		{
-			input: "simple/name",
-			match: true,
-			subs:  []string{"simple", "name"},
-		},
-		{
-			input: "library/ubuntu",
-			match: true,
-			subs:  []string{"library", "ubuntu"},
-		},
-		{
-			input: "docker/stevvooe/app",
-			match: true,
-			subs:  []string{"docker", "stevvooe/app"},
-		},
-		{
-			input: "aa/aa/aa/aa/aa/aa/aa/aa/aa/bb/bb/bb/bb/bb/bb",
-			match: true,
-			subs:  []string{"aa", "aa/aa/aa/aa/aa/aa/aa/aa/bb/bb/bb/bb/bb/bb"},
-		},
-		{
-			input: "aa/aa/bb/bb/bb",
-			match: true,
-			subs:  []string{"aa", "aa/bb/bb/bb"},
-		},
-		{
-			input: "a/a/a/a",
-			match: true,
-			subs:  []string{"a", "a/a/a"},
-		},
-		{
-			input: "a/a/a/a/",
-			match: false,
-		},
-		{
-			input: "a//a/a",
-			match: false,
-		},
-		{
-			input: "a",
-			match: true,
-			subs:  []string{"", "a"},
-		},
-		{
-			input: "a/aa",
-			match: true,
-			subs:  []string{"a", "aa"},
-		},
-		{
-			input: "a/aa/a",
-			match: true,
-			subs:  []string{"a", "aa/a"},
-		},
-		{
-			input: "foo.com",
-			match: true,
-			subs:  []string{"", "foo.com"},
-		},
-		{
-			input: "foo.com/",
-			match: false,
-		},
-		{
-			input: "foo.com:8080/bar",
-			match: true,
-			subs:  []string{"foo.com:8080", "bar"},
-		},
-		{
-			input: "foo.com:http/bar",
-			match: false,
-		},
-		{
-			input: "foo.com/bar",
-			match: true,
-			subs:  []string{"foo.com", "bar"},
-		},
-		{
-			input: "foo.com/bar/baz",
-			match: true,
-			subs:  []string{"foo.com", "bar/baz"},
-		},
-		{
-			input: "localhost:8080/bar",
-			match: true,
-			subs:  []string{"localhost:8080", "bar"},
-		},
-		{
-			input: "sub-dom1.foo.com/bar/baz/quux",
-			match: true,
-			subs:  []string{"sub-dom1.foo.com", "bar/baz/quux"},
-		},
-		{
-			input: "blog.foo.com/bar/baz",
-			match: true,
-			subs:  []string{"blog.foo.com", "bar/baz"},
-		},
-		{
-			input: "a^a",
-			match: false,
-		},
-		{
-			input: "aa/asdf$$^/aa",
-			match: false,
-		},
-		{
-			input: "asdf$$^/aa",
-			match: false,
-		},
-		{
-			input: "aa-a/a",
-			match: true,
-			subs:  []string{"aa-a", "a"},
-		},
-		{
-			input: strings.Repeat("a/", 128) + "a",
-			match: true,
-			subs:  []string{"a", strings.Repeat("a/", 127) + "a"},
-		},
-		{
-			input: "a-/a/a/a",
-			match: false,
-		},
-		{
-			input: "foo.com/a-/a/a",
-			match: false,
-		},
-		{
-			input: "-foo/bar",
-			match: false,
-		},
-		{
-			input: "foo/bar-",
-			match: false,
-		},
-		{
-			input: "foo-/bar",
-			match: false,
-		},
-		{
-			input: "foo/-bar",
-			match: false,
-		},
-		{
-			input: "_foo/bar",
-			match: false,
-		},
-		{
-			input: "foo_bar",
-			match: true,
-			subs:  []string{"", "foo_bar"},
-		},
-		{
-			input: "foo_bar.com",
-			match: true,
-			subs:  []string{"", "foo_bar.com"},
-		},
-		{
-			input: "foo_bar.com:8080",
-			match: false,
-		},
-		{
-			input: "foo_bar.com:8080/app",
-			match: false,
-		},
-		{
-			input: "foo.com/foo_bar",
-			match: true,
-			subs:  []string{"foo.com", "foo_bar"},
-		},
-		{
-			input: "____/____",
-			match: false,
-		},
-		{
-			input: "_docker/_docker",
-			match: false,
-		},
-		{
-			input: "docker_/docker_",
-			match: false,
-		},
-		{
-			input: "b.gcr.io/test.example.com/my-app",
-			match: true,
-			subs:  []string{"b.gcr.io", "test.example.com/my-app"},
-		},
-		{
-			input: "xn--n3h.com/myimage", // ☃.com in punycode
-			match: true,
-			subs:  []string{"xn--n3h.com", "myimage"},
-		},
-		{
-			input: "xn--7o8h.com/myimage", // 🐳.com in punycode
-			match: true,
-			subs:  []string{"xn--7o8h.com", "myimage"},
-		},
-		{
-			input: "example.com/xn--7o8h.com/myimage", // 🐳.com in punycode
-			match: true,
-			subs:  []string{"example.com", "xn--7o8h.com/myimage"},
-		},
-		{
-			input: "example.com/some_separator__underscore/myimage",
-			match: true,
-			subs:  []string{"example.com", "some_separator__underscore/myimage"},
-		},
-		{
-			input: "example.com/__underscore/myimage",
-			match: false,
-		},
-		{
-			input: "example.com/..dots/myimage",
-			match: false,
-		},
-		{
-			input: "example.com/.dots/myimage",
-			match: false,
-		},
-		{
-			input: "example.com/nodouble..dots/myimage",
-			match: false,
-		},
-		{
-			input: "example.com/nodouble..dots/myimage",
-			match: false,
-		},
-		{
-			input: "docker./docker",
-			match: false,
-		},
-		{
-			input: ".docker/docker",
-			match: false,
-		},
-		{
-			input: "docker-/docker",
-			match: false,
-		},
-		{
-			input: "-docker/docker",
-			match: false,
-		},
-		{
-			input: "do..cker/docker",
-			match: false,
-		},
-		{
-			input: "do__cker:8080/docker",
-			match: false,
-		},
-		{
-			input: "do__cker/docker",
-			match: true,
-			subs:  []string{"", "do__cker/docker"},
-		},
-		{
-			input: "b.gcr.io/test.example.com/my-app",
-			match: true,
-			subs:  []string{"b.gcr.io", "test.example.com/my-app"},
-		},
-		{
-			input: "registry.io/foo/project--id.module--name.ver---sion--name",
-			match: true,
-			subs:  []string{"registry.io", "foo/project--id.module--name.ver---sion--name"},
-		},
-		{
-			input: "Asdf.com/foo/bar", // uppercase character in hostname
-			match: true,
-		},
-		{
-			input: "Foo/FarB", // uppercase characters in remote name
-			match: false,
-		},
-	}
-	for _, tc := range tests {
-		tc := tc
-		t.Run(tc.input, func(t *testing.T) {
-			t.Parallel()
-			checkRegexp(t, anchoredNameRegexp, tc)
-		})
-	}
-}
-
-func TestReferenceRegexp(t *testing.T) {
-	t.Parallel()
-	if ReferenceRegexp.NumSubexp() != 3 {
-		t.Fatalf("anchored name regexp should have three submatches: %v, %v != 3",
-			ReferenceRegexp, ReferenceRegexp.NumSubexp())
-	}
-
-	tests := []regexpMatch{
-		{
-			input: "registry.com:8080/myapp:tag",
-			match: true,
-			subs:  []string{"registry.com:8080/myapp", "tag", ""},
-		},
-		{
-			input: "registry.com:8080/myapp@sha256:be178c0543eb17f5f3043021c9e5fcf30285e557a4fc309cce97ff9ca6182912",
-			match: true,
-			subs:  []string{"registry.com:8080/myapp", "", "sha256:be178c0543eb17f5f3043021c9e5fcf30285e557a4fc309cce97ff9ca6182912"},
-		},
-		{
-			input: "registry.com:8080/myapp:tag2@sha256:be178c0543eb17f5f3043021c9e5fcf30285e557a4fc309cce97ff9ca6182912",
-			match: true,
-			subs:  []string{"registry.com:8080/myapp", "tag2", "sha256:be178c0543eb17f5f3043021c9e5fcf30285e557a4fc309cce97ff9ca6182912"},
-		},
-		{
-			input: "registry.com:8080/myapp@sha256:badbadbadbad",
-			match: false,
-		},
-		{
-			input: "registry.com:8080/myapp:invalid~tag",
-			match: false,
-		},
-		{
-			input: "bad_hostname.com:8080/myapp:tag",
-			match: false,
-		},
-		{
-			input:// localhost treated as name, missing tag with 8080 as tag
-			"localhost:8080@sha256:be178c0543eb17f5f3043021c9e5fcf30285e557a4fc309cce97ff9ca6182912",
-			match: true,
-			subs:  []string{"localhost", "8080", "sha256:be178c0543eb17f5f3043021c9e5fcf30285e557a4fc309cce97ff9ca6182912"},
-		},
-		{
-			input: "localhost:8080/name@sha256:be178c0543eb17f5f3043021c9e5fcf30285e557a4fc309cce97ff9ca6182912",
-			match: true,
-			subs:  []string{"localhost:8080/name", "", "sha256:be178c0543eb17f5f3043021c9e5fcf30285e557a4fc309cce97ff9ca6182912"},
-		},
-		{
-			input: "localhost:http/name@sha256:be178c0543eb17f5f3043021c9e5fcf30285e557a4fc309cce97ff9ca6182912",
-			match: false,
-		},
-		{
-			// localhost will be treated as an image name without a host
-			input: "localhost@sha256:be178c0543eb17f5f3043021c9e5fcf30285e557a4fc309cce97ff9ca6182912",
-			match: true,
-			subs:  []string{"localhost", "", "sha256:be178c0543eb17f5f3043021c9e5fcf30285e557a4fc309cce97ff9ca6182912"},
-		},
-		{
-			input: "registry.com:8080/myapp@bad",
-			match: false,
-		},
-		{
-			input: "registry.com:8080/myapp@2bad",
-			match: false, // TODO(dmcgowan): Support this as valid
-		},
-	}
-
-	for _, tc := range tests {
-		tc := tc
-		t.Run(tc.input, func(t *testing.T) {
-			t.Parallel()
-			checkRegexp(t, ReferenceRegexp, tc)
-		})
-	}
-}
-
-func TestIdentifierRegexp(t *testing.T) {
-	t.Parallel()
-	tests := []struct {
-		input string
-		match bool
-	}{
-		{
-			input: "da304e823d8ca2b9d863a3c897baeb852ba21ea9a9f1414736394ae7fcaf9821",
-			match: true,
-		},
-		{
-			input: "7EC43B381E5AEFE6E04EFB0B3F0693FF2A4A50652D64AEC573905F2DB5889A1C",
-			match: false,
-		},
-		{
-			input: "da304e823d8ca2b9d863a3c897baeb852ba21ea9a9f1414736394ae7fcaf",
-			match: false,
-		},
-		{
-			input: "sha256:da304e823d8ca2b9d863a3c897baeb852ba21ea9a9f1414736394ae7fcaf9821",
-			match: false,
-		},
-		{
-			input: "da304e823d8ca2b9d863a3c897baeb852ba21ea9a9f1414736394ae7fcaf98218482",
-			match: false,
-		},
-	}
-	for _, tc := range tests {
-		tc := tc
-		t.Run(tc.input, func(t *testing.T) {
-			t.Parallel()
-			match := anchoredIdentifierRegexp.MatchString(tc.input)
-			if match != tc.match {
-				t.Errorf("Expected match=%t, got %t", tc.match, match)
-			}
-		})
-	}
-}
diff --git a/reference/sort_test.go b/reference/sort_test.go
deleted file mode 100644
index be072904..00000000
--- a/reference/sort_test.go
+++ /dev/null
@@ -1,84 +0,0 @@
-/*
-   Copyright The containerd Authors.
-
-   Licensed under the Apache License, Version 2.0 (the "License");
-   you may not use this file except in compliance with the License.
-   You may obtain a copy of the License at
-
-       http://www.apache.org/licenses/LICENSE-2.0
-
-   Unless required by applicable law or agreed to in writing, software
-   distributed under the License is distributed on an "AS IS" BASIS,
-   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-   See the License for the specific language governing permissions and
-   limitations under the License.
-*/
-
-package reference
-
-import (
-	"io"
-	"math/rand"
-	"testing"
-
-	"github.com/opencontainers/go-digest"
-)
-
-func TestReferenceSorting(t *testing.T) {
-	t.Parallel()
-	digested := func(seed int64) string {
-		b, err := io.ReadAll(io.LimitReader(rand.New(rand.NewSource(seed)), 64))
-		if err != nil {
-			panic(err)
-		}
-		return digest.FromBytes(b).String()
-	}
-	// Add z. prefix to string sort after "sha256:"
-	r1 := func(name, tag string, seed int64) string {
-		return "z.containerd.io/" + name + ":" + tag + "@" + digested(seed)
-	}
-	r2 := func(name, tag string) string {
-		return "z.containerd.io/" + name + ":" + tag
-	}
-	r3 := func(name string, seed int64) string {
-		return "z.containerd.io/" + name + "@" + digested(seed)
-	}
-
-	for i, tc := range []struct {
-		unsorted []string
-		expected []string
-	}{
-		{
-			unsorted: []string{r2("name", "latest"), r3("name", 1), r1("name", "latest", 1)},
-			expected: []string{r1("name", "latest", 1), r2("name", "latest"), r3("name", 1)},
-		},
-		{
-			unsorted: []string{"can't parse this:latest", r3("name", 1), r2("name", "latest")},
-			expected: []string{r2("name", "latest"), r3("name", 1), "can't parse this:latest"},
-		},
-		{
-			unsorted: []string{digested(1), r3("name", 1), r2("name", "latest")},
-			expected: []string{r2("name", "latest"), r3("name", 1), digested(1)},
-		},
-		{
-			unsorted: []string{r2("name", "tag2"), r2("name", "tag3"), r2("name", "tag1")},
-			expected: []string{r2("name", "tag1"), r2("name", "tag2"), r2("name", "tag3")},
-		},
-		{
-			unsorted: []string{r2("name-2", "tag"), r2("name-3", "tag"), r2("name-1", "tag")},
-			expected: []string{r2("name-1", "tag"), r2("name-2", "tag"), r2("name-3", "tag")},
-		},
-	} {
-		sorted := Sort(tc.unsorted)
-		if len(sorted) != len(tc.expected) {
-			t.Errorf("[%d]: Mismatched sized, got %d, expected %d", i, len(sorted), len(tc.expected))
-			continue
-		}
-		for j := range sorted {
-			if sorted[j] != tc.expected[j] {
-				t.Errorf("[%d]: Wrong value at %d, got %q, expected %q", i, j, sorted[j], tc.expected[j])
-				break
-			}
-		}
-	}
-}
diff --git a/registry/api/v2/urls_test.go b/registry/api/v2/urls_test.go
index 0ccf3518..ba908531 100644
--- a/registry/api/v2/urls_test.go
+++ b/registry/api/v2/urls_test.go
@@ -7,7 +7,7 @@ import (
 	"reflect"
 	"testing"
 
-	"github.com/distribution/distribution/v3/reference"
+	"github.com/distribution/reference"
 )
 
 type urlBuilderTestCase struct {
diff --git a/registry/client/repository_test.go b/registry/client/repository_test.go
index 56681c0f..4cda067a 100644
--- a/registry/client/repository_test.go
+++ b/registry/client/repository_test.go
@@ -20,11 +20,11 @@ import (
 	"github.com/distribution/distribution/v3/context"
 	"github.com/distribution/distribution/v3/manifest"
 	"github.com/distribution/distribution/v3/manifest/ocischema"
-	"github.com/distribution/distribution/v3/reference"
 	"github.com/distribution/distribution/v3/registry/api/errcode"
 	v2 "github.com/distribution/distribution/v3/registry/api/v2"
 	"github.com/distribution/distribution/v3/testutil"
 	"github.com/distribution/distribution/v3/uuid"
+	"github.com/distribution/reference"
 	"github.com/opencontainers/go-digest"
 	v1 "github.com/opencontainers/image-spec/specs-go/v1"
 )
diff --git a/registry/handlers/api_test.go b/registry/handlers/api_test.go
index 2be94f51..c921ce9a 100644
--- a/registry/handlers/api_test.go
+++ b/registry/handlers/api_test.go
@@ -24,13 +24,13 @@ import (
 	"github.com/distribution/distribution/v3/manifest"
 	"github.com/distribution/distribution/v3/manifest/manifestlist"
 	"github.com/distribution/distribution/v3/manifest/schema2"
-	"github.com/distribution/distribution/v3/reference"
 	"github.com/distribution/distribution/v3/registry/api/errcode"
 	v2 "github.com/distribution/distribution/v3/registry/api/v2"
 	storagedriver "github.com/distribution/distribution/v3/registry/storage/driver"
 	"github.com/distribution/distribution/v3/registry/storage/driver/factory"
 	_ "github.com/distribution/distribution/v3/registry/storage/driver/inmemory"
 	"github.com/distribution/distribution/v3/testutil"
+	"github.com/distribution/reference"
 	"github.com/docker/libtrust"
 	"github.com/gorilla/handlers"
 	"github.com/opencontainers/go-digest"
diff --git a/registry/proxy/proxyblobstore_test.go b/registry/proxy/proxyblobstore_test.go
index d90d2384..60297b36 100644
--- a/registry/proxy/proxyblobstore_test.go
+++ b/registry/proxy/proxyblobstore_test.go
@@ -11,12 +11,12 @@ import (
 	"time"
 
 	"github.com/distribution/distribution/v3"
-	"github.com/distribution/distribution/v3/reference"
 	"github.com/distribution/distribution/v3/registry/proxy/scheduler"
 	"github.com/distribution/distribution/v3/registry/storage"
 	"github.com/distribution/distribution/v3/registry/storage/cache/memory"
 	"github.com/distribution/distribution/v3/registry/storage/driver/filesystem"
 	"github.com/distribution/distribution/v3/registry/storage/driver/inmemory"
+	"github.com/distribution/reference"
 	"github.com/opencontainers/go-digest"
 )
 
diff --git a/registry/proxy/proxymanifeststore_test.go b/registry/proxy/proxymanifeststore_test.go
index bd1c7ad9..2ac325d4 100644
--- a/registry/proxy/proxymanifeststore_test.go
+++ b/registry/proxy/proxymanifeststore_test.go
@@ -8,7 +8,6 @@ import (
 
 	"github.com/distribution/distribution/v3"
 	"github.com/distribution/distribution/v3/manifest/schema2"
-	"github.com/distribution/distribution/v3/reference"
 	"github.com/distribution/distribution/v3/registry/client/auth"
 	"github.com/distribution/distribution/v3/registry/client/auth/challenge"
 	"github.com/distribution/distribution/v3/registry/proxy/scheduler"
@@ -16,6 +15,7 @@ import (
 	"github.com/distribution/distribution/v3/registry/storage/cache/memory"
 	"github.com/distribution/distribution/v3/registry/storage/driver/inmemory"
 	"github.com/distribution/distribution/v3/testutil"
+	"github.com/distribution/reference"
 	"github.com/opencontainers/go-digest"
 )
 
diff --git a/registry/proxy/scheduler/scheduler_test.go b/registry/proxy/scheduler/scheduler_test.go
index c1324ff8..1309a1b0 100644
--- a/registry/proxy/scheduler/scheduler_test.go
+++ b/registry/proxy/scheduler/scheduler_test.go
@@ -7,8 +7,8 @@ import (
 	"time"
 
 	"github.com/distribution/distribution/v3/context"
-	"github.com/distribution/distribution/v3/reference"
 	"github.com/distribution/distribution/v3/registry/storage/driver/inmemory"
+	"github.com/distribution/reference"
 )
 
 func testRefs(t *testing.T) (reference.Reference, reference.Reference, reference.Reference) {
diff --git a/registry/storage/blob_test.go b/registry/storage/blob_test.go
index 01158255..f3cc1179 100644
--- a/registry/storage/blob_test.go
+++ b/registry/storage/blob_test.go
@@ -11,10 +11,10 @@ import (
 	"testing"
 
 	"github.com/distribution/distribution/v3"
-	"github.com/distribution/distribution/v3/reference"
 	"github.com/distribution/distribution/v3/registry/storage/cache/memory"
 	"github.com/distribution/distribution/v3/registry/storage/driver/inmemory"
 	"github.com/distribution/distribution/v3/testutil"
+	"github.com/distribution/reference"
 	"github.com/opencontainers/go-digest"
 )
 
diff --git a/registry/storage/catalog_test.go b/registry/storage/catalog_test.go
index 965aa8ab..2249265e 100644
--- a/registry/storage/catalog_test.go
+++ b/registry/storage/catalog_test.go
@@ -8,11 +8,11 @@ import (
 	"testing"
 
 	"github.com/distribution/distribution/v3"
-	"github.com/distribution/distribution/v3/reference"
 	"github.com/distribution/distribution/v3/registry/storage/cache/memory"
 	"github.com/distribution/distribution/v3/registry/storage/driver"
 	"github.com/distribution/distribution/v3/registry/storage/driver/inmemory"
 	"github.com/distribution/distribution/v3/testutil"
+	"github.com/distribution/reference"
 	"github.com/opencontainers/go-digest"
 )
 
@@ -151,11 +151,7 @@ func TestCatalogInParts(t *testing.T) {
 	lastRepo = p[len(p)-1]
 	numFilled, err = env.registry.Repositories(env.ctx, p, lastRepo)
 
-	if numFilled != len(p) {
-		t.Errorf("Expected more values in catalog")
-	}
-
-	if err != io.EOF {
+	if err != io.EOF || numFilled != len(p) {
 		t.Errorf("Expected end of catalog")
 	}
 
diff --git a/registry/storage/driver/s3-aws/s3_test.go b/registry/storage/driver/s3-aws/s3_test.go
index 9f39975b..060ed2d8 100644
--- a/registry/storage/driver/s3-aws/s3_test.go
+++ b/registry/storage/driver/s3-aws/s3_test.go
@@ -491,7 +491,6 @@ func TestWalk(t *testing.T) {
 
 	fileset := []string{
 		"/file1",
-		"/folder1-suffix/file1",
 		"/folder1/file1",
 		"/folder2/file1",
 		"/folder3/subfolder1/subfolder1/file1",
@@ -525,23 +524,18 @@ func TestWalk(t *testing.T) {
 		}
 	}()
 
-	noopFn := func(fileInfo storagedriver.FileInfo) error { return nil }
-
 	tcs := []struct {
 		name     string
 		fn       storagedriver.WalkFn
 		from     string
-		options  []func(*storagedriver.WalkOptions)
 		expected []string
 		err      bool
 	}{
 		{
 			name: "walk all",
-			fn:   noopFn,
+			fn:   func(fileInfo storagedriver.FileInfo) error { return nil },
 			expected: []string{
 				"/file1",
-				"/folder1-suffix",
-				"/folder1-suffix/file1",
 				"/folder1",
 				"/folder1/file1",
 				"/folder2",
@@ -570,8 +564,6 @@ func TestWalk(t *testing.T) {
 			},
 			expected: []string{
 				"/file1",
-				"/folder1-suffix",
-				"/folder1-suffix/file1",
 				"/folder1",
 				"/folder1/file1",
 				"/folder2",
@@ -582,101 +574,22 @@ func TestWalk(t *testing.T) {
 				"/folder4/file1",
 			},
 		},
-		{
-			name: "start late without from",
-			fn:   noopFn,
-			options: []func(*storagedriver.WalkOptions){
-				storagedriver.WithStartAfterHint("/folder3/subfolder1/subfolder1/file1"),
-			},
-			expected: []string{
-				// start late
-				"/folder3",
-				"/folder3/subfolder2",
-				"/folder3/subfolder2/subfolder1",
-				"/folder3/subfolder2/subfolder1/file1",
-				"/folder4",
-				"/folder4/file1",
-			},
-			err: false,
-		},
-		{
-			name: "start late with from",
-			fn:   noopFn,
-			from: "/folder3",
-			options: []func(*storagedriver.WalkOptions){
-				storagedriver.WithStartAfterHint("/folder3/subfolder1/subfolder1/file1"),
-			},
-			expected: []string{
-				// start late
-				"/folder3/subfolder2",
-				"/folder3/subfolder2/subfolder1",
-				"/folder3/subfolder2/subfolder1/file1",
-			},
-			err: false,
-		},
-		{
-			name: "start after from",
-			fn:   noopFn,
-			from: "/folder1",
-			options: []func(*storagedriver.WalkOptions){
-				storagedriver.WithStartAfterHint("/folder2"),
-			},
-			expected: []string{},
-			err:      false,
-		},
-		{
-			name: "start matches from",
-			fn:   noopFn,
-			from: "/folder3",
-			options: []func(*storagedriver.WalkOptions){
-				storagedriver.WithStartAfterHint("/folder3"),
-			},
-			expected: []string{
-				"/folder3/subfolder1",
-				"/folder3/subfolder1/subfolder1",
-				"/folder3/subfolder1/subfolder1/file1",
-				"/folder3/subfolder2",
-				"/folder3/subfolder2/subfolder1",
-				"/folder3/subfolder2/subfolder1/file1",
-			},
-			err: false,
-		},
-		{
-			name: "start doesn't exist",
-			fn:   noopFn,
-			from: "/folder3",
-			options: []func(*storagedriver.WalkOptions){
-				storagedriver.WithStartAfterHint("/folder3/notafolder/notafile"),
-			},
-			expected: []string{
-				"/folder3/subfolder1",
-				"/folder3/subfolder1/subfolder1",
-				"/folder3/subfolder1/subfolder1/file1",
-				"/folder3/subfolder2",
-				"/folder3/subfolder2/subfolder1",
-				"/folder3/subfolder2/subfolder1/file1",
-			},
-			err: false,
-		},
 		{
 			name: "stop early",
 			fn: func(fileInfo storagedriver.FileInfo) error {
 				if fileInfo.Path() == "/folder1/file1" {
-					return storagedriver.ErrFilledBuffer
+					return storagedriver.ErrSkipDir
 				}
 				return nil
 			},
 			expected: []string{
 				"/file1",
-				"/folder1-suffix",
-				"/folder1-suffix/file1",
 				"/folder1",
 				"/folder1/file1",
 				// stop early
 			},
 			err: false,
 		},
-
 		{
 			name: "error",
 			fn: func(fileInfo storagedriver.FileInfo) error {
@@ -689,7 +602,7 @@ func TestWalk(t *testing.T) {
 		},
 		{
 			name: "from folder",
-			fn:   noopFn,
+			fn:   func(fileInfo storagedriver.FileInfo) error { return nil },
 			expected: []string{
 				"/folder1/file1",
 			},
@@ -706,7 +619,7 @@ func TestWalk(t *testing.T) {
 			err := drvr.Walk(context.Background(), tc.from, func(fileInfo storagedriver.FileInfo) error {
 				walked = append(walked, fileInfo.Path())
 				return tc.fn(fileInfo)
-			}, tc.options...)
+			})
 			if tc.err && err == nil {
 				t.Fatalf("expected err")
 			}
diff --git a/registry/storage/driver/walk_test.go b/registry/storage/driver/walk_test.go
index 28f12b61..5e7fdf77 100644
--- a/registry/storage/driver/walk_test.go
+++ b/registry/storage/driver/walk_test.go
@@ -2,8 +2,8 @@ package driver
 
 import (
 	"context"
-	"errors"
 	"fmt"
+	"strings"
 	"testing"
 )
 
@@ -36,12 +36,7 @@ type fileSystem struct {
 }
 
 func (cfs *fileSystem) List(_ context.Context, path string) ([]string, error) {
-	children := cfs.fileset[path]
-	if children == nil {
-		return nil, PathNotFoundError{Path: path}
-	} else {
-		return children, nil
-	}
+	return cfs.fileset[path], nil
 }
 
 func (cfs *fileSystem) Stat(_ context.Context, path string) (FileInfo, error) {
@@ -83,16 +78,9 @@ func TestWalkFileRemoved(t *testing.T) {
 func TestWalkFallback(t *testing.T) {
 	d := &fileSystem{
 		fileset: map[string][]string{
-			"/":                              {"/file1", "/folder1", "/folder1-suffix", "/folder2", "/folder3", "/folder4"},
-			"/folder1":                       {"/folder1/file1"},
-			"/folder1-suffix":                {"/folder1-suffix/file1"}, // importantly, - is before / in the ascii table
-			"/folder2":                       {"/folder2/file1"},
-			"/folder3":                       {"/folder3/subfolder1", "/folder3/subfolder2"},
-			"/folder3/subfolder1":            {"/folder3/subfolder1/subfolder1"},
-			"/folder3/subfolder1/subfolder1": {"/folder3/subfolder1/subfolder1/file1"},
-			"/folder3/subfolder2":            {"/folder3/subfolder2/subfolder1"},
-			"/folder3/subfolder2/subfolder1": {"/folder3/subfolder2/subfolder1/file1"},
-			"/folder4":                       {"/folder4/file1"},
+			"/":        {"/file1", "/folder1", "/folder2"},
+			"/folder1": {"/folder1/file1"},
+			"/folder2": {"/folder2/file1"},
 		},
 	}
 	noopFn := func(fileInfo FileInfo) error { return nil }
@@ -101,7 +89,6 @@ func TestWalkFallback(t *testing.T) {
 		name     string
 		fn       WalkFn
 		from     string
-		options  []func(*WalkOptions)
 		expected []string
 		err      bool
 	}{
@@ -112,19 +99,8 @@ func TestWalkFallback(t *testing.T) {
 				"/file1",
 				"/folder1",
 				"/folder1/file1",
-				"/folder1-suffix",
-				"/folder1-suffix/file1",
 				"/folder2",
 				"/folder2/file1",
-				"/folder3",
-				"/folder3/subfolder1",
-				"/folder3/subfolder1/subfolder1",
-				"/folder3/subfolder1/subfolder1/file1",
-				"/folder3/subfolder2",
-				"/folder3/subfolder2/subfolder1",
-				"/folder3/subfolder2/subfolder1/file1",
-				"/folder4",
-				"/folder4/file1",
 			},
 		},
 		{
@@ -133,7 +109,7 @@ func TestWalkFallback(t *testing.T) {
 				if fileInfo.Path() == "/folder1" {
 					return ErrSkipDir
 				}
-				if fileInfo.Path() == "/folder1" {
+				if strings.Contains(fileInfo.Path(), "/folder1") {
 					t.Fatalf("skipped dir %s and should not walk %s", "/folder1", fileInfo.Path())
 				}
 				return nil
@@ -142,124 +118,15 @@ func TestWalkFallback(t *testing.T) {
 				"/file1",
 				"/folder1", // return ErrSkipDir, skip anything under /folder1
 				// skip /folder1/file1
-				"/folder1-suffix",
-				"/folder1-suffix/file1",
-				"/folder2",
-				"/folder2/file1",
-				"/folder3",
-				"/folder3/subfolder1",
-				"/folder3/subfolder1/subfolder1",
-				"/folder3/subfolder1/subfolder1/file1",
-				"/folder3/subfolder2",
-				"/folder3/subfolder2/subfolder1",
-				"/folder3/subfolder2/subfolder1/file1",
-				"/folder4",
-				"/folder4/file1",
-			},
-		},
-		{
-			name: "start late expecting -suffix",
-			fn:   noopFn,
-			options: []func(*WalkOptions){
-				WithStartAfterHint("/folder1/file1"),
-			},
-			expected: []string{
-				"/folder1-suffix",
-				"/folder1-suffix/file1",
 				"/folder2",
 				"/folder2/file1",
-				"/folder3",
-				"/folder3/subfolder1",
-				"/folder3/subfolder1/subfolder1",
-				"/folder3/subfolder1/subfolder1/file1",
-				"/folder3/subfolder2",
-				"/folder3/subfolder2/subfolder1",
-				"/folder3/subfolder2/subfolder1/file1",
-				"/folder4",
-				"/folder4/file1",
-			},
-			err: false,
-		},
-		{
-			name: "start late without from",
-			fn:   noopFn,
-			options: []func(*WalkOptions){
-				WithStartAfterHint("/folder3/subfolder1/subfolder1/file1"),
-			},
-			expected: []string{
-				// start late
-				"/folder3/subfolder2",
-				"/folder3/subfolder2/subfolder1",
-				"/folder3/subfolder2/subfolder1/file1",
-				"/folder4",
-				"/folder4/file1",
-			},
-			err: false,
-		},
-		{
-			name: "start late with from",
-			fn:   noopFn,
-			from: "/folder3",
-			options: []func(*WalkOptions){
-				WithStartAfterHint("/folder3/subfolder1/subfolder1/file1"),
-			},
-			expected: []string{
-				// start late
-				"/folder3/subfolder2",
-				"/folder3/subfolder2/subfolder1",
-				"/folder3/subfolder2/subfolder1/file1",
 			},
-			err: false,
-		},
-		{
-			name: "start after from",
-			fn:   noopFn,
-			from: "/folder1",
-			options: []func(*WalkOptions){
-				WithStartAfterHint("/folder1-suffix"),
-			},
-			expected: []string{},
-			err:      false,
-		},
-		{
-			name: "start matches from",
-			fn:   noopFn,
-			from: "/folder3",
-			options: []func(*WalkOptions){
-				WithStartAfterHint("/folder3"),
-			},
-			expected: []string{
-				"/folder3/subfolder1",
-				"/folder3/subfolder1/subfolder1",
-				"/folder3/subfolder1/subfolder1/file1",
-				"/folder3/subfolder2",
-				"/folder3/subfolder2/subfolder1",
-				"/folder3/subfolder2/subfolder1/file1",
-			},
-			err: false,
-		},
-		{
-			name: "start doesn't exist",
-			fn:   noopFn,
-			from: "/folder3",
-			options: []func(*WalkOptions){
-				WithStartAfterHint("/folder3/notafolder/notafile"),
-			},
-			expected: []string{
-				"/folder3/subfolder1",
-				"/folder3/subfolder1/subfolder1",
-				"/folder3/subfolder1/subfolder1/file1",
-				"/folder3/subfolder2",
-				"/folder3/subfolder2/subfolder1",
-				"/folder3/subfolder2/subfolder1/file1",
-			},
-			err: false,
 		},
 		{
 			name: "stop early",
 			fn: func(fileInfo FileInfo) error {
 				if fileInfo.Path() == "/folder1/file1" {
-					return ErrFilledBuffer
+					return ErrSkipDir
 				}
 				return nil
 			},
@@ -270,16 +137,6 @@ func TestWalkFallback(t *testing.T) {
 				// stop early
 			},
 		},
-		{
-			name: "error",
-			fn: func(fileInfo FileInfo) error {
-				return errors.New("foo")
-			},
-			expected: []string{
-				"/file1",
-			},
-			err: true,
-		},
 		{
 			name: "from folder",
 			fn:   noopFn,
@@ -302,7 +159,7 @@ func TestWalkFallback(t *testing.T) {
 					t.Fatalf("fileInfo isDir not matching file system: expected %t actual %t", d.isDir(fileInfo.Path()), fileInfo.IsDir())
 				}
 				return tc.fn(fileInfo)
-			}, tc.options...)
+			})
 			if tc.err && err == nil {
 				t.Fatalf("expected err")
 			}
@@ -320,7 +177,7 @@ func compareWalked(t *testing.T, expected, walked []string) {
 	}
 	for i := range walked {
 		if walked[i] != expected[i] {
-			t.Fatalf("expected walked to come in order expected: walked %s; expected %s;", walked, expected)
+			t.Fatalf("expected walked to come in order expected: walked %s", walked)
 		}
 	}
 }
diff --git a/registry/storage/garbagecollect_test.go b/registry/storage/garbagecollect_test.go
index 695dbe9d..edf7f1c3 100644
--- a/registry/storage/garbagecollect_test.go
+++ b/registry/storage/garbagecollect_test.go
@@ -7,10 +7,10 @@ import (
 
 	"github.com/distribution/distribution/v3"
 	"github.com/distribution/distribution/v3/context"
-	"github.com/distribution/distribution/v3/reference"
 	"github.com/distribution/distribution/v3/registry/storage/driver"
 	"github.com/distribution/distribution/v3/registry/storage/driver/inmemory"
 	"github.com/distribution/distribution/v3/testutil"
+	"github.com/distribution/reference"
 	"github.com/opencontainers/go-digest"
 )
 
diff --git a/registry/storage/linkedblobstore_test.go b/registry/storage/linkedblobstore_test.go
index 57bb8eb7..357e0ef9 100644
--- a/registry/storage/linkedblobstore_test.go
+++ b/registry/storage/linkedblobstore_test.go
@@ -10,8 +10,8 @@ import (
 	"testing"
 
 	"github.com/distribution/distribution/v3"
-	"github.com/distribution/distribution/v3/reference"
 	"github.com/distribution/distribution/v3/testutil"
+	"github.com/distribution/reference"
 	"github.com/opencontainers/go-digest"
 )
 
diff --git a/registry/storage/manifeststore_test.go b/registry/storage/manifeststore_test.go
index 3285d541..82a6c97d 100644
--- a/registry/storage/manifeststore_test.go
+++ b/registry/storage/manifeststore_test.go
@@ -12,11 +12,11 @@ import (
 	"github.com/distribution/distribution/v3/manifest"
 	"github.com/distribution/distribution/v3/manifest/ocischema"
 	"github.com/distribution/distribution/v3/manifest/schema2"
-	"github.com/distribution/distribution/v3/reference"
 	"github.com/distribution/distribution/v3/registry/storage/cache/memory"
 	"github.com/distribution/distribution/v3/registry/storage/driver"
 	"github.com/distribution/distribution/v3/registry/storage/driver/inmemory"
 	"github.com/distribution/distribution/v3/testutil"
+	"github.com/distribution/reference"
 	"github.com/opencontainers/go-digest"
 	v1 "github.com/opencontainers/image-spec/specs-go/v1"
 )
diff --git a/registry/storage/tagstore_test.go b/registry/storage/tagstore_test.go
index 650416c1..2659cfac 100644
--- a/registry/storage/tagstore_test.go
+++ b/registry/storage/tagstore_test.go
@@ -8,8 +8,8 @@ import (
 	"github.com/distribution/distribution/v3"
 	"github.com/distribution/distribution/v3/manifest"
 	"github.com/distribution/distribution/v3/manifest/schema2"
-	"github.com/distribution/distribution/v3/reference"
 	"github.com/distribution/distribution/v3/registry/storage/driver/inmemory"
+	"github.com/distribution/reference"
 	digest "github.com/opencontainers/go-digest"
 )