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

     1  package gps
     2  
     3  // versionUnifier facilitates cross-type version comparison and set operations.
     4  type versionUnifier struct {
     5  	b   sourceBridge
     6  	mtr *metrics
     7  }
     8  
     9  // pairVersion takes an UnpairedVersion and attempts to pair it with an
    10  // underlying Revision in the context of the provided ProjectIdentifier by
    11  // consulting the canonical version list.
    12  func (vu versionUnifier) pairVersion(id ProjectIdentifier, v UnpairedVersion) PairedVersion {
    13  	vl, err := vu.b.listVersions(id)
    14  	if err != nil {
    15  		return nil
    16  	}
    17  
    18  	vu.mtr.push("b-pair-version")
    19  	// doing it like this is a bit sloppy
    20  	for _, v2 := range vl {
    21  		if p, ok := v2.(PairedVersion); ok {
    22  			if p.Matches(v) {
    23  				vu.mtr.pop()
    24  				return p
    25  			}
    26  		}
    27  	}
    28  
    29  	vu.mtr.pop()
    30  	return nil
    31  }
    32  
    33  // pairRevision takes a Revision  and attempts to pair it with all possible
    34  // versionsby consulting the canonical version list of the provided
    35  // ProjectIdentifier.
    36  func (vu versionUnifier) pairRevision(id ProjectIdentifier, r Revision) []Version {
    37  	vl, err := vu.b.listVersions(id)
    38  	if err != nil {
    39  		return nil
    40  	}
    41  
    42  	vu.mtr.push("b-pair-rev")
    43  	p := []Version{r}
    44  	// doing it like this is a bit sloppy
    45  	for _, v2 := range vl {
    46  		if pv, ok := v2.(PairedVersion); ok {
    47  			if pv.Matches(r) {
    48  				p = append(p, pv)
    49  			}
    50  		}
    51  	}
    52  
    53  	vu.mtr.pop()
    54  	return p
    55  }
    56  
    57  // matches performs a typical match check between the provided version and
    58  // constraint. If that basic check fails and the provided version is incomplete
    59  // (e.g. an unpaired version or bare revision), it will attempt to gather more
    60  // information on one or the other and re-perform the comparison.
    61  func (vu versionUnifier) matches(id ProjectIdentifier, c Constraint, v Version) bool {
    62  	if c.Matches(v) {
    63  		return true
    64  	}
    65  
    66  	vu.mtr.push("b-matches")
    67  	// This approach is slightly wasteful, but just SO much less verbose, and
    68  	// more easily understood.
    69  	vtu := vu.createTypeUnion(id, v)
    70  
    71  	var uc Constraint
    72  	if cv, ok := c.(Version); ok {
    73  		uc = vu.createTypeUnion(id, cv)
    74  	} else {
    75  		uc = c
    76  	}
    77  
    78  	vu.mtr.pop()
    79  	return uc.Matches(vtu)
    80  }
    81  
    82  // matchesAny is the authoritative version of Constraint.MatchesAny.
    83  func (vu versionUnifier) matchesAny(id ProjectIdentifier, c1, c2 Constraint) bool {
    84  	if c1.MatchesAny(c2) {
    85  		return true
    86  	}
    87  
    88  	vu.mtr.push("b-matches-any")
    89  	// This approach is slightly wasteful, but just SO much less verbose, and
    90  	// more easily understood.
    91  	var uc1, uc2 Constraint
    92  	if v1, ok := c1.(Version); ok {
    93  		uc1 = vu.createTypeUnion(id, v1)
    94  	} else {
    95  		uc1 = c1
    96  	}
    97  
    98  	if v2, ok := c2.(Version); ok {
    99  		uc2 = vu.createTypeUnion(id, v2)
   100  	} else {
   101  		uc2 = c2
   102  	}
   103  
   104  	vu.mtr.pop()
   105  	return uc1.MatchesAny(uc2)
   106  }
   107  
   108  // intersect is the authoritative version of Constraint.Intersect.
   109  func (vu versionUnifier) intersect(id ProjectIdentifier, c1, c2 Constraint) Constraint {
   110  	rc := c1.Intersect(c2)
   111  	if rc != none {
   112  		return rc
   113  	}
   114  
   115  	vu.mtr.push("b-intersect")
   116  	// This approach is slightly wasteful, but just SO much less verbose, and
   117  	// more easily understood.
   118  	var uc1, uc2 Constraint
   119  	if v1, ok := c1.(Version); ok {
   120  		uc1 = vu.createTypeUnion(id, v1)
   121  	} else {
   122  		uc1 = c1
   123  	}
   124  
   125  	if v2, ok := c2.(Version); ok {
   126  		uc2 = vu.createTypeUnion(id, v2)
   127  	} else {
   128  		uc2 = c2
   129  	}
   130  
   131  	vu.mtr.pop()
   132  	return uc1.Intersect(uc2)
   133  }
   134  
   135  // createTypeUnion creates a versionTypeUnion for the provided version.
   136  //
   137  // This union may (and typically will) end up being nothing more than the single
   138  // input version, but creating a versionTypeUnion guarantees that 'local'
   139  // constraint checks (direct method calls) are authoritative.
   140  func (vu versionUnifier) createTypeUnion(id ProjectIdentifier, v Version) versionTypeUnion {
   141  	switch tv := v.(type) {
   142  	case Revision:
   143  		return versionTypeUnion(vu.pairRevision(id, tv))
   144  	case PairedVersion:
   145  		return versionTypeUnion(vu.pairRevision(id, tv.Underlying()))
   146  	case UnpairedVersion:
   147  		pv := vu.pairVersion(id, tv)
   148  		if pv == nil {
   149  			return versionTypeUnion{tv}
   150  		}
   151  
   152  		return versionTypeUnion(vu.pairRevision(id, pv.Underlying()))
   153  	}
   154  
   155  	return nil
   156  }
   157  
   158  // versionTypeUnion represents a set of versions that are, within the scope of
   159  // this solver run, equivalent.
   160  //
   161  // The simple case here is just a pair - a normal version plus its underlying
   162  // revision - but if a tag or branch point at the same rev, then we consider
   163  // them equivalent. Again, however, this equivalency is short-lived; it must be
   164  // re-assessed during every solver run.
   165  //
   166  // The union members are treated as being OR'd together:  all constraint
   167  // operations attempt each member, and will take the most open/optimistic
   168  // answer.
   169  //
   170  // This technically does allow tags to match branches - something we otherwise
   171  // try hard to avoid - but because the original input constraint never actually
   172  // changes (and is never written out in the Solution), there's no harmful case
   173  // of a user suddenly riding a branch when they expected a fixed tag.
   174  type versionTypeUnion []Version
   175  
   176  // This should generally not be called, but is required for the interface. If it
   177  // is called, we have a bigger problem (the type has escaped the solver); thus,
   178  // panic.
   179  func (vtu versionTypeUnion) String() string {
   180  	panic("versionTypeUnion should never be turned into a string; it is solver internal-only")
   181  }
   182  
   183  func (vtu versionTypeUnion) typedString() string {
   184  	panic("versionTypeUnion should never be turned into a string; it is solver internal-only")
   185  }
   186  
   187  // This should generally not be called, but is required for the interface. If it
   188  // is called, we have a bigger problem (the type has escaped the solver); thus,
   189  // panic.
   190  func (vtu versionTypeUnion) Type() VersionType {
   191  	panic("versionTypeUnion should never need to answer a Type() call; it is solver internal-only")
   192  }
   193  
   194  // Matches takes a version, and returns true if that version matches any version
   195  // contained in the union.
   196  //
   197  // This DOES allow tags to match branches, albeit indirectly through a revision.
   198  func (vtu versionTypeUnion) Matches(v Version) bool {
   199  	vtu2, otherIs := v.(versionTypeUnion)
   200  
   201  	for _, v1 := range vtu {
   202  		if otherIs {
   203  			for _, v2 := range vtu2 {
   204  				if v1.Matches(v2) {
   205  					return true
   206  				}
   207  			}
   208  		} else if v1.Matches(v) {
   209  			return true
   210  		}
   211  	}
   212  
   213  	return false
   214  }
   215  
   216  // MatchesAny returns true if any of the contained versions (which are also
   217  // constraints) in the union successfully MatchAny with the provided
   218  // constraint.
   219  func (vtu versionTypeUnion) MatchesAny(c Constraint) bool {
   220  	vtu2, otherIs := c.(versionTypeUnion)
   221  
   222  	for _, v1 := range vtu {
   223  		if otherIs {
   224  			for _, v2 := range vtu2 {
   225  				if v1.MatchesAny(v2) {
   226  					return true
   227  				}
   228  			}
   229  		} else if v1.MatchesAny(c) {
   230  			return true
   231  		}
   232  	}
   233  
   234  	return false
   235  }
   236  
   237  // Intersect takes a constraint, and attempts to intersect it with all the
   238  // versions contained in the union until one returns non-none. If that never
   239  // happens, then none is returned.
   240  //
   241  // In order to avoid weird version floating elsewhere in the solver, the union
   242  // always returns the input constraint. (This is probably obviously correct, but
   243  // is still worth noting.)
   244  func (vtu versionTypeUnion) Intersect(c Constraint) Constraint {
   245  	vtu2, otherIs := c.(versionTypeUnion)
   246  
   247  	for _, v1 := range vtu {
   248  		if otherIs {
   249  			for _, v2 := range vtu2 {
   250  				if rc := v1.Intersect(v2); rc != none {
   251  					return rc
   252  				}
   253  			}
   254  		} else if rc := v1.Intersect(c); rc != none {
   255  			return rc
   256  		}
   257  	}
   258  
   259  	return none
   260  }