1package filesystem
2
3import (
4 "bytes"
5 "fmt"
6 "io"
7 "os"
8 "sync"
9 "time"
10
11 "github.com/go-git/go-git/v5/plumbing"
12 "github.com/go-git/go-git/v5/plumbing/cache"
13 "github.com/go-git/go-git/v5/plumbing/format/idxfile"
14 "github.com/go-git/go-git/v5/plumbing/format/objfile"
15 "github.com/go-git/go-git/v5/plumbing/format/packfile"
16 "github.com/go-git/go-git/v5/plumbing/storer"
17 "github.com/go-git/go-git/v5/storage/filesystem/dotgit"
18 "github.com/go-git/go-git/v5/utils/ioutil"
19)
20
21type ObjectStorage struct {
22 options Options
23
24 // objectCache is an object cache used to cache delta's bases and also recently
25 // loaded loose objects.
26 objectCache cache.Object
27
28 dir *dotgit.DotGit
29 index map[plumbing.Hash]idxfile.Index
30
31 packList []plumbing.Hash
32 packListIdx int
33 packfiles map[plumbing.Hash]*packfile.Packfile
34 muI sync.RWMutex
35 muP sync.RWMutex
36}
37
38// NewObjectStorage creates a new ObjectStorage with the given .git directory and cache.
39func NewObjectStorage(dir *dotgit.DotGit, objectCache cache.Object) *ObjectStorage {
40 return NewObjectStorageWithOptions(dir, objectCache, Options{})
41}
42
43// NewObjectStorageWithOptions creates a new ObjectStorage with the given .git directory, cache and extra options
44func NewObjectStorageWithOptions(dir *dotgit.DotGit, objectCache cache.Object, ops Options) *ObjectStorage {
45 return &ObjectStorage{
46 options: ops,
47 objectCache: objectCache,
48 dir: dir,
49 }
50}
51
52func (s *ObjectStorage) requireIndex() error {
53 if s.index != nil {
54 return nil
55 }
56
57 s.index = make(map[plumbing.Hash]idxfile.Index)
58 packs, err := s.dir.ObjectPacks()
59 if err != nil {
60 return err
61 }
62
63 for _, h := range packs {
64 if err := s.loadIdxFile(h); err != nil {
65 return err
66 }
67 }
68
69 return nil
70}
71
72// Reindex indexes again all packfiles. Useful if git changed packfiles externally
73func (s *ObjectStorage) Reindex() {
74 s.index = nil
75}
76
77func (s *ObjectStorage) loadIdxFile(h plumbing.Hash) (err error) {
78 f, err := s.dir.ObjectPackIdx(h)
79 if err != nil {
80 return err
81 }
82
83 defer ioutil.CheckClose(f, &err)
84
85 idxf := idxfile.NewMemoryIndex()
86 d := idxfile.NewDecoder(f)
87 if err = d.Decode(idxf); err != nil {
88 return err
89 }
90
91 s.index[h] = idxf
92 return err
93}
94
95func (s *ObjectStorage) RawObjectWriter(typ plumbing.ObjectType, sz int64) (w io.WriteCloser, err error) {
96 ow, err := s.dir.NewObject()
97 if err != nil {
98 return nil, err
99 }
100
101 err = ow.WriteHeader(typ, sz)
102 if err != nil {
103 return nil, err
104 }
105
106 return ow, nil
107}
108
109func (s *ObjectStorage) NewEncodedObject() plumbing.EncodedObject {
110 return &plumbing.MemoryObject{}
111}
112
113func (s *ObjectStorage) PackfileWriter() (io.WriteCloser, error) {
114 if err := s.requireIndex(); err != nil {
115 return nil, err
116 }
117
118 w, err := s.dir.NewObjectPack()
119 if err != nil {
120 return nil, err
121 }
122
123 w.Notify = func(h plumbing.Hash, writer *idxfile.Writer) {
124 index, err := writer.Index()
125 if err == nil {
126 s.index[h] = index
127 }
128 }
129
130 return w, nil
131}
132
133// SetEncodedObject adds a new object to the storage.
134func (s *ObjectStorage) SetEncodedObject(o plumbing.EncodedObject) (h plumbing.Hash, err error) {
135 if o.Type() == plumbing.OFSDeltaObject || o.Type() == plumbing.REFDeltaObject {
136 return plumbing.ZeroHash, plumbing.ErrInvalidType
137 }
138
139 ow, err := s.dir.NewObject()
140 if err != nil {
141 return plumbing.ZeroHash, err
142 }
143
144 defer ioutil.CheckClose(ow, &err)
145
146 or, err := o.Reader()
147 if err != nil {
148 return plumbing.ZeroHash, err
149 }
150
151 defer ioutil.CheckClose(or, &err)
152
153 if err = ow.WriteHeader(o.Type(), o.Size()); err != nil {
154 return plumbing.ZeroHash, err
155 }
156
157 if _, err = io.Copy(ow, or); err != nil {
158 return plumbing.ZeroHash, err
159 }
160
161 return o.Hash(), err
162}
163
164// LazyWriter returns a lazy ObjectWriter that is bound to a DotGit file.
165// It first write the header passing on the object type and size, so
166// that the object contents can be written later, without the need to
167// create a MemoryObject and buffering its entire contents into memory.
168func (s *ObjectStorage) LazyWriter() (w io.WriteCloser, wh func(typ plumbing.ObjectType, sz int64) error, err error) {
169 ow, err := s.dir.NewObject()
170 if err != nil {
171 return nil, nil, err
172 }
173
174 return ow, ow.WriteHeader, nil
175}
176
177// HasEncodedObject returns nil if the object exists, without actually
178// reading the object data from storage.
179func (s *ObjectStorage) HasEncodedObject(h plumbing.Hash) (err error) {
180 // Check unpacked objects
181 f, err := s.dir.Object(h)
182 if err != nil {
183 if !os.IsNotExist(err) {
184 return err
185 }
186 // Fall through to check packed objects.
187 } else {
188 defer ioutil.CheckClose(f, &err)
189 return nil
190 }
191
192 // Check packed objects.
193 if err := s.requireIndex(); err != nil {
194 return err
195 }
196 _, _, offset := s.findObjectInPackfile(h)
197 if offset == -1 {
198 return plumbing.ErrObjectNotFound
199 }
200 return nil
201}
202
203func (s *ObjectStorage) encodedObjectSizeFromUnpacked(h plumbing.Hash) (
204 size int64, err error) {
205 f, err := s.dir.Object(h)
206 if err != nil {
207 if os.IsNotExist(err) {
208 return 0, plumbing.ErrObjectNotFound
209 }
210
211 return 0, err
212 }
213
214 r, err := objfile.NewReader(f)
215 if err != nil {
216 return 0, err
217 }
218 defer ioutil.CheckClose(r, &err)
219
220 _, size, err = r.Header()
221 return size, err
222}
223
224func (s *ObjectStorage) packfile(idx idxfile.Index, pack plumbing.Hash) (*packfile.Packfile, error) {
225 if p := s.packfileFromCache(pack); p != nil {
226 return p, nil
227 }
228
229 f, err := s.dir.ObjectPack(pack)
230 if err != nil {
231 return nil, err
232 }
233
234 p := packfile.NewPackfile(f,
235 packfile.WithIdx(idx),
236 packfile.WithFs(s.dir.Fs()),
237 packfile.WithCache(s.objectCache),
238 )
239 return p, s.storePackfileInCache(pack, p)
240}
241
242func (s *ObjectStorage) packfileFromCache(hash plumbing.Hash) *packfile.Packfile {
243 s.muP.Lock()
244 defer s.muP.Unlock()
245
246 if s.packfiles == nil {
247 if s.options.KeepDescriptors {
248 s.packfiles = make(map[plumbing.Hash]*packfile.Packfile)
249 } else if s.options.MaxOpenDescriptors > 0 {
250 s.packList = make([]plumbing.Hash, s.options.MaxOpenDescriptors)
251 s.packfiles = make(map[plumbing.Hash]*packfile.Packfile, s.options.MaxOpenDescriptors)
252 }
253 }
254
255 return s.packfiles[hash]
256}
257
258func (s *ObjectStorage) storePackfileInCache(hash plumbing.Hash, p *packfile.Packfile) error {
259 s.muP.Lock()
260 defer s.muP.Unlock()
261
262 if s.options.KeepDescriptors {
263 s.packfiles[hash] = p
264 return nil
265 }
266
267 if s.options.MaxOpenDescriptors <= 0 {
268 return nil
269 }
270
271 // start over as the limit of packList is hit
272 if s.packListIdx >= len(s.packList) {
273 s.packListIdx = 0
274 }
275
276 // close the existing packfile if open
277 if next := s.packList[s.packListIdx]; !next.IsZero() {
278 open := s.packfiles[next]
279 delete(s.packfiles, next)
280 if open != nil {
281 if err := open.Close(); err != nil {
282 return err
283 }
284 }
285 }
286
287 // cache newly open packfile
288 s.packList[s.packListIdx] = hash
289 s.packfiles[hash] = p
290 s.packListIdx++
291
292 return nil
293}
294
295func (s *ObjectStorage) encodedObjectSizeFromPackfile(h plumbing.Hash) (
296 size int64, err error) {
297 if err := s.requireIndex(); err != nil {
298 return 0, err
299 }
300
301 pack, _, offset := s.findObjectInPackfile(h)
302 if offset == -1 {
303 return 0, plumbing.ErrObjectNotFound
304 }
305
306 idx := s.index[pack]
307 hash, err := idx.FindHash(offset)
308 if err == nil {
309 obj, ok := s.objectCache.Get(hash)
310 if ok {
311 return obj.Size(), nil
312 }
313 } else if err != nil && err != plumbing.ErrObjectNotFound {
314 return 0, err
315 }
316
317 p, err := s.packfile(idx, pack)
318 if err != nil {
319 return 0, err
320 }
321
322 if !s.options.KeepDescriptors && s.options.MaxOpenDescriptors == 0 {
323 defer ioutil.CheckClose(p, &err)
324 }
325
326 return p.GetSizeByOffset(offset)
327}
328
329// EncodedObjectSize returns the plaintext size of the given object,
330// without actually reading the full object data from storage.
331func (s *ObjectStorage) EncodedObjectSize(h plumbing.Hash) (
332 size int64, err error) {
333 size, err = s.encodedObjectSizeFromUnpacked(h)
334 if err != nil && err != plumbing.ErrObjectNotFound {
335 return 0, err
336 } else if err == nil {
337 return size, nil
338 }
339
340 return s.encodedObjectSizeFromPackfile(h)
341}
342
343// EncodedObject returns the object with the given hash, by searching for it in
344// the packfile and the git object directories.
345func (s *ObjectStorage) EncodedObject(t plumbing.ObjectType, h plumbing.Hash) (plumbing.EncodedObject, error) {
346 var obj plumbing.EncodedObject
347 var err error
348
349 if s.index != nil {
350 obj, err = s.getFromPackfile(h, false)
351 if err == plumbing.ErrObjectNotFound {
352 obj, err = s.getFromUnpacked(h)
353 }
354 } else {
355 obj, err = s.getFromUnpacked(h)
356 if err == plumbing.ErrObjectNotFound {
357 obj, err = s.getFromPackfile(h, false)
358 }
359 }
360
361 // If the error is still object not found, check if it's a shared object
362 // repository.
363 if err == plumbing.ErrObjectNotFound {
364 dotgits, e := s.dir.Alternates()
365 if e == nil {
366 // Create a new object storage with the DotGit(s) and check for the
367 // required hash object. Skip when not found.
368 for _, dg := range dotgits {
369 o := NewObjectStorage(dg, s.objectCache)
370 enobj, enerr := o.EncodedObject(t, h)
371 if enerr != nil {
372 continue
373 }
374 return enobj, nil
375 }
376 }
377 }
378
379 if err != nil {
380 return nil, err
381 }
382
383 if obj == nil || (plumbing.AnyObject != t && obj.Type() != t) {
384 return nil, plumbing.ErrObjectNotFound
385 }
386
387 return obj, nil
388}
389
390// DeltaObject returns the object with the given hash, by searching for
391// it in the packfile and the git object directories.
392func (s *ObjectStorage) DeltaObject(t plumbing.ObjectType,
393 h plumbing.Hash) (plumbing.EncodedObject, error) {
394 obj, err := s.getFromUnpacked(h)
395 if err == plumbing.ErrObjectNotFound {
396 obj, err = s.getFromPackfile(h, true)
397 }
398
399 if err != nil {
400 return nil, err
401 }
402
403 if plumbing.AnyObject != t && obj.Type() != t {
404 return nil, plumbing.ErrObjectNotFound
405 }
406
407 return obj, nil
408}
409
410func (s *ObjectStorage) getFromUnpacked(h plumbing.Hash) (obj plumbing.EncodedObject, err error) {
411 f, err := s.dir.Object(h)
412 if err != nil {
413 if os.IsNotExist(err) {
414 return nil, plumbing.ErrObjectNotFound
415 }
416
417 return nil, err
418 }
419 defer ioutil.CheckClose(f, &err)
420
421 if cacheObj, found := s.objectCache.Get(h); found {
422 return cacheObj, nil
423 }
424
425 r, err := objfile.NewReader(f)
426 if err != nil {
427 return nil, err
428 }
429
430 defer ioutil.CheckClose(r, &err)
431
432 t, size, err := r.Header()
433 if err != nil {
434 return nil, err
435 }
436
437 if s.options.LargeObjectThreshold > 0 && size > s.options.LargeObjectThreshold {
438 obj = dotgit.NewEncodedObject(s.dir, h, t, size)
439 return obj, nil
440 }
441
442 obj = s.NewEncodedObject()
443
444 obj.SetType(t)
445 obj.SetSize(size)
446 w, err := obj.Writer()
447 if err != nil {
448 return nil, err
449 }
450
451 defer ioutil.CheckClose(w, &err)
452
453 bufp := copyBufferPool.Get().(*[]byte)
454 buf := *bufp
455 _, err = io.CopyBuffer(w, r, buf)
456 copyBufferPool.Put(bufp)
457
458 s.objectCache.Put(obj)
459
460 return obj, err
461}
462
463var copyBufferPool = sync.Pool{
464 New: func() interface{} {
465 b := make([]byte, 32*1024)
466 return &b
467 },
468}
469
470// Get returns the object with the given hash, by searching for it in
471// the packfile.
472func (s *ObjectStorage) getFromPackfile(h plumbing.Hash, canBeDelta bool) (
473 plumbing.EncodedObject, error) {
474
475 if err := s.requireIndex(); err != nil {
476 return nil, err
477 }
478
479 pack, hash, offset := s.findObjectInPackfile(h)
480 if offset == -1 {
481 return nil, plumbing.ErrObjectNotFound
482 }
483
484 s.muI.RLock()
485 idx := s.index[pack]
486 s.muI.RUnlock()
487
488 p, err := s.packfile(idx, pack)
489 if err != nil {
490 return nil, err
491 }
492
493 if !s.options.KeepDescriptors && s.options.MaxOpenDescriptors == 0 {
494 defer ioutil.CheckClose(p, &err)
495 }
496
497 if canBeDelta {
498 return s.decodeDeltaObjectAt(p, offset, hash)
499 }
500
501 return p.GetByOffset(offset)
502}
503
504// TODO: refactor this logic into packfile package.
505func (s *ObjectStorage) decodeDeltaObjectAt(
506 p *packfile.Packfile,
507 offset int64,
508 hash plumbing.Hash,
509) (plumbing.EncodedObject, error) {
510 scan, err := p.Scanner()
511 if err != nil {
512 return nil, err
513 }
514 err = scan.SeekFromStart(offset)
515 if err != nil {
516 return nil, err
517 }
518
519 if !scan.Scan() {
520 return nil, fmt.Errorf("failed to decode delta object")
521 }
522
523 header := scan.Data().Value().(packfile.ObjectHeader)
524
525 var (
526 base plumbing.Hash
527 )
528
529 switch header.Type {
530 case plumbing.REFDeltaObject:
531 base = header.Reference
532 case plumbing.OFSDeltaObject:
533 base, err = p.FindHash(header.OffsetReference)
534 if err != nil {
535 return nil, err
536 }
537 default:
538 return p.GetByOffset(offset)
539 }
540
541 obj := &plumbing.MemoryObject{}
542 obj.SetType(header.Type)
543 w, err := obj.Writer()
544 if err != nil {
545 return nil, err
546 }
547
548 if err := scan.WriteObject(&header, w); err != nil {
549 return nil, err
550 }
551
552 return newDeltaObject(obj, hash, base, header.Size), nil
553}
554
555func (s *ObjectStorage) findObjectInPackfile(h plumbing.Hash) (plumbing.Hash, plumbing.Hash, int64) {
556 defer s.muI.Unlock()
557 s.muI.Lock()
558
559 for packfile, index := range s.index {
560 offset, err := index.FindOffset(h)
561 if err == nil {
562 return packfile, h, offset
563 }
564 }
565
566 return plumbing.ZeroHash, plumbing.ZeroHash, -1
567}
568
569// HashesWithPrefix returns all objects with a hash that starts with a prefix by searching for
570// them in the packfile and the git object directories.
571func (s *ObjectStorage) HashesWithPrefix(prefix []byte) ([]plumbing.Hash, error) {
572 hashes, err := s.dir.ObjectsWithPrefix(prefix)
573 if err != nil {
574 return nil, err
575 }
576
577 seen := hashListAsMap(hashes)
578
579 // TODO: This could be faster with some idxfile changes,
580 // or diving into the packfile.
581 if err := s.requireIndex(); err != nil {
582 return nil, err
583 }
584 for _, index := range s.index {
585 ei, err := index.Entries()
586 if err != nil {
587 return nil, err
588 }
589 for {
590 e, err := ei.Next()
591 if err == io.EOF {
592 break
593 } else if err != nil {
594 return nil, err
595 }
596 if bytes.HasPrefix(e.Hash[:], prefix) {
597 if _, ok := seen[e.Hash]; ok {
598 continue
599 }
600 hashes = append(hashes, e.Hash)
601 }
602 }
603 ei.Close()
604 }
605
606 return hashes, nil
607}
608
609// IterEncodedObjects returns an iterator for all the objects in the packfile
610// with the given type.
611func (s *ObjectStorage) IterEncodedObjects(t plumbing.ObjectType) (storer.EncodedObjectIter, error) {
612 objects, err := s.dir.Objects()
613 if err != nil {
614 return nil, err
615 }
616
617 seen := make(map[plumbing.Hash]struct{})
618 var iters []storer.EncodedObjectIter
619 if len(objects) != 0 {
620 iters = append(iters, &objectsIter{s: s, t: t, h: objects})
621 seen = hashListAsMap(objects)
622 }
623
624 packi, err := s.buildPackfileIters(t, seen)
625 if err != nil {
626 return nil, err
627 }
628
629 iters = append(iters, packi)
630 return storer.NewMultiEncodedObjectIter(iters), nil
631}
632
633func (s *ObjectStorage) buildPackfileIters(
634 t plumbing.ObjectType,
635 seen map[plumbing.Hash]struct{},
636) (storer.EncodedObjectIter, error) {
637 if err := s.requireIndex(); err != nil {
638 return nil, err
639 }
640
641 packs, err := s.dir.ObjectPacks()
642 if err != nil {
643 return nil, err
644 }
645 return &lazyPackfilesIter{
646 hashes: packs,
647 open: func(h plumbing.Hash) (storer.EncodedObjectIter, error) {
648 pack, err := s.dir.ObjectPack(h)
649 if err != nil {
650 return nil, err
651 }
652 return newPackfileIter(
653 s.dir.Fs(), pack, t, seen, s.index[h],
654 s.objectCache, s.options.KeepDescriptors,
655 )
656 },
657 }, nil
658}
659
660// Close closes all opened files.
661func (s *ObjectStorage) Close() error {
662 var firstError error
663
664 s.muP.RLock()
665 defer s.muP.RUnlock()
666
667 if s.options.KeepDescriptors || s.options.MaxOpenDescriptors > 0 {
668 for _, packfile := range s.packfiles {
669 err := packfile.Close()
670 if firstError == nil && err != nil {
671 firstError = err
672 }
673 }
674 }
675
676 s.packfiles = nil
677 s.dir.Close()
678
679 return firstError
680}
681
682func hashListAsMap(l []plumbing.Hash) map[plumbing.Hash]struct{} {
683 m := make(map[plumbing.Hash]struct{}, len(l))
684 for _, h := range l {
685 m[h] = struct{}{}
686 }
687 return m
688}
689
690func (s *ObjectStorage) ForEachObjectHash(fun func(plumbing.Hash) error) error {
691 err := s.dir.ForEachObjectHash(fun)
692 if err == storer.ErrStop {
693 return nil
694 }
695 return err
696}
697
698func (s *ObjectStorage) LooseObjectTime(hash plumbing.Hash) (time.Time, error) {
699 fi, err := s.dir.ObjectStat(hash)
700 if err != nil {
701 return time.Time{}, err
702 }
703 return fi.ModTime(), nil
704}
705
706func (s *ObjectStorage) DeleteLooseObject(hash plumbing.Hash) error {
707 return s.dir.ObjectDelete(hash)
708}
709
710func (s *ObjectStorage) ObjectPacks() ([]plumbing.Hash, error) {
711 return s.dir.ObjectPacks()
712}
713
714func (s *ObjectStorage) DeleteOldObjectPackAndIndex(h plumbing.Hash, t time.Time) error {
715 return s.dir.DeleteOldObjectPackAndIndex(h, t)
716}