@@ -57,21 +57,43 @@ var (
5757 ErrSymRefTargetNotFound = errors .New ("symbolic reference target not found" )
5858)
5959
60+ // Options holds configuration for the storage.
61+ type Options struct {
62+ // ExclusiveAccess means that the filesystem is not modified externally
63+ // while the repo is open.
64+ ExclusiveAccess bool
65+ }
66+
6067// The DotGit type represents a local git repository on disk. This
6168// type is not zero-value-safe, use the New function to initialize it.
6269type DotGit struct {
63- fs billy.Filesystem
70+ options Options
71+ fs billy.Filesystem
6472
6573 // incoming object directory information
6674 incomingChecked bool
6775 incomingDirName string
76+
77+ objectList []plumbing.Hash
78+ objectMap map [plumbing.Hash ]struct {}
79+ packList []plumbing.Hash
80+ packMap map [plumbing.Hash ]struct {}
6881}
6982
7083// New returns a DotGit value ready to be used. The path argument must
7184// be the absolute path of a git repository directory (e.g.
7285// "/foo/bar/.git").
7386func New (fs billy.Filesystem ) * DotGit {
74- return & DotGit {fs : fs }
87+ return NewWithOptions (fs , Options {})
88+ }
89+
90+ // NewWithOptions creates a new DotGit and sets non default configuration
91+ // options. See New for complete help.
92+ func NewWithOptions (fs billy.Filesystem , o Options ) * DotGit {
93+ return & DotGit {
94+ options : o ,
95+ fs : fs ,
96+ }
7597}
7698
7799// Initialize creates all the folder scaffolding.
@@ -143,11 +165,25 @@ func (d *DotGit) Shallow() (billy.File, error) {
143165// NewObjectPack return a writer for a new packfile, it saves the packfile to
144166// disk and also generates and save the index for the given packfile.
145167func (d * DotGit ) NewObjectPack () (* PackWriter , error ) {
168+ d .cleanPackList ()
146169 return newPackWrite (d .fs )
147170}
148171
149172// ObjectPacks returns the list of availables packfiles
150173func (d * DotGit ) ObjectPacks () ([]plumbing.Hash , error ) {
174+ if ! d .options .ExclusiveAccess {
175+ return d .objectPacks ()
176+ }
177+
178+ err := d .genPackList ()
179+ if err != nil {
180+ return nil , err
181+ }
182+
183+ return d .packList , nil
184+ }
185+
186+ func (d * DotGit ) objectPacks () ([]plumbing.Hash , error ) {
151187 packDir := d .fs .Join (objectsPath , packPath )
152188 files , err := d .fs .ReadDir (packDir )
153189 if err != nil {
@@ -181,6 +217,11 @@ func (d *DotGit) objectPackPath(hash plumbing.Hash, extension string) string {
181217}
182218
183219func (d * DotGit ) objectPackOpen (hash plumbing.Hash , extension string ) (billy.File , error ) {
220+ err := d .hasPack (hash )
221+ if err != nil {
222+ return nil , err
223+ }
224+
184225 pack , err := d .fs .Open (d .objectPackPath (hash , extension ))
185226 if err != nil {
186227 if os .IsNotExist (err ) {
@@ -195,15 +236,27 @@ func (d *DotGit) objectPackOpen(hash plumbing.Hash, extension string) (billy.Fil
195236
196237// ObjectPack returns a fs.File of the given packfile
197238func (d * DotGit ) ObjectPack (hash plumbing.Hash ) (billy.File , error ) {
239+ err := d .hasPack (hash )
240+ if err != nil {
241+ return nil , err
242+ }
243+
198244 return d .objectPackOpen (hash , `pack` )
199245}
200246
201247// ObjectPackIdx returns a fs.File of the index file for a given packfile
202248func (d * DotGit ) ObjectPackIdx (hash plumbing.Hash ) (billy.File , error ) {
249+ err := d .hasPack (hash )
250+ if err != nil {
251+ return nil , err
252+ }
253+
203254 return d .objectPackOpen (hash , `idx` )
204255}
205256
206257func (d * DotGit ) DeleteOldObjectPackAndIndex (hash plumbing.Hash , t time.Time ) error {
258+ d .cleanPackList ()
259+
207260 path := d .objectPackPath (hash , `pack` )
208261 if ! t .IsZero () {
209262 fi , err := d .fs .Stat (path )
@@ -224,12 +277,23 @@ func (d *DotGit) DeleteOldObjectPackAndIndex(hash plumbing.Hash, t time.Time) er
224277
225278// NewObject return a writer for a new object file.
226279func (d * DotGit ) NewObject () (* ObjectWriter , error ) {
280+ d .cleanObjectList ()
281+
227282 return newObjectWriter (d .fs )
228283}
229284
230285// Objects returns a slice with the hashes of objects found under the
231286// .git/objects/ directory.
232287func (d * DotGit ) Objects () ([]plumbing.Hash , error ) {
288+ if d .options .ExclusiveAccess {
289+ err := d .genObjectList ()
290+ if err != nil {
291+ return nil , err
292+ }
293+
294+ return d .objectList , nil
295+ }
296+
233297 var objects []plumbing.Hash
234298 err := d .ForEachObjectHash (func (hash plumbing.Hash ) error {
235299 objects = append (objects , hash )
@@ -241,9 +305,29 @@ func (d *DotGit) Objects() ([]plumbing.Hash, error) {
241305 return objects , nil
242306}
243307
244- // Objects returns a slice with the hashes of objects found under the
245- // .git/objects/ directory.
308+ // ForEachObjectHash iterates over the hashes of objects found under the
309+ // .git/objects/ directory and executes the provided function .
246310func (d * DotGit ) ForEachObjectHash (fun func (plumbing.Hash ) error ) error {
311+ if ! d .options .ExclusiveAccess {
312+ return d .forEachObjectHash (fun )
313+ }
314+
315+ err := d .genObjectList ()
316+ if err != nil {
317+ return err
318+ }
319+
320+ for _ , h := range d .objectList {
321+ err := fun (h )
322+ if err != nil {
323+ return err
324+ }
325+ }
326+
327+ return nil
328+ }
329+
330+ func (d * DotGit ) forEachObjectHash (fun func (plumbing.Hash ) error ) error {
247331 files , err := d .fs .ReadDir (objectsPath )
248332 if err != nil {
249333 if os .IsNotExist (err ) {
@@ -278,6 +362,87 @@ func (d *DotGit) ForEachObjectHash(fun func(plumbing.Hash) error) error {
278362 return nil
279363}
280364
365+ func (d * DotGit ) cleanObjectList () {
366+ d .objectMap = nil
367+ d .objectList = nil
368+ }
369+
370+ func (d * DotGit ) genObjectList () error {
371+ if d .objectMap != nil {
372+ return nil
373+ }
374+
375+ d .objectMap = make (map [plumbing.Hash ]struct {})
376+ return d .forEachObjectHash (func (h plumbing.Hash ) error {
377+ d .objectList = append (d .objectList , h )
378+ d .objectMap [h ] = struct {}{}
379+
380+ return nil
381+ })
382+ }
383+
384+ func (d * DotGit ) hasObject (h plumbing.Hash ) error {
385+ if ! d .options .ExclusiveAccess {
386+ return nil
387+ }
388+
389+ err := d .genObjectList ()
390+ if err != nil {
391+ return err
392+ }
393+
394+ _ , ok := d .objectMap [h ]
395+ if ! ok {
396+ return plumbing .ErrObjectNotFound
397+ }
398+
399+ return nil
400+ }
401+
402+ func (d * DotGit ) cleanPackList () {
403+ d .packMap = nil
404+ d .packList = nil
405+ }
406+
407+ func (d * DotGit ) genPackList () error {
408+ if d .packMap != nil {
409+ return nil
410+ }
411+
412+ op , err := d .objectPacks ()
413+ if err != nil {
414+ return err
415+ }
416+
417+ d .packMap = make (map [plumbing.Hash ]struct {})
418+ d .packList = nil
419+
420+ for _ , h := range op {
421+ d .packList = append (d .packList , h )
422+ d .packMap [h ] = struct {}{}
423+ }
424+
425+ return nil
426+ }
427+
428+ func (d * DotGit ) hasPack (h plumbing.Hash ) error {
429+ if ! d .options .ExclusiveAccess {
430+ return nil
431+ }
432+
433+ err := d .genPackList ()
434+ if err != nil {
435+ return err
436+ }
437+
438+ _ , ok := d .packMap [h ]
439+ if ! ok {
440+ return ErrPackfileNotFound
441+ }
442+
443+ return nil
444+ }
445+
281446func (d * DotGit ) objectPath (h plumbing.Hash ) string {
282447 hash := h .String ()
283448 return d .fs .Join (objectsPath , hash [0 :2 ], hash [2 :40 ])
@@ -322,6 +487,11 @@ func (d *DotGit) hasIncomingObjects() bool {
322487
323488// Object returns a fs.File pointing the object file, if exists
324489func (d * DotGit ) Object (h plumbing.Hash ) (billy.File , error ) {
490+ err := d .hasObject (h )
491+ if err != nil {
492+ return nil , err
493+ }
494+
325495 obj1 , err1 := d .fs .Open (d .objectPath (h ))
326496 if os .IsNotExist (err1 ) && d .hasIncomingObjects () {
327497 obj2 , err2 := d .fs .Open (d .incomingObjectPath (h ))
@@ -335,6 +505,11 @@ func (d *DotGit) Object(h plumbing.Hash) (billy.File, error) {
335505
336506// ObjectStat returns a os.FileInfo pointing the object file, if exists
337507func (d * DotGit ) ObjectStat (h plumbing.Hash ) (os.FileInfo , error ) {
508+ err := d .hasObject (h )
509+ if err != nil {
510+ return nil , err
511+ }
512+
338513 obj1 , err1 := d .fs .Stat (d .objectPath (h ))
339514 if os .IsNotExist (err1 ) && d .hasIncomingObjects () {
340515 obj2 , err2 := d .fs .Stat (d .incomingObjectPath (h ))
@@ -348,6 +523,8 @@ func (d *DotGit) ObjectStat(h plumbing.Hash) (os.FileInfo, error) {
348523
349524// ObjectDelete removes the object file, if exists
350525func (d * DotGit ) ObjectDelete (h plumbing.Hash ) error {
526+ d .cleanObjectList ()
527+
351528 err1 := d .fs .Remove (d .objectPath (h ))
352529 if os .IsNotExist (err1 ) && d .hasIncomingObjects () {
353530 err2 := d .fs .Remove (d .incomingObjectPath (h ))
0 commit comments