grim/govcs

f58bec590cb3
Parents 063c05e63cab
Children ba0d24484add
Allow Git detection to handle repos without any commits yet
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/exec/exec_test.go Thu Mar 08 12:52:20 2018 -0600
@@ -0,0 +1,35 @@
+// govcs
+// Copyright 2017 Gary Kramlich <grim@reaperworld.com>
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+package exec
+
+import (
+ "testing"
+
+ "github.com/aphistic/sweet"
+ "github.com/aphistic/sweet-junit"
+ . "github.com/onsi/gomega"
+)
+
+func TestMain(m *testing.M) {
+ RegisterFailHandler(sweet.GomegaFail)
+
+ sweet.Run(m, func(s *sweet.S) {
+ s.RegisterPlugin(junit.NewPlugin())
+
+ s.AddSuite(&mockSuite{})
+ })
+}
--- a/exec/mock.go Wed Dec 27 23:20:33 2017 -0600
+++ b/exec/mock.go Thu Mar 08 12:52:20 2018 -0600
@@ -17,18 +17,48 @@
//
package exec
-type MockCommand struct {
- stdout []byte
- err error
+import (
+ "sync"
+)
+
+type MockResult struct {
+ Stdout []byte
+ Err error
}
-func NewMockCommand(output string, err error) *MockCommand {
+func NewMockResult(stdout string, err error) *MockResult {
+ return &MockResult{
+ Stdout: []byte(stdout),
+ Err: err,
+ }
+}
+
+type MockCommand struct {
+ outputLock sync.Mutex
+
+ curResult int
+ results []*MockResult
+}
+
+func NewMockCommand(results ...*MockResult) *MockCommand {
return &MockCommand{
- stdout: []byte(output),
- err: err,
+ results: results,
}
}
func (c *MockCommand) Output() ([]byte, error) {
- return c.stdout, c.err
+ c.outputLock.Lock()
+ defer c.outputLock.Unlock()
+
+ if len(c.results) == 0 {
+ return nil, nil
+ }
+
+ res := c.results[c.curResult]
+
+ if c.curResult < len(c.results)-1 {
+ c.curResult++
+ }
+
+ return res.Stdout, res.Err
}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/exec/mock_test.go Thu Mar 08 12:52:20 2018 -0600
@@ -0,0 +1,67 @@
+// govcs
+// Copyright 2017 Gary Kramlich <grim@reaperworld.com>
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+package exec
+
+import (
+ "errors"
+
+ "github.com/aphistic/sweet"
+ . "github.com/onsi/gomega"
+)
+
+type mockSuite struct{}
+
+func (s *mockSuite) TestCommandNoResult(t sweet.T) {
+ cmd := NewMockCommand()
+
+ out, err := cmd.Output()
+ Expect(out).To(BeNil())
+ Expect(err).To(BeNil())
+}
+
+func (s *mockSuite) TestCommandSingleResult(t sweet.T) {
+ cmd := NewMockCommand(
+ NewMockResult("output", errors.New("err")),
+ )
+
+ out, err := cmd.Output()
+ Expect(out).To(Equal([]byte("output")))
+ Expect(err).To(Equal(errors.New("err")))
+
+ out, err = cmd.Output()
+ Expect(out).To(Equal([]byte("output")))
+ Expect(err).To(Equal(errors.New("err")))
+}
+
+func (s *mockSuite) TestCommandMultipleResult(t sweet.T) {
+ cmd := NewMockCommand(
+ NewMockResult("output1", errors.New("err1")),
+ NewMockResult("output2", errors.New("err2")),
+ )
+
+ out, err := cmd.Output()
+ Expect(out).To(Equal([]byte("output1")))
+ Expect(err).To(Equal(errors.New("err1")))
+
+ out, err = cmd.Output()
+ Expect(out).To(Equal([]byte("output2")))
+ Expect(err).To(Equal(errors.New("err2")))
+
+ out, err = cmd.Output()
+ Expect(out).To(Equal([]byte("output2")))
+ Expect(err).To(Equal(errors.New("err2")))
+}
--- a/git/git.go Wed Dec 27 23:20:33 2017 -0600
+++ b/git/git.go Thu Mar 08 12:52:20 2018 -0600
@@ -25,6 +25,21 @@
"bitbucket.org/rw_grim/govcs/vcs"
)
+func handleRevParseError(out string, err error) string {
+ if err == nil {
+ return out
+ }
+
+ // This handles the case where a user is in a git repo without any
+ // commits yet. Git returns an error as if it's outside the repo
+ // but still prints "HEAD" to stdout.
+ if err != nil && out == "HEAD" {
+ return out
+ }
+
+ return ""
+}
+
// Git is an instance of vcs.VCS that will operate on a Git repository.
type Git struct {
wd string
@@ -57,32 +72,55 @@
name = "origin"
}
- return g.runCmd([]string{"config", "--local", "remote." + name + ".url"})
+ out, err := g.runCmd([]string{"config", "--local", "remote." + name + ".url"})
+ if err != nil {
+ return ""
+ }
+
+ return out
}
// Root will return the root of the repository.
func (g *Git) Root() string {
- return g.runCmd([]string{"rev-parse", "--show-toplevel"})
+ out, err := g.runCmd([]string{"rev-parse", "--show-toplevel"})
+ if err != nil {
+ return ""
+ }
+ return out
}
// Commit will return the commit id of the repository.
func (g *Git) Commit() string {
- return g.runCmd([]string{"rev-parse", "HEAD"})
+ out, err := g.runCmd([]string{"rev-parse", "HEAD"})
+ return handleRevParseError(out, err)
}
// ShortCommit returns the short commit id of the repository.
func (g *Git) ShortCommit() string {
- return g.runCmd([]string{"rev-parse", "--short", "HEAD"})
+ out, err := g.runCmd([]string{"rev-parse", "--short", "HEAD"})
+ if err != nil {
+ // If we can't get a short commit hash, try to get the full one.
+ // This is here to handle the case where a repo doesn't have any commits
+ // yet and returns a failure for the --short option but returns HEAD
+ // for the full commit id.
+ return g.Commit()
+ }
+
+ return out
}
// Branch returns the name of the checked out branch.
func (g *Git) Branch() string {
- return g.runCmd([]string{"rev-parse", "--abbrev-ref", "HEAD"})
+ out, err := g.runCmd([]string{"rev-parse", "--abbrev-ref", "HEAD"})
+ return handleRevParseError(out, err)
}
// Config will look up the given key and return a string slice of the values.
func (g *Git) Config(key string) []string {
- output := g.runCmd([]string{"config", key})
+ output, err := g.runCmd([]string{"config", key})
+ if err != nil {
+ output = ""
+ }
return strings.Split(output, "\n")
}
@@ -90,15 +128,12 @@
// runCmd will run the git command without the users config, it will also set
// working directory. All you need to pass in is a string slice of the
// subcommand that you want to run.
-func (g *Git) runCmd(cmdv []string) string {
+func (g *Git) runCmd(cmdv []string) (string, error) {
os.Setenv("GIT_CONFIG_NOSYSTEM", "1")
args := append([]string{"-C", g.wd}, cmdv...)
cmd := exec.Command("git", args...)
output, err := cmd.Output()
- if err != nil {
- return ""
- }
- return strings.TrimSpace(string(output))
+ return strings.TrimSpace(string(output)), err
}
--- a/git/git_test.go Wed Dec 27 23:20:33 2017 -0600
+++ b/git/git_test.go Thu Mar 08 12:52:20 2018 -0600
@@ -50,7 +50,7 @@
}
func (s *gitSuite) TestDetectFound(t sweet.T) {
- backend := s.setup(exec.NewMockCommand("found", nil))
+ backend := s.setup(exec.NewMockCommand(exec.NewMockResult("found", nil)))
defer exec.SetBackend(backend)
vcs := Detect("working-directory")
@@ -62,7 +62,7 @@
}
func (s *gitSuite) TestDetectNotFound(t sweet.T) {
- backend := s.setup(exec.NewMockCommand("", errors.New("not found")))
+ backend := s.setup(exec.NewMockCommand(exec.NewMockResult("", errors.New("not found"))))
defer exec.SetBackend(backend)
vcs := Detect("")
@@ -70,7 +70,7 @@
}
func (s *gitSuite) TestRemoteEmpty(t sweet.T) {
- backend := s.setup(exec.NewMockCommand("https://bitbucket.org/rw_grim/covcs\n", nil))
+ backend := s.setup(exec.NewMockCommand(exec.NewMockResult("https://bitbucket.org/rw_grim/covcs\n", nil)))
defer exec.SetBackend(backend)
git := Git{}
@@ -79,7 +79,7 @@
}
func (s *gitSuite) TestRemoteNamed(t sweet.T) {
- backend := s.setup(exec.NewMockCommand("https://bitbucket.org/rw_grim/covcs2\n", nil))
+ backend := s.setup(exec.NewMockCommand(exec.NewMockResult("https://bitbucket.org/rw_grim/covcs2\n", nil)))
defer exec.SetBackend(backend)
git := Git{}
@@ -88,7 +88,7 @@
func (s *gitSuite) TestRoot(t sweet.T) {
path := "/home/foo/go/src/github.com/bar/baz"
- backend := s.setup(exec.NewMockCommand(path+"\n", nil))
+ backend := s.setup(exec.NewMockCommand(exec.NewMockResult(path+"\n", nil)))
defer exec.SetBackend(backend)
git := Git{}
@@ -96,15 +96,36 @@
}
func (s *gitSuite) TestCommit(t sweet.T) {
- backend := s.setup(exec.NewMockCommand("deadbeef", nil))
+ backend := s.setup(exec.NewMockCommand(exec.NewMockResult("deadbeef", nil)))
defer exec.SetBackend(backend)
git := Git{}
Expect(git.Commit()).To(Equal("deadbeef"))
}
+func (s *gitSuite) TestCommitOnEmptyRepo(t sweet.T) {
+ backend := s.setup(exec.NewMockCommand(exec.NewMockResult("HEAD", errors.New("exited"))))
+ defer exec.SetBackend(backend)
+
+ git := Git{}
+ Expect(git.Commit()).To(Equal("HEAD"))
+}
+
+func (s *gitSuite) TestShortCommitOnEmptyRepo(t sweet.T) {
+ backend := s.setup(exec.NewMockCommand(
+ // Short commit returns an error and an empty output
+ exec.NewMockResult("", errors.New("exited")),
+ // We then call normal commit and that returns HEAD and an error
+ exec.NewMockResult("HEAD", errors.New("exited")),
+ ))
+ defer exec.SetBackend(backend)
+
+ git := Git{}
+ Expect(git.ShortCommit()).To(Equal("HEAD"))
+}
+
func (s *gitSuite) TestBranch(t sweet.T) {
- backend := s.setup(exec.NewMockCommand("my-fancy-branch", nil))
+ backend := s.setup(exec.NewMockCommand(exec.NewMockResult("my-fancy-branch", nil)))
defer exec.SetBackend(backend)
git := Git{}
@@ -112,7 +133,7 @@
}
func (s *gitSuite) TestConfigUserEmail(t sweet.T) {
- backend := s.setup(exec.NewMockCommand("grim@reaperworld.com\n", nil))
+ backend := s.setup(exec.NewMockCommand(exec.NewMockResult("grim@reaperworld.com\n", nil)))
defer exec.SetBackend(backend)
git := Git{}
--- a/govcs_test.go Wed Dec 27 23:20:33 2017 -0600
+++ b/govcs_test.go Thu Mar 08 12:52:20 2018 -0600
@@ -50,8 +50,10 @@
}
return exec.NewMockCommand(
- "",
- errors.New("not a "+cmdName+" repo"),
+ exec.NewMockResult(
+ "",
+ errors.New("not a "+cmdName+" repo"),
+ ),
)
})
@@ -59,7 +61,7 @@
}
func (s *vcsSuite) TestFindGitFromGit(t sweet.T) {
- backend := s.setup("git", exec.NewMockCommand("found", nil))
+ backend := s.setup("git", exec.NewMockCommand(exec.NewMockResult("found", nil)))
defer exec.SetBackend(backend)
vcs, err := Detect("working-directory")
@@ -72,7 +74,7 @@
}
func (s *vcsSuite) TestFindHgFromHg(t sweet.T) {
- backend := s.setup("hg", exec.NewMockCommand("found", nil))
+ backend := s.setup("hg", exec.NewMockCommand(exec.NewMockResult("found", nil)))
defer exec.SetBackend(backend)
vcs, err := Detect("working-directory")
@@ -85,7 +87,7 @@
}
func (s *vcsSuite) TestFindNoRepository(t sweet.T) {
- backend := s.setup("", exec.NewMockCommand("", errors.New("not a repo")))
+ backend := s.setup("", exec.NewMockCommand(exec.NewMockResult("", errors.New("not a repo"))))
defer exec.SetBackend(backend)
vcs, err := Detect("working-directory")
--- a/hg/hg_test.go Wed Dec 27 23:20:33 2017 -0600
+++ b/hg/hg_test.go Thu Mar 08 12:52:20 2018 -0600
@@ -50,7 +50,7 @@
}
func (s *hgSuite) TestDetectFound(t sweet.T) {
- backend := s.setup(exec.NewMockCommand("found", nil))
+ backend := s.setup(exec.NewMockCommand(exec.NewMockResult("found", nil)))
defer exec.SetBackend(backend)
vcs := Detect("working-directory")
@@ -62,7 +62,7 @@
}
func (s *hgSuite) TestDetectNotFound(t sweet.T) {
- backend := s.setup(exec.NewMockCommand("", errors.New("not found")))
+ backend := s.setup(exec.NewMockCommand(exec.NewMockResult("", errors.New("not found"))))
defer exec.SetBackend(backend)
vcs := Detect("")
@@ -70,7 +70,7 @@
}
func (s *hgSuite) TestRemoteEmpty(t sweet.T) {
- backend := s.setup(exec.NewMockCommand("mypath\n", nil))
+ backend := s.setup(exec.NewMockCommand(exec.NewMockResult("mypath\n", nil)))
defer exec.SetBackend(backend)
hg := Mercurial{}
@@ -79,7 +79,7 @@
}
func (s *hgSuite) TestRemoteNamed(t sweet.T) {
- backend := s.setup(exec.NewMockCommand("myupstream\n", nil))
+ backend := s.setup(exec.NewMockCommand(exec.NewMockResult("myupstream\n", nil)))
defer exec.SetBackend(backend)
hg := Mercurial{}
@@ -88,7 +88,7 @@
func (s *hgSuite) TestRoot(t sweet.T) {
path := "/home/foo/go/src/bitbucket.org/bar/baz"
- backend := s.setup(exec.NewMockCommand(path+"\n", nil))
+ backend := s.setup(exec.NewMockCommand(exec.NewMockResult(path+"\n", nil)))
defer exec.SetBackend(backend)
hg := Mercurial{}
@@ -96,7 +96,7 @@
}
func (s *hgSuite) TestCommit(t sweet.T) {
- backend := s.setup(exec.NewMockCommand("deadbeef", nil))
+ backend := s.setup(exec.NewMockCommand(exec.NewMockResult("deadbeef", nil)))
defer exec.SetBackend(backend)
hg := Mercurial{}
@@ -104,7 +104,7 @@
}
func (s *hgSuite) TestBranch(t sweet.T) {
- backend := s.setup(exec.NewMockCommand("my-fancy-branch", nil))
+ backend := s.setup(exec.NewMockCommand(exec.NewMockResult("my-fancy-branch", nil)))
defer exec.SetBackend(backend)
hg := Mercurial{}
@@ -112,7 +112,7 @@
}
func (s *hgSuite) TestBookmark(t sweet.T) {
- backend := s.setup(exec.NewMockCommand("my-fancy-bookmark", nil))
+ backend := s.setup(exec.NewMockCommand(exec.NewMockResult("my-fancy-bookmark", nil)))
defer exec.SetBackend(backend)
hg := Mercurial{}
@@ -120,7 +120,9 @@
}
func (s *hgSuite) TestConfigPaths(t sweet.T) {
- backend := s.setup(exec.NewMockCommand("paths.default=default\npaths.upstream=upstream\n", nil))
+ backend := s.setup(exec.NewMockCommand(
+ exec.NewMockResult("paths.default=default\npaths.upstream=upstream\n", nil),
+ ))
defer exec.SetBackend(backend)
hg := Mercurial{}
--- a/svn/svn.go Wed Dec 27 23:20:33 2017 -0600
+++ b/svn/svn.go Thu Mar 08 12:52:20 2018 -0600
@@ -19,7 +19,6 @@
import (
"encoding/xml"
- "fmt"
"strings"
"bitbucket.org/rw_grim/govcs/exec"
@@ -52,7 +51,6 @@
err := s.info()
if err != nil {
- fmt.Printf("%#v\n", err)
return nil
}
@@ -105,7 +103,6 @@
var raw rawSubversion
err = xml.Unmarshal(output, &raw)
- fmt.Printf("rawSubversion: %#v\n", raw)
if err != nil {
return err
}
--- a/svn/svn_test.go Wed Dec 27 23:20:33 2017 -0600
+++ b/svn/svn_test.go Thu Mar 08 12:52:20 2018 -0600
@@ -77,7 +77,7 @@
}
func (s *svnSuite) TestDetectFound(t sweet.T) {
- backend := s.setup(exec.NewMockCommand(data, nil))
+ backend := s.setup(exec.NewMockCommand(exec.NewMockResult(data, nil)))
defer exec.SetBackend(backend)
vcs := Detect("working-directory")
@@ -95,7 +95,7 @@
}
func (s *svnSuite) TestDetectNotFound(t sweet.T) {
- backend := s.setup(exec.NewMockCommand("", errors.New("not found")))
+ backend := s.setup(exec.NewMockCommand(exec.NewMockResult("", errors.New("not found"))))
defer exec.SetBackend(backend)
vcs := Detect("")