NuGet prerelease lookups match other versions and miss equivalent ones
Summary
NuGet reads a prerelease label as text, so 1.0-beta and 1.0-beta.0 are two versions, and it ignores build metadata, so 2.0.0-alpha+build is the same version as 2.0.0-alpha. GitLab's NuGet lookups get both wrong for prerelease versions:
- The normalized version stored for a prerelease version reads the dots and leading zeros of its label as version parts:
1.0-betais stored as1.0-beta.0, and1.0.0-beta.007abcas1.0.0-beta.7abc. A lookup of1.0-beta.0therefore finds a1.0-betapackage. - A lookup of a prerelease version keeps its build metadata, so after
2.0.0-alpha, a push of2.0.0-alpha+buildfinds no package. The other order finds it, through the stored normalized version. - When it does, the file keeps the pushed version in its name: after
2.0.0-alpha+b, a push of2.0.0-alphaaddsfoo.2.0.0-alpha.nupkgto that package, while a download of2.0.0-alphalooks upfoo.2.0.0-alpha+b.nupkg, the name built from the package's version, and serves the older file. - The lease of
CreateOrUpdatePackageServiceis keyed on the prerelease version as pushed, so pushes of2.0.0-alphaand2.0.0-alpha+buildat the same time take two leases, although the duplicate check of2.0.0-alphafinds a stored2.0.0-alpha+build. Both can create a package.
!265928 (merged) normalizes only release versions in these lookups and leaves these cases as they are on master: fixing the first one needs a backfill of packages_nuget_metadata.normalized_version, and the others are outside that merge request's scope.
Steps to reproduce
- In a group, turn off Allow duplicates for NuGet packages and leave Exceptions empty.
- In a project of that group, push a package as
2.0.0-alpha, then as2.0.0-alpha+build. - Push another package as
1.0-beta, then sendDELETE /api/v4/projects/:id/packages/nuget/<name>/1.0-beta.0. - In a group that allows duplicates, push a third package as
2.0.0-alpha+b, then as2.0.0-alphawith another description, and download2.0.0-alpha.
Example Project
Not needed: the steps work in any group with the NuGet registry.
What is the current bug behavior?
The push of 2.0.0-alpha+build answers 201 Created and creates a second package for the same NuGet version. The DELETE of 1.0-beta.0, a version that was never pushed, marks the 1.0-beta package for destruction. The download of 2.0.0-alpha returns the file of the first push.
What is the expected correct behavior?
The second push is refused with 409 Conflict, the DELETE answers 404 Not Found, and the download returns the file of the second push.
Relevant logs and/or screenshots
Not applicable.
Output of checks
Found in the review of !265928 (merged). Specs on master and on that merge request's branch show the lookups, and the #nuget_file_name rows of package_spec.rb on that branch show that a prerelease push keeps its own version in the file name, as master does. Not tried on GitLab.com.
Possible fixes
- Remove the build metadata of a requested prerelease version in lookups and in the lease key too. Unlike the rest of the normalization, removing it cannot make two different prerelease labels equal.
- When a prerelease push adds its file to a package whose version differs only by build metadata, name the file after that package's version, as a release push does.
- Normalize the release part of a prerelease version and keep its label as it is, so that
1.0-betais stored as1.0.0-beta, then backfillnormalized_versionfor stored prerelease versions. A requested prerelease version can then be normalized the same way.