mirror of
				https://github.com/superseriousbusiness/gotosocial.git
				synced 2025-11-02 16:52:25 -06:00 
			
		
		
		
	
		
			
	
	
		
			251 lines
		
	
	
	
		
			8.4 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
		
		
			
		
	
	
			251 lines
		
	
	
	
		
			8.4 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| 
								 | 
							
								// Copyright 2018 The Go Authors. All rights reserved.
							 | 
						||
| 
								 | 
							
								// Use of this source code is governed by a BSD-style
							 | 
						||
| 
								 | 
							
								// license that can be found in the LICENSE file.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								// Pseudo-versions
							 | 
						||
| 
								 | 
							
								//
							 | 
						||
| 
								 | 
							
								// Code authors are expected to tag the revisions they want users to use,
							 | 
						||
| 
								 | 
							
								// including prereleases. However, not all authors tag versions at all,
							 | 
						||
| 
								 | 
							
								// and not all commits a user might want to try will have tags.
							 | 
						||
| 
								 | 
							
								// A pseudo-version is a version with a special form that allows us to
							 | 
						||
| 
								 | 
							
								// address an untagged commit and order that version with respect to
							 | 
						||
| 
								 | 
							
								// other versions we might encounter.
							 | 
						||
| 
								 | 
							
								//
							 | 
						||
| 
								 | 
							
								// A pseudo-version takes one of the general forms:
							 | 
						||
| 
								 | 
							
								//
							 | 
						||
| 
								 | 
							
								//	(1) vX.0.0-yyyymmddhhmmss-abcdef123456
							 | 
						||
| 
								 | 
							
								//	(2) vX.Y.(Z+1)-0.yyyymmddhhmmss-abcdef123456
							 | 
						||
| 
								 | 
							
								//	(3) vX.Y.(Z+1)-0.yyyymmddhhmmss-abcdef123456+incompatible
							 | 
						||
| 
								 | 
							
								//	(4) vX.Y.Z-pre.0.yyyymmddhhmmss-abcdef123456
							 | 
						||
| 
								 | 
							
								//	(5) vX.Y.Z-pre.0.yyyymmddhhmmss-abcdef123456+incompatible
							 | 
						||
| 
								 | 
							
								//
							 | 
						||
| 
								 | 
							
								// If there is no recently tagged version with the right major version vX,
							 | 
						||
| 
								 | 
							
								// then form (1) is used, creating a space of pseudo-versions at the bottom
							 | 
						||
| 
								 | 
							
								// of the vX version range, less than any tagged version, including the unlikely v0.0.0.
							 | 
						||
| 
								 | 
							
								//
							 | 
						||
| 
								 | 
							
								// If the most recent tagged version before the target commit is vX.Y.Z or vX.Y.Z+incompatible,
							 | 
						||
| 
								 | 
							
								// then the pseudo-version uses form (2) or (3), making it a prerelease for the next
							 | 
						||
| 
								 | 
							
								// possible semantic version after vX.Y.Z. The leading 0 segment in the prerelease string
							 | 
						||
| 
								 | 
							
								// ensures that the pseudo-version compares less than possible future explicit prereleases
							 | 
						||
| 
								 | 
							
								// like vX.Y.(Z+1)-rc1 or vX.Y.(Z+1)-1.
							 | 
						||
| 
								 | 
							
								//
							 | 
						||
| 
								 | 
							
								// If the most recent tagged version before the target commit is vX.Y.Z-pre or vX.Y.Z-pre+incompatible,
							 | 
						||
| 
								 | 
							
								// then the pseudo-version uses form (4) or (5), making it a slightly later prerelease.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								package module
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								import (
							 | 
						||
| 
								 | 
							
									"errors"
							 | 
						||
| 
								 | 
							
									"fmt"
							 | 
						||
| 
								 | 
							
									"strings"
							 | 
						||
| 
								 | 
							
									"time"
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									"golang.org/x/mod/internal/lazyregexp"
							 | 
						||
| 
								 | 
							
									"golang.org/x/mod/semver"
							 | 
						||
| 
								 | 
							
								)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								var pseudoVersionRE = lazyregexp.New(`^v[0-9]+\.(0\.0-|\d+\.\d+-([^+]*\.)?0\.)\d{14}-[A-Za-z0-9]+(\+[0-9A-Za-z-]+(\.[0-9A-Za-z-]+)*)?$`)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								const PseudoVersionTimestampFormat = "20060102150405"
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								// PseudoVersion returns a pseudo-version for the given major version ("v1")
							 | 
						||
| 
								 | 
							
								// preexisting older tagged version ("" or "v1.2.3" or "v1.2.3-pre"), revision time,
							 | 
						||
| 
								 | 
							
								// and revision identifier (usually a 12-byte commit hash prefix).
							 | 
						||
| 
								 | 
							
								func PseudoVersion(major, older string, t time.Time, rev string) string {
							 | 
						||
| 
								 | 
							
									if major == "" {
							 | 
						||
| 
								 | 
							
										major = "v0"
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
									segment := fmt.Sprintf("%s-%s", t.UTC().Format(PseudoVersionTimestampFormat), rev)
							 | 
						||
| 
								 | 
							
									build := semver.Build(older)
							 | 
						||
| 
								 | 
							
									older = semver.Canonical(older)
							 | 
						||
| 
								 | 
							
									if older == "" {
							 | 
						||
| 
								 | 
							
										return major + ".0.0-" + segment // form (1)
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
									if semver.Prerelease(older) != "" {
							 | 
						||
| 
								 | 
							
										return older + ".0." + segment + build // form (4), (5)
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									// Form (2), (3).
							 | 
						||
| 
								 | 
							
									// Extract patch from vMAJOR.MINOR.PATCH
							 | 
						||
| 
								 | 
							
									i := strings.LastIndex(older, ".") + 1
							 | 
						||
| 
								 | 
							
									v, patch := older[:i], older[i:]
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									// Reassemble.
							 | 
						||
| 
								 | 
							
									return v + incDecimal(patch) + "-0." + segment + build
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								// ZeroPseudoVersion returns a pseudo-version with a zero timestamp and
							 | 
						||
| 
								 | 
							
								// revision, which may be used as a placeholder.
							 | 
						||
| 
								 | 
							
								func ZeroPseudoVersion(major string) string {
							 | 
						||
| 
								 | 
							
									return PseudoVersion(major, "", time.Time{}, "000000000000")
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								// incDecimal returns the decimal string incremented by 1.
							 | 
						||
| 
								 | 
							
								func incDecimal(decimal string) string {
							 | 
						||
| 
								 | 
							
									// Scan right to left turning 9s to 0s until you find a digit to increment.
							 | 
						||
| 
								 | 
							
									digits := []byte(decimal)
							 | 
						||
| 
								 | 
							
									i := len(digits) - 1
							 | 
						||
| 
								 | 
							
									for ; i >= 0 && digits[i] == '9'; i-- {
							 | 
						||
| 
								 | 
							
										digits[i] = '0'
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
									if i >= 0 {
							 | 
						||
| 
								 | 
							
										digits[i]++
							 | 
						||
| 
								 | 
							
									} else {
							 | 
						||
| 
								 | 
							
										// digits is all zeros
							 | 
						||
| 
								 | 
							
										digits[0] = '1'
							 | 
						||
| 
								 | 
							
										digits = append(digits, '0')
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
									return string(digits)
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								// decDecimal returns the decimal string decremented by 1, or the empty string
							 | 
						||
| 
								 | 
							
								// if the decimal is all zeroes.
							 | 
						||
| 
								 | 
							
								func decDecimal(decimal string) string {
							 | 
						||
| 
								 | 
							
									// Scan right to left turning 0s to 9s until you find a digit to decrement.
							 | 
						||
| 
								 | 
							
									digits := []byte(decimal)
							 | 
						||
| 
								 | 
							
									i := len(digits) - 1
							 | 
						||
| 
								 | 
							
									for ; i >= 0 && digits[i] == '0'; i-- {
							 | 
						||
| 
								 | 
							
										digits[i] = '9'
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
									if i < 0 {
							 | 
						||
| 
								 | 
							
										// decimal is all zeros
							 | 
						||
| 
								 | 
							
										return ""
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
									if i == 0 && digits[i] == '1' && len(digits) > 1 {
							 | 
						||
| 
								 | 
							
										digits = digits[1:]
							 | 
						||
| 
								 | 
							
									} else {
							 | 
						||
| 
								 | 
							
										digits[i]--
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
									return string(digits)
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								// IsPseudoVersion reports whether v is a pseudo-version.
							 | 
						||
| 
								 | 
							
								func IsPseudoVersion(v string) bool {
							 | 
						||
| 
								 | 
							
									return strings.Count(v, "-") >= 2 && semver.IsValid(v) && pseudoVersionRE.MatchString(v)
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								// IsZeroPseudoVersion returns whether v is a pseudo-version with a zero base,
							 | 
						||
| 
								 | 
							
								// timestamp, and revision, as returned by [ZeroPseudoVersion].
							 | 
						||
| 
								 | 
							
								func IsZeroPseudoVersion(v string) bool {
							 | 
						||
| 
								 | 
							
									return v == ZeroPseudoVersion(semver.Major(v))
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								// PseudoVersionTime returns the time stamp of the pseudo-version v.
							 | 
						||
| 
								 | 
							
								// It returns an error if v is not a pseudo-version or if the time stamp
							 | 
						||
| 
								 | 
							
								// embedded in the pseudo-version is not a valid time.
							 | 
						||
| 
								 | 
							
								func PseudoVersionTime(v string) (time.Time, error) {
							 | 
						||
| 
								 | 
							
									_, timestamp, _, _, err := parsePseudoVersion(v)
							 | 
						||
| 
								 | 
							
									if err != nil {
							 | 
						||
| 
								 | 
							
										return time.Time{}, err
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
									t, err := time.Parse("20060102150405", timestamp)
							 | 
						||
| 
								 | 
							
									if err != nil {
							 | 
						||
| 
								 | 
							
										return time.Time{}, &InvalidVersionError{
							 | 
						||
| 
								 | 
							
											Version: v,
							 | 
						||
| 
								 | 
							
											Pseudo:  true,
							 | 
						||
| 
								 | 
							
											Err:     fmt.Errorf("malformed time %q", timestamp),
							 | 
						||
| 
								 | 
							
										}
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
									return t, nil
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								// PseudoVersionRev returns the revision identifier of the pseudo-version v.
							 | 
						||
| 
								 | 
							
								// It returns an error if v is not a pseudo-version.
							 | 
						||
| 
								 | 
							
								func PseudoVersionRev(v string) (rev string, err error) {
							 | 
						||
| 
								 | 
							
									_, _, rev, _, err = parsePseudoVersion(v)
							 | 
						||
| 
								 | 
							
									return
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								// PseudoVersionBase returns the canonical parent version, if any, upon which
							 | 
						||
| 
								 | 
							
								// the pseudo-version v is based.
							 | 
						||
| 
								 | 
							
								//
							 | 
						||
| 
								 | 
							
								// If v has no parent version (that is, if it is "vX.0.0-[…]"),
							 | 
						||
| 
								 | 
							
								// PseudoVersionBase returns the empty string and a nil error.
							 | 
						||
| 
								 | 
							
								func PseudoVersionBase(v string) (string, error) {
							 | 
						||
| 
								 | 
							
									base, _, _, build, err := parsePseudoVersion(v)
							 | 
						||
| 
								 | 
							
									if err != nil {
							 | 
						||
| 
								 | 
							
										return "", err
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									switch pre := semver.Prerelease(base); pre {
							 | 
						||
| 
								 | 
							
									case "":
							 | 
						||
| 
								 | 
							
										// vX.0.0-yyyymmddhhmmss-abcdef123456 → ""
							 | 
						||
| 
								 | 
							
										if build != "" {
							 | 
						||
| 
								 | 
							
											// Pseudo-versions of the form vX.0.0-yyyymmddhhmmss-abcdef123456+incompatible
							 | 
						||
| 
								 | 
							
											// are nonsensical: the "vX.0.0-" prefix implies that there is no parent tag,
							 | 
						||
| 
								 | 
							
											// but the "+incompatible" suffix implies that the major version of
							 | 
						||
| 
								 | 
							
											// the parent tag is not compatible with the module's import path.
							 | 
						||
| 
								 | 
							
											//
							 | 
						||
| 
								 | 
							
											// There are a few such entries in the index generated by proxy.golang.org,
							 | 
						||
| 
								 | 
							
											// but we believe those entries were generated by the proxy itself.
							 | 
						||
| 
								 | 
							
											return "", &InvalidVersionError{
							 | 
						||
| 
								 | 
							
												Version: v,
							 | 
						||
| 
								 | 
							
												Pseudo:  true,
							 | 
						||
| 
								 | 
							
												Err:     fmt.Errorf("lacks base version, but has build metadata %q", build),
							 | 
						||
| 
								 | 
							
											}
							 | 
						||
| 
								 | 
							
										}
							 | 
						||
| 
								 | 
							
										return "", nil
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									case "-0":
							 | 
						||
| 
								 | 
							
										// vX.Y.(Z+1)-0.yyyymmddhhmmss-abcdef123456 → vX.Y.Z
							 | 
						||
| 
								 | 
							
										// vX.Y.(Z+1)-0.yyyymmddhhmmss-abcdef123456+incompatible → vX.Y.Z+incompatible
							 | 
						||
| 
								 | 
							
										base = strings.TrimSuffix(base, pre)
							 | 
						||
| 
								 | 
							
										i := strings.LastIndexByte(base, '.')
							 | 
						||
| 
								 | 
							
										if i < 0 {
							 | 
						||
| 
								 | 
							
											panic("base from parsePseudoVersion missing patch number: " + base)
							 | 
						||
| 
								 | 
							
										}
							 | 
						||
| 
								 | 
							
										patch := decDecimal(base[i+1:])
							 | 
						||
| 
								 | 
							
										if patch == "" {
							 | 
						||
| 
								 | 
							
											// vX.0.0-0 is invalid, but has been observed in the wild in the index
							 | 
						||
| 
								 | 
							
											// generated by requests to proxy.golang.org.
							 | 
						||
| 
								 | 
							
											//
							 | 
						||
| 
								 | 
							
											// NOTE(bcmills): I cannot find a historical bug that accounts for
							 | 
						||
| 
								 | 
							
											// pseudo-versions of this form, nor have I seen such versions in any
							 | 
						||
| 
								 | 
							
											// actual go.mod files. If we find actual examples of this form and a
							 | 
						||
| 
								 | 
							
											// reasonable theory of how they came into existence, it seems fine to
							 | 
						||
| 
								 | 
							
											// treat them as equivalent to vX.0.0 (especially since the invalid
							 | 
						||
| 
								 | 
							
											// pseudo-versions have lower precedence than the real ones). For now, we
							 | 
						||
| 
								 | 
							
											// reject them.
							 | 
						||
| 
								 | 
							
											return "", &InvalidVersionError{
							 | 
						||
| 
								 | 
							
												Version: v,
							 | 
						||
| 
								 | 
							
												Pseudo:  true,
							 | 
						||
| 
								 | 
							
												Err:     fmt.Errorf("version before %s would have negative patch number", base),
							 | 
						||
| 
								 | 
							
											}
							 | 
						||
| 
								 | 
							
										}
							 | 
						||
| 
								 | 
							
										return base[:i+1] + patch + build, nil
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									default:
							 | 
						||
| 
								 | 
							
										// vX.Y.Z-pre.0.yyyymmddhhmmss-abcdef123456 → vX.Y.Z-pre
							 | 
						||
| 
								 | 
							
										// vX.Y.Z-pre.0.yyyymmddhhmmss-abcdef123456+incompatible → vX.Y.Z-pre+incompatible
							 | 
						||
| 
								 | 
							
										if !strings.HasSuffix(base, ".0") {
							 | 
						||
| 
								 | 
							
											panic(`base from parsePseudoVersion missing ".0" before date: ` + base)
							 | 
						||
| 
								 | 
							
										}
							 | 
						||
| 
								 | 
							
										return strings.TrimSuffix(base, ".0") + build, nil
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								var errPseudoSyntax = errors.New("syntax error")
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								func parsePseudoVersion(v string) (base, timestamp, rev, build string, err error) {
							 | 
						||
| 
								 | 
							
									if !IsPseudoVersion(v) {
							 | 
						||
| 
								 | 
							
										return "", "", "", "", &InvalidVersionError{
							 | 
						||
| 
								 | 
							
											Version: v,
							 | 
						||
| 
								 | 
							
											Pseudo:  true,
							 | 
						||
| 
								 | 
							
											Err:     errPseudoSyntax,
							 | 
						||
| 
								 | 
							
										}
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
									build = semver.Build(v)
							 | 
						||
| 
								 | 
							
									v = strings.TrimSuffix(v, build)
							 | 
						||
| 
								 | 
							
									j := strings.LastIndex(v, "-")
							 | 
						||
| 
								 | 
							
									v, rev = v[:j], v[j+1:]
							 | 
						||
| 
								 | 
							
									i := strings.LastIndex(v, "-")
							 | 
						||
| 
								 | 
							
									if j := strings.LastIndex(v, "."); j > i {
							 | 
						||
| 
								 | 
							
										base = v[:j] // "vX.Y.Z-pre.0" or "vX.Y.(Z+1)-0"
							 | 
						||
| 
								 | 
							
										timestamp = v[j+1:]
							 | 
						||
| 
								 | 
							
									} else {
							 | 
						||
| 
								 | 
							
										base = v[:i] // "vX.0.0"
							 | 
						||
| 
								 | 
							
										timestamp = v[i+1:]
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
									return base, timestamp, rev, build, nil
							 | 
						||
| 
								 | 
							
								}
							 |