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 }