github.com/sirkon/goproxy@v1.4.8/internal/modfetch/pseudo.go (about)

     1  // Copyright 2018 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  // pseudo-versions
     6  //
     7  // Code authors are expected to tag the revisions they want users to use,
     8  // including prereleases. However, not all authors tag versions at all,
     9  // and not all commits a user might want to try will have tags.
    10  // A pseudo-version is a version with a special form that allows us to
    11  // address an untagged commit and order that version with respect to
    12  // other versions we might encounter.
    13  //
    14  // A pseudo-version takes one of the general forms:
    15  //
    16  //	(1) vX.0.0-yyyymmddhhmmss-abcdef123456
    17  //	(2) vX.Y.(Z+1)-0.yyyymmddhhmmss-abcdef123456
    18  //	(3) vX.Y.(Z+1)-0.yyyymmddhhmmss-abcdef123456+incompatible
    19  //	(4) vX.Y.Z-pre.0.yyyymmddhhmmss-abcdef123456
    20  //	(5) vX.Y.Z-pre.0.yyyymmddhhmmss-abcdef123456+incompatible
    21  //
    22  // If there is no recently tagged version with the right major version vX,
    23  // then form (1) is used, creating a space of pseudo-versions at the bottom
    24  // of the vX version range, less than any tagged version, including the unlikely v0.0.0.
    25  //
    26  // If the most recent tagged version before the target commit is vX.Y.Z or vX.Y.Z+incompatible,
    27  // then the pseudo-version uses form (2) or (3), making it a prerelease for the next
    28  // possible semantic version after vX.Y.Z. The leading 0 segment in the prerelease string
    29  // ensures that the pseudo-version compares less than possible future explicit prereleases
    30  // like vX.Y.(Z+1)-rc1 or vX.Y.(Z+1)-1.
    31  //
    32  // If the most recent tagged version before the target commit is vX.Y.Z-pre or vX.Y.Z-pre+incompatible,
    33  // then the pseudo-version uses form (4) or (5), making it a slightly later prerelease.
    34  
    35  package modfetch
    36  
    37  import (
    38  	"fmt"
    39  	"github.com/sirkon/goproxy/internal/semver"
    40  	"regexp"
    41  	"strings"
    42  	"time"
    43  )
    44  
    45  // PseudoVersion returns a pseudo-version for the given major version ("v1")
    46  // preexisting older tagged version ("" or "v1.2.3" or "v1.2.3-pre"), revision time,
    47  // and revision identifier (usually a 12-byte commit hash prefix).
    48  func PseudoVersion(major, older string, t time.Time, rev string) string {
    49  	if major == "" {
    50  		major = "v0"
    51  	}
    52  	major = strings.TrimSuffix(major, "-unstable") // make gopkg.in/macaroon-bakery.v2-unstable use "v2"
    53  	segment := fmt.Sprintf("%s-%s", t.UTC().Format("20060102150405"), rev)
    54  	build := semver.Build(older)
    55  	older = semver.Canonical(older)
    56  	if older == "" {
    57  		return major + ".0.0-" + segment // form (1)
    58  	}
    59  	if semver.Prerelease(older) != "" {
    60  		return older + ".0." + segment + build // form (4), (5)
    61  	}
    62  
    63  	// Form (2), (3).
    64  	// extract patch from vMAJOR.MINOR.PATCH
    65  	v := older[:len(older)]
    66  	i := strings.LastIndex(v, ".") + 1
    67  	v, patch := v[:i], v[i:]
    68  
    69  	// Increment PATCH by adding 1 to decimal:
    70  	// scan right to left turning 9s to 0s until you find a digit to increment.
    71  	// (Number might exceed int64, but math/big is overkill.)
    72  	digits := []byte(patch)
    73  	for i = len(digits) - 1; i >= 0 && digits[i] == '9'; i-- {
    74  		digits[i] = '0'
    75  	}
    76  	if i >= 0 {
    77  		digits[i]++
    78  	} else {
    79  		// digits is all zeros
    80  		digits[0] = '1'
    81  		digits = append(digits, '0')
    82  	}
    83  	patch = string(digits)
    84  
    85  	// Reassemble.
    86  	return v + patch + "-0." + segment + build
    87  }
    88  
    89  var pseudoVersionRE = regexp.MustCompile(`^v[0-9]+\.(0\.0-|\d+\.\d+-([^+]*\.)?0\.)\d{14}-[A-Za-z0-9]+(\+incompatible)?$`)
    90  
    91  // IsPseudoVersion reports whether v is a pseudo-version.
    92  func IsPseudoVersion(v string) bool {
    93  	return strings.Count(v, "-") >= 2 && semver.IsValid(v) && pseudoVersionRE.MatchString(v)
    94  }
    95  
    96  // PseudoVersionTime returns the time stamp of the pseudo-version v.
    97  // It returns an error if v is not a pseudo-version or if the time stamp
    98  // embedded in the pseudo-version is not a valid time.
    99  func PseudoVersionTime(v string) (time.Time, error) {
   100  	timestamp, _, err := parsePseudoVersion(v)
   101  	t, err := time.Parse("20060102150405", timestamp)
   102  	if err != nil {
   103  		return time.Time{}, fmt.Errorf("pseudo-version with malformed time %s: %q", timestamp, v)
   104  	}
   105  	return t, nil
   106  }
   107  
   108  // PseudoVersionRev returns the revision identifier of the pseudo-version v.
   109  // It returns an error if v is not a pseudo-version.
   110  func PseudoVersionRev(v string) (rev string, err error) {
   111  	_, rev, err = parsePseudoVersion(v)
   112  	return
   113  }
   114  
   115  func parsePseudoVersion(v string) (timestamp, rev string, err error) {
   116  	if !IsPseudoVersion(v) {
   117  		return "", "", fmt.Errorf("malformed pseudo-version %q", v)
   118  	}
   119  	v = strings.TrimSuffix(v, "+incompatible")
   120  	j := strings.LastIndex(v, "-")
   121  	v, rev = v[:j], v[j+1:]
   122  	i := strings.LastIndex(v, "-")
   123  	if j := strings.LastIndex(v, "."); j > i {
   124  		timestamp = v[j+1:]
   125  	} else {
   126  		timestamp = v[i+1:]
   127  	}
   128  	return timestamp, rev, nil
   129  }