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

plumbing/storer: add ExclusiveAccess option to Storer #941

Merged
merged 8 commits into from
Sep 4, 2018
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions plumbing/storer/storer.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,9 @@ type Initializer interface {
// any.
Init() error
}

// Options holds configuration for the storage.
type Options struct {
// Static means that the filesystem is not modified while the repo is open.
Static bool
}
5 changes: 2 additions & 3 deletions repository.go
Original file line number Diff line number Diff line change
Expand Up @@ -235,9 +235,8 @@ func PlainOpen(path string) (*Repository, error) {
return PlainOpenWithOptions(path, &PlainOpenOptions{})
}

// PlainOpen opens a git repository from the given path. It detects if the
// repository is bare or a normal one. If the path doesn't contain a valid
// repository ErrRepositoryNotExists is returned
// PlainOpenWithOptions opens a git repository from the given path with specific
// options. See PlainOpen for more info.
func PlainOpenWithOptions(path string, o *PlainOpenOptions) (*Repository, error) {
dot, wt, err := dotGitToOSFilesystems(path, o.DetectDotGit)
if err != nil {
Expand Down
177 changes: 174 additions & 3 deletions storage/filesystem/dotgit/dotgit.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (

"gopkg.in/src-d/go-billy.v4/osfs"
"gopkg.in/src-d/go-git.v4/plumbing"
"gopkg.in/src-d/go-git.v4/plumbing/storer"
"gopkg.in/src-d/go-git.v4/utils/ioutil"

"gopkg.in/src-d/go-billy.v4"
Expand Down Expand Up @@ -60,18 +61,33 @@ var (
// The DotGit type represents a local git repository on disk. This
// type is not zero-value-safe, use the New function to initialize it.
type DotGit struct {
storer.Options
fs billy.Filesystem

// incoming object directory information
incomingChecked bool
incomingDirName string

objectList []plumbing.Hash
objectMap map[plumbing.Hash]struct{}
packList []plumbing.Hash
packMap map[plumbing.Hash]struct{}
}

// New returns a DotGit value ready to be used. The path argument must
// be the absolute path of a git repository directory (e.g.
// "/foo/bar/.git").
func New(fs billy.Filesystem) *DotGit {
return &DotGit{fs: fs}
return NewWithOptions(fs, storer.Options{})
}

// NewWithOptions creates a new DotGit and sets non default configuration
// options. See New for complete help.
func NewWithOptions(fs billy.Filesystem, o storer.Options) *DotGit {
return &DotGit{
Options: o,
fs: fs,
}
}

// Initialize creates all the folder scaffolding.
Expand Down Expand Up @@ -143,11 +159,25 @@ func (d *DotGit) Shallow() (billy.File, error) {
// NewObjectPack return a writer for a new packfile, it saves the packfile to
// disk and also generates and save the index for the given packfile.
func (d *DotGit) NewObjectPack() (*PackWriter, error) {
d.cleanPackList()
return newPackWrite(d.fs)
}

// ObjectPacks returns the list of availables packfiles
func (d *DotGit) ObjectPacks() ([]plumbing.Hash, error) {
if !d.Static {
return d.objectPacks()
}

err := d.genPackList()
if err != nil {
return nil, err
}

return d.packList, nil
}

func (d *DotGit) objectPacks() ([]plumbing.Hash, error) {
packDir := d.fs.Join(objectsPath, packPath)
files, err := d.fs.ReadDir(packDir)
if err != nil {
Expand Down Expand Up @@ -181,6 +211,11 @@ func (d *DotGit) objectPackPath(hash plumbing.Hash, extension string) string {
}

func (d *DotGit) objectPackOpen(hash plumbing.Hash, extension string) (billy.File, error) {
err := d.hasPack(hash)
if err != nil {
return nil, err
}

pack, err := d.fs.Open(d.objectPackPath(hash, extension))
if err != nil {
if os.IsNotExist(err) {
Expand All @@ -195,15 +230,27 @@ func (d *DotGit) objectPackOpen(hash plumbing.Hash, extension string) (billy.Fil

// ObjectPack returns a fs.File of the given packfile
func (d *DotGit) ObjectPack(hash plumbing.Hash) (billy.File, error) {
err := d.hasPack(hash)
if err != nil {
return nil, err
}

return d.objectPackOpen(hash, `pack`)
}

// ObjectPackIdx returns a fs.File of the index file for a given packfile
func (d *DotGit) ObjectPackIdx(hash plumbing.Hash) (billy.File, error) {
err := d.hasPack(hash)
if err != nil {
return nil, err
}

return d.objectPackOpen(hash, `idx`)
}

func (d *DotGit) DeleteOldObjectPackAndIndex(hash plumbing.Hash, t time.Time) error {
d.cleanPackList()

path := d.objectPackPath(hash, `pack`)
if !t.IsZero() {
fi, err := d.fs.Stat(path)
Expand All @@ -224,12 +271,23 @@ func (d *DotGit) DeleteOldObjectPackAndIndex(hash plumbing.Hash, t time.Time) er

// NewObject return a writer for a new object file.
func (d *DotGit) NewObject() (*ObjectWriter, error) {
d.cleanObjectList()

return newObjectWriter(d.fs)
}

// Objects returns a slice with the hashes of objects found under the
// .git/objects/ directory.
func (d *DotGit) Objects() ([]plumbing.Hash, error) {
if d.Static {
err := d.genObjectList()
if err != nil {
return nil, err
}

return d.objectList, nil
}

var objects []plumbing.Hash
err := d.ForEachObjectHash(func(hash plumbing.Hash) error {
objects = append(objects, hash)
Expand All @@ -241,9 +299,29 @@ func (d *DotGit) Objects() ([]plumbing.Hash, error) {
return objects, nil
}

// Objects returns a slice with the hashes of objects found under the
// .git/objects/ directory.
// ForEachObjectHash iterates over the hashes of objects found under the
// .git/objects/ directory and executes the provided function.
func (d *DotGit) ForEachObjectHash(fun func(plumbing.Hash) error) error {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah, it should be fun ;)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is fun indeed!

if !d.Static {
return d.forEachObjectHash(fun)
}

err := d.genObjectList()
if err != nil {
return err
}

for _, h := range d.objectList {
err := fun(h)
if err != nil {
return err
}
}

return nil
}

func (d *DotGit) forEachObjectHash(fun func(plumbing.Hash) error) error {
files, err := d.fs.ReadDir(objectsPath)
if err != nil {
if os.IsNotExist(err) {
Expand Down Expand Up @@ -278,6 +356,87 @@ func (d *DotGit) ForEachObjectHash(fun func(plumbing.Hash) error) error {
return nil
}

func (d *DotGit) cleanObjectList() {
d.objectMap = nil
d.objectList = nil
}

func (d *DotGit) genObjectList() error {
if d.objectMap != nil {
return nil
}

d.objectMap = make(map[plumbing.Hash]struct{})
return d.forEachObjectHash(func(h plumbing.Hash) error {
d.objectList = append(d.objectList, h)
d.objectMap[h] = struct{}{}

return nil
})
}

func (d *DotGit) hasObject(h plumbing.Hash) error {
if !d.Static {
return nil
}

err := d.genObjectList()
if err != nil {
return err
}

_, ok := d.objectMap[h]
if !ok {
return plumbing.ErrObjectNotFound
}

return nil
}

func (d *DotGit) cleanPackList() {
d.packMap = nil
d.packList = nil
}

func (d *DotGit) genPackList() error {
if d.packMap != nil {
return nil
}

op, err := d.objectPacks()
if err != nil {
return err
}

d.packMap = make(map[plumbing.Hash]struct{})
d.packList = nil

for _, h := range op {
d.packList = append(d.packList, h)
d.packMap[h] = struct{}{}
}

return nil
}

func (d *DotGit) hasPack(h plumbing.Hash) error {
if !d.Static {
return nil
}

err := d.genPackList()
if err != nil {
return err
}

_, ok := d.packMap[h]
if !ok {
return ErrPackfileNotFound
}

return nil
}

func (d *DotGit) objectPath(h plumbing.Hash) string {
hash := h.String()
return d.fs.Join(objectsPath, hash[0:2], hash[2:40])
Expand Down Expand Up @@ -322,6 +481,11 @@ func (d *DotGit) hasIncomingObjects() bool {

// Object returns a fs.File pointing the object file, if exists
func (d *DotGit) Object(h plumbing.Hash) (billy.File, error) {
err := d.hasObject(h)
if err != nil {
return nil, err
}

obj1, err1 := d.fs.Open(d.objectPath(h))
if os.IsNotExist(err1) && d.hasIncomingObjects() {
obj2, err2 := d.fs.Open(d.incomingObjectPath(h))
Expand All @@ -335,6 +499,11 @@ func (d *DotGit) Object(h plumbing.Hash) (billy.File, error) {

// ObjectStat returns a os.FileInfo pointing the object file, if exists
func (d *DotGit) ObjectStat(h plumbing.Hash) (os.FileInfo, error) {
err := d.hasObject(h)
if err != nil {
return nil, err
}

obj1, err1 := d.fs.Stat(d.objectPath(h))
if os.IsNotExist(err1) && d.hasIncomingObjects() {
obj2, err2 := d.fs.Stat(d.incomingObjectPath(h))
Expand All @@ -348,6 +517,8 @@ func (d *DotGit) ObjectStat(h plumbing.Hash) (os.FileInfo, error) {

// ObjectDelete removes the object file, if exists
func (d *DotGit) ObjectDelete(h plumbing.Hash) error {
d.cleanObjectList()

err1 := d.fs.Remove(d.objectPath(h))
if os.IsNotExist(err1) && d.hasIncomingObjects() {
err2 := d.fs.Remove(d.incomingObjectPath(h))
Expand Down
18 changes: 15 additions & 3 deletions storage/filesystem/storage.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
package filesystem

import (
"gopkg.in/src-d/go-git.v4/plumbing/storer"
"gopkg.in/src-d/go-git.v4/storage/filesystem/dotgit"

"gopkg.in/src-d/go-billy.v4"
Expand All @@ -11,6 +12,8 @@ import (
// standard git format (this is, the .git directory). Zero values of this type
// are not safe to use, see the NewStorage function below.
type Storage struct {
storer.Options
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is an embedded struct? and not a private field?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're right. There's no reason why it should be embedded or public. It's now changed.


fs billy.Filesystem
dir *dotgit.DotGit

Expand All @@ -24,15 +27,24 @@ type Storage struct {

// NewStorage returns a new Storage backed by a given `fs.Filesystem`
func NewStorage(fs billy.Filesystem) (*Storage, error) {
dir := dotgit.New(fs)
return NewStorageWithOptions(fs, storer.Options{})
}

// NewStorageWithOptions returns a new Storage backed by a given `fs.Filesystem`
func NewStorageWithOptions(
fs billy.Filesystem,
ops storer.Options,
) (*Storage, error) {
dir := dotgit.NewWithOptions(fs, ops)
o, err := NewObjectStorage(dir)
if err != nil {
return nil, err
}

return &Storage{
fs: fs,
dir: dir,
Options: ops,
fs: fs,
dir: dir,

ObjectStorage: o,
ReferenceStorage: ReferenceStorage{dir: dir},
Expand Down
20 changes: 20 additions & 0 deletions storage/filesystem/storage_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@ func (s *StorageSuite) SetUpTest(c *C) {
storage, err := NewStorage(osfs.New(s.dir))
c.Assert(err, IsNil)

setUpTest(s, c, storage)
}

func setUpTest(s *StorageSuite, c *C, storage *Storage) {
// ensure that right interfaces are implemented
var _ storer.EncodedObjectStorer = storage
var _ storer.IndexStorer = storage
Expand All @@ -51,3 +55,19 @@ func (s *StorageSuite) TestNewStorageShouldNotAddAnyContentsToDir(c *C) {
c.Assert(err, IsNil)
c.Assert(fis, HasLen, 0)
}

type StorageStaticSuite struct {
StorageSuite
}

var _ = Suite(&StorageStaticSuite{})

func (s *StorageStaticSuite) SetUpTest(c *C) {
s.dir = c.MkDir()
storage, err := NewStorageWithOptions(
osfs.New(s.dir),
storer.Options{Static: true})
c.Assert(err, IsNil)

setUpTest(&s.StorageSuite, c, storage)
}