├── .travis.yml ├── LICENSE ├── README.md ├── alldocs.go ├── delete.go ├── fetch.go ├── gbvendor ├── _testdata │ ├── copyfile │ │ └── a │ │ │ └── rick │ ├── src │ │ └── github.com │ │ │ └── foo │ │ │ └── bar │ │ │ └── main.go │ └── vendor │ │ └── src │ │ ├── bitbucket.org │ │ └── fwoop │ │ │ └── ftang │ │ │ └── kthulu.go │ │ └── github.com │ │ ├── hoo │ │ └── wuu │ │ │ └── goo.go │ │ ├── lypo │ │ └── moopo │ │ │ └── tropo.go │ │ └── quux │ │ └── flobble │ │ └── wobble.go ├── depset.go ├── discovery.go ├── imports.go ├── imports_test.go ├── manifest.go ├── manifest_test.go ├── repo.go ├── repo_test.go ├── stringset.go └── stringset_test.go ├── help.go ├── list.go ├── main.go ├── restore.go ├── update.go └── vendor ├── github.com └── constabulary │ └── gb │ └── fileutils │ ├── _testdata │ └── copyfile │ │ └── a │ │ └── rick │ ├── fileutils.go │ ├── fileutils_test.go │ └── path_test.go └── manifest /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | 3 | go: 4 | - 1.5.3 5 | - 1.6rc2 6 | - tip 7 | 8 | matrix: 9 | allow_failures: 10 | - go: tip 11 | 12 | sudo: false 13 | 14 | os: 15 | - linux 16 | - osx 17 | 18 | # ssh_known_hosts is broken on OS X, run ssh-keyscan manually instead 19 | # See https://github.com/travis-ci/travis-ci/issues/5596 20 | # addons: 21 | # ssh_known_hosts: 22 | # - bitbucket.org 23 | before_install: 24 | - ssh-keyscan -t rsa -T 30 -H bitbucket.org | tee -a $HOME/.ssh/known_hosts 25 | 26 | env: 27 | global: 28 | - GO15VENDOREXPERIMENT="1" 29 | 30 | install: 31 | - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then brew update; fi 32 | - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then brew install bazaar; fi 33 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 constabulary 4 | Copyright (c) 2015 Filippo Valsorda 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # gvt, the go vendoring tool 2 | [![GoDoc](https://godoc.org/github.com/FiloSottile/gvt?status.svg)](https://godoc.org/github.com/FiloSottile/gvt) 3 | [![Build Status](https://travis-ci.org/FiloSottile/gvt.svg?branch=master)](https://travis-ci.org/FiloSottile/gvt) 4 | 5 | *本 fork 解决了国内 golang.org/x/net 之类的 package 被墙的问题* 6 | 7 | `gvt` is a simple Go vendoring tool made for the 8 | [GO15VENDOREXPERIMENT](https://docs.google.com/document/d/1Bz5-UB7g2uPBdOx-rw5t9MxJwkfpx90cqG9AFL0JAYo/edit), 9 | based on [gb-vendor](https://github.com/constabulary/gb). 10 | 11 | It lets you easily and "idiomatically" include external dependencies in your repository to get 12 | reproducible builds. 13 | 14 | * No need to learn a new tool or format! 15 | You already know how to use `gvt`: just run `gvt fetch` when and like you would run `go get`. 16 | You can imagine what `gvt update` and `gvt delete` do. 17 | 18 | * No need to change how you build your project! 19 | `gvt` downloads packages to `./vendor/...`. With `GO15VENDOREXPERIMENT=1` the stock Go compiler 20 | will find and use those dependencies automatically (without import path or GOPATH changes). 21 | 22 | * No need to manually chase, copy or cleanup dependencies! 23 | `gvt` works recursively as you would expect, and lets you update vendored dependencies. It also 24 | writes a manifest to `./vendor/manifest` and never touches your system GOPATH. Finally, it 25 | strips the VCS metadata so that you can commit the vendored source cleanly. 26 | 27 | * No need for your users and occasional contributors to install **or even know about** gvt! 28 | Packages whose dependencies are vendored with `gvt` are `go build`-able and `go get`-able out of 29 | the box by Go 1.5 with `GO15VENDOREXPERIMENT=1` set. 30 | 31 | *Note that projects must live within the GOPATH tree in order to be go buildable with the 32 | GO15VENDOREXPERIMENT flag.* 33 | 34 | If you use and like (or dislike!) `gvt`, it would definitely make my day better if you dropped a 35 | line at `gvt -at- filippo.io` :) 36 | 37 | ## Installation 38 | 39 | With a [correctly configured](https://golang.org/doc/code.html#GOPATH) Go installation: 40 | 41 | ``` 42 | GO15VENDOREXPERIMENT=1 go get -u github.com/FiloSottile/gvt 43 | ``` 44 | 45 | ## Usage 46 | 47 | You know how to use `go get`? That's how you use `gvt fetch`. 48 | 49 | ``` 50 | # This will fetch the dependency into the ./vendor folder. 51 | $ gvt fetch github.com/fatih/color 52 | 2015/09/05 02:38:06 fetching recursive dependency github.com/mattn/go-isatty 53 | 2015/09/05 02:38:07 fetching recursive dependency github.com/shiena/ansicolor 54 | 55 | $ tree -d 56 | . 57 | └── vendor 58 | └── github.com 59 | ├── fatih 60 | │ └── color 61 | ├── mattn 62 | │ └── go-isatty 63 | └── shiena 64 | └── ansicolor 65 | └── ansicolor 66 | 67 | 9 directories 68 | 69 | $ cat > main.go 70 | package main 71 | import "github.com/fatih/color" 72 | func main() { 73 | color.Red("Hello, world!") 74 | } 75 | 76 | $ export GO15VENDOREXPERIMENT=1 77 | $ go build . 78 | $ ./hello 79 | Hello, world! 80 | 81 | $ git add main.go vendor/ && git commit 82 | 83 | ``` 84 | 85 | A full set of example usage can be found on [GoDoc](https://godoc.org/github.com/FiloSottile/gvt). 86 | 87 | ## Alternative: not checking in vendored source 88 | 89 | Some developers prefer not to check in the source of the vendored dependencies. In that case you can 90 | add lines like these to e.g. your `.gitignore` 91 | 92 | vendor/** 93 | !vendor/manifest 94 | 95 | When you check out the source again, you can then run `gvt restore` to fetch all the dependencies at 96 | the revisions specified in the `vendor/manifest` file. 97 | 98 | Please consider that this approach has the following consequences: 99 | 100 | * the package consumer will need gvt to fetch the dependencies 101 | * the dependencies will need to remain available from the source repositories: if the original 102 | repository goes down or rewrites history, build reproducibility is lost 103 | * `go get` won't work on your package 104 | 105 | ## Troubleshooting 106 | 107 | ### `fatal: Not a git repository [...]` 108 | ### `error: tag 'fetch' not found.` 109 | 110 | These errors can occur because you have an alias for `gvt` pointing to `git verify-tag` 111 | (default if using oh-my-zsh). 112 | 113 | Recent versions of oh-my-zsh [removed the alias](https://github.com/robbyrussell/oh-my-zsh/pull/4841). You can update with `upgrade_oh_my_zsh`. 114 | 115 | Alternatively, run this, and preferably add it to your `~/.bashrc` / `~/.zshrc`: `unalias gvt`. 116 | 117 | ### `go build` can't find the vendored package 118 | 119 | Make sure you set `GO15VENDOREXPERIMENT=1`. 120 | 121 | Also note that GO15VENDOREXPERIMENT does not apply when outside the GOPATH tree. That is, your 122 | project must be somewhere in a subfolder of `$GOPATH`. 123 | 124 | ## License 125 | 126 | MIT licensed. See the LICENSE file for details. 127 | -------------------------------------------------------------------------------- /alldocs.go: -------------------------------------------------------------------------------- 1 | // DO NOT EDIT THIS FILE. 2 | //go:generate gvt help documentation 3 | 4 | /* 5 | gvt, a simple go vendoring tool based on gb-vendor. 6 | 7 | Usage: 8 | gvt command [arguments] 9 | 10 | The commands are: 11 | 12 | fetch fetch a remote dependency 13 | restore restore dependencies from manifest 14 | update update a local dependency 15 | list list dependencies one per line 16 | delete delete a local dependency 17 | 18 | Use "gvt help [command]" for more information about a command. 19 | 20 | 21 | Fetch a remote dependency 22 | 23 | Usage: 24 | gvt fetch [-branch branch] [-revision rev | -tag tag] [-precaire] [-no-recurse] importpath 25 | 26 | fetch vendors an upstream import path. 27 | 28 | The import path may include a url scheme. This may be useful when fetching dependencies 29 | from private repositories that cannot be probed. 30 | 31 | Flags: 32 | -branch branch 33 | fetch from the named branch. Will also be used by gvt update. 34 | If not supplied the default upstream branch will be used. 35 | -no-recurse 36 | do not fetch recursively. 37 | -tag tag 38 | fetch the specified tag. 39 | -revision rev 40 | fetch the specific revision from the branch or repository. 41 | If no revision supplied, the latest available will be fetched. 42 | -precaire 43 | allow the use of insecure protocols. 44 | 45 | Restore dependencies from manifest 46 | 47 | Usage: 48 | gvt restore [-precaire] [-connections N] 49 | 50 | restore fetches the dependencies listed in the manifest. 51 | 52 | It's meant for workflows that don't include checking in to VCS the vendored 53 | source, for example if .gitignore includes lines like 54 | 55 | vendor/** 56 | !vendor/manifest 57 | 58 | Note that such a setup requires "gvt restore" to build the source, relies on 59 | the availability of the dependencies repositories and breaks "go get". 60 | 61 | Flags: 62 | -precaire 63 | allow the use of insecure protocols. 64 | -connections 65 | count of parallel download connections. 66 | 67 | Update a local dependency 68 | 69 | Usage: 70 | gvt update [ -all | importpath ] 71 | 72 | update replaces the source with the latest available from the head of the fetched branch. 73 | 74 | Updating from one copy of a dependency to another is ONLY possible when the 75 | dependency was fetched by branch, without using -tag or -revision. It will be 76 | updated to the HEAD of that branch, switching branches is not supported. 77 | 78 | To update across branches, or from one tag/revision to another, you must first 79 | use delete to remove the dependency, then fetch [ -tag | -revision | -branch ] 80 | to replace it. 81 | 82 | Flags: 83 | -all 84 | update all dependencies in the manifest. 85 | -precaire 86 | allow the use of insecure protocols. 87 | 88 | List dependencies one per line 89 | 90 | Usage: 91 | gvt list [-f format] 92 | 93 | list formats the contents of the manifest file. 94 | 95 | Flags: 96 | -f 97 | controls the template used for printing each manifest entry. If not supplied 98 | the default value is "{{.Importpath}}\t{{.Repository}}{{.Path}}\t{{.Branch}}\t{{.Revision}}" 99 | 100 | Delete a local dependency 101 | 102 | Usage: 103 | gvt delete [-all] importpath 104 | 105 | delete removes a dependency from the vendor directory and the manifest 106 | 107 | Flags: 108 | -all 109 | remove all dependencies 110 | 111 | */ 112 | package main 113 | -------------------------------------------------------------------------------- /delete.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "path/filepath" 7 | 8 | "github.com/constabulary/gb/fileutils" 9 | 10 | "github.com/polaris1119/gvt/gbvendor" 11 | ) 12 | 13 | var ( 14 | deleteAll bool // delete all dependencies 15 | ) 16 | 17 | func addDeleteFlags(fs *flag.FlagSet) { 18 | fs.BoolVar(&deleteAll, "all", false, "delete all dependencies") 19 | } 20 | 21 | var cmdDelete = &Command{ 22 | Name: "delete", 23 | UsageLine: "delete [-all] importpath", 24 | Short: "delete a local dependency", 25 | Long: `delete removes a dependency from the vendor directory and the manifest 26 | 27 | Flags: 28 | -all 29 | remove all dependencies 30 | 31 | `, 32 | Run: func(args []string) error { 33 | if len(args) != 1 && !deleteAll { 34 | return fmt.Errorf("delete: import path or --all flag is missing") 35 | } else if len(args) == 1 && deleteAll { 36 | return fmt.Errorf("delete: you cannot specify path and --all flag at once") 37 | } 38 | 39 | m, err := vendor.ReadManifest(manifestFile()) 40 | if err != nil { 41 | return fmt.Errorf("could not load manifest: %v", err) 42 | } 43 | 44 | var dependencies []vendor.Dependency 45 | if deleteAll { 46 | dependencies = make([]vendor.Dependency, len(m.Dependencies)) 47 | copy(dependencies, m.Dependencies) 48 | } else { 49 | p := args[0] 50 | dependency, err := m.GetDependencyForImportpath(p) 51 | if err != nil { 52 | return fmt.Errorf("could not get dependency: %v", err) 53 | } 54 | dependencies = append(dependencies, dependency) 55 | } 56 | 57 | for _, d := range dependencies { 58 | path := d.Importpath 59 | 60 | if err := m.RemoveDependency(d); err != nil { 61 | return fmt.Errorf("dependency could not be deleted: %v", err) 62 | } 63 | 64 | if err := fileutils.RemoveAll(filepath.Join(vendorDir(), filepath.FromSlash(path))); err != nil { 65 | // TODO(dfc) need to apply vendor.cleanpath here to remove indermediate directories. 66 | return fmt.Errorf("dependency could not be deleted: %v", err) 67 | } 68 | } 69 | return vendor.WriteManifest(manifestFile(), m) 70 | }, 71 | AddFlags: addDeleteFlags, 72 | } 73 | -------------------------------------------------------------------------------- /fetch.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "go/build" 7 | "log" 8 | "net/url" 9 | "path/filepath" 10 | "runtime" 11 | "sort" 12 | 13 | "github.com/constabulary/gb/fileutils" 14 | "github.com/polaris1119/gvt/gbvendor" 15 | ) 16 | 17 | var ( 18 | branch string 19 | revision string // revision (commit) 20 | tag string 21 | noRecurse bool 22 | insecure bool // Allow the use of insecure protocols 23 | 24 | recurse bool // should we fetch recursively 25 | ) 26 | 27 | func addFetchFlags(fs *flag.FlagSet) { 28 | fs.StringVar(&branch, "branch", "", "branch of the package") 29 | fs.StringVar(&revision, "revision", "", "revision of the package") 30 | fs.StringVar(&tag, "tag", "", "tag of the package") 31 | fs.BoolVar(&noRecurse, "no-recurse", false, "do not fetch recursively") 32 | fs.BoolVar(&insecure, "precaire", false, "allow the use of insecure protocols") 33 | } 34 | 35 | var cmdFetch = &Command{ 36 | Name: "fetch", 37 | UsageLine: "fetch [-branch branch] [-revision rev | -tag tag] [-precaire] [-no-recurse] importpath", 38 | Short: "fetch a remote dependency", 39 | Long: `fetch vendors an upstream import path. 40 | 41 | The import path may include a url scheme. This may be useful when fetching dependencies 42 | from private repositories that cannot be probed. 43 | 44 | Flags: 45 | -branch branch 46 | fetch from the named branch. Will also be used by gvt update. 47 | If not supplied the default upstream branch will be used. 48 | -no-recurse 49 | do not fetch recursively. 50 | -tag tag 51 | fetch the specified tag. 52 | -revision rev 53 | fetch the specific revision from the branch or repository. 54 | If no revision supplied, the latest available will be fetched. 55 | -precaire 56 | allow the use of insecure protocols. 57 | 58 | `, 59 | Run: func(args []string) error { 60 | switch len(args) { 61 | case 0: 62 | return fmt.Errorf("fetch: import path missing") 63 | case 1: 64 | path := args[0] 65 | recurse = !noRecurse 66 | return fetch(path, recurse) 67 | default: 68 | return fmt.Errorf("more than one import path supplied") 69 | } 70 | }, 71 | AddFlags: addFetchFlags, 72 | } 73 | 74 | var AlreadyErr = fmt.Errorf("alread vendored") 75 | 76 | func fetch(path string, recurse bool) error { 77 | m, err := vendor.ReadManifest(manifestFile()) 78 | if err != nil { 79 | return fmt.Errorf("could not load manifest: %v", err) 80 | } 81 | 82 | repo, extra, err := vendor.DeduceRemoteRepo(path, insecure) 83 | if err != nil { 84 | return err 85 | } 86 | 87 | // strip of any scheme portion from the path, it is already 88 | // encoded in the repo. 89 | path = stripscheme(path) 90 | 91 | if m.HasImportpath(path) { 92 | log.Printf("%s is already vendored", path) 93 | return AlreadyErr 94 | } 95 | 96 | wc, err := repo.Checkout(branch, tag, revision) 97 | 98 | if err != nil { 99 | return err 100 | } 101 | 102 | rev, err := wc.Revision() 103 | if err != nil { 104 | return err 105 | } 106 | 107 | branch, err := wc.Branch() 108 | if err != nil { 109 | return err 110 | } 111 | 112 | dep := vendor.Dependency{ 113 | Importpath: path, 114 | Repository: repo.URL(), 115 | Revision: rev, 116 | Branch: branch, 117 | Path: extra, 118 | } 119 | 120 | if err := m.AddDependency(dep); err != nil { 121 | return err 122 | } 123 | 124 | dst := filepath.Join(vendorDir(), dep.Importpath) 125 | src := filepath.Join(wc.Dir(), dep.Path) 126 | 127 | if err := fileutils.Copypath(dst, src); err != nil { 128 | return err 129 | } 130 | 131 | if err := vendor.WriteManifest(manifestFile(), m); err != nil { 132 | return err 133 | } 134 | 135 | if err := wc.Destroy(); err != nil { 136 | return err 137 | } 138 | 139 | if !recurse { 140 | return nil 141 | } 142 | 143 | // if we are recursing, overwrite branch, tag and revision 144 | // values so recursive fetching checks out from HEAD. 145 | branch = "" 146 | tag = "" 147 | revision = "" 148 | 149 | ForLoop: 150 | for done := false; !done; { 151 | 152 | paths := []struct { 153 | Root, Prefix string 154 | }{ 155 | {filepath.Join(runtime.GOROOT(), "src"), ""}, 156 | } 157 | m, err := vendor.ReadManifest(manifestFile()) 158 | if err != nil { 159 | return err 160 | } 161 | for _, d := range m.Dependencies { 162 | paths = append(paths, struct{ Root, Prefix string }{filepath.Join(vendorDir(), filepath.FromSlash(d.Importpath)), filepath.FromSlash(d.Importpath)}) 163 | } 164 | 165 | dsm, err := vendor.LoadPaths(paths...) 166 | if err != nil { 167 | return err 168 | } 169 | 170 | is, ok := dsm[filepath.Join(vendorDir(), path)] 171 | if !ok { 172 | return fmt.Errorf("unable to locate depset for %q", path) 173 | } 174 | 175 | missing := findMissing(pkgs(is.Pkgs), dsm) 176 | switch len(missing) { 177 | case 0: 178 | done = true 179 | default: 180 | 181 | // sort keys in ascending order, so the shortest missing import path 182 | // with be fetched first. 183 | keys := keys(missing) 184 | sort.Strings(keys) 185 | pkg := keys[0] 186 | log.Printf("fetching recursive dependency %s", pkg) 187 | if err := fetch(pkg, false); err != nil { 188 | if err == AlreadyErr { 189 | break ForLoop 190 | } 191 | return err 192 | } 193 | } 194 | } 195 | 196 | return nil 197 | } 198 | 199 | func keys(m map[string]bool) []string { 200 | var s []string 201 | for k := range m { 202 | s = append(s, k) 203 | } 204 | return s 205 | } 206 | 207 | func pkgs(m map[string]*vendor.Pkg) []*vendor.Pkg { 208 | var p []*vendor.Pkg 209 | for _, v := range m { 210 | p = append(p, v) 211 | } 212 | return p 213 | } 214 | 215 | func findMissing(pkgs []*vendor.Pkg, dsm map[string]*vendor.Depset) map[string]bool { 216 | missing := make(map[string]bool) 217 | imports := make(map[string]*vendor.Pkg) 218 | for _, s := range dsm { 219 | for _, p := range s.Pkgs { 220 | imports[p.ImportPath] = p 221 | } 222 | } 223 | 224 | // make fake C package for cgo 225 | imports["C"] = &vendor.Pkg{ 226 | Depset: nil, // probably a bad idea 227 | Package: &build.Package{ 228 | Name: "C", 229 | }, 230 | } 231 | stk := make(map[string]bool) 232 | push := func(v string) { 233 | if stk[v] { 234 | panic(fmt.Sprintln("import loop:", v, stk)) 235 | } 236 | stk[v] = true 237 | } 238 | pop := func(v string) { 239 | if !stk[v] { 240 | panic(fmt.Sprintln("impossible pop:", v, stk)) 241 | } 242 | delete(stk, v) 243 | } 244 | 245 | // checked records import paths who's dependencies are all present 246 | checked := make(map[string]bool) 247 | 248 | var fn func(string) 249 | fn = func(importpath string) { 250 | p, ok := imports[importpath] 251 | if !ok { 252 | missing[importpath] = true 253 | return 254 | } 255 | 256 | // have we already walked this arm, if so, skip it 257 | if checked[importpath] { 258 | return 259 | } 260 | 261 | sz := len(missing) 262 | push(importpath) 263 | for _, i := range p.Imports { 264 | if i == importpath { 265 | continue 266 | } 267 | fn(i) 268 | } 269 | 270 | // if the size of the missing map has not changed 271 | // this entire subtree is complete, mark it as such 272 | if len(missing) == sz { 273 | checked[importpath] = true 274 | } 275 | pop(importpath) 276 | } 277 | for _, pkg := range pkgs { 278 | fn(pkg.ImportPath) 279 | } 280 | return missing 281 | } 282 | 283 | // stripscheme removes any scheme components from url like paths. 284 | func stripscheme(path string) string { 285 | u, err := url.Parse(path) 286 | if err != nil { 287 | panic(err) 288 | } 289 | return u.Host + u.Path 290 | } 291 | -------------------------------------------------------------------------------- /gbvendor/_testdata/copyfile/a/rick: -------------------------------------------------------------------------------- 1 | /never/going/to/give/you/up -------------------------------------------------------------------------------- /gbvendor/_testdata/src/github.com/foo/bar/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/quux/flobble" 7 | // "bitbucket.org/fwoop/ftang" // commented out, this is deliberate 8 | moo "github.com/lypo/moopo" 9 | ) 10 | 11 | import "github.com/hoo/wuu" 12 | 13 | func main() { 14 | fmt.Println(flobble.Q) 15 | fmt.Prinln(moo.Q) 16 | fmt.Println(wuu.Q) 17 | } 18 | -------------------------------------------------------------------------------- /gbvendor/_testdata/vendor/src/bitbucket.org/fwoop/ftang/kthulu.go: -------------------------------------------------------------------------------- 1 | package ftang 2 | 3 | const CAT = "ack!" 4 | -------------------------------------------------------------------------------- /gbvendor/_testdata/vendor/src/github.com/hoo/wuu/goo.go: -------------------------------------------------------------------------------- 1 | package wuu 2 | 3 | const Q = "hey" 4 | -------------------------------------------------------------------------------- /gbvendor/_testdata/vendor/src/github.com/lypo/moopo/tropo.go: -------------------------------------------------------------------------------- 1 | package moopo 2 | 3 | const Q = "hi" 4 | -------------------------------------------------------------------------------- /gbvendor/_testdata/vendor/src/github.com/quux/flobble/wobble.go: -------------------------------------------------------------------------------- 1 | package flobble 2 | 3 | const Q = "hello" 4 | -------------------------------------------------------------------------------- /gbvendor/depset.go: -------------------------------------------------------------------------------- 1 | package vendor 2 | 3 | import ( 4 | "fmt" 5 | "go/build" 6 | "os" 7 | "path/filepath" 8 | "strings" 9 | ) 10 | 11 | // Pkg describes a Go package. 12 | type Pkg struct { 13 | *Depset 14 | *build.Package 15 | } 16 | 17 | // Depset describes a set of related Go packages. 18 | type Depset struct { 19 | Root string 20 | Prefix string 21 | Pkgs map[string]*Pkg 22 | } 23 | 24 | // LoadPaths returns a map of paths to Depsets. 25 | func LoadPaths(paths ...struct{ Root, Prefix string }) (map[string]*Depset, error) { 26 | m := make(map[string]*Depset) 27 | for _, p := range paths { 28 | set, err := LoadTree(p.Root, p.Prefix) 29 | if err != nil { 30 | return nil, err 31 | } 32 | m[set.Root] = set 33 | } 34 | return m, nil 35 | } 36 | 37 | // LoadTree parses a tree of source files into a map of *pkgs. 38 | func LoadTree(root string, prefix string) (*Depset, error) { 39 | d := Depset{ 40 | Root: root, 41 | Prefix: prefix, 42 | Pkgs: make(map[string]*Pkg), 43 | } 44 | fn := func(dir string, fi os.FileInfo) error { 45 | importpath := filepath.Join(prefix, dir[len(root)+1:]) 46 | 47 | // if we're at the root of a tree, skip it 48 | if importpath == "" { 49 | return nil 50 | } 51 | 52 | p, err := loadPackage(&d, dir) 53 | if err != nil { 54 | if _, ok := err.(*build.NoGoError); ok { 55 | return nil 56 | } 57 | return fmt.Errorf("loadPackage(%q, %q): %v", dir, importpath, err) 58 | } 59 | p.ImportPath = filepath.ToSlash(importpath) 60 | if p != nil { 61 | d.Pkgs[p.ImportPath] = p 62 | } 63 | return nil 64 | } 65 | 66 | // handle root of the tree 67 | fi, err := os.Stat(root) 68 | if err != nil { 69 | return nil, err 70 | } 71 | if err := fn(root+string(filepath.Separator), fi); err != nil { 72 | return nil, err 73 | } 74 | 75 | // walk sub directories 76 | err = eachDir(root, fn) 77 | return &d, err 78 | } 79 | 80 | func loadPackage(d *Depset, dir string) (*Pkg, error) { 81 | p := Pkg{ 82 | Depset: d, 83 | } 84 | var err error 85 | 86 | // expolit local import logic 87 | p.Package, err = build.ImportDir(dir, build.ImportComment) 88 | return &p, err 89 | } 90 | 91 | func eachDir(dir string, fn func(string, os.FileInfo) error) error { 92 | f, err := os.Open(dir) 93 | if err != nil { 94 | return err 95 | } 96 | defer f.Close() 97 | files, err := f.Readdir(-1) 98 | for _, fi := range files { 99 | if !fi.IsDir() { 100 | continue 101 | } 102 | if strings.HasPrefix(fi.Name(), "_") || strings.HasPrefix(fi.Name(), ".") || fi.Name() == "testdata" { 103 | continue 104 | } 105 | path := filepath.Join(dir, fi.Name()) 106 | if err := fn(path, fi); err != nil { 107 | return err 108 | } 109 | if err := eachDir(path, fn); err != nil { 110 | return err 111 | } 112 | } 113 | return nil 114 | } 115 | -------------------------------------------------------------------------------- /gbvendor/discovery.go: -------------------------------------------------------------------------------- 1 | // Copyright 2012 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package vendor 6 | 7 | import ( 8 | "encoding/xml" 9 | "fmt" 10 | "io" 11 | "strings" 12 | ) 13 | 14 | // charsetReader returns a reader for the given charset. Currently 15 | // it only supports UTF-8 and ASCII. Otherwise, it returns a meaningful 16 | // error which is printed by go get, so the user can find why the package 17 | // wasn't downloaded if the encoding is not supported. Note that, in 18 | // order to reduce potential errors, ASCII is treated as UTF-8 (i.e. characters 19 | // greater than 0x7f are not rejected). 20 | func charsetReader(charset string, input io.Reader) (io.Reader, error) { 21 | switch strings.ToLower(charset) { 22 | case "ascii": 23 | return input, nil 24 | default: 25 | return nil, fmt.Errorf("can't decode XML document using charset %q", charset) 26 | } 27 | } 28 | 29 | type metaImport struct { 30 | Prefix, VCS, RepoRoot string 31 | } 32 | 33 | // parseMetaGoImports returns meta imports from the HTML in r. 34 | // Parsing ends at the end of the section or the beginning of the . 35 | func parseMetaGoImports(r io.Reader) (imports []metaImport, err error) { 36 | d := xml.NewDecoder(r) 37 | d.CharsetReader = charsetReader 38 | d.Strict = false 39 | var t xml.Token 40 | for { 41 | t, err = d.Token() 42 | if err != nil { 43 | if err == io.EOF { 44 | err = nil 45 | } 46 | return 47 | } 48 | if e, ok := t.(xml.StartElement); ok && strings.EqualFold(e.Name.Local, "body") { 49 | return 50 | } 51 | if e, ok := t.(xml.EndElement); ok && strings.EqualFold(e.Name.Local, "head") { 52 | return 53 | } 54 | e, ok := t.(xml.StartElement) 55 | if !ok || !strings.EqualFold(e.Name.Local, "meta") { 56 | continue 57 | } 58 | if attrValue(e.Attr, "name") != "go-import" { 59 | continue 60 | } 61 | if f := strings.Fields(attrValue(e.Attr, "content")); len(f) == 3 { 62 | imports = append(imports, metaImport{ 63 | Prefix: f[0], 64 | VCS: f[1], 65 | RepoRoot: f[2], 66 | }) 67 | } 68 | } 69 | } 70 | 71 | // attrValue returns the attribute value for the case-insensitive key 72 | // `name', or the empty string if nothing is found. 73 | func attrValue(attrs []xml.Attr, name string) string { 74 | for _, a := range attrs { 75 | if strings.EqualFold(a.Name.Local, name) { 76 | return a.Value 77 | } 78 | } 79 | return "" 80 | } 81 | -------------------------------------------------------------------------------- /gbvendor/imports.go: -------------------------------------------------------------------------------- 1 | package vendor 2 | 3 | import ( 4 | "fmt" 5 | "go/parser" 6 | "go/token" 7 | "io" 8 | "net/http" 9 | "os" 10 | "path/filepath" 11 | "strings" 12 | ) 13 | 14 | // ParseImports parses Go packages from a specific root returning a set of import paths. 15 | func ParseImports(root string) (map[string]bool, error) { 16 | pkgs := make(map[string]bool) 17 | 18 | var walkFn = func(path string, info os.FileInfo, err error) error { 19 | if info.IsDir() { 20 | name := info.Name() 21 | if strings.HasPrefix(name, ".") || strings.HasPrefix(name, "_") || name == "testdata" { 22 | return filepath.SkipDir 23 | } 24 | return nil 25 | } 26 | if filepath.Ext(path) != ".go" { // Parse only go source files 27 | return nil 28 | } 29 | 30 | fs := token.NewFileSet() 31 | f, err := parser.ParseFile(fs, path, nil, parser.ImportsOnly) 32 | if err != nil { 33 | return err 34 | } 35 | 36 | for _, s := range f.Imports { 37 | p := strings.Replace(s.Path.Value, "\"", "", -1) 38 | pkgs[p] = true 39 | } 40 | return nil 41 | } 42 | 43 | err := filepath.Walk(root, walkFn) 44 | return pkgs, err 45 | } 46 | 47 | // FetchMetadata fetchs the remote metadata for path. 48 | func FetchMetadata(path string, insecure bool) (rc io.ReadCloser, err error) { 49 | defer func() { 50 | if err != nil { 51 | err = fmt.Errorf("unable to determine remote metadata protocol: %s", err) 52 | } 53 | }() 54 | // try https first 55 | rc, err = fetchMetadata("https", path) 56 | if err == nil { 57 | return 58 | } 59 | // try http if supported 60 | if insecure { 61 | rc, err = fetchMetadata("http", path) 62 | } 63 | return 64 | } 65 | 66 | func fetchMetadata(scheme, path string) (io.ReadCloser, error) { 67 | url := fmt.Sprintf("%s://%s?go-get=1", scheme, path) 68 | switch scheme { 69 | case "https", "http": 70 | resp, err := http.Get(url) 71 | if err != nil { 72 | return nil, fmt.Errorf("failed to access url %q", url) 73 | } 74 | return resp.Body, nil 75 | default: 76 | return nil, fmt.Errorf("unknown remote protocol scheme: %q", scheme) 77 | } 78 | } 79 | 80 | // ParseMetadata fetchs and decodes remote metadata for path. 81 | func ParseMetadata(path string, insecure bool) (string, string, string, error) { 82 | rc, err := FetchMetadata(path, insecure) 83 | if err != nil { 84 | return "", "", "", err 85 | } 86 | defer rc.Close() 87 | 88 | imports, err := parseMetaGoImports(rc) 89 | if err != nil { 90 | return "", "", "", err 91 | } 92 | match := -1 93 | for i, im := range imports { 94 | if !strings.HasPrefix(path, im.Prefix) { 95 | continue 96 | } 97 | if match != -1 { 98 | return "", "", "", fmt.Errorf("multiple meta tags match import path %q", path) 99 | } 100 | match = i 101 | } 102 | if match == -1 { 103 | return "", "", "", fmt.Errorf("go-import metadata not found") 104 | } 105 | return imports[match].Prefix, imports[match].VCS, imports[match].RepoRoot, nil 106 | } 107 | -------------------------------------------------------------------------------- /gbvendor/imports_test.go: -------------------------------------------------------------------------------- 1 | package vendor 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "io" 7 | "os" 8 | "path/filepath" 9 | "reflect" 10 | "testing" 11 | ) 12 | 13 | func TestParseImports(t *testing.T) { 14 | root := filepath.Join(getwd(t), "_testdata", "src") 15 | 16 | got, err := ParseImports(root) 17 | if err != nil { 18 | t.Fatalf("ParseImports(%q): %v", root, err) 19 | } 20 | 21 | want := set("fmt", "github.com/quux/flobble", "github.com/lypo/moopo", "github.com/hoo/wuu") 22 | if !reflect.DeepEqual(got, want) { 23 | t.Fatalf("ParseImports(%q): want: %v, got %v", root, want, got) 24 | } 25 | } 26 | 27 | func TestFetchMetadata(t *testing.T) { 28 | if testing.Short() { 29 | t.Skipf("skipping network tests in -short mode") 30 | } 31 | type testParams struct { 32 | path string 33 | want string 34 | insecure bool 35 | } 36 | tests := []testParams{{ 37 | path: "golang.org/x/tools/cmd/godoc", 38 | want: ` 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | Nothing to see here; move along. 48 | 49 | 50 | `, 51 | }, { 52 | path: "gopkg.in/check.v1", 53 | want: ` 54 | 55 | 56 | 57 | 58 | 59 | 60 | go get gopkg.in/check.v1 61 | 62 | 63 | `, 64 | }} 65 | 66 | for _, tt := range tests { 67 | r, err := FetchMetadata(tt.path, tt.insecure) 68 | if err != nil { 69 | t.Error(err) 70 | continue 71 | } 72 | var buf bytes.Buffer 73 | if _, err := io.Copy(&buf, r); err != nil { 74 | t.Error(err) 75 | r.Close() 76 | continue 77 | } 78 | r.Close() 79 | got := buf.String() 80 | if got != tt.want { 81 | t.Errorf("FetchMetadata(%q): want %q, got %q", tt.path, tt.want, got) 82 | } 83 | } 84 | 85 | // Test for error catch. 86 | errTests := []testParams{{ 87 | path: "any.inaccessible.server/the.project", 88 | want: `unable to determine remote metadata protocol: failed to access url "http://any.inaccessible.server/the.project?go-get=1"`, 89 | insecure: true, 90 | }, { 91 | path: "any.inaccessible.server/the.project", 92 | want: `unable to determine remote metadata protocol: failed to access url "https://any.inaccessible.server/the.project?go-get=1"`, 93 | insecure: false, 94 | }} 95 | 96 | for _, ett := range errTests { 97 | r, err := FetchMetadata(ett.path, ett.insecure) 98 | if err == nil { 99 | t.Errorf("Access to url %q without any error, but the error should be happen.", ett.path) 100 | if r != nil { 101 | r.Close() 102 | } 103 | continue 104 | } 105 | got := err.Error() 106 | if got != ett.want { 107 | t.Errorf("FetchMetadata(%q): want %q, got %q", ett.path, ett.want, got) 108 | } 109 | } 110 | } 111 | 112 | func TestParseMetadata(t *testing.T) { 113 | if testing.Short() { 114 | t.Skipf("skipping network tests in -short mode") 115 | } 116 | tests := []struct { 117 | path string 118 | importpath string 119 | vcs string 120 | reporoot string 121 | insecure bool 122 | err error 123 | }{{ 124 | path: "golang.org/x/tools/cmd/godoc", 125 | importpath: "golang.org/x/tools", 126 | vcs: "git", 127 | reporoot: "https://go.googlesource.com/tools", 128 | }, { 129 | path: "gopkg.in/check.v1", 130 | importpath: "gopkg.in/check.v1", 131 | vcs: "git", 132 | reporoot: "https://gopkg.in/check.v1", 133 | }, { 134 | path: "gopkg.in/mgo.v2/bson", 135 | importpath: "gopkg.in/mgo.v2", 136 | vcs: "git", 137 | reporoot: "https://gopkg.in/mgo.v2", 138 | }, { 139 | path: "speter.net/go/exp", 140 | err: fmt.Errorf("go-import metadata not found"), 141 | }} 142 | 143 | for _, tt := range tests { 144 | importpath, vcs, reporoot, err := ParseMetadata(tt.path, tt.insecure) 145 | if !reflect.DeepEqual(err, tt.err) { 146 | t.Error(err) 147 | continue 148 | } 149 | if importpath != tt.importpath || vcs != tt.vcs || reporoot != tt.reporoot { 150 | t.Errorf("ParseMetadata(%q): want %s %s %s, got %s %s %s ", tt.path, tt.importpath, tt.vcs, tt.reporoot, importpath, vcs, reporoot) 151 | } 152 | } 153 | } 154 | 155 | func getwd(t *testing.T) string { 156 | cwd, err := os.Getwd() 157 | if err != nil { 158 | t.Fatal(err) 159 | } 160 | return cwd 161 | } 162 | -------------------------------------------------------------------------------- /gbvendor/manifest.go: -------------------------------------------------------------------------------- 1 | package vendor 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "fmt" 7 | "io" 8 | "os" 9 | "reflect" 10 | "sort" 11 | ) 12 | 13 | // gb-vendor manifest support 14 | 15 | // Manifest describes the layout of $PROJECT/vendor/manifest. 16 | type Manifest struct { 17 | // Manifest version. Current manifest version is 0. 18 | Version int `json:"version"` 19 | 20 | // Depenencies is a list of vendored dependencies. 21 | Dependencies []Dependency `json:"dependencies"` 22 | } 23 | 24 | // AddDependency adds a Dependency to the current Manifest. 25 | // If the dependency exists already then it returns and error. 26 | func (m *Manifest) AddDependency(dep Dependency) error { 27 | if m.HasImportpath(dep.Importpath) { 28 | return fmt.Errorf("already registered") 29 | } 30 | m.Dependencies = append(m.Dependencies, dep) 31 | return nil 32 | } 33 | 34 | // RemoveDependency removes a Dependency from the current Manifest. 35 | // If the dependency does not exist then it returns an error. 36 | func (m *Manifest) RemoveDependency(dep Dependency) error { 37 | for i, d := range m.Dependencies { 38 | if reflect.DeepEqual(d, dep) { 39 | m.Dependencies = append(m.Dependencies[:i], m.Dependencies[i+1:]...) 40 | return nil 41 | } 42 | } 43 | return fmt.Errorf("dependency does not exist") 44 | } 45 | 46 | // HasImportpath reports whether the Manifest contains the import path. 47 | func (m *Manifest) HasImportpath(path string) bool { 48 | _, err := m.GetDependencyForImportpath(path) 49 | return err == nil 50 | } 51 | 52 | // GetDependencyForRepository return a dependency for specified URL 53 | // If the dependency does not exist it returns an error 54 | func (m *Manifest) GetDependencyForImportpath(path string) (Dependency, error) { 55 | for _, d := range m.Dependencies { 56 | if d.Importpath == path { 57 | return d, nil 58 | } 59 | } 60 | return Dependency{}, fmt.Errorf("dependency for %s does not exist", path) 61 | } 62 | 63 | // Dependency describes one vendored import path of code 64 | // A Dependency is an Importpath sources from a Respository 65 | // at Revision from Path. 66 | type Dependency struct { 67 | // Importpath is name by which this dependency is known. 68 | Importpath string `json:"importpath"` 69 | 70 | // Repository is the remote DVCS location that this 71 | // dependency was fetched from. 72 | Repository string `json:"repository"` 73 | 74 | // Revision is the revision that describes the dependency's 75 | // remote revision. 76 | Revision string `json:"revision"` 77 | 78 | // Branch is the branch the Revision was located on. 79 | // Can be blank if not needed. 80 | Branch string `json:"branch"` 81 | 82 | // Path is the path inside the Repository where the 83 | // dependency was fetched from. 84 | Path string `json:"path,omitempty"` 85 | } 86 | 87 | // WriteManifest writes a Manifest to the path. If the manifest does 88 | // not exist, it is created. If it does exist, it will be overwritten. 89 | // If the manifest file is empty (0 dependencies) it will be deleted. 90 | // The dependencies will be ordered by import path to reduce churn when making 91 | // changes. 92 | // TODO(dfc) write to temporary file and move atomically to avoid 93 | // destroying a working vendorfile. 94 | func WriteManifest(path string, m *Manifest) error { 95 | if len(m.Dependencies) == 0 { 96 | err := os.Remove(path) 97 | if !os.IsNotExist(err) { 98 | return err 99 | } 100 | return nil 101 | } 102 | 103 | f, err := os.Create(path) 104 | if err != nil { 105 | return err 106 | } 107 | if err := writeManifest(f, m); err != nil { 108 | f.Close() 109 | return err 110 | } 111 | return f.Close() 112 | } 113 | 114 | func writeManifest(w io.Writer, m *Manifest) error { 115 | sort.Sort(byImportpath(m.Dependencies)) 116 | buf, err := json.MarshalIndent(m, "", "\t") 117 | if err != nil { 118 | return err 119 | } 120 | _, err = io.Copy(w, bytes.NewReader(buf)) 121 | return err 122 | } 123 | 124 | // ReadManifest reads a Manifest from path. If the Manifest is not 125 | // found, a blank Manifest will be returned. 126 | func ReadManifest(path string) (*Manifest, error) { 127 | f, err := os.Open(path) 128 | if err != nil { 129 | if os.IsNotExist(err) { 130 | return new(Manifest), nil 131 | } 132 | return nil, err 133 | } 134 | defer f.Close() 135 | return readManifest(f) 136 | } 137 | 138 | func readManifest(r io.Reader) (*Manifest, error) { 139 | var m Manifest 140 | d := json.NewDecoder(r) 141 | err := d.Decode(&m) 142 | return &m, err 143 | } 144 | 145 | type byImportpath []Dependency 146 | 147 | func (s byImportpath) Len() int { return len(s) } 148 | func (s byImportpath) Less(i, j int) bool { return s[i].Importpath < s[j].Importpath } 149 | func (s byImportpath) Swap(i, j int) { s[i], s[j] = s[j], s[i] } 150 | -------------------------------------------------------------------------------- /gbvendor/manifest_test.go: -------------------------------------------------------------------------------- 1 | package vendor 2 | 3 | import ( 4 | "bytes" 5 | "os" 6 | "path/filepath" 7 | "testing" 8 | 9 | "github.com/constabulary/gb/fileutils" 10 | ) 11 | 12 | func mktemp(t *testing.T) string { 13 | s, err := mktmp() 14 | if err != nil { 15 | t.Fatal(err) 16 | } 17 | return s 18 | } 19 | 20 | func assertNotExists(t *testing.T, path string) { 21 | _, err := os.Stat(path) 22 | if err == nil || !os.IsNotExist(err) { 23 | t.Fatalf("expected %q to be not found, got %v", path, err) 24 | } 25 | } 26 | 27 | func assertExists(t *testing.T, path string) { 28 | _, err := os.Stat(path) 29 | if err != nil { 30 | t.Fatalf("expected %q to be found, got %v", path, err) 31 | } 32 | } 33 | 34 | func TestManifest(t *testing.T) { 35 | root := mktemp(t) 36 | defer fileutils.RemoveAll(root) 37 | 38 | mf := filepath.Join(root, "vendor") 39 | 40 | // check that reading an non existant manifest 41 | // does not return an error 42 | m, err := ReadManifest(mf) 43 | if err != nil { 44 | t.Fatalf("reading a non existant manifest should not fail: %v", err) 45 | } 46 | 47 | // check that no manifest file was created 48 | assertNotExists(t, mf) 49 | 50 | // add a dep 51 | m.Dependencies = append(m.Dependencies, Dependency{ 52 | Importpath: "github.com/foo/bar/baz", 53 | Repository: "https://github.com/foo/bar", 54 | Revision: "cafebad", 55 | Branch: "master", 56 | Path: "/baz", 57 | }) 58 | 59 | // write it back 60 | if err := WriteManifest(mf, m); err != nil { 61 | t.Fatalf("WriteManifest failed: %v", err) 62 | } 63 | 64 | // check the manifest was written 65 | assertExists(t, mf) 66 | 67 | // remove it 68 | m.Dependencies = nil 69 | if err := WriteManifest(mf, m); err != nil { 70 | t.Fatalf("WriteManifest failed: %v", err) 71 | } 72 | 73 | // check that no manifest file was removed 74 | assertNotExists(t, mf) 75 | } 76 | 77 | func TestEmptyPathIsNotWritten(t *testing.T) { 78 | m := Manifest{ 79 | Version: 0, 80 | Dependencies: []Dependency{{ 81 | Importpath: "github.com/foo/bar", 82 | Repository: "https://github.com/foo/bar", 83 | Revision: "abcdef", 84 | Branch: "master", 85 | }}, 86 | } 87 | var buf bytes.Buffer 88 | if err := writeManifest(&buf, &m); err != nil { 89 | t.Fatal(err) 90 | } 91 | want := `{ 92 | "version": 0, 93 | "dependencies": [ 94 | { 95 | "importpath": "github.com/foo/bar", 96 | "repository": "https://github.com/foo/bar", 97 | "revision": "abcdef", 98 | "branch": "master" 99 | } 100 | ] 101 | }` 102 | got := buf.String() 103 | if want != got { 104 | t.Fatalf("want: %s, got %s", want, got) 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /gbvendor/repo.go: -------------------------------------------------------------------------------- 1 | package vendor 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "io" 7 | "io/ioutil" 8 | "log" 9 | "net/url" 10 | "os" 11 | "os/exec" 12 | "path/filepath" 13 | "regexp" 14 | "strings" 15 | 16 | "github.com/constabulary/gb/fileutils" 17 | ) 18 | 19 | // RemoteRepo describes a remote dvcs repository. 20 | type RemoteRepo interface { 21 | 22 | // Checkout checks out a specific branch, tag, or revision. 23 | // The interpretation of these three values is impementation 24 | // specific. 25 | Checkout(branch, tag, revision string) (WorkingCopy, error) 26 | 27 | // URL returns the URL the clone was taken from. It should 28 | // only be called after Clone. 29 | URL() string 30 | } 31 | 32 | // WorkingCopy represents a local copy of a remote dvcs repository. 33 | type WorkingCopy interface { 34 | 35 | // Dir is the root of this working copy. 36 | Dir() string 37 | 38 | // Revision returns the revision of this working copy. 39 | Revision() (string, error) 40 | 41 | // Branch returns the branch to which this working copy belongs. 42 | Branch() (string, error) 43 | 44 | // Destroy removes the working copy. 45 | Destroy() error 46 | } 47 | 48 | var ( 49 | ghregex = regexp.MustCompile(`^(?Pgithub\.com/([A-Za-z0-9_.\-]+/[A-Za-z0-9_.\-]+))(/[A-Za-z0-9_.\-]+)*$`) 50 | bbregex = regexp.MustCompile(`^(?Pbitbucket\.org/(?P[A-Za-z0-9_.\-]+/[A-Za-z0-9_.\-]+))(/[A-Za-z0-9_.\-]+)*$`) 51 | lpregex = regexp.MustCompile(`^launchpad.net/([A-Za-z0-9-._]+)(/[A-Za-z0-9-._]+)?(/.+)?`) 52 | gcregex = regexp.MustCompile(`^(?Pcode\.google\.com/[pr]/(?P[a-z0-9\-]+)(\.(?P[a-z0-9\-]+))?)(/[A-Za-z0-9_.\-]+)*$`) 53 | genericre = regexp.MustCompile(`^(?P(?P([a-z0-9.\-]+\.)+[a-z0-9.\-]+(:[0-9]+)?/[A-Za-z0-9_.\-/~]*?)\.(?Pbzr|git|hg|svn))([/A-Za-z0-9_.\-~]+)*$`) 54 | ) 55 | 56 | // DeduceRemoteRepo takes a potential import path and returns a RemoteRepo 57 | // representing the remote location of the source of an import path. 58 | // Remote repositories can be bare import paths, or urls including a checkout scheme. 59 | // If deduction would cause traversal of an insecure host, a message will be 60 | // printed and the travelsal path will be ignored. 61 | func DeduceRemoteRepo(path string, insecure bool, repository ...string) (RemoteRepo, string, error) { 62 | u, err := url.Parse(path) 63 | if err != nil { 64 | return nil, "", fmt.Errorf("%q is not a valid import path", path) 65 | } 66 | 67 | var schemes []string 68 | if u.Scheme != "" { 69 | schemes = append(schemes, u.Scheme) 70 | } 71 | 72 | path = u.Host + u.Path 73 | if !regexp.MustCompile(`^([A-Za-z0-9-]+)(\.[A-Za-z0-9-]+)+(/[A-Za-z0-9-_.~]+)*$`).MatchString(path) { 74 | return nil, "", fmt.Errorf("%q is not a valid import path", path) 75 | } 76 | 77 | switch { 78 | case ghregex.MatchString(path): 79 | v := ghregex.FindStringSubmatch(path) 80 | url := &url.URL{ 81 | Host: "github.com", 82 | Path: v[2], 83 | } 84 | repo, err := Gitrepo(url, insecure, schemes...) 85 | return repo, v[0][len(v[1]):], err 86 | case bbregex.MatchString(path): 87 | v := bbregex.FindStringSubmatch(path) 88 | url := &url.URL{ 89 | Host: "bitbucket.org", 90 | Path: v[2], 91 | } 92 | repo, err := Gitrepo(url, insecure, schemes...) 93 | if err == nil { 94 | return repo, v[0][len(v[1]):], nil 95 | } 96 | repo, err = Hgrepo(url, insecure) 97 | if err == nil { 98 | return repo, v[0][len(v[1]):], nil 99 | } 100 | return nil, "", fmt.Errorf("unknown repository type") 101 | case gcregex.MatchString(path): 102 | v := gcregex.FindStringSubmatch(path) 103 | url := &url.URL{ 104 | Host: "code.google.com", 105 | Path: "p/" + v[2], 106 | } 107 | repo, err := Hgrepo(url, insecure, schemes...) 108 | if err == nil { 109 | return repo, v[0][len(v[1]):], nil 110 | } 111 | repo, err = Gitrepo(url, insecure, schemes...) 112 | if err == nil { 113 | return repo, v[0][len(v[1]):], nil 114 | } 115 | return nil, "", fmt.Errorf("unknown repository type") 116 | case lpregex.MatchString(path): 117 | v := lpregex.FindStringSubmatch(path) 118 | v = append(v, "", "") 119 | if v[2] == "" { 120 | // launchpad.net/project" 121 | repo, err := Bzrrepo(fmt.Sprintf("https://launchpad.net/%v", v[1])) 122 | return repo, "", err 123 | } 124 | // launchpad.net/project/series" 125 | repo, err := Bzrrepo(fmt.Sprintf("https://launchpad.net/%s/%s", v[1], v[2])) 126 | return repo, v[3], err 127 | } 128 | 129 | // try the general syntax 130 | if genericre.MatchString(path) { 131 | v := genericre.FindStringSubmatch(path) 132 | switch v[5] { 133 | case "git": 134 | x := strings.SplitN(v[1], "/", 2) 135 | url := &url.URL{ 136 | Host: x[0], 137 | Path: x[1], 138 | } 139 | repo, err := Gitrepo(url, insecure, schemes...) 140 | return repo, v[6], err 141 | case "hg": 142 | x := strings.SplitN(v[1], "/", 2) 143 | url := &url.URL{ 144 | Host: x[0], 145 | Path: x[1], 146 | } 147 | repo, err := Hgrepo(url, insecure, schemes...) 148 | return repo, v[6], err 149 | case "bzr": 150 | repo, err := Bzrrepo("https://" + v[1]) 151 | return repo, v[6], err 152 | default: 153 | return nil, "", fmt.Errorf("unknown repository type: %q", v[5]) 154 | 155 | } 156 | } 157 | 158 | var importpath, vcs, reporoot string 159 | 160 | // golang.org 被墙 161 | golangX := "golang.org/x" 162 | if len(repository) > 0 { 163 | importpath, vcs, reporoot = path, "git", repository[0] 164 | } else if strings.Contains(path, golangX) { 165 | importpath, vcs, reporoot = path, "git", "https://github.com/golang/" 166 | 167 | githubPath := importpath[len(golangX)+1:] 168 | pos := strings.Index(githubPath, "/") 169 | if pos == -1 { 170 | reporoot += githubPath 171 | } else { 172 | reporoot += githubPath[:pos] 173 | importpath = golangX + "/" + githubPath[:pos] 174 | } 175 | } else { 176 | // no idea, try to resolve as a vanity import 177 | importpath, vcs, reporoot, err = ParseMetadata(path, insecure) 178 | if err != nil { 179 | return nil, "", err 180 | } 181 | } 182 | 183 | u, err = url.Parse(reporoot) 184 | if err != nil { 185 | return nil, "", err 186 | } 187 | extra := path[len(importpath):] 188 | switch vcs { 189 | case "git": 190 | u.Path = u.Path[1:] 191 | repo, err := Gitrepo(u, insecure, u.Scheme) 192 | return repo, extra, err 193 | case "hg": 194 | u.Path = u.Path[1:] 195 | repo, err := Hgrepo(u, insecure, u.Scheme) 196 | return repo, extra, err 197 | case "bzr": 198 | repo, err := Bzrrepo(reporoot) 199 | return repo, extra, err 200 | default: 201 | return nil, "", fmt.Errorf("unknown repository type: %q", vcs) 202 | } 203 | } 204 | 205 | // Gitrepo returns a RemoteRepo representing a remote git repository. 206 | func Gitrepo(url *url.URL, insecure bool, schemes ...string) (RemoteRepo, error) { 207 | if len(schemes) == 0 { 208 | schemes = []string{"https", "git", "ssh", "http"} 209 | } 210 | u, err := probeGitUrl(url, insecure, schemes) 211 | if err != nil { 212 | return nil, err 213 | } 214 | return &gitrepo{ 215 | url: u, 216 | }, nil 217 | } 218 | 219 | func probeGitUrl(u *url.URL, insecure bool, schemes []string) (string, error) { 220 | git := func(url *url.URL) error { 221 | out, err := run("git", "ls-remote", url.String(), "HEAD") 222 | if err != nil { 223 | return err 224 | } 225 | 226 | if !bytes.Contains(out, []byte("HEAD")) { 227 | return fmt.Errorf("not a git repo") 228 | } 229 | return nil 230 | } 231 | return probe(git, u, insecure, schemes...) 232 | } 233 | 234 | func probeHgUrl(u *url.URL, insecure bool, schemes []string) (string, error) { 235 | hg := func(url *url.URL) error { 236 | _, err := run("hg", "identify", url.String()) 237 | return err 238 | } 239 | return probe(hg, u, insecure, schemes...) 240 | } 241 | 242 | func probeBzrUrl(u string) error { 243 | bzr := func(url *url.URL) error { 244 | _, err := run("bzr", "info", url.String()) 245 | return err 246 | } 247 | url, err := url.Parse(u) 248 | if err != nil { 249 | return err 250 | } 251 | _, err = probe(bzr, url, false, "https") 252 | return err 253 | } 254 | 255 | // probe calls the supplied vcs function to probe a variety of url constructions. 256 | // If vcs returns non nil, it is assumed that the url is not a valid repo. 257 | func probe(vcs func(*url.URL) error, url *url.URL, insecure bool, schemes ...string) (string, error) { 258 | var unsuccessful []string 259 | for _, scheme := range schemes { 260 | 261 | // make copy of url and apply scheme 262 | url := *url 263 | url.Scheme = scheme 264 | 265 | switch url.Scheme { 266 | case "https", "ssh": 267 | if err := vcs(&url); err == nil { 268 | return url.String(), nil 269 | } 270 | case "http", "git": 271 | if !insecure { 272 | log.Printf("skipping insecure protocol: %s", url.String()) 273 | continue 274 | } 275 | if err := vcs(&url); err == nil { 276 | return url.String(), nil 277 | } 278 | default: 279 | return "", fmt.Errorf("unsupported scheme: %v", url.Scheme) 280 | } 281 | unsuccessful = append(unsuccessful, url.String()) 282 | } 283 | return "", fmt.Errorf("vcs probe failed, tried: %s", strings.Join(unsuccessful, ",")) 284 | } 285 | 286 | // gitrepo is a git RemoteRepo. 287 | type gitrepo struct { 288 | 289 | // remote repository url, see man 1 git-clone 290 | url string 291 | } 292 | 293 | func (g *gitrepo) URL() string { 294 | return g.url 295 | } 296 | 297 | // Checkout fetchs the remote branch, tag, or revision. If the branch is blank, 298 | // then the default remote branch will be used. If the branch is "HEAD" and 299 | // revision is empty, an impossible update is assumed. 300 | func (g *gitrepo) Checkout(branch, tag, revision string) (WorkingCopy, error) { 301 | if branch == "HEAD" && revision == "" { 302 | return nil, fmt.Errorf("cannot update %q as it has been previously fetched with -tag or -revision. Please use gvt delete then fetch again.", g.url) 303 | } 304 | if !atMostOne(tag, revision) { 305 | return nil, fmt.Errorf("only one of tag or revision may be supplied") 306 | } 307 | if !atMostOne(branch, tag) { 308 | return nil, fmt.Errorf("only one of branch or tag may be supplied") 309 | } 310 | dir, err := mktmp() 311 | if err != nil { 312 | return nil, err 313 | } 314 | wc := workingcopy{ 315 | path: dir, 316 | } 317 | 318 | quiet := false 319 | args := []string{ 320 | "clone", 321 | "-q", // silence progress report to stderr 322 | g.url, 323 | dir, 324 | } 325 | if branch != "" && branch != "HEAD" { 326 | args = append(args, "--branch", branch, "--single-branch") 327 | } 328 | if tag != "" { 329 | quiet = true // git REALLY wants to tell you how awesome 'detached HEAD' is... 330 | args = append(args, "--branch", tag, "--single-branch") 331 | args = append(args, "--depth", "1") 332 | } 333 | if revision == "" { 334 | args = append(args, "--depth", "1") 335 | } 336 | 337 | if quiet { 338 | err = runQuiet("git", args...) 339 | } else { 340 | _, err = run("git", args...) 341 | } 342 | if err != nil { 343 | wc.Destroy() 344 | return nil, err 345 | } 346 | 347 | if revision != "" { 348 | if err := runOutPath(os.Stderr, dir, "git", "checkout", "-q", revision); err != nil { 349 | wc.Destroy() 350 | return nil, err 351 | } 352 | } 353 | 354 | return &GitClone{wc}, nil 355 | } 356 | 357 | type workingcopy struct { 358 | path string 359 | } 360 | 361 | func (w workingcopy) Dir() string { return w.path } 362 | 363 | func (w workingcopy) Destroy() error { 364 | return fileutils.RemoveAll(w.path) 365 | } 366 | 367 | // GitClone is a git WorkingCopy. 368 | type GitClone struct { 369 | workingcopy 370 | } 371 | 372 | func (g *GitClone) Revision() (string, error) { 373 | rev, err := runPath(g.path, "git", "rev-parse", "HEAD") 374 | return strings.TrimSpace(string(rev)), err 375 | } 376 | 377 | func (g *GitClone) Branch() (string, error) { 378 | rev, err := runPath(g.path, "git", "rev-parse", "--abbrev-ref", "HEAD") 379 | return strings.TrimSpace(string(rev)), err 380 | } 381 | 382 | // Hgrepo returns a RemoteRepo representing a remote git repository. 383 | func Hgrepo(u *url.URL, insecure bool, schemes ...string) (RemoteRepo, error) { 384 | if len(schemes) == 0 { 385 | schemes = []string{"https", "http"} 386 | } 387 | url, err := probeHgUrl(u, insecure, schemes) 388 | if err != nil { 389 | return nil, err 390 | } 391 | return &hgrepo{ 392 | url: url, 393 | }, nil 394 | } 395 | 396 | // hgrepo is a Mercurial repo. 397 | type hgrepo struct { 398 | 399 | // remote repository url, see man 1 hg 400 | url string 401 | } 402 | 403 | func (h *hgrepo) URL() string { return h.url } 404 | 405 | func (h *hgrepo) Checkout(branch, tag, revision string) (WorkingCopy, error) { 406 | if !atMostOne(tag, revision) { 407 | return nil, fmt.Errorf("only one of tag or revision may be supplied") 408 | } 409 | dir, err := mktmp() 410 | if err != nil { 411 | return nil, err 412 | } 413 | args := []string{ 414 | "clone", 415 | h.url, 416 | dir, 417 | "--noninteractive", 418 | } 419 | 420 | if branch != "" { 421 | args = append(args, "--branch", branch) 422 | } 423 | if err := runOut(os.Stderr, "hg", args...); err != nil { 424 | fileutils.RemoveAll(dir) 425 | return nil, err 426 | } 427 | if revision != "" { 428 | if err := runOut(os.Stderr, "hg", "--cwd", dir, "update", "-r", revision); err != nil { 429 | fileutils.RemoveAll(dir) 430 | return nil, err 431 | } 432 | } 433 | 434 | return &HgClone{ 435 | workingcopy{ 436 | path: dir, 437 | }, 438 | }, nil 439 | } 440 | 441 | // HgClone is a mercurial WorkingCopy. 442 | type HgClone struct { 443 | workingcopy 444 | } 445 | 446 | func (h *HgClone) Revision() (string, error) { 447 | rev, err := run("hg", "--cwd", h.path, "id", "-i") 448 | return strings.TrimSpace(string(rev)), err 449 | } 450 | 451 | func (h *HgClone) Branch() (string, error) { 452 | rev, err := run("hg", "--cwd", h.path, "branch") 453 | return strings.TrimSpace(string(rev)), err 454 | } 455 | 456 | // Bzrrepo returns a RemoteRepo representing a remote bzr repository. 457 | func Bzrrepo(url string) (RemoteRepo, error) { 458 | if err := probeBzrUrl(url); err != nil { 459 | return nil, err 460 | } 461 | return &bzrrepo{ 462 | url: url, 463 | }, nil 464 | } 465 | 466 | // bzrrepo is a bzr RemoteRepo. 467 | type bzrrepo struct { 468 | 469 | // remote repository url 470 | url string 471 | } 472 | 473 | func (b *bzrrepo) URL() string { 474 | return b.url 475 | } 476 | 477 | func (b *bzrrepo) Checkout(branch, tag, revision string) (WorkingCopy, error) { 478 | if !atMostOne(tag, revision) { 479 | return nil, fmt.Errorf("only one of tag or revision may be supplied") 480 | } 481 | dir, err := mktmp() 482 | if err != nil { 483 | return nil, err 484 | } 485 | wc := filepath.Join(dir, "wc") 486 | if err := runOut(os.Stderr, "bzr", "branch", b.url, wc); err != nil { 487 | fileutils.RemoveAll(dir) 488 | return nil, err 489 | } 490 | 491 | return &BzrClone{ 492 | workingcopy{ 493 | path: wc, 494 | }, 495 | }, nil 496 | } 497 | 498 | // BzrClone is a bazaar WorkingCopy. 499 | type BzrClone struct { 500 | workingcopy 501 | } 502 | 503 | func (b *BzrClone) Revision() (string, error) { 504 | return "1", nil 505 | } 506 | 507 | func (b *BzrClone) Branch() (string, error) { 508 | return "master", nil 509 | } 510 | 511 | func (b *BzrClone) Destroy() error { 512 | if err := (workingcopy{b.path}).Destroy(); err != nil { 513 | return err 514 | } 515 | parent := filepath.Dir(b.path) 516 | return os.Remove(parent) 517 | } 518 | 519 | func cleanPath(path string) error { 520 | if files, _ := ioutil.ReadDir(path); len(files) > 0 || filepath.Base(path) == "vendor" { 521 | return nil 522 | } 523 | parent := filepath.Dir(path) 524 | if err := fileutils.RemoveAll(path); err != nil { 525 | return err 526 | } 527 | return cleanPath(parent) 528 | } 529 | 530 | func mktmp() (string, error) { 531 | return ioutil.TempDir("", "gvt-") 532 | } 533 | 534 | func run(c string, args ...string) ([]byte, error) { 535 | var buf bytes.Buffer 536 | err := runOut(&buf, c, args...) 537 | return buf.Bytes(), err 538 | } 539 | 540 | func runOut(w io.Writer, c string, args ...string) error { 541 | cmd := exec.Command(c, args...) 542 | cmd.Stdin = nil 543 | cmd.Stdout = w 544 | cmd.Stderr = os.Stderr 545 | return cmd.Run() 546 | } 547 | 548 | func runQuiet(c string, args ...string) error { 549 | cmd := exec.Command(c, args...) 550 | cmd.Stdin = nil 551 | cmd.Stdout = nil 552 | cmd.Stderr = nil 553 | return cmd.Run() 554 | } 555 | 556 | func runPath(path string, c string, args ...string) ([]byte, error) { 557 | var buf bytes.Buffer 558 | err := runOutPath(&buf, path, c, args...) 559 | return buf.Bytes(), err 560 | } 561 | 562 | func runOutPath(w io.Writer, path string, c string, args ...string) error { 563 | cmd := exec.Command(c, args...) 564 | cmd.Dir = path 565 | cmd.Stdin = nil 566 | cmd.Stdout = w 567 | cmd.Stderr = os.Stderr 568 | return cmd.Run() 569 | } 570 | 571 | // atMostOne returns true if no more than one string supplied is not empty. 572 | func atMostOne(args ...string) bool { 573 | var c int 574 | for _, arg := range args { 575 | if arg != "" { 576 | c++ 577 | } 578 | } 579 | return c < 2 580 | } 581 | 582 | // oneof returns the first non empty string 583 | func oneOf(args ...string) string { 584 | for _, arg := range args { 585 | if arg != "" { 586 | return arg 587 | } 588 | } 589 | return "" 590 | } 591 | -------------------------------------------------------------------------------- /gbvendor/repo_test.go: -------------------------------------------------------------------------------- 1 | package vendor 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | "testing" 7 | ) 8 | 9 | func TestDeduceRemoteRepo(t *testing.T) { 10 | if testing.Short() { 11 | t.Skipf("skipping network tests in -short mode") 12 | } 13 | tests := []struct { 14 | path string 15 | want RemoteRepo 16 | extra string 17 | err error 18 | insecure bool 19 | }{{ 20 | path: "", 21 | err: fmt.Errorf(`"" is not a valid import path`), 22 | }, { 23 | path: "corporate", 24 | err: fmt.Errorf(`"corporate" is not a valid import path`), 25 | }, { 26 | path: "github.com/cznic/b", 27 | want: &gitrepo{ 28 | url: "https://github.com/cznic/b", 29 | }, 30 | }, { 31 | path: "github.com/pkg/sftp", 32 | want: &gitrepo{ 33 | url: "https://github.com/pkg/sftp", 34 | }, 35 | }, { 36 | path: "github.com/pkg/sftp/examples/gsftp", 37 | want: &gitrepo{ 38 | url: "https://github.com/pkg/sftp", 39 | }, 40 | extra: "/examples/gsftp", 41 | }, { 42 | path: "github.com/coreos/go-etcd", 43 | want: &gitrepo{ 44 | url: "https://github.com/coreos/go-etcd", 45 | }, 46 | }, { 47 | path: "bitbucket.org/davecheney/gitrepo/cmd/main", 48 | want: &gitrepo{ 49 | url: "https://bitbucket.org/davecheney/gitrepo", 50 | }, 51 | extra: "/cmd/main", 52 | }, { 53 | path: "bitbucket.org/davecheney/hgrepo/cmd/main", 54 | want: &hgrepo{ 55 | url: "https://bitbucket.org/davecheney/hgrepo", 56 | }, 57 | extra: "/cmd/main", 58 | }, { 59 | path: "code.google.com/p/goauth2/oauth", 60 | want: &hgrepo{ 61 | url: "https://code.google.com/p/goauth2", 62 | }, 63 | extra: "/oauth", 64 | }, { 65 | path: "code.google.com/p/gami", 66 | want: &gitrepo{ 67 | url: "https://code.google.com/p/gami", 68 | }, 69 | }, { 70 | path: "git.eclipse.org/gitroot/paho/org.eclipse.paho.mqtt.golang.git", 71 | want: &gitrepo{ 72 | url: "https://git.eclipse.org/gitroot/paho/org.eclipse.paho.mqtt.golang.git", 73 | }, 74 | }, { 75 | path: "git.apache.org/thrift.git/lib/go/thrift", 76 | want: &gitrepo{ 77 | url: "https://git.apache.org/thrift.git", 78 | }, 79 | extra: "/lib/go/thrift", 80 | }, { 81 | path: "gopkg.in/check.v1", 82 | want: &gitrepo{ 83 | url: "https://gopkg.in/check.v1", 84 | }, 85 | extra: "", 86 | }, { 87 | path: "goji.io", 88 | want: &gitrepo{ 89 | url: "https://github.com/goji/goji", 90 | }, 91 | extra: "", 92 | }, { 93 | path: "golang.org/x/tools/go/vcs", 94 | want: &gitrepo{ 95 | url: "https://go.googlesource.com/tools", 96 | }, 97 | extra: "/go/vcs", 98 | }, { 99 | path: "labix.org/v2/mgo", 100 | want: &bzrrepo{ 101 | url: "https://launchpad.net/mgo/v2", 102 | }, 103 | insecure: true, 104 | }, { 105 | path: "launchpad.net/gnuflag", 106 | want: &bzrrepo{ 107 | url: "https://launchpad.net/gnuflag", 108 | }, 109 | }, { 110 | path: "https://github.com/pkg/sftp", 111 | want: &gitrepo{ 112 | url: "https://github.com/pkg/sftp", 113 | }, 114 | }, { 115 | path: "git://github.com/pkg/sftp", 116 | want: &gitrepo{ 117 | url: "git://github.com/pkg/sftp", 118 | }, 119 | insecure: true, 120 | }, { 121 | path: "code.google.com/p/google-api-go-client/bigquery/v2", 122 | want: &hgrepo{ 123 | url: "https://code.google.com/p/google-api-go-client", 124 | }, 125 | extra: "/bigquery/v2", 126 | }, { 127 | path: "code.google.com/p/go-sqlite/go1/sqlite3", 128 | want: &hgrepo{ 129 | url: "https://code.google.com/p/go-sqlite", 130 | }, 131 | extra: "/go1/sqlite3", 132 | }} 133 | 134 | for _, tt := range tests { 135 | t.Logf("DeduceRemoteRepo(%q, %v)", tt.path, tt.insecure) 136 | got, extra, err := DeduceRemoteRepo(tt.path, tt.insecure) 137 | if !reflect.DeepEqual(err, tt.err) { 138 | t.Errorf("DeduceRemoteRepo(%q): want err: %v, got err: %v", tt.path, tt.err, err) 139 | continue 140 | } 141 | if !reflect.DeepEqual(got, tt.want) || extra != tt.extra { 142 | t.Errorf("DeduceRemoteRepo(%q): want %#v, %v, got %#v, %v", tt.path, tt.want, tt.extra, got, extra) 143 | } 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /gbvendor/stringset.go: -------------------------------------------------------------------------------- 1 | package vendor 2 | 3 | // union returns the union of a and b. 4 | func union(a, b map[string]bool) map[string]bool { 5 | r := make(map[string]bool) 6 | for k := range a { 7 | r[k] = true 8 | } 9 | for k := range b { 10 | r[k] = true 11 | } 12 | return r 13 | } 14 | 15 | // intersection returns the intersection of a and b. 16 | func intersection(a, b map[string]bool) map[string]bool { 17 | r := make(map[string]bool) 18 | for k := range a { 19 | if b[k] { 20 | r[k] = true 21 | } 22 | } 23 | return r 24 | } 25 | 26 | // difference returns the symetric difference of a and b. 27 | func difference(a, b map[string]bool) map[string]bool { 28 | r := make(map[string]bool) 29 | for k := range a { 30 | if !b[k] { 31 | r[k] = true 32 | } 33 | } 34 | for k := range b { 35 | if !a[k] { 36 | r[k] = true 37 | } 38 | } 39 | return r 40 | } 41 | 42 | // contains returns true if a contains all the elements in s. 43 | func contains(a map[string]bool, s ...string) bool { 44 | var r bool 45 | for _, e := range s { 46 | if !a[e] { 47 | return false 48 | } 49 | r = true 50 | } 51 | return r 52 | } 53 | -------------------------------------------------------------------------------- /gbvendor/stringset_test.go: -------------------------------------------------------------------------------- 1 | package vendor 2 | 3 | import "testing" 4 | import "reflect" 5 | 6 | func set(args ...string) map[string]bool { 7 | r := make(map[string]bool) 8 | for _, a := range args { 9 | r[a] = true 10 | } 11 | return r 12 | } 13 | 14 | func TestUnion(t *testing.T) { 15 | tests := []struct { 16 | a, b map[string]bool 17 | want map[string]bool 18 | }{{ 19 | a: nil, b: nil, 20 | want: set(), 21 | }, { 22 | a: nil, b: set("b"), 23 | want: set("b"), 24 | }, { 25 | a: set("a"), b: nil, 26 | want: set("a"), 27 | }, { 28 | a: set("a"), b: set("b"), 29 | want: set("b", "a"), 30 | }, { 31 | a: set("c"), b: set("c"), 32 | want: set("c"), 33 | }} 34 | 35 | for _, tt := range tests { 36 | got := union(tt.a, tt.b) 37 | if !reflect.DeepEqual(tt.want, got) { 38 | t.Errorf("union(%v, %v) want: %v, got %v", tt.a, tt.b, tt.want, got) 39 | } 40 | } 41 | } 42 | 43 | func TestIntersection(t *testing.T) { 44 | tests := []struct { 45 | a, b map[string]bool 46 | want map[string]bool 47 | }{{ 48 | a: nil, b: nil, 49 | want: set(), 50 | }, { 51 | a: nil, b: set("b"), 52 | want: set(), 53 | }, { 54 | a: set("a"), b: nil, 55 | want: set(), 56 | }, { 57 | a: set("a"), b: set("b"), 58 | want: set(), 59 | }, { 60 | a: set("c"), b: set("c"), 61 | want: set("c"), 62 | }, { 63 | a: set("a", "c"), b: set("b", "c"), 64 | want: set("c"), 65 | }} 66 | 67 | for _, tt := range tests { 68 | got := intersection(tt.a, tt.b) 69 | if !reflect.DeepEqual(tt.want, got) { 70 | t.Errorf("intersection(%v, %v) want: %v, got %v", tt.a, tt.b, tt.want, got) 71 | } 72 | } 73 | } 74 | 75 | func TestDifference(t *testing.T) { 76 | tests := []struct { 77 | a, b map[string]bool 78 | want map[string]bool 79 | }{{ 80 | a: nil, b: nil, 81 | want: set(), 82 | }, { 83 | a: nil, b: set("b"), 84 | want: set("b"), 85 | }, { 86 | a: set("a"), b: nil, 87 | want: set("a"), 88 | }, { 89 | a: set("a"), b: set("b"), 90 | want: set("a", "b"), 91 | }, { 92 | a: set("c"), b: set("c"), 93 | want: set(), 94 | }, { 95 | a: set("a", "c"), b: set("b", "c"), 96 | want: set("a", "b"), 97 | }} 98 | 99 | for _, tt := range tests { 100 | got := difference(tt.a, tt.b) 101 | if !reflect.DeepEqual(tt.want, got) { 102 | t.Errorf("difference(%v, %v) want: %v, got %v", tt.a, tt.b, tt.want, got) 103 | } 104 | } 105 | } 106 | 107 | func TestContains(t *testing.T) { 108 | tests := []struct { 109 | a map[string]bool 110 | s []string 111 | want bool 112 | }{{ 113 | a: nil, s: nil, 114 | want: false, 115 | }, { 116 | a: set("a"), s: nil, 117 | want: false, 118 | }, { 119 | a: set("a"), s: []string{"a"}, 120 | want: true, 121 | }, { 122 | a: set("a"), s: []string{"b"}, 123 | want: false, 124 | }, { 125 | a: set("a", "b"), s: []string{"b"}, 126 | want: true, 127 | }, { 128 | a: set("a"), s: []string{"a", "b"}, 129 | want: false, 130 | }, { 131 | a: set("a", "b", "c"), s: []string{"a", "b"}, 132 | want: true, 133 | }, { 134 | a: set("a", "b", "c"), s: []string{"x", "b"}, 135 | want: false, 136 | }, { 137 | a: set("a", "b", "c"), s: []string{"b", "c", "d"}, 138 | want: false, 139 | }} 140 | 141 | for _, tt := range tests { 142 | got := contains(tt.a, tt.s...) 143 | if !reflect.DeepEqual(tt.want, got) { 144 | t.Errorf("contains(%v, %v) want: %v, got %v", tt.a, tt.s, tt.want, got) 145 | } 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /help.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bufio" 5 | "bytes" 6 | "fmt" 7 | "io" 8 | "os" 9 | "strings" 10 | "text/template" 11 | "unicode" 12 | "unicode/utf8" 13 | ) 14 | 15 | var helpTemplate = `usage: gvt {{.UsageLine}} 16 | 17 | {{.Long | trim}} 18 | ` 19 | 20 | // help implements the 'help' command. 21 | func help(args []string) { 22 | if len(args) == 0 { 23 | printUsage(os.Stdout) 24 | return 25 | } 26 | if len(args) != 1 { 27 | fmt.Fprintf(os.Stderr, "usage: gvt help command\n\nToo many arguments given.\n") 28 | os.Exit(2) 29 | } 30 | 31 | arg := args[0] 32 | 33 | // 'gvt help documentation' generates alldocs.go. 34 | if arg == "documentation" { 35 | var u bytes.Buffer 36 | printUsage(&u) 37 | f, _ := os.Create("alldocs.go") 38 | tmpl(f, documentationTemplate, struct { 39 | Usage string 40 | Commands []*Command 41 | }{ 42 | u.String(), 43 | commands, 44 | }) 45 | f.Close() 46 | return 47 | } 48 | 49 | for _, cmd := range commands { 50 | if cmd.Name == arg { 51 | tmpl(os.Stdout, helpTemplate, cmd) 52 | return 53 | } 54 | } 55 | 56 | fmt.Fprintf(os.Stderr, "Unknown help topic %#q. Run 'gvt help'.\n", arg) 57 | os.Exit(2) 58 | } 59 | 60 | var usageTemplate = `gvt, a simple go vendoring tool based on gb-vendor. 61 | 62 | Usage: 63 | gvt command [arguments] 64 | 65 | The commands are: 66 | {{range .}} 67 | {{.Name | printf "%-11s"}} {{.Short}}{{end}} 68 | 69 | Use "gvt help [command]" for more information about a command. 70 | ` 71 | 72 | var documentationTemplate = `// DO NOT EDIT THIS FILE. 73 | //go:generate gvt help documentation 74 | 75 | /* 76 | {{ .Usage }} 77 | 78 | {{range .Commands}}{{if .Short}}{{.Short | capitalize}} 79 | {{end}} 80 | Usage: 81 | gvt {{.UsageLine}} 82 | 83 | {{.Long | trim}} 84 | 85 | {{end}}*/ 86 | package main 87 | ` 88 | 89 | // tmpl executes the given template text on data, writing the result to w. 90 | func tmpl(w io.Writer, text string, data interface{}) { 91 | t := template.New("top") 92 | t.Funcs(template.FuncMap{"trim": strings.TrimSpace, "capitalize": capitalize}) 93 | template.Must(t.Parse(text)) 94 | if err := t.Execute(w, data); err != nil { 95 | panic(err) 96 | } 97 | } 98 | 99 | func capitalize(s string) string { 100 | if s == "" { 101 | return s 102 | } 103 | r, n := utf8.DecodeRuneInString(s) 104 | return string(unicode.ToTitle(r)) + s[n:] 105 | } 106 | 107 | func printUsage(w io.Writer) { 108 | bw := bufio.NewWriter(w) 109 | tmpl(bw, usageTemplate, commands) 110 | bw.Flush() 111 | } 112 | -------------------------------------------------------------------------------- /list.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "html/template" 7 | "os" 8 | "text/tabwriter" 9 | 10 | "github.com/polaris1119/gvt/gbvendor" 11 | ) 12 | 13 | var ( 14 | format string 15 | ) 16 | 17 | func addListFlags(fs *flag.FlagSet) { 18 | fs.StringVar(&format, "f", "{{.Importpath}}\t{{.Repository}}{{.Path}}\t{{.Branch}}\t{{.Revision}}", "format template") 19 | } 20 | 21 | var cmdList = &Command{ 22 | Name: "list", 23 | UsageLine: "list [-f format]", 24 | Short: "list dependencies one per line", 25 | Long: `list formats the contents of the manifest file. 26 | 27 | Flags: 28 | -f 29 | controls the template used for printing each manifest entry. If not supplied 30 | the default value is "{{.Importpath}}\t{{.Repository}}{{.Path}}\t{{.Branch}}\t{{.Revision}}" 31 | 32 | `, 33 | Run: func(args []string) error { 34 | m, err := vendor.ReadManifest(manifestFile()) 35 | if err != nil { 36 | return fmt.Errorf("could not load manifest: %v", err) 37 | } 38 | tmpl, err := template.New("list").Parse(format) 39 | if err != nil { 40 | return fmt.Errorf("unable to parse template %q: %v", format, err) 41 | } 42 | w := tabwriter.NewWriter(os.Stdout, 1, 2, 1, ' ', 0) 43 | for _, dep := range m.Dependencies { 44 | if err := tmpl.Execute(w, dep); err != nil { 45 | return fmt.Errorf("unable to execute template: %v", err) 46 | } 47 | fmt.Fprintln(w) 48 | } 49 | return w.Flush() 50 | }, 51 | AddFlags: addListFlags, 52 | } 53 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "log" 7 | "os" 8 | "path/filepath" 9 | ) 10 | 11 | var fs = flag.NewFlagSet(os.Args[0], flag.ContinueOnError) 12 | 13 | func init() { 14 | fs.Usage = func() {} 15 | } 16 | 17 | type Command struct { 18 | Name string 19 | UsageLine string 20 | Short string 21 | Long string 22 | Run func(args []string) error 23 | AddFlags func(fs *flag.FlagSet) 24 | } 25 | 26 | var commands = []*Command{ 27 | cmdFetch, 28 | cmdRestore, 29 | cmdUpdate, 30 | cmdList, 31 | cmdDelete, 32 | } 33 | 34 | func main() { 35 | args := os.Args[1:] 36 | 37 | switch { 38 | case len(args) < 1, args[0] == "-h", args[0] == "-help": 39 | printUsage(os.Stdout) 40 | os.Exit(0) 41 | case args[0] == "help": 42 | help(args[1:]) 43 | return 44 | case args[0] == "rebuild": 45 | // rebuild was renamed restore, alias for backwards compatibility 46 | args[0] = "restore" 47 | } 48 | 49 | for _, command := range commands { 50 | if command.Name == args[0] { 51 | 52 | // add extra flags if necessary 53 | if command.AddFlags != nil { 54 | command.AddFlags(fs) 55 | } 56 | 57 | if err := fs.Parse(args[1:]); err != nil { 58 | if err == flag.ErrHelp { 59 | help(args[:1]) 60 | os.Exit(0) 61 | } 62 | fmt.Fprint(os.Stderr, "\n") 63 | help(args[:1]) 64 | os.Exit(3) 65 | } 66 | 67 | if err := command.Run(fs.Args()); err != nil { 68 | log.Fatalf("command %q failed: %v", command.Name, err) 69 | } 70 | return 71 | } 72 | } 73 | fmt.Fprintf(os.Stderr, "unknown command: %q\n\n", args[0]) 74 | printUsage(os.Stderr) 75 | os.Exit(3) 76 | } 77 | 78 | const manifestfile = "manifest" 79 | 80 | func vendorDir() string { 81 | wd, err := os.Getwd() 82 | if err != nil { 83 | log.Fatal(err) 84 | } 85 | return filepath.Join(wd, "vendor") 86 | } 87 | 88 | func manifestFile() string { 89 | return filepath.Join(vendorDir(), manifestfile) 90 | } 91 | -------------------------------------------------------------------------------- /restore.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "log" 7 | "os" 8 | "path/filepath" 9 | "sync" 10 | "sync/atomic" 11 | 12 | "github.com/constabulary/gb/fileutils" 13 | "github.com/polaris1119/gvt/gbvendor" 14 | ) 15 | 16 | var ( 17 | rbInsecure bool // Allow the use of insecure protocols 18 | rbConnections uint // Count of concurrent download connections 19 | ) 20 | 21 | func addRestoreFlags(fs *flag.FlagSet) { 22 | fs.BoolVar(&rbInsecure, "precaire", false, "allow the use of insecure protocols") 23 | fs.UintVar(&rbConnections, "connections", 8, "count of parallel download connections") 24 | } 25 | 26 | var cmdRestore = &Command{ 27 | Name: "restore", 28 | UsageLine: "restore [-precaire] [-connections N]", 29 | Short: "restore dependencies from manifest", 30 | Long: `restore fetches the dependencies listed in the manifest. 31 | 32 | It's meant for workflows that don't include checking in to VCS the vendored 33 | source, for example if .gitignore includes lines like 34 | 35 | vendor/** 36 | !vendor/manifest 37 | 38 | Note that such a setup requires "gvt restore" to build the source, relies on 39 | the availability of the dependencies repositories and breaks "go get". 40 | 41 | Flags: 42 | -precaire 43 | allow the use of insecure protocols. 44 | -connections 45 | count of parallel download connections. 46 | `, 47 | Run: func(args []string) error { 48 | switch len(args) { 49 | case 0: 50 | return restore(manifestFile()) 51 | default: 52 | return fmt.Errorf("restore takes no arguments") 53 | } 54 | }, 55 | AddFlags: addRestoreFlags, 56 | } 57 | 58 | func restore(manFile string) error { 59 | m, err := vendor.ReadManifest(manFile) 60 | if err != nil { 61 | return fmt.Errorf("could not load manifest: %v", err) 62 | } 63 | 64 | var errors uint32 65 | var wg sync.WaitGroup 66 | depC := make(chan vendor.Dependency) 67 | for i := 0; i < int(rbConnections); i++ { 68 | wg.Add(1) 69 | go func() { 70 | defer wg.Done() 71 | for d := range depC { 72 | if err := downloadDependency(d, &errors, vendorDir(), false); err != nil { 73 | log.Printf("%s: %v", d.Importpath, err) 74 | atomic.AddUint32(&errors, 1) 75 | } 76 | } 77 | }() 78 | } 79 | 80 | for _, dep := range m.Dependencies { 81 | depC <- dep 82 | } 83 | close(depC) 84 | wg.Wait() 85 | 86 | if errors > 0 { 87 | return fmt.Errorf("failed to fetch %d dependencies", errors) 88 | } 89 | 90 | return nil 91 | } 92 | 93 | func downloadDependency(dep vendor.Dependency, errors *uint32, vendorDir string, recursive bool) error { 94 | if recursive { 95 | log.Printf("fetching recursive %s", dep.Importpath) 96 | } else { 97 | log.Printf("fetching %s", dep.Importpath) 98 | } 99 | 100 | repo, _, err := vendor.DeduceRemoteRepo(dep.Importpath, rbInsecure, dep.Repository) 101 | if err != nil { 102 | return fmt.Errorf("dependency could not be processed: %s", err) 103 | } 104 | // We can't pass the branch here, and benefit from narrow clones, as the 105 | // revision might not be in the branch tree anymore. Thanks rebase. 106 | wc, err := repo.Checkout("", "", dep.Revision) 107 | if err != nil { 108 | return fmt.Errorf("dependency could not be fetched: %s", err) 109 | } 110 | dst := filepath.Join(vendorDir, dep.Importpath) 111 | src := filepath.Join(wc.Dir(), dep.Path) 112 | 113 | if _, err := os.Stat(dst); err == nil { 114 | if err := fileutils.RemoveAll(dst); err != nil { 115 | return fmt.Errorf("dependency could not be deleted: %v", err) 116 | } 117 | } 118 | 119 | if err := fileutils.Copypath(dst, src); err != nil { 120 | return err 121 | } 122 | 123 | if err := wc.Destroy(); err != nil { 124 | return err 125 | } 126 | 127 | // Check for for manifests in dependencies 128 | man := filepath.Join(dst, "vendor", "manifest") 129 | venDir := filepath.Join(dst, "vendor") 130 | if _, err := os.Stat(man); err == nil { 131 | m, err := vendor.ReadManifest(man) 132 | if err != nil { 133 | return fmt.Errorf("could not load manifest: %v", err) 134 | } 135 | for _, d := range m.Dependencies { 136 | if err := downloadDependency(d, errors, venDir, true); err != nil { 137 | log.Printf("%s: %v", d.Importpath, err) 138 | atomic.AddUint32(errors, 1) 139 | } 140 | } 141 | } 142 | 143 | return nil 144 | } 145 | -------------------------------------------------------------------------------- /update.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "path/filepath" 7 | 8 | "github.com/constabulary/gb/fileutils" 9 | 10 | "github.com/polaris1119/gvt/gbvendor" 11 | ) 12 | 13 | var ( 14 | updateAll bool // update all dependencies 15 | ) 16 | 17 | func addUpdateFlags(fs *flag.FlagSet) { 18 | fs.BoolVar(&updateAll, "all", false, "update all dependencies") 19 | fs.BoolVar(&insecure, "precaire", false, "allow the use of insecure protocols") 20 | } 21 | 22 | var cmdUpdate = &Command{ 23 | Name: "update", 24 | UsageLine: "update [ -all | importpath ]", 25 | Short: "update a local dependency", 26 | Long: `update replaces the source with the latest available from the head of the fetched branch. 27 | 28 | Updating from one copy of a dependency to another is ONLY possible when the 29 | dependency was fetched by branch, without using -tag or -revision. It will be 30 | updated to the HEAD of that branch, switching branches is not supported. 31 | 32 | To update across branches, or from one tag/revision to another, you must first 33 | use delete to remove the dependency, then fetch [ -tag | -revision | -branch ] 34 | to replace it. 35 | 36 | Flags: 37 | -all 38 | update all dependencies in the manifest. 39 | -precaire 40 | allow the use of insecure protocols. 41 | 42 | `, 43 | Run: func(args []string) error { 44 | if len(args) != 1 && !updateAll { 45 | return fmt.Errorf("update: import path or -all flag is missing") 46 | } else if len(args) == 1 && updateAll { 47 | return fmt.Errorf("update: you cannot specify path and -all flag at once") 48 | } 49 | 50 | m, err := vendor.ReadManifest(manifestFile()) 51 | if err != nil { 52 | return fmt.Errorf("could not load manifest: %v", err) 53 | } 54 | 55 | var dependencies []vendor.Dependency 56 | if updateAll { 57 | dependencies = make([]vendor.Dependency, len(m.Dependencies)) 58 | copy(dependencies, m.Dependencies) 59 | } else { 60 | p := args[0] 61 | dependency, err := m.GetDependencyForImportpath(p) 62 | if err != nil { 63 | return fmt.Errorf("could not get dependency: %v", err) 64 | } 65 | dependencies = append(dependencies, dependency) 66 | } 67 | 68 | for _, d := range dependencies { 69 | err = m.RemoveDependency(d) 70 | if err != nil { 71 | return fmt.Errorf("dependency could not be deleted from manifest: %v", err) 72 | } 73 | 74 | repo, extra, err := vendor.DeduceRemoteRepo(d.Importpath, insecure, d.Repository) 75 | if err != nil { 76 | return fmt.Errorf("could not determine repository for import %q", d.Importpath) 77 | } 78 | 79 | wc, err := repo.Checkout(d.Branch, "", "") 80 | if err != nil { 81 | return err 82 | } 83 | 84 | rev, err := wc.Revision() 85 | if err != nil { 86 | return err 87 | } 88 | 89 | branch, err := wc.Branch() 90 | if err != nil { 91 | return err 92 | } 93 | 94 | dep := vendor.Dependency{ 95 | Importpath: d.Importpath, 96 | Repository: repo.URL(), 97 | Revision: rev, 98 | Branch: branch, 99 | Path: extra, 100 | } 101 | 102 | if err := fileutils.RemoveAll(filepath.Join(vendorDir(), filepath.FromSlash(d.Importpath))); err != nil { 103 | // TODO(dfc) need to apply vendor.cleanpath here to remove intermediate directories. 104 | return fmt.Errorf("dependency could not be deleted: %v", err) 105 | } 106 | 107 | dst := filepath.Join(vendorDir(), filepath.FromSlash(dep.Importpath)) 108 | src := filepath.Join(wc.Dir(), dep.Path) 109 | 110 | if err := fileutils.Copypath(dst, src); err != nil { 111 | return err 112 | } 113 | 114 | if err := m.AddDependency(dep); err != nil { 115 | return err 116 | } 117 | 118 | if err := vendor.WriteManifest(manifestFile(), m); err != nil { 119 | return err 120 | } 121 | 122 | if err := wc.Destroy(); err != nil { 123 | return err 124 | } 125 | } 126 | 127 | return nil 128 | }, 129 | AddFlags: addUpdateFlags, 130 | } 131 | -------------------------------------------------------------------------------- /vendor/github.com/constabulary/gb/fileutils/_testdata/copyfile/a/rick: -------------------------------------------------------------------------------- 1 | /never/going/to/give/you/up -------------------------------------------------------------------------------- /vendor/github.com/constabulary/gb/fileutils/fileutils.go: -------------------------------------------------------------------------------- 1 | // package fileutils provides utililty methods to copy and move files and directories. 2 | package fileutils 3 | 4 | import ( 5 | "fmt" 6 | "io" 7 | "os" 8 | "path/filepath" 9 | "runtime" 10 | "strings" 11 | ) 12 | 13 | const debugCopypath = false 14 | const debugCopyfile = false 15 | 16 | // Copypath copies the contents of src to dst, excluding any file or 17 | // directory that starts with a period. 18 | func Copypath(dst string, src string) error { 19 | err := filepath.Walk(src, func(path string, info os.FileInfo, err error) error { 20 | if err != nil { 21 | return err 22 | } 23 | 24 | if strings.HasPrefix(filepath.Base(path), ".") { 25 | if info.IsDir() { 26 | return filepath.SkipDir 27 | } 28 | return nil 29 | } 30 | 31 | if info.IsDir() { 32 | return nil 33 | } 34 | 35 | if info.Mode()&os.ModeSymlink != 0 { 36 | if debugCopypath { 37 | fmt.Printf("skipping symlink: %v\n", path) 38 | } 39 | return nil 40 | } 41 | 42 | dst := filepath.Join(dst, path[len(src):]) 43 | return Copyfile(dst, path) 44 | }) 45 | if err != nil { 46 | // if there was an error during copying, remove the partial copy. 47 | RemoveAll(dst) 48 | } 49 | return err 50 | } 51 | 52 | func Copyfile(dst, src string) error { 53 | err := mkdir(filepath.Dir(dst)) 54 | if err != nil { 55 | return fmt.Errorf("copyfile: mkdirall: %v", err) 56 | } 57 | r, err := os.Open(src) 58 | if err != nil { 59 | return fmt.Errorf("copyfile: open(%q): %v", src, err) 60 | } 61 | defer r.Close() 62 | w, err := os.Create(dst) 63 | if err != nil { 64 | return fmt.Errorf("copyfile: create(%q): %v", dst, err) 65 | } 66 | defer w.Close() 67 | if debugCopyfile { 68 | fmt.Printf("copyfile(dst: %v, src: %v)\n", dst, src) 69 | } 70 | _, err = io.Copy(w, r) 71 | return err 72 | } 73 | 74 | // RemoveAll removes path and any children it contains. Unlike os.RemoveAll it 75 | // deletes read only files on Windows. 76 | func RemoveAll(path string) error { 77 | if runtime.GOOS == "windows" { 78 | // Simple case: if Remove works, we're done. 79 | err := os.Remove(path) 80 | if err == nil || os.IsNotExist(err) { 81 | return nil 82 | } 83 | // make sure all files are writable so we can delete them 84 | filepath.Walk(path, func(path string, info os.FileInfo, err error) error { 85 | if err != nil { 86 | // walk gave us some error, give it back. 87 | return err 88 | } 89 | mode := info.Mode() 90 | if mode|0200 == mode { 91 | return nil 92 | } 93 | return os.Chmod(path, mode|0200) 94 | }) 95 | } 96 | return os.RemoveAll(path) 97 | } 98 | 99 | func mkdir(path string) error { 100 | return os.MkdirAll(path, 0755) 101 | } 102 | -------------------------------------------------------------------------------- /vendor/github.com/constabulary/gb/fileutils/fileutils_test.go: -------------------------------------------------------------------------------- 1 | package fileutils 2 | 3 | import ( 4 | "io/ioutil" 5 | "path/filepath" 6 | "runtime" 7 | "testing" 8 | ) 9 | 10 | func TestCopypathSkipsSymlinks(t *testing.T) { 11 | if runtime.GOOS == "windows" { 12 | t.Skip("no symlinks on windows y'all") 13 | } 14 | dst := mktemp(t) 15 | defer RemoveAll(dst) 16 | src := filepath.Join("_testdata", "copyfile", "a") 17 | if err := Copypath(dst, src); err != nil { 18 | t.Fatalf("copypath(%s, %s): %v", dst, src, err) 19 | } 20 | } 21 | 22 | func mktemp(t *testing.T) string { 23 | s, err := ioutil.TempDir("", "fileutils_test") 24 | if err != nil { 25 | t.Fatal(err) 26 | } 27 | return s 28 | } 29 | -------------------------------------------------------------------------------- /vendor/github.com/constabulary/gb/fileutils/path_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2009 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | package fileutils 5 | 6 | import ( 7 | "os" 8 | "runtime" 9 | "testing" 10 | ) 11 | 12 | var isReadonlyError = func(error) bool { return false } 13 | 14 | func TestRemoveAll(t *testing.T) { 15 | tmpDir := os.TempDir() 16 | // Work directory. 17 | path := tmpDir + "/_TestRemoveAll_" 18 | fpath := path + "/file" 19 | dpath := path + "/dir" 20 | 21 | // Make directory with 1 file and remove. 22 | if err := os.MkdirAll(path, 0777); err != nil { 23 | t.Fatalf("MkdirAll %q: %s", path, err) 24 | } 25 | fd, err := os.Create(fpath) 26 | if err != nil { 27 | t.Fatalf("create %q: %s", fpath, err) 28 | } 29 | fd.Close() 30 | if err = RemoveAll(path); err != nil { 31 | t.Fatalf("RemoveAll %q (first): %s", path, err) 32 | } 33 | if _, err = os.Lstat(path); err == nil { 34 | t.Fatalf("Lstat %q succeeded after RemoveAll (first)", path) 35 | } 36 | 37 | // Make directory with file and subdirectory and remove. 38 | if err = os.MkdirAll(dpath, 0777); err != nil { 39 | t.Fatalf("MkdirAll %q: %s", dpath, err) 40 | } 41 | fd, err = os.Create(fpath) 42 | if err != nil { 43 | t.Fatalf("create %q: %s", fpath, err) 44 | } 45 | fd.Close() 46 | fd, err = os.Create(dpath + "/file") 47 | if err != nil { 48 | t.Fatalf("create %q: %s", fpath, err) 49 | } 50 | fd.Close() 51 | if err = RemoveAll(path); err != nil { 52 | t.Fatalf("RemoveAll %q (second): %s", path, err) 53 | } 54 | if _, err := os.Lstat(path); err == nil { 55 | t.Fatalf("Lstat %q succeeded after RemoveAll (second)", path) 56 | } 57 | 58 | // Determine if we should run the following test. 59 | testit := true 60 | if runtime.GOOS == "windows" { 61 | // Chmod is not supported under windows. 62 | testit = false 63 | } else { 64 | // Test fails as root. 65 | testit = os.Getuid() != 0 66 | } 67 | if testit { 68 | // Make directory with file and subdirectory and trigger error. 69 | if err = os.MkdirAll(dpath, 0777); err != nil { 70 | t.Fatalf("MkdirAll %q: %s", dpath, err) 71 | } 72 | 73 | for _, s := range []string{fpath, dpath + "/file1", path + "/zzz"} { 74 | fd, err = os.Create(s) 75 | if err != nil { 76 | t.Fatalf("create %q: %s", s, err) 77 | } 78 | fd.Close() 79 | } 80 | if err = os.Chmod(dpath, 0); err != nil { 81 | t.Fatalf("Chmod %q 0: %s", dpath, err) 82 | } 83 | 84 | // No error checking here: either RemoveAll 85 | // will or won't be able to remove dpath; 86 | // either way we want to see if it removes fpath 87 | // and path/zzz. Reasons why RemoveAll might 88 | // succeed in removing dpath as well include: 89 | // * running as root 90 | // * running on a file system without permissions (FAT) 91 | RemoveAll(path) 92 | os.Chmod(dpath, 0777) 93 | 94 | for _, s := range []string{fpath, path + "/zzz"} { 95 | if _, err = os.Lstat(s); err == nil { 96 | t.Fatalf("Lstat %q succeeded after partial RemoveAll", s) 97 | } 98 | } 99 | } 100 | if err = RemoveAll(path); err != nil { 101 | t.Fatalf("RemoveAll %q after partial RemoveAll: %s", path, err) 102 | } 103 | if _, err = os.Lstat(path); err == nil { 104 | t.Fatalf("Lstat %q succeeded after RemoveAll (final)", path) 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /vendor/manifest: -------------------------------------------------------------------------------- 1 | { 2 | "version": 0, 3 | "dependencies": [ 4 | { 5 | "importpath": "github.com/constabulary/gb/fileutils", 6 | "repository": "https://github.com/constabulary/gb", 7 | "revision": "572f68e6f0e403df7e6b4426bb26e535b988398c", 8 | "branch": "master", 9 | "path": "/fileutils" 10 | } 11 | ] 12 | } --------------------------------------------------------------------------------