Skip to content
This repository was archived by the owner on Sep 11, 2020. It is now read-only.

Commit d1b5bce

Browse files
authored
Merge pull request #1006 from mcuadros/transactional-storage
storage: transactional, new storage with transactional capabilities
2 parents a1f6ef4 + 9631774 commit d1b5bce

20 files changed

+1161
-8
lines changed

plumbing/storer/object.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -222,7 +222,7 @@ type MultiEncodedObjectIter struct {
222222
}
223223

224224
// NewMultiEncodedObjectIter returns an object iterator for the given slice of
225-
// objects.
225+
// EncodedObjectIters.
226226
func NewMultiEncodedObjectIter(iters []EncodedObjectIter) EncodedObjectIter {
227227
return &MultiEncodedObjectIter{iters: iters}
228228
}

plumbing/storer/reference.go

+66-4
Original file line numberDiff line numberDiff line change
@@ -131,25 +131,87 @@ func (iter *ReferenceSliceIter) Next() (*plumbing.Reference, error) {
131131
// an error happens or the end of the iter is reached. If ErrStop is sent
132132
// the iteration is stop but no error is returned. The iterator is closed.
133133
func (iter *ReferenceSliceIter) ForEach(cb func(*plumbing.Reference) error) error {
134+
return forEachReferenceIter(iter, cb)
135+
}
136+
137+
type bareReferenceIterator interface {
138+
Next() (*plumbing.Reference, error)
139+
Close()
140+
}
141+
142+
func forEachReferenceIter(iter bareReferenceIterator, cb func(*plumbing.Reference) error) error {
134143
defer iter.Close()
135-
for _, r := range iter.series {
136-
if err := cb(r); err != nil {
144+
for {
145+
obj, err := iter.Next()
146+
if err != nil {
147+
if err == io.EOF {
148+
return nil
149+
}
150+
151+
return err
152+
}
153+
154+
if err := cb(obj); err != nil {
137155
if err == ErrStop {
138156
return nil
139157
}
140158

141159
return err
142160
}
143161
}
144-
145-
return nil
146162
}
147163

148164
// Close releases any resources used by the iterator.
149165
func (iter *ReferenceSliceIter) Close() {
150166
iter.pos = len(iter.series)
151167
}
152168

169+
// MultiReferenceIter implements ReferenceIter. It iterates over several
170+
// ReferenceIter,
171+
//
172+
// The MultiReferenceIter must be closed with a call to Close() when it is no
173+
// longer needed.
174+
type MultiReferenceIter struct {
175+
iters []ReferenceIter
176+
}
177+
178+
// NewMultiReferenceIter returns an reference iterator for the given slice of
179+
// EncodedObjectIters.
180+
func NewMultiReferenceIter(iters []ReferenceIter) ReferenceIter {
181+
return &MultiReferenceIter{iters: iters}
182+
}
183+
184+
// Next returns the next reference from the iterator, if one iterator reach
185+
// io.EOF is removed and the next one is used.
186+
func (iter *MultiReferenceIter) Next() (*plumbing.Reference, error) {
187+
if len(iter.iters) == 0 {
188+
return nil, io.EOF
189+
}
190+
191+
obj, err := iter.iters[0].Next()
192+
if err == io.EOF {
193+
iter.iters[0].Close()
194+
iter.iters = iter.iters[1:]
195+
return iter.Next()
196+
}
197+
198+
return obj, err
199+
}
200+
201+
// ForEach call the cb function for each reference contained on this iter until
202+
// an error happens or the end of the iter is reached. If ErrStop is sent
203+
// the iteration is stop but no error is returned. The iterator is closed.
204+
func (iter *MultiReferenceIter) ForEach(cb func(*plumbing.Reference) error) error {
205+
return forEachReferenceIter(iter, cb)
206+
}
207+
208+
// Close releases any resources used by the iterator.
209+
func (iter *MultiReferenceIter) Close() {
210+
for _, i := range iter.iters {
211+
i.Close()
212+
}
213+
}
214+
153215
// ResolveReference resolves a SymbolicReference to a HashReference.
154216
func ResolveReference(s ReferenceStorer, n plumbing.ReferenceName) (*plumbing.Reference, error) {
155217
r, err := s.Reference(n)

plumbing/storer/reference_test.go

+23
Original file line numberDiff line numberDiff line change
@@ -172,3 +172,26 @@ func (s *ReferenceSuite) TestReferenceFilteredIterForEachStop(c *C) {
172172

173173
c.Assert(count, Equals, 1)
174174
}
175+
176+
func (s *ReferenceSuite) TestMultiReferenceIterForEach(c *C) {
177+
i := NewMultiReferenceIter(
178+
[]ReferenceIter{
179+
NewReferenceSliceIter([]*plumbing.Reference{
180+
plumbing.NewReferenceFromStrings("foo", "foo"),
181+
}),
182+
NewReferenceSliceIter([]*plumbing.Reference{
183+
plumbing.NewReferenceFromStrings("bar", "bar"),
184+
}),
185+
},
186+
)
187+
188+
var result []string
189+
err := i.ForEach(func(r *plumbing.Reference) error {
190+
result = append(result, r.Name().String())
191+
return nil
192+
})
193+
194+
c.Assert(err, IsNil)
195+
c.Assert(result, HasLen, 2)
196+
c.Assert(result, DeepEquals, []string{"foo", "bar"})
197+
}

storage/filesystem/dotgit/dotgit.go

+2-1
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import (
1414

1515
"gopkg.in/src-d/go-billy.v4/osfs"
1616
"gopkg.in/src-d/go-git.v4/plumbing"
17+
"gopkg.in/src-d/go-git.v4/storage"
1718
"gopkg.in/src-d/go-git.v4/utils/ioutil"
1819

1920
"gopkg.in/src-d/go-billy.v4"
@@ -596,7 +597,7 @@ func (d *DotGit) checkReferenceAndTruncate(f billy.File, old *plumbing.Reference
596597
return err
597598
}
598599
if ref.Hash() != old.Hash() {
599-
return fmt.Errorf("reference has changed concurrently")
600+
return storage.ErrReferenceHasChanged
600601
}
601602
_, err = f.Seek(0, io.SeekStart)
602603
if err != nil {

storage/memory/storage.go

+1-2
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ import (
1313
)
1414

1515
var ErrUnsupportedObjectType = fmt.Errorf("unsupported object type")
16-
var ErrRefHasChanged = fmt.Errorf("reference has changed concurrently")
1716

1817
// Storage is an implementation of git.Storer that stores data on memory, being
1918
// ephemeral. The use of this storage should be done in controlled envoriments,
@@ -258,7 +257,7 @@ func (r ReferenceStorage) CheckAndSetReference(ref, old *plumbing.Reference) err
258257
if old != nil {
259258
tmp := r[ref.Name()]
260259
if tmp != nil && tmp.Hash() != old.Hash() {
261-
return ErrRefHasChanged
260+
return storage.ErrReferenceHasChanged
262261
}
263262
}
264263
r[ref.Name()] = ref

storage/storer.go

+4
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,14 @@
11
package storage
22

33
import (
4+
"errors"
5+
46
"gopkg.in/src-d/go-git.v4/config"
57
"gopkg.in/src-d/go-git.v4/plumbing/storer"
68
)
79

10+
var ErrReferenceHasChanged = errors.New("reference has changed concurrently")
11+
812
// Storer is a generic storage of objects, references and any information
913
// related to a particular repository. The package gopkg.in/src-d/go-git.v4/storage
1014
// contains two implementation a filesystem base implementation (such as `.git`)

storage/test/storage_suite.go

+51
Original file line numberDiff line numberDiff line change
@@ -280,6 +280,57 @@ func (s *BaseStorageSuite) TestSetReferenceAndGetReference(c *C) {
280280
c.Assert(e.Hash().String(), Equals, "bc9968d75e48de59f0870ffb71f5e160bbbdcf52")
281281
}
282282

283+
func (s *BaseStorageSuite) TestCheckAndSetReference(c *C) {
284+
err := s.Storer.SetReference(
285+
plumbing.NewReferenceFromStrings("foo", "482e0eada5de4039e6f216b45b3c9b683b83bfa"),
286+
)
287+
c.Assert(err, IsNil)
288+
289+
err = s.Storer.CheckAndSetReference(
290+
plumbing.NewReferenceFromStrings("foo", "bc9968d75e48de59f0870ffb71f5e160bbbdcf52"),
291+
plumbing.NewReferenceFromStrings("foo", "482e0eada5de4039e6f216b45b3c9b683b83bfa"),
292+
)
293+
c.Assert(err, IsNil)
294+
295+
e, err := s.Storer.Reference(plumbing.ReferenceName("foo"))
296+
c.Assert(err, IsNil)
297+
c.Assert(e.Hash().String(), Equals, "bc9968d75e48de59f0870ffb71f5e160bbbdcf52")
298+
}
299+
300+
func (s *BaseStorageSuite) TestCheckAndSetReferenceNil(c *C) {
301+
err := s.Storer.SetReference(
302+
plumbing.NewReferenceFromStrings("foo", "482e0eada5de4039e6f216b45b3c9b683b83bfa"),
303+
)
304+
c.Assert(err, IsNil)
305+
306+
err = s.Storer.CheckAndSetReference(
307+
plumbing.NewReferenceFromStrings("foo", "bc9968d75e48de59f0870ffb71f5e160bbbdcf52"),
308+
nil,
309+
)
310+
c.Assert(err, IsNil)
311+
312+
e, err := s.Storer.Reference(plumbing.ReferenceName("foo"))
313+
c.Assert(err, IsNil)
314+
c.Assert(e.Hash().String(), Equals, "bc9968d75e48de59f0870ffb71f5e160bbbdcf52")
315+
}
316+
317+
func (s *BaseStorageSuite) TestCheckAndSetReferenceError(c *C) {
318+
err := s.Storer.SetReference(
319+
plumbing.NewReferenceFromStrings("foo", "c3f4688a08fd86f1bf8e055724c84b7a40a09733"),
320+
)
321+
c.Assert(err, IsNil)
322+
323+
err = s.Storer.CheckAndSetReference(
324+
plumbing.NewReferenceFromStrings("foo", "bc9968d75e48de59f0870ffb71f5e160bbbdcf52"),
325+
plumbing.NewReferenceFromStrings("foo", "482e0eada5de4039e6f216b45b3c9b683b83bfa"),
326+
)
327+
c.Assert(err, Equals, storage.ErrReferenceHasChanged)
328+
329+
e, err := s.Storer.Reference(plumbing.ReferenceName("foo"))
330+
c.Assert(err, IsNil)
331+
c.Assert(e.Hash().String(), Equals, "c3f4688a08fd86f1bf8e055724c84b7a40a09733")
332+
}
333+
283334
func (s *BaseStorageSuite) TestRemoveReference(c *C) {
284335
err := s.Storer.SetReference(
285336
plumbing.NewReferenceFromStrings("foo", "bc9968d75e48de59f0870ffb71f5e160bbbdcf52"),

storage/transactional/config.go

+50
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
package transactional
2+
3+
import "gopkg.in/src-d/go-git.v4/config"
4+
5+
// ConfigStorage implements the storer.ConfigStorage for the transactional package.
6+
type ConfigStorage struct {
7+
config.ConfigStorer
8+
temporal config.ConfigStorer
9+
10+
set bool
11+
}
12+
13+
// NewConfigStorage returns a new ConfigStorer based on a base storer and a
14+
// temporal storer.
15+
func NewConfigStorage(s, temporal config.ConfigStorer) *ConfigStorage {
16+
return &ConfigStorage{ConfigStorer: s, temporal: temporal}
17+
}
18+
19+
// SetConfig honors the storer.ConfigStorer interface.
20+
func (c *ConfigStorage) SetConfig(cfg *config.Config) error {
21+
if err := c.temporal.SetConfig(cfg); err != nil {
22+
return err
23+
}
24+
25+
c.set = true
26+
return nil
27+
}
28+
29+
// Config honors the storer.ConfigStorer interface.
30+
func (c *ConfigStorage) Config() (*config.Config, error) {
31+
if !c.set {
32+
return c.ConfigStorer.Config()
33+
}
34+
35+
return c.temporal.Config()
36+
}
37+
38+
// Commit it copies the config from the temporal storage into the base storage.
39+
func (c *ConfigStorage) Commit() error {
40+
if !c.set {
41+
return nil
42+
}
43+
44+
cfg, err := c.temporal.Config()
45+
if err != nil {
46+
return err
47+
}
48+
49+
return c.ConfigStorer.SetConfig(cfg)
50+
}

storage/transactional/config_test.go

+82
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
package transactional
2+
3+
import (
4+
. "gopkg.in/check.v1"
5+
"gopkg.in/src-d/go-git.v4/config"
6+
"gopkg.in/src-d/go-git.v4/storage/memory"
7+
)
8+
9+
var _ = Suite(&ConfigSuite{})
10+
11+
type ConfigSuite struct{}
12+
13+
func (s *ConfigSuite) TestSetConfigBase(c *C) {
14+
cfg := config.NewConfig()
15+
cfg.Core.Worktree = "foo"
16+
17+
base := memory.NewStorage()
18+
err := base.SetConfig(cfg)
19+
c.Assert(err, IsNil)
20+
21+
temporal := memory.NewStorage()
22+
cs := NewConfigStorage(base, temporal)
23+
24+
cfg, err = cs.Config()
25+
c.Assert(err, IsNil)
26+
c.Assert(cfg.Core.Worktree, Equals, "foo")
27+
}
28+
29+
func (s *ConfigSuite) TestSetConfigTemporal(c *C) {
30+
cfg := config.NewConfig()
31+
cfg.Core.Worktree = "foo"
32+
33+
base := memory.NewStorage()
34+
err := base.SetConfig(cfg)
35+
c.Assert(err, IsNil)
36+
37+
temporal := memory.NewStorage()
38+
39+
cfg = config.NewConfig()
40+
cfg.Core.Worktree = "bar"
41+
42+
cs := NewConfigStorage(base, temporal)
43+
err = cs.SetConfig(cfg)
44+
c.Assert(err, IsNil)
45+
46+
baseCfg, err := base.Config()
47+
c.Assert(err, IsNil)
48+
c.Assert(baseCfg.Core.Worktree, Equals, "foo")
49+
50+
temporalCfg, err := temporal.Config()
51+
c.Assert(err, IsNil)
52+
c.Assert(temporalCfg.Core.Worktree, Equals, "bar")
53+
54+
cfg, err = cs.Config()
55+
c.Assert(err, IsNil)
56+
c.Assert(temporalCfg.Core.Worktree, Equals, "bar")
57+
}
58+
59+
func (s *ConfigSuite) TestCommit(c *C) {
60+
cfg := config.NewConfig()
61+
cfg.Core.Worktree = "foo"
62+
63+
base := memory.NewStorage()
64+
err := base.SetConfig(cfg)
65+
c.Assert(err, IsNil)
66+
67+
temporal := memory.NewStorage()
68+
69+
cfg = config.NewConfig()
70+
cfg.Core.Worktree = "bar"
71+
72+
cs := NewConfigStorage(base, temporal)
73+
err = cs.SetConfig(cfg)
74+
c.Assert(err, IsNil)
75+
76+
err = cs.Commit()
77+
c.Assert(err, IsNil)
78+
79+
baseCfg, err := base.Config()
80+
c.Assert(err, IsNil)
81+
c.Assert(baseCfg.Core.Worktree, Equals, "bar")
82+
}

storage/transactional/doc.go

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
// Package transactional is a transactional implementation of git.Storer, it
2+
// demux the write and read operation of two separate storers, allowing to merge
3+
// content calling Storage.Commit.
4+
//
5+
// The API and functionality of this package are considered EXPERIMENTAL and is
6+
// not considered stable nor production ready.
7+
package transactional

0 commit comments

Comments
 (0)