github.com/sdboyer/gps@v0.16.3/version.go (about)

     1  package gps
     2  
     3  import (
     4  	"fmt"
     5  	"sort"
     6  
     7  	"github.com/Masterminds/semver"
     8  )
     9  
    10  // VersionType indicates a type for a Version that conveys some additional
    11  // semantics beyond that which is literally embedded on the Go type.
    12  type VersionType uint8
    13  
    14  // VersionTypes for the four major classes of version we deal with
    15  const (
    16  	IsRevision VersionType = iota
    17  	IsVersion
    18  	IsSemver
    19  	IsBranch
    20  )
    21  
    22  // Version represents one of the different types of versions used by gps.
    23  //
    24  // Version composes Constraint, because all versions can be used as a constraint
    25  // (where they allow one, and only one, version - themselves), but constraints
    26  // are not necessarily discrete versions.
    27  //
    28  // Version is an interface, but it contains private methods, which restricts it
    29  // to gps's own internal implementations. We do this for the confluence of
    30  // two reasons: the implementation of Versions is complete (there is no case in
    31  // which we'd need other types), and the implementation relies on type magic
    32  // under the hood, which would be unsafe to do if other dynamic types could be
    33  // hiding behind the interface.
    34  type Version interface {
    35  	Constraint
    36  
    37  	// Indicates the type of version - Revision, Branch, Version, or Semver
    38  	Type() VersionType
    39  }
    40  
    41  // PairedVersion represents a normal Version, but paired with its corresponding,
    42  // underlying Revision.
    43  type PairedVersion interface {
    44  	Version
    45  
    46  	// Underlying returns the immutable Revision that identifies this Version.
    47  	Underlying() Revision
    48  
    49  	// Unpair returns the surface-level UnpairedVersion that half of the pair.
    50  	//
    51  	// It does NOT modify the original PairedVersion
    52  	Unpair() UnpairedVersion
    53  
    54  	// Ensures it is impossible to be both a PairedVersion and an
    55  	// UnpairedVersion
    56  	_pair(int)
    57  }
    58  
    59  // UnpairedVersion represents a normal Version, with a method for creating a
    60  // VersionPair by indicating the version's corresponding, underlying Revision.
    61  type UnpairedVersion interface {
    62  	Version
    63  	// Is takes the underlying Revision that this UnpairedVersion corresponds
    64  	// to and unites them into a PairedVersion.
    65  	Is(Revision) PairedVersion
    66  	// Ensures it is impossible to be both a PairedVersion and an
    67  	// UnpairedVersion
    68  	_pair(bool)
    69  }
    70  
    71  // types are weird
    72  func (branchVersion) _pair(bool) {}
    73  func (plainVersion) _pair(bool)  {}
    74  func (semVersion) _pair(bool)    {}
    75  func (versionPair) _pair(int)    {}
    76  
    77  // NewBranch creates a new Version to represent a floating version (in
    78  // general, a branch).
    79  func NewBranch(body string) UnpairedVersion {
    80  	return branchVersion{
    81  		name: body,
    82  		// We always set isDefault to false here, because the property is
    83  		// specifically designed to be internal-only: only the SourceManager
    84  		// gets to mark it. This is OK because nothing that client code is
    85  		// responsible for needs to care about has to touch it it.
    86  		//
    87  		// TODO(sdboyer) ...maybe. this just ugly.
    88  		isDefault: false,
    89  	}
    90  }
    91  
    92  func newDefaultBranch(body string) UnpairedVersion {
    93  	return branchVersion{
    94  		name:      body,
    95  		isDefault: true,
    96  	}
    97  }
    98  
    99  // NewVersion creates a Semver-typed Version if the provided version string is
   100  // valid semver, and a plain/non-semver version if not.
   101  func NewVersion(body string) UnpairedVersion {
   102  	sv, err := semver.NewVersion(body)
   103  
   104  	if err != nil {
   105  		return plainVersion(body)
   106  	}
   107  	return semVersion{sv: sv}
   108  }
   109  
   110  // A Revision represents an immutable versioning identifier.
   111  type Revision string
   112  
   113  // String converts the Revision back into a string.
   114  func (r Revision) String() string {
   115  	return string(r)
   116  }
   117  
   118  func (r Revision) typedString() string {
   119  	return "r-" + string(r)
   120  }
   121  
   122  // Type indicates the type of version - for revisions, "revision".
   123  func (r Revision) Type() VersionType {
   124  	return IsRevision
   125  }
   126  
   127  // Matches is the Revision acting as a constraint; it checks to see if the provided
   128  // version is the same Revision as itself.
   129  func (r Revision) Matches(v Version) bool {
   130  	switch tv := v.(type) {
   131  	case versionTypeUnion:
   132  		return tv.Matches(r)
   133  	case Revision:
   134  		return r == tv
   135  	case versionPair:
   136  		return r == tv.r
   137  	}
   138  
   139  	return false
   140  }
   141  
   142  // MatchesAny is the Revision acting as a constraint; it checks to see if the provided
   143  // version is the same Revision as itself.
   144  func (r Revision) MatchesAny(c Constraint) bool {
   145  	switch tc := c.(type) {
   146  	case anyConstraint:
   147  		return true
   148  	case noneConstraint:
   149  		return false
   150  	case versionTypeUnion:
   151  		return tc.MatchesAny(r)
   152  	case Revision:
   153  		return r == tc
   154  	case versionPair:
   155  		return r == tc.r
   156  	}
   157  
   158  	return false
   159  }
   160  
   161  // Intersect computes the intersection of the Constraint with the provided
   162  // Constraint. For Revisions, this can only be another, exactly equal
   163  // Revision, or a PairedVersion whose underlying Revision is exactly equal.
   164  func (r Revision) Intersect(c Constraint) Constraint {
   165  	switch tc := c.(type) {
   166  	case anyConstraint:
   167  		return r
   168  	case noneConstraint:
   169  		return none
   170  	case versionTypeUnion:
   171  		return tc.Intersect(r)
   172  	case Revision:
   173  		if r == tc {
   174  			return r
   175  		}
   176  	case versionPair:
   177  		if r == tc.r {
   178  			return r
   179  		}
   180  	}
   181  
   182  	return none
   183  }
   184  
   185  type branchVersion struct {
   186  	name      string
   187  	isDefault bool
   188  }
   189  
   190  func (v branchVersion) String() string {
   191  	return string(v.name)
   192  }
   193  
   194  func (v branchVersion) typedString() string {
   195  	return fmt.Sprintf("b-%s", v.String())
   196  }
   197  
   198  func (v branchVersion) Type() VersionType {
   199  	return IsBranch
   200  }
   201  
   202  func (v branchVersion) Matches(v2 Version) bool {
   203  	switch tv := v2.(type) {
   204  	case versionTypeUnion:
   205  		return tv.Matches(v)
   206  	case branchVersion:
   207  		return v.name == tv.name
   208  	case versionPair:
   209  		if tv2, ok := tv.v.(branchVersion); ok {
   210  			return tv2.name == v.name
   211  		}
   212  	}
   213  	return false
   214  }
   215  
   216  func (v branchVersion) MatchesAny(c Constraint) bool {
   217  	switch tc := c.(type) {
   218  	case anyConstraint:
   219  		return true
   220  	case noneConstraint:
   221  		return false
   222  	case versionTypeUnion:
   223  		return tc.MatchesAny(v)
   224  	case branchVersion:
   225  		return v.name == tc.name
   226  	case versionPair:
   227  		if tc2, ok := tc.v.(branchVersion); ok {
   228  			return tc2.name == v.name
   229  		}
   230  	}
   231  
   232  	return false
   233  }
   234  
   235  func (v branchVersion) Intersect(c Constraint) Constraint {
   236  	switch tc := c.(type) {
   237  	case anyConstraint:
   238  		return v
   239  	case noneConstraint:
   240  		return none
   241  	case versionTypeUnion:
   242  		return tc.Intersect(v)
   243  	case branchVersion:
   244  		if v.name == tc.name {
   245  			return v
   246  		}
   247  	case versionPair:
   248  		if tc2, ok := tc.v.(branchVersion); ok {
   249  			if v.name == tc2.name {
   250  				return v
   251  			}
   252  		}
   253  	}
   254  
   255  	return none
   256  }
   257  
   258  func (v branchVersion) Is(r Revision) PairedVersion {
   259  	return versionPair{
   260  		v: v,
   261  		r: r,
   262  	}
   263  }
   264  
   265  type plainVersion string
   266  
   267  func (v plainVersion) String() string {
   268  	return string(v)
   269  }
   270  
   271  func (v plainVersion) typedString() string {
   272  	return fmt.Sprintf("pv-%s", v.String())
   273  }
   274  
   275  func (v plainVersion) Type() VersionType {
   276  	return IsVersion
   277  }
   278  
   279  func (v plainVersion) Matches(v2 Version) bool {
   280  	switch tv := v2.(type) {
   281  	case versionTypeUnion:
   282  		return tv.Matches(v)
   283  	case plainVersion:
   284  		return v == tv
   285  	case versionPair:
   286  		if tv2, ok := tv.v.(plainVersion); ok {
   287  			return tv2 == v
   288  		}
   289  	}
   290  	return false
   291  }
   292  
   293  func (v plainVersion) MatchesAny(c Constraint) bool {
   294  	switch tc := c.(type) {
   295  	case anyConstraint:
   296  		return true
   297  	case noneConstraint:
   298  		return false
   299  	case versionTypeUnion:
   300  		return tc.MatchesAny(v)
   301  	case plainVersion:
   302  		return v == tc
   303  	case versionPair:
   304  		if tc2, ok := tc.v.(plainVersion); ok {
   305  			return tc2 == v
   306  		}
   307  	}
   308  
   309  	return false
   310  }
   311  
   312  func (v plainVersion) Intersect(c Constraint) Constraint {
   313  	switch tc := c.(type) {
   314  	case anyConstraint:
   315  		return v
   316  	case noneConstraint:
   317  		return none
   318  	case versionTypeUnion:
   319  		return tc.Intersect(v)
   320  	case plainVersion:
   321  		if v == tc {
   322  			return v
   323  		}
   324  	case versionPair:
   325  		if tc2, ok := tc.v.(plainVersion); ok {
   326  			if v == tc2 {
   327  				return v
   328  			}
   329  		}
   330  	}
   331  
   332  	return none
   333  }
   334  
   335  func (v plainVersion) Is(r Revision) PairedVersion {
   336  	return versionPair{
   337  		v: v,
   338  		r: r,
   339  	}
   340  }
   341  
   342  type semVersion struct {
   343  	sv *semver.Version
   344  }
   345  
   346  func (v semVersion) String() string {
   347  	str := v.sv.Original()
   348  	if str == "" {
   349  		str = v.sv.String()
   350  	}
   351  	return str
   352  }
   353  
   354  func (v semVersion) typedString() string {
   355  	return fmt.Sprintf("sv-%s", v.String())
   356  }
   357  
   358  func (v semVersion) Type() VersionType {
   359  	return IsSemver
   360  }
   361  
   362  func (v semVersion) Matches(v2 Version) bool {
   363  	switch tv := v2.(type) {
   364  	case versionTypeUnion:
   365  		return tv.Matches(v)
   366  	case semVersion:
   367  		return v.sv.Equal(tv.sv)
   368  	case versionPair:
   369  		if tv2, ok := tv.v.(semVersion); ok {
   370  			return tv2.sv.Equal(v.sv)
   371  		}
   372  	}
   373  	return false
   374  }
   375  
   376  func (v semVersion) MatchesAny(c Constraint) bool {
   377  	switch tc := c.(type) {
   378  	case anyConstraint:
   379  		return true
   380  	case noneConstraint:
   381  		return false
   382  	case versionTypeUnion:
   383  		return tc.MatchesAny(v)
   384  	case semVersion:
   385  		return v.sv.Equal(tc.sv)
   386  	case semverConstraint:
   387  		return tc.Intersect(v) != none
   388  	case versionPair:
   389  		if tc2, ok := tc.v.(semVersion); ok {
   390  			return tc2.sv.Equal(v.sv)
   391  		}
   392  	}
   393  
   394  	return false
   395  }
   396  
   397  func (v semVersion) Intersect(c Constraint) Constraint {
   398  	switch tc := c.(type) {
   399  	case anyConstraint:
   400  		return v
   401  	case noneConstraint:
   402  		return none
   403  	case versionTypeUnion:
   404  		return tc.Intersect(v)
   405  	case semVersion:
   406  		if v.sv.Equal(tc.sv) {
   407  			return v
   408  		}
   409  	case semverConstraint:
   410  		return tc.Intersect(v)
   411  	case versionPair:
   412  		if tc2, ok := tc.v.(semVersion); ok {
   413  			if v.sv.Equal(tc2.sv) {
   414  				return v
   415  			}
   416  		}
   417  	}
   418  
   419  	return none
   420  }
   421  
   422  func (v semVersion) Is(r Revision) PairedVersion {
   423  	return versionPair{
   424  		v: v,
   425  		r: r,
   426  	}
   427  }
   428  
   429  type versionPair struct {
   430  	v UnpairedVersion
   431  	r Revision
   432  }
   433  
   434  func (v versionPair) String() string {
   435  	return v.v.String()
   436  }
   437  
   438  func (v versionPair) typedString() string {
   439  	return fmt.Sprintf("%s-%s", v.Unpair().typedString(), v.Underlying().typedString())
   440  }
   441  
   442  func (v versionPair) Type() VersionType {
   443  	return v.v.Type()
   444  }
   445  
   446  func (v versionPair) Underlying() Revision {
   447  	return v.r
   448  }
   449  
   450  func (v versionPair) Unpair() UnpairedVersion {
   451  	return v.v
   452  }
   453  
   454  func (v versionPair) Matches(v2 Version) bool {
   455  	switch tv2 := v2.(type) {
   456  	case versionTypeUnion:
   457  		return tv2.Matches(v)
   458  	case versionPair:
   459  		return v.r == tv2.r
   460  	case Revision:
   461  		return v.r == tv2
   462  	}
   463  
   464  	switch tv := v.v.(type) {
   465  	case plainVersion, branchVersion:
   466  		if tv.Matches(v2) {
   467  			return true
   468  		}
   469  	case semVersion:
   470  		if tv2, ok := v2.(semVersion); ok {
   471  			if tv.sv.Equal(tv2.sv) {
   472  				return true
   473  			}
   474  		}
   475  	}
   476  
   477  	return false
   478  }
   479  
   480  func (v versionPair) MatchesAny(c2 Constraint) bool {
   481  	return c2.Matches(v)
   482  }
   483  
   484  func (v versionPair) Intersect(c2 Constraint) Constraint {
   485  	switch tc := c2.(type) {
   486  	case anyConstraint:
   487  		return v
   488  	case noneConstraint:
   489  		return none
   490  	case versionTypeUnion:
   491  		return tc.Intersect(v)
   492  	case versionPair:
   493  		if v.r == tc.r {
   494  			return v.r
   495  		}
   496  	case Revision:
   497  		if v.r == tc {
   498  			return v.r
   499  		}
   500  	case semverConstraint:
   501  		if tv, ok := v.v.(semVersion); ok {
   502  			if tc.Intersect(tv) == v.v {
   503  				return v
   504  			}
   505  		}
   506  		// If the semver intersection failed, we know nothing could work
   507  		return none
   508  	}
   509  
   510  	switch tv := v.v.(type) {
   511  	case plainVersion, branchVersion:
   512  		if c2.Matches(v) {
   513  			return v
   514  		}
   515  	case semVersion:
   516  		if tv2, ok := c2.(semVersion); ok {
   517  			if tv.sv.Equal(tv2.sv) {
   518  				return v
   519  			}
   520  		}
   521  	}
   522  
   523  	return none
   524  }
   525  
   526  // compareVersionType is a sort func helper that makes a coarse-grained sorting
   527  // decision based on version type.
   528  //
   529  // Make sure that l and r have already been converted from versionPair (if
   530  // applicable).
   531  func compareVersionType(l, r Version) int {
   532  	// Big fugly double type switch. No reflect, because this can be smack in a hot loop
   533  	switch l.(type) {
   534  	case Revision:
   535  		switch r.(type) {
   536  		case Revision:
   537  			return 0
   538  		case branchVersion, plainVersion, semVersion:
   539  			return 1
   540  		}
   541  
   542  	case plainVersion:
   543  		switch r.(type) {
   544  		case Revision:
   545  			return -1
   546  		case plainVersion:
   547  			return 0
   548  		case branchVersion, semVersion:
   549  			return 1
   550  		}
   551  
   552  	case branchVersion:
   553  		switch r.(type) {
   554  		case Revision, plainVersion:
   555  			return -1
   556  		case branchVersion:
   557  			return 0
   558  		case semVersion:
   559  			return 1
   560  		}
   561  
   562  	case semVersion:
   563  		switch r.(type) {
   564  		case Revision, branchVersion, plainVersion:
   565  			return -1
   566  		case semVersion:
   567  			return 0
   568  		}
   569  	}
   570  	panic("unknown version type")
   571  }
   572  
   573  // SortForUpgrade sorts a slice of []Version in roughly descending order, so
   574  // that presumably newer versions are visited first. The rules are:
   575  //
   576  //  - All semver versions come first, and sort mostly according to the semver
   577  //  2.0 spec (as implemented by github.com/Masterminds/semver lib), with one
   578  //  exception:
   579  //  - Semver versions with a prerelease are after *all* non-prerelease semver.
   580  //  Within this subset they are sorted first by their numerical component, then
   581  //  lexicographically by their prerelease version.
   582  //  - The default branch(es) is next; the exact semantics of that are specific
   583  //  to the underlying source.
   584  //  - All other branches come next, sorted lexicographically.
   585  //  - All non-semver versions (tags) are next, sorted lexicographically.
   586  //  - Revisions, if any, are last, sorted lexicographically. Revisions do not
   587  //  typically appear in version lists, so the only invariant we maintain is
   588  //  determinism - deeper semantics, like chronology or topology, do not matter.
   589  //
   590  // So, given a slice of the following versions:
   591  //
   592  //  - Branch: master devel
   593  //  - Semver tags: v1.0.0, v1.1.0, v1.1.0-alpha1
   594  //  - Non-semver tags: footag
   595  //  - Revision: f6e74e8d
   596  //
   597  // Sorting for upgrade will result in the following slice.
   598  //
   599  //  [v1.1.0 v1.0.0 v1.1.0-alpha1 footag devel master f6e74e8d]
   600  func SortForUpgrade(vl []Version) {
   601  	sort.Sort(upgradeVersionSorter(vl))
   602  }
   603  
   604  // SortPairedForUpgrade has the same behavior as SortForUpgrade, but operates on
   605  // []PairedVersion types.
   606  func SortPairedForUpgrade(vl []PairedVersion) {
   607  	sort.Sort(pvupgradeVersionSorter(vl))
   608  }
   609  
   610  // SortForDowngrade sorts a slice of []Version in roughly ascending order, so
   611  // that presumably older versions are visited first.
   612  //
   613  // This is *not* the same as reversing SortForUpgrade (or you could simply
   614  // sort.Reverse()). The type precedence is the same, including the semver vs.
   615  // semver-with-prerelease relation. Lexicographical comparisons within
   616  // non-semver tags, branches, and revisions remains the same as well; because we
   617  // treat these domains as having no ordering relation, there can be no real
   618  // concept of "upgrade" vs "downgrade", so there is no reason to reverse them.
   619  //
   620  // Thus, the only binary relation that is reversed for downgrade is within-type
   621  // comparisons for semver.
   622  //
   623  // So, given a slice of the following versions:
   624  //
   625  //  - Branch: master devel
   626  //  - Semver tags: v1.0.0, v1.1.0, v1.1.0-alpha1
   627  //  - Non-semver tags: footag
   628  //  - Revision: f6e74e8d
   629  //
   630  // Sorting for downgrade will result in the following slice.
   631  //
   632  //  [v1.0.0 v1.1.0 v1.1.0-alpha1 footag devel master f6e74e8d]
   633  func SortForDowngrade(vl []Version) {
   634  	sort.Sort(downgradeVersionSorter(vl))
   635  }
   636  
   637  // SortPairedForDowngrade has the same behavior as SortForDowngrade, but
   638  // operates on []PairedVersion types.
   639  func SortPairedForDowngrade(vl []PairedVersion) {
   640  	sort.Sort(pvdowngradeVersionSorter(vl))
   641  }
   642  
   643  type upgradeVersionSorter []Version
   644  
   645  func (vs upgradeVersionSorter) Len() int {
   646  	return len(vs)
   647  }
   648  
   649  func (vs upgradeVersionSorter) Swap(i, j int) {
   650  	vs[i], vs[j] = vs[j], vs[i]
   651  }
   652  
   653  func (vs upgradeVersionSorter) Less(i, j int) bool {
   654  	l, r := vs[i], vs[j]
   655  	return vLess(l, r, false)
   656  }
   657  
   658  type pvupgradeVersionSorter []PairedVersion
   659  
   660  func (vs pvupgradeVersionSorter) Len() int {
   661  	return len(vs)
   662  }
   663  
   664  func (vs pvupgradeVersionSorter) Swap(i, j int) {
   665  	vs[i], vs[j] = vs[j], vs[i]
   666  }
   667  func (vs pvupgradeVersionSorter) Less(i, j int) bool {
   668  	l, r := vs[i], vs[j]
   669  	return vLess(l, r, false)
   670  }
   671  
   672  type downgradeVersionSorter []Version
   673  
   674  func (vs downgradeVersionSorter) Len() int {
   675  	return len(vs)
   676  }
   677  
   678  func (vs downgradeVersionSorter) Swap(i, j int) {
   679  	vs[i], vs[j] = vs[j], vs[i]
   680  }
   681  
   682  func (vs downgradeVersionSorter) Less(i, j int) bool {
   683  	l, r := vs[i], vs[j]
   684  	return vLess(l, r, true)
   685  }
   686  
   687  type pvdowngradeVersionSorter []PairedVersion
   688  
   689  func (vs pvdowngradeVersionSorter) Len() int {
   690  	return len(vs)
   691  }
   692  
   693  func (vs pvdowngradeVersionSorter) Swap(i, j int) {
   694  	vs[i], vs[j] = vs[j], vs[i]
   695  }
   696  func (vs pvdowngradeVersionSorter) Less(i, j int) bool {
   697  	l, r := vs[i], vs[j]
   698  	return vLess(l, r, true)
   699  }
   700  
   701  func vLess(l, r Version, down bool) bool {
   702  	if tl, ispair := l.(versionPair); ispair {
   703  		l = tl.v
   704  	}
   705  	if tr, ispair := r.(versionPair); ispair {
   706  		r = tr.v
   707  	}
   708  
   709  	switch compareVersionType(l, r) {
   710  	case -1:
   711  		return true
   712  	case 1:
   713  		return false
   714  	case 0:
   715  		break
   716  	default:
   717  		panic("unreachable")
   718  	}
   719  
   720  	switch tl := l.(type) {
   721  	case branchVersion:
   722  		tr := r.(branchVersion)
   723  		if tl.isDefault != tr.isDefault {
   724  			// If they're not both defaults, then return the left val: if left
   725  			// is the default, then it is "less" (true) b/c we want it earlier.
   726  			// Else the right is the default, and so the left should be later
   727  			// (false).
   728  			return tl.isDefault
   729  		}
   730  		return l.String() < r.String()
   731  	case Revision, plainVersion:
   732  		// All that we can do now is alpha sort
   733  		return l.String() < r.String()
   734  	}
   735  
   736  	// This ensures that pre-release versions are always sorted after ALL
   737  	// full-release versions
   738  	lsv, rsv := l.(semVersion).sv, r.(semVersion).sv
   739  	lpre, rpre := lsv.Prerelease() == "", rsv.Prerelease() == ""
   740  	if (lpre && !rpre) || (!lpre && rpre) {
   741  		return lpre
   742  	}
   743  
   744  	if down {
   745  		return lsv.LessThan(rsv)
   746  	}
   747  	return lsv.GreaterThan(rsv)
   748  }
   749  
   750  func hidePair(pvl []PairedVersion) []Version {
   751  	vl := make([]Version, 0, len(pvl))
   752  	for _, v := range pvl {
   753  		vl = append(vl, v)
   754  	}
   755  	return vl
   756  }
   757  
   758  // VersionComponentStrings decomposes a Version into the underlying number, branch and revision
   759  func VersionComponentStrings(v Version) (revision string, branch string, version string) {
   760  	switch tv := v.(type) {
   761  	case UnpairedVersion:
   762  	case Revision:
   763  		revision = tv.String()
   764  	case PairedVersion:
   765  		revision = tv.Underlying().String()
   766  	}
   767  
   768  	switch v.Type() {
   769  	case IsBranch:
   770  		branch = v.String()
   771  	case IsSemver, IsVersion:
   772  		version = v.String()
   773  	}
   774  
   775  	return
   776  }