Add glide.yaml autoconversion logic and tests
diff --git a/cfg/config.go b/cfg/config.go
index 3b46764..f0e1248 100644
--- a/cfg/config.go
+++ b/cfg/config.go
@@ -7,6 +7,7 @@
 	"io/ioutil"
 	"reflect"
 	"sort"
+	"strconv"
 	"strings"
 
 	"github.com/Masterminds/vcs"
@@ -45,8 +46,15 @@
 
 	// Imports contains a list of all dependency constraints for a project. For
 	// more detail on how these are captured see the Dependency type.
+	// TODO rename
+	// TODO mapify
 	Imports Dependencies `yaml:"dependencies"`
 
+	// DevImports contains the test or other development dependency constraints
+	// for a project. See the Dependency type for more details on how this is
+	// recorded.
+	// TODO rename
+	// TODO mapify
 	DevImports Dependencies `yaml:"testDependencies"`
 }
 
@@ -58,22 +66,27 @@
 	License     string       `yaml:"license,omitempty"`
 	Owners      Owners       `yaml:"owners,omitempty"`
 	Ignore      []string     `yaml:"ignore,omitempty"`
-	Imports     Dependencies `yaml:"dependencies"`
+	Imports     Dependencies `yaml:"dependencies,omitempty"`
 	DevImports  Dependencies `yaml:"testDependencies,omitempty"`
+	// used to guarantee that this wil fail on unmarshaling legacy yamls
+	Compat  int `yaml:"import,omitempty"`
+	Compat2 int `yaml:"testImport,omitempty"`
 }
 
 // ConfigFromYaml returns an instance of Config from YAML
-func ConfigFromYaml(yml []byte) (*Config, bool, error) {
-	cfg := &Config{}
-	err := yaml.Unmarshal([]byte(yml), &cfg)
+func ConfigFromYaml(yml []byte) (cfg *Config, legacy bool, err error) {
+	cfg = &Config{}
+	err = yaml.Unmarshal(yml, cfg)
 	if err != nil {
 		lcfg := &lConfig1{}
-		err = yaml.Unmarshal([]byte(yml), &lcfg)
-		if err != nil {
-			// TODO(sdboyer) convert to new form, then return
+		err = yaml.Unmarshal(yml, &lcfg)
+		if err == nil {
+			legacy = true
+			cfg, err = lcfg.Convert()
 		}
 	}
-	return cfg, false, err
+
+	return
 }
 
 // Marshal converts a Config instance to YAML
@@ -404,7 +417,7 @@
 // A transitive representation of a dependency for yaml import/export.
 type dep struct {
 	Name       string   `yaml:"package"`
-	Reference  string   `yaml:"version,omitempty"`
+	Reference  string   `yaml:"version,omitempty"` // TODO rename
 	Branch     string   `yaml:"branch,omitempty"`
 	Repository string   `yaml:"repo,omitempty"`
 	Arch       []string `yaml:"arch,omitempty"`
@@ -473,6 +486,45 @@
 	return nil
 }
 
+// deduceConstraint tries to puzzle out what kind of version is given in a string -
+// semver, a revision, or as a fallback, a plain tag
+func deduceConstraint(s string) gps.Constraint {
+	// always semver if we can
+	c, err := gps.NewSemverConstraint(s)
+	if err == nil {
+		return c
+	}
+
+	if len(s) == 40 {
+		if _, err = hex.DecodeString(s); err == nil {
+			// Whether or not it's intended to be a SHA1 digest, this is a
+			// valid byte sequence for that, so go with Revision. This
+			// covers git and hg
+			return gps.Revision(s)
+		}
+	}
+	// Next, try for bzr, which has a three-component GUID separated by
+	// dashes. There should be two, but the email part could contain
+	// internal dashes
+	if strings.Count(s, "-") >= 2 {
+		// Work from the back to avoid potential confusion from the email
+		i3 := strings.LastIndex(s, "-")
+		if _, err = hex.DecodeString(s[i3:]); err == nil {
+			i2 := strings.LastIndex(s[:i3], "-")
+			if _, err = strconv.ParseUint(s[i2:i3], 10, 64); err != nil {
+				// Getting this far means it'd pretty much be nuts if it's not a
+				// bzr rev, so don't bother parsing the email.
+				return gps.Revision(s)
+			}
+		}
+	}
+
+	// If not a plain SHA1 or bzr custom GUID, assume a plain version.
+	//
+	// svn, you ask? lol, madame. lol.
+	return gps.NewVersion(s)
+}
+
 // MarshalYAML is a hook for gopkg.in/yaml.v2 in the marshaling process
 func (d *Dependency) MarshalYAML() (interface{}, error) {
 	newDep := &dep{
diff --git a/cfg/config_test.go b/cfg/config_test.go
index 58e0904..db563dd 100644
--- a/cfg/config_test.go
+++ b/cfg/config_test.go
@@ -37,6 +37,7 @@
       - i386
       - arm
   - package: github.com/Masterminds/structable
+    version: v1.0.0
   - package: github.com/Masterminds/cookoo/color
   - package: github.com/Masterminds/cookoo/convert
 
@@ -238,6 +239,58 @@
 	}
 }
 
+func TestLegacyConfigAutoconvert(t *testing.T) {
+	c, leg, err := ConfigFromYaml([]byte(lyml))
+	if err != nil {
+		t.Errorf("ConfigFromYaml failed to detect and autoconvert legacy yaml file with err %s", err)
+	}
+
+	if !leg {
+		t.Errorf("ConfigFromYaml failed to report autoconversion of legacy yaml file")
+	}
+
+	if c.Name != "fake/testing" {
+		t.Error("ConfigFromYaml failed to properly autoconvert legacy yaml file")
+	}
+
+	// Two should survive the conversion
+	if len(c.Imports) != 2 {
+		t.Error("Expected two dep clauses to survive conversion, but got ", len(c.Imports))
+	}
+
+	found := false
+	found2 := false
+	for _, i := range c.Imports {
+		if i.Name == "github.com/Masterminds/convert" {
+			found = true
+			ref := gps.Revision("a9949121a2e2192ca92fa6dddfeaaa4a4412d955")
+			if i.Constraint != ref {
+				t.Errorf("(%s) Expected %q for constraint, got %q", i.Name, ref, i.Constraint)
+			}
+
+			repo := "git@github.com:Masterminds/convert.git"
+			if i.Repository != repo {
+				t.Errorf("(%s) Expected %q for repository, got %q", i.Name, repo, i.Repository)
+			}
+		}
+
+		if i.Name == "github.com/Masterminds/structable" {
+			found2 = true
+			ref := gps.NewVersion("v1.0.0")
+			if i.Constraint != ref {
+				t.Errorf("(%s) Expected %q for constraint, got %q", i.Name, ref, i.Constraint)
+			}
+		}
+	}
+	if !found {
+		t.Error("Unable to find github.com/Masterminds/convert")
+	}
+	if !found2 {
+		t.Error("Unable to find github.com/Masterminds/structable")
+	}
+
+}
+
 func TestConfigFromYaml(t *testing.T) {
 	c, _, err := ConfigFromYaml([]byte(yml))
 	if err != nil {
@@ -255,7 +308,7 @@
 		t.Error("ConfigFromYaml failed to parse yaml for HasDependency")
 	}
 
-	if c.HasDependency("github.com/Masterminds/convert") != true {
+	if !c.HasDependency("github.com/Masterminds/convert") {
 		t.Error("HasDependency failing to pickup depenency")
 	}
 
diff --git a/cfg/legacy.go b/cfg/legacy.go
index 4cf294d..3c78703 100644
--- a/cfg/legacy.go
+++ b/cfg/legacy.go
@@ -2,6 +2,7 @@
 
 import (
 	"fmt"
+	"path"
 	"reflect"
 	"strings"
 
@@ -42,9 +43,54 @@
 	return err
 }
 
+func (c *lConfig1) Convert() (*Config, error) {
+	// This is probably already done, but do it just in case
+	err := c.DeDupe()
+	if err != nil {
+		return nil, err
+	}
+
+	// Pull over the easy values
+	c2 := &Config{
+		Name:        c.Name,
+		Description: c.Description,
+		Home:        c.Home,
+		License:     c.License,
+		Owners:      c.Owners,
+		Ignore:      c.Ignore,
+	}
+
+	// _if_ the name is set, path-prepend it to exclude, and add that to ignore.
+	// Otherwise, we just skip excludes. Not that big a deal, since they're a
+	// root-only property anyway; the user can reasonably recreate.
+
+	if c.Name != "" {
+		for _, excl := range c.Exclude {
+			c.Ignore = append(c.Ignore, path.Join(c.Name, excl))
+			// The trailing * is interpreted by gps as an ignore on that and all
+			// child paths (or, soon - https://github.com/sdboyer/gps/issues/88)
+			c.Ignore = append(c.Ignore, path.Join(c.Name, excl, "*"))
+		}
+	}
+
+	// Quitting early on this might seem risky, but a) all possible errs
+	// _should_ have already been surfaced in the earlier DeDupe(), and b) there
+	// are no new errs introduced by the conversion itself, so this doesn't
+	// actually increase the surface area for failures vis-a-vis pre-gps glide.
+	c2.Imports, err = c.Imports.Convert()
+	if err != nil {
+		return nil, err
+	}
+	c2.DevImports, err = c.DevImports.Convert()
+	if err != nil {
+		return nil, err
+	}
+
+	return c2, nil
+}
+
 // DeDupe consolidates duplicate dependencies on a Config instance
 func (c *lConfig1) DeDupe() error {
-
 	// Remove duplicates in the imports
 	var err error
 	c.Imports, err = c.Imports.DeDupe()
@@ -118,6 +164,35 @@
 
 type lDependencies1 []*lDependency1
 
+func (ds lDependencies1) Convert() (Dependencies, error) {
+	dds, err := ds.DeDupe()
+	if err != nil {
+		return nil, err
+	}
+
+	var ds2 Dependencies
+	for _, d := range dds {
+		// If we have neither a repo nor a reference, then it's pointless to
+		// include this dep in the list (it will add no information to gps)
+		if d.Repository == "" && d.Reference == "" {
+			continue
+		}
+
+		d2 := &Dependency{
+			Name:       d.Name,
+			Repository: d.Repository,
+		}
+
+		d2.Constraint = deduceConstraint(d.Reference)
+		// TODO(sdboyer) if the above result is a plain version, the old
+		// semantics are ambiguous wrt if the user wants a branch or tag. Check
+		// the version list (via source manager) to convert most sanely?
+		ds2 = append(ds2, d2)
+	}
+
+	return ds2, nil
+}
+
 type lDependency1 struct {
 	Name        string   `yaml:"package"`
 	Reference   string   `yaml:"version,omitempty"`
@@ -129,7 +204,7 @@
 	Os          []string `yaml:"os,omitempty"`
 }
 
-// Legacy unmarshaler
+// Legacy unmarshaler for dependency component of yaml files
 func (d *lDependency1) UnmarshalYAML(unmarshal func(interface{}) error) error {
 	newDep := &ldep1{}
 	err := unmarshal(&newDep)