Skip to content

Commit 1e97572

Browse files
authored
Merge pull request #3 from bk2204/expose-fs
Add a storage abstraction
2 parents d53af9b + 033511c commit 1e97572

17 files changed

+514
-85
lines changed

backend.go

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
package gitobj
2+
3+
import (
4+
"io"
5+
6+
"github.com/git-lfs/gitobj/pack"
7+
"github.com/git-lfs/gitobj/storage"
8+
)
9+
10+
// NewFilesystemBackend initializes a new filesystem-based backend.
11+
func NewFilesystemBackend(root, tmp string) (storage.Backend, error) {
12+
fsobj := newFileStorer(root, tmp)
13+
packs, err := pack.NewStorage(root)
14+
if err != nil {
15+
return nil, err
16+
}
17+
18+
return &filesystemBackend{fs: fsobj, packs: packs}, nil
19+
}
20+
21+
// NewMemoryBackend initializes a new memory-based backend.
22+
//
23+
// A value of "nil" is acceptable and indicates that no entries should be added
24+
// to the memory backend at construction time.
25+
func NewMemoryBackend(m map[string]io.ReadWriter) (storage.Backend, error) {
26+
return &memoryBackend{ms: newMemoryStorer(m)}, nil
27+
}
28+
29+
type filesystemBackend struct {
30+
fs *fileStorer
31+
packs *pack.Storage
32+
}
33+
34+
func (b *filesystemBackend) Storage() (storage.Storage, storage.WritableStorage) {
35+
return storage.MultiStorage(b.fs, b.packs), b.fs
36+
}
37+
38+
type memoryBackend struct {
39+
ms *memoryStorer
40+
}
41+
42+
func (b *memoryBackend) Storage() (storage.Storage, storage.WritableStorage) {
43+
return b.ms, b.ms
44+
}

backend_test.go

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
package gitobj
2+
3+
import (
4+
"bytes"
5+
"encoding/hex"
6+
"io"
7+
"io/ioutil"
8+
"testing"
9+
10+
"github.com/stretchr/testify/assert"
11+
)
12+
13+
func TestNewMemoryBackend(t *testing.T) {
14+
backend, err := NewMemoryBackend(nil)
15+
assert.NoError(t, err)
16+
17+
ro, rw := backend.Storage()
18+
assert.Equal(t, ro, rw)
19+
assert.NotNil(t, ro.(*memoryStorer))
20+
}
21+
22+
func TestNewMemoryBackendWithReadOnlyData(t *testing.T) {
23+
sha := "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
24+
oid, err := hex.DecodeString(sha)
25+
26+
assert.Nil(t, err)
27+
28+
m := map[string]io.ReadWriter{
29+
sha: bytes.NewBuffer([]byte{0x1}),
30+
}
31+
32+
backend, err := NewMemoryBackend(m)
33+
assert.NoError(t, err)
34+
35+
ro, _ := backend.Storage()
36+
reader, err := ro.Open(oid)
37+
assert.NoError(t, err)
38+
39+
contents, err := ioutil.ReadAll(reader)
40+
assert.NoError(t, err)
41+
assert.Equal(t, []byte{0x1}, contents)
42+
}
43+
44+
func TestNewMemoryBackendWithWritableData(t *testing.T) {
45+
sha := "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
46+
oid, err := hex.DecodeString(sha)
47+
48+
assert.Nil(t, err)
49+
50+
backend, err := NewMemoryBackend(make(map[string]io.ReadWriter))
51+
assert.NoError(t, err)
52+
53+
buf := bytes.NewBuffer([]byte{0x1})
54+
55+
ro, rw := backend.Storage()
56+
rw.Store(oid, buf)
57+
58+
reader, err := ro.Open(oid)
59+
assert.NoError(t, err)
60+
61+
contents, err := ioutil.ReadAll(reader)
62+
assert.NoError(t, err)
63+
assert.Equal(t, []byte{0x1}, contents)
64+
}

errors/errors.go

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
package errors
2+
3+
import (
4+
"fmt"
5+
)
6+
7+
// noSuchObject is an error type that occurs when no object with a given object
8+
// ID is available.
9+
type noSuchObject struct {
10+
oid []byte
11+
}
12+
13+
// Error implements the error.Error() function.
14+
func (e *noSuchObject) Error() string {
15+
return fmt.Sprintf("gitobj: no such object: %x", e.oid)
16+
}
17+
18+
// NoSuchObject creates a new error representing a missing object with a given
19+
// object ID.
20+
func NoSuchObject(oid []byte) error {
21+
return &noSuchObject{oid: oid}
22+
}
23+
24+
// IsNoSuchObject indicates whether an error is a noSuchObject and is non-nil.
25+
func IsNoSuchObject(e error) bool {
26+
err, ok := e.(*noSuchObject)
27+
return ok && err != nil
28+
}

errors/errors_test.go

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package errors
2+
3+
import (
4+
"encoding/hex"
5+
"testing"
6+
7+
"github.com/stretchr/testify/assert"
8+
)
9+
10+
func TestNoSuchObjectTypeErrFormatting(t *testing.T) {
11+
sha := "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
12+
oid, err := hex.DecodeString(sha)
13+
assert.NoError(t, err)
14+
15+
err = NoSuchObject(oid)
16+
17+
assert.Equal(t, "gitobj: no such object: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", err.Error())
18+
assert.Equal(t, IsNoSuchObject(err), true)
19+
}
20+
21+
func TestIsNoSuchObjectNilHandling(t *testing.T) {
22+
assert.Equal(t, IsNoSuchObject((*noSuchObject)(nil)), false)
23+
assert.Equal(t, IsNoSuchObject(nil), false)
24+
}

file_storer.go

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ import (
77
"io/ioutil"
88
"os"
99
"path/filepath"
10+
11+
"github.com/git-lfs/gitobj/errors"
1012
)
1113

1214
// fileStorer implements the storer interface by writing to the .git/objects
@@ -27,14 +29,18 @@ func newFileStorer(root, tmp string) *fileStorer {
2729
}
2830
}
2931

30-
// Open implements the storer.Open function, and returns a io.ReadWriteCloser
32+
// Open implements the storer.Open function, and returns a io.ReadCloser
3133
// for the given SHA. If the file does not exist, or if there was any other
3234
// error in opening the file, an error will be returned.
3335
//
3436
// It is the caller's responsibility to close the given file "f" after its use
3537
// is complete.
36-
func (fs *fileStorer) Open(sha []byte) (f io.ReadWriteCloser, err error) {
37-
return fs.open(fs.path(sha), os.O_RDONLY)
38+
func (fs *fileStorer) Open(sha []byte) (f io.ReadCloser, err error) {
39+
f, err = fs.open(fs.path(sha), os.O_RDONLY)
40+
if os.IsNotExist(err) {
41+
return nil, errors.NoSuchObject(sha)
42+
}
43+
return f, err
3844
}
3945

4046
// Store implements the storer.Store function and returns the number of bytes
@@ -90,6 +96,16 @@ func (fs *fileStorer) Root() string {
9096
return fs.root
9197
}
9298

99+
// Close closes the file storer.
100+
func (fs *fileStorer) Close() error {
101+
return nil
102+
}
103+
104+
// IsCompressed returns true, because the file storer returns compressed data.
105+
func (fs *fileStorer) IsCompressed() bool {
106+
return true
107+
}
108+
93109
// open opens a given file.
94110
func (fs *fileStorer) open(path string, flag int) (*os.File, error) {
95111
return os.OpenFile(path, flag, 0)

memory_storer.go

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,9 @@ import (
44
"bytes"
55
"fmt"
66
"io"
7-
"os"
87
"sync"
8+
9+
"github.com/git-lfs/gitobj/errors"
910
)
1011

1112
// memoryStorer is an implementation of the storer interface that holds data for
@@ -51,17 +52,27 @@ func (ms *memoryStorer) Store(sha []byte, r io.Reader) (n int64, err error) {
5152
// Open implements the storer.Open function, and returns a io.ReadWriteCloser
5253
// for the given SHA. If a reader for the given SHA does not exist an error will
5354
// be returned.
54-
func (ms *memoryStorer) Open(sha []byte) (f io.ReadWriteCloser, err error) {
55+
func (ms *memoryStorer) Open(sha []byte) (f io.ReadCloser, err error) {
5556
ms.mu.Lock()
5657
defer ms.mu.Unlock()
5758

5859
key := fmt.Sprintf("%x", sha)
5960
if _, ok := ms.fs[key]; !ok {
60-
return nil, os.ErrNotExist
61+
return nil, errors.NoSuchObject(sha)
6162
}
6263
return ms.fs[key], nil
6364
}
6465

66+
// Close closes the memory storer.
67+
func (ms *memoryStorer) Close() error {
68+
return nil
69+
}
70+
71+
// IsCompressed returns true, because the memory storer returns compressed data.
72+
func (ms *memoryStorer) IsCompressed() bool {
73+
return true
74+
}
75+
6576
// bufCloser wraps a type satisfying the io.ReadWriter interface with a no-op
6677
// Close() function, thus implementing the io.ReadWriteCloser composite
6778
// interface.

memory_storer_test.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,10 @@ import (
55
"encoding/hex"
66
"io"
77
"io/ioutil"
8-
"os"
98
"strings"
109
"testing"
1110

11+
"github.com/git-lfs/gitobj/errors"
1212
"github.com/stretchr/testify/assert"
1313
)
1414

@@ -35,6 +35,7 @@ func TestMemoryStorerAcceptsNilEntries(t *testing.T) {
3535

3636
assert.NotNil(t, ms)
3737
assert.Equal(t, 0, len(ms.fs))
38+
assert.NoError(t, ms.Close())
3839
}
3940

4041
func TestMemoryStorerDoesntOpenMissingEntries(t *testing.T) {
@@ -46,7 +47,7 @@ func TestMemoryStorerDoesntOpenMissingEntries(t *testing.T) {
4647
ms := newMemoryStorer(nil)
4748

4849
f, err := ms.Open(hex)
49-
assert.Equal(t, os.ErrNotExist, err)
50+
assert.Equal(t, errors.NoSuchObject(hex), err)
5051
assert.Nil(t, f)
5152
}
5253

0 commit comments

Comments
 (0)