From 05990998ef7d19a1d329233b097a03443067e248 Mon Sep 17 00:00:00 2001 From: Yuxuan Che Date: Thu, 29 Jan 2026 15:34:10 -0800 Subject: [PATCH 1/2] fix: normalize hf.co to huggingface.co to prevent duplicate models When pulling models from HuggingFace, users can use either hf.co or huggingface.co as the domain. Previously, these were treated as different sources, causing the same model to be stored twice with different tags. This change normalizes hf.co URLs to huggingface.co in the normalizeModelName function, ensuring that: - hf.co/org/model and huggingface.co/org/model are treated as the same - Models are not pulled/stored twice when using different URL formats Fixes #609 --- pkg/distribution/distribution/client.go | 6 +++++ .../distribution/normalize_test.go | 22 +++++++++++++++++++ 2 files changed, 28 insertions(+) diff --git a/pkg/distribution/distribution/client.go b/pkg/distribution/distribution/client.go index f150adef..5c14d0cb 100644 --- a/pkg/distribution/distribution/client.go +++ b/pkg/distribution/distribution/client.go @@ -123,6 +123,12 @@ func (c *Client) normalizeModelName(model string) string { return model } + // Normalize HuggingFace short URL (hf.co) to canonical form (huggingface.co) + // This ensures that hf.co/org/model and huggingface.co/org/model are treated as the same model + if strings.HasPrefix(model, "hf.co/") { + model = "huggingface.co/" + strings.TrimPrefix(model, "hf.co/") + } + // If it looks like an ID or digest, try to resolve it to full ID if c.looksLikeID(model) || c.looksLikeDigest(model) { if fullID := c.resolveID(model); fullID != "" { diff --git a/pkg/distribution/distribution/normalize_test.go b/pkg/distribution/distribution/normalize_test.go index eff18597..26dd5567 100644 --- a/pkg/distribution/distribution/normalize_test.go +++ b/pkg/distribution/distribution/normalize_test.go @@ -123,6 +123,28 @@ func TestNormalizeModelName(t *testing.T) { input: "MyModel", expected: "ai/mymodel:latest", }, + + // HuggingFace URL normalization + { + name: "hf.co normalized to huggingface.co", + input: "hf.co/org/model", + expected: "huggingface.co/org/model:latest", + }, + { + name: "hf.co with tag normalized to huggingface.co", + input: "hf.co/org/model:Q4_K_M", + expected: "huggingface.co/org/model:Q4_K_M", + }, + { + name: "huggingface.co stays unchanged", + input: "huggingface.co/org/model", + expected: "huggingface.co/org/model:latest", + }, + { + name: "huggingface.co with tag stays unchanged", + input: "huggingface.co/org/model:Q4_K_M", + expected: "huggingface.co/org/model:Q4_K_M", + }, } for _, tt := range tests { From cfe841c0ead9bae6c76810262c7045fe6ebf8aa0 Mon Sep 17 00:00:00 2001 From: Yuxuan Che Date: Fri, 30 Jan 2026 10:11:47 -0800 Subject: [PATCH 2/2] refactor: use strings.CutPrefix for cleaner prefix handling --- pkg/distribution/distribution/client.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/distribution/distribution/client.go b/pkg/distribution/distribution/client.go index 5c14d0cb..82586068 100644 --- a/pkg/distribution/distribution/client.go +++ b/pkg/distribution/distribution/client.go @@ -125,8 +125,8 @@ func (c *Client) normalizeModelName(model string) string { // Normalize HuggingFace short URL (hf.co) to canonical form (huggingface.co) // This ensures that hf.co/org/model and huggingface.co/org/model are treated as the same model - if strings.HasPrefix(model, "hf.co/") { - model = "huggingface.co/" + strings.TrimPrefix(model, "hf.co/") + if rest, found := strings.CutPrefix(model, "hf.co/"); found { + model = "huggingface.co/" + rest } // If it looks like an ID or digest, try to resolve it to full ID