github.com/golang/dep@v0.5.4/gps/version_queue.go (about)

     1  // Copyright 2017 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  package gps
     6  
     7  import (
     8  	"fmt"
     9  	"strings"
    10  )
    11  
    12  type failedVersion struct {
    13  	v Version
    14  	f error
    15  }
    16  
    17  type versionQueue struct {
    18  	id           ProjectIdentifier
    19  	pi           []Version
    20  	lockv, prefv Version
    21  	fails        []failedVersion
    22  	b            sourceBridge
    23  	failed       bool
    24  	allLoaded    bool
    25  	adverr       error
    26  }
    27  
    28  func newVersionQueue(id ProjectIdentifier, lockv, prefv Version, b sourceBridge) (*versionQueue, error) {
    29  	vq := &versionQueue{
    30  		id: id,
    31  		b:  b,
    32  	}
    33  
    34  	// Lock goes in first, if present
    35  	if lockv != nil {
    36  		vq.lockv = lockv
    37  		vq.pi = append(vq.pi, lockv)
    38  	}
    39  
    40  	// Preferred version next
    41  	if prefv != nil {
    42  		vq.prefv = prefv
    43  		vq.pi = append(vq.pi, prefv)
    44  	}
    45  
    46  	if len(vq.pi) == 0 {
    47  		var err error
    48  		vq.pi, err = vq.b.listVersions(vq.id)
    49  		if err != nil {
    50  			// TODO(sdboyer) pushing this error this early entails that we
    51  			// unconditionally deep scan (e.g. vendor), as well as hitting the
    52  			// network.
    53  			return nil, err
    54  		}
    55  		vq.allLoaded = true
    56  	}
    57  
    58  	return vq, nil
    59  }
    60  
    61  func (vq *versionQueue) current() Version {
    62  	if len(vq.pi) > 0 {
    63  		return vq.pi[0]
    64  	}
    65  
    66  	return nil
    67  }
    68  
    69  // advance moves the versionQueue forward to the next available version,
    70  // recording the failure that eliminated the current version.
    71  func (vq *versionQueue) advance(fail error) error {
    72  	// Nothing in the queue means...nothing in the queue, nicely enough
    73  	if vq.adverr != nil || len(vq.pi) == 0 { // should be a redundant check, but just in case
    74  		return vq.adverr
    75  	}
    76  
    77  	// Record the fail reason and pop the queue
    78  	vq.fails = append(vq.fails, failedVersion{
    79  		v: vq.pi[0],
    80  		f: fail,
    81  	})
    82  	vq.pi = vq.pi[1:]
    83  
    84  	// *now*, if the queue is empty, ensure all versions have been loaded
    85  	if len(vq.pi) == 0 {
    86  		if vq.allLoaded {
    87  			// This branch gets hit when the queue is first fully exhausted,
    88  			// after a previous advance() already called ListVersions().
    89  			return nil
    90  		}
    91  		vq.allLoaded = true
    92  
    93  		var vltmp []Version
    94  		vltmp, vq.adverr = vq.b.listVersions(vq.id)
    95  		if vq.adverr != nil {
    96  			return vq.adverr
    97  		}
    98  		// defensive copy - calling listVersions here means slice contents may
    99  		// be modified when removing prefv/lockv.
   100  		vq.pi = make([]Version, len(vltmp))
   101  		copy(vq.pi, vltmp)
   102  
   103  		// search for and remove lockv and prefv, in a pointer GC-safe manner
   104  		//
   105  		// could use the version comparator for binary search here to avoid
   106  		// O(n) each time...if it matters
   107  		var delkeys []int
   108  		for k, pi := range vq.pi {
   109  			if pi == vq.lockv || pi == vq.prefv {
   110  				delkeys = append(delkeys, k)
   111  			}
   112  		}
   113  
   114  		for k, dk := range delkeys {
   115  			dk -= k
   116  			copy(vq.pi[dk:], vq.pi[dk+1:])
   117  			// write nil to final position for GC safety
   118  			vq.pi[len(vq.pi)-1] = nil
   119  			vq.pi = vq.pi[:len(vq.pi)-1]
   120  		}
   121  
   122  		if len(vq.pi) == 0 {
   123  			// If listing versions added nothing (new), then return now
   124  			return nil
   125  		}
   126  	}
   127  
   128  	// We're finally sure that there's something in the queue. Remove the
   129  	// failure marker, as the current version may have failed, but the next one
   130  	// hasn't yet
   131  	vq.failed = false
   132  
   133  	// If all have been loaded and the queue is empty, we're definitely out
   134  	// of things to try. Return empty, though, because vq semantics dictate
   135  	// that we don't explicitly indicate the end of the queue here.
   136  	return nil
   137  }
   138  
   139  // isExhausted indicates whether or not the queue has definitely been exhausted,
   140  // in which case it will return true.
   141  //
   142  // It may return false negatives - suggesting that there is more in the queue
   143  // when a subsequent call to current() will be empty. Plan accordingly.
   144  func (vq *versionQueue) isExhausted() bool {
   145  	if !vq.allLoaded {
   146  		return false
   147  	}
   148  	return len(vq.pi) == 0
   149  }
   150  
   151  func (vq *versionQueue) String() string {
   152  	var vs []string
   153  
   154  	for _, v := range vq.pi {
   155  		vs = append(vs, v.String())
   156  	}
   157  	return fmt.Sprintf("[%s]", strings.Join(vs, ", "))
   158  }