本站源代码
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

816 lines
17KB

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