diff --git a/pkg/cmd/release/upload/upload.go b/pkg/cmd/release/upload/upload.go index b3af37c4a..12cba4542 100644 --- a/pkg/cmd/release/upload/upload.go +++ b/pkg/cmd/release/upload/upload.go @@ -4,6 +4,7 @@ import ( "context" "fmt" "net/http" + "regexp" "strings" "github.com/MakeNowJust/heredoc" @@ -93,8 +94,9 @@ func uploadRun(opts *UploadOptions) error { var existingNames []string for _, a := range opts.Assets { + sanitizedFileName := sanitizeFileName(a.Name) for _, ea := range release.Assets { - if ea.Name == a.Name { + if ea.Name == sanitizedFileName { a.ExistingURL = ea.APIURL existingNames = append(existingNames, ea.Name) break @@ -122,3 +124,30 @@ func uploadRun(opts *UploadOptions) error { return nil } + +// this method attempts to mimic the same functionality on the client that the platform does on +// uploaded assets in order to allow the --clobber logic work correctly, since that feature is +// one that only exists in the client +func sanitizeFileName(name string) string { + value := text.RemoveDiacritics(name) + // Stripped all non-ascii characters, provide default name. + if strings.HasPrefix(value, ".") { + value = "default" + value + } + + // Replace special characters with the separator + value = regexp.MustCompile(`(?i)[^a-z0-9\-_\+@]+`).ReplaceAllLiteralString(value, ".") + + // No more than one of the separator in a row. + value = regexp.MustCompile(`\.{2,}`).ReplaceAllLiteralString(value, ".") + + // Remove leading/trailing separator. + value = strings.Trim(value, ".") + + // Just file extension left, add default name. + if name != value && !strings.Contains(value, ".") { + value = "default." + value + } + + return value +} diff --git a/pkg/cmd/release/upload/upload_test.go b/pkg/cmd/release/upload/upload_test.go new file mode 100644 index 000000000..7a85c82a3 --- /dev/null +++ b/pkg/cmd/release/upload/upload_test.go @@ -0,0 +1,52 @@ +package upload + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func Test_SanitizeFileName(t *testing.T) { + tests := []struct { + name string + expected string + }{ + { + name: "foo", + expected: "foo", + }, + { + name: "foo bar", + expected: "foo.bar", + }, + { + name: ".foo", + expected: "default.foo", + }, + { + name: "Foo bar", + expected: "Foo.bar", + }, + { + name: "Hello, दुनिया", + expected: "default.Hello", + }, + { + name: "this+has+plusses.jpg", + expected: "this+has+plusses.jpg", + }, + { + name: "this@has@at@signs.jpg", + expected: "this@has@at@signs.jpg", + }, + { + name: "façade.exposé", + expected: "facade.expose", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert.Equal(t, tt.expected, sanitizeFileName(tt.name)) + }) + } +}