-
Notifications
You must be signed in to change notification settings - Fork 534
storage: transactional, new storage with transactional capabilities #1006
Changes from 1 commit
ff04a1d
3052df3
a2b39f5
12dc3ef
9631774
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
package transactional | ||
|
||
import "gopkg.in/src-d/go-git.v4/config" | ||
|
||
type ConfigStorage struct { | ||
config.ConfigStorer | ||
temporal config.ConfigStorer | ||
|
||
set bool | ||
} | ||
|
||
func NewConfigStorage(s, temporal config.ConfigStorer) *ConfigStorage { | ||
return &ConfigStorage{ConfigStorer: s, temporal: temporal} | ||
} | ||
|
||
func (c *ConfigStorage) SetConfig(cfg *config.Config) error { | ||
if err := c.temporal.SetConfig(cfg); err != nil { | ||
return err | ||
} | ||
|
||
c.set = true | ||
return nil | ||
} | ||
|
||
func (c *ConfigStorage) Commit() error { | ||
if !c.set { | ||
return nil | ||
} | ||
|
||
cfg, err := c.temporal.Config() | ||
if err != nil { | ||
return err | ||
} | ||
|
||
return c.ConfigStorer.SetConfig(cfg) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,70 @@ | ||
package transactional | ||
|
||
import ( | ||
"testing" | ||
|
||
. "gopkg.in/check.v1" | ||
"gopkg.in/src-d/go-git.v4/config" | ||
"gopkg.in/src-d/go-git.v4/storage/memory" | ||
) | ||
|
||
func Test(t *testing.T) { TestingT(t) } | ||
|
||
var _ = Suite(&ConfigSuite{}) | ||
|
||
type ConfigSuite struct{} | ||
|
||
func (s *ConfigSuite) TestSetConfig(c *C) { | ||
cfg := config.NewConfig() | ||
cfg.Core.Worktree = "foo" | ||
|
||
base := memory.NewStorage() | ||
err := base.SetConfig(cfg) | ||
c.Assert(err, IsNil) | ||
|
||
temporal := memory.NewStorage() | ||
|
||
cfg = config.NewConfig() | ||
cfg.Core.Worktree = "bar" | ||
|
||
cs := NewConfigStorage(base, temporal) | ||
err = cs.SetConfig(cfg) | ||
c.Assert(err, IsNil) | ||
|
||
baseCfg, err := base.Config() | ||
c.Assert(err, IsNil) | ||
c.Assert(baseCfg.Core.Worktree, Equals, "foo") | ||
|
||
temporalCfg, err := temporal.Config() | ||
c.Assert(err, IsNil) | ||
c.Assert(temporalCfg.Core.Worktree, Equals, "bar") | ||
|
||
cfg, err = cs.Config() | ||
c.Assert(err, IsNil) | ||
c.Assert(temporalCfg.Core.Worktree, Equals, "bar") | ||
} | ||
|
||
func (s *ConfigSuite) TestCommit(c *C) { | ||
cfg := config.NewConfig() | ||
cfg.Core.Worktree = "foo" | ||
|
||
base := memory.NewStorage() | ||
err := base.SetConfig(cfg) | ||
c.Assert(err, IsNil) | ||
|
||
temporal := memory.NewStorage() | ||
|
||
cfg = config.NewConfig() | ||
cfg.Core.Worktree = "bar" | ||
|
||
cs := NewConfigStorage(base, temporal) | ||
err = cs.SetConfig(cfg) | ||
c.Assert(err, IsNil) | ||
|
||
err = cs.Commit() | ||
c.Assert(err, IsNil) | ||
|
||
baseCfg, err := base.Config() | ||
c.Assert(err, IsNil) | ||
c.Assert(baseCfg.Core.Worktree, Equals, "bar") | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
package transactional | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No |
||
|
||
import ( | ||
"gopkg.in/src-d/go-git.v4/plumbing" | ||
"gopkg.in/src-d/go-git.v4/plumbing/storer" | ||
) | ||
|
||
type ObjectStorage struct { | ||
storer.EncodedObjectStorer | ||
temporal storer.EncodedObjectStorer | ||
} | ||
|
||
func NewObjectStorage(s, temporal storer.EncodedObjectStorer) *ObjectStorage { | ||
return &ObjectStorage{EncodedObjectStorer: s, temporal: temporal} | ||
} | ||
|
||
func (o *ObjectStorage) SetEncodedObject(obj plumbing.EncodedObject) (plumbing.Hash, error) { | ||
return o.temporal.SetEncodedObject(obj) | ||
} | ||
|
||
func (o *ObjectStorage) HasEncodedObject(h plumbing.Hash) error { | ||
err := o.EncodedObjectStorer.HasEncodedObject(h) | ||
if err == plumbing.ErrObjectNotFound { | ||
return o.temporal.HasEncodedObject(h) | ||
} | ||
|
||
return err | ||
} | ||
|
||
func (o *ObjectStorage) EncodedObjectSize(h plumbing.Hash) (int64, error) { | ||
sz, err := o.EncodedObjectStorer.EncodedObjectSize(h) | ||
if err == plumbing.ErrObjectNotFound { | ||
return o.temporal.EncodedObjectSize(h) | ||
} | ||
|
||
return sz, err | ||
} | ||
|
||
func (o *ObjectStorage) EncodedObject(t plumbing.ObjectType, h plumbing.Hash) (plumbing.EncodedObject, error) { | ||
obj, err := o.EncodedObjectStorer.EncodedObject(t, h) | ||
if err == plumbing.ErrObjectNotFound { | ||
return o.temporal.EncodedObject(t, h) | ||
} | ||
|
||
return obj, err | ||
} | ||
|
||
func (o *ObjectStorage) IterEncodedObjects(t plumbing.ObjectType) (storer.EncodedObjectIter, error) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Mixed concepts here. If we are talking about Also, depending on the use case, maybe we want to do the opposite, a There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It should be ok to read objects in a transaction. Just like in SQL. As long as read in a transaction sees writes in the same transaction, and does not read objects written in a different transaction. |
||
baseIter, err := o.EncodedObjectStorer.IterEncodedObjects(t) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
temporalIter, err := o.temporal.IterEncodedObjects(t) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
return storer.NewMultiEncodedObjectIter([]storer.EncodedObjectIter{ | ||
baseIter, | ||
temporalIter, | ||
}), nil | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,119 @@ | ||
package transactional | ||
|
||
import ( | ||
. "gopkg.in/check.v1" | ||
"gopkg.in/src-d/go-git.v4/plumbing" | ||
"gopkg.in/src-d/go-git.v4/storage/memory" | ||
) | ||
|
||
var _ = Suite(&ObjectSuite{}) | ||
|
||
type ObjectSuite struct{} | ||
|
||
func (s *ObjectSuite) TestHasEncodedObject(c *C) { | ||
base := memory.NewStorage() | ||
temporal := memory.NewStorage() | ||
|
||
os := NewObjectStorage(base, temporal) | ||
|
||
commit := base.NewEncodedObject() | ||
commit.SetType(plumbing.CommitObject) | ||
|
||
ch, err := base.SetEncodedObject(commit) | ||
c.Assert(ch.IsZero(), Equals, false) | ||
c.Assert(err, IsNil) | ||
|
||
tree := base.NewEncodedObject() | ||
tree.SetType(plumbing.TreeObject) | ||
|
||
th, err := os.SetEncodedObject(tree) | ||
c.Assert(th.IsZero(), Equals, false) | ||
c.Assert(err, IsNil) | ||
|
||
err = os.HasEncodedObject(th) | ||
c.Assert(err, IsNil) | ||
|
||
err = os.HasEncodedObject(ch) | ||
c.Assert(err, IsNil) | ||
|
||
err = base.HasEncodedObject(th) | ||
c.Assert(err, Equals, plumbing.ErrObjectNotFound) | ||
} | ||
|
||
func (s *ObjectSuite) TestEncodedObjectAndEncodedObjectSize(c *C) { | ||
base := memory.NewStorage() | ||
temporal := memory.NewStorage() | ||
|
||
os := NewObjectStorage(base, temporal) | ||
|
||
commit := base.NewEncodedObject() | ||
commit.SetType(plumbing.CommitObject) | ||
|
||
ch, err := base.SetEncodedObject(commit) | ||
c.Assert(ch.IsZero(), Equals, false) | ||
c.Assert(err, IsNil) | ||
|
||
tree := base.NewEncodedObject() | ||
tree.SetType(plumbing.TreeObject) | ||
|
||
th, err := os.SetEncodedObject(tree) | ||
c.Assert(th.IsZero(), Equals, false) | ||
c.Assert(err, IsNil) | ||
|
||
otree, err := os.EncodedObject(plumbing.TreeObject, th) | ||
c.Assert(err, IsNil) | ||
c.Assert(otree.Hash(), Equals, tree.Hash()) | ||
|
||
treeSz, err := os.EncodedObjectSize(th) | ||
c.Assert(err, IsNil) | ||
c.Assert(treeSz, Equals, int64(0)) | ||
|
||
ocommit, err := os.EncodedObject(plumbing.CommitObject, ch) | ||
c.Assert(err, IsNil) | ||
c.Assert(ocommit.Hash(), Equals, commit.Hash()) | ||
|
||
commitSz, err := os.EncodedObjectSize(ch) | ||
c.Assert(err, IsNil) | ||
c.Assert(commitSz, Equals, int64(0)) | ||
|
||
_, err = base.EncodedObject(plumbing.TreeObject, th) | ||
c.Assert(err, Equals, plumbing.ErrObjectNotFound) | ||
|
||
_, err = base.EncodedObjectSize(th) | ||
c.Assert(err, Equals, plumbing.ErrObjectNotFound) | ||
} | ||
|
||
func (s *ObjectSuite) TestIterEncodedObjects(c *C) { | ||
base := memory.NewStorage() | ||
temporal := memory.NewStorage() | ||
|
||
os := NewObjectStorage(base, temporal) | ||
|
||
commit := base.NewEncodedObject() | ||
commit.SetType(plumbing.CommitObject) | ||
|
||
ch, err := base.SetEncodedObject(commit) | ||
c.Assert(ch.IsZero(), Equals, false) | ||
c.Assert(err, IsNil) | ||
|
||
tree := base.NewEncodedObject() | ||
tree.SetType(plumbing.TreeObject) | ||
|
||
th, err := os.SetEncodedObject(tree) | ||
c.Assert(th.IsZero(), Equals, false) | ||
c.Assert(err, IsNil) | ||
|
||
iter, err := os.IterEncodedObjects(plumbing.AnyObject) | ||
c.Assert(err, IsNil) | ||
|
||
var hashes []plumbing.Hash | ||
err = iter.ForEach(func(obj plumbing.EncodedObject) error { | ||
hashes = append(hashes, obj.Hash()) | ||
return nil | ||
}) | ||
|
||
c.Assert(err, IsNil) | ||
c.Assert(hashes, HasLen, 2) | ||
c.Assert(hashes[0], Equals, ch) | ||
c.Assert(hashes[1], Equals, th) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
package transactional |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This might not be transactional depending on the ConfigStorer implementation under the hood. Per example, using filesystem implementation
SetConfig
can fail onWrite
, causing a partial write.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Indeed, this can be a problem. At least for our use case, since we don't do a commit per operation, but after a group of operations. That is, we want a Commit on the full storage, not just part of it.