Updated dependencies: - github.com/Masterminds/vcs to version 1.6.0 - github.com/codegangsta/cli to the latest tip of head. This included a new subcommand category feature
diff --git a/glide.lock b/glide.lock index 1bbde7a..eeae594 100644 --- a/glide.lock +++ b/glide.lock
@@ -1,12 +1,12 @@ -hash: 83c7c3c86a8d50d46ac2731938195e348f2445e6106325a4f90976563c946bd8 -updated: 2016-03-23T21:38:59.47773549-04:00 +hash: 3a5b11283c409c77e79505c08a39550a966d62fb0896f4355c10d699482840a3 +updated: 2016-04-18T09:49:03.335458835-04:00 imports: - name: github.com/codegangsta/cli - version: 9fec0fad02befc9209347cc6d620e68e1b45f74d + version: 71f57d300dd6a780ac1856c005c4b518cfd498ec - name: github.com/Masterminds/semver version: 808ed7761c233af2de3f9729a041d68c62527f3a - name: github.com/Masterminds/vcs - version: b22ee1673cdd03ef47bb0b422736a7f17ff0648c + version: fa85cceafacd29c84a8aa6e68967bb9f1754e5e3 - name: gopkg.in/yaml.v2 version: a83829b6f1293c91addabc89d0571c246397bbf4 devImports: []
diff --git a/glide.yaml b/glide.yaml index a8eb79a..e9e3a02 100644 --- a/glide.yaml +++ b/glide.yaml
@@ -11,7 +11,7 @@ import: - package: gopkg.in/yaml.v2 - package: github.com/Masterminds/vcs - version: ^1.5.1 + version: ^1.6.0 - package: github.com/codegangsta/cli - package: github.com/Masterminds/semver version: ^1.0.0
diff --git a/vendor/github.com/Masterminds/vcs/CHANGELOG.md b/vendor/github.com/Masterminds/vcs/CHANGELOG.md index 96af5ec..7a04e56 100644 --- a/vendor/github.com/Masterminds/vcs/CHANGELOG.md +++ b/vendor/github.com/Masterminds/vcs/CHANGELOG.md
@@ -1,3 +1,17 @@ +# 1.6.0 (2016-04-18) + +- Issue #26: Added Init method to initialize a repo at the local location + (thanks tony). +- Issue #19: Added method to retrieve tags for a commit. +- Issue #24: Reworked errors returned from common methods. Now differing + VCS implementations return the same errors. The original VCS specific error + is available on the error. See the docs for more details. +- Issue #25: Export the function RunFromDir which runs VCS commands from the + root of the local directory. This is useful for those that want to build and + extend on top of the vcs package (thanks tony). +- Issue #22: Added Ping command to test if remote location is present and + accessible. + # 1.5.1 (2016-03-23) - Fixing bug parsing some Git commit dates.
diff --git a/vendor/github.com/Masterminds/vcs/bzr.go b/vendor/github.com/Masterminds/vcs/bzr.go index 965b598..1d36137 100644 --- a/vendor/github.com/Masterminds/vcs/bzr.go +++ b/vendor/github.com/Masterminds/vcs/bzr.go
@@ -1,6 +1,8 @@ package vcs import ( + "fmt" + "net/url" "os" "os/exec" "path/filepath" @@ -39,7 +41,7 @@ c.Env = envForDir(c.Dir) out, err := c.CombinedOutput() if err != nil { - return nil, err + return nil, NewLocalError("Unable to retrieve local repo information", err, string(out)) } m := bzrDetectURL.FindStringSubmatch(string(out)) @@ -70,36 +72,76 @@ if _, err := os.Stat(basePath); os.IsNotExist(err) { err = os.MkdirAll(basePath, 0755) if err != nil { - return err + return NewLocalError("Unable to create directory", err, "") } } - _, err := s.run("bzr", "branch", s.Remote(), s.LocalPath()) - return err + out, err := s.run("bzr", "branch", s.Remote(), s.LocalPath()) + if err != nil { + return NewRemoteError("Unable to get repository", err, string(out)) + } + + return nil +} + +// Init initializes a bazaar repository at local location. +func (s *BzrRepo) Init() error { + out, err := s.run("bzr", "init", s.LocalPath()) + + // There are some windows cases where bazaar cannot create the parent + // directory if it does not already exist, to the location it's trying + // to create the repo. Catch that error and try to handle it. + if err != nil && s.isUnableToCreateDir(err) { + + basePath := filepath.Dir(filepath.FromSlash(s.LocalPath())) + if _, err := os.Stat(basePath); os.IsNotExist(err) { + err = os.MkdirAll(basePath, 0755) + if err != nil { + return NewLocalError("Unable to initialize repository", err, "") + } + + out, err = s.run("bzr", "init", s.LocalPath()) + if err != nil { + return NewLocalError("Unable to initialize repository", err, string(out)) + } + return nil + } + + } else if err != nil { + return NewLocalError("Unable to initialize repository", err, string(out)) + } + + return nil } // Update performs a Bzr pull and update to an existing checkout. func (s *BzrRepo) Update() error { - _, err := s.runFromDir("bzr", "pull") + out, err := s.RunFromDir("bzr", "pull") if err != nil { - return err + return NewRemoteError("Unable to update repository", err, string(out)) } - _, err = s.runFromDir("bzr", "update") - return err + out, err = s.RunFromDir("bzr", "update") + if err != nil { + return NewRemoteError("Unable to update repository", err, string(out)) + } + return nil } // UpdateVersion sets the version of a package currently checked out via Bzr. func (s *BzrRepo) UpdateVersion(version string) error { - _, err := s.runFromDir("bzr", "update", "-r", version) - return err + out, err := s.RunFromDir("bzr", "update", "-r", version) + if err != nil { + return NewLocalError("Unable to update checked out version", err, string(out)) + } + return nil } // Version retrieves the current version. func (s *BzrRepo) Version() (string, error) { - out, err := s.runFromDir("bzr", "revno", "--tree") + out, err := s.RunFromDir("bzr", "revno", "--tree") if err != nil { - return "", err + return "", NewLocalError("Unable to retrieve checked out version", err, string(out)) } return strings.TrimSpace(string(out)), nil @@ -107,13 +149,13 @@ // Date retrieves the date on the latest commit. func (s *BzrRepo) Date() (time.Time, error) { - out, err := s.runFromDir("bzr", "version-info", "--custom", "--template={date}") + out, err := s.RunFromDir("bzr", "version-info", "--custom", "--template={date}") if err != nil { - return time.Time{}, err + return time.Time{}, NewLocalError("Unable to retrieve revision date", err, string(out)) } t, err := time.Parse(longForm, string(out)) if err != nil { - return time.Time{}, err + return time.Time{}, NewLocalError("Unable to retrieve revision date", err, string(out)) } return t, nil } @@ -138,9 +180,9 @@ // Tags returns a list of available tags on the repository. func (s *BzrRepo) Tags() ([]string, error) { - out, err := s.runFromDir("bzr", "tags") + out, err := s.RunFromDir("bzr", "tags") if err != nil { - return []string{}, err + return []string{}, NewLocalError("Unable to retrieve tags", err, string(out)) } tags := s.referenceList(string(out), `(?m-s)^(\S+)`) return tags, nil @@ -149,7 +191,7 @@ // IsReference returns if a string is a reference. A reference can be a // commit id or tag. func (s *BzrRepo) IsReference(r string) bool { - _, err := s.runFromDir("bzr", "revno", "-r", r) + _, err := s.RunFromDir("bzr", "revno", "-r", r) if err == nil { return true } @@ -160,14 +202,14 @@ // IsDirty returns if the checkout has been modified from the checked // out reference. func (s *BzrRepo) IsDirty() bool { - out, err := s.runFromDir("bzr", "diff") + out, err := s.RunFromDir("bzr", "diff") return err != nil || len(out) != 0 } // CommitInfo retrieves metadata about a commit. func (s *BzrRepo) CommitInfo(id string) (*CommitInfo, error) { r := "-r" + id - out, err := s.runFromDir("bzr", "log", r, "--log-format=long") + out, err := s.RunFromDir("bzr", "log", r, "--log-format=long") if err != nil { return nil, ErrRevisionUnavailable } @@ -188,7 +230,7 @@ ts := strings.TrimSpace(strings.TrimPrefix(l, "timestamp:")) ci.Date, err = time.Parse(format, ts) if err != nil { - return nil, err + return nil, NewLocalError("Unable to retrieve commit information", err, string(out)) } } else if strings.TrimSpace(l) == "message:" { track = i @@ -206,3 +248,60 @@ return ci, nil } + +// TagsFromCommit retrieves tags from a commit id. +func (s *BzrRepo) TagsFromCommit(id string) ([]string, error) { + out, err := s.RunFromDir("bzr", "tags", "-r", id) + if err != nil { + return []string{}, NewLocalError("Unable to retrieve tags", err, string(out)) + } + + tags := s.referenceList(string(out), `(?m-s)^(\S+)`) + return tags, nil +} + +// Ping returns if remote location is accessible. +func (s *BzrRepo) Ping() bool { + + // Running bzr info is slow. Many of the projects are on launchpad which + // has a public 1.0 API we can use. + u, err := url.Parse(s.Remote()) + if err == nil { + if u.Host == "launchpad.net" { + try := strings.TrimPrefix(u.Path, "/") + + // get returns the body and an err. If the status code is not a 200 + // an error is returned. Launchpad returns a 404 for a codebase that + // does not exist. Otherwise it returns a JSON object describing it. + _, er := get("https://api.launchpad.net/1.0/" + try) + if er == nil { + return true + } + return false + } + } + + // This is the same command that Go itself uses but it's not fast (or fast + // enough by my standards). A faster method would be useful. + _, err = s.run("bzr", "info", s.Remote()) + if err != nil { + return false + } + + return true +} + +// Multi-lingual manner check for the VCS error that it couldn't create directory. +// https://bazaar.launchpad.net/~bzr-pqm/bzr/bzr.dev/files/head:/po/ +func (s *BzrRepo) isUnableToCreateDir(err error) bool { + msg := err.Error() + if strings.HasPrefix(msg, fmt.Sprintf("Parent directory of %s does not exist.", s.LocalPath())) || + strings.HasPrefix(msg, fmt.Sprintf("Nadřazený adresář %s neexistuje.", s.LocalPath())) || + strings.HasPrefix(msg, fmt.Sprintf("El directorio padre de %s no existe.", s.LocalPath())) || + strings.HasPrefix(msg, fmt.Sprintf("%s の親ディレクトリがありません。", s.LocalPath())) || + strings.HasPrefix(msg, fmt.Sprintf("Родительская директория для %s не существует.", s.LocalPath())) { + return true + } + + return false +}
diff --git a/vendor/github.com/Masterminds/vcs/bzr_test.go b/vendor/github.com/Masterminds/vcs/bzr_test.go index ce61d75..6125444 100644 --- a/vendor/github.com/Masterminds/vcs/bzr_test.go +++ b/vendor/github.com/Masterminds/vcs/bzr_test.go
@@ -123,6 +123,22 @@ t.Error("Bzr tags is not reporting the correct version") } + tags, err = repo.TagsFromCommit("2") + if err != nil { + t.Error(err) + } + if len(tags) != 0 { + t.Error("Bzr is incorrectly returning tags for a commit") + } + + tags, err = repo.TagsFromCommit("3") + if err != nil { + t.Error(err) + } + if len(tags) != 1 || tags[0] != "1.0.0" { + t.Error("Bzr is incorrectly returning tags for a commit") + } + branches, err := repo.Branches() if err != nil { t.Error(err) @@ -196,3 +212,68 @@ t.Error(nrerr) } } + +func TestBzrPing(t *testing.T) { + tempDir, err := ioutil.TempDir("", "go-vcs-bzr-tests") + if err != nil { + t.Error(err) + } + defer func() { + err = os.RemoveAll(tempDir) + if err != nil { + t.Error(err) + } + }() + + repo, err := NewBzrRepo("https://launchpad.net/govcstestbzrrepo", tempDir) + if err != nil { + t.Error(err) + } + + ping := repo.Ping() + if !ping { + t.Error("Bzr unable to ping working repo") + } + + repo, err = NewBzrRepo("https://launchpad.net/ihopethisneverexistsbecauseitshouldnt", tempDir) + if err != nil { + t.Error(err) + } + + ping = repo.Ping() + if ping { + t.Error("Bzr got a ping response from when it should not have") + } +} + +func TestBzrInit(t *testing.T) { + tempDir, err := ioutil.TempDir("", "go-vcs-bzr-tests") + repoDir := tempDir + "/repo" + if err != nil { + t.Error(err) + } + defer func() { + err = os.RemoveAll(tempDir) + if err != nil { + t.Error(err) + } + }() + + repo, err := NewBzrRepo(repoDir, repoDir) + if err != nil { + t.Error(err) + } + + err = repo.Init() + if err != nil { + t.Error(err) + } + + v, err := repo.Version() + if err != nil { + t.Error(err) + } + if v != "0" { + t.Errorf("Bzr Init returns wrong version: %s", v) + } +}
diff --git a/vendor/github.com/Masterminds/vcs/errors.go b/vendor/github.com/Masterminds/vcs/errors.go new file mode 100644 index 0000000..ea8c5fc --- /dev/null +++ b/vendor/github.com/Masterminds/vcs/errors.go
@@ -0,0 +1,107 @@ +package vcs + +import "errors" + +// The vcs package provides ways to work with errors that hide the underlying +// implementation details but make them accessible if needed. For basic errors +// that do not have underlying implementation specific details or the underlying +// details are likely not necessairy there are errors for comparison. +// +// For example: +// +// ci, err := repo.CommitInfo("123") +// if err == vcs.ErrRevisionUnavailable { +// // The commit id was not available in the VCS. +// } +// +// There are other times where getting the details are more useful. For example, +// if you're performing a repo.Get() and an error occurs. In general you'll want +// to consistently know it failed. But, you may want to know the underlying +// details (opt-in) to them. For those cases there is a different form of error +// handling. +// +// For example: +// +// err := repo.Get() +// if err != nil { +// // A RemoteError was returned. This has access to the output of the +// // vcs command, original error, and has a consistent cross vcs message. +// } +// +// The errors returned here can be used in type switches to detect the underlying +// error. For example: +// +// switch err.(type) { +// case *vcs.RemoteError: +// // This an error connecting to a remote system. +// } +// +// For more information on using type switches to detect error types you can +// read the Go wiki at https://github.com/golang/go/wiki/Errors + +var ( + // ErrWrongVCS is returned when an action is tried on the wrong VCS. + ErrWrongVCS = errors.New("Wrong VCS detected") + + // ErrCannotDetectVCS is returned when VCS cannot be detected from URI string. + ErrCannotDetectVCS = errors.New("Cannot detect VCS") + + // ErrWrongRemote occurs when the passed in remote does not match the VCS + // configured endpoint. + ErrWrongRemote = errors.New("The Remote does not match the VCS endpoint") + + // ErrRevisionUnavailable happens when commit revision information is + // unavailable. + ErrRevisionUnavailable = errors.New("Revision unavailable") +) + +// RemoteError is returned when an operation fails against a remote repo +type RemoteError struct { + vcsError +} + +// NewRemoteError constructs a RemoteError +func NewRemoteError(msg string, err error, out string) error { + e := &RemoteError{} + e.s = msg + e.e = err + e.o = out + + return e +} + +// LocalError is returned when a local operation has an error +type LocalError struct { + vcsError +} + +// NewLocalError constructs a LocalError +func NewLocalError(msg string, err error, out string) error { + e := &LocalError{} + e.s = msg + e.e = err + e.o = out + + return e +} + +type vcsError struct { + s string + e error // The original error + o string // The output from executing the command +} + +// Error implements the Error interface +func (e *vcsError) Error() string { + return e.s +} + +// Original retrieves the underlying implementation specific error. +func (e *vcsError) Original() error { + return e.e +} + +// Out retrieves the output of the original command that was run. +func (e *vcsError) Out() string { + return e.o +}
diff --git a/vendor/github.com/Masterminds/vcs/errors_test.go b/vendor/github.com/Masterminds/vcs/errors_test.go new file mode 100644 index 0000000..2effd7c --- /dev/null +++ b/vendor/github.com/Masterminds/vcs/errors_test.go
@@ -0,0 +1,36 @@ +package vcs + +import ( + "errors" + "testing" +) + +func TestNewRemoteError(t *testing.T) { + base := errors.New("Foo error") + out := "This is a test" + msg := "remote error msg" + + e := NewRemoteError(msg, base, out) + + switch e.(type) { + case *RemoteError: + // This is the right error type + default: + t.Error("Wrong error type returned from NewRemoteError") + } +} + +func TestNewLocalError(t *testing.T) { + base := errors.New("Foo error") + out := "This is a test" + msg := "local error msg" + + e := NewLocalError(msg, base, out) + + switch e.(type) { + case *LocalError: + // This is the right error type + default: + t.Error("Wrong error type returned from NewLocalError") + } +}
diff --git a/vendor/github.com/Masterminds/vcs/git.go b/vendor/github.com/Masterminds/vcs/git.go index 636ddc7..5d4b956 100644 --- a/vendor/github.com/Masterminds/vcs/git.go +++ b/vendor/github.com/Masterminds/vcs/git.go
@@ -33,7 +33,7 @@ c.Env = envForDir(c.Dir) out, err := c.CombinedOutput() if err != nil { - return nil, err + return nil, NewLocalError("Unable to retrieve local repo information", err, string(out)) } localRemote := strings.TrimSpace(string(out)) @@ -64,7 +64,7 @@ // Get is used to perform an initial clone of a repository. func (s *GitRepo) Get() error { - _, err := s.run("git", "clone", s.Remote(), s.LocalPath()) + out, err := s.run("git", "clone", s.Remote(), s.LocalPath()) // There are some windows cases where Git cannot create the parent directory, // if it does not already exist, to the location it's trying to create the @@ -75,53 +75,93 @@ if _, err := os.Stat(basePath); os.IsNotExist(err) { err = os.MkdirAll(basePath, 0755) if err != nil { - return err + return NewLocalError("Unable to create directory", err, "") } - _, err = s.run("git", "clone", s.Remote(), s.LocalPath()) + out, err = s.run("git", "clone", s.Remote(), s.LocalPath()) + if err != nil { + return NewRemoteError("Unable to get repository", err, string(out)) + } return err } + } else if err != nil { + return NewRemoteError("Unable to get repository", err, string(out)) } - return err + return nil +} + +// Init initializes a git repository at local location. +func (s *GitRepo) Init() error { + out, err := s.run("git", "init", s.LocalPath()) + + // There are some windows cases where Git cannot create the parent directory, + // if it does not already exist, to the location it's trying to create the + // repo. Catch that error and try to handle it. + if err != nil && s.isUnableToCreateDir(err) { + + basePath := filepath.Dir(filepath.FromSlash(s.LocalPath())) + if _, err := os.Stat(basePath); os.IsNotExist(err) { + err = os.MkdirAll(basePath, 0755) + if err != nil { + return NewLocalError("Unable to initialize repository", err, "") + } + + out, err = s.run("git", "init", s.LocalPath()) + if err != nil { + return NewLocalError("Unable to initialize repository", err, string(out)) + } + return nil + } + + } else if err != nil { + return NewLocalError("Unable to initialize repository", err, string(out)) + } + + return nil } // Update performs an Git fetch and pull to an existing checkout. func (s *GitRepo) Update() error { // Perform a fetch to make sure everything is up to date. - _, err := s.runFromDir("git", "fetch", s.RemoteLocation) + out, err := s.RunFromDir("git", "fetch", s.RemoteLocation) if err != nil { - return err + return NewRemoteError("Unable to update repository", err, string(out)) } // When in a detached head state, such as when an individual commit is checked // out do not attempt a pull. It will cause an error. detached, err := isDetachedHead(s.LocalPath()) - if err != nil { - return err + return NewLocalError("Unable to update repository", err, "") } if detached == true { return nil } - _, err = s.runFromDir("git", "pull") - return err + out, err = s.RunFromDir("git", "pull") + if err != nil { + return NewRemoteError("Unable to update repository", err, string(out)) + } + return nil } // UpdateVersion sets the version of a package currently checked out via Git. func (s *GitRepo) UpdateVersion(version string) error { - _, err := s.runFromDir("git", "checkout", version) - return err + out, err := s.RunFromDir("git", "checkout", version) + if err != nil { + return NewLocalError("Unable to update checked out version", err, string(out)) + } + return nil } // Version retrieves the current version. func (s *GitRepo) Version() (string, error) { - out, err := s.runFromDir("git", "rev-parse", "HEAD") + out, err := s.RunFromDir("git", "rev-parse", "HEAD") if err != nil { - return "", err + return "", NewLocalError("Unable to retrieve checked out version", err, string(out)) } return strings.TrimSpace(string(out)), nil @@ -129,22 +169,22 @@ // Date retrieves the date on the latest commit. func (s *GitRepo) Date() (time.Time, error) { - out, err := s.runFromDir("git", "log", "-1", "--date=iso", "--pretty=format:%cd") + out, err := s.RunFromDir("git", "log", "-1", "--date=iso", "--pretty=format:%cd") if err != nil { - return time.Time{}, err + return time.Time{}, NewLocalError("Unable to retrieve revision date", err, string(out)) } t, err := time.Parse(longForm, string(out)) if err != nil { - return time.Time{}, err + return time.Time{}, NewLocalError("Unable to retrieve revision date", err, string(out)) } return t, nil } // Branches returns a list of available branches on the RemoteLocation func (s *GitRepo) Branches() ([]string, error) { - out, err := s.runFromDir("git", "show-ref") + out, err := s.RunFromDir("git", "show-ref") if err != nil { - return []string{}, err + return []string{}, NewLocalError("Unable to retrieve branches", err, string(out)) } branches := s.referenceList(string(out), `(?m-s)(?:`+s.RemoteLocation+`)/(\S+)$`) return branches, nil @@ -152,9 +192,9 @@ // Tags returns a list of available tags on the RemoteLocation func (s *GitRepo) Tags() ([]string, error) { - out, err := s.runFromDir("git", "show-ref") + out, err := s.RunFromDir("git", "show-ref") if err != nil { - return []string{}, err + return []string{}, NewLocalError("Unable to retrieve tags", err, string(out)) } tags := s.referenceList(string(out), `(?m-s)(?:tags)/(\S+)$`) return tags, nil @@ -172,7 +212,7 @@ // IsReference returns if a string is a reference. A reference can be a // commit id, branch, or tag. func (s *GitRepo) IsReference(r string) bool { - _, err := s.runFromDir("git", "rev-parse", "--verify", r) + _, err := s.RunFromDir("git", "rev-parse", "--verify", r) if err == nil { return true } @@ -180,7 +220,7 @@ // Some refs will fail rev-parse. For example, a remote branch that has // not been checked out yet. This next step should pickup the other // possible references. - _, err = s.runFromDir("git", "show-ref", r) + _, err = s.RunFromDir("git", "show-ref", r) if err == nil { return true } @@ -191,14 +231,14 @@ // IsDirty returns if the checkout has been modified from the checked // out reference. func (s *GitRepo) IsDirty() bool { - out, err := s.runFromDir("git", "diff") + out, err := s.RunFromDir("git", "diff") return err != nil || len(out) != 0 } // CommitInfo retrieves metadata about a commit. func (s *GitRepo) CommitInfo(id string) (*CommitInfo, error) { fm := `--pretty=format:"<logentry><commit>%H</commit><author>%an <%ae></author><date>%aD</date><message>%s</message></logentry>"` - out, err := s.runFromDir("git", "log", id, fm, "-1") + out, err := s.RunFromDir("git", "log", id, fm, "-1") if err != nil { return nil, ErrRevisionUnavailable } @@ -211,12 +251,12 @@ }{} err = xml.Unmarshal(out, &cis) if err != nil { - return nil, err + return nil, NewLocalError("Unable to retrieve commit information", err, string(out)) } t, err := time.Parse("Mon, _2 Jan 2006 15:04:05 -0700", cis.Date) if err != nil { - return nil, err + return nil, NewLocalError("Unable to retrieve commit information", err, string(out)) } ci := &CommitInfo{ @@ -229,6 +269,49 @@ return ci, nil } +// TagsFromCommit retrieves tags from a commit id. +func (s *GitRepo) TagsFromCommit(id string) ([]string, error) { + // This is imperfect and a better method would be great. + + var re []string + + out, err := s.RunFromDir("git", "show-ref", "-d") + if err != nil { + return []string{}, NewLocalError("Unable to retrieve tags", err, string(out)) + } + + lines := strings.Split(string(out), "\n") + var list []string + for _, i := range lines { + if strings.HasPrefix(strings.TrimSpace(i), id) { + list = append(list, i) + } + } + tags := s.referenceList(strings.Join(list, "\n"), `(?m-s)(?:tags)/(\S+)$`) + for _, t := range tags { + re = append(re, t) + } + + return re, nil +} + +// Ping returns if remote location is accessible. +func (s *GitRepo) Ping() bool { + c := exec.Command("git", "ls-remote", s.Remote()) + + // If prompted for a username and password, which GitHub does for all things + // not public, it's considered not available. To make it available the + // remote needs to be different. + c.Env = mergeEnvLists([]string{"GIT_TERMINAL_PROMPT=0"}, os.Environ()) + _, err := c.CombinedOutput() + if err != nil { + return false + } + + return true +} + +// isDetachedHead will detect if git repo is in "detached head" state. func isDetachedHead(dir string) (bool, error) { c := exec.Command("git", "status", "-uno") c.Dir = dir @@ -242,8 +325,9 @@ return detached, nil } -// In a multi-langual manner check for the Git error that it couldn't create -// the directory. +// isUnableToCreateDir checks for an error in Init() to see if an error +// where the parent directory of the VCS local path doesn't exist. This is +// done in a multi-lingual manner. func (s *GitRepo) isUnableToCreateDir(err error) bool { msg := err.Error() if strings.HasPrefix(msg, "could not create work tree dir") ||
diff --git a/vendor/github.com/Masterminds/vcs/git_test.go b/vendor/github.com/Masterminds/vcs/git_test.go index a1a4f93..5fe6259 100644 --- a/vendor/github.com/Masterminds/vcs/git_test.go +++ b/vendor/github.com/Masterminds/vcs/git_test.go
@@ -15,7 +15,6 @@ // with a known git service. func TestGit(t *testing.T) { - tempDir, err := ioutil.TempDir("", "go-vcs-git-tests") if err != nil { t.Error(err) @@ -141,6 +140,22 @@ t.Error("Git tags is not reporting the correct version") } + tags, err = repo.TagsFromCommit("74dd547545b7df4aa285bcec1b54e2b76f726395") + if err != nil { + t.Error(err) + } + if len(tags) != 0 { + t.Error("Git is incorrectly returning tags for a commit") + } + + tags, err = repo.TagsFromCommit("30605f6ac35fcb075ad0bfa9296f90a7d891523e") + if err != nil { + t.Error(err) + } + if len(tags) != 1 || tags[0] != "1.0.0" { + t.Error("Git is incorrectly returning tags for a commit") + } + branches, err := repo.Branches() if err != nil { t.Error(err) @@ -215,3 +230,65 @@ t.Error(nrerr) } } + +func TestGitPing(t *testing.T) { + tempDir, err := ioutil.TempDir("", "go-vcs-git-tests") + if err != nil { + t.Error(err) + } + defer func() { + err = os.RemoveAll(tempDir) + if err != nil { + t.Error(err) + } + }() + + repo, err := NewGitRepo("https://github.com/Masterminds/VCSTestRepo", tempDir) + if err != nil { + t.Error(err) + } + + ping := repo.Ping() + if !ping { + t.Error("Git unable to ping working repo") + } + + repo, err = NewGitRepo("https://github.com/Masterminds/ihopethisneverexistsbecauseitshouldnt", tempDir) + if err != nil { + t.Error(err) + } + + ping = repo.Ping() + if ping { + t.Error("Git got a ping response from when it should not have") + } +} + +func TestGitInit(t *testing.T) { + tempDir, err := ioutil.TempDir("", "go-vcs-git-tests") + repoDir := tempDir + "/repo" + if err != nil { + t.Error(err) + } + defer func() { + err = os.RemoveAll(tempDir) + if err != nil { + t.Error(err) + } + }() + + repo, err := NewGitRepo(repoDir, repoDir) + if err != nil { + t.Error(err) + } + + err = repo.Init() + if err != nil { + t.Error(err) + } + + _, err = repo.RunFromDir("git", "status") + if err != nil { + t.Error(err) + } +}
diff --git a/vendor/github.com/Masterminds/vcs/hg.go b/vendor/github.com/Masterminds/vcs/hg.go index e3fe575..4ce055c 100644 --- a/vendor/github.com/Masterminds/vcs/hg.go +++ b/vendor/github.com/Masterminds/vcs/hg.go
@@ -36,7 +36,7 @@ c.Env = envForDir(c.Dir) out, err := c.CombinedOutput() if err != nil { - return nil, err + return nil, NewLocalError("Unable to retrieve local repo information", err, string(out)) } m := hgDetectURL.FindStringSubmatch(string(out)) @@ -66,31 +66,49 @@ // Get is used to perform an initial clone of a repository. func (s *HgRepo) Get() error { - _, err := s.run("hg", "clone", s.Remote(), s.LocalPath()) - return err + out, err := s.run("hg", "clone", s.Remote(), s.LocalPath()) + if err != nil { + return NewRemoteError("Unable to get repository", err, string(out)) + } + return nil +} + +// Init will initialize a mercurial repository at local location. +func (s *HgRepo) Init() error { + out, err := s.run("hg", "init", s.LocalPath()) + if err != nil { + return NewLocalError("Unable to initialize repository", err, string(out)) + } + return nil } // Update performs a Mercurial pull to an existing checkout. func (s *HgRepo) Update() error { - _, err := s.runFromDir("hg", "update") - return err + out, err := s.RunFromDir("hg", "update") + if err != nil { + return NewRemoteError("Unable to update repository", err, string(out)) + } + return nil } // UpdateVersion sets the version of a package currently checked out via Hg. func (s *HgRepo) UpdateVersion(version string) error { - _, err := s.runFromDir("hg", "pull") + out, err := s.RunFromDir("hg", "pull") if err != nil { - return err + return NewLocalError("Unable to update checked out version", err, string(out)) } - _, err = s.runFromDir("hg", "update", version) - return err + out, err = s.RunFromDir("hg", "update", version) + if err != nil { + return NewLocalError("Unable to update checked out version", err, string(out)) + } + return nil } // Version retrieves the current version. func (s *HgRepo) Version() (string, error) { - out, err := s.runFromDir("hg", "identify") + out, err := s.RunFromDir("hg", "identify") if err != nil { - return "", err + return "", NewLocalError("Unable to retrieve checked out version", err, string(out)) } parts := strings.SplitN(string(out), " ", 2) @@ -102,15 +120,15 @@ func (s *HgRepo) Date() (time.Time, error) { version, err := s.Version() if err != nil { - return time.Time{}, err + return time.Time{}, NewLocalError("Unable to retrieve revision date", err, "") } - out, err := s.runFromDir("hg", "log", "-r", version, "--template", "{date|isodatesec}") + out, err := s.RunFromDir("hg", "log", "-r", version, "--template", "{date|isodatesec}") if err != nil { - return time.Time{}, err + return time.Time{}, NewLocalError("Unable to retrieve revision date", err, string(out)) } t, err := time.Parse(longForm, string(out)) if err != nil { - return time.Time{}, err + return time.Time{}, NewLocalError("Unable to retrieve revision date", err, string(out)) } return t, nil } @@ -126,9 +144,9 @@ // Branches returns a list of available branches func (s *HgRepo) Branches() ([]string, error) { - out, err := s.runFromDir("hg", "branches") + out, err := s.RunFromDir("hg", "branches") if err != nil { - return []string{}, err + return []string{}, NewLocalError("Unable to retrieve branches", err, string(out)) } branches := s.referenceList(string(out), `(?m-s)^(\S+)`) return branches, nil @@ -136,9 +154,9 @@ // Tags returns a list of available tags func (s *HgRepo) Tags() ([]string, error) { - out, err := s.runFromDir("hg", "tags") + out, err := s.RunFromDir("hg", "tags") if err != nil { - return []string{}, err + return []string{}, NewLocalError("Unable to retrieve tags", err, string(out)) } tags := s.referenceList(string(out), `(?m-s)^(\S+)`) return tags, nil @@ -147,7 +165,7 @@ // IsReference returns if a string is a reference. A reference can be a // commit id, branch, or tag. func (s *HgRepo) IsReference(r string) bool { - _, err := s.runFromDir("hg", "log", "-r", r) + _, err := s.RunFromDir("hg", "log", "-r", r) if err == nil { return true } @@ -158,13 +176,13 @@ // IsDirty returns if the checkout has been modified from the checked // out reference. func (s *HgRepo) IsDirty() bool { - out, err := s.runFromDir("hg", "diff") + out, err := s.RunFromDir("hg", "diff") return err != nil || len(out) != 0 } // CommitInfo retrieves metadata about a commit. func (s *HgRepo) CommitInfo(id string) (*CommitInfo, error) { - out, err := s.runFromDir("hg", "log", "-r", id, "--style=xml") + out, err := s.RunFromDir("hg", "log", "-r", id, "--style=xml") if err != nil { return nil, ErrRevisionUnavailable } @@ -187,7 +205,7 @@ logs := &Log{} err = xml.Unmarshal(out, &logs) if err != nil { - return nil, err + return nil, NewLocalError("Unable to retrieve commit information", err, string(out)) } if len(logs.Logs) == 0 { return nil, ErrRevisionUnavailable @@ -202,9 +220,53 @@ if logs.Logs[0].Date != "" { ci.Date, err = time.Parse(time.RFC3339, logs.Logs[0].Date) if err != nil { - return nil, err + return nil, NewLocalError("Unable to retrieve commit information", err, string(out)) } } return ci, nil } + +// TagsFromCommit retrieves tags from a commit id. +func (s *HgRepo) TagsFromCommit(id string) ([]string, error) { + // Hg has a single tag per commit. If a second tag is added to a commit a + // new commit is created and the tag is attached to that new commit. + out, err := s.RunFromDir("hg", "log", "-r", id, "--style=xml") + if err != nil { + return []string{}, NewLocalError("Unable to retrieve tags", err, string(out)) + } + + type Logentry struct { + Node string `xml:"node,attr"` + Tag string `xml:"tag"` + } + type Log struct { + XMLName xml.Name `xml:"log"` + Logs []Logentry `xml:"logentry"` + } + + logs := &Log{} + err = xml.Unmarshal(out, &logs) + if err != nil { + return []string{}, NewLocalError("Unable to retrieve tags", err, string(out)) + } + if len(logs.Logs) == 0 { + return []string{}, NewLocalError("Unable to retrieve tags", err, string(out)) + } + + t := strings.TrimSpace(logs.Logs[0].Tag) + if t != "" { + return []string{t}, nil + } + return []string{}, nil +} + +// Ping returns if remote location is accessible. +func (s *HgRepo) Ping() bool { + _, err := s.run("hg", "identify", s.Remote()) + if err != nil { + return false + } + + return true +}
diff --git a/vendor/github.com/Masterminds/vcs/hg_test.go b/vendor/github.com/Masterminds/vcs/hg_test.go index 9b81937..d63f0d8 100644 --- a/vendor/github.com/Masterminds/vcs/hg_test.go +++ b/vendor/github.com/Masterminds/vcs/hg_test.go
@@ -2,6 +2,7 @@ import ( "io/ioutil" + "strings" "time" //"log" "os" @@ -123,6 +124,22 @@ t.Error("Hg tags is not reporting the correct version") } + tags, err = repo.TagsFromCommit("a5494ba2177f") + if err != nil { + t.Error(err) + } + if len(tags) != 0 { + t.Error("Hg is incorrectly returning tags for a commit") + } + + tags, err = repo.TagsFromCommit("d680e82228d2") + if err != nil { + t.Error(err) + } + if len(tags) != 1 || tags[0] != "1.0.0" { + t.Error("Hg is incorrectly returning tags for a commit") + } + branches, err := repo.Branches() if err != nil { t.Error(err) @@ -199,3 +216,68 @@ t.Error(nrerr) } } + +func TestHgPing(t *testing.T) { + tempDir, err := ioutil.TempDir("", "go-vcs-hg-tests") + if err != nil { + t.Error(err) + } + defer func() { + err = os.RemoveAll(tempDir) + if err != nil { + t.Error(err) + } + }() + + repo, err := NewHgRepo("https://bitbucket.org/mattfarina/testhgrepo", tempDir) + if err != nil { + t.Error(err) + } + + ping := repo.Ping() + if !ping { + t.Error("Hg unable to ping working repo") + } + + repo, err = NewHgRepo("https://bitbucket.org/mattfarina/ihopethisneverexistsbecauseitshouldnt", tempDir) + if err != nil { + t.Error(err) + } + + ping = repo.Ping() + if ping { + t.Error("Hg got a ping response from when it should not have") + } +} + +func TestHgInit(t *testing.T) { + tempDir, err := ioutil.TempDir("", "go-vcs-hg-tests") + repoDir := tempDir + "/repo" + if err != nil { + t.Error(err) + } + defer func() { + err = os.RemoveAll(tempDir) + if err != nil { + t.Error(err) + } + }() + + repo, err := NewHgRepo(repoDir, repoDir) + if err != nil { + t.Error(err) + } + + err = repo.Init() + if err != nil { + t.Error(err) + } + + v, err := repo.Version() + if err != nil { + t.Error(err) + } + if !strings.HasPrefix(v, "000000") { + t.Errorf("Hg Init reporting wrong initial version: %s", v) + } +}
diff --git a/vendor/github.com/Masterminds/vcs/repo.go b/vendor/github.com/Masterminds/vcs/repo.go index 318d1ec..61a39a0 100644 --- a/vendor/github.com/Masterminds/vcs/repo.go +++ b/vendor/github.com/Masterminds/vcs/repo.go
@@ -26,7 +26,6 @@ package vcs import ( - "errors" "fmt" "io/ioutil" "log" @@ -37,22 +36,6 @@ "time" ) -var ( - // ErrWrongVCS is returned when an action is tried on the wrong VCS. - ErrWrongVCS = errors.New("Wrong VCS detected") - - // ErrCannotDetectVCS is returned when VCS cannot be detected from URI string. - ErrCannotDetectVCS = errors.New("Cannot detect VCS") - - // ErrWrongRemote occurs when the passed in remote does not match the VCS - // configured endpoint. - ErrWrongRemote = errors.New("The Remote does not match the VCS endpoint") - - // ErrRevisionUnavailable happens when commit revision information is - // unavailable. - ErrRevisionUnavailable = errors.New("Revision unavailable") -) - // Logger is where you can provide a logger, implementing the log.Logger interface, // where verbose output from each VCS will be written. The default logger does // not log data. To log data supply your own logger or change the output location @@ -97,6 +80,9 @@ // Get is used to perform an initial clone/checkout of a repository. Get() error + // Initializes a new repository locally. + Init() error + // Update performs an update to an existing checkout of a repository. Update() error @@ -118,9 +104,6 @@ // Tags returns a list of available tags on the repository. Tags() ([]string, error) - // TODO: Provide a consistent manner to get reference information across - // multiple VCS. - // IsReference returns if a string is a reference. A reference can be a // commit id, branch, or tag. IsReference(string) bool @@ -131,6 +114,15 @@ // CommitInfo retrieves metadata about a commit. CommitInfo(string) (*CommitInfo, error) + + // TagsFromCommit retrieves tags from a commit id. + TagsFromCommit(string) ([]string, error) + + // Ping returns if remote location is accessible. + Ping() bool + + // RunFromDir executes a command from repo's directory. + RunFromDir(cmd string, args ...string) ([]byte, error) } // NewRepo returns a Repo based on trying to detect the source control from the @@ -219,7 +211,7 @@ return out, err } -func (b *base) runFromDir(cmd string, args ...string) ([]byte, error) { +func (b *base) RunFromDir(cmd string, args ...string) ([]byte, error) { c := exec.Command(cmd, args...) c.Dir = b.local c.Env = envForDir(c.Dir)
diff --git a/vendor/github.com/Masterminds/vcs/svn.go b/vendor/github.com/Masterminds/vcs/svn.go index f14ccf9..85f60b5 100644 --- a/vendor/github.com/Masterminds/vcs/svn.go +++ b/vendor/github.com/Masterminds/vcs/svn.go
@@ -4,6 +4,7 @@ "encoding/xml" "os" "os/exec" + "path/filepath" "regexp" "strings" "time" @@ -35,7 +36,7 @@ // the repo passed in here. out, err := exec.Command("svn", "info", local).CombinedOutput() if err != nil { - return nil, err + return nil, NewLocalError("Unable to retrieve local repo information", err, string(out)) } m := svnDetectURL.FindStringSubmatch(string(out)) @@ -67,28 +68,68 @@ // Note, because SVN isn't distributed this is a checkout without // a clone. func (s *SvnRepo) Get() error { - _, err := s.run("svn", "checkout", s.Remote(), s.LocalPath()) - return err + remote := s.Remote() + if strings.HasPrefix(remote, "/") { + remote = "file://" + remote + } + out, err := s.run("svn", "checkout", remote, s.LocalPath()) + if err != nil { + return NewRemoteError("Unable to get repository", err, string(out)) + } + return nil +} + +// Init will create a svn repository at remote location. +func (s *SvnRepo) Init() error { + out, err := s.run("svnadmin", "create", s.Remote()) + + if err != nil && s.isUnableToCreateDir(err) { + + basePath := filepath.Dir(filepath.FromSlash(s.Remote())) + if _, err := os.Stat(basePath); os.IsNotExist(err) { + err = os.MkdirAll(basePath, 0755) + if err != nil { + return NewLocalError("Unable to initialize repository", err, "") + } + + out, err = s.run("svnadmin", "create", s.Remote()) + if err != nil { + return NewLocalError("Unable to initialize repository", err, string(out)) + } + return nil + } + + } else if err != nil { + return NewLocalError("Unable to initialize repository", err, string(out)) + } + + return nil } // Update performs an SVN update to an existing checkout. func (s *SvnRepo) Update() error { - _, err := s.runFromDir("svn", "update") + out, err := s.RunFromDir("svn", "update") + if err != nil { + return NewRemoteError("Unable to update repository", err, string(out)) + } return err } // UpdateVersion sets the version of a package currently checked out via SVN. func (s *SvnRepo) UpdateVersion(version string) error { - _, err := s.runFromDir("svn", "update", "-r", version) - return err + out, err := s.RunFromDir("svn", "update", "-r", version) + if err != nil { + return NewRemoteError("Unable to update checked out version", err, string(out)) + } + return nil } // Version retrieves the current version. func (s *SvnRepo) Version() (string, error) { - out, err := s.runFromDir("svnversion", ".") + out, err := s.RunFromDir("svnversion", ".") s.log(out) if err != nil { - return "", err + return "", NewLocalError("Unable to retrieve checked out version", err, string(out)) } return strings.TrimSpace(string(out)), nil } @@ -97,16 +138,16 @@ func (s *SvnRepo) Date() (time.Time, error) { version, err := s.Version() if err != nil { - return time.Time{}, err + return time.Time{}, NewLocalError("Unable to retrieve revision date", err, "") } - out, err := s.runFromDir("svn", "pget", "svn:date", "--revprop", "-r", version) + out, err := s.RunFromDir("svn", "pget", "svn:date", "--revprop", "-r", version) if err != nil { - return time.Time{}, err + return time.Time{}, NewLocalError("Unable to retrieve revision date", err, string(out)) } const longForm = "2006-01-02T15:04:05.000000Z\n" t, err := time.Parse(longForm, string(out)) if err != nil { - return time.Time{}, err + return time.Time{}, NewLocalError("Unable to retrieve revision date", err, string(out)) } return t, nil } @@ -144,7 +185,7 @@ // IsReference returns if a string is a reference. A reference is a commit id. // Branches and tags are part of the path. func (s *SvnRepo) IsReference(r string) bool { - out, err := s.runFromDir("svn", "log", "-r", r) + out, err := s.RunFromDir("svn", "log", "-r", r) // This is a complete hack. There must be a better way to do this. Pull // requests welcome. When the reference isn't real you get a line of @@ -162,15 +203,15 @@ // IsDirty returns if the checkout has been modified from the checked // out reference. func (s *SvnRepo) IsDirty() bool { - out, err := s.runFromDir("svn", "diff") + out, err := s.RunFromDir("svn", "diff") return err != nil || len(out) != 0 } // CommitInfo retrieves metadata about a commit. func (s *SvnRepo) CommitInfo(id string) (*CommitInfo, error) { - out, err := s.runFromDir("svn", "log", "-r", id, "--xml") + out, err := s.RunFromDir("svn", "log", "-r", id, "--xml") if err != nil { - return nil, err + return nil, NewRemoteError("Unable to retrieve commit information", err, string(out)) } type Logentry struct { @@ -186,7 +227,7 @@ logs := &Log{} err = xml.Unmarshal(out, &logs) if err != nil { - return nil, err + return nil, NewLocalError("Unable to retrieve commit information", err, string(out)) } if len(logs.Logs) == 0 { return nil, ErrRevisionUnavailable @@ -201,9 +242,37 @@ if len(logs.Logs[0].Date) > 0 { ci.Date, err = time.Parse(time.RFC3339Nano, logs.Logs[0].Date) if err != nil { - return nil, err + return nil, NewLocalError("Unable to retrieve commit information", err, string(out)) } } return ci, nil } + +// TagsFromCommit retrieves tags from a commit id. +func (s *SvnRepo) TagsFromCommit(id string) ([]string, error) { + // Svn tags are a convention implemented as paths. See the details on the + // Tag() method for more information. + return []string{}, nil +} + +// Ping returns if remote location is accessible. +func (s *SvnRepo) Ping() bool { + _, err := s.run("svn", "--non-interactive", "info", s.Remote()) + if err != nil { + return false + } + + return true +} + +// isUnableToCreateDir checks for an error in Init() to see if an error +// where the parent directory of the VCS local path doesn't exist. +func (s *SvnRepo) isUnableToCreateDir(err error) bool { + msg := err.Error() + if strings.HasPrefix(msg, "E000002") { + return true + } + + return false +}
diff --git a/vendor/github.com/Masterminds/vcs/svn_test.go b/vendor/github.com/Masterminds/vcs/svn_test.go index 8c8d0ee..349b072 100644 --- a/vendor/github.com/Masterminds/vcs/svn_test.go +++ b/vendor/github.com/Masterminds/vcs/svn_test.go
@@ -134,6 +134,14 @@ t.Error("Svn is incorrectly returning tags") } + tags, err = repo.TagsFromCommit("2") + if err != nil { + t.Error(err) + } + if len(tags) != 0 { + t.Error("Svn is incorrectly returning tags for a commit") + } + branches, err := repo.Branches() if err != nil { t.Error(err) @@ -207,3 +215,74 @@ t.Error(nrerr) } } + +func TestSvnPing(t *testing.T) { + tempDir, err := ioutil.TempDir("", "go-vcs-svn-tests") + if err != nil { + t.Error(err) + } + defer func() { + err = os.RemoveAll(tempDir) + if err != nil { + t.Error(err) + } + }() + + repo, err := NewSvnRepo("https://github.com/Masterminds/VCSTestRepo/trunk", tempDir) + if err != nil { + t.Error(err) + } + + ping := repo.Ping() + if !ping { + t.Error("Svn unable to ping working repo") + } + + repo, err = NewSvnRepo("https://github.com/Masterminds/ihopethisneverexistsbecauseitshouldnt", tempDir) + if err != nil { + t.Error(err) + } + + ping = repo.Ping() + if ping { + t.Error("Svn got a ping response from when it should not have") + } +} + +func TestSvnInit(t *testing.T) { + tempDir, err := ioutil.TempDir("", "go-vcs-svn-tests") + remoteDir := tempDir + "/remoteDir" + localDir := tempDir + "/localDir" + if err != nil { + t.Error(err) + } + defer func() { + err = os.RemoveAll(tempDir) + if err != nil { + t.Error(err) + } + }() + + repo, err := NewSvnRepo(remoteDir, localDir) + if err != nil { + t.Error(err) + } + + err = repo.Init() + if err != nil { + t.Error(err) + } + + err = repo.Get() + if err != nil { + t.Error(err) + } + + v, err := repo.Version() + if err != nil { + t.Error(err) + } + if v != "0" { + t.Errorf("Svn Init returns wrong version: %s", v) + } +}
diff --git a/vendor/github.com/codegangsta/cli/README.md b/vendor/github.com/codegangsta/cli/README.md index bb769fe..d9371cf 100644 --- a/vendor/github.com/codegangsta/cli/README.md +++ b/vendor/github.com/codegangsta/cli/README.md
@@ -329,6 +329,45 @@ ... ``` +### Subcommands categories + +For additional organization in apps that have many subcommands, you can +associate a category for each command to group them together in the help +output. + +E.g. + +```go +... + app.Commands = []cli.Command{ + { + Name: "noop", + }, + { + Name: "add", + Category: "template", + }, + { + Name: "remove", + Category: "template", + }, + } +... +``` + +Will include: + +``` +... +COMMANDS: + noop + + Template actions: + add + remove +... +``` + ### Bash Completion You can enable completion commands by setting the `EnableBashCompletion`
diff --git a/vendor/github.com/codegangsta/cli/app.go b/vendor/github.com/codegangsta/cli/app.go index 6632ec0..bd20a2d 100644 --- a/vendor/github.com/codegangsta/cli/app.go +++ b/vendor/github.com/codegangsta/cli/app.go
@@ -5,7 +5,8 @@ "io" "io/ioutil" "os" - "path" + "path/filepath" + "sort" "time" ) @@ -34,6 +35,8 @@ HideHelp bool // Boolean to hide built-in version flag and the VERSION section of help HideVersion bool + // Populate on app startup, only gettable throught method Categories() + categories CommandCategories // An action to execute when the bash-completion flag is set BashComplete func(context *Context) // An action to execute before any subcommands are run, but after the context is ready @@ -77,8 +80,8 @@ // Creates a new cli Application with some reasonable defaults for Name, Usage, Version and Action. func NewApp() *App { return &App{ - Name: path.Base(os.Args[0]), - HelpName: path.Base(os.Args[0]), + Name: filepath.Base(os.Args[0]), + HelpName: filepath.Base(os.Args[0]), Usage: "A new cli application", UsageText: "", Version: "0.0.0", @@ -104,6 +107,12 @@ } a.Commands = newCmds + a.categories = CommandCategories{} + for _, command := range a.Commands { + a.categories = a.categories.AddCommand(command.Category, command) + } + sort.Sort(a.categories) + // append help to commands if a.Command(helpCommand.Name) == nil && !a.HideHelp { a.Commands = append(a.Commands, helpCommand) @@ -316,6 +325,11 @@ return nil } +// Returnes the array containing all the categories with the commands they contain +func (a *App) Categories() CommandCategories { + return a.categories +} + func (a *App) hasFlag(flag Flag) bool { for _, f := range a.Flags { if flag == f {
diff --git a/vendor/github.com/codegangsta/cli/app_test.go b/vendor/github.com/codegangsta/cli/app_test.go index 7feaf1f..ebf26c7 100644 --- a/vendor/github.com/codegangsta/cli/app_test.go +++ b/vendor/github.com/codegangsta/cli/app_test.go
@@ -8,6 +8,7 @@ "io" "io/ioutil" "os" + "reflect" "strings" "testing" ) @@ -939,6 +940,55 @@ } } +func TestApp_Run_Categories(t *testing.T) { + app := NewApp() + app.Name = "categories" + app.Commands = []Command{ + Command{ + Name: "command1", + Category: "1", + }, + Command{ + Name: "command2", + Category: "1", + }, + Command{ + Name: "command3", + Category: "2", + }, + } + buf := new(bytes.Buffer) + app.Writer = buf + + app.Run([]string{"categories"}) + + expect := CommandCategories{ + &CommandCategory{ + Name: "1", + Commands: []Command{ + app.Commands[0], + app.Commands[1], + }, + }, + &CommandCategory{ + Name: "2", + Commands: []Command{ + app.Commands[2], + }, + }, + } + if !reflect.DeepEqual(app.Categories(), expect) { + t.Fatalf("expected categories %#v, to equal %#v", app.Categories(), expect) + } + + output := buf.String() + t.Logf("output: %q\n", buf.Bytes()) + + if !strings.Contains(output, "1:\n command1") { + t.Errorf("want buffer to include category %q, did not: \n%q", "1:\n command1", output) + } +} + func TestApp_Run_DoesNotOverwriteErrorFromBefore(t *testing.T) { app := NewApp() app.Action = func(c *Context) {}
diff --git a/vendor/github.com/codegangsta/cli/category.go b/vendor/github.com/codegangsta/cli/category.go new file mode 100644 index 0000000..7dbf218 --- /dev/null +++ b/vendor/github.com/codegangsta/cli/category.go
@@ -0,0 +1,30 @@ +package cli + +type CommandCategories []*CommandCategory + +type CommandCategory struct { + Name string + Commands Commands +} + +func (c CommandCategories) Less(i, j int) bool { + return c[i].Name < c[j].Name +} + +func (c CommandCategories) Len() int { + return len(c) +} + +func (c CommandCategories) Swap(i, j int) { + c[i], c[j] = c[j], c[i] +} + +func (c CommandCategories) AddCommand(category string, command Command) CommandCategories { + for _, commandCategory := range c { + if commandCategory.Name == category { + commandCategory.Commands = append(commandCategory.Commands, command) + return c + } + } + return append(c, &CommandCategory{Name: category, Commands: []Command{command}}) +}
diff --git a/vendor/github.com/codegangsta/cli/command.go b/vendor/github.com/codegangsta/cli/command.go index 0153713..1a05b54 100644 --- a/vendor/github.com/codegangsta/cli/command.go +++ b/vendor/github.com/codegangsta/cli/command.go
@@ -3,6 +3,7 @@ import ( "fmt" "io/ioutil" + "sort" "strings" ) @@ -22,6 +23,8 @@ Description string // A short description of the arguments of this command ArgsUsage string + // The category the command is part of + Category string // The function to call when checking for bash command completions BashComplete func(context *Context) // An action to execute before any sub-subcommands are run, but after the context is ready @@ -37,7 +40,7 @@ // If this function is not set, the "Incorrect usage" is displayed and the execution is interrupted. OnUsageError func(context *Context, err error) error // List of child commands - Subcommands []Command + Subcommands Commands // List of flags to parse Flags []Flag // Treat all flags as normal arguments if true @@ -59,6 +62,8 @@ return strings.Join(c.commandNamePath, " ") } +type Commands []Command + // Invokes the command given the context, parses ctx.Args() to generate command-specific flags func (c Command) Run(ctx *Context) (err error) { if len(c.Subcommands) > 0 { @@ -227,6 +232,13 @@ app.Email = ctx.App.Email app.Writer = ctx.App.Writer + app.categories = CommandCategories{} + for _, command := range c.Subcommands { + app.categories = app.categories.AddCommand(command.Category, command) + } + + sort.Sort(app.categories) + // bash completion app.EnableBashCompletion = ctx.App.EnableBashCompletion if c.BashComplete != nil {
diff --git a/vendor/github.com/codegangsta/cli/help.go b/vendor/github.com/codegangsta/cli/help.go index d3a12a2..adf157d 100644 --- a/vendor/github.com/codegangsta/cli/help.go +++ b/vendor/github.com/codegangsta/cli/help.go
@@ -23,9 +23,10 @@ AUTHOR(S): {{range .Authors}}{{ . }}{{end}} {{end}}{{if .Commands}} -COMMANDS: - {{range .Commands}}{{join .Names ", "}}{{ "\t" }}{{.Usage}} - {{end}}{{end}}{{if .Flags}} +COMMANDS:{{range .Categories}}{{if .Name}} + {{.Name}}{{ ":" }}{{end}}{{range .Commands}} + {{.Name}}{{with .ShortName}}, {{.}}{{end}}{{ "\t" }}{{.Usage}}{{end}} +{{end}}{{end}}{{if .Flags}} GLOBAL OPTIONS: {{range .Flags}}{{.}} {{end}}{{end}}{{if .Copyright }} @@ -41,7 +42,10 @@ {{.HelpName}} - {{.Usage}} USAGE: - {{.HelpName}}{{if .Flags}} [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}}{{if .Description}} + {{.HelpName}}{{if .Flags}} [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}}{{if .Category}} + +CATEGORY: + {{.Category}}{{end}}{{if .Description}} DESCRIPTION: {{.Description}}{{end}}{{if .Flags}} @@ -60,9 +64,10 @@ USAGE: {{.HelpName}} command{{if .Flags}} [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}} -COMMANDS: - {{range .Commands}}{{join .Names ", "}}{{ "\t" }}{{.Usage}} - {{end}}{{if .Flags}} +COMMANDS:{{range .Categories}}{{if .Name}} + {{.Name}}{{ ":" }}{{end}}{{range .Commands}} + {{.Name}}{{with .ShortName}}, {{.}}{{end}}{{ "\t" }}{{.Usage}}{{end}} +{{end}}{{if .Flags}} OPTIONS: {{range .Flags}}{{.}} {{end}}{{end}}