fork of go-git with some jj specific features
at main 16 kB view raw
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}