Merge branch 'albrow-feature/no-color'

Conflicts:
	glide.go
diff --git a/.travis.yml b/.travis.yml
index 4c85a20..b66cd48 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -17,8 +17,7 @@
 # in the vendor directory. We don't need to test all dependent packages.
 # Only testing this project.
 script:
-  - GO15VENDOREXPERIMENT=1 go test -v .
-  - GO15VENDOREXPERIMENT=1 go test -v ./cmd
+  - GO15VENDOREXPERIMENT=1 go test -v . ./cmd ./gb ./util ./yaml
 
 notifications:
   irc: "irc.freenode.net#masterminds"
diff --git a/README.md b/README.md
index 5d97527..2d93340 100644
--- a/README.md
+++ b/README.md
@@ -25,6 +25,7 @@
     - hg
     - svn
 * Support custom local and global plugins (see docs/plugins.md)
+* Repository caching including reuse of packages in the `$GOPATH`
 
 ## How It Works
 
diff --git a/cmd/cache.go b/cmd/cache.go
new file mode 100644
index 0000000..0a976e5
--- /dev/null
+++ b/cmd/cache.go
@@ -0,0 +1,101 @@
+package cmd
+
+import (
+	"encoding/json"
+	"errors"
+	"io/ioutil"
+	"net/url"
+	"os"
+	"path/filepath"
+	"strings"
+	"time"
+
+	"github.com/Masterminds/cookoo"
+)
+
+// EnsureCacheDir Creates the $HOME/.glide/cache directory (unless home is
+// specified to be different) if it does not exist.
+func EnsureCacheDir(c cookoo.Context, p *cookoo.Params) (interface{}, cookoo.Interrupt) {
+	home := p.Get("home", "").(string)
+	if home == "" {
+		return nil, errors.New("No home directory set to create")
+	}
+	err := os.MkdirAll(filepath.Join(home, "cache", "info"), os.ModeDir|os.ModePerm)
+	if err != nil {
+		Warn("Error creating Glide directory %s", home)
+	}
+	return false, nil
+}
+
+// Pass in a repo location and get a cache key from it.
+func cacheCreateKey(repo string) (string, error) {
+
+	// A url needs a scheme. A git repo such as
+	// git@github.com:Masterminds/cookoo.git reworked to the url parser.
+	c := strings.Contains(repo, "://")
+	if !c {
+		repo = "ssh://" + repo
+	}
+
+	u, err := url.Parse(repo)
+	if err != nil {
+		return "", err
+	}
+
+	if !c {
+		u.Scheme = ""
+	}
+
+	var key string
+	if u.Scheme != "" {
+		key = u.Scheme + "-"
+	}
+	if u.User != nil && u.User.Username() != "" {
+		key = key + u.User.Username() + "-"
+	}
+	key = key + u.Host
+	if u.Path != "" {
+		key = key + strings.Replace(u.Path, "/", "-", -1)
+	}
+
+	key = strings.Replace(key, ":", "-", -1)
+
+	return key, nil
+}
+
+type cacheRepoInfo struct {
+	DefaultBranch string `json:"default-branch"`
+	LastUpdate    string `json:"last-update"`
+}
+
+func saveCacheRepoData(key string, data cacheRepoInfo, location string) error {
+	data.LastUpdate = time.Now().String()
+	d, err := json.Marshal(data)
+	if err != nil {
+		return err
+	}
+
+	p := filepath.Join(location, "cache", "info", key+".json")
+	f, err := os.Create(p)
+	if err != nil {
+		return err
+	}
+	defer f.Close()
+
+	_, err = f.Write(d)
+	return err
+}
+
+func cacheRepoData(key, location string) (*cacheRepoInfo, error) {
+	c := &cacheRepoInfo{}
+	p := filepath.Join(location, "cache", "info", key+".json")
+	f, err := ioutil.ReadFile(p)
+	if err != nil {
+		return &cacheRepoInfo{}, err
+	}
+	err = json.Unmarshal(f, c)
+	if err != nil {
+		return &cacheRepoInfo{}, err
+	}
+	return c, nil
+}
diff --git a/cmd/cache_test.go b/cmd/cache_test.go
new file mode 100644
index 0000000..6104434
--- /dev/null
+++ b/cmd/cache_test.go
@@ -0,0 +1,21 @@
+package cmd
+
+import "testing"
+
+func TestCacheCreateKey(t *testing.T) {
+	tests := map[string]string{
+		"https://github.com/foo/bar": "https-github.com-foo-bar",
+		"git@github.com:foo/bar":     "git-github.com-foo-bar",
+	}
+
+	for k, v := range tests {
+		key, err := cacheCreateKey(k)
+		if err != nil {
+			t.Errorf("Cache key generation err: %s", err)
+			continue
+		}
+		if key != v {
+			t.Errorf("Expected cache key %s for %s but got %s", v, k, key)
+		}
+	}
+}
diff --git a/cmd/flatten.go b/cmd/flatten.go
index c5174ea..abb3ce8 100644
--- a/cmd/flatten.go
+++ b/cmd/flatten.go
@@ -27,6 +27,11 @@
 func Flatten(c cookoo.Context, p *cookoo.Params) (interface{}, cookoo.Interrupt) {
 	conf := p.Get("conf", &yaml.Config{}).(*yaml.Config)
 	skip := p.Get("skip", false).(bool)
+	home := p.Get("home", "").(string)
+	cache := p.Get("cache", false).(bool)
+	cacheGopath := p.Get("cacheGopath", false).(bool)
+	skipGopath := p.Get("skipGopath", false).(bool)
+
 	if skip {
 		return conf, nil
 	}
@@ -50,7 +55,7 @@
 
 	f := &flattening{conf, vend, vend, deps, packages}
 
-	err := recFlatten(f, force)
+	err := recFlatten(f, force, home, cache, cacheGopath, skipGopath)
 	flattenSetRefs(f)
 	Info("Project relies on %d dependencies.", len(deps))
 	exportFlattenedDeps(conf, deps)
@@ -84,7 +89,7 @@
 var updateCache = map[string]bool{}
 
 // refFlatten recursively flattens the vendor tree.
-func recFlatten(f *flattening, force bool) error {
+func recFlatten(f *flattening, force bool, home string, cache, cacheGopath, skipGopath bool) error {
 	Debug("---> Inspecting %s for changes (%d packages).\n", f.curr, len(f.scan))
 	for _, imp := range f.scan {
 		Debug("----> Scanning %s", imp)
@@ -104,14 +109,14 @@
 
 		if len(mod) > 0 {
 			Debug("----> Updating all dependencies for %q (%d)", imp, len(mod))
-			flattenGlideUp(f, base, force)
+			flattenGlideUp(f, base, home, force, cache, cacheGopath, skipGopath)
 			f2 := &flattening{
 				conf: f.conf,
 				top:  f.top,
 				curr: base,
 				deps: f.deps,
 				scan: mod}
-			recFlatten(f2, force)
+			recFlatten(f2, force, home, cache, cacheGopath, skipGopath)
 		}
 	}
 
@@ -123,7 +128,7 @@
 // While this is expensive, it is also necessary to make sure we have the
 // correct version of all dependencies. We might be able to simplify by
 // marking packages dirty when they are added.
-func flattenGlideUp(f *flattening, base string, force bool) error {
+func flattenGlideUp(f *flattening, base, home string, force, cache, cacheGopath, skipGopath bool) error {
 	//vdir := path.Join(base, "vendor")
 	for _, imp := range f.deps {
 		wd := path.Join(f.top, imp.Name)
@@ -133,7 +138,7 @@
 				continue
 			}
 			Debug("Updating project %s (%s)\n", imp.Name, wd)
-			if err := VcsUpdate(imp, f.top, force); err != nil {
+			if err := VcsUpdate(imp, f.top, home, force, cache, cacheGopath, skipGopath); err != nil {
 				// We can still go on just fine even if this fails.
 				Warn("Skipped update %s: %s\n", imp.Name, err)
 				continue
@@ -141,7 +146,7 @@
 			updateCache[imp.Name] = true
 		} else {
 			Debug("Importing %s to project %s\n", imp.Name, wd)
-			if err := VcsGet(imp, wd); err != nil {
+			if err := VcsGet(imp, wd, home, cache, cacheGopath, skipGopath); err != nil {
 				Warn("Skipped getting %s: %v\n", imp.Name, err)
 				continue
 			}
diff --git a/cmd/get_imports.go b/cmd/get_imports.go
index 3740cc9..fa55356 100644
--- a/cmd/get_imports.go
+++ b/cmd/get_imports.go
@@ -1,7 +1,13 @@
 package cmd
 
 import (
+	"encoding/json"
 	"fmt"
+	"io/ioutil"
+	"net/http"
+	"net/url"
+	"os/exec"
+	"path/filepath"
 	"sort"
 	//"log"
 
@@ -38,6 +44,10 @@
 	names := p.Get("packages", []string{}).([]string)
 	cfg := p.Get("conf", nil).(*yaml.Config)
 	insecure := p.Get("insecure", false).(bool)
+	home := p.Get("home", "").(string)
+	cache := p.Get("cache", false).(bool)
+	cacheGopath := p.Get("cacheGopath", false).(bool)
+	skipGopath := p.Get("skipGopath", false).(bool)
 
 	Info("Preparing to install %d package.", len(names))
 
@@ -60,13 +70,6 @@
 
 		dest := path.Join(cwd, root)
 
-		var repoURL string
-		if insecure {
-			repoURL = "http://" + root
-		} else {
-			repoURL = "https://" + root
-		}
-		repo, err := v.NewRepo(repoURL, dest)
 		if err != nil {
 			Error("Could not construct repo for %q: %s", name, err)
 			return false, err
@@ -86,8 +89,7 @@
 		if len(subpkg) > 0 && subpkg != "/" {
 			dep.Subpackages = []string{subpkg}
 		}
-
-		if err := repo.Get(); err != nil {
+		if err := VcsGet(dep, dest, home, cache, cacheGopath, skipGopath); err != nil {
 			return dep, err
 		}
 
@@ -99,29 +101,6 @@
 	return deps, nil
 }
 
-// GetImports iterates over the imported packages and gets them.
-func GetImports(c cookoo.Context, p *cookoo.Params) (interface{}, cookoo.Interrupt) {
-	cfg := p.Get("conf", nil).(*yaml.Config)
-	cwd, err := VendorPath(c)
-	if err != nil {
-		Error("Failed to prepare vendor directory: %s", err)
-		return false, err
-	}
-
-	if len(cfg.Imports) == 0 {
-		Info("No dependencies found. Nothing downloaded.\n")
-		return false, nil
-	}
-
-	for _, dep := range cfg.Imports {
-		if err := VcsGet(dep, cwd); err != nil {
-			Warn("Skipped getting %s: %v\n", dep.Name, err)
-		}
-	}
-
-	return true, nil
-}
-
 // UpdateImports iterates over the imported packages and updates them.
 //
 // Params:
@@ -133,6 +112,11 @@
 	cfg := p.Get("conf", nil).(*yaml.Config)
 	force := p.Get("force", true).(bool)
 	plist := p.Get("packages", []string{}).([]string)
+	home := p.Get("home", "").(string)
+	cache := p.Get("cache", false).(bool)
+	cacheGopath := p.Get("cacheGopath", false).(bool)
+	skipGopath := p.Get("skipGopath", false).(bool)
+
 	pkgs := list2map(plist)
 	restrict := len(pkgs) > 0
 
@@ -157,7 +141,7 @@
 		// flattening from causing unnecessary updates.
 		updateCache[dep.Name] = true
 
-		if err := VcsUpdate(dep, cwd, force); err != nil {
+		if err := VcsUpdate(dep, cwd, home, force, cache, cacheGopath, skipGopath); err != nil {
 			Warn("Update failed for %s: %s\n", dep.Name, err)
 		}
 	}
@@ -233,18 +217,206 @@
 // VcsGet figures out how to fetch a dependency, and then gets it.
 //
 // VcsGet installs into the dest.
-func VcsGet(dep *yaml.Dependency, dest string) error {
+func VcsGet(dep *yaml.Dependency, dest, home string, cache, cacheGopath, skipGopath bool) error {
 
+	// When not skipping the $GOPATH look in it for a copy of the package
+	if !skipGopath {
+		// Check if the $GOPATH has a viable version to use and if so copy to vendor
+		gps := Gopaths()
+		for _, p := range gps {
+			d := filepath.Join(p, "src", dep.Name)
+			if _, err := os.Stat(d); err == nil {
+				empty, err := isDirectoryEmpty(d)
+				if empty || err != nil {
+					continue
+				}
+
+				repo, err := dep.GetRepo(d)
+				if err != nil {
+					continue
+				}
+
+				// Dirty repos have uncomitted changes.
+				if repo.IsDirty() {
+					continue
+				}
+
+				// Having found a repo we copy it to vendor and update it.
+				Debug("Found %s in GOPATH at %s. Copying to %s", dep.Name, d, dest)
+				err = copyDir(d, dest)
+				if err != nil {
+					return err
+				}
+
+				// Update the repo in the vendor directory
+				Debug("Updating %s, now in the vendor path at %s", dep.Name, dest)
+				repo, err = dep.GetRepo(dest)
+				if err != nil {
+					return err
+				}
+				err = repo.Update()
+				if err != nil {
+					return err
+				}
+
+				// If there is no reference set on the dep we try to checkout
+				// the default branch.
+				if dep.Reference == "" {
+					db := defaultBranch(repo, home)
+					if db != "" {
+						err = repo.UpdateVersion(db)
+						if err != nil {
+							Debug("Attempting to set the version on %s to %s failed. Error %s", dep.Name, db, err)
+						}
+					}
+				}
+				return nil
+			}
+		}
+	}
+
+	// When opting in to cache in the GOPATH attempt to do put a copy there.
+	if cacheGopath {
+
+		// Since we didn't find an existing copy in the GOPATHs try to clone there.
+		gp := Gopath()
+		if gp != "" {
+			d := filepath.Join(gp, "src", dep.Name)
+			if _, err := os.Stat(d); os.IsNotExist(err) {
+				// Empty directory so we checkout out the code here.
+				Debug("Retrieving %s to %s before copying to vendor", dep.Name, d)
+				repo, err := dep.GetRepo(d)
+				if err != nil {
+					return err
+				}
+				repo.Get()
+
+				branch := findCurrentBranch(repo)
+				if branch != "" {
+					// we know the default branch so we can store it in the cache
+					var loc string
+					if dep.Repository != "" {
+						loc = dep.Repository
+					} else {
+						loc = "https://" + dep.Name
+					}
+					key, err := cacheCreateKey(loc)
+					if err == nil {
+						Debug("Saving default branch for %s", repo.Remote())
+						c := cacheRepoInfo{DefaultBranch: branch}
+						saveCacheRepoData(key, c, home)
+					}
+				}
+
+				Debug("Copying %s from GOPATH at %s to %s", dep.Name, d, dest)
+				err = copyDir(d, dest)
+				if err != nil {
+					return err
+				}
+
+				return nil
+			}
+		}
+	}
+
+	// If opting in to caching attempt to put it in the cache folder
+	if cache {
+		// Check if the cache has a viable version and try to use that.
+		var loc string
+		if dep.Repository != "" {
+			loc = dep.Repository
+		} else {
+			loc = "https://" + dep.Name
+		}
+		key, err := cacheCreateKey(loc)
+		if err == nil {
+			d := filepath.Join(home, "cache", "src", key)
+
+			repo, err := dep.GetRepo(d)
+			if err != nil {
+				return err
+			}
+			// If the directory does not exist this is a first cache.
+			if _, err = os.Stat(d); os.IsNotExist(err) {
+				Debug("Adding %s to the cache for the first time", dep.Name)
+				err = repo.Get()
+				if err != nil {
+					return err
+				}
+				branch := findCurrentBranch(repo)
+				if branch != "" {
+					// we know the default branch so we can store it in the cache
+					var loc string
+					if dep.Repository != "" {
+						loc = dep.Repository
+					} else {
+						loc = "https://" + dep.Name
+					}
+					key, err := cacheCreateKey(loc)
+					if err == nil {
+						Debug("Saving default branch for %s", repo.Remote())
+						c := cacheRepoInfo{DefaultBranch: branch}
+						err = saveCacheRepoData(key, c, home)
+						if err != nil {
+							Debug("Error saving %s to cache. Error: %s", repo.Remote(), err)
+						}
+					}
+				}
+
+			} else {
+				Debug("Updating %s in the cache", dep.Name)
+				err = repo.Update()
+				if err != nil {
+					return err
+				}
+			}
+
+			Debug("Copying %s from the cache to %s", dep.Name, dest)
+			err = copyDir(d, dest)
+			if err != nil {
+				return err
+			}
+
+			return nil
+		} else {
+			Warn("Cache key generation error: %s", err)
+		}
+	}
+
+	// If unable to cache pull directly into the vendor/ directory.
 	repo, err := dep.GetRepo(dest)
 	if err != nil {
 		return err
 	}
 
-	return repo.Get()
+	gerr := repo.Get()
+
+	// Attempt to cache the default branch
+	branch := findCurrentBranch(repo)
+	if branch != "" {
+		// we know the default branch so we can store it in the cache
+		var loc string
+		if dep.Repository != "" {
+			loc = dep.Repository
+		} else {
+			loc = "https://" + dep.Name
+		}
+		key, err := cacheCreateKey(loc)
+		if err == nil {
+			Debug("Saving default branch for %s", repo.Remote())
+			c := cacheRepoInfo{DefaultBranch: branch}
+			err = saveCacheRepoData(key, c, home)
+			if err != nil {
+				Debug("Error saving %s to cache. Error: %s", repo.Remote(), err)
+			}
+		}
+	}
+
+	return gerr
 }
 
 // VcsUpdate updates to a particular checkout based on the VCS setting.
-func VcsUpdate(dep *yaml.Dependency, vend string, force bool) error {
+func VcsUpdate(dep *yaml.Dependency, vend, home string, force, cache, cacheGopath, skipGopath bool) error {
 	Info("Fetching updates for %s.\n", dep.Name)
 
 	if filterArchOs(dep) {
@@ -255,7 +427,7 @@
 	dest := path.Join(vend, dep.Name)
 	// If destination doesn't exist we need to perform an initial checkout.
 	if _, err := os.Stat(dest); os.IsNotExist(err) {
-		if err = VcsGet(dep, dest); err != nil {
+		if err = VcsGet(dep, dest, home, cache, cacheGopath, skipGopath); err != nil {
 			Warn("Unable to checkout %s\n", dep.Name)
 			return err
 		}
@@ -292,7 +464,7 @@
 				if rerr != nil {
 					return rerr
 				}
-				if err = VcsGet(dep, dest); err != nil {
+				if err = VcsGet(dep, dest, home, cache, cacheGopath, skipGopath); err != nil {
 					Warn("Unable to checkout %s\n", dep.Name)
 					return err
 				}
@@ -430,3 +602,158 @@
 
 	return version, nil
 }
+
+// Some repos will have multiple branches in them (e.g. Git) while others
+// (e.g. Svn) will not.
+// TODO(mattfarina): Add API calls to github, bitbucket, etc.
+func defaultBranch(repo v.Repo, home string) string {
+
+	// Svn and Bzr use different locations (paths or entire locations)
+	// for branches so we won't have a default branch.
+	if repo.Vcs() == v.Svn || repo.Vcs() == v.Bzr {
+		return ""
+	}
+
+	// Check the cache for a value.
+	key, kerr := cacheCreateKey(repo.Remote())
+	var d cacheRepoInfo
+	if kerr == nil {
+		d, err := cacheRepoData(key, home)
+		if err == nil {
+			if d.DefaultBranch != "" {
+				return d.DefaultBranch
+			}
+		}
+	}
+
+	// If we don't have it in the store try some APIs
+	r := repo.Remote()
+	u, err := url.Parse(r)
+	if err != nil {
+		return ""
+	}
+	if u.Scheme == "" {
+		// Where there is no scheme we try urls like git@github.com:foo/bar
+		r = strings.Replace(r, ":", "/", -1)
+		r = "ssh://" + r
+		u, err = url.Parse(r)
+		if err != nil {
+			return ""
+		}
+		u.Scheme = ""
+	}
+	if u.Host == "github.com" {
+		parts := strings.Split(u.Path, "/")
+		if len(parts) != 2 {
+			return ""
+		}
+		api := fmt.Sprintf("https://api.github.com/repos/%s/%s", parts[0], parts[1])
+		resp, err := http.Get(api)
+		if err != nil {
+			return ""
+		}
+		defer resp.Body.Close()
+		if resp.StatusCode >= 300 || resp.StatusCode < 200 {
+			return ""
+		}
+		body, err := ioutil.ReadAll(resp.Body)
+		var data interface{}
+		err = json.Unmarshal(body, &data)
+		if err != nil {
+			return ""
+		}
+		gh := data.(map[string]interface{})
+		db := gh["default_branch"].(string)
+		if kerr == nil {
+			d.DefaultBranch = db
+			saveCacheRepoData(key, d, home)
+		}
+		return db
+	}
+
+	if u.Host == "bitbucket.org" {
+		parts := strings.Split(u.Path, "/")
+		if len(parts) != 2 {
+			return ""
+		}
+		api := fmt.Sprintf("https://bitbucket.org/api/1.0/repositories/%s/%s/main-branch/", parts[0], parts[1])
+		resp, err := http.Get(api)
+		if err != nil {
+			return ""
+		}
+		defer resp.Body.Close()
+		if resp.StatusCode >= 300 || resp.StatusCode < 200 {
+			return ""
+		}
+		body, err := ioutil.ReadAll(resp.Body)
+		var data interface{}
+		err = json.Unmarshal(body, &data)
+		if err != nil {
+			return ""
+		}
+		bb := data.(map[string]interface{})
+		db := bb["name"].(string)
+		if kerr == nil {
+			d.DefaultBranch = db
+			saveCacheRepoData(key, d, home)
+		}
+		return db
+	}
+
+	return ""
+}
+
+// From a local repo find out the current branch name if there is one.
+func findCurrentBranch(repo v.Repo) string {
+	Debug("Attempting to find current branch for %s", repo.Remote())
+	// Svn and Bzr don't have default branches.
+	if repo.Vcs() == v.Svn || repo.Vcs() == v.Bzr {
+		return ""
+	}
+
+	if repo.Vcs() == v.Git {
+		c := exec.Command("git", "symbolic-ref", "--short", "HEAD")
+		c.Dir = repo.LocalPath()
+		c.Env = envForDir(c.Dir)
+		out, err := c.CombinedOutput()
+		if err != nil {
+			Debug("Unable to find current branch for %s, error: %s", repo.Remote(), err)
+			return ""
+		}
+		return strings.TrimSpace(string(out))
+	}
+
+	if repo.Vcs() == v.Hg {
+		c := exec.Command("hg", "branch")
+		c.Dir = repo.LocalPath()
+		c.Env = envForDir(c.Dir)
+		out, err := c.CombinedOutput()
+		if err != nil {
+			Debug("Unable to find current branch for %s, error: %s", repo.Remote(), err)
+			return ""
+		}
+		return strings.TrimSpace(string(out))
+	}
+
+	return ""
+}
+
+func envForDir(dir string) []string {
+	env := os.Environ()
+	return mergeEnvLists([]string{"PWD=" + dir}, env)
+}
+
+func mergeEnvLists(in, out []string) []string {
+NextVar:
+	for _, inkv := range in {
+		k := strings.SplitAfterN(inkv, "=", 2)[0]
+		for i, outkv := range out {
+			if strings.HasPrefix(outkv, k) {
+				out[i] = inkv
+				continue NextVar
+			}
+		}
+		out = append(out, inkv)
+	}
+	return out
+}
diff --git a/cmd/get_imports_test.go b/cmd/get_imports_test.go
index 9deddf4..b072ed4 100644
--- a/cmd/get_imports_test.go
+++ b/cmd/get_imports_test.go
@@ -1,26 +1,6 @@
 package cmd
 
-import (
-	"testing"
-
-	"github.com/Masterminds/cookoo"
-	"github.com/Masterminds/glide/yaml"
-)
-
-func TestGetImportsEmptyConfig(t *testing.T) {
-	_, _, c := cookoo.Cookoo()
-	SilenceLogs(c)
-	cfg := new(yaml.Config)
-	p := cookoo.NewParamsWithValues(map[string]interface{}{"conf": cfg})
-	res, it := GetImports(c, p)
-	if it != nil {
-		t.Errorf("Interrupt value non-nil")
-	}
-	bres, ok := res.(bool)
-	if !ok || bres {
-		t.Errorf("Result was non-bool or true: ok=%t bres=%t", ok, bres)
-	}
-}
+import "github.com/Masterminds/cookoo"
 
 func SilenceLogs(c cookoo.Context) {
 	p := cookoo.NewParamsWithValues(map[string]interface{}{"quiet": true})
diff --git a/cmd/util.go b/cmd/util.go
index 9bc1c96..3a27e63 100644
--- a/cmd/util.go
+++ b/cmd/util.go
@@ -162,3 +162,78 @@
 	}
 	return true, err
 }
+
+// We copy the directory here rather than jumping out to a shell so we can
+// support multiple operating systems.
+func copyDir(source string, dest string) error {
+
+	// get properties of source dir
+	si, err := os.Stat(source)
+	if err != nil {
+		return err
+	}
+
+	err = os.MkdirAll(dest, si.Mode())
+	if err != nil {
+		return err
+	}
+
+	d, _ := os.Open(source)
+
+	objects, err := d.Readdir(-1)
+
+	for _, obj := range objects {
+
+		sp := filepath.Join(source, "/", obj.Name())
+
+		dp := filepath.Join(dest, "/", obj.Name())
+
+		if obj.IsDir() {
+			err = copyDir(sp, dp)
+			if err != nil {
+				return err
+			}
+		} else {
+			// perform copy
+			err = copyFile(sp, dp)
+			if err != nil {
+				return err
+			}
+		}
+
+	}
+	return nil
+}
+
+func copyFile(source string, dest string) error {
+	ln, err := os.Readlink(source)
+	if err == nil {
+		return os.Symlink(ln, dest)
+	}
+	s, err := os.Open(source)
+	if err != nil {
+		return err
+	}
+
+	defer s.Close()
+
+	d, err := os.Create(dest)
+	if err != nil {
+		return err
+	}
+
+	defer d.Close()
+
+	_, err = io.Copy(d, s)
+	if err != nil {
+		return err
+	}
+
+	si, err := os.Stat(source)
+	if err != nil {
+		return err
+	}
+	err = os.Chmod(dest, si.Mode())
+
+	return err
+}
diff --git a/glide.go b/glide.go
index 89be05c..9e7ad57 100644
--- a/glide.go
+++ b/glide.go
@@ -37,6 +37,8 @@
 package main
 
 import (
+	"path/filepath"
+
 	"github.com/Masterminds/glide/cmd"
 
 	"github.com/Masterminds/cookoo"
@@ -44,6 +46,7 @@
 
 	"fmt"
 	"os"
+	"os/user"
 )
 
 var version = "dev"
@@ -94,6 +97,12 @@
 			Name:  "debug",
 			Usage: "Print Debug messages (verbose)",
 		},
+		cli.StringFlag{
+			Name:   "home",
+			Value:  defaultGlideDir(),
+			Usage:  "The location of Glide files",
+			EnvVar: "GLIDE_HOME",
+		},
 		cli.BoolFlag{
 			Name:  "no-color",
 			Usage: "Turn off colored output for log messages",
@@ -172,6 +181,18 @@
 					Name:  "insecure",
 					Usage: "Use http:// rather than https:// to retrieve pacakges.",
 				},
+				cli.BoolFlag{
+					Name:  "cache",
+					Usage: "When downloading dependencies attempt to cache them.",
+				},
+				cli.BoolFlag{
+					Name:  "cache-gopath",
+					Usage: "When downloading dependencies attempt to put them in the GOPATH, too.",
+				},
+				cli.BoolFlag{
+					Name:  "skip-gopath",
+					Usage: "Skip attempting to copy a dependency from the GOPATH.",
+				},
 			},
 			Action: func(c *cli.Context) {
 				if len(c.Args()) < 1 {
@@ -181,6 +202,9 @@
 				cxt.Put("packages", []string(c.Args()))
 				cxt.Put("skipFlatten", !c.Bool("no-recursive"))
 				cxt.Put("insecure", c.Bool("insecure"))
+				cxt.Put("useCache", c.Bool("cache"))
+				cxt.Put("cacheGopath", c.Bool("cache-gopath"))
+				cxt.Put("skipGopath", c.Bool("skip-gopath"))
 				// FIXME: Are these used anywhere?
 				if c.Bool("import") {
 					cxt.Put("importGodeps", true)
@@ -342,6 +366,18 @@
 					Name:  "file, f",
 					Usage: "Save all of the discovered dependencies to a Glide YAML file.",
 				},
+				cli.BoolFlag{
+					Name:  "cache",
+					Usage: "When downloading dependencies attempt to cache them.",
+				},
+				cli.BoolFlag{
+					Name:  "cache-gopath",
+					Usage: "When downloading dependencies attempt to put them in the GOPATH, too.",
+				},
+				cli.BoolFlag{
+					Name:  "skip-gopath",
+					Usage: "Skip attempting to copy a dependency from the GOPATH.",
+				},
 			},
 			Action: func(c *cli.Context) {
 				cxt.Put("deleteOptIn", c.Bool("delete"))
@@ -350,6 +386,9 @@
 				cxt.Put("deleteFlatten", c.Bool("delete-flatten"))
 				cxt.Put("toPath", c.String("file"))
 				cxt.Put("toStdout", false)
+				cxt.Put("useCache", c.Bool("cache"))
+				cxt.Put("cacheGopath", c.Bool("cache-gopath"))
+				cxt.Put("skipGopath", c.Bool("skip-gopath"))
 				if c.Bool("import") {
 					cxt.Put("importGodeps", true)
 					cxt.Put("importGPM", true)
@@ -425,6 +464,7 @@
 	cxt.Put("debug", c.GlobalBool("debug"))
 	cxt.Put("no-color", c.GlobalBool("no-color"))
 	cxt.Put("yaml", c.GlobalString("yaml"))
+	cxt.Put("home", c.GlobalString("home"))
 	cxt.Put("cliArgs", c.Args())
 	if err := router.HandleRequest(route, cxt, false); err != nil {
 		fmt.Printf("Oops! %s\n", err)
@@ -444,7 +484,8 @@
 
 	reg.Route("@ready", "Prepare for glide commands.").
 		Does(cmd.ReadyToGlide, "ready").Using("filename").From("cxt:yaml").
-		Does(cmd.ParseYaml, "cfg").Using("filename").From("cxt:yaml")
+		Does(cmd.ParseYaml, "cfg").Using("filename").From("cxt:yaml").
+		Does(cmd.EnsureCacheDir, "_").Using("home").From("cxt:home")
 
 	reg.Route("get", "Install a pkg in vendor, and store the results in the glide.yaml").
 		Includes("@startup").
@@ -454,9 +495,17 @@
 		Using("packages").From("cxt:packages").
 		Using("conf").From("cxt:cfg").
 		Using("insecure").From("cxt:insecure").
+		Using("home").From("cxt:home").
+		Using("cache").From("cxt:useCache").
+		Using("cacheGopath").From("cxt:cacheGopath").
+		Using("skipGopath").From("cxt:skipGopath").
 		Does(cmd.Flatten, "flatten").Using("conf").From("cxt:cfg").
 		Using("packages").From("cxt:packages").
 		Using("force").From("cxt:forceUpdate").
+		Using("home").From("cxt:home").
+		Using("cache").From("cxt:useCache").
+		Using("cacheGopath").From("cxt:cacheGopath").
+		Using("skipGopath").From("cxt:skipGopath").
 		Does(cmd.WriteYaml, "out").
 		Using("conf").From("cxt:cfg").
 		Using("filename").WithDefault("glide.yaml").From("cxt:yaml")
@@ -483,11 +532,19 @@
 		Using("conf").From("cxt:cfg").
 		Using("force").From("cxt:forceUpdate").
 		Using("packages").From("cxt:packages").
+		Using("home").From("cxt:home").
+		Using("cache").From("cxt:useCache").
+		Using("cacheGopath").From("cxt:cacheGopath").
+		Using("skipGopath").From("cxt:skipGopath").
 		Does(cmd.SetReference, "version").Using("conf").From("cxt:cfg").
 		Does(cmd.Flatten, "flattened").Using("conf").From("cxt:cfg").
 		Using("packages").From("cxt:packages").
 		Using("force").From("cxt:forceUpdate").
 		Using("skip").From("cxt:skipFlatten").
+		Using("home").From("cxt:home").
+		Using("cache").From("cxt:useCache").
+		Using("cacheGopath").From("cxt:cacheGopath").
+		Using("skipGopath").From("cxt:skipGopath").
 		Does(cmd.VendoredCleanUp, "_").
 		Using("conf").From("cxt:cfg").
 		Using("update").From("cxt:updateVendoredDeps").
@@ -588,3 +645,11 @@
 		Does(cmd.DropToShell, "plugin").
 		Using("command").From("cxt:command")
 }
+
+func defaultGlideDir() string {
+	c, err := user.Current()
+	if err != nil {
+		return ""
+	}
+	return filepath.Join(c.HomeDir, ".glide")
+}
diff --git a/glide.yaml b/glide.yaml
index aefd6a6..55f6078 100644
--- a/glide.yaml
+++ b/glide.yaml
@@ -1,9 +1,6 @@
 package: github.com/Masterminds/glide
 import:
-  - package: github.com/kylelemons/go-gypsy
-    subpackages:
-      - yaml
-    flatten: true
+  - package: gopkg.in/yaml.v2
   - package: github.com/Masterminds/cookoo
     version: master
     repo:    git@github.com:Masterminds/cookoo.git
@@ -11,7 +8,7 @@
     subpackages:
       - .
   - package: github.com/Masterminds/vcs
-    version: ^1.1.4
+    version: ^1.2.0
   - package: github.com/codegangsta/cli
   - package: github.com/Masterminds/semver
     version: ^1.0.0
diff --git a/yaml/yaml.go b/yaml/yaml.go
index de0fcb2..be1e8bb 100644
--- a/yaml/yaml.go
+++ b/yaml/yaml.go
@@ -72,7 +72,7 @@
 	Parent     *Config
 	Name       string       `yaml:"package"`
 	Imports    Dependencies `yaml:"import"`
-	DevImports Dependencies `yaml:"devimport"`
+	DevImports Dependencies `yaml:"devimport,omitempty"`
 }
 
 // HasDependency returns true if the given name is listed as an import or dev import.