mirror of
https://github.com/LMMS/lmms.git
synced 2026-03-14 12:08:33 -04:00
Use valid Semver versions for pre-releases (#5636)
* Fix ProjectVersion handling of pre-releases * Add workaround for old, non-standard version * Attempt to fix versioning * More consistent comments * Apply suggestions from code review - Set CompareType's underlying type to int and revert change to ProjectVersion::compare's parameters - Add "None" and "All" as names elements of CompareType enum - Preserve hyphens in prerelease identifiers - Pad invalid (too short) versions to prevent crashes or nasty behavior - Compare numeric identifiers to non-numeric ones correctly - Don't interpret identifiers of form "-#" as numeric (where '#' is any number of digits) - Add tests to ensure fixes in this commit work and won't regress in the future * CMAKE fixes from code review Co-authored-by: Tres Finocchiaro <tres.finocchiaro@gmail.com> * Remove unnecessary changes to CMake logic * More const, more reference * Apply suggestions from code review Co-authored-by: Tres Finocchiaro <tres.finocchiaro@gmail.com>
This commit is contained in:
@@ -27,123 +27,105 @@
|
||||
|
||||
#include "ProjectVersion.h"
|
||||
|
||||
int parseMajor(QString & version) {
|
||||
return version.section( '.', 0, 0 ).toInt();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
int parseMinor(QString & version) {
|
||||
return version.section( '.', 1, 1 ).toInt();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
int parseRelease(QString & version) {
|
||||
return version.section( '.', 2, 2 ).section( '-', 0, 0 ).toInt();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
QString parseStage(QString & version) {
|
||||
return version.section( '.', 2, 2 ).section( '-', 1 );
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
int parseBuild(QString & version) {
|
||||
return version.section( '.', 3 ).toInt();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
ProjectVersion::ProjectVersion(QString version, CompareType c) :
|
||||
m_version(version),
|
||||
m_major(parseMajor(m_version)),
|
||||
m_minor(parseMinor(m_version)),
|
||||
m_release(parseRelease(m_version)),
|
||||
m_stage(parseStage(m_version)),
|
||||
m_build(parseBuild(m_version)),
|
||||
m_compareType(c)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
ProjectVersion::ProjectVersion(const char* version, CompareType c) :
|
||||
m_version(QString(version)),
|
||||
m_major(parseMajor(m_version)),
|
||||
m_minor(parseMinor(m_version)),
|
||||
m_release(parseRelease(m_version)),
|
||||
m_stage(parseStage(m_version)),
|
||||
m_build(parseBuild(m_version)),
|
||||
m_compareType(c)
|
||||
{
|
||||
// Version numbers may have build data, prefixed with a '+',
|
||||
// but this mustn't affect version precedence in comparisons
|
||||
QString metadataStripped = version.split("+").first();
|
||||
// They must have an obligatory initial segement, and may have
|
||||
// optional identifiers prefaced by a '-'. Both parts affect precedence
|
||||
QString obligatorySegment = metadataStripped.section('-', 0, 0);
|
||||
QString prereleaseSegment = metadataStripped.section('-', 1);
|
||||
|
||||
// The obligatory segment consists of three identifiers: MAJOR.MINOR.PATCH
|
||||
QStringList mainVersion = obligatorySegment.split(".");
|
||||
// HACK: Pad invalid versions in order to prevent crashes
|
||||
while (mainVersion.size() < 3){ mainVersion.append("0"); }
|
||||
m_major = mainVersion.at(0).toInt();
|
||||
m_minor = mainVersion.at(1).toInt();
|
||||
m_patch = mainVersion.at(2).toInt();
|
||||
|
||||
// Any # of optional pre-release identifiers may follow, separated by '.'s
|
||||
if (!prereleaseSegment.isEmpty()){ m_labels = prereleaseSegment.split("."); }
|
||||
|
||||
// HACK: Handle old (1.2.2 and earlier), non-standard versions of the form
|
||||
// MAJOR.MINOR.PATCH.COMMITS, used for non-release builds from source.
|
||||
if (mainVersion.size() >= 4 && m_major <= 1 && m_minor <= 2 && m_patch <= 2){
|
||||
// Drop the standard version identifiers. erase(a, b) removes [a,b)
|
||||
mainVersion.erase(mainVersion.begin(), mainVersion.begin() + 3);
|
||||
// Prepend the remaining identifiers as prerelease versions
|
||||
m_labels = mainVersion + m_labels;
|
||||
// Bump the patch version. x.y.z-a < x.y.z, but we want x.y.z.a > x.y.z
|
||||
m_patch += 1;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
ProjectVersion::ProjectVersion(const char* version, CompareType c) : ProjectVersion(QString(version), c)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
//! @param c Determines the number of identifiers to check when comparing
|
||||
int ProjectVersion::compare(const ProjectVersion & a, const ProjectVersion & b, CompareType c)
|
||||
{
|
||||
if(a.getMajor() != b.getMajor())
|
||||
{
|
||||
return a.getMajor() - b.getMajor();
|
||||
}
|
||||
if(c == Major)
|
||||
{
|
||||
return 0;
|
||||
// How many identifiers to compare before we consider the versions equal
|
||||
const int limit = static_cast<int>(c);
|
||||
|
||||
// Use the value of limit to zero out identifiers we don't care about
|
||||
int aMaj = 0, bMaj = 0, aMin = 0, bMin = 0, aPat = 0, bPat = 0;
|
||||
if (limit >= 1){ aMaj = a.getMajor(); bMaj = b.getMajor(); }
|
||||
if (limit >= 2){ aMin = a.getMinor(); bMin = b.getMinor(); }
|
||||
if (limit >= 3){ aPat = a.getPatch(); bPat = b.getPatch(); }
|
||||
|
||||
// Then we can compare as if we care about every identifier
|
||||
if(aMaj != bMaj){ return aMaj - bMaj; }
|
||||
if(aMin != bMin){ return aMin - bMin; }
|
||||
if(aPat != bPat){ return aPat - bPat; }
|
||||
|
||||
// Decide how many optional identifiers we care about
|
||||
const int maxLabels = qMax(0, limit - 3);
|
||||
const auto aLabels = a.getLabels().mid(0, maxLabels);
|
||||
const auto bLabels = b.getLabels().mid(0, maxLabels);
|
||||
|
||||
// We can only compare identifiers if both versions have them
|
||||
const int commonLabels = qMin(aLabels.size(), bLabels.size());
|
||||
// If one version has optional labels and the other doesn't,
|
||||
// the one without them is bigger
|
||||
if (commonLabels == 0){ return bLabels.size() - aLabels.size(); }
|
||||
|
||||
// Otherwise, compare as many labels as we can
|
||||
for (int i = 0; i < commonLabels; i++){
|
||||
const QString& labelA = aLabels.at(i);
|
||||
const QString& labelB = bLabels.at(i);
|
||||
// If both labels are the same, skip
|
||||
if (labelA == labelB){ continue; }
|
||||
// Numeric and non-numeric identifiers compare differently
|
||||
bool aIsNumeric = false, bIsNumeric = false;
|
||||
const int numA = labelA.toInt(&aIsNumeric);
|
||||
const int numB = labelB.toInt(&bIsNumeric);
|
||||
// toInt reads '-x' as a negative number, semver says it's non-numeric
|
||||
aIsNumeric &= !labelA.startsWith("-");
|
||||
bIsNumeric &= !labelB.startsWith("-");
|
||||
// If only one identifier is numeric, that one is smaller
|
||||
if (aIsNumeric != bIsNumeric){ return aIsNumeric ? -1 : 1; }
|
||||
// If both are numeric, compare as numbers
|
||||
if (aIsNumeric && bIsNumeric){ return numA - numB; }
|
||||
// Otherwise, compare lexically
|
||||
return labelA.compare(labelB);
|
||||
}
|
||||
|
||||
if(a.getMinor() != b.getMinor())
|
||||
{
|
||||
return a.getMinor() - b.getMinor();
|
||||
}
|
||||
if(c == Minor)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
if(a.getRelease() != b.getRelease())
|
||||
{
|
||||
return a.getRelease() - b.getRelease();
|
||||
}
|
||||
if(c == Release)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
if(!(a.getStage().isEmpty() && b.getStage().isEmpty()))
|
||||
{
|
||||
// make sure 0.x.y > 0.x.y-alpha
|
||||
if(a.getStage().isEmpty())
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
if(b.getStage().isEmpty())
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
// 0.x.y-beta > 0.x.y-alpha
|
||||
int cmp = QString::compare(a.getStage(), b.getStage());
|
||||
if(cmp)
|
||||
{
|
||||
return cmp;
|
||||
}
|
||||
}
|
||||
if(c == Stage)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
return a.getBuild() - b.getBuild();
|
||||
// If everything else matches, the version with more labels is bigger
|
||||
return aLabels.size() - bLabels.size();
|
||||
}
|
||||
|
||||
|
||||
@@ -153,6 +135,3 @@ int ProjectVersion::compare(ProjectVersion v1, ProjectVersion v2)
|
||||
{
|
||||
return compare(v1, v2, std::min(v1.getCompareType(), v2.getCompareType()));
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user