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
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
185 changes: 181 additions & 4 deletions storage/filesystem/dotgit/dotgit.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,21 +57,43 @@ var (
ErrSymRefTargetNotFound = errors.New("symbolic reference target not found")
)

// Options holds configuration for the storage.
type Options struct {
// ExclusiveAccess means that the filesystem is not modified externally
// while the repo is open.
ExclusiveAccess bool
}

// 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 {
fs billy.Filesystem
options 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, Options{})
}

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

// Initialize creates all the folder scaffolding.
Expand Down Expand Up @@ -143,11 +165,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.options.ExclusiveAccess {
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 +217,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 +236,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 +277,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.options.ExclusiveAccess {
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 +305,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 {
if !d.options.ExclusiveAccess {
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 +362,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.options.ExclusiveAccess {
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.options.ExclusiveAccess {
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 +487,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 +505,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 +523,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
24 changes: 24 additions & 0 deletions storage/filesystem/dotgit/dotgit_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"strings"
"testing"

"gopkg.in/src-d/go-billy.v4"
"gopkg.in/src-d/go-git.v4/plumbing"

. "gopkg.in/check.v1"
Expand Down Expand Up @@ -424,6 +425,18 @@ func (s *SuiteDotGit) TestObjectPacks(c *C) {
fs := f.DotGit()
dir := New(fs)

testObjectPacks(c, fs, dir, f)
}

func (s *SuiteDotGit) TestObjectPacksExclusive(c *C) {
f := fixtures.Basic().ByTag(".git").One()
fs := f.DotGit()
dir := NewWithOptions(fs, Options{ExclusiveAccess: true})

testObjectPacks(c, fs, dir, f)
}

func testObjectPacks(c *C, fs billy.Filesystem, dir *DotGit, f *fixtures.Fixture) {
hashes, err := dir.ObjectPacks()
c.Assert(err, IsNil)
c.Assert(hashes, HasLen, 1)
Expand Down Expand Up @@ -506,6 +519,17 @@ func (s *SuiteDotGit) TestObjects(c *C) {
fs := fixtures.ByTag(".git").ByTag("unpacked").One().DotGit()
dir := New(fs)

testObjects(c, fs, dir)
}

func (s *SuiteDotGit) TestObjectsExclusive(c *C) {
fs := fixtures.ByTag(".git").ByTag("unpacked").One().DotGit()
dir := NewWithOptions(fs, Options{ExclusiveAccess: true})

testObjects(c, fs, dir)
}

func testObjects(c *C, fs billy.Filesystem, dir *DotGit) {
hashes, err := dir.Objects()
c.Assert(err, IsNil)
c.Assert(hashes, HasLen, 187)
Expand Down
12 changes: 12 additions & 0 deletions storage/filesystem/object.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ import (
)

type ObjectStorage struct {
options Options

// deltaBaseCache is an object cache uses to cache delta's bases when
deltaBaseCache cache.Object

Expand All @@ -27,7 +29,17 @@ type ObjectStorage struct {

// NewObjectStorage creates a new ObjectStorage with the given .git directory.
func NewObjectStorage(dir *dotgit.DotGit) (ObjectStorage, error) {
return NewObjectStorageWithOptions(dir, Options{})
}

// NewObjectStorageWithOptions creates a new ObjectStorage with the given .git
// directory and sets its options.
func NewObjectStorageWithOptions(
dir *dotgit.DotGit,
ops Options,
) (ObjectStorage, error) {
s := ObjectStorage{
options: ops,
deltaBaseCache: cache.NewObjectLRUDefault(),
dir: dir,
}
Expand Down
Loading