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

Commit 1e1a7d0

Browse files
committed
git: add Static option to PlainOpen
Also adds Static configuration to Storage and DotGit. This option means that the git repository is not expected to be modified while open and enables some optimizations. Each time a file is accessed the storer tries to open an object file for the requested hash. When this is done for a lot of objects it is expensive. With Static option a list of object files is generated the first time an object is accessed and used to check if exists instead of using system calls. A similar optimization is done for packfiles. Signed-off-by: Javi Fontan <jfontan@gmail.com>
1 parent 5cc316b commit 1e1a7d0

File tree

6 files changed

+249
-10
lines changed

6 files changed

+249
-10
lines changed

options.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -431,6 +431,8 @@ type PlainOpenOptions struct {
431431
// DetectDotGit defines whether parent directories should be
432432
// walked until a .git directory or file is found.
433433
DetectDotGit bool
434+
// Static means that the repository won't be modified while open.
435+
Static bool
434436
}
435437

436438
// Validate validates the fields and sets the default values.

repository.go

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -235,9 +235,8 @@ func PlainOpen(path string) (*Repository, error) {
235235
return PlainOpenWithOptions(path, &PlainOpenOptions{})
236236
}
237237

238-
// PlainOpen opens a git repository from the given path. It detects if the
239-
// repository is bare or a normal one. If the path doesn't contain a valid
240-
// repository ErrRepositoryNotExists is returned
238+
// PlainOpenWithOptions opens a git repository from the given path with specific
239+
// options. See PlainOpen for more info.
241240
func PlainOpenWithOptions(path string, o *PlainOpenOptions) (*Repository, error) {
242241
dot, wt, err := dotGitToOSFilesystems(path, o.DetectDotGit)
243242
if err != nil {
@@ -252,7 +251,11 @@ func PlainOpenWithOptions(path string, o *PlainOpenOptions) (*Repository, error)
252251
return nil, err
253252
}
254253

255-
s, err := filesystem.NewStorage(dot)
254+
so := filesystem.StorageOptions{
255+
Static: o.Static,
256+
}
257+
258+
s, err := filesystem.NewStorageWithOptions(dot, so)
256259
if err != nil {
257260
return nil, err
258261
}

repository_test.go

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -550,6 +550,25 @@ func (s *RepositorySuite) TestPlainOpenNotExistsDetectDotGit(c *C) {
550550
c.Assert(r, IsNil)
551551
}
552552

553+
func (s *RepositorySuite) TestPlainOpenStatic(c *C) {
554+
dir, err := ioutil.TempDir("", "plain-open")
555+
c.Assert(err, IsNil)
556+
defer os.RemoveAll(dir)
557+
558+
r, err := PlainInit(dir, true)
559+
c.Assert(err, IsNil)
560+
c.Assert(r, NotNil)
561+
562+
op := &PlainOpenOptions{Static: true}
563+
r, err = PlainOpenWithOptions(dir, op)
564+
c.Assert(err, IsNil)
565+
c.Assert(r, NotNil)
566+
567+
sto, ok := r.Storer.(*filesystem.Storage)
568+
c.Assert(ok, Equals, true)
569+
c.Assert(sto.StorageOptions.Static, Equals, true)
570+
}
571+
553572
func (s *RepositorySuite) TestPlainClone(c *C) {
554573
r, err := PlainClone(c.MkDir(), false, &CloneOptions{
555574
URL: s.GetBasicLocalRepositoryURL(),

storage/filesystem/dotgit/dotgit.go

Lines changed: 179 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -60,18 +60,39 @@ var (
6060
// The DotGit type represents a local git repository on disk. This
6161
// type is not zero-value-safe, use the New function to initialize it.
6262
type DotGit struct {
63+
DotGitOptions
6364
fs billy.Filesystem
6465

6566
// incoming object directory information
6667
incomingChecked bool
6768
incomingDirName string
69+
70+
objectList []plumbing.Hash
71+
objectMap map[plumbing.Hash]struct{}
72+
packList []plumbing.Hash
73+
packMap map[plumbing.Hash]struct{}
74+
}
75+
76+
// DotGitOptions holds configuration options for new DotGit objects.
77+
type DotGitOptions struct {
78+
// Static means that the filesystem won't be changed while the repo is open.
79+
Static bool
6880
}
6981

7082
// New returns a DotGit value ready to be used. The path argument must
7183
// be the absolute path of a git repository directory (e.g.
7284
// "/foo/bar/.git").
7385
func New(fs billy.Filesystem) *DotGit {
74-
return &DotGit{fs: fs}
86+
return NewWithOptions(fs, DotGitOptions{})
87+
}
88+
89+
// NewWithOptions creates a new DotGit and sets non default configuration
90+
// options. See New for complete help.
91+
func NewWithOptions(fs billy.Filesystem, o DotGitOptions) *DotGit {
92+
return &DotGit{
93+
DotGitOptions: o,
94+
fs: fs,
95+
}
7596
}
7697

7798
// Initialize creates all the folder scaffolding.
@@ -143,11 +164,25 @@ func (d *DotGit) Shallow() (billy.File, error) {
143164
// NewObjectPack return a writer for a new packfile, it saves the packfile to
144165
// disk and also generates and save the index for the given packfile.
145166
func (d *DotGit) NewObjectPack() (*PackWriter, error) {
167+
d.cleanPackList()
146168
return newPackWrite(d.fs)
147169
}
148170

149171
// ObjectPacks returns the list of availables packfiles
150172
func (d *DotGit) ObjectPacks() ([]plumbing.Hash, error) {
173+
if !d.Static {
174+
return d.objectPacks()
175+
}
176+
177+
err := d.genPackList()
178+
if err != nil {
179+
return nil, err
180+
}
181+
182+
return d.packList, nil
183+
}
184+
185+
func (d *DotGit) objectPacks() ([]plumbing.Hash, error) {
151186
packDir := d.fs.Join(objectsPath, packPath)
152187
files, err := d.fs.ReadDir(packDir)
153188
if err != nil {
@@ -181,6 +216,11 @@ func (d *DotGit) objectPackPath(hash plumbing.Hash, extension string) string {
181216
}
182217

183218
func (d *DotGit) objectPackOpen(hash plumbing.Hash, extension string) (billy.File, error) {
219+
err := d.hasPack(hash)
220+
if err != nil {
221+
return nil, err
222+
}
223+
184224
pack, err := d.fs.Open(d.objectPackPath(hash, extension))
185225
if err != nil {
186226
if os.IsNotExist(err) {
@@ -195,15 +235,27 @@ func (d *DotGit) objectPackOpen(hash plumbing.Hash, extension string) (billy.Fil
195235

196236
// ObjectPack returns a fs.File of the given packfile
197237
func (d *DotGit) ObjectPack(hash plumbing.Hash) (billy.File, error) {
238+
err := d.hasPack(hash)
239+
if err != nil {
240+
return nil, err
241+
}
242+
198243
return d.objectPackOpen(hash, `pack`)
199244
}
200245

201246
// ObjectPackIdx returns a fs.File of the index file for a given packfile
202247
func (d *DotGit) ObjectPackIdx(hash plumbing.Hash) (billy.File, error) {
248+
err := d.hasPack(hash)
249+
if err != nil {
250+
return nil, err
251+
}
252+
203253
return d.objectPackOpen(hash, `idx`)
204254
}
205255

206256
func (d *DotGit) DeleteOldObjectPackAndIndex(hash plumbing.Hash, t time.Time) error {
257+
d.cleanPackList()
258+
207259
path := d.objectPackPath(hash, `pack`)
208260
if !t.IsZero() {
209261
fi, err := d.fs.Stat(path)
@@ -224,12 +276,23 @@ func (d *DotGit) DeleteOldObjectPackAndIndex(hash plumbing.Hash, t time.Time) er
224276

225277
// NewObject return a writer for a new object file.
226278
func (d *DotGit) NewObject() (*ObjectWriter, error) {
279+
d.cleanObjectList()
280+
227281
return newObjectWriter(d.fs)
228282
}
229283

230284
// Objects returns a slice with the hashes of objects found under the
231285
// .git/objects/ directory.
232286
func (d *DotGit) Objects() ([]plumbing.Hash, error) {
287+
if d.Static {
288+
err := d.genObjectList()
289+
if err != nil {
290+
return nil, err
291+
}
292+
293+
return d.objectList, nil
294+
}
295+
233296
var objects []plumbing.Hash
234297
err := d.ForEachObjectHash(func(hash plumbing.Hash) error {
235298
objects = append(objects, hash)
@@ -241,9 +304,29 @@ func (d *DotGit) Objects() ([]plumbing.Hash, error) {
241304
return objects, nil
242305
}
243306

244-
// Objects returns a slice with the hashes of objects found under the
245-
// .git/objects/ directory.
307+
// ForEachObjectHash iterates over the hashes of objects found under the
308+
// .git/objects/ directory and executes the provided .
246309
func (d *DotGit) ForEachObjectHash(fun func(plumbing.Hash) error) error {
310+
if !d.Static {
311+
return d.forEachObjectHash(fun)
312+
}
313+
314+
err := d.genObjectList()
315+
if err != nil {
316+
return err
317+
}
318+
319+
for _, h := range d.objectList {
320+
err := fun(h)
321+
if err != nil {
322+
return err
323+
}
324+
}
325+
326+
return nil
327+
}
328+
329+
func (d *DotGit) forEachObjectHash(fun func(plumbing.Hash) error) error {
247330
files, err := d.fs.ReadDir(objectsPath)
248331
if err != nil {
249332
if os.IsNotExist(err) {
@@ -278,6 +361,87 @@ func (d *DotGit) ForEachObjectHash(fun func(plumbing.Hash) error) error {
278361
return nil
279362
}
280363

364+
func (d *DotGit) cleanObjectList() {
365+
d.objectMap = nil
366+
d.objectList = nil
367+
}
368+
369+
func (d *DotGit) genObjectList() error {
370+
if d.objectMap != nil {
371+
return nil
372+
}
373+
374+
d.objectMap = make(map[plumbing.Hash]struct{})
375+
return d.forEachObjectHash(func(h plumbing.Hash) error {
376+
d.objectList = append(d.objectList, h)
377+
d.objectMap[h] = struct{}{}
378+
379+
return nil
380+
})
381+
}
382+
383+
func (d *DotGit) hasObject(h plumbing.Hash) error {
384+
if !d.Static {
385+
return nil
386+
}
387+
388+
err := d.genObjectList()
389+
if err != nil {
390+
return err
391+
}
392+
393+
_, ok := d.objectMap[h]
394+
if !ok {
395+
return plumbing.ErrObjectNotFound
396+
}
397+
398+
return nil
399+
}
400+
401+
func (d *DotGit) cleanPackList() {
402+
d.packMap = nil
403+
d.packList = nil
404+
}
405+
406+
func (d *DotGit) genPackList() error {
407+
if d.packMap != nil {
408+
return nil
409+
}
410+
411+
op, err := d.objectPacks()
412+
if err != nil {
413+
return err
414+
}
415+
416+
d.packMap = make(map[plumbing.Hash]struct{})
417+
d.packList = nil
418+
419+
for _, h := range op {
420+
d.packList = append(d.packList, h)
421+
d.packMap[h] = struct{}{}
422+
}
423+
424+
return nil
425+
}
426+
427+
func (d *DotGit) hasPack(h plumbing.Hash) error {
428+
if !d.Static {
429+
return nil
430+
}
431+
432+
err := d.genPackList()
433+
if err != nil {
434+
return err
435+
}
436+
437+
_, ok := d.packMap[h]
438+
if !ok {
439+
return ErrPackfileNotFound
440+
}
441+
442+
return nil
443+
}
444+
281445
func (d *DotGit) objectPath(h plumbing.Hash) string {
282446
hash := h.String()
283447
return d.fs.Join(objectsPath, hash[0:2], hash[2:40])
@@ -322,6 +486,11 @@ func (d *DotGit) hasIncomingObjects() bool {
322486

323487
// Object returns a fs.File pointing the object file, if exists
324488
func (d *DotGit) Object(h plumbing.Hash) (billy.File, error) {
489+
err := d.hasObject(h)
490+
if err != nil {
491+
return nil, err
492+
}
493+
325494
obj1, err1 := d.fs.Open(d.objectPath(h))
326495
if os.IsNotExist(err1) && d.hasIncomingObjects() {
327496
obj2, err2 := d.fs.Open(d.incomingObjectPath(h))
@@ -335,6 +504,11 @@ func (d *DotGit) Object(h plumbing.Hash) (billy.File, error) {
335504

336505
// ObjectStat returns a os.FileInfo pointing the object file, if exists
337506
func (d *DotGit) ObjectStat(h plumbing.Hash) (os.FileInfo, error) {
507+
err := d.hasObject(h)
508+
if err != nil {
509+
return nil, err
510+
}
511+
338512
obj1, err1 := d.fs.Stat(d.objectPath(h))
339513
if os.IsNotExist(err1) && d.hasIncomingObjects() {
340514
obj2, err2 := d.fs.Stat(d.incomingObjectPath(h))
@@ -348,6 +522,8 @@ func (d *DotGit) ObjectStat(h plumbing.Hash) (os.FileInfo, error) {
348522

349523
// ObjectDelete removes the object file, if exists
350524
func (d *DotGit) ObjectDelete(h plumbing.Hash) error {
525+
d.cleanObjectList()
526+
351527
err1 := d.fs.Remove(d.objectPath(h))
352528
if os.IsNotExist(err1) && d.hasIncomingObjects() {
353529
err2 := d.fs.Remove(d.incomingObjectPath(h))

storage/filesystem/storage.go

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ import (
1111
// standard git format (this is, the .git directory). Zero values of this type
1212
// are not safe to use, see the NewStorage function below.
1313
type Storage struct {
14+
StorageOptions
15+
1416
fs billy.Filesystem
1517
dir *dotgit.DotGit
1618

@@ -22,17 +24,36 @@ type Storage struct {
2224
ModuleStorage
2325
}
2426

27+
// StorageOptions holds configuration for the storage.
28+
type StorageOptions struct {
29+
// Static means that the filesystem is not modified while the repo is open.
30+
Static bool
31+
}
32+
2533
// NewStorage returns a new Storage backed by a given `fs.Filesystem`
2634
func NewStorage(fs billy.Filesystem) (*Storage, error) {
27-
dir := dotgit.New(fs)
35+
return NewStorageWithOptions(fs, StorageOptions{})
36+
}
37+
38+
// NewStorageWithOptions returns a new Storage backed by a given `fs.Filesystem`
39+
func NewStorageWithOptions(
40+
fs billy.Filesystem,
41+
ops StorageOptions,
42+
) (*Storage, error) {
43+
dOps := dotgit.DotGitOptions{
44+
Static: ops.Static,
45+
}
46+
47+
dir := dotgit.NewWithOptions(fs, dOps)
2848
o, err := NewObjectStorage(dir)
2949
if err != nil {
3050
return nil, err
3151
}
3252

3353
return &Storage{
34-
fs: fs,
35-
dir: dir,
54+
StorageOptions: ops,
55+
fs: fs,
56+
dir: dir,
3657

3758
ObjectStorage: o,
3859
ReferenceStorage: ReferenceStorage{dir: dir},

0 commit comments

Comments
 (0)