forked from sdboyer/gps
/
version_queue.go
117 lines (99 loc) · 2.48 KB
/
version_queue.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
package vsolver
import (
"fmt"
"strings"
)
type failedVersion struct {
v Version
f error
}
type versionQueue struct {
id ProjectIdentifier
pi []Version
fails []failedVersion
sm sourceBridge
failed bool
hasLock, allLoaded bool
}
func newVersionQueue(id ProjectIdentifier, lockv atom, sm sourceBridge) (*versionQueue, error) {
vq := &versionQueue{
id: id,
sm: sm,
}
if lockv != nilpa {
vq.hasLock = true
vq.pi = append(vq.pi, lockv.v)
} else {
var err error
vq.pi, err = vq.sm.listVersions(vq.id)
if err != nil {
// TODO pushing this error this early entails that we
// unconditionally deep scan (e.g. vendor), as well as hitting the
// network.
return nil, err
}
vq.allLoaded = true
}
return vq, nil
}
func (vq *versionQueue) current() Version {
if len(vq.pi) > 0 {
return vq.pi[0]
}
return nil
}
func (vq *versionQueue) advance(fail error) (err error) {
// The current version may have failed, but the next one hasn't
vq.failed = false
if len(vq.pi) == 0 {
return
}
vq.fails = append(vq.fails, failedVersion{
v: vq.pi[0],
f: fail,
})
if vq.allLoaded {
vq.pi = vq.pi[1:]
return
}
vq.allLoaded = true
// Can only get here if no lock was initially provided, so we know we
// should have that
lockv := vq.pi[0]
vq.pi, err = vq.sm.listVersions(vq.id)
if err != nil {
return
}
// search for and remove locked version
// TODO should be able to avoid O(n) here each time...if it matters
for k, pi := range vq.pi {
if pi == lockv {
// GC-safe deletion for slice w/pointer elements
//vq.pi, vq.pi[len(vq.pi)-1] = append(vq.pi[:k], vq.pi[k+1:]...), nil
vq.pi = append(vq.pi[:k], vq.pi[k+1:]...)
}
}
// normal end of queue. we don't error; it's left to the caller to infer an
// empty queue w/a subsequent call to current(), which will return an empty
// item.
// TODO this approach kinda...sucks
return
}
// isExhausted indicates whether or not the queue has definitely been exhausted,
// in which case it will return true.
//
// It may return false negatives - suggesting that there is more in the queue
// when a subsequent call to current() will be empty. Plan accordingly.
func (vq *versionQueue) isExhausted() bool {
if !vq.allLoaded {
return false
}
return len(vq.pi) == 0
}
func (vq *versionQueue) String() string {
var vs []string
for _, v := range vq.pi {
vs = append(vs, v.String())
}
return fmt.Sprintf("[%s]", strings.Join(vs, ", "))
}