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

Commit 74a0a26

Browse files
committed
Interpret strings as branches > semver constraints
When a user supplied string in an imported config file, or specified to dep ensure, can be interpreted multiple ways, prefer the branch over a semver constraint. In #710, glide.yaml specified v2 for https://github.com/go-mgo/mgo. When we assume that is a semver constraint, solve fails because the hinted revision in the lock (a commit on the v2 branch) doesn't satisfy the assumed constraint of ^2.0.0. The new preferred match order for the user string is: * revision * branch * semver constraint * tag I am giving preference of a semver constraint over a tag so that a bare version, 1.0.0, is interpreted more loosely with an implied caret, ^1.0.0, instead of the stricter exact match.
1 parent c3259a4 commit 74a0a26

12 files changed

+208
-50
lines changed

cmd/dep/ensure.go

+30-30
Original file line numberDiff line numberDiff line change
@@ -304,39 +304,19 @@ func getProjectConstraint(arg string, sm gps.SourceManager) (gps.ProjectConstrai
304304
return gps.ProjectConstraint{Ident: pi, Constraint: c}, nil
305305
}
306306

307-
// deduceConstraint tries to puzzle out what kind of version is given in a string -
308-
// semver, a revision, or as a fallback, a plain tag
307+
// deduceConstraint tries to puzzle out what kind of version is given in a string.
308+
// Preference is given first for revisions, then branches, then semver constraints, then plain tags.
309309
func deduceConstraint(s string, pi gps.ProjectIdentifier, sm gps.SourceManager) (gps.Constraint, error) {
310-
if s == "" {
311-
// Find the default branch
312-
versions, err := sm.ListVersions(pi)
313-
if err != nil {
314-
return nil, errors.Wrapf(err, "list versions for %s(%s)", pi.ProjectRoot, pi.Source) // means repo does not exist
315-
}
316-
317-
gps.SortPairedForUpgrade(versions)
318-
for _, v := range versions {
319-
if v.Type() == gps.IsBranch {
320-
return v.Unpair(), nil
321-
}
322-
}
323-
}
324-
325-
// always semver if we can
326-
c, err := gps.NewSemverConstraintIC(s)
327-
if err == nil {
328-
return c, nil
329-
}
330-
331310
slen := len(s)
332311
if slen == 40 {
333-
if _, err = hex.DecodeString(s); err == nil {
312+
if _, err := hex.DecodeString(s); err == nil {
334313
// Whether or not it's intended to be a SHA1 digest, this is a
335314
// valid byte sequence for that, so go with Revision. This
336315
// covers git and hg
337316
return gps.Revision(s), nil
338317
}
339318
}
319+
340320
// Next, try for bzr, which has a three-component GUID separated by
341321
// dashes. There should be two, but the email part could contain
342322
// internal dashes
@@ -349,24 +329,44 @@ func deduceConstraint(s string, pi gps.ProjectIdentifier, sm gps.SourceManager)
349329
}
350330

351331
i2 := strings.LastIndex(s[:i3], "-")
352-
if _, err = strconv.ParseUint(s[i2+1:i3], 10, 64); err == nil {
332+
if _, err := strconv.ParseUint(s[i2+1:i3], 10, 64); err == nil {
353333
// Getting this far means it'd pretty much be nuts if it's not a
354334
// bzr rev, so don't bother parsing the email.
355335
return gps.Revision(s), nil
356336
}
357337
}
358338

359-
// call out to network and get the package's versions
339+
// Lookup the string in the repository
340+
var version gps.PairedVersion
360341
versions, err := sm.ListVersions(pi)
361342
if err != nil {
362343
return nil, errors.Wrapf(err, "list versions for %s(%s)", pi.ProjectRoot, pi.Source) // means repo does not exist
363344
}
364-
365-
for _, version := range versions {
366-
if s == version.String() {
367-
return version.Unpair(), nil
345+
gps.SortPairedForUpgrade(versions)
346+
for _, v := range versions {
347+
// Pick the default branch if no constraint is given
348+
if s == "" || s == v.String() {
349+
version = v
350+
break
368351
}
369352
}
353+
354+
// Branch
355+
if version != nil && version.Type() == gps.IsBranch {
356+
return version.Unpair(), nil
357+
}
358+
359+
// Semver Constraint
360+
c, err := gps.NewSemverConstraintIC(s)
361+
if c != nil && err == nil {
362+
return c, nil
363+
}
364+
365+
// Tag
366+
if version != nil {
367+
return version.Unpair(), nil
368+
}
369+
370370
return nil, errors.Errorf("%s is not a valid version for the package %s(%s)", s, pi.ProjectRoot, pi.Source)
371371
}
372372

cmd/dep/glide_importer.go

+8-9
Original file line numberDiff line numberDiff line change
@@ -178,11 +178,11 @@ func (g *glideImporter) convert(pr gps.ProjectRoot) (*dep.Manifest, *dep.Lock, e
178178
lock = &dep.Lock{}
179179

180180
for _, pkg := range g.lock.Imports {
181-
lp := g.buildLockedProject(pkg)
181+
lp := g.buildLockedProject(pkg, manifest)
182182
lock.P = append(lock.P, lp)
183183
}
184184
for _, pkg := range g.lock.TestImports {
185-
lp := g.buildLockedProject(pkg)
185+
lp := g.buildLockedProject(pkg, manifest)
186186
lock.P = append(lock.P, lp)
187187
}
188188
}
@@ -213,19 +213,18 @@ func (g *glideImporter) buildProjectConstraint(pkg glidePackage) (pc gps.Project
213213
return
214214
}
215215

216-
func (g *glideImporter) buildLockedProject(pkg glideLockedPackage) gps.LockedProject {
216+
func (g *glideImporter) buildLockedProject(pkg glideLockedPackage, manifest *dep.Manifest) gps.LockedProject {
217217
pi := gps.ProjectIdentifier{
218218
ProjectRoot: gps.ProjectRoot(pkg.Name),
219219
Source: pkg.Repository,
220220
}
221221
revision := gps.Revision(pkg.Reference)
222+
pp := manifest.Constraints[pi.ProjectRoot]
222223

223-
version, err := lookupVersionForRevision(revision, pi, g.sm)
224-
if err != nil {
225-
// Warn about the problem, it is not enough to warrant failing
226-
warn := errors.Wrapf(err, "Unable to lookup the version represented by %s in %s(%s). Falling back to locking the revision only.", revision, pi.ProjectRoot, pi.Source)
227-
g.logger.Printf(warn.Error())
228-
version = revision
224+
version, warning := lookupVersionForLockedProject(pi, pp.Constraint, revision, g.sm)
225+
if warning != nil {
226+
// Only warn about the problem, it is not enough to warrant failing
227+
g.logger.Println(warning.Error())
229228
}
230229

231230
f := fb.NewLockedProjectFeedback(version, pi.ProjectRoot, fb.DepTypeImported)

cmd/dep/glide_importer_test.go

+54-6
Original file line numberDiff line numberDiff line change
@@ -44,8 +44,8 @@ func TestGlideConfig_Import(t *testing.T) {
4444
defer sm.Release()
4545

4646
h.TempDir(filepath.Join("src", testGlideProjectRoot))
47-
h.TempCopy(filepath.Join(testGlideProjectRoot, glideYamlName), "glide/glide.yaml")
48-
h.TempCopy(filepath.Join(testGlideProjectRoot, glideLockName), "glide/glide.lock")
47+
h.TempCopy(filepath.Join(testGlideProjectRoot, glideYamlName), "glide/glide0.yaml")
48+
h.TempCopy(filepath.Join(testGlideProjectRoot, glideLockName), "glide/glide0.lock")
4949
projectRoot := h.Path(testGlideProjectRoot)
5050

5151
// Capture stderr so we can verify output
@@ -68,7 +68,55 @@ func TestGlideConfig_Import(t *testing.T) {
6868
t.Fatal("Expected the lock to be generated")
6969
}
7070

71-
goldenFile := "glide/expected_import_output.txt"
71+
goldenFile := "glide/golden0.txt"
72+
got := verboseOutput.String()
73+
want := h.GetTestFileString(goldenFile)
74+
if want != got {
75+
if *test.UpdateGolden {
76+
if err := h.WriteTestFile(goldenFile, got); err != nil {
77+
t.Fatalf("%+v", errors.Wrapf(err, "Unable to write updated golden file %s", goldenFile))
78+
}
79+
} else {
80+
t.Fatalf("expected %s, got %s", want, got)
81+
}
82+
}
83+
}
84+
85+
func TestGlideConfig_Import_BranchWithSemverName(t *testing.T) {
86+
h := test.NewHelper(t)
87+
defer h.Cleanup()
88+
89+
ctx := newTestContext(h)
90+
sm, err := ctx.SourceManager()
91+
h.Must(err)
92+
defer sm.Release()
93+
94+
h.TempDir(filepath.Join("src", testGlideProjectRoot))
95+
h.TempCopy(filepath.Join(testGlideProjectRoot, glideYamlName), "glide/glide1.yaml")
96+
h.TempCopy(filepath.Join(testGlideProjectRoot, glideLockName), "glide/glide1.lock")
97+
projectRoot := h.Path(testGlideProjectRoot)
98+
99+
// Capture stderr so we can verify output
100+
verboseOutput := &bytes.Buffer{}
101+
ctx.Err = log.New(verboseOutput, "", 0)
102+
103+
g := newGlideImporter(ctx.Err, false, sm) // Disable verbose so that we don't print values that change each test run
104+
if !g.HasDepMetadata(projectRoot) {
105+
t.Fatal("Expected the importer to detect the glide configuration files")
106+
}
107+
108+
m, l, err := g.Import(projectRoot, testGlideProjectRoot)
109+
h.Must(err)
110+
111+
if m == nil {
112+
t.Fatal("Expected the manifest to be generated")
113+
}
114+
115+
if l == nil {
116+
t.Fatal("Expected the lock to be generated")
117+
}
118+
119+
goldenFile := "glide/golden1.txt"
72120
got := verboseOutput.String()
73121
want := h.GetTestFileString(goldenFile)
74122
if want != got {
@@ -91,9 +139,9 @@ func TestGlideConfig_Import_MissingLockFile(t *testing.T) {
91139
h.Must(err)
92140
defer sm.Release()
93141

94-
h.TempDir(filepath.Join("src", "glidetest"))
95-
h.TempCopy(filepath.Join("glidetest", glideYamlName), "glide/glide.yaml")
96-
projectRoot := h.Path("glidetest")
142+
h.TempDir(filepath.Join("src", testGlideProjectRoot))
143+
h.TempCopy(filepath.Join(testGlideProjectRoot, glideYamlName), "glide/glide0.yaml")
144+
projectRoot := h.Path(testGlideProjectRoot)
97145

98146
g := newGlideImporter(ctx.Err, true, sm)
99147
if !g.HasDepMetadata(projectRoot) {

cmd/dep/root_analyzer.go

+22-4
Original file line numberDiff line numberDiff line change
@@ -146,19 +146,37 @@ func (a *rootAnalyzer) Info() (string, int) {
146146
return name, version
147147
}
148148

149-
func lookupVersionForRevision(rev gps.Revision, pi gps.ProjectIdentifier, sm gps.SourceManager) (gps.Version, error) {
149+
// lookupVersionForLockedProject figures out the appropriate version for a locked
150+
// project based on the locked revision and the constraint from the manifest.
151+
// First try matching the revision to a version, then try the constraint from the
152+
// manifest, then finally the revision.
153+
func lookupVersionForLockedProject(pi gps.ProjectIdentifier, c gps.Constraint, rev gps.Revision, sm gps.SourceManager) (version gps.Version, warning error) {
150154
// Find the version that goes with this revision, if any
151155
versions, err := sm.ListVersions(pi)
152156
if err != nil {
153-
return nil, errors.Wrapf(err, "Unable to list versions for %s(%s)", pi.ProjectRoot, pi.Source)
157+
warning = errors.Wrapf(err, "Unable to lookup the version represented by %s in %s(%s). Falling back to locking the revision only.", rev, pi.ProjectRoot, pi.Source)
158+
return
154159
}
155160

156161
gps.SortPairedForUpgrade(versions) // Sort versions in asc order
157162
for _, v := range versions {
158163
if v.Underlying() == rev {
159-
return v, nil
164+
version = v
165+
return
160166
}
161167
}
162168

163-
return rev, nil
169+
// Use the version from the manifest as long as it wasn't a range
170+
switch tv := c.(type) {
171+
case gps.PairedVersion:
172+
version = tv.Unpair().Is(rev)
173+
return
174+
case gps.UnpairedVersion:
175+
version = tv.Is(rev)
176+
return
177+
}
178+
179+
// Give up and lock only to a revision
180+
version = rev
181+
return
164182
}

cmd/dep/root_analyzer_test.go

+71-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,12 @@
44

55
package main
66

7-
import "testing"
7+
import (
8+
"testing"
9+
10+
"github.com/golang/dep/internal/gps"
11+
"github.com/golang/dep/internal/test"
12+
)
813

914
func TestRootAnalyzer_Info(t *testing.T) {
1015
testCases := map[bool]string{
@@ -19,3 +24,68 @@ func TestRootAnalyzer_Info(t *testing.T) {
1924
}
2025
}
2126
}
27+
28+
func TestLookupVersionForLockedProject_MatchRevisionToTag(t *testing.T) {
29+
h := test.NewHelper(t)
30+
defer h.Cleanup()
31+
32+
ctx := newTestContext(h)
33+
sm, err := ctx.SourceManager()
34+
h.Must(err)
35+
defer sm.Release()
36+
37+
pi := gps.ProjectIdentifier{ProjectRoot: gps.ProjectRoot("github.com/sdboyer/deptest")}
38+
c, _ := gps.NewSemverConstraint("^0.8.1")
39+
rev := gps.Revision("ff2948a2ac8f538c4ecd55962e919d1e13e74baf")
40+
v, warning := lookupVersionForLockedProject(pi, c, rev, sm)
41+
h.Must(warning)
42+
43+
wantV := "v1.0.0"
44+
gotV := v.String()
45+
if gotV != wantV {
46+
t.Fatalf("Expected '%s', got '%s'", wantV, gotV)
47+
}
48+
}
49+
50+
func TestLookupVersionForLockedProject_FallbackToConstraint(t *testing.T) {
51+
h := test.NewHelper(t)
52+
defer h.Cleanup()
53+
54+
ctx := newTestContext(h)
55+
sm, err := ctx.SourceManager()
56+
h.Must(err)
57+
defer sm.Release()
58+
59+
pi := gps.ProjectIdentifier{ProjectRoot: gps.ProjectRoot("github.com/sdboyer/deptest")}
60+
c := gps.NewBranch("master")
61+
rev := gps.Revision("c575196502940c07bf89fd6d95e83b999162e051")
62+
v, warning := lookupVersionForLockedProject(pi, c, rev, sm)
63+
h.Must(warning)
64+
65+
wantV := c.String()
66+
gotV := v.String()
67+
if gotV != wantV {
68+
t.Fatalf("Expected '%s', got '%s'", wantV, gotV)
69+
}
70+
}
71+
72+
func TestLookupVersionForLockedProject_FallbackToRevision(t *testing.T) {
73+
h := test.NewHelper(t)
74+
defer h.Cleanup()
75+
76+
ctx := newTestContext(h)
77+
sm, err := ctx.SourceManager()
78+
h.Must(err)
79+
defer sm.Release()
80+
81+
pi := gps.ProjectIdentifier{ProjectRoot: gps.ProjectRoot("github.com/sdboyer/deptest")}
82+
rev := gps.Revision("c575196502940c07bf89fd6d95e83b999162e051")
83+
v, warning := lookupVersionForLockedProject(pi, nil, rev, sm)
84+
h.Must(warning)
85+
86+
wantV := rev.String()
87+
gotV := v.String()
88+
if gotV != wantV {
89+
t.Fatalf("Expected '%s', got '%s'", wantV, gotV)
90+
}
91+
}
File renamed without changes.
File renamed without changes.

cmd/dep/testdata/glide/glide1.lock

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
hash: 3b7d5be7d68cd94765ac91ea2f58b554151f67f5d0bf499ea8cadb600538a0e5
2+
updated: 2017-05-03T15:13:27.629768623+02:00
3+
imports:
4+
- name: gopkg.in/mgo.v2
5+
version: 29cc868a5ca65f401ff318143f9408d02f4799cc
6+
subpackages:
7+
- bson
8+
- internal/sasl
9+
- internal/scram
10+
devImports: []

cmd/dep/testdata/glide/glide1.yaml

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
package: github.com/golang/notexist
2+
import:
3+
- package: gopkg.in/mgo.v2
4+
version: v2

cmd/dep/testdata/glide/expected_import_output.txt cmd/dep/testdata/glide/golden0.txt

+1
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,4 @@ Converting from glide.yaml and glide.lock...
55
Using master as initial constraint for imported dep github.com/golang/lint
66
Trying v0.8.1 (3f4c3be) as initial lock for imported dep github.com/sdboyer/deptest
77
Trying v2.0.0 (5c60720) as initial lock for imported dep github.com/sdboyer/deptestdos
8+
Trying master (cb00e56) as initial lock for imported dep github.com/golang/lint

cmd/dep/testdata/glide/golden1.txt

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
Detected glide configuration files...
2+
Converting from glide.yaml and glide.lock...
3+
Using v2 as initial constraint for imported dep gopkg.in/mgo.v2
4+
Trying v2 (29cc868) as initial lock for imported dep gopkg.in/mgo.v2

internal/feedback/feedback_test.go

+4
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,10 @@ func TestGetConstraintString(t *testing.T) {
4141
feedback: GetLockingFeedback("master", "436f39d", DepTypeTransitive, "github.com/baz/qux"),
4242
want: "Locking in master (436f39d) for transitive dep github.com/baz/qux",
4343
},
44+
{
45+
feedback: GetLockingFeedback("master", "436f39d", DepTypeImported, "github.com/baz/qux"),
46+
want: "Trying master (436f39d) as initial lock for imported dep github.com/baz/qux",
47+
},
4448
}
4549

4650
for _, c := range cases {

0 commit comments

Comments
 (0)