@@ -26,6 +26,10 @@ type ObjectStorage struct {
2626
2727 dir * dotgit.DotGit
2828 index map [plumbing.Hash ]idxfile.Index
29+
30+ packList []plumbing.Hash
31+ packListIdx int
32+ packfiles map [plumbing.Hash ]* packfile.Packfile
2933}
3034
3135// NewObjectStorage creates a new ObjectStorage with the given .git directory and cache.
@@ -187,6 +191,73 @@ func (s *ObjectStorage) encodedObjectSizeFromUnpacked(h plumbing.Hash) (
187191 return size , err
188192}
189193
194+ func (s * ObjectStorage ) packfile (idx idxfile.Index , pack plumbing.Hash ) (* packfile.Packfile , error ) {
195+ if p := s .packfileFromCache (pack ); p != nil {
196+ return p , nil
197+ }
198+
199+ f , err := s .dir .ObjectPack (pack )
200+ if err != nil {
201+ return nil , err
202+ }
203+
204+ var p * packfile.Packfile
205+ if s .objectCache != nil {
206+ p = packfile .NewPackfileWithCache (idx , s .dir .Fs (), f , s .objectCache )
207+ } else {
208+ p = packfile .NewPackfile (idx , s .dir .Fs (), f )
209+ }
210+
211+ return p , s .storePackfileInCache (pack , p )
212+ }
213+
214+ func (s * ObjectStorage ) packfileFromCache (hash plumbing.Hash ) * packfile.Packfile {
215+ if s .packfiles == nil {
216+ if s .options .KeepDescriptors {
217+ s .packfiles = make (map [plumbing.Hash ]* packfile.Packfile )
218+ } else if s .options .MaxOpenDescriptors > 0 {
219+ s .packList = make ([]plumbing.Hash , s .options .MaxOpenDescriptors )
220+ s .packfiles = make (map [plumbing.Hash ]* packfile.Packfile , s .options .MaxOpenDescriptors )
221+ }
222+ }
223+
224+ return s .packfiles [hash ]
225+ }
226+
227+ func (s * ObjectStorage ) storePackfileInCache (hash plumbing.Hash , p * packfile.Packfile ) error {
228+ if s .options .KeepDescriptors {
229+ s .packfiles [hash ] = p
230+ return nil
231+ }
232+
233+ if s .options .MaxOpenDescriptors <= 0 {
234+ return nil
235+ }
236+
237+ // start over as the limit of packList is hit
238+ if s .packListIdx >= len (s .packList ) {
239+ s .packListIdx = 0
240+ }
241+
242+ // close the existing packfile if open
243+ if next := s .packList [s .packListIdx ]; ! next .IsZero () {
244+ open := s .packfiles [next ]
245+ delete (s .packfiles , next )
246+ if open != nil {
247+ if err := open .Close (); err != nil {
248+ return err
249+ }
250+ }
251+ }
252+
253+ // cache newly open packfile
254+ s .packList [s .packListIdx ] = hash
255+ s .packfiles [hash ] = p
256+ s .packListIdx ++
257+
258+ return nil
259+ }
260+
190261func (s * ObjectStorage ) encodedObjectSizeFromPackfile (h plumbing.Hash ) (
191262 size int64 , err error ) {
192263 if err := s .requireIndex (); err != nil {
@@ -198,12 +269,6 @@ func (s *ObjectStorage) encodedObjectSizeFromPackfile(h plumbing.Hash) (
198269 return 0 , plumbing .ErrObjectNotFound
199270 }
200271
201- f , err := s .dir .ObjectPack (pack )
202- if err != nil {
203- return 0 , err
204- }
205- defer ioutil .CheckClose (f , & err )
206-
207272 idx := s .index [pack ]
208273 hash , err := idx .FindHash (offset )
209274 if err == nil {
@@ -215,11 +280,13 @@ func (s *ObjectStorage) encodedObjectSizeFromPackfile(h plumbing.Hash) (
215280 return 0 , err
216281 }
217282
218- var p * packfile.Packfile
219- if s .objectCache != nil {
220- p = packfile .NewPackfileWithCache (idx , s .dir .Fs (), f , s .objectCache )
221- } else {
222- p = packfile .NewPackfile (idx , s .dir .Fs (), f )
283+ p , err := s .packfile (idx , pack )
284+ if err != nil {
285+ return 0 , err
286+ }
287+
288+ if ! s .options .KeepDescriptors && s .options .MaxOpenDescriptors == 0 {
289+ defer ioutil .CheckClose (p , & err )
223290 }
224291
225292 return p .GetSizeByOffset (offset )
@@ -361,29 +428,28 @@ func (s *ObjectStorage) getFromPackfile(h plumbing.Hash, canBeDelta bool) (
361428 return nil , plumbing .ErrObjectNotFound
362429 }
363430
364- f , err := s .dir .ObjectPack (pack )
431+ idx := s .index [pack ]
432+ p , err := s .packfile (idx , pack )
365433 if err != nil {
366434 return nil , err
367435 }
368436
369- if ! s .options .KeepDescriptors {
370- defer ioutil .CheckClose (f , & err )
437+ if ! s .options .KeepDescriptors && s . options . MaxOpenDescriptors == 0 {
438+ defer ioutil .CheckClose (p , & err )
371439 }
372440
373- idx := s .index [pack ]
374441 if canBeDelta {
375- return s .decodeDeltaObjectAt (f , idx , offset , hash )
442+ return s .decodeDeltaObjectAt (p , offset , hash )
376443 }
377444
378- return s .decodeObjectAt (f , idx , offset )
445+ return s .decodeObjectAt (p , offset )
379446}
380447
381448func (s * ObjectStorage ) decodeObjectAt (
382- f billy.File ,
383- idx idxfile.Index ,
449+ p * packfile.Packfile ,
384450 offset int64 ,
385451) (plumbing.EncodedObject , error ) {
386- hash , err := idx .FindHash (offset )
452+ hash , err := p .FindHash (offset )
387453 if err == nil {
388454 obj , ok := s .objectCache .Get (hash )
389455 if ok {
@@ -395,28 +461,16 @@ func (s *ObjectStorage) decodeObjectAt(
395461 return nil , err
396462 }
397463
398- var p * packfile.Packfile
399- if s .objectCache != nil {
400- p = packfile .NewPackfileWithCache (idx , s .dir .Fs (), f , s .objectCache )
401- } else {
402- p = packfile .NewPackfile (idx , s .dir .Fs (), f )
403- }
404-
405464 return p .GetByOffset (offset )
406465}
407466
408467func (s * ObjectStorage ) decodeDeltaObjectAt (
409- f billy.File ,
410- idx idxfile.Index ,
468+ p * packfile.Packfile ,
411469 offset int64 ,
412470 hash plumbing.Hash ,
413471) (plumbing.EncodedObject , error ) {
414- if _ , err := f .Seek (0 , io .SeekStart ); err != nil {
415- return nil , err
416- }
417-
418- p := packfile .NewScanner (f )
419- header , err := p .SeekObjectHeader (offset )
472+ scan := p .Scanner ()
473+ header , err := scan .SeekObjectHeader (offset )
420474 if err != nil {
421475 return nil , err
422476 }
@@ -429,12 +483,12 @@ func (s *ObjectStorage) decodeDeltaObjectAt(
429483 case plumbing .REFDeltaObject :
430484 base = header .Reference
431485 case plumbing .OFSDeltaObject :
432- base , err = idx .FindHash (header .OffsetReference )
486+ base , err = p .FindHash (header .OffsetReference )
433487 if err != nil {
434488 return nil , err
435489 }
436490 default :
437- return s .decodeObjectAt (f , idx , offset )
491+ return s .decodeObjectAt (p , offset )
438492 }
439493
440494 obj := & plumbing.MemoryObject {}
@@ -444,7 +498,7 @@ func (s *ObjectStorage) decodeDeltaObjectAt(
444498 return nil , err
445499 }
446500
447- if _ , _ , err := p .NextObject (w ); err != nil {
501+ if _ , _ , err := scan .NextObject (w ); err != nil {
448502 return nil , err
449503 }
450504
@@ -515,7 +569,20 @@ func (s *ObjectStorage) buildPackfileIters(
515569
516570// Close closes all opened files.
517571func (s * ObjectStorage ) Close () error {
518- return s .dir .Close ()
572+ var firstError error
573+ if s .options .KeepDescriptors || s .options .MaxOpenDescriptors > 0 {
574+ for _ , packfile := range s .packfiles {
575+ err := packfile .Close ()
576+ if firstError == nil && err != nil {
577+ firstError = err
578+ }
579+ }
580+ }
581+
582+ s .packfiles = nil
583+ s .dir .Close ()
584+
585+ return firstError
519586}
520587
521588type lazyPackfilesIter struct {
0 commit comments