浏览代码

x/model: move x/build.Ref -> x/model.Path

Also, update names and comments accordingly.
Blake Mizerany 1 年之前
父节点
当前提交
58de2b8d4a

+ 38 - 44
x/build/blob/ref.go → x/model/path.go

@@ -1,4 +1,4 @@
-package blob
+package model
 
 
 import (
 import (
 	"cmp"
 	"cmp"
@@ -7,7 +7,7 @@ import (
 	"strings"
 	"strings"
 )
 )
 
 
-const MaxRefLength = 255
+const MaxPathLength = 255
 
 
 type PartKind int
 type PartKind int
 
 
@@ -30,12 +30,12 @@ var kindNames = map[PartKind]string{
 	Build:     "Build",
 	Build:     "Build",
 }
 }
 
 
-// Ref is an opaque reference to a blob.
+// Path is an opaque reference to a model.
 //
 //
 // It is comparable and can be used as a map key.
 // It is comparable and can be used as a map key.
 //
 //
-// Users or Ref must check Valid before using it.
-type Ref struct {
+// Users or Path must check Valid before using it.
+type Path struct {
 	domain    string
 	domain    string
 	namespace string
 	namespace string
 	name      string
 	name      string
@@ -46,7 +46,7 @@ type Ref struct {
 // Format returns a string representation of the ref with the given
 // Format returns a string representation of the ref with the given
 // concreteness. If a part is missing, it is replaced with a loud
 // concreteness. If a part is missing, it is replaced with a loud
 // placeholder.
 // placeholder.
-func (r Ref) Full() string {
+func (r Path) Full() string {
 	r.domain = cmp.Or(r.domain, "!(MISSING DOMAIN)")
 	r.domain = cmp.Or(r.domain, "!(MISSING DOMAIN)")
 	r.namespace = cmp.Or(r.namespace, "!(MISSING NAMESPACE)")
 	r.namespace = cmp.Or(r.namespace, "!(MISSING NAMESPACE)")
 	r.name = cmp.Or(r.name, "!(MISSING NAME)")
 	r.name = cmp.Or(r.name, "!(MISSING NAME)")
@@ -55,21 +55,21 @@ func (r Ref) Full() string {
 	return r.String()
 	return r.String()
 }
 }
 
 
-func (r Ref) NameAndTag() string {
+func (r Path) NameAndTag() string {
 	r.domain = ""
 	r.domain = ""
 	r.namespace = ""
 	r.namespace = ""
 	r.build = ""
 	r.build = ""
 	return r.String()
 	return r.String()
 }
 }
 
 
-func (r Ref) NameTagAndBuild() string {
+func (r Path) NameTagAndBuild() string {
 	r.domain = ""
 	r.domain = ""
 	r.namespace = ""
 	r.namespace = ""
 	return r.String()
 	return r.String()
 }
 }
 
 
 // String returns the fully qualified ref string.
 // String returns the fully qualified ref string.
-func (r Ref) String() string {
+func (r Path) String() string {
 	var b strings.Builder
 	var b strings.Builder
 	if r.domain != "" {
 	if r.domain != "" {
 		b.WriteString(r.domain)
 		b.WriteString(r.domain)
@@ -93,19 +93,19 @@ func (r Ref) String() string {
 
 
 // Complete reports whether the ref is fully qualified. That is it has a
 // Complete reports whether the ref is fully qualified. That is it has a
 // domain, namespace, name, tag, and build.
 // domain, namespace, name, tag, and build.
-func (r Ref) Complete() bool {
+func (r Path) Complete() bool {
 	return r.Valid() && !slices.Contains(r.Parts(), "")
 	return r.Valid() && !slices.Contains(r.Parts(), "")
 }
 }
 
 
 // CompleteWithoutBuild reports whether the ref would be complete if it had a
 // CompleteWithoutBuild reports whether the ref would be complete if it had a
 // valid build.
 // valid build.
-func (r Ref) CompleteWithoutBuild() bool {
+func (r Path) CompleteWithoutBuild() bool {
 	r.build = "x"
 	r.build = "x"
 	return r.Valid() && r.Complete()
 	return r.Valid() && r.Complete()
 }
 }
 
 
 // Less returns true if r is less concrete than o; false otherwise.
 // Less returns true if r is less concrete than o; false otherwise.
-func (r Ref) Less(o Ref) bool {
+func (r Path) Less(o Path) bool {
 	rp := r.Parts()
 	rp := r.Parts()
 	op := o.Parts()
 	op := o.Parts()
 	for i := range rp {
 	for i := range rp {
@@ -119,7 +119,7 @@ func (r Ref) Less(o Ref) bool {
 // Parts returns the parts of the ref in order of concreteness.
 // Parts returns the parts of the ref in order of concreteness.
 //
 //
 // The length of the returned slice is always 5.
 // The length of the returned slice is always 5.
-func (r Ref) Parts() []string {
+func (r Path) Parts() []string {
 	return []string{
 	return []string{
 		r.domain,
 		r.domain,
 		r.namespace,
 		r.namespace,
@@ -129,36 +129,30 @@ func (r Ref) Parts() []string {
 	}
 	}
 }
 }
 
 
-func (r Ref) Domain() string    { return r.namespace }
-func (r Ref) Namespace() string { return r.namespace }
-func (r Ref) Name() string      { return r.name }
-func (r Ref) Tag() string       { return r.tag }
-func (r Ref) Build() string     { return r.build }
+func (r Path) Domain() string    { return r.namespace }
+func (r Path) Namespace() string { return r.namespace }
+func (r Path) Name() string      { return r.name }
+func (r Path) Tag() string       { return r.tag }
+func (r Path) Build() string     { return r.build }
 
 
-// ParseRef parses a ref string into a Ref. A ref string is a name, an
-// optional tag, and an optional build, separated by colons and pluses.
+// ParsePath parses a model path string into a Path.
 //
 //
-// The name must be valid ascii [a-zA-Z0-9_].
-// The tag must be valid ascii [a-zA-Z0-9_].
-// The build must be valid ascii [a-zA-Z0-9_].
+// Examples of valid paths:
 //
 //
-// It returns then zero value if the ref is invalid.
+//	"example.com/mistral:7b+x"
+//	"example.com/mistral:7b+Q4_0"
+//	"mistral:7b+x"
+//	"example.com/x/mistral:latest+Q4_0"
+//	"example.com/x/mistral:latest"
 //
 //
-//	// Valid Examples:
-//	ParseRef("mistral:latest") returns ("mistral", "latest", "")
-//	ParseRef("mistral") returns ("mistral", "", "")
-//	ParseRef("mistral:30B") returns ("mistral", "30B", "")
-//	ParseRef("mistral:7b") returns ("mistral", "7b", "")
-//	ParseRef("mistral:7b+Q4_0") returns ("mistral", "7b", "Q4_0")
-//	ParseRef("mistral+KQED") returns ("mistral", "latest", "KQED")
-//	ParseRef(".x.:7b+Q4_0:latest") returns (".x.", "7b", "Q4_0")
-//	ParseRef("-grok-f.oo:7b+Q4_0") returns ("-grok-f.oo", "7b", "Q4_0")
+// Examples of invalid paths:
 //
 //
-//	// Invalid Examples:
-//	ParseRef("m stral") returns ("", "", "") // zero
-//	ParseRef("... 129 chars ...") returns ("", "", "") // zero
-func ParseRef(s string) Ref {
-	var r Ref
+//	"example.com/mistral:7b+"
+//	"example.com/mistral:7b+Q4_0+"
+//	"x/y/z/z:8n+I"
+//	""
+func ParsePath(s string) Path {
+	var r Path
 	for kind, part := range Parts(s) {
 	for kind, part := range Parts(s) {
 		switch kind {
 		switch kind {
 		case Domain:
 		case Domain:
@@ -172,11 +166,11 @@ func ParseRef(s string) Ref {
 		case Build:
 		case Build:
 			r.build = strings.ToUpper(part)
 			r.build = strings.ToUpper(part)
 		case Invalid:
 		case Invalid:
-			return Ref{}
+			return Path{}
 		}
 		}
 	}
 	}
 	if !r.Valid() {
 	if !r.Valid() {
-		return Ref{}
+		return Path{}
 	}
 	}
 	return r
 	return r
 }
 }
@@ -185,8 +179,8 @@ func ParseRef(s string) Ref {
 // The name is left untouched.
 // The name is left untouched.
 //
 //
 // Use this for merging a ref with a default ref.
 // Use this for merging a ref with a default ref.
-func Merge(a, b Ref) Ref {
-	return Ref{
+func Merge(a, b Path) Path {
+	return Path{
 		// name is left untouched
 		// name is left untouched
 		name: a.name,
 		name: a.name,
 
 
@@ -211,7 +205,7 @@ func Parts(s string) iter.Seq2[PartKind, string] {
 			s = s[len("https://"):]
 			s = s[len("https://"):]
 		}
 		}
 
 
-		if len(s) > MaxRefLength || len(s) == 0 {
+		if len(s) > MaxPathLength || len(s) == 0 {
 			return
 			return
 		}
 		}
 
 
@@ -282,7 +276,7 @@ func Parts(s string) iter.Seq2[PartKind, string] {
 
 
 // Valid returns true if the ref has a valid name. To know if a ref is
 // Valid returns true if the ref has a valid name. To know if a ref is
 // "complete", use Complete.
 // "complete", use Complete.
-func (r Ref) Valid() bool {
+func (r Path) Valid() bool {
 	// Parts ensures we only have valid parts, so no need to validate
 	// Parts ensures we only have valid parts, so no need to validate
 	// them here, only check if we have a name or not.
 	// them here, only check if we have a name or not.
 	return r.name != ""
 	return r.name != ""

+ 43 - 43
x/build/blob/ref_test.go → x/model/path_test.go

@@ -1,4 +1,4 @@
-package blob
+package model
 
 
 import (
 import (
 	"fmt"
 	"fmt"
@@ -6,7 +6,7 @@ import (
 	"testing"
 	"testing"
 )
 )
 
 
-var testRefs = map[string]Ref{
+var testPaths = map[string]Path{
 	"mistral:latest":      {name: "mistral", tag: "latest"},
 	"mistral:latest":      {name: "mistral", tag: "latest"},
 	"mistral":             {name: "mistral"},
 	"mistral":             {name: "mistral"},
 	"mistral:30B":         {name: "mistral", tag: "30B"},
 	"mistral:30B":         {name: "mistral", tag: "30B"},
@@ -36,33 +36,33 @@ var testRefs = map[string]Ref{
 	"file:///etc/passwd:latest":   {},
 	"file:///etc/passwd:latest":   {},
 	"file:///etc/passwd:latest+u": {},
 	"file:///etc/passwd:latest+u": {},
 
 
-	strings.Repeat("a", MaxRefLength):   {name: strings.Repeat("a", MaxRefLength)},
-	strings.Repeat("a", MaxRefLength+1): {},
+	strings.Repeat("a", MaxPathLength):   {name: strings.Repeat("a", MaxPathLength)},
+	strings.Repeat("a", MaxPathLength+1): {},
 }
 }
 
 
-func TestRefParts(t *testing.T) {
+func TestPathParts(t *testing.T) {
 	const wantNumParts = 5
 	const wantNumParts = 5
-	var ref Ref
-	if len(ref.Parts()) != wantNumParts {
-		t.Errorf("Parts() = %d; want %d", len(ref.Parts()), wantNumParts)
+	var p Path
+	if len(p.Parts()) != wantNumParts {
+		t.Errorf("Parts() = %d; want %d", len(p.Parts()), wantNumParts)
 	}
 	}
 }
 }
 
 
-func TestParseRef(t *testing.T) {
-	for s, want := range testRefs {
+func TestParsePath(t *testing.T) {
+	for s, want := range testPaths {
 		for _, prefix := range []string{"", "https://", "http://"} {
 		for _, prefix := range []string{"", "https://", "http://"} {
 			// We should get the same results with or without the
 			// We should get the same results with or without the
 			// http(s) prefixes
 			// http(s) prefixes
 			s := prefix + s
 			s := prefix + s
 
 
 			t.Run(s, func(t *testing.T) {
 			t.Run(s, func(t *testing.T) {
-				got := ParseRef(s)
+				got := ParsePath(s)
 				if got != want {
 				if got != want {
-					t.Errorf("ParseRef(%q) = %q; want %q", s, got, want)
+					t.Errorf("ParsePath(%q) = %q; want %q", s, got, want)
 				}
 				}
 
 
 				// test round-trip
 				// test round-trip
-				if ParseRef(got.String()) != got {
+				if ParsePath(got.String()) != got {
 					t.Errorf("String() = %s; want %s", got.String(), s)
 					t.Errorf("String() = %s; want %s", got.String(), s)
 				}
 				}
 
 
@@ -76,7 +76,7 @@ func TestParseRef(t *testing.T) {
 	}
 	}
 }
 }
 
 
-func TestRefComplete(t *testing.T) {
+func TestPathComplete(t *testing.T) {
 	cases := []struct {
 	cases := []struct {
 		in                   string
 		in                   string
 		complete             bool
 		complete             bool
@@ -92,19 +92,19 @@ func TestRefComplete(t *testing.T) {
 
 
 	for _, tt := range cases {
 	for _, tt := range cases {
 		t.Run(tt.in, func(t *testing.T) {
 		t.Run(tt.in, func(t *testing.T) {
-			ref := ParseRef(tt.in)
-			t.Logf("ParseRef(%q) = %#v", tt.in, ref)
-			if g := ref.Complete(); g != tt.complete {
+			p := ParsePath(tt.in)
+			t.Logf("ParsePath(%q) = %#v", tt.in, p)
+			if g := p.Complete(); g != tt.complete {
 				t.Errorf("Complete(%q) = %v; want %v", tt.in, g, tt.complete)
 				t.Errorf("Complete(%q) = %v; want %v", tt.in, g, tt.complete)
 			}
 			}
-			if g := ref.CompleteWithoutBuild(); g != tt.completeWithoutBuild {
+			if g := p.CompleteWithoutBuild(); g != tt.completeWithoutBuild {
 				t.Errorf("CompleteWithoutBuild(%q) = %v; want %v", tt.in, g, tt.completeWithoutBuild)
 				t.Errorf("CompleteWithoutBuild(%q) = %v; want %v", tt.in, g, tt.completeWithoutBuild)
 			}
 			}
 		})
 		})
 	}
 	}
 }
 }
 
 
-func TestRefStringVariants(t *testing.T) {
+func TestPathStringVariants(t *testing.T) {
 	cases := []struct {
 	cases := []struct {
 		in              string
 		in              string
 		nameAndTag      string
 		nameAndTag      string
@@ -116,19 +116,19 @@ func TestRefStringVariants(t *testing.T) {
 
 
 	for _, tt := range cases {
 	for _, tt := range cases {
 		t.Run(tt.in, func(t *testing.T) {
 		t.Run(tt.in, func(t *testing.T) {
-			ref := ParseRef(tt.in)
-			t.Logf("ParseRef(%q) = %#v", tt.in, ref)
-			if g := ref.NameAndTag(); g != tt.nameAndTag {
+			p := ParsePath(tt.in)
+			t.Logf("ParsePath(%q) = %#v", tt.in, p)
+			if g := p.NameAndTag(); g != tt.nameAndTag {
 				t.Errorf("NameAndTag(%q) = %q; want %q", tt.in, g, tt.nameAndTag)
 				t.Errorf("NameAndTag(%q) = %q; want %q", tt.in, g, tt.nameAndTag)
 			}
 			}
-			if g := ref.NameTagAndBuild(); g != tt.nameTagAndBuild {
+			if g := p.NameTagAndBuild(); g != tt.nameTagAndBuild {
 				t.Errorf("NameTagAndBuild(%q) = %q; want %q", tt.in, g, tt.nameTagAndBuild)
 				t.Errorf("NameTagAndBuild(%q) = %q; want %q", tt.in, g, tt.nameTagAndBuild)
 			}
 			}
 		})
 		})
 	}
 	}
 }
 }
 
 
-func TestRefFull(t *testing.T) {
+func TestPathFull(t *testing.T) {
 	const empty = "!(MISSING DOMAIN)/!(MISSING NAMESPACE)/!(MISSING NAME):!(MISSING TAG)+!(MISSING BUILD)"
 	const empty = "!(MISSING DOMAIN)/!(MISSING NAMESPACE)/!(MISSING NAME):!(MISSING TAG)+!(MISSING BUILD)"
 
 
 	cases := []struct {
 	cases := []struct {
@@ -151,53 +151,53 @@ func TestRefFull(t *testing.T) {
 
 
 	for _, tt := range cases {
 	for _, tt := range cases {
 		t.Run(tt.in, func(t *testing.T) {
 		t.Run(tt.in, func(t *testing.T) {
-			ref := ParseRef(tt.in)
-			t.Logf("ParseRef(%q) = %#v", tt.in, ref)
-			if g := ref.Full(); g != tt.wantFull {
+			p := ParsePath(tt.in)
+			t.Logf("ParsePath(%q) = %#v", tt.in, p)
+			if g := p.Full(); g != tt.wantFull {
 				t.Errorf("Full(%q) = %q; want %q", tt.in, g, tt.wantFull)
 				t.Errorf("Full(%q) = %q; want %q", tt.in, g, tt.wantFull)
 			}
 			}
 		})
 		})
 	}
 	}
 }
 }
 
 
-func TestParseRefAllocs(t *testing.T) {
+func TestParsePathAllocs(t *testing.T) {
 	// test allocations
 	// test allocations
-	var r Ref
+	var r Path
 	allocs := testing.AllocsPerRun(1000, func() {
 	allocs := testing.AllocsPerRun(1000, func() {
-		r = ParseRef("example.com/mistral:7b+Q4_0")
+		r = ParsePath("example.com/mistral:7b+Q4_0")
 	})
 	})
 	_ = r
 	_ = r
 	if allocs > 0 {
 	if allocs > 0 {
-		t.Errorf("ParseRef allocs = %v; want 0", allocs)
+		t.Errorf("ParsePath allocs = %v; want 0", allocs)
 	}
 	}
 }
 }
 
 
-func BenchmarkParseRef(b *testing.B) {
+func BenchmarkParsePath(b *testing.B) {
 	b.ReportAllocs()
 	b.ReportAllocs()
 
 
-	var r Ref
+	var r Path
 	for i := 0; i < b.N; i++ {
 	for i := 0; i < b.N; i++ {
-		r = ParseRef("example.com/mistral:7b+Q4_0")
+		r = ParsePath("example.com/mistral:7b+Q4_0")
 	}
 	}
 	_ = r
 	_ = r
 }
 }
 
 
-func FuzzParseRef(f *testing.F) {
+func FuzzParsePath(f *testing.F) {
 	f.Add("example.com/mistral:7b+Q4_0")
 	f.Add("example.com/mistral:7b+Q4_0")
 	f.Add("example.com/mistral:7b+q4_0")
 	f.Add("example.com/mistral:7b+q4_0")
 	f.Add("example.com/mistral:7b+x")
 	f.Add("example.com/mistral:7b+x")
 	f.Add("x/y/z:8n+I")
 	f.Add("x/y/z:8n+I")
 	f.Fuzz(func(t *testing.T, s string) {
 	f.Fuzz(func(t *testing.T, s string) {
-		r0 := ParseRef(s)
+		r0 := ParsePath(s)
 		if !r0.Valid() {
 		if !r0.Valid() {
-			if r0 != (Ref{}) {
-				t.Errorf("expected invalid ref to be zero value; got %#v", r0)
+			if r0 != (Path{}) {
+				t.Errorf("expected invalid path to be zero value; got %#v", r0)
 			}
 			}
-			t.Skipf("invalid ref: %q", s)
+			t.Skipf("invalid path: %q", s)
 		}
 		}
 
 
 		for _, p := range r0.Parts() {
 		for _, p := range r0.Parts() {
-			if len(p) > MaxRefLength {
+			if len(p) > MaxPathLength {
 				t.Errorf("part too long: %q", p)
 				t.Errorf("part too long: %q", p)
 			}
 			}
 		}
 		}
@@ -206,7 +206,7 @@ func FuzzParseRef(f *testing.F) {
 			t.Errorf("String() did not round-trip with case insensitivity: %q\ngot  = %q\nwant = %q", s, r0.String(), s)
 			t.Errorf("String() did not round-trip with case insensitivity: %q\ngot  = %q\nwant = %q", s, r0.String(), s)
 		}
 		}
 
 
-		r1 := ParseRef(r0.String())
+		r1 := ParsePath(r0.String())
 		if r0 != r1 {
 		if r0 != r1 {
 			t.Errorf("round-trip mismatch: %+v != %+v", r0, r1)
 			t.Errorf("round-trip mismatch: %+v != %+v", r0, r1)
 		}
 		}
@@ -216,8 +216,8 @@ func FuzzParseRef(f *testing.F) {
 
 
 func ExampleMerge() {
 func ExampleMerge() {
 	r := Merge(
 	r := Merge(
-		ParseRef("mistral"),
-		ParseRef("registry.ollama.com/XXXXX:latest+Q4_0"),
+		ParsePath("mistral"),
+		ParsePath("registry.ollama.com/XXXXX:latest+Q4_0"),
 	)
 	)
 	fmt.Println(r)
 	fmt.Println(r)
 
 

+ 0 - 0
x/build/blob/testdata/fuzz/FuzzParseRef/1d43ee52085cb4aa → x/model/testdata/fuzz/FuzzParseRef/1d43ee52085cb4aa


+ 0 - 0
x/build/blob/testdata/fuzz/FuzzParseRef/27fd759314f0e6d6 → x/model/testdata/fuzz/FuzzParseRef/27fd759314f0e6d6


+ 0 - 0
x/build/blob/testdata/fuzz/FuzzParseRef/3e3b70dba384074d → x/model/testdata/fuzz/FuzzParseRef/3e3b70dba384074d


+ 0 - 0
x/build/blob/testdata/fuzz/FuzzParseRef/71f1fdff711b6dab → x/model/testdata/fuzz/FuzzParseRef/71f1fdff711b6dab


+ 0 - 0
x/build/blob/testdata/fuzz/FuzzParseRef/82c2975c430ac608 → x/model/testdata/fuzz/FuzzParseRef/82c2975c430ac608


+ 0 - 0
x/build/blob/testdata/fuzz/FuzzParseRef/b51b1c875e61a948 → x/model/testdata/fuzz/FuzzParseRef/b51b1c875e61a948


+ 1 - 1
x/registry/apitype/apitype.go

@@ -18,7 +18,7 @@ type Layer struct {
 }
 }
 
 
 type PushRequest struct {
 type PushRequest struct {
-	Ref      string          `json:"ref"`
+	Name     string          `json:"ref"`
 	Manifest json.RawMessage `json:"manifest"`
 	Manifest json.RawMessage `json:"manifest"`
 
 
 	// Parts is a list of upload parts that the client upload in the previous
 	// Parts is a list of upload parts that the client upload in the previous

+ 1 - 1
x/registry/client.go

@@ -32,7 +32,7 @@ func (c *Client) Push(ctx context.Context, ref string, manifest []byte, p *PushP
 	p = cmp.Or(p, &PushParams{})
 	p = cmp.Or(p, &PushParams{})
 	// TODO(bmizerany): backoff
 	// TODO(bmizerany): backoff
 	v, err := ollama.Do[apitype.PushResponse](ctx, c.oclient(), "POST", "/v1/push", &apitype.PushRequest{
 	v, err := ollama.Do[apitype.PushResponse](ctx, c.oclient(), "POST", "/v1/push", &apitype.PushRequest{
-		Ref:           ref,
+		Name:          ref,
 		Manifest:      manifest,
 		Manifest:      manifest,
 		CompleteParts: p.CompleteParts,
 		CompleteParts: p.CompleteParts,
 	})
 	})

+ 5 - 5
x/registry/server.go

@@ -14,8 +14,8 @@ import (
 	"strconv"
 	"strconv"
 	"time"
 	"time"
 
 
-	"bllamo.com/build/blob"
 	"bllamo.com/client/ollama"
 	"bllamo.com/client/ollama"
+	"bllamo.com/model"
 	"bllamo.com/oweb"
 	"bllamo.com/oweb"
 	"bllamo.com/registry/apitype"
 	"bllamo.com/registry/apitype"
 	"bllamo.com/utils/upload"
 	"bllamo.com/utils/upload"
@@ -82,9 +82,9 @@ func (s *Server) handlePush(w http.ResponseWriter, r *http.Request) error {
 		return err
 		return err
 	}
 	}
 
 
-	ref := blob.ParseRef(pr.Ref)
-	if !ref.Complete() {
-		return oweb.Invalid("name", pr.Ref, "must be complete")
+	mp := model.ParsePath(pr.Name)
+	if !mp.Complete() {
+		return oweb.Invalid("name", pr.Name, "must be complete")
 	}
 	}
 
 
 	m, err := oweb.DecodeUserJSON[apitype.Manifest]("manifest", bytes.NewReader(pr.Manifest))
 	m, err := oweb.DecodeUserJSON[apitype.Manifest]("manifest", bytes.NewReader(pr.Manifest))
@@ -205,7 +205,7 @@ func (s *Server) handlePush(w http.ResponseWriter, r *http.Request) error {
 	if len(requirements) == 0 {
 	if len(requirements) == 0 {
 		// Commit the manifest
 		// Commit the manifest
 		body := bytes.NewReader(pr.Manifest)
 		body := bytes.NewReader(pr.Manifest)
-		path := path.Join("manifests", path.Join(ref.Parts()...))
+		path := path.Join("manifests", path.Join(mp.Parts()...))
 		_, err := s.mc().PutObject(r.Context(), bucketTODO, path, body, int64(len(pr.Manifest)), minio.PutObjectOptions{})
 		_, err := s.mc().PutObject(r.Context(), bucketTODO, path, body, int64(len(pr.Manifest)), minio.PutObjectOptions{})
 		if err != nil {
 		if err != nil {
 			return err
 			return err