Skip to content
This repository was archived by the owner on Sep 9, 2020. It is now read-only.

Commit 13ec211

Browse files
committed
gps: Diff->Delta, and bitfield for change checking
Also convert the SafeWriter to use LockDelta.
1 parent df2c26b commit 13ec211

File tree

7 files changed

+199
-207
lines changed

7 files changed

+199
-207
lines changed

gps/verify/digest.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -294,7 +294,7 @@ const (
294294

295295
// EmptyDigestInLock is used when the digest for a dependency listed in the
296296
// lock file is the empty string. While this is a special case of
297-
// DigestMismatchInLock, keeping both cases discrete is a desired feature.
297+
// DigestMismatchInLock, separating the cases is a desired feature.
298298
EmptyDigestInLock
299299

300300
// DigestMismatchInLock is used when the digest for a dependency listed in

gps/verify/digest_test.go

+65-25
Original file line numberDiff line numberDiff line change
@@ -152,15 +152,6 @@ func TestVerifyDepTree(t *testing.T) {
152152
"launchpad.net/match": {0x7e, 0x10, 0x6, 0x2f, 0x8, 0x3, 0x3c, 0x76, 0xae, 0xbc, 0xa4, 0xc9, 0xec, 0x73, 0x67, 0x15, 0x70, 0x2b, 0x0, 0x89, 0x27, 0xbb, 0x61, 0x9d, 0xc7, 0xc3, 0x39, 0x46, 0x3, 0x91, 0xb7, 0x3b},
153153
}
154154

155-
status, err := VerifyDepTree(vendorRoot, wantSums)
156-
if err != nil {
157-
t.Fatal(err)
158-
}
159-
160-
if got, want := len(status), 7; got != want {
161-
t.Errorf("\n(GOT): %v; (WNT): %v", got, want)
162-
}
163-
164155
checkStatus := func(t *testing.T, status map[string]VendorStatus, key string, want VendorStatus) {
165156
got, ok := status[key]
166157
if !ok {
@@ -172,25 +163,74 @@ func TestVerifyDepTree(t *testing.T) {
172163
}
173164
}
174165

175-
checkStatus(t, status, "github.com/alice/match", NoMismatch)
176-
checkStatus(t, status, "github.com/alice/mismatch", DigestMismatchInLock)
177-
checkStatus(t, status, "github.com/alice/notInLock", NotInLock)
178-
checkStatus(t, status, "github.com/bob/match", NoMismatch)
179-
checkStatus(t, status, "github.com/bob/emptyDigest", EmptyDigestInLock)
180-
checkStatus(t, status, "github.com/charlie/notInTree", NotInTree)
181-
checkStatus(t, status, "launchpad.net/match", NoMismatch)
182-
183-
if t.Failed() {
184-
for k, want := range wantSums {
185-
got, err := DigestFromDirectory(filepath.Join(vendorRoot, k))
186-
if err != nil {
187-
t.Error(err)
166+
t.Run("normal", func(t *testing.T) {
167+
t.Parallel()
168+
wantDigests := make(map[string]VersionedDigest)
169+
for k, v := range wantSums {
170+
wantDigests[k] = VersionedDigest{
171+
HashVersion: HashVersion,
172+
Digest: v,
188173
}
189-
if !bytes.Equal(got.Digest, want) {
190-
t.Errorf("%q\n(GOT):\n\t%#v\n(WNT):\n\t%#v", k, got, want)
174+
}
175+
176+
status, err := VerifyDepTree(vendorRoot, wantDigests)
177+
if err != nil {
178+
t.Fatal(err)
179+
}
180+
181+
if got, want := len(status), 7; got != want {
182+
t.Errorf("Unexpected result count from VerifyDepTree:\n\t(GOT): %v\n\t(WNT): %v", got, want)
183+
}
184+
185+
checkStatus(t, status, "github.com/alice/match", NoMismatch)
186+
checkStatus(t, status, "github.com/alice/mismatch", DigestMismatchInLock)
187+
checkStatus(t, status, "github.com/alice/notInLock", NotInLock)
188+
checkStatus(t, status, "github.com/bob/match", NoMismatch)
189+
checkStatus(t, status, "github.com/bob/emptyDigest", EmptyDigestInLock)
190+
checkStatus(t, status, "github.com/charlie/notInTree", NotInTree)
191+
checkStatus(t, status, "launchpad.net/match", NoMismatch)
192+
193+
if t.Failed() {
194+
for k, want := range wantSums {
195+
got, err := DigestFromDirectory(filepath.Join(vendorRoot, k))
196+
if err != nil {
197+
t.Error(err)
198+
}
199+
if !bytes.Equal(got.Digest, want) {
200+
t.Errorf("Digest mismatch for %q\n(GOT):\n\t%#v\n(WNT):\n\t%#v", k, got, want)
201+
}
191202
}
192203
}
193-
}
204+
205+
})
206+
207+
t.Run("hashv-mismatch", func(t *testing.T) {
208+
t.Parallel()
209+
wantDigests := make(map[string]VersionedDigest)
210+
for k, v := range wantSums {
211+
wantDigests[k] = VersionedDigest{
212+
HashVersion: HashVersion + 1,
213+
Digest: v,
214+
}
215+
}
216+
217+
status, err := VerifyDepTree(vendorRoot, wantDigests)
218+
if err != nil {
219+
t.Fatal(err)
220+
}
221+
222+
if got, want := len(status), 7; got != want {
223+
t.Errorf("Unexpected result count from VerifyDepTree:\n\t(GOT): %v\n\t(WNT): %v", got, want)
224+
}
225+
226+
checkStatus(t, status, "github.com/alice/match", HashVersionMismatch)
227+
checkStatus(t, status, "github.com/alice/mismatch", HashVersionMismatch)
228+
checkStatus(t, status, "github.com/alice/notInLock", NotInLock)
229+
checkStatus(t, status, "github.com/bob/match", HashVersionMismatch)
230+
checkStatus(t, status, "github.com/bob/emptyDigest", HashVersionMismatch)
231+
checkStatus(t, status, "github.com/charlie/notInTree", NotInTree)
232+
checkStatus(t, status, "launchpad.net/match", HashVersionMismatch)
233+
})
194234
}
195235

196236
func BenchmarkDigestFromDirectory(b *testing.B) {

gps/verify/lockdiff.go

+112-40
Original file line numberDiff line numberDiff line change
@@ -68,10 +68,10 @@ type LockDiff struct {
6868
Modify []LockedProjectDiff
6969
}
7070

71-
type LockDiff2 struct {
71+
type LockDelta struct {
7272
AddedImportInputs []string
7373
RemovedImportInputs []string
74-
ProjectDiffs map[gps.ProjectRoot]LockedProjectDiff2
74+
ProjectDeltas map[gps.ProjectRoot]LockedProjectDelta
7575
}
7676

7777
// LockedProjectDiff contains the before and after snapshot of a project reference.
@@ -85,13 +85,13 @@ type LockedProjectDiff struct {
8585
Packages []StringDiff
8686
}
8787

88-
type LockedProjectDiff2 struct {
88+
type LockedProjectDelta struct {
8989
Name gps.ProjectRoot
9090
ProjectRemoved, ProjectAdded bool
91-
LockedProjectPartsDiff
91+
LockedProjectPartsDelta
9292
}
9393

94-
type LockedProjectPartsDiff struct {
94+
type LockedProjectPartsDelta struct {
9595
PackagesAdded, PackagesRemoved []string
9696
VersionBefore, VersionAfter gps.UnpairedVersion
9797
RevisionBefore, RevisionAfter gps.Revision
@@ -100,13 +100,13 @@ type LockedProjectPartsDiff struct {
100100
HashChanged, HashVersionChanged bool
101101
}
102102

103-
// DiffLocks compares two locks and identifies the differences between them.
104-
// Returns nil if there are no differences.
105-
func DiffLocks2(l1, l2 gps.Lock) LockDiff2 {
103+
// DiffLocks2 compares two locks and computes a semantically rich delta between
104+
// them.
105+
func DiffLocks2(l1, l2 gps.Lock) LockDelta {
106106
// Default nil locks to empty locks, so that we can still generate a diff
107107
if l1 == nil {
108108
if l2 == nil {
109-
return LockDiff2{}
109+
return LockDelta{}
110110
}
111111
l1 = gps.SimpleLock{}
112112
}
@@ -119,16 +119,16 @@ func DiffLocks2(l1, l2 gps.Lock) LockDiff2 {
119119
p1 = sortLockedProjects(p1)
120120
p2 = sortLockedProjects(p2)
121121

122-
diff := LockDiff2{
123-
ProjectDiffs: make(map[gps.ProjectRoot]LockedProjectDiff2),
122+
diff := LockDelta{
123+
ProjectDeltas: make(map[gps.ProjectRoot]LockedProjectDelta),
124124
}
125125

126126
var i2next int
127127
for i1 := 0; i1 < len(p1); i1++ {
128128
lp1 := p1[i1]
129129
pr1 := lp1.Ident().ProjectRoot
130130

131-
lpd := LockedProjectDiff2{
131+
lpd := LockedProjectDelta{
132132
Name: pr1,
133133
}
134134

@@ -138,10 +138,10 @@ func DiffLocks2(l1, l2 gps.Lock) LockDiff2 {
138138

139139
switch strings.Compare(string(pr1), string(pr2)) {
140140
case 0: // Found a matching project
141-
lpd.LockedProjectPartsDiff = DiffProjects2(lp1, lp2)
141+
lpd.LockedProjectPartsDelta = DiffProjects2(lp1, lp2)
142142
i2next = i2 + 1 // Don't visit this project again
143143
case +1: // Found a new project
144-
diff.ProjectDiffs[pr2] = LockedProjectDiff2{
144+
diff.ProjectDeltas[pr2] = LockedProjectDelta{
145145
Name: pr2,
146146
ProjectAdded: true,
147147
}
@@ -154,14 +154,14 @@ func DiffLocks2(l1, l2 gps.Lock) LockDiff2 {
154154
break // Done evaluating this project, move onto the next
155155
}
156156

157-
diff.ProjectDiffs[pr1] = lpd
157+
diff.ProjectDeltas[pr1] = lpd
158158
}
159159

160160
// Anything that still hasn't been evaluated are adds
161161
for i2 := i2next; i2 < len(p2); i2++ {
162162
lp2 := p2[i2]
163163
pr2 := lp2.Ident().ProjectRoot
164-
diff.ProjectDiffs[pr2] = LockedProjectDiff2{
164+
diff.ProjectDeltas[pr2] = LockedProjectDelta{
165165
Name: pr2,
166166
ProjectAdded: true,
167167
}
@@ -205,8 +205,8 @@ func findAddedAndRemoved(l1, l2 []string) (add, remove []string) {
205205
return add, remove
206206
}
207207

208-
func DiffProjects2(lp1, lp2 gps.LockedProject) LockedProjectPartsDiff {
209-
ld := LockedProjectPartsDiff{
208+
func DiffProjects2(lp1, lp2 gps.LockedProject) LockedProjectPartsDelta {
209+
ld := LockedProjectPartsDelta{
210210
SourceBefore: lp1.Ident().Source,
211211
SourceAfter: lp2.Ident().Source,
212212
}
@@ -239,51 +239,118 @@ func DiffProjects2(lp1, lp2 gps.LockedProject) LockedProjectPartsDiff {
239239
if ok1 && ok2 {
240240
ld.PruneOptsBefore, ld.PruneOptsAfter = vp1.PruneOpts, vp2.PruneOpts
241241

242-
// Only consider hashes for diffing if neither were the zero value.
243-
if !vp1.Digest.IsEmpty() && !vp2.Digest.IsEmpty() {
244-
if vp1.Digest.HashVersion != vp2.Digest.HashVersion {
245-
ld.HashVersionChanged = true
246-
}
247-
if !bytes.Equal(vp1.Digest.Digest, vp2.Digest.Digest) {
248-
ld.HashChanged = true
249-
}
242+
if vp1.Digest.HashVersion != vp2.Digest.HashVersion {
243+
ld.HashVersionChanged = true
244+
}
245+
if !bytes.Equal(vp1.Digest.Digest, vp2.Digest.Digest) {
246+
ld.HashChanged = true
250247
}
248+
} else if ok1 {
249+
ld.PruneOptsBefore = vp1.PruneOpts
250+
ld.HashVersionChanged = true
251+
ld.HashChanged = true
252+
} else if ok2 {
253+
ld.PruneOptsAfter = vp2.PruneOpts
254+
ld.HashVersionChanged = true
255+
ld.HashChanged = true
251256
}
252257

253258
return ld
254259
}
255260

256-
func (ld LockDiff2) Changed() bool {
257-
if len(ld.AddedImportInputs) > 0 || len(ld.RemovedImportInputs) > 0 {
261+
type DeltaDimension uint16
262+
263+
const (
264+
InputImportsChanged DeltaDimension = 1 << iota
265+
ProjectAdded
266+
ProjectRemoved
267+
SourceChanged
268+
VersionChanged
269+
RevisionChanged
270+
PackagesChanged
271+
PruneOptsChanged
272+
HashVersionChanged
273+
HashChanged
274+
AnyChanged = (1 << iota) - 1
275+
)
276+
277+
func (ld LockDelta) Changed(flags DeltaDimension) bool {
278+
if flags&InputImportsChanged != 0 && (len(ld.AddedImportInputs) > 0 || len(ld.RemovedImportInputs) > 0) {
258279
return true
259280
}
260281

261-
for _, ld := range ld.ProjectDiffs {
262-
if ld.Changed() {
282+
for _, ld := range ld.ProjectDeltas {
283+
if ld.Changed(AnyChanged) {
263284
return true
264285
}
265286
}
266287

267288
return false
268289
}
269290

270-
func (ld LockedProjectDiff2) Changed() bool {
271-
return ld.WasRemoved() || ld.WasAdded() || ld.RevisionChanged() || ld.VersionChanged() || ld.SourceChanged() || ld.PackagesChanged() || ld.HashChanged || ld.HashVersionChanged
291+
// Changed indicates whether the delta contains a change along the dimensions
292+
// with their corresponding bits set.
293+
//
294+
// For example, if only the Revision changed, and this method is called with
295+
// SourceChanged | VersionChanged, it will return false; if it is called with
296+
// VersionChanged | RevisionChanged, it will return true.
297+
func (ld LockedProjectDelta) Changed(flags DeltaDimension) bool {
298+
if flags&ProjectAdded != 0 && ld.WasAdded() {
299+
return true
300+
}
301+
302+
if flags&ProjectRemoved != 0 && ld.WasRemoved() {
303+
return true
304+
}
305+
306+
return ld.LockedProjectPartsDelta.Changed(flags & ^ProjectAdded & ^ProjectRemoved)
272307
}
273308

274-
func (ld LockedProjectDiff2) WasRemoved() bool {
309+
func (ld LockedProjectDelta) WasRemoved() bool {
275310
return ld.ProjectRemoved
276311
}
277312

278-
func (ld LockedProjectDiff2) WasAdded() bool {
313+
func (ld LockedProjectDelta) WasAdded() bool {
279314
return ld.ProjectAdded
280315
}
281316

282-
func (ld LockedProjectPartsDiff) SourceChanged() bool {
317+
func (ld LockedProjectPartsDelta) Changed(flags DeltaDimension) bool {
318+
if flags&SourceChanged != 0 && ld.SourceChanged() {
319+
return true
320+
}
321+
322+
if flags&RevisionChanged != 0 && ld.RevisionChanged() {
323+
return true
324+
}
325+
326+
if flags&PruneOptsChanged != 0 && ld.PruneOptsChanged() {
327+
return true
328+
}
329+
330+
if flags&HashChanged != 0 && ld.HashChanged {
331+
return true
332+
}
333+
334+
if flags&HashVersionChanged != 0 && ld.HashVersionChanged {
335+
return true
336+
}
337+
338+
if flags&VersionChanged != 0 && ld.VersionChanged() {
339+
return true
340+
}
341+
342+
if flags&PackagesChanged != 0 && ld.PackagesChanged() {
343+
return true
344+
}
345+
346+
return false
347+
}
348+
349+
func (ld LockedProjectPartsDelta) SourceChanged() bool {
283350
return ld.SourceBefore != ld.SourceAfter
284351
}
285352

286-
func (ld LockedProjectPartsDiff) VersionChanged() bool {
353+
func (ld LockedProjectPartsDelta) VersionChanged() bool {
287354
if ld.VersionBefore == nil && ld.VersionAfter == nil {
288355
return false
289356
} else if (ld.VersionBefore == nil || ld.VersionAfter == nil) || (ld.VersionBefore.Type() != ld.VersionAfter.Type()) {
@@ -295,7 +362,7 @@ func (ld LockedProjectPartsDiff) VersionChanged() bool {
295362
return false
296363
}
297364

298-
func (ld LockedProjectPartsDiff) VersionTypeChanged() bool {
365+
func (ld LockedProjectPartsDelta) VersionTypeChanged() bool {
299366
if ld.VersionBefore == nil && ld.VersionAfter == nil {
300367
return false
301368
} else if (ld.VersionBefore == nil || ld.VersionAfter == nil) || (ld.VersionBefore.Type() != ld.VersionAfter.Type()) {
@@ -305,15 +372,15 @@ func (ld LockedProjectPartsDiff) VersionTypeChanged() bool {
305372
return false
306373
}
307374

308-
func (ld LockedProjectPartsDiff) RevisionChanged() bool {
375+
func (ld LockedProjectPartsDelta) RevisionChanged() bool {
309376
return ld.RevisionBefore != ld.RevisionAfter
310377
}
311378

312-
func (ld LockedProjectPartsDiff) PackagesChanged() bool {
379+
func (ld LockedProjectPartsDelta) PackagesChanged() bool {
313380
return len(ld.PackagesAdded) > 0 || len(ld.PackagesRemoved) > 0
314381
}
315382

316-
func (ld LockedProjectPartsDiff) PruneOptsChanged() bool {
383+
func (ld LockedProjectPartsDelta) PruneOptsChanged() bool {
317384
return ld.PruneOptsBefore != ld.PruneOptsAfter
318385
}
319386

@@ -518,3 +585,8 @@ func DiffProjects(lp1, lp2 gps.LockedProject) *LockedProjectDiff {
518585
}
519586
return &diff
520587
}
588+
589+
type VendorDiff struct {
590+
LockDelta LockDelta
591+
VendorStatus map[string]VendorStatus
592+
}

0 commit comments

Comments
 (0)