Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

More robust error responses #24

Merged
merged 4 commits into from
Apr 14, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 25 additions & 15 deletions bzr.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ func NewBzrRepo(remote, local string) (*BzrRepo, error) {
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))

Expand Down Expand Up @@ -71,36 +71,46 @@ func (s *BzrRepo) Get() error {
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
}

// 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")
if err != nil {
return "", err
return "", NewLocalError("Unable to retrieve checked out version", err, string(out))
}

return strings.TrimSpace(string(out)), nil
Expand All @@ -110,11 +120,11 @@ func (s *BzrRepo) Version() (string, error) {
func (s *BzrRepo) Date() (time.Time, error) {
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
}
Expand All @@ -141,7 +151,7 @@ func (s *BzrRepo) Branches() ([]string, error) {
func (s *BzrRepo) Tags() ([]string, error) {
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
Expand Down Expand Up @@ -189,7 +199,7 @@ func (s *BzrRepo) CommitInfo(id string) (*CommitInfo, error) {
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
Expand Down
107 changes: 107 additions & 0 deletions errors.go
Original file line number Diff line number Diff line change
@@ -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")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not immediately obvious to me where this would be used. I don't remember all the flows exactly, but isn't this the kind of thing where you'd only encounter this if you try to explicitly use the wrong type on an existing on-disk repo?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ErrWrongVCS is an error that exists today. It happens when the type of VCS you already have cloned locally is mismatched with the remote location type. Say the local VCS is bzr and the endpoint is git.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note, this did show up in Glide when folks migrated from Google Code (Hg) to GitHub.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, totally me just boneheadedly note scrolling far enough down in the diff to see these already existed


// 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
}
36 changes: 36 additions & 0 deletions errors_test.go
Original file line number Diff line number Diff line change
@@ -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")
}
}
50 changes: 30 additions & 20 deletions git.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ func NewGitRepo(remote, local string) (*GitRepo, error) {
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))
Expand Down Expand Up @@ -64,7 +64,7 @@ func (s GitRepo) Vcs() Type {

// 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
Expand All @@ -75,53 +75,63 @@ func (s *GitRepo) Get() error {
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
}

// 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")
if err != nil {
return "", err
return "", NewLocalError("Unable to retrieve checked out version", err, string(out))
}

return strings.TrimSpace(string(out)), nil
Expand All @@ -131,11 +141,11 @@ func (s *GitRepo) Version() (string, error) {
func (s *GitRepo) Date() (time.Time, error) {
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
}
Expand All @@ -144,7 +154,7 @@ func (s *GitRepo) Date() (time.Time, error) {
func (s *GitRepo) Branches() ([]string, error) {
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
Expand All @@ -154,7 +164,7 @@ func (s *GitRepo) Branches() ([]string, error) {
func (s *GitRepo) Tags() ([]string, error) {
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
Expand Down Expand Up @@ -211,12 +221,12 @@ func (s *GitRepo) CommitInfo(id string) (*CommitInfo, error) {
}{}
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{
Expand Down
Loading