Skip to content

Commit

Permalink
Add pipe package, streamline error handling in vcs
Browse files Browse the repository at this point in the history
  • Loading branch information
JohnStarich committed Nov 20, 2019
1 parent dcd027f commit aa28182
Show file tree
Hide file tree
Showing 4 changed files with 158 additions and 60 deletions.
27 changes: 27 additions & 0 deletions pipe/pipe.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package pipe

// Op is the common pipe operation. Can be composed into Ops and run as a single unit
type Op interface {
Do() error
}

// OpFunc can easily wrap an anonymous function into an Op
type OpFunc func() error

// Do implements the Op interface
func (o OpFunc) Do() error {
return o()
}

// Ops can run a slice of Op's in series, stopping on the first error
type Ops []Op

// Do implements the Op interface
func (ops Ops) Do() error {
for _, op := range ops {
if err := op.Do(); err != nil {
return err
}
}
return nil
}
54 changes: 54 additions & 0 deletions pipe/pipe_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package pipe

import (
"errors"
"testing"

"github.com/stretchr/testify/assert"
)

func TestOpFuncDo(t *testing.T) {
t.Run("no error", func(t *testing.T) {
op := OpFunc(func() error {
return nil
})
assert.NoError(t, op.Do())
})

t.Run("error", func(t *testing.T) {
someErr := errors.New("some error")
op := OpFunc(func() error {
return someErr
})
assert.Equal(t, someErr, op.Do())
})
}

func TestOpsDo(t *testing.T) {
nilOp := OpFunc(func() error {
return nil
})
someErr := errors.New("some error")
errOp := OpFunc(func() error {
return someErr
})

detectOp := func(ran *bool) Op {
return OpFunc(func() error {
*ran = true
return nil
})
}

t.Run("no errors", func(t *testing.T) {
var ranLast bool
assert.NoError(t, Ops{nilOp, nilOp, nilOp, detectOp(&ranLast)}.Do())
assert.True(t, ranLast)
})

t.Run("stops on first error", func(t *testing.T) {
var ranAfterError bool
assert.Equal(t, someErr, Ops{nilOp, errOp, detectOp(&ranAfterError), nilOp}.Do())
assert.False(t, ranAfterError)
})
}
73 changes: 40 additions & 33 deletions vcs/vcs.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"sync"
"time"

"github.com/johnstarich/sage/pipe"
"github.com/pkg/errors"
"gopkg.in/src-d/go-git.v4"
"gopkg.in/src-d/go-git.v4/plumbing"
Expand Down Expand Up @@ -42,40 +43,46 @@ type syncRepo struct {
}

func initVCS(path string) (*git.Repository, error) {
repo, err := git.PlainInit(path, false)
if err != nil {
return nil, err
}
tree, err := repo.Worktree()
if err != nil {
return nil, err
}

status, err := tree.Status()
if err != nil {
return nil, err
}

added := false
for file, stat := range status {
// add any untracked files, excluding hidden and tmp files
if stat.Worktree == git.Untracked && !strings.HasPrefix(file, ".") && !strings.HasSuffix(file, ".tmp") {
_, err := tree.Add(file)
if err != nil {
return nil, err
var err error
var repo *git.Repository
var tree *git.Worktree
var status git.Status
return repo, pipe.Ops{
pipe.OpFunc(func() error {
repo, err = git.PlainInit(path, false)
return err
}),
pipe.OpFunc(func() error {
tree, err = repo.Worktree()
return err
}),
pipe.OpFunc(func() error {
status, err = tree.Status()
return err
}),
pipe.OpFunc(func() error {
var ops pipe.Ops
added := false
for file, stat := range status {
// add any untracked files, excluding hidden and tmp files
if stat.Worktree == git.Untracked && !strings.HasPrefix(file, ".") && !strings.HasSuffix(file, ".tmp") {
fileCopy := file
ops = append(ops, pipe.OpFunc(func() error {
_, err := tree.Add(fileCopy)
return err
}))
added = true
}
}
added = true
}
}
if added {
_, err := tree.Commit("Initial commit", &git.CommitOptions{
Author: sageAuthor(),
})
if err != nil {
return nil, err
}
}
return repo, nil
if added {
ops = append(ops, pipe.OpFunc(func() error {
_, err := tree.Commit("Initial commit", &git.CommitOptions{Author: sageAuthor()})
return err
}))
}
return ops.Do()
}),
}.Do()
}

func sageAuthor() *object.Signature {
Expand Down
64 changes: 37 additions & 27 deletions vcs/vcs_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package vcs
import (
"io/ioutil"
"os"
"path/filepath"
"testing"

"github.com/stretchr/testify/assert"
Expand All @@ -11,18 +12,22 @@ import (
"gopkg.in/src-d/go-git.v4/plumbing/object"
)

const testDBPath = "./testdb"

func cleanupTestDB(t *testing.T) {
t.Helper()
require.NoError(t, os.RemoveAll(testDBPath))
}

func TestOpen(t *testing.T) {
cleanup := func() {
require.NoError(t, os.RemoveAll("./testdb"))
}
cleanup()
defer cleanup()
err := os.MkdirAll("./testdb", 0750)
cleanupTestDB(t)
defer cleanupTestDB(t)
err := os.MkdirAll(testDBPath, 0750)
require.NoError(t, err)
err = ioutil.WriteFile("./testdb/bucket.json", []byte(`{}`), 0750)
err = ioutil.WriteFile(filepath.Join(testDBPath, "bucket.json"), []byte(`{}`), 0750)
require.NoError(t, err)

repoInt, err := Open("./testdb")
repoInt, err := Open(testDBPath)
require.NoError(t, err)
require.IsType(t, &syncRepo{}, repoInt)

Expand All @@ -49,21 +54,29 @@ func TestOpen(t *testing.T) {
assert.Equal(t, 1, count)
}

func TestOpenMkdirErr(t *testing.T) {
cleanupTestDB(t)
defer cleanupTestDB(t)

err := ioutil.WriteFile(testDBPath, []byte(`I'm not a database!`), 0750)
require.NoError(t, err)
_, err = Open(testDBPath)
require.Error(t, err)
assert.Equal(t, "mkdir testdb: not a directory", err.Error())
}

func TestCommitFiles(t *testing.T) {
cleanup := func() {
require.NoError(t, os.RemoveAll("./testdb"))
}
cleanup()
defer cleanup()
cleanupTestDB(t)
defer cleanupTestDB(t)

repoInt, err := Open("./testdb")
repoInt, err := Open(testDBPath)
require.NoError(t, err)
require.IsType(t, &syncRepo{}, repoInt)
repo := repoInt.(*syncRepo)

err = repo.CommitFiles(func() error {
return ioutil.WriteFile("./testdb/some file.txt", []byte("hello world"), 0750)
}, "add some file", "./testdb/some file.txt")
return ioutil.WriteFile(filepath.Join(testDBPath, "some file.txt"), []byte("hello world"), 0750)
}, "add some file", filepath.Join(testDBPath, "some file.txt"))
require.NoError(t, err)

getCount := func() int {
Expand All @@ -81,27 +94,24 @@ func TestCommitFiles(t *testing.T) {
assert.Equal(t, 1, getCount())

err = repo.CommitFiles(func() error {
return ioutil.WriteFile("./testdb/some other file.txt", []byte("hello world"), 0750)
}, "add some other file", "./testdb/some other file.txt")
return ioutil.WriteFile(filepath.Join(testDBPath, "some other file.txt"), []byte("hello world"), 0750)
}, "add some other file", filepath.Join(testDBPath, "some other file.txt"))
require.NoError(t, err)
assert.Equal(t, 2, getCount())
}

func TestCommitNoChanges(t *testing.T) {
cleanup := func() {
require.NoError(t, os.RemoveAll("./testdb"))
}
cleanup()
defer cleanup()
cleanupTestDB(t)
defer cleanupTestDB(t)

repoInt, err := Open("./testdb")
repoInt, err := Open(testDBPath)
require.NoError(t, err)
require.IsType(t, &syncRepo{}, repoInt)
repo := repoInt.(*syncRepo)

err = repo.CommitFiles(func() error {
return ioutil.WriteFile("./testdb/some file.txt", []byte("hello world"), 0750)
}, "add some file", "./testdb/some file.txt")
return ioutil.WriteFile(filepath.Join(testDBPath, "some file.txt"), []byte("hello world"), 0750)
}, "add some file", filepath.Join(testDBPath, "some file.txt"))
require.NoError(t, err)

getCount := func() int {
Expand All @@ -118,7 +128,7 @@ func TestCommitNoChanges(t *testing.T) {
}
assert.Equal(t, 1, getCount())

err = repo.CommitFiles(func() error { return nil }, "add same file", "./testdb/some file.txt")
err = repo.CommitFiles(func() error { return nil }, "add same file", filepath.Join(testDBPath, "some file.txt"))
require.NoError(t, err)
assert.Equal(t, 1, getCount(), "no commit should be made for unchanged file")
}

0 comments on commit aa28182

Please sign in to comment.