├── .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 | [](https://godoc.org/github.com/FiloSottile/gvt)
3 | [](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 | }
--------------------------------------------------------------------------------