Initial commit based on gb c057f8ce
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..abe0e88
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,22 @@
+The MIT License (MIT)
+
+Copyright (c) 2015 constabulary
+Copyright (c) 2015 Filippo Valsorda
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..993b22d
--- /dev/null
+++ b/README.md
@@ -0,0 +1,63 @@
+# gvt, the go vendoring tool
+
+`gvt` is a simple Go vendoring tool made for the GO15VENDOREXPERIMENT. It's based entirely on [gb-vendor](https://github.com/constabulary/gb).
+
+You run `gvt fetch` when you would run `go get`. gvt downloads dependencies to `./vendor/`. With `GO15VENDOREXPERIMENT=1` set the compiler will find and use those packages without need to do import path rewriting. `gvt` works recursively as you would expect, and lets you update vendored dependencies. It also writes a manifest to `./vendor/manifest`.
+
+Packages whose dependencies are vendored with gvt are `go build`-able and `go get`-able by Go 1.5 with `GO15VENDOREXPERIMENT=1` set.
+
+## Installation
+
+```
+go get -u github.com/FiloSottile/gvt
+```
+
+## Usage
+
+```
+$ gvt fetch github.com/fatih/color
+2015/09/05 02:38:06 fetching recursive dependency github.com/mattn/go-isatty
+2015/09/05 02:38:07 fetching recursive dependency github.com/shiena/ansicolor
+
+$ tree -d
+.
+└── vendor
+    └── github.com
+        ├── fatih
+        │   └── color
+        ├── mattn
+        │   └── go-isatty
+        └── shiena
+            └── ansicolor
+                └── ansicolor
+
+9 directories
+
+$ cat > main.go
+package main
+import "github.com/fatih/color"
+func main() {
+    color.Red("Hello, world!")
+}
+
+$ export GO15VENDOREXPERIMENT=1
+
+$ go build .
+
+$ ./hello
+Hello, world!
+
+$ gvt update github.com/fatih/color
+```
+
+[Full usage on godoc.](https://godoc.org/github.com/FiloSottile/gvt)
+
+## Why
+
+There are many Go vendoring tools, but they all have some subset of the following problems
+
+    * no GO15VENDOREXPERIMENT support: old tools are based on import path rewriting or GOPATH overrides
+    * requirement to run on clients: some require the user to install the tool and run it after cloning, which breaks `go get`
+    * **no real fetching support**: tools like Godep just copy source from your GOPATH, instead of pulling it from the Internet
+    * prominent metadata files: there's no need for the manifest to be in your repository root, or in its own empty folder
+    * entire different build stack: gb-vendor is awesome but it requires you to build your project with gb
diff --git a/alldocs.go b/alldocs.go
new file mode 100644
index 0000000..32da424
--- /dev/null
+++ b/alldocs.go
@@ -0,0 +1,92 @@
+// DO NOT EDIT THIS FILE.
+//go:generate gvt help documentation
+
+/*
+gvt, a tool to manage your vendored dependencies.
+
+Usage:
+        gvt command [arguments]
+
+The commands are:
+
+        fetch       fetch a remote dependency
+        update      update a local dependency
+        list        lists dependencies, one per line
+        delete      delete a local dependency
+
+Use "gvt help [command]" for more information about a command.
+
+
+## Fetch a remote dependency
+
+Usage:
+        gvt fetch [-branch branch | -revision rev | -tag tag] [-precaire] [-no-recurse] importpath
+
+fetch vendors an upstream import path.
+
+The import path may include a url scheme. This may be useful when fetching dependencies
+from private repositories that cannot be probed.
+
+Flags:
+	-branch branch
+		fetch from the name branch. If not supplied the default upstream
+		branch will be used.
+	-no-recurse
+		do not fetch recursively.
+	-tag tag
+		fetch the specified tag. If not supplied the default upstream
+		branch will be used.
+	-revision rev
+		fetch the specific revision from the branch (if supplied). If no
+		revision supplied, the latest available will be supplied.
+	-precaire
+		allow the use of insecure protocols.
+
+## Update a local dependency
+
+Usage:
+        gvt update [-all] import
+
+update will replaces the source with the latest available from the head of the master branch.
+
+Updating from one copy of a dependency to another comes with several restrictions.
+The first is you can only update to the head of the branch your dependency was vendored from, switching branches is not supported.
+The second restriction is if you have used -tag or -revision while vendoring a dependency, your dependency is "headless"
+(to borrow a term from git) and cannot be updated.
+
+To update across branches, or from one tag/revision to another, you must first use delete to remove the dependency, then
+fetch [-tag | -revision | -branch ] [-precaire] to replace it.
+
+Flags:
+	-all
+		will update all dependencies in the manifest, otherwise only the dependency supplied.
+	-precaire
+		allow the use of insecure protocols.
+
+## Lists dependencies, one per line
+
+Usage:
+        gvt list [-f format]
+
+list formats the contents of the manifest file.
+
+The output
+
+Flags:
+	-f
+		controls the template used for printing each manifest entry. If not supplied
+		the default value is "{{.Importpath}}\t{{.Repository}}{{.Path}}\t{{.Branch}}\t{{.Revision}}"
+
+## Delete a local dependency
+
+Usage:
+        gvt delete [-all] importpath
+
+delete removes a dependency from the vendor directory and the manifest
+
+Flags:
+	-all
+		remove all dependencies
+
+*/
+package main
diff --git a/delete.go b/delete.go
new file mode 100644
index 0000000..9a0476c
--- /dev/null
+++ b/delete.go
@@ -0,0 +1,70 @@
+package main
+
+import (
+	"flag"
+	"fmt"
+	"path/filepath"
+
+	"github.com/FiloSottile/gvt/gbvendor"
+)
+
+var (
+	deleteAll bool // delete all dependencies
+)
+
+func addDeleteFlags(fs *flag.FlagSet) {
+	fs.BoolVar(&deleteAll, "all", false, "delete all dependencies")
+}
+
+var cmdDelete = &Command{
+	Name:      "delete",
+	UsageLine: "delete [-all] importpath",
+	Short:     "delete a local dependency",
+	Long: `delete removes a dependency from the vendor directory and the manifest
+
+Flags:
+	-all
+		remove all dependencies
+
+`,
+	Run: func(args []string) error {
+		if len(args) != 1 && !deleteAll {
+			return fmt.Errorf("delete: import path or --all flag is missing")
+		} else if len(args) == 1 && deleteAll {
+			return fmt.Errorf("delete: you cannot specify path and --all flag at once")
+		}
+
+		m, err := vendor.ReadManifest(manifestFile())
+		if err != nil {
+			return fmt.Errorf("could not load manifest: %v", err)
+		}
+
+		var dependencies []vendor.Dependency
+		if deleteAll {
+			dependencies = make([]vendor.Dependency, len(m.Dependencies))
+			copy(dependencies, m.Dependencies)
+		} else {
+			p := args[0]
+			dependency, err := m.GetDependencyForImportpath(p)
+			if err != nil {
+				return fmt.Errorf("could not get dependency: %v", err)
+			}
+			dependencies = append(dependencies, dependency)
+		}
+
+		for _, d := range dependencies {
+			path := d.Importpath
+
+			if err := m.RemoveDependency(d); err != nil {
+				return fmt.Errorf("dependency could not be deleted: %v", err)
+			}
+
+			if err := vendor.RemoveAll(filepath.Join(vendorDir(), filepath.FromSlash(path))); err != nil {
+				// TODO(dfc) need to apply vendor.cleanpath here to remove indermediate directories.
+				return fmt.Errorf("dependency could not be deleted: %v", err)
+			}
+		}
+		return vendor.WriteManifest(manifestFile(), m)
+	},
+	AddFlags: addDeleteFlags,
+}
diff --git a/fetch.go b/fetch.go
new file mode 100644
index 0000000..831d620
--- /dev/null
+++ b/fetch.go
@@ -0,0 +1,285 @@
+package main
+
+import (
+	"flag"
+	"fmt"
+	"log"
+	"net/url"
+	"path/filepath"
+	"runtime"
+	"sort"
+
+	"go/build"
+
+	"github.com/FiloSottile/gvt/gbvendor"
+)
+
+var (
+	branch    string
+	revision  string // revision (commit)
+	tag       string
+	noRecurse bool
+	insecure  bool // Allow the use of insecure protocols
+
+	recurse bool // should we fetch recursively
+)
+
+func addFetchFlags(fs *flag.FlagSet) {
+	fs.StringVar(&branch, "branch", "", "branch of the package")
+	fs.StringVar(&revision, "revision", "", "revision of the package")
+	fs.StringVar(&tag, "tag", "", "tag of the package")
+	fs.BoolVar(&noRecurse, "no-recurse", false, "do not fetch recursively")
+	fs.BoolVar(&insecure, "precaire", false, "allow the use of insecure protocols")
+}
+
+var cmdFetch = &Command{
+	Name:      "fetch",
+	UsageLine: "fetch [-branch branch | -revision rev | -tag tag] [-precaire] [-no-recurse] importpath",
+	Short:     "fetch a remote dependency",
+	Long: `fetch vendors an upstream import path.
+
+The import path may include a url scheme. This may be useful when fetching dependencies
+from private repositories that cannot be probed.
+
+Flags:
+	-branch branch
+		fetch from the name branch. If not supplied the default upstream
+		branch will be used.
+	-no-recurse
+		do not fetch recursively.
+	-tag tag
+		fetch the specified tag. If not supplied the default upstream
+		branch will be used.
+	-revision rev
+		fetch the specific revision from the branch (if supplied). If no
+		revision supplied, the latest available will be supplied.
+	-precaire
+		allow the use of insecure protocols.
+
+`,
+	Run: func(args []string) error {
+		switch len(args) {
+		case 0:
+			return fmt.Errorf("fetch: import path missing")
+		case 1:
+			path := args[0]
+			recurse = !noRecurse
+			return fetch(path, recurse)
+		default:
+			return fmt.Errorf("more than one import path supplied")
+		}
+	},
+	AddFlags: addFetchFlags,
+}
+
+func fetch(path string, recurse bool) error {
+	m, err := vendor.ReadManifest(manifestFile())
+	if err != nil {
+		return fmt.Errorf("could not load manifest: %v", err)
+	}
+
+	repo, extra, err := vendor.DeduceRemoteRepo(path, insecure)
+	if err != nil {
+		return err
+	}
+
+	// strip of any scheme portion from the path, it is already
+	// encoded in the repo.
+	path = stripscheme(path)
+
+	if m.HasImportpath(path) {
+		return fmt.Errorf("%s is already vendored", path)
+	}
+
+	var wc vendor.WorkingCopy
+
+	// if we are not recursing, then always fetch the HEAD of the master
+	if recurse {
+		wc, err = repo.Checkout(branch, tag, revision)
+	} else {
+		wc, err = repo.Checkout("", "", "")
+	}
+
+	if err != nil {
+		return err
+	}
+
+	rev, err := wc.Revision()
+	if err != nil {
+		return err
+	}
+
+	branch, err := wc.Branch()
+	if err != nil {
+		return err
+	}
+
+	dep := vendor.Dependency{
+		Importpath: path,
+		Repository: repo.URL(),
+		Revision:   rev,
+		Branch:     branch,
+		Path:       extra,
+	}
+
+	if err := m.AddDependency(dep); err != nil {
+		return err
+	}
+
+	dst := filepath.Join(vendorDir(), dep.Importpath)
+	src := filepath.Join(wc.Dir(), dep.Path)
+
+	if err := vendor.Copypath(dst, src); err != nil {
+		return err
+	}
+
+	if err := vendor.WriteManifest(manifestFile(), m); err != nil {
+		return err
+	}
+
+	if err := wc.Destroy(); err != nil {
+		return err
+	}
+
+	if !recurse {
+		return nil
+	}
+
+	for done := false; !done; {
+
+		paths := []struct {
+			Root, Prefix string
+		}{
+			{filepath.Join(runtime.GOROOT(), "src"), ""},
+		}
+		m, err := vendor.ReadManifest(manifestFile())
+		if err != nil {
+			return err
+		}
+		for _, d := range m.Dependencies {
+			paths = append(paths, struct{ Root, Prefix string }{filepath.Join(vendorDir(), filepath.FromSlash(d.Importpath)), filepath.FromSlash(d.Importpath)})
+		}
+
+		dsm, err := vendor.LoadPaths(paths...)
+		if err != nil {
+			return err
+		}
+
+		is, ok := dsm[filepath.Join(vendorDir(), path)]
+		if !ok {
+			return fmt.Errorf("unable to locate depset for %q", path)
+		}
+
+		missing := findMissing(pkgs(is.Pkgs), dsm)
+		switch len(missing) {
+		case 0:
+			done = true
+		default:
+
+			// sort keys in ascending order, so the shortest missing import path
+			// with be fetched first.
+			keys := keys(missing)
+			sort.Strings(keys)
+			pkg := keys[0]
+			log.Printf("fetching recursive dependency %s", pkg)
+			if err := fetch(pkg, false); err != nil {
+				return err
+			}
+		}
+	}
+
+	return nil
+}
+
+func keys(m map[string]bool) []string {
+	var s []string
+	for k := range m {
+		s = append(s, k)
+	}
+	return s
+}
+
+func pkgs(m map[string]*vendor.Pkg) []*vendor.Pkg {
+	var p []*vendor.Pkg
+	for _, v := range m {
+		p = append(p, v)
+	}
+	return p
+}
+
+func findMissing(pkgs []*vendor.Pkg, dsm map[string]*vendor.Depset) map[string]bool {
+	missing := make(map[string]bool)
+	imports := make(map[string]*vendor.Pkg)
+	for _, s := range dsm {
+		for _, p := range s.Pkgs {
+			imports[p.ImportPath] = p
+		}
+	}
+
+	// make fake C package for cgo
+	imports["C"] = &vendor.Pkg{
+		Depset: nil, // probably a bad idea
+		Package: &build.Package{
+			Name: "C",
+		},
+	}
+	stk := make(map[string]bool)
+	push := func(v string) {
+		if stk[v] {
+			panic(fmt.Sprintln("import loop:", v, stk))
+		}
+		stk[v] = true
+	}
+	pop := func(v string) {
+		if !stk[v] {
+			panic(fmt.Sprintln("impossible pop:", v, stk))
+		}
+		delete(stk, v)
+	}
+
+	// checked records import paths who's dependencies are all present
+	checked := make(map[string]bool)
+
+	var fn func(string)
+	fn = func(importpath string) {
+		p, ok := imports[importpath]
+		if !ok {
+			missing[importpath] = true
+			return
+		}
+
+		// have we already walked this arm, if so, skip it
+		if checked[importpath] {
+			return
+		}
+
+		sz := len(missing)
+		push(importpath)
+		for _, i := range p.Imports {
+			if i == importpath {
+				continue
+			}
+			fn(i)
+		}
+
+		// if the size of the missing map has not changed
+		// this entire subtree is complete, mark it as such
+		if len(missing) == sz {
+			checked[importpath] = true
+		}
+		pop(importpath)
+	}
+	for _, pkg := range pkgs {
+		fn(pkg.ImportPath)
+	}
+	return missing
+}
+
+// stripscheme removes any scheme components from url like paths.
+func stripscheme(path string) string {
+	u, err := url.Parse(path)
+	if err != nil {
+		panic(err)
+	}
+	return u.Host + u.Path
+}
diff --git a/gbvendor/_testdata/copyfile/a/rick b/gbvendor/_testdata/copyfile/a/rick
new file mode 120000
index 0000000..0db35ec
--- /dev/null
+++ b/gbvendor/_testdata/copyfile/a/rick
@@ -0,0 +1 @@
+/never/going/to/give/you/up
\ No newline at end of file
diff --git a/gbvendor/_testdata/src/github.com/foo/bar/main.go b/gbvendor/_testdata/src/github.com/foo/bar/main.go
new file mode 100644
index 0000000..399afdd
--- /dev/null
+++ b/gbvendor/_testdata/src/github.com/foo/bar/main.go
@@ -0,0 +1,17 @@
+package main
+
+import (
+	"fmt"
+
+	"github.com/quux/flobble"
+	// "bitbucket.org/fwoop/ftang" // commented out, this is deliberate
+	moo "github.com/lypo/moopo"
+)
+
+import "github.com/hoo/wuu"
+
+func main() {
+	fmt.Println(flobble.Q)
+	fmt.Prinln(moo.Q)
+	fmt.Println(wuu.Q)
+}
diff --git a/gbvendor/_testdata/vendor/src/bitbucket.org/fwoop/ftang/kthulu.go b/gbvendor/_testdata/vendor/src/bitbucket.org/fwoop/ftang/kthulu.go
new file mode 100644
index 0000000..baa83bb
--- /dev/null
+++ b/gbvendor/_testdata/vendor/src/bitbucket.org/fwoop/ftang/kthulu.go
@@ -0,0 +1,3 @@
+package ftang
+
+const CAT = "ack!"
diff --git a/gbvendor/_testdata/vendor/src/github.com/hoo/wuu/goo.go b/gbvendor/_testdata/vendor/src/github.com/hoo/wuu/goo.go
new file mode 100644
index 0000000..fd4d087
--- /dev/null
+++ b/gbvendor/_testdata/vendor/src/github.com/hoo/wuu/goo.go
@@ -0,0 +1,3 @@
+package wuu
+
+const Q = "hey"
diff --git a/gbvendor/_testdata/vendor/src/github.com/lypo/moopo/tropo.go b/gbvendor/_testdata/vendor/src/github.com/lypo/moopo/tropo.go
new file mode 100644
index 0000000..834ab54
--- /dev/null
+++ b/gbvendor/_testdata/vendor/src/github.com/lypo/moopo/tropo.go
@@ -0,0 +1,3 @@
+package moopo
+
+const Q = "hi"
diff --git a/gbvendor/_testdata/vendor/src/github.com/quux/flobble/wobble.go b/gbvendor/_testdata/vendor/src/github.com/quux/flobble/wobble.go
new file mode 100644
index 0000000..fa9e631
--- /dev/null
+++ b/gbvendor/_testdata/vendor/src/github.com/quux/flobble/wobble.go
@@ -0,0 +1,3 @@
+package flobble
+
+const Q = "hello"
diff --git a/gbvendor/copy.go b/gbvendor/copy.go
new file mode 100644
index 0000000..1d2f599
--- /dev/null
+++ b/gbvendor/copy.go
@@ -0,0 +1,69 @@
+package vendor
+
+import (
+	"fmt"
+	"io"
+	"os"
+	"path/filepath"
+	"strings"
+)
+
+const debugCopypath = true
+const debugCopyfile = false
+
+// Copypath copies the contents of src to dst, excluding any file or
+// directory that starts with a period.
+func Copypath(dst string, src string) error {
+	err := filepath.Walk(src, func(path string, info os.FileInfo, err error) error {
+		if err != nil {
+			return err
+		}
+
+		if strings.HasPrefix(filepath.Base(path), ".") {
+			if info.IsDir() {
+				return filepath.SkipDir
+			}
+			return nil
+		}
+
+		if info.IsDir() {
+			return nil
+		}
+
+		if info.Mode()&os.ModeSymlink != 0 {
+			if debugCopypath {
+				fmt.Printf("skipping symlink: %v\n", path)
+			}
+			return nil
+		}
+
+		dst := filepath.Join(dst, path[len(src):])
+		return copyfile(dst, path)
+	})
+	if err != nil {
+		// if there was an error during copying, remove the partial copy.
+		RemoveAll(dst)
+	}
+	return err
+}
+
+func copyfile(dst, src string) error {
+	err := mkdir(filepath.Dir(dst))
+	if err != nil {
+		return fmt.Errorf("copyfile: mkdirall: %v", err)
+	}
+	r, err := os.Open(src)
+	if err != nil {
+		return fmt.Errorf("copyfile: open(%q): %v", src, err)
+	}
+	defer r.Close()
+	w, err := os.Create(dst)
+	if err != nil {
+		return fmt.Errorf("copyfile: create(%q): %v", dst, err)
+	}
+	if debugCopyfile {
+		fmt.Printf("copyfile(dst: %v, src: %v)\n", dst, src)
+	}
+	_, err = io.Copy(w, r)
+	return err
+}
diff --git a/gbvendor/copy_test.go b/gbvendor/copy_test.go
new file mode 100644
index 0000000..297d198
--- /dev/null
+++ b/gbvendor/copy_test.go
@@ -0,0 +1,19 @@
+package vendor
+
+import (
+	"path/filepath"
+	"runtime"
+	"testing"
+)
+
+func TestCopypathSkipsSymlinks(t *testing.T) {
+	if runtime.GOOS == "windows" {
+		t.Skip("no symlinks on windows y'all")
+	}
+	dst := mktemp(t)
+	defer RemoveAll(dst)
+	src := filepath.Join("_testdata", "copyfile", "a")
+	if err := Copypath(dst, src); err != nil {
+		t.Fatalf("copypath(%s, %s): %v", dst, src, err)
+	}
+}
diff --git a/gbvendor/depset.go b/gbvendor/depset.go
new file mode 100644
index 0000000..b4c40d8
--- /dev/null
+++ b/gbvendor/depset.go
@@ -0,0 +1,114 @@
+package vendor
+
+import (
+	"fmt"
+	"go/build"
+	"os"
+	"path/filepath"
+	"strings"
+)
+
+// Pkg describes a Go package.
+type Pkg struct {
+	*Depset
+	*build.Package
+}
+
+// Depset describes a set of related Go packages.
+type Depset struct {
+	Root   string
+	Prefix string
+	Pkgs   map[string]*Pkg
+}
+
+// LoadPaths returns a map of paths to Depsets.
+func LoadPaths(paths ...struct{ Root, Prefix string }) (map[string]*Depset, error) {
+	m := make(map[string]*Depset)
+	for _, p := range paths {
+		set, err := LoadTree(p.Root, p.Prefix)
+		if err != nil {
+			return nil, err
+		}
+		m[set.Root] = set
+	}
+	return m, nil
+}
+
+// LoadTree parses a tree of source files into a map of *pkgs.
+func LoadTree(root string, prefix string) (*Depset, error) {
+	d := Depset{
+		Root:   root,
+		Prefix: prefix,
+		Pkgs:   make(map[string]*Pkg),
+	}
+	fn := func(dir string, fi os.FileInfo) error {
+		importpath := filepath.Join(prefix, dir[len(root)+1:])
+
+		// if we're at the root of a tree, skip it
+		if importpath == "" {
+			return nil
+		}
+
+		p, err := loadPackage(&d, dir)
+		if err != nil {
+			if _, ok := err.(*build.NoGoError); ok {
+				return nil
+			}
+			return fmt.Errorf("loadPackage(%q, %q): %v", dir, importpath, err)
+		}
+		p.ImportPath = filepath.ToSlash(importpath)
+		if p != nil {
+			d.Pkgs[p.ImportPath] = p
+		}
+		return nil
+	}
+
+	// handle root of the tree
+	fi, err := os.Stat(root)
+	if err != nil {
+		return nil, err
+	}
+	if err := fn(root+string(filepath.Separator), fi); err != nil {
+		return nil, err
+	}
+
+	// walk sub directories
+	err = eachDir(root, fn)
+	return &d, err
+}
+
+func loadPackage(d *Depset, dir string) (*Pkg, error) {
+	p := Pkg{
+		Depset: d,
+	}
+	var err error
+
+	// expolit local import logic
+	p.Package, err = build.ImportDir(dir, build.ImportComment)
+	return &p, err
+}
+
+func eachDir(dir string, fn func(string, os.FileInfo) error) error {
+	f, err := os.Open(dir)
+	if err != nil {
+		return err
+	}
+	defer f.Close()
+	files, err := f.Readdir(-1)
+	for _, fi := range files {
+		if !fi.IsDir() {
+			continue
+		}
+		if strings.HasPrefix(fi.Name(), "_") || strings.HasPrefix(fi.Name(), ".") || fi.Name() == "testdata" {
+			continue
+		}
+		path := filepath.Join(dir, fi.Name())
+		if err := fn(path, fi); err != nil {
+			return err
+		}
+		if err := eachDir(path, fn); err != nil {
+			return err
+		}
+	}
+	return nil
+}
diff --git a/gbvendor/discovery.go b/gbvendor/discovery.go
new file mode 100644
index 0000000..f504200
--- /dev/null
+++ b/gbvendor/discovery.go
@@ -0,0 +1,80 @@
+// Copyright 2012 The Go Authors.  All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package vendor
+
+import (
+	"encoding/xml"
+	"fmt"
+	"io"
+	"strings"
+)
+
+// charsetReader returns a reader for the given charset. Currently
+// it only supports UTF-8 and ASCII. Otherwise, it returns a meaningful
+// error which is printed by go get, so the user can find why the package
+// wasn't downloaded if the encoding is not supported. Note that, in
+// order to reduce potential errors, ASCII is treated as UTF-8 (i.e. characters
+// greater than 0x7f are not rejected).
+func charsetReader(charset string, input io.Reader) (io.Reader, error) {
+	switch strings.ToLower(charset) {
+	case "ascii":
+		return input, nil
+	default:
+		return nil, fmt.Errorf("can't decode XML document using charset %q", charset)
+	}
+}
+
+type metaImport struct {
+	Prefix, VCS, RepoRoot string
+}
+
+// parseMetaGoImports returns meta imports from the HTML in r.
+// Parsing ends at the end of the <head> section or the beginning of the <body>.
+func parseMetaGoImports(r io.Reader) (imports []metaImport, err error) {
+	d := xml.NewDecoder(r)
+	d.CharsetReader = charsetReader
+	d.Strict = false
+	var t xml.Token
+	for {
+		t, err = d.Token()
+		if err != nil {
+			if err == io.EOF {
+				err = nil
+			}
+			return
+		}
+		if e, ok := t.(xml.StartElement); ok && strings.EqualFold(e.Name.Local, "body") {
+			return
+		}
+		if e, ok := t.(xml.EndElement); ok && strings.EqualFold(e.Name.Local, "head") {
+			return
+		}
+		e, ok := t.(xml.StartElement)
+		if !ok || !strings.EqualFold(e.Name.Local, "meta") {
+			continue
+		}
+		if attrValue(e.Attr, "name") != "go-import" {
+			continue
+		}
+		if f := strings.Fields(attrValue(e.Attr, "content")); len(f) == 3 {
+			imports = append(imports, metaImport{
+				Prefix:   f[0],
+				VCS:      f[1],
+				RepoRoot: f[2],
+			})
+		}
+	}
+}
+
+// attrValue returns the attribute value for the case-insensitive key
+// `name', or the empty string if nothing is found.
+func attrValue(attrs []xml.Attr, name string) string {
+	for _, a := range attrs {
+		if strings.EqualFold(a.Name.Local, name) {
+			return a.Value
+		}
+	}
+	return ""
+}
diff --git a/gbvendor/imports.go b/gbvendor/imports.go
new file mode 100644
index 0000000..93223cc
--- /dev/null
+++ b/gbvendor/imports.go
@@ -0,0 +1,110 @@
+package vendor
+
+import (
+	"fmt"
+	"go/parser"
+	"go/token"
+	"io"
+	"net/http"
+	"os"
+	"path/filepath"
+	"strings"
+
+	"github.com/constabulary/gb"
+)
+
+// ParseImports parses Go packages from a specific root returning a set of import paths.
+func ParseImports(root string) (map[string]bool, error) {
+	pkgs := make(map[string]bool)
+
+	var walkFn = func(path string, info os.FileInfo, err error) error {
+		if info.IsDir() {
+			name := info.Name()
+			if strings.HasPrefix(name, ".") || strings.HasPrefix(name, "_") || name == "testdata" {
+				return filepath.SkipDir
+			}
+			return nil
+		}
+		if filepath.Ext(path) != ".go" { // Parse only go source files
+			return nil
+		}
+
+		fs := token.NewFileSet()
+		f, err := parser.ParseFile(fs, path, nil, parser.ImportsOnly)
+		if err != nil {
+			return err
+		}
+
+		for _, s := range f.Imports {
+			p := strings.Replace(s.Path.Value, "\"", "", -1)
+			if !contains(stdlib, p) {
+				pkgs[p] = true
+			}
+		}
+		return nil
+	}
+
+	err := filepath.Walk(root, walkFn)
+	return pkgs, err
+}
+
+// FetchMetadata fetchs the remote metadata for path.
+func FetchMetadata(path string, insecure bool) (io.ReadCloser, error) {
+	schemes := []string{"https", "http"}
+	for _, s := range schemes {
+		if r, err := fetchMetadata(s, path, insecure); err == nil {
+			return r, nil
+		}
+	}
+	return nil, fmt.Errorf("unable to determine remote metadata protocol")
+}
+
+func fetchMetadata(scheme, path string, insecure bool) (io.ReadCloser, error) {
+	url := fmt.Sprintf("%s://%s?go-get=1", scheme, path)
+	switch scheme {
+	case "https":
+		resp, err := http.Get(url)
+		if err == nil {
+			return resp.Body, nil
+		}
+	case "http":
+		if !insecure {
+			gb.Infof("skipping insecure protocol: %v", url)
+		} else {
+			resp, err := http.Get(url)
+			if err == nil {
+				return resp.Body, nil
+			}
+		}
+	}
+
+	return nil, fmt.Errorf("unknown remote protocol scheme: %q", scheme)
+}
+
+// ParseMetadata fetchs and decodes remote metadata for path.
+func ParseMetadata(path string, insecure bool) (string, string, string, error) {
+	rc, err := FetchMetadata(path, insecure)
+	if err != nil {
+		return "", "", "", err
+	}
+	defer rc.Close()
+
+	imports, err := parseMetaGoImports(rc)
+	if err != nil {
+		return "", "", "", err
+	}
+	match := -1
+	for i, im := range imports {
+		if !strings.HasPrefix(path, im.Prefix) {
+			continue
+		}
+		if match != -1 {
+			return "", "", "", fmt.Errorf("multiple meta tags match import path %q", path)
+		}
+		match = i
+	}
+	if match == -1 {
+		return "", "", "", fmt.Errorf("go-import metadata not found")
+	}
+	return imports[match].Prefix, imports[match].VCS, imports[match].RepoRoot, nil
+}
diff --git a/gbvendor/imports_test.go b/gbvendor/imports_test.go
new file mode 100644
index 0000000..2794292
--- /dev/null
+++ b/gbvendor/imports_test.go
@@ -0,0 +1,134 @@
+package vendor
+
+import (
+	"bytes"
+	"fmt"
+	"io"
+	"os"
+	"path/filepath"
+	"reflect"
+	"testing"
+)
+
+func TestParseImports(t *testing.T) {
+	root := filepath.Join(getwd(t), "_testdata", "src")
+
+	got, err := ParseImports(root)
+	if err != nil {
+		t.Fatalf("ParseImports(%q): %v", root, err)
+	}
+
+	want := set("github.com/quux/flobble", "github.com/lypo/moopo", "github.com/hoo/wuu")
+	if !reflect.DeepEqual(got, want) {
+		t.Fatalf("ParseImports(%q): want: %v, got %v", root, want, got)
+	}
+}
+
+func TestFetchMetadata(t *testing.T) {
+	if testing.Short() {
+		t.Skipf("skipping network tests in -short mode")
+	}
+	tests := []struct {
+		path     string
+		want     string
+		insecure bool
+	}{{
+		path: "golang.org/x/tools/cmd/godoc",
+		want: `<!DOCTYPE html>
+<html>
+<head>
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
+<meta name="go-import" content="golang.org/x/tools git https://go.googlesource.com/tools">
+<meta name="go-source" content="golang.org/x/tools https://github.com/golang/tools/ https://github.com/golang/tools/tree/master{/dir} https://github.com/golang/tools/blob/master{/dir}/{file}#L{line}">
+<meta http-equiv="refresh" content="0; url=https://godoc.org/golang.org/x/tools/cmd/godoc">
+</head>
+<body>
+Nothing to see here; <a href="https://godoc.org/golang.org/x/tools/cmd/godoc">move along</a>.
+</body>
+</html>
+`,
+	}, {
+		path: "gopkg.in/check.v1",
+		want: `
+<html>
+<head>
+<meta name="go-import" content="gopkg.in/check.v1 git https://gopkg.in/check.v1">
+<meta name="go-source" content="gopkg.in/check.v1 _ https://github.com/go-check/check/tree/v1{/dir} https://github.com/go-check/check/blob/v1{/dir}/{file}#L{line}">
+</head>
+<body>
+go get gopkg.in/check.v1
+</body>
+</html>
+`,
+	}}
+
+	for _, tt := range tests {
+		r, err := FetchMetadata(tt.path, tt.insecure)
+		if err != nil {
+			t.Error(err)
+			continue
+		}
+		var buf bytes.Buffer
+		if _, err := io.Copy(&buf, r); err != nil {
+			t.Error(err)
+			r.Close()
+			continue
+		}
+		r.Close()
+		got := buf.String()
+		if got != tt.want {
+			t.Errorf("FetchMetadata(%q): want %q, got %q", tt.path, tt.want, got)
+		}
+	}
+}
+
+func TestParseMetadata(t *testing.T) {
+	if testing.Short() {
+		t.Skipf("skipping network tests in -short mode")
+	}
+	tests := []struct {
+		path       string
+		importpath string
+		vcs        string
+		reporoot   string
+		insecure   bool
+		err        error
+	}{{
+		path:       "golang.org/x/tools/cmd/godoc",
+		importpath: "golang.org/x/tools",
+		vcs:        "git",
+		reporoot:   "https://go.googlesource.com/tools",
+	}, {
+		path:       "gopkg.in/check.v1",
+		importpath: "gopkg.in/check.v1",
+		vcs:        "git",
+		reporoot:   "https://gopkg.in/check.v1",
+	}, {
+		path:       "gopkg.in/mgo.v2/bson",
+		importpath: "gopkg.in/mgo.v2",
+		vcs:        "git",
+		reporoot:   "https://gopkg.in/mgo.v2",
+	}, {
+		path: "speter.net/go/exp",
+		err:  fmt.Errorf("go-import metadata not found"),
+	}}
+
+	for _, tt := range tests {
+		importpath, vcs, reporoot, err := ParseMetadata(tt.path, tt.insecure)
+		if !reflect.DeepEqual(err, tt.err) {
+			t.Error(err)
+			continue
+		}
+		if importpath != tt.importpath || vcs != tt.vcs || reporoot != tt.reporoot {
+			t.Errorf("ParseMetadata(%q): want %s %s %s, got %s %s %s ", tt.path, tt.importpath, tt.vcs, tt.reporoot, importpath, vcs, reporoot)
+		}
+	}
+}
+
+func getwd(t *testing.T) string {
+	cwd, err := os.Getwd()
+	if err != nil {
+		t.Fatal(err)
+	}
+	return cwd
+}
diff --git a/gbvendor/manifest.go b/gbvendor/manifest.go
new file mode 100644
index 0000000..f4d973e
--- /dev/null
+++ b/gbvendor/manifest.go
@@ -0,0 +1,149 @@
+package vendor
+
+import (
+	"bytes"
+	"encoding/json"
+	"fmt"
+	"io"
+	"os"
+	"reflect"
+	"sort"
+)
+
+// gb-vendor manifest support
+
+// Manifest describes the layout of $PROJECT/vendor/manifest.
+type Manifest struct {
+	// Manifest version. Current manifest version is 0.
+	Version int `json:"version"`
+
+	// Depenencies is a list of vendored dependencies.
+	Dependencies []Dependency `json:"dependencies"`
+}
+
+// AddDependency adds a Dependency to the current Manifest.
+// If the dependency exists already then it returns and error.
+func (m *Manifest) AddDependency(dep Dependency) error {
+	if m.HasImportpath(dep.Importpath) {
+		return fmt.Errorf("already registered")
+	}
+	m.Dependencies = append(m.Dependencies, dep)
+	return nil
+}
+
+// RemoveDependency removes a Dependency from the current Manifest.
+// If the dependency does not exist then it returns an error.
+func (m *Manifest) RemoveDependency(dep Dependency) error {
+	for i, d := range m.Dependencies {
+		if reflect.DeepEqual(d, dep) {
+			m.Dependencies = append(m.Dependencies[:i], m.Dependencies[i+1:]...)
+			return nil
+		}
+	}
+	return fmt.Errorf("dependency does not exist")
+}
+
+// HasImportpath reports whether the Manifest contains the import path.
+func (m *Manifest) HasImportpath(path string) bool {
+	_, err := m.GetDependencyForImportpath(path)
+	return err == nil
+}
+
+// GetDependencyForRepository return a dependency for specified URL
+// If the dependency does not exist it returns an error
+func (m *Manifest) GetDependencyForImportpath(path string) (Dependency, error) {
+	for _, d := range m.Dependencies {
+		if d.Importpath == path {
+			return d, nil
+		}
+	}
+	return Dependency{}, fmt.Errorf("dependency for %s does not exist", path)
+}
+
+// Dependency describes one vendored import path of code
+// A Dependency is an Importpath sources from a Respository
+// at Revision from Path.
+type Dependency struct {
+	// Importpath is name by which this dependency is known.
+	Importpath string `json:"importpath"`
+
+	// Repository is the remote DVCS location that this
+	// dependency was fetched from.
+	Repository string `json:"repository"`
+
+	// Revision is the revision that describes the dependency's
+	// remote revision.
+	Revision string `json:"revision"`
+
+	// Branch is the branch the Revision was located on.
+	// Can be blank if not needed.
+	Branch string `json:"branch"`
+
+	// Path is the path inside the Repository where the
+	// dependency was fetched from.
+	Path string `json:"path,omitempty"`
+}
+
+// WriteManifest writes a Manifest to the path. If the manifest does
+// not exist, it is created. If it does exist, it will be overwritten.
+// If the manifest file is empty (0 dependencies) it will be deleted.
+// The dependencies will be ordered by import path to reduce churn when making
+// changes.
+// TODO(dfc) write to temporary file and move atomically to avoid
+// destroying a working vendorfile.
+func WriteManifest(path string, m *Manifest) error {
+	if len(m.Dependencies) == 0 {
+		err := os.Remove(path)
+		if !os.IsNotExist(err) {
+			return err
+		}
+		return nil
+	}
+
+	f, err := os.Create(path)
+	if err != nil {
+		return err
+	}
+	if err := writeManifest(f, m); err != nil {
+		f.Close()
+		return err
+	}
+	return f.Close()
+}
+
+func writeManifest(w io.Writer, m *Manifest) error {
+	sort.Sort(byImportpath(m.Dependencies))
+	buf, err := json.MarshalIndent(m, "", "\t")
+	if err != nil {
+		return err
+	}
+	_, err = io.Copy(w, bytes.NewReader(buf))
+	return err
+}
+
+// ReadManifest reads a Manifest from path. If the Manifest is not
+// found, a blank Manifest will be returned.
+func ReadManifest(path string) (*Manifest, error) {
+	f, err := os.Open(path)
+	if err != nil {
+		if os.IsNotExist(err) {
+			return new(Manifest), nil
+		}
+		return nil, err
+	}
+	defer f.Close()
+	return readManifest(f)
+}
+
+func readManifest(r io.Reader) (*Manifest, error) {
+	var m Manifest
+	d := json.NewDecoder(r)
+	err := d.Decode(&m)
+	return &m, err
+}
+
+type byImportpath []Dependency
+
+func (s byImportpath) Len() int           { return len(s) }
+func (s byImportpath) Less(i, j int) bool { return s[i].Importpath < s[j].Importpath }
+func (s byImportpath) Swap(i, j int)      { s[i], s[j] = s[j], s[i] }
diff --git a/gbvendor/manifest_test.go b/gbvendor/manifest_test.go
new file mode 100644
index 0000000..479f588
--- /dev/null
+++ b/gbvendor/manifest_test.go
@@ -0,0 +1,104 @@
+package vendor
+
+import (
+	"bytes"
+	"os"
+	"path/filepath"
+	"testing"
+)
+
+func mktemp(t *testing.T) string {
+	s, err := mktmp()
+	if err != nil {
+		t.Fatal(err)
+	}
+	return s
+}
+
+func assertNotExists(t *testing.T, path string) {
+	_, err := os.Stat(path)
+	if err == nil || !os.IsNotExist(err) {
+		t.Fatalf("expected %q to be not found, got %v", path, err)
+	}
+}
+
+func assertExists(t *testing.T, path string) {
+	_, err := os.Stat(path)
+	if err != nil {
+		t.Fatalf("expected %q to be found, got %v", path, err)
+	}
+}
+
+func TestManifest(t *testing.T) {
+	root := mktemp(t)
+	defer RemoveAll(root)
+
+	mf := filepath.Join(root, "vendor")
+
+	// check that reading an non existant manifest
+	// does not return an error
+	m, err := ReadManifest(mf)
+	if err != nil {
+		t.Fatalf("reading a non existant manifest should not fail: %v", err)
+	}
+
+	// check that no manifest file was created
+	assertNotExists(t, mf)
+
+	// add a dep
+	m.Dependencies = append(m.Dependencies, Dependency{
+		Importpath: "github.com/foo/bar/baz",
+		Repository: "https://github.com/foo/bar",
+		Revision:   "cafebad",
+		Branch:     "master",
+		Path:       "/baz",
+	})
+
+	// write it back
+	if err := WriteManifest(mf, m); err != nil {
+		t.Fatalf("WriteManifest failed: %v", err)
+	}
+
+	// check the manifest was written
+	assertExists(t, mf)
+
+	// remove it
+	m.Dependencies = nil
+	if err := WriteManifest(mf, m); err != nil {
+		t.Fatalf("WriteManifest failed: %v", err)
+	}
+
+	// check that no manifest file was removed
+	assertNotExists(t, mf)
+}
+
+func TestEmptyPathIsNotWritten(t *testing.T) {
+	m := Manifest{
+		Version: 0,
+		Dependencies: []Dependency{{
+			Importpath: "github.com/foo/bar",
+			Repository: "https://github.com/foo/bar",
+			Revision:   "abcdef",
+			Branch:     "master",
+		}},
+	}
+	var buf bytes.Buffer
+	if err := writeManifest(&buf, &m); err != nil {
+		t.Fatal(err)
+	}
+	want := `{
+	"version": 0,
+	"dependencies": [
+		{
+			"importpath": "github.com/foo/bar",
+			"repository": "https://github.com/foo/bar",
+			"revision": "abcdef",
+			"branch": "master"
+		}
+	]
+}`
+	got := buf.String()
+	if want != got {
+		t.Fatalf("want: %s, got %s", want, got)
+	}
+}
diff --git a/gbvendor/remove.go b/gbvendor/remove.go
new file mode 100644
index 0000000..fdc155b
--- /dev/null
+++ b/gbvendor/remove.go
@@ -0,0 +1,23 @@
+package vendor
+
+import (
+	"os"
+	"path/filepath"
+	"runtime"
+)
+
+// RemoveAll removes path and any children it contains. Unlike os.RemoveAll it
+// deletes read only files on Windows.
+func RemoveAll(path string) error {
+	if runtime.GOOS == "windows" {
+		// make sure all files are writable so we can delete them
+		filepath.Walk(path, func(path string, info os.FileInfo, err error) error {
+			mode := info.Mode()
+			if mode|0200 == mode {
+				return nil
+			}
+			return os.Chmod(path, mode|0200)
+		})
+	}
+	return os.RemoveAll(path)
+}
diff --git a/gbvendor/repo.go b/gbvendor/repo.go
new file mode 100644
index 0000000..521054b
--- /dev/null
+++ b/gbvendor/repo.go
@@ -0,0 +1,542 @@
+package vendor
+
+import (
+	"bytes"
+	"fmt"
+	"io"
+	"io/ioutil"
+	"net/url"
+	"os"
+	"os/exec"
+	"path/filepath"
+	"regexp"
+	"strings"
+
+	"github.com/constabulary/gb"
+)
+
+// RemoteRepo describes a remote dvcs repository.
+type RemoteRepo interface {
+
+	// Checkout checks out a specific branch, tag, or revision.
+	// The interpretation of these three values is impementation
+	// specific.
+	Checkout(branch, tag, revision string) (WorkingCopy, error)
+
+	// URL returns the URL the clone was taken from. It should
+	// only be called after Clone.
+	URL() string
+}
+
+// WorkingCopy represents a local copy of a remote dvcs repository.
+type WorkingCopy interface {
+
+	// Dir is the root of this working copy.
+	Dir() string
+
+	// Revision returns the revision of this working copy.
+	Revision() (string, error)
+
+	// Branch returns the branch to which this working copy belongs.
+	Branch() (string, error)
+
+	// Destroy removes the working copy and cleans path to the working copy.
+	Destroy() error
+}
+
+var (
+	ghregex   = regexp.MustCompile(`^(?P<root>github\.com/([A-Za-z0-9_.\-]+/[A-Za-z0-9_.\-]+))(/[A-Za-z0-9_.\-]+)*$`)
+	bbregex   = regexp.MustCompile(`^(?P<root>bitbucket\.org/(?P<bitname>[A-Za-z0-9_.\-]+/[A-Za-z0-9_.\-]+))(/[A-Za-z0-9_.\-]+)*$`)
+	lpregex   = regexp.MustCompile(`^launchpad.net/([A-Za-z0-9-._]+)(/[A-Za-z0-9-._]+)?(/.+)?`)
+	gcregex   = regexp.MustCompile(`^(?P<root>code\.google\.com/[pr]/(?P<project>[a-z0-9\-]+)(\.(?P<subrepo>[a-z0-9\-]+))?)(/[A-Za-z0-9_.\-]+)*$`)
+	genericre = regexp.MustCompile(`^(?P<root>(?P<repo>([a-z0-9.\-]+\.)+[a-z0-9.\-]+(:[0-9]+)?/[A-Za-z0-9_.\-/]*?)\.(?P<vcs>bzr|git|hg|svn))([/A-Za-z0-9_.\-]+)*$`)
+)
+
+// DeduceRemoteRepo takes a potential import path and returns a RemoteRepo
+// representing the remote location of the source of an import path.
+// Remote repositories can be bare import paths, or urls including a checkout scheme.
+// If deduction would cause traversal of an insecure host, a message will be
+// printed and the travelsal path will be ignored.
+func DeduceRemoteRepo(path string, insecure bool) (RemoteRepo, string, error) {
+	u, err := url.Parse(path)
+	if err != nil {
+		return nil, "", fmt.Errorf("%q is not a valid import path", path)
+	}
+
+	var schemes []string
+	if u.Scheme != "" {
+		schemes = append(schemes, u.Scheme)
+	}
+
+	path = u.Host + u.Path
+	if !regexp.MustCompile(`^([A-Za-z0-9-]+)(.[A-Za-z0-9-]+)+(/[A-Za-z0-9-_.]+)+$`).MatchString(path) {
+		return nil, "", fmt.Errorf("%q is not a valid import path", path)
+	}
+
+	switch {
+	case ghregex.MatchString(path):
+		v := ghregex.FindStringSubmatch(path)
+		url := &url.URL{
+			Host: "github.com",
+			Path: v[2],
+		}
+		repo, err := Gitrepo(url, insecure, schemes...)
+		return repo, v[0][len(v[1]):], err
+	case bbregex.MatchString(path):
+		v := bbregex.FindStringSubmatch(path)
+		url := &url.URL{
+			Host: "bitbucket.org",
+			Path: v[2],
+		}
+		repo, err := Gitrepo(url, insecure, schemes...)
+		if err == nil {
+			return repo, v[0][len(v[1]):], nil
+		}
+		repo, err = Hgrepo(url, insecure)
+		if err == nil {
+			return repo, v[0][len(v[1]):], nil
+		}
+		return nil, "", fmt.Errorf("unknown repository type")
+	case gcregex.MatchString(path):
+		v := gcregex.FindStringSubmatch(path)
+		url := &url.URL{
+			Host: "code.google.com",
+			Path: "p/" + v[2],
+		}
+		repo, err := Hgrepo(url, insecure, schemes...)
+		if err == nil {
+			return repo, v[0][len(v[1]):], nil
+		}
+		repo, err = Gitrepo(url, insecure, schemes...)
+		if err == nil {
+			return repo, v[0][len(v[1]):], nil
+		}
+		return nil, "", fmt.Errorf("unknown repository type")
+	case lpregex.MatchString(path):
+		v := lpregex.FindStringSubmatch(path)
+		v = append(v, "", "")
+		if v[2] == "" {
+			// launchpad.net/project"
+			repo, err := Bzrrepo(fmt.Sprintf("https://launchpad.net/%v", v[1]))
+			return repo, "", err
+		}
+		// launchpad.net/project/series"
+		repo, err := Bzrrepo(fmt.Sprintf("https://launchpad.net/%s/%s", v[1], v[2]))
+		return repo, v[3], err
+	}
+
+	// try the general syntax
+	if genericre.MatchString(path) {
+		v := genericre.FindStringSubmatch(path)
+		switch v[5] {
+		case "git":
+			x := strings.SplitN(v[1], "/", 2)
+			url := &url.URL{
+				Host: x[0],
+				Path: x[1],
+			}
+			repo, err := Gitrepo(url, insecure, schemes...)
+			return repo, v[6], err
+		case "hg":
+			x := strings.SplitN(v[1], "/", 2)
+			url := &url.URL{
+				Host: x[0],
+				Path: x[1],
+			}
+			repo, err := Hgrepo(url, insecure, schemes...)
+			return repo, v[6], err
+		case "bzr":
+			repo, err := Bzrrepo("https://" + v[1])
+			return repo, v[6], err
+		default:
+			return nil, "", fmt.Errorf("unknown repository type: %q", v[5])
+
+		}
+	}
+
+	// no idea, try to resolve as a vanity import
+	importpath, vcs, reporoot, err := ParseMetadata(path, insecure)
+	if err != nil {
+		return nil, "", err
+	}
+	u, err = url.Parse(reporoot)
+	if err != nil {
+		return nil, "", err
+	}
+	extra := path[len(importpath):]
+	switch vcs {
+	case "git":
+		u.Path = u.Path[1:]
+		repo, err := Gitrepo(u, insecure, u.Scheme)
+		return repo, extra, err
+	case "hg":
+		u.Path = u.Path[1:]
+		repo, err := Hgrepo(u, insecure, u.Scheme)
+		return repo, extra, err
+	case "bzr":
+		repo, err := Bzrrepo(reporoot)
+		return repo, extra, err
+	default:
+		return nil, "", fmt.Errorf("unknown repository type: %q", vcs)
+	}
+}
+
+// Gitrepo returns a RemoteRepo representing a remote git repository.
+func Gitrepo(url *url.URL, insecure bool, schemes ...string) (RemoteRepo, error) {
+	if len(schemes) == 0 {
+		schemes = []string{"https", "git", "ssh", "http"}
+	}
+	u, err := probeGitUrl(url, insecure, schemes)
+	if err != nil {
+		return nil, err
+	}
+	return &gitrepo{
+		url: u,
+	}, nil
+}
+
+func probeGitUrl(u *url.URL, insecure bool, schemes []string) (string, error) {
+	git := func(url *url.URL) error {
+		out, err := run("git", "ls-remote", url.String(), "HEAD")
+		if err != nil {
+			return err
+		}
+
+		if !bytes.Contains(out, []byte("HEAD")) {
+			return fmt.Errorf("not a git repo")
+		}
+		return nil
+	}
+	return probe(git, u, insecure, schemes...)
+}
+
+func probeHgUrl(u *url.URL, insecure bool, schemes []string) (string, error) {
+	hg := func(url *url.URL) error {
+		_, err := run("hg", "identify", url.String())
+		return err
+	}
+	return probe(hg, u, insecure, schemes...)
+}
+
+func probeBzrUrl(u string) error {
+	bzr := func(url *url.URL) error {
+		_, err := run("bzr", "info", url.String())
+		return err
+	}
+	url, err := url.Parse(u)
+	if err != nil {
+		return err
+	}
+	_, err = probe(bzr, url, false, "https")
+	return err
+}
+
+// probe calls the supplied vcs function to probe a variety of url constructions.
+// If vcs returns non nil, it is assumed that the url is not a valid repo.
+func probe(vcs func(*url.URL) error, url *url.URL, insecure bool, schemes ...string) (string, error) {
+	var unsuccessful []string
+	for _, scheme := range schemes {
+
+		// make copy of url and apply scheme
+		url := *url
+		url.Scheme = scheme
+
+		switch url.Scheme {
+		case "https", "ssh":
+			if err := vcs(&url); err == nil {
+				return url.String(), nil
+			}
+		case "http", "git":
+			if !insecure {
+				gb.Infof("skipping insecure protocol: %s", url.String())
+				continue
+			}
+			if err := vcs(&url); err == nil {
+				return url.String(), nil
+			}
+		default:
+			return "", fmt.Errorf("unsupported scheme: %v", url.Scheme)
+		}
+		unsuccessful = append(unsuccessful, url.String())
+	}
+	return "", fmt.Errorf("vcs probe failed, tried: %s", strings.Join(unsuccessful, ","))
+}
+
+// gitrepo is a git RemoteRepo.
+type gitrepo struct {
+
+	// remote repository url, see man 1 git-clone
+	url string
+}
+
+func (g *gitrepo) URL() string {
+	return g.url
+}
+
+// Checkout fetchs the remote branch, tag, or revision. If more than one is
+// supplied, an error is returned. If the branch is blank,
+// then the default remote branch will be used. If the branch is "HEAD", an
+// error will be returned.
+func (g *gitrepo) Checkout(branch, tag, revision string) (WorkingCopy, error) {
+	if branch == "HEAD" {
+		return nil, fmt.Errorf("cannot update %q as it has been previously fetched with -tag or -revision. Please use gb vendor delete then fetch again.", g.url)
+	}
+	if !atMostOne(branch, tag, revision) {
+		return nil, fmt.Errorf("only one of branch, tag or revision may be supplied")
+	}
+	dir, err := mktmp()
+	if err != nil {
+		return nil, err
+	}
+	wc := workingcopy{
+		path: dir,
+	}
+
+	args := []string{
+		"clone",
+		"-q", // silence progress report to stderr
+		g.url,
+		dir,
+	}
+	if branch != "" {
+		args = append(args, "--branch", branch)
+	}
+
+	if _, err := run("git", args...); err != nil {
+		wc.Destroy()
+		return nil, err
+	}
+
+	if revision != "" || tag != "" {
+		if err := runOutPath(os.Stderr, dir, "git", "checkout", "-q", oneOf(revision, tag)); err != nil {
+			wc.Destroy()
+			return nil, err
+		}
+	}
+
+	return &GitClone{wc}, nil
+}
+
+type workingcopy struct {
+	path string
+}
+
+func (w workingcopy) Dir() string { return w.path }
+
+func (w workingcopy) Destroy() error {
+	if err := RemoveAll(w.path); err != nil {
+		return err
+	}
+	parent := filepath.Dir(w.path)
+	return cleanPath(parent)
+}
+
+// GitClone is a git WorkingCopy.
+type GitClone struct {
+	workingcopy
+}
+
+func (g *GitClone) Revision() (string, error) {
+	rev, err := runPath(g.path, "git", "rev-parse", "HEAD")
+	return strings.TrimSpace(string(rev)), err
+}
+
+func (g *GitClone) Branch() (string, error) {
+	rev, err := runPath(g.path, "git", "rev-parse", "--abbrev-ref", "HEAD")
+	return strings.TrimSpace(string(rev)), err
+}
+
+// Hgrepo returns a RemoteRepo representing a remote git repository.
+func Hgrepo(u *url.URL, insecure bool, schemes ...string) (RemoteRepo, error) {
+	if len(schemes) == 0 {
+		schemes = []string{"https", "http"}
+	}
+	url, err := probeHgUrl(u, insecure, schemes)
+	if err != nil {
+		return nil, err
+	}
+	return &hgrepo{
+		url: url,
+	}, nil
+}
+
+// hgrepo is a Mercurial repo.
+type hgrepo struct {
+
+	// remote repository url, see man 1 hg
+	url string
+}
+
+func (h *hgrepo) URL() string { return h.url }
+
+func (h *hgrepo) Checkout(branch, tag, revision string) (WorkingCopy, error) {
+	if !atMostOne(tag, revision) {
+		return nil, fmt.Errorf("only one of tag or revision may be supplied")
+	}
+	dir, err := mktmp()
+	if err != nil {
+		return nil, err
+	}
+	args := []string{
+		"clone",
+		h.url,
+		dir,
+	}
+
+	if branch != "" {
+		args = append(args, "--branch", branch)
+	}
+	if err := runOut(os.Stderr, "hg", args...); err != nil {
+		RemoveAll(dir)
+		return nil, err
+	}
+	if revision != "" {
+		if err := runOut(os.Stderr, "hg", "--cwd", dir, "update", "-r", revision); err != nil {
+			RemoveAll(dir)
+			return nil, err
+		}
+	}
+
+	return &HgClone{
+		workingcopy{
+			path: dir,
+		},
+	}, nil
+}
+
+// HgClone is a mercurial WorkingCopy.
+type HgClone struct {
+	workingcopy
+}
+
+func (h *HgClone) Revision() (string, error) {
+	rev, err := run("hg", "--cwd", h.path, "id", "-i")
+	return strings.TrimSpace(string(rev)), err
+}
+
+func (h *HgClone) Branch() (string, error) {
+	rev, err := run("hg", "--cwd", h.path, "branch")
+	return strings.TrimSpace(string(rev)), err
+}
+
+// Bzrrepo returns a RemoteRepo representing a remote bzr repository.
+func Bzrrepo(url string) (RemoteRepo, error) {
+	if err := probeBzrUrl(url); err != nil {
+		return nil, err
+	}
+	return &bzrrepo{
+		url: url,
+	}, nil
+}
+
+// bzrrepo is a bzr RemoteRepo.
+type bzrrepo struct {
+
+	// remote repository url
+	url string
+}
+
+func (b *bzrrepo) URL() string {
+	return b.url
+}
+
+func (b *bzrrepo) Checkout(branch, tag, revision string) (WorkingCopy, error) {
+	if !atMostOne(tag, revision) {
+		return nil, fmt.Errorf("only one of tag or revision may be supplied")
+	}
+	dir, err := mktmp()
+	if err != nil {
+		return nil, err
+	}
+	wc := filepath.Join(dir, "wc")
+	if err := runOut(os.Stderr, "bzr", "branch", b.url, wc); err != nil {
+		RemoveAll(dir)
+		return nil, err
+	}
+
+	return &BzrClone{
+		workingcopy{
+			path: wc,
+		},
+	}, nil
+}
+
+// BzrClone is a bazaar WorkingCopy.
+type BzrClone struct {
+	workingcopy
+}
+
+func (b *BzrClone) Revision() (string, error) {
+	return "1", nil
+}
+
+func (b *BzrClone) Branch() (string, error) {
+	return "master", nil
+}
+
+func cleanPath(path string) error {
+	if files, _ := ioutil.ReadDir(path); len(files) > 0 || filepath.Base(path) == "src" {
+		return nil
+	}
+	parent := filepath.Dir(path)
+	if err := RemoveAll(path); err != nil {
+		return err
+	}
+	return cleanPath(parent)
+}
+
+func mkdir(path string) error {
+	return os.MkdirAll(path, 0755)
+}
+
+func mktmp() (string, error) {
+	return ioutil.TempDir("", "gb-vendor-")
+}
+
+func run(c string, args ...string) ([]byte, error) {
+	var buf bytes.Buffer
+	err := runOut(&buf, c, args...)
+	return buf.Bytes(), err
+}
+
+func runOut(w io.Writer, c string, args ...string) error {
+	cmd := exec.Command(c, args...)
+	cmd.Stdout = w
+	cmd.Stderr = os.Stderr
+	return cmd.Run()
+}
+
+func runPath(path string, c string, args ...string) ([]byte, error) {
+	var buf bytes.Buffer
+	err := runOutPath(&buf, path, c, args...)
+	return buf.Bytes(), err
+}
+
+func runOutPath(w io.Writer, path string, c string, args ...string) error {
+	cmd := exec.Command(c, args...)
+	cmd.Dir = path
+	cmd.Stdout = w
+	cmd.Stderr = os.Stderr
+	return cmd.Run()
+}
+
+// atMostOne returns true if no more than one string supplied is not empty.
+func atMostOne(args ...string) bool {
+	var c int
+	for _, arg := range args {
+		if arg != "" {
+			c++
+		}
+	}
+	return c < 2
+}
+
+// oneof returns the first non empty string
+func oneOf(args ...string) string {
+	for _, arg := range args {
+		if arg != "" {
+			return arg
+		}
+	}
+	return ""
+}
diff --git a/gbvendor/repo_test.go b/gbvendor/repo_test.go
new file mode 100644
index 0000000..03c5107
--- /dev/null
+++ b/gbvendor/repo_test.go
@@ -0,0 +1,139 @@
+package vendor
+
+import (
+	"fmt"
+	"reflect"
+	"testing"
+)
+
+func TestDeduceRemoteRepo(t *testing.T) {
+	if testing.Short() {
+		t.Skipf("skipping network tests in -short mode")
+	}
+	tests := []struct {
+		path     string
+		want     RemoteRepo
+		extra    string
+		err      error
+		insecure bool
+	}{{
+		path: "",
+		err:  fmt.Errorf(`"" is not a valid import path`),
+	}, {
+		path: "corporate",
+		err:  fmt.Errorf(`"corporate" is not a valid import path`),
+	}, {
+		path: "github.com/cznic/b",
+		want: &gitrepo{
+			url: "https://github.com/cznic/b",
+		},
+	}, {
+		path: "github.com/pkg/sftp",
+		want: &gitrepo{
+			url: "https://github.com/pkg/sftp",
+		},
+	}, {
+		path: "github.com/pkg/sftp/examples/gsftp",
+		want: &gitrepo{
+			url: "https://github.com/pkg/sftp",
+		},
+		extra: "/examples/gsftp",
+	}, {
+		path: "github.com/coreos/go-etcd",
+		want: &gitrepo{
+			url: "https://github.com/coreos/go-etcd",
+		},
+	}, {
+		path: "bitbucket.org/davecheney/gitrepo/cmd/main",
+		want: &gitrepo{
+			url: "https://bitbucket.org/davecheney/gitrepo",
+		},
+		extra: "/cmd/main",
+	}, {
+		path: "bitbucket.org/davecheney/hgrepo/cmd/main",
+		want: &hgrepo{
+			url: "https://bitbucket.org/davecheney/hgrepo",
+		},
+		extra: "/cmd/main",
+	}, {
+		path: "code.google.com/p/goauth2/oauth",
+		want: &hgrepo{
+			url: "https://code.google.com/p/goauth2",
+		},
+		extra: "/oauth",
+	}, {
+		path: "code.google.com/p/gami",
+		want: &gitrepo{
+			url: "https://code.google.com/p/gami",
+		},
+	}, {
+		path: "git.eclipse.org/gitroot/paho/org.eclipse.paho.mqtt.golang.git",
+		want: &gitrepo{
+			url: "https://git.eclipse.org/gitroot/paho/org.eclipse.paho.mqtt.golang.git",
+		},
+	}, {
+		path: "git.apache.org/thrift.git/lib/go/thrift",
+		want: &gitrepo{
+			url: "https://git.apache.org/thrift.git",
+		},
+		extra: "/lib/go/thrift",
+	}, {
+		path: "gopkg.in/check.v1",
+		want: &gitrepo{
+			url: "https://gopkg.in/check.v1",
+		},
+		extra: "",
+	}, {
+		path: "golang.org/x/tools/go/vcs",
+		want: &gitrepo{
+			url: "https://go.googlesource.com/tools",
+		},
+		extra: "/go/vcs",
+	}, {
+		path: "labix.org/v2/mgo",
+		want: &bzrrepo{
+			url: "https://launchpad.net/mgo/v2",
+		},
+		insecure: true,
+	}, {
+		path: "launchpad.net/gnuflag",
+		want: &bzrrepo{
+			url: "https://launchpad.net/gnuflag",
+		},
+	}, {
+		path: "https://github.com/pkg/sftp",
+		want: &gitrepo{
+			url: "https://github.com/pkg/sftp",
+		},
+	}, {
+		path: "git://github.com/pkg/sftp",
+		want: &gitrepo{
+			url: "git://github.com/pkg/sftp",
+		},
+		insecure: true,
+	}, {
+		path: "code.google.com/p/google-api-go-client/bigquery/v2",
+		want: &hgrepo{
+			url: "https://code.google.com/p/google-api-go-client",
+		},
+		extra: "/bigquery/v2",
+	}, {
+		path: "code.google.com/p/go-sqlite/go1/sqlite3",
+		want: &hgrepo{
+			url: "https://code.google.com/p/go-sqlite",
+		},
+		extra: "/go1/sqlite3",
+	}}
+
+	for _, tt := range tests {
+		t.Logf("DeduceRemoteRepo(%q, %v)", tt.path, tt.insecure)
+		got, extra, err := DeduceRemoteRepo(tt.path, tt.insecure)
+		if !reflect.DeepEqual(err, tt.err) {
+			t.Errorf("DeduceRemoteRepo(%q): want err: %v, got err: %v", tt.path, tt.err, err)
+			continue
+		}
+		if !reflect.DeepEqual(got, tt.want) || extra != tt.extra {
+			t.Errorf("DeduceRemoteRepo(%q): want %#v, %v, got %#v, %v", tt.path, tt.want, tt.extra, got, extra)
+		}
+	}
+}
diff --git a/gbvendor/stdlib.go b/gbvendor/stdlib.go
new file mode 100644
index 0000000..f297561
--- /dev/null
+++ b/gbvendor/stdlib.go
@@ -0,0 +1,138 @@
+package vendor
+
+// packages from the standard lib. They are excluded
+// from the package map.
+var stdlib = map[string]bool{
+	"C":                   true,
+	"archive/tar":         true,
+	"archive/zip":         true,
+	"bufio":               true,
+	"bytes":               true,
+	"compress/bzip2":      true,
+	"compress/flate":      true,
+	"compress/gzip":       true,
+	"compress/lzw":        true,
+	"compress/zlib":       true,
+	"container/heap":      true,
+	"container/list":      true,
+	"container/ring":      true,
+	"crypto":              true,
+	"crypto/aes":          true,
+	"crypto/cipher":       true,
+	"crypto/des":          true,
+	"crypto/dsa":          true,
+	"crypto/ecdsa":        true,
+	"crypto/elliptic":     true,
+	"crypto/hmac":         true,
+	"crypto/md5":          true,
+	"crypto/rand":         true,
+	"crypto/rc4":          true,
+	"crypto/rsa":          true,
+	"crypto/sha1":         true,
+	"crypto/sha256":       true,
+	"crypto/sha512":       true,
+	"crypto/subtle":       true,
+	"crypto/tls":          true,
+	"crypto/x509":         true,
+	"crypto/x509/pkix":    true,
+	"database/sql":        true,
+	"database/sql/driver": true,
+	"debug/dwarf":         true,
+	"debug/elf":           true,
+	"debug/gosym":         true,
+	"debug/macho":         true,
+	"debug/pe":            true,
+	"encoding":            true,
+	"encoding/ascii85":    true,
+	"encoding/asn1":       true,
+	"encoding/base32":     true,
+	"encoding/base64":     true,
+	"encoding/binary":     true,
+	"encoding/csv":        true,
+	"encoding/gob":        true,
+	"encoding/hex":        true,
+	"encoding/json":       true,
+	"encoding/pem":        true,
+	"encoding/xml":        true,
+	"errors":              true,
+	"expvar":              true,
+	"flag":                true,
+	"fmt":                 true,
+	"go/ast":              true,
+	"go/build":            true,
+	"go/doc":              true,
+	"go/format":           true,
+	"go/parser":           true,
+	"go/printer":          true,
+	"go/scanner":          true,
+	"go/token":            true,
+	"hash":                true,
+	"hash/adler32":        true,
+	"hash/crc32":          true,
+	"hash/crc64":          true,
+	"hash/fnv":            true,
+	"html":                true,
+	"html/template":       true,
+	"image":               true,
+	"image/color":         true,
+	"image/draw":          true,
+	"image/gif":           true,
+	"image/jpeg":          true,
+	"image/png":           true,
+	"index/suffixarray":   true,
+	"io":                  true,
+	"io/ioutil":           true,
+	"log":                 true,
+	"log/syslog":          true,
+	"math":                true,
+	"math/big":            true,
+	"math/cmplx":          true,
+	"math/rand":           true,
+	"mime":                true,
+	"mime/multipart":      true,
+	"net":                 true,
+	"net/http":            true,
+	"net/http/cgi":        true,
+	"net/http/cookiejar":  true,
+	"net/http/fcgi":       true,
+	"net/http/httptest":   true,
+	"net/http/httputil":   true,
+	"net/http/pprof":      true,
+	"net/mail":            true,
+	"net/rpc":             true,
+	"net/rpc/jsonrpc":     true,
+	"net/smtp":            true,
+	"net/textproto":       true,
+	"net/url":             true,
+	"os":                  true,
+	"os/exec":             true,
+	"os/signal":           true,
+	"os/user":             true,
+	"path":                true,
+	"path/filepath":       true,
+	"reflect":             true,
+	"regexp":              true,
+	"regexp/syntax":       true,
+	"runtime":             true,
+	"runtime/cgo":         true,
+	"runtime/debug":       true,
+	"runtime/pprof":       true,
+	"sort":                true,
+	"strconv":             true,
+	"strings":             true,
+	"sync":                true,
+	"sync/atomic":         true,
+	"syscall":             true,
+	"testing":             true,
+	"testing/iotest":      true,
+	"testing/quick":       true,
+	"text/scanner":        true,
+	"text/tabwriter":      true,
+	"text/template":       true,
+	"text/template/parse": true,
+	"time":                true,
+	"unicode":             true,
+	"unicode/utf16":       true,
+	"unicode/utf8":        true,
+	"unsafe":              true,
+}
diff --git a/gbvendor/stringset.go b/gbvendor/stringset.go
new file mode 100644
index 0000000..007eae5
--- /dev/null
+++ b/gbvendor/stringset.go
@@ -0,0 +1,52 @@
+package vendor
+
+// union returns the union of a and b.
+func union(a, b map[string]bool) map[string]bool {
+	r := make(map[string]bool)
+	for k := range a {
+		r[k] = true
+	}
+	for k := range b {
+		r[k] = true
+	}
+	return r
+}
+
+// intersection returns the intersection of a and b.
+func intersection(a, b map[string]bool) map[string]bool {
+	r := make(map[string]bool)
+	for k := range a {
+		if b[k] {
+			r[k] = true
+		}
+	}
+	return r
+}
+
+// difference returns the symetric difference of a and b.
+func difference(a, b map[string]bool) map[string]bool {
+	r := make(map[string]bool)
+	for k := range a {
+		if !b[k] {
+			r[k] = true
+		}
+	}
+	for k := range b {
+		if !a[k] {
+			r[k] = true
+		}
+	}
+	return r
+}
+
+// contains returns true if a contains all the elements in s.
+func contains(a map[string]bool, s ...string) bool {
+	var r bool
+	for _, e := range s {
+		if !a[e] {
+			return false
+		}
+		r = true
+	}
+	return r
+}
diff --git a/gbvendor/stringset_test.go b/gbvendor/stringset_test.go
new file mode 100644
index 0000000..d8781a4
--- /dev/null
+++ b/gbvendor/stringset_test.go
@@ -0,0 +1,147 @@
+package vendor
+
+import "testing"
+import "reflect"
+
+func set(args ...string) map[string]bool {
+	r := make(map[string]bool)
+	for _, a := range args {
+		r[a] = true
+	}
+	return r
+}
+
+func TestUnion(t *testing.T) {
+	tests := []struct {
+		a, b map[string]bool
+		want map[string]bool
+	}{{
+		a: nil, b: nil,
+		want: set(),
+	}, {
+		a: nil, b: set("b"),
+		want: set("b"),
+	}, {
+		a: set("a"), b: nil,
+		want: set("a"),
+	}, {
+		a: set("a"), b: set("b"),
+		want: set("b", "a"),
+	}, {
+		a: set("c"), b: set("c"),
+		want: set("c"),
+	}}
+
+	for _, tt := range tests {
+		got := union(tt.a, tt.b)
+		if !reflect.DeepEqual(tt.want, got) {
+			t.Errorf("union(%v, %v) want: %v, got %v", tt.a, tt.b, tt.want, got)
+		}
+	}
+}
+
+func TestIntersection(t *testing.T) {
+	tests := []struct {
+		a, b map[string]bool
+		want map[string]bool
+	}{{
+		a: nil, b: nil,
+		want: set(),
+	}, {
+		a: nil, b: set("b"),
+		want: set(),
+	}, {
+		a: set("a"), b: nil,
+		want: set(),
+	}, {
+		a: set("a"), b: set("b"),
+		want: set(),
+	}, {
+		a: set("c"), b: set("c"),
+		want: set("c"),
+	}, {
+		a: set("a", "c"), b: set("b", "c"),
+		want: set("c"),
+	}}
+
+	for _, tt := range tests {
+		got := intersection(tt.a, tt.b)
+		if !reflect.DeepEqual(tt.want, got) {
+			t.Errorf("intersection(%v, %v) want: %v, got %v", tt.a, tt.b, tt.want, got)
+		}
+	}
+}
+
+func TestDifference(t *testing.T) {
+	tests := []struct {
+		a, b map[string]bool
+		want map[string]bool
+	}{{
+		a: nil, b: nil,
+		want: set(),
+	}, {
+		a: nil, b: set("b"),
+		want: set("b"),
+	}, {
+		a: set("a"), b: nil,
+		want: set("a"),
+	}, {
+		a: set("a"), b: set("b"),
+		want: set("a", "b"),
+	}, {
+		a: set("c"), b: set("c"),
+		want: set(),
+	}, {
+		a: set("a", "c"), b: set("b", "c"),
+		want: set("a", "b"),
+	}}
+
+	for _, tt := range tests {
+		got := difference(tt.a, tt.b)
+		if !reflect.DeepEqual(tt.want, got) {
+			t.Errorf("difference(%v, %v) want: %v, got %v", tt.a, tt.b, tt.want, got)
+		}
+	}
+}
+
+func TestContains(t *testing.T) {
+	tests := []struct {
+		a    map[string]bool
+		s    []string
+		want bool
+	}{{
+		a: nil, s: nil,
+		want: false,
+	}, {
+		a: set("a"), s: nil,
+		want: false,
+	}, {
+		a: set("a"), s: []string{"a"},
+		want: true,
+	}, {
+		a: set("a"), s: []string{"b"},
+		want: false,
+	}, {
+		a: set("a", "b"), s: []string{"b"},
+		want: true,
+	}, {
+		a: set("a"), s: []string{"a", "b"},
+		want: false,
+	}, {
+		a: set("a", "b", "c"), s: []string{"a", "b"},
+		want: true,
+	}, {
+		a: set("a", "b", "c"), s: []string{"x", "b"},
+		want: false,
+	}, {
+		a: set("a", "b", "c"), s: []string{"b", "c", "d"},
+		want: false,
+	}}
+
+	for _, tt := range tests {
+		got := contains(tt.a, tt.s...)
+		if !reflect.DeepEqual(tt.want, got) {
+			t.Errorf("contains(%v, %v) want: %v, got %v", tt.a, tt.s, tt.want, got)
+		}
+	}
+}
diff --git a/help.go b/help.go
new file mode 100644
index 0000000..a0c8856
--- /dev/null
+++ b/help.go
@@ -0,0 +1,117 @@
+package main
+
+import (
+	"bufio"
+	"bytes"
+	"fmt"
+	"io"
+	"os"
+	"strings"
+	"text/template"
+	"unicode"
+	"unicode/utf8"
+)
+
+var helpTemplate = `usage: gvt {{.UsageLine}}
+
+{{.Long | trim}}
+`
+
+// help implements the 'help' command.
+func help(args []string) {
+	if len(args) == 0 {
+		printUsage(os.Stdout)
+		return
+	}
+	if len(args) != 1 {
+		fmt.Fprintf(os.Stderr, "usage: gvt help command\n\nToo many arguments given.\n")
+		os.Exit(2)
+	}
+
+	arg := args[0]
+
+	// 'gvt help documentation' generates alldocs.go.
+	if arg == "documentation" {
+		var u bytes.Buffer
+		printUsage(&u)
+		f, _ := os.Create("alldocs.go")
+		tmpl(f, documentationTemplate, struct {
+			Usage    string
+			Commands []*Command
+		}{
+			u.String(),
+			commands,
+		})
+		f.Close()
+		return
+	}
+
+	for _, cmd := range commands {
+		if cmd.Name == arg {
+			tmpl(os.Stdout, helpTemplate, cmd)
+			// not exit 2: succeeded at 'gb help cmd'.
+			return
+		}
+	}
+
+	fmt.Fprintf(os.Stderr, "Unknown help topic %#q. Run 'gb help'.\n", arg)
+	os.Exit(2) // failed at 'gb help cmd'
+}
+
+var usageTemplate = `gvt, a simple go vendoring tool based on gb-vendor.
+
+Usage:
+        gvt command [arguments]
+
+The commands are:
+{{range .}}
+        {{.Name | printf "%-11s"}} {{.Short}}{{end}}
+
+Use "gvt help [command]" for more information about a command.
+`
+
+var documentationTemplate = `// DO NOT EDIT THIS FILE.
+//go:generate gvt help documentation
+
+/*
+{{ .Usage }}
+
+{{range .Commands}}{{if .Short}}## {{.Short | capitalize}}
+{{end}}
+Usage:
+        gvt {{.UsageLine}}
+
+{{.Long | trim}}
+
+{{end}}*/
+package main
+`
+
+// tmpl executes the given template text on data, writing the result to w.
+func tmpl(w io.Writer, text string, data interface{}) {
+	t := template.New("top")
+	t.Funcs(template.FuncMap{"trim": strings.TrimSpace, "capitalize": capitalize})
+	template.Must(t.Parse(text))
+	if err := t.Execute(w, data); err != nil {
+		panic(err)
+	}
+}
+
+func capitalize(s string) string {
+	if s == "" {
+		return s
+	}
+	r, n := utf8.DecodeRuneInString(s)
+	return string(unicode.ToTitle(r)) + s[n:]
+}
+
+func printUsage(w io.Writer) {
+	bw := bufio.NewWriter(w)
+	tmpl(bw, usageTemplate, commands)
+	bw.Flush()
+}
+
+func usage() {
+	printUsage(os.Stderr)
+	os.Exit(2)
+}
diff --git a/list.go b/list.go
new file mode 100644
index 0000000..e3113d9
--- /dev/null
+++ b/list.go
@@ -0,0 +1,54 @@
+package main
+
+import (
+	"flag"
+	"fmt"
+	"html/template"
+	"os"
+	"text/tabwriter"
+
+	"github.com/FiloSottile/gvt/gbvendor"
+)
+
+var (
+	format string
+)
+
+func addListFlags(fs *flag.FlagSet) {
+	fs.StringVar(&format, "f", "{{.Importpath}}\t{{.Repository}}{{.Path}}\t{{.Branch}}\t{{.Revision}}", "format template")
+}
+
+var cmdList = &Command{
+	Name:      "list",
+	UsageLine: "list [-f format]",
+	Short:     "lists dependencies, one per line",
+	Long: `list formats the contents of the manifest file.
+
+The output
+
+Flags:
+	-f
+		controls the template used for printing each manifest entry. If not supplied
+		the default value is "{{.Importpath}}\t{{.Repository}}{{.Path}}\t{{.Branch}}\t{{.Revision}}"
+
+`,
+	Run: func(args []string) error {
+		m, err := vendor.ReadManifest(manifestFile())
+		if err != nil {
+			return fmt.Errorf("could not load manifest: %v", err)
+		}
+		tmpl, err := template.New("list").Parse(format)
+		if err != nil {
+			return fmt.Errorf("unable to parse template %q: %v", format, err)
+		}
+		w := tabwriter.NewWriter(os.Stdout, 1, 2, 1, ' ', 0)
+		for _, dep := range m.Dependencies {
+			if err := tmpl.Execute(w, dep); err != nil {
+				return fmt.Errorf("unable to execute template: %v", err)
+			}
+			fmt.Fprintln(w)
+		}
+		return w.Flush()
+	},
+	AddFlags: addListFlags,
+}
diff --git a/main.go b/main.go
new file mode 100644
index 0000000..89a8dc6
--- /dev/null
+++ b/main.go
@@ -0,0 +1,81 @@
+package main
+
+import (
+	"flag"
+	"log"
+	"os"
+	"path/filepath"
+)
+
+var fs = flag.NewFlagSet(os.Args[0], flag.ExitOnError)
+
+func init() {
+	fs.Usage = func() {
+		printUsage(os.Stderr)
+		os.Exit(2)
+	}
+}
+
+type Command struct {
+	Name      string
+	UsageLine string
+	Short     string
+	Long      string
+	Run       func(args []string) error
+	AddFlags  func(fs *flag.FlagSet)
+}
+
+var commands = []*Command{
+	cmdFetch,
+	cmdUpdate,
+	cmdList,
+	cmdDelete,
+}
+
+func main() {
+	args := os.Args[1:]
+
+	switch {
+	case len(args) < 1, args[0] == "-h", args[0] == "-help":
+		fs.Usage()
+		os.Exit(1)
+	case args[0] == "help":
+		help(args[1:])
+		return
+	}
+
+	for _, command := range commands {
+		if command.Name == args[0] {
+
+			// add extra flags if necessary
+			if command.AddFlags != nil {
+				command.AddFlags(fs)
+			}
+
+			if err := fs.Parse(args[1:]); err != nil {
+				log.Fatalf("could not parse flags: %v", err)
+			}
+			args = fs.Args() // reset args to the leftovers from fs.Parse
+
+			if err := command.Run(args); err != nil {
+				log.Fatalf("command %q failed: %v", command.Name, err)
+			}
+			return
+		}
+	}
+	log.Fatalf("unknown command %q ", args[0])
+}
+
+const manifestfile = "manifest"
+
+func vendorDir() string {
+	wd, err := os.Getwd()
+	if err != nil {
+		log.Fatal(err)
+	}
+	return filepath.Join(wd, "vendor")
+}
+
+func manifestFile() string {
+	return filepath.Join(vendorDir(), manifestfile)
+}
diff --git a/update.go b/update.go
new file mode 100644
index 0000000..86d0fb1
--- /dev/null
+++ b/update.go
@@ -0,0 +1,128 @@
+package main
+
+import (
+	"flag"
+	"fmt"
+	"path/filepath"
+
+	"github.com/FiloSottile/gvt/gbvendor"
+)
+
+var (
+	updateAll bool // update all dependencies
+)
+
+func addUpdateFlags(fs *flag.FlagSet) {
+	fs.BoolVar(&updateAll, "all", false, "update all dependencies")
+	fs.BoolVar(&insecure, "precaire", false, "allow the use of insecure protocols")
+}
+
+var cmdUpdate = &Command{
+	Name:      "update",
+	UsageLine: "update [-all] import",
+	Short:     "update a local dependency",
+	Long: `update will replaces the source with the latest available from the head of the master branch.
+
+Updating from one copy of a dependency to another comes with several restrictions.
+The first is you can only update to the head of the branch your dependency was vendored from, switching branches is not supported.
+The second restriction is if you have used -tag or -revision while vendoring a dependency, your dependency is "headless"
+(to borrow a term from git) and cannot be updated.
+
+To update across branches, or from one tag/revision to another, you must first use delete to remove the dependency, then
+fetch [-tag | -revision | -branch ] [-precaire] to replace it.
+
+Flags:
+	-all
+		will update all dependencies in the manifest, otherwise only the dependency supplied.
+	-precaire
+		allow the use of insecure protocols.
+
+`,
+	Run: func(args []string) error {
+		if len(args) != 1 && !updateAll {
+			return fmt.Errorf("update: import path or --all flag is missing")
+		} else if len(args) == 1 && updateAll {
+			return fmt.Errorf("update: you cannot specify path and --all flag at once")
+		}
+
+		m, err := vendor.ReadManifest(manifestFile())
+		if err != nil {
+			return fmt.Errorf("could not load manifest: %v", err)
+		}
+
+		var dependencies []vendor.Dependency
+		if updateAll {
+			dependencies = make([]vendor.Dependency, len(m.Dependencies))
+			copy(dependencies, m.Dependencies)
+		} else {
+			p := args[0]
+			dependency, err := m.GetDependencyForImportpath(p)
+			if err != nil {
+				return fmt.Errorf("could not get dependency: %v", err)
+			}
+			dependencies = append(dependencies, dependency)
+		}
+
+		for _, d := range dependencies {
+			err = m.RemoveDependency(d)
+			if err != nil {
+				return fmt.Errorf("dependency could not be deleted from manifest: %v", err)
+			}
+
+			repo, extra, err := vendor.DeduceRemoteRepo(d.Importpath, insecure)
+			if err != nil {
+				return fmt.Errorf("could not determine repository for import %q", d.Importpath)
+			}
+
+			wc, err := repo.Checkout(d.Branch, "", "")
+			if err != nil {
+				return err
+			}
+
+			rev, err := wc.Revision()
+			if err != nil {
+				return err
+			}
+
+			branch, err := wc.Branch()
+			if err != nil {
+				return err
+			}
+
+			dep := vendor.Dependency{
+				Importpath: d.Importpath,
+				Repository: repo.URL(),
+				Revision:   rev,
+				Branch:     branch,
+				Path:       extra,
+			}
+
+			if err := vendor.RemoveAll(filepath.Join(vendorDir(), filepath.FromSlash(d.Importpath))); err != nil {
+				// TODO(dfc) need to apply vendor.cleanpath here to remove intermediate directories.
+				return fmt.Errorf("dependency could not be deleted: %v", err)
+			}
+
+			dst := filepath.Join(vendorDir(), filepath.FromSlash(dep.Importpath))
+			src := filepath.Join(wc.Dir(), dep.Path)
+
+			if err := vendor.Copypath(dst, src); err != nil {
+				return err
+			}
+
+			if err := m.AddDependency(dep); err != nil {
+				return err
+			}
+
+			if err := vendor.WriteManifest(manifestFile(), m); err != nil {
+				return err
+			}
+
+			if err := wc.Destroy(); err != nil {
+				return err
+			}
+		}
+
+		return nil
+	},
+	AddFlags: addUpdateFlags,
+}