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.

DfsObjDatabase.java 22KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736
  1. /*
  2. * Copyright (C) 2011, Google Inc. and others
  3. *
  4. * This program and the accompanying materials are made available under the
  5. * terms of the Eclipse Distribution License v. 1.0 which is available at
  6. * https://www.eclipse.org/org/documents/edl-v10.php.
  7. *
  8. * SPDX-License-Identifier: BSD-3-Clause
  9. */
  10. package org.eclipse.jgit.internal.storage.dfs;
  11. import static java.util.stream.Collectors.joining;
  12. import java.io.FileNotFoundException;
  13. import java.io.IOException;
  14. import java.util.ArrayList;
  15. import java.util.Arrays;
  16. import java.util.Collection;
  17. import java.util.Collections;
  18. import java.util.Comparator;
  19. import java.util.HashMap;
  20. import java.util.HashSet;
  21. import java.util.List;
  22. import java.util.Map;
  23. import java.util.Set;
  24. import java.util.concurrent.atomic.AtomicReference;
  25. import org.eclipse.jgit.internal.storage.pack.PackExt;
  26. import org.eclipse.jgit.lib.AnyObjectId;
  27. import org.eclipse.jgit.lib.ObjectDatabase;
  28. import org.eclipse.jgit.lib.ObjectInserter;
  29. import org.eclipse.jgit.lib.ObjectReader;
  30. /**
  31. * Manages objects stored in
  32. * {@link org.eclipse.jgit.internal.storage.dfs.DfsPackFile} on a storage
  33. * system.
  34. */
  35. public abstract class DfsObjDatabase extends ObjectDatabase {
  36. private static final PackList NO_PACKS = new PackList(
  37. new DfsPackFile[0],
  38. new DfsReftable[0]) {
  39. @Override
  40. boolean dirty() {
  41. return true;
  42. }
  43. @Override
  44. void clearDirty() {
  45. // Always dirty.
  46. }
  47. @Override
  48. public void markDirty() {
  49. // Always dirty.
  50. }
  51. };
  52. /**
  53. * Sources for a pack file.
  54. * <p>
  55. * <strong>Note:</strong> When sorting packs by source, do not use the default
  56. * comparator based on {@link Enum#compareTo}. Prefer {@link
  57. * #DEFAULT_COMPARATOR} or your own {@link ComparatorBuilder}.
  58. */
  59. public enum PackSource {
  60. /** The pack is created by ObjectInserter due to local activity. */
  61. INSERT,
  62. /**
  63. * The pack is created by PackParser due to a network event.
  64. * <p>
  65. * A received pack can be from either a push into the repository, or a
  66. * fetch into the repository, the direction doesn't matter. A received
  67. * pack was built by the remote Git implementation and may not match the
  68. * storage layout preferred by this version. Received packs are likely
  69. * to be either compacted or garbage collected in the future.
  70. */
  71. RECEIVE,
  72. /**
  73. * The pack was created by compacting multiple packs together.
  74. * <p>
  75. * Packs created by compacting multiple packs together aren't nearly as
  76. * efficient as a fully garbage collected repository, but may save disk
  77. * space by reducing redundant copies of base objects.
  78. *
  79. * @see DfsPackCompactor
  80. */
  81. COMPACT,
  82. /**
  83. * Pack was created by Git garbage collection by this implementation.
  84. * <p>
  85. * This source is only used by the {@link DfsGarbageCollector} when it
  86. * builds a pack file by traversing the object graph and copying all
  87. * reachable objects into a new pack stream.
  88. *
  89. * @see DfsGarbageCollector
  90. */
  91. GC,
  92. /** Created from non-heads by {@link DfsGarbageCollector}. */
  93. GC_REST,
  94. /**
  95. * RefTreeGraph pack was created by Git garbage collection.
  96. *
  97. * @see DfsGarbageCollector
  98. */
  99. GC_TXN,
  100. /**
  101. * Pack was created by Git garbage collection.
  102. * <p>
  103. * This pack contains only unreachable garbage that was found during the
  104. * last GC pass. It is retained in a new pack until it is safe to prune
  105. * these objects from the repository.
  106. */
  107. UNREACHABLE_GARBAGE;
  108. /**
  109. * Default comparator for sources.
  110. * <p>
  111. * Sorts generally newer, smaller types such as {@code INSERT} and {@code
  112. * RECEIVE} earlier; older, larger types such as {@code GC} later; and
  113. * {@code UNREACHABLE_GARBAGE} at the end.
  114. */
  115. public static final Comparator<PackSource> DEFAULT_COMPARATOR =
  116. new ComparatorBuilder()
  117. .add(INSERT, RECEIVE)
  118. .add(COMPACT)
  119. .add(GC)
  120. .add(GC_REST)
  121. .add(GC_TXN)
  122. .add(UNREACHABLE_GARBAGE)
  123. .build();
  124. /**
  125. * Builder for describing {@link PackSource} ordering where some values are
  126. * explicitly considered equal to others.
  127. */
  128. public static class ComparatorBuilder {
  129. private final Map<PackSource, Integer> ranks = new HashMap<>();
  130. private int counter;
  131. /**
  132. * Add a collection of sources that should sort as equal.
  133. * <p>
  134. * Sources in the input will sort after sources listed in previous calls
  135. * to this method.
  136. *
  137. * @param sources
  138. * sources in this equivalence class.
  139. * @return this.
  140. */
  141. public ComparatorBuilder add(PackSource... sources) {
  142. for (PackSource s : sources) {
  143. ranks.put(s, Integer.valueOf(counter));
  144. }
  145. counter++;
  146. return this;
  147. }
  148. /**
  149. * Build the comparator.
  150. *
  151. * @return new comparator instance.
  152. * @throws IllegalArgumentException
  153. * not all {@link PackSource} instances were explicitly assigned
  154. * an equivalence class.
  155. */
  156. public Comparator<PackSource> build() {
  157. return new PackSourceComparator(ranks);
  158. }
  159. }
  160. private static class PackSourceComparator implements Comparator<PackSource> {
  161. private final Map<PackSource, Integer> ranks;
  162. private PackSourceComparator(Map<PackSource, Integer> ranks) {
  163. if (!ranks.keySet().equals(
  164. new HashSet<>(Arrays.asList(PackSource.values())))) {
  165. throw new IllegalArgumentException();
  166. }
  167. this.ranks = new HashMap<>(ranks);
  168. }
  169. @Override
  170. public int compare(PackSource a, PackSource b) {
  171. return ranks.get(a).compareTo(ranks.get(b));
  172. }
  173. @Override
  174. public String toString() {
  175. return Arrays.stream(PackSource.values())
  176. .map(s -> s + "=" + ranks.get(s)) //$NON-NLS-1$
  177. .collect(joining(", ", getClass().getSimpleName() + "{", "}")); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
  178. }
  179. }
  180. }
  181. private final AtomicReference<PackList> packList;
  182. private final DfsRepository repository;
  183. private DfsReaderOptions readerOptions;
  184. private Comparator<DfsPackDescription> packComparator;
  185. /**
  186. * Initialize an object database for our repository.
  187. *
  188. * @param repository
  189. * repository owning this object database.
  190. * @param options
  191. * how readers should access the object database.
  192. */
  193. protected DfsObjDatabase(DfsRepository repository,
  194. DfsReaderOptions options) {
  195. this.repository = repository;
  196. this.packList = new AtomicReference<>(NO_PACKS);
  197. this.readerOptions = options;
  198. this.packComparator = DfsPackDescription.objectLookupComparator();
  199. }
  200. /**
  201. * Get configured reader options, such as read-ahead.
  202. *
  203. * @return configured reader options, such as read-ahead.
  204. */
  205. public DfsReaderOptions getReaderOptions() {
  206. return readerOptions;
  207. }
  208. /**
  209. * Set the comparator used when searching for objects across packs.
  210. * <p>
  211. * An optimal comparator will find more objects without having to load large
  212. * idx files from storage only to find that they don't contain the object.
  213. * See {@link DfsPackDescription#objectLookupComparator()} for the default
  214. * heuristics.
  215. *
  216. * @param packComparator
  217. * comparator.
  218. */
  219. public void setPackComparator(Comparator<DfsPackDescription> packComparator) {
  220. this.packComparator = packComparator;
  221. }
  222. /** {@inheritDoc} */
  223. @Override
  224. public DfsReader newReader() {
  225. return new DfsReader(this);
  226. }
  227. /** {@inheritDoc} */
  228. @Override
  229. public ObjectInserter newInserter() {
  230. return new DfsInserter(this);
  231. }
  232. /**
  233. * Scan and list all available pack files in the repository.
  234. *
  235. * @return list of available packs. The returned array is shared with the
  236. * implementation and must not be modified by the caller.
  237. * @throws java.io.IOException
  238. * the pack list cannot be initialized.
  239. */
  240. public DfsPackFile[] getPacks() throws IOException {
  241. return getPackList().packs;
  242. }
  243. /**
  244. * Scan and list all available reftable files in the repository.
  245. *
  246. * @return list of available reftables. The returned array is shared with
  247. * the implementation and must not be modified by the caller.
  248. * @throws java.io.IOException
  249. * the pack list cannot be initialized.
  250. */
  251. public DfsReftable[] getReftables() throws IOException {
  252. return getPackList().reftables;
  253. }
  254. /**
  255. * Scan and list all available pack files in the repository.
  256. *
  257. * @return list of available packs, with some additional metadata. The
  258. * returned array is shared with the implementation and must not be
  259. * modified by the caller.
  260. * @throws java.io.IOException
  261. * the pack list cannot be initialized.
  262. */
  263. public PackList getPackList() throws IOException {
  264. return scanPacks(NO_PACKS);
  265. }
  266. /**
  267. * Get repository owning this object database.
  268. *
  269. * @return repository owning this object database.
  270. */
  271. protected DfsRepository getRepository() {
  272. return repository;
  273. }
  274. /**
  275. * List currently known pack files in the repository, without scanning.
  276. *
  277. * @return list of available packs. The returned array is shared with the
  278. * implementation and must not be modified by the caller.
  279. */
  280. public DfsPackFile[] getCurrentPacks() {
  281. return getCurrentPackList().packs;
  282. }
  283. /**
  284. * List currently known reftable files in the repository, without scanning.
  285. *
  286. * @return list of available reftables. The returned array is shared with
  287. * the implementation and must not be modified by the caller.
  288. */
  289. public DfsReftable[] getCurrentReftables() {
  290. return getCurrentPackList().reftables;
  291. }
  292. /**
  293. * List currently known pack files in the repository, without scanning.
  294. *
  295. * @return list of available packs, with some additional metadata. The
  296. * returned array is shared with the implementation and must not be
  297. * modified by the caller.
  298. */
  299. public PackList getCurrentPackList() {
  300. return packList.get();
  301. }
  302. /**
  303. * Does the requested object exist in this database?
  304. * <p>
  305. * This differs from ObjectDatabase's implementation in that we can selectively
  306. * ignore unreachable (garbage) objects.
  307. *
  308. * @param objectId
  309. * identity of the object to test for existence of.
  310. * @param avoidUnreachableObjects
  311. * if true, ignore objects that are unreachable.
  312. * @return true if the specified object is stored in this database.
  313. * @throws java.io.IOException
  314. * the object store cannot be accessed.
  315. */
  316. public boolean has(AnyObjectId objectId, boolean avoidUnreachableObjects)
  317. throws IOException {
  318. try (ObjectReader or = newReader()) {
  319. or.setAvoidUnreachableObjects(avoidUnreachableObjects);
  320. return or.has(objectId);
  321. }
  322. }
  323. /**
  324. * Generate a new unique name for a pack file.
  325. *
  326. * @param source
  327. * where the pack stream is created.
  328. * @return a unique name for the pack file. Must not collide with any other
  329. * pack file name in the same DFS.
  330. * @throws java.io.IOException
  331. * a new unique pack description cannot be generated.
  332. */
  333. protected abstract DfsPackDescription newPack(PackSource source)
  334. throws IOException;
  335. /**
  336. * Generate a new unique name for a pack file.
  337. *
  338. * <p>
  339. * Default implementation of this method would be equivalent to
  340. * {@code newPack(source).setEstimatedPackSize(estimatedPackSize)}. But the
  341. * clients can override this method to use the given
  342. * {@code estomatedPackSize} value more efficiently in the process of
  343. * creating a new
  344. * {@link org.eclipse.jgit.internal.storage.dfs.DfsPackDescription} object.
  345. *
  346. * @param source
  347. * where the pack stream is created.
  348. * @param estimatedPackSize
  349. * the estimated size of the pack.
  350. * @return a unique name for the pack file. Must not collide with any other
  351. * pack file name in the same DFS.
  352. * @throws java.io.IOException
  353. * a new unique pack description cannot be generated.
  354. */
  355. protected DfsPackDescription newPack(PackSource source,
  356. long estimatedPackSize) throws IOException {
  357. DfsPackDescription pack = newPack(source);
  358. pack.setEstimatedPackSize(estimatedPackSize);
  359. return pack;
  360. }
  361. /**
  362. * Commit a pack and index pair that was written to the DFS.
  363. * <p>
  364. * Committing the pack/index pair makes them visible to readers. The JGit
  365. * DFS code always writes the pack, then the index. This allows a simple
  366. * commit process to do nothing if readers always look for both files to
  367. * exist and the DFS performs atomic creation of the file (e.g. stream to a
  368. * temporary file and rename to target on close).
  369. * <p>
  370. * During pack compaction or GC the new pack file may be replacing other
  371. * older files. Implementations should remove those older files (if any) as
  372. * part of the commit of the new file.
  373. * <p>
  374. * This method is a trivial wrapper around
  375. * {@link #commitPackImpl(Collection, Collection)} that calls the
  376. * implementation and fires events.
  377. *
  378. * @param desc
  379. * description of the new packs.
  380. * @param replaces
  381. * if not null, list of packs to remove.
  382. * @throws java.io.IOException
  383. * the packs cannot be committed. On failure a rollback must
  384. * also be attempted by the caller.
  385. */
  386. protected void commitPack(Collection<DfsPackDescription> desc,
  387. Collection<DfsPackDescription> replaces) throws IOException {
  388. commitPackImpl(desc, replaces);
  389. getRepository().fireEvent(new DfsPacksChangedEvent());
  390. }
  391. /**
  392. * Implementation of pack commit.
  393. *
  394. * @see #commitPack(Collection, Collection)
  395. * @param desc
  396. * description of the new packs.
  397. * @param replaces
  398. * if not null, list of packs to remove.
  399. * @throws java.io.IOException
  400. * the packs cannot be committed.
  401. */
  402. protected abstract void commitPackImpl(Collection<DfsPackDescription> desc,
  403. Collection<DfsPackDescription> replaces) throws IOException;
  404. /**
  405. * Try to rollback a pack creation.
  406. * <p>
  407. * JGit DFS always writes the pack first, then the index. If the pack does
  408. * not yet exist, then neither does the index. A safe DFS implementation
  409. * would try to remove both files to ensure they are really gone.
  410. * <p>
  411. * A rollback does not support failures, as it only occurs when there is
  412. * already a failure in progress. A DFS implementor may wish to log
  413. * warnings/error messages when a rollback fails, but should not send new
  414. * exceptions up the Java callstack.
  415. *
  416. * @param desc
  417. * pack to delete.
  418. */
  419. protected abstract void rollbackPack(Collection<DfsPackDescription> desc);
  420. /**
  421. * List the available pack files.
  422. * <p>
  423. * The returned list must support random access and must be mutable by the
  424. * caller. It is sorted in place using the natural sorting of the returned
  425. * DfsPackDescription objects.
  426. *
  427. * @return available packs. May be empty if there are no packs.
  428. * @throws java.io.IOException
  429. * the packs cannot be listed and the object database is not
  430. * functional to the caller.
  431. */
  432. protected abstract List<DfsPackDescription> listPacks() throws IOException;
  433. /**
  434. * Open a pack, pack index, or other related file for reading.
  435. *
  436. * @param desc
  437. * description of pack related to the data that will be read.
  438. * This is an instance previously obtained from
  439. * {@link #listPacks()}, but not necessarily from the same
  440. * DfsObjDatabase instance.
  441. * @param ext
  442. * file extension that will be read i.e "pack" or "idx".
  443. * @return channel to read the file.
  444. * @throws java.io.FileNotFoundException
  445. * the file does not exist.
  446. * @throws java.io.IOException
  447. * the file cannot be opened.
  448. */
  449. protected abstract ReadableChannel openFile(
  450. DfsPackDescription desc, PackExt ext)
  451. throws FileNotFoundException, IOException;
  452. /**
  453. * Open a pack, pack index, or other related file for writing.
  454. *
  455. * @param desc
  456. * description of pack related to the data that will be written.
  457. * This is an instance previously obtained from
  458. * {@link #newPack(PackSource)}.
  459. * @param ext
  460. * file extension that will be written i.e "pack" or "idx".
  461. * @return channel to write the file.
  462. * @throws java.io.IOException
  463. * the file cannot be opened.
  464. */
  465. protected abstract DfsOutputStream writeFile(
  466. DfsPackDescription desc, PackExt ext) throws IOException;
  467. void addPack(DfsPackFile newPack) throws IOException {
  468. PackList o, n;
  469. do {
  470. o = packList.get();
  471. if (o == NO_PACKS) {
  472. // The repository may not have needed any existing objects to
  473. // complete the current task of creating a pack (e.g. push of a
  474. // pack with no external deltas). Because we don't scan for
  475. // newly added packs on missed object lookups, scan now to
  476. // make sure all older packs are available in the packList.
  477. o = scanPacks(o);
  478. // Its possible the scan identified the pack we were asked to
  479. // add, as the pack was already committed via commitPack().
  480. // If this is the case return without changing the list.
  481. for (DfsPackFile p : o.packs) {
  482. if (p.key.equals(newPack.key)) {
  483. return;
  484. }
  485. }
  486. }
  487. DfsPackFile[] packs = new DfsPackFile[1 + o.packs.length];
  488. packs[0] = newPack;
  489. System.arraycopy(o.packs, 0, packs, 1, o.packs.length);
  490. n = new PackListImpl(packs, o.reftables);
  491. } while (!packList.compareAndSet(o, n));
  492. }
  493. void addReftable(DfsPackDescription add, Set<DfsPackDescription> remove)
  494. throws IOException {
  495. PackList o, n;
  496. do {
  497. o = packList.get();
  498. if (o == NO_PACKS) {
  499. o = scanPacks(o);
  500. for (DfsReftable t : o.reftables) {
  501. if (t.getPackDescription().equals(add)) {
  502. return;
  503. }
  504. }
  505. }
  506. List<DfsReftable> tables = new ArrayList<>(1 + o.reftables.length);
  507. for (DfsReftable t : o.reftables) {
  508. if (!remove.contains(t.getPackDescription())) {
  509. tables.add(t);
  510. }
  511. }
  512. tables.add(new DfsReftable(add));
  513. n = new PackListImpl(o.packs, tables.toArray(new DfsReftable[0]));
  514. } while (!packList.compareAndSet(o, n));
  515. }
  516. PackList scanPacks(PackList original) throws IOException {
  517. PackList o, n;
  518. synchronized (packList) {
  519. do {
  520. o = packList.get();
  521. if (o != original) {
  522. // Another thread did the scan for us, while we
  523. // were blocked on the monitor above.
  524. //
  525. return o;
  526. }
  527. n = scanPacksImpl(o);
  528. if (n == o)
  529. return n;
  530. } while (!packList.compareAndSet(o, n));
  531. }
  532. getRepository().fireEvent(new DfsPacksChangedEvent());
  533. return n;
  534. }
  535. private PackList scanPacksImpl(PackList old) throws IOException {
  536. DfsBlockCache cache = DfsBlockCache.getInstance();
  537. Map<DfsPackDescription, DfsPackFile> packs = packMap(old);
  538. Map<DfsPackDescription, DfsReftable> reftables = reftableMap(old);
  539. List<DfsPackDescription> scanned = listPacks();
  540. Collections.sort(scanned, packComparator);
  541. List<DfsPackFile> newPacks = new ArrayList<>(scanned.size());
  542. List<DfsReftable> newReftables = new ArrayList<>(scanned.size());
  543. boolean foundNew = false;
  544. for (DfsPackDescription dsc : scanned) {
  545. DfsPackFile oldPack = packs.remove(dsc);
  546. if (oldPack != null) {
  547. newPacks.add(oldPack);
  548. } else if (dsc.hasFileExt(PackExt.PACK)) {
  549. newPacks.add(new DfsPackFile(cache, dsc));
  550. foundNew = true;
  551. }
  552. DfsReftable oldReftable = reftables.remove(dsc);
  553. if (oldReftable != null) {
  554. newReftables.add(oldReftable);
  555. } else if (dsc.hasFileExt(PackExt.REFTABLE)) {
  556. newReftables.add(new DfsReftable(cache, dsc));
  557. foundNew = true;
  558. }
  559. }
  560. if (newPacks.isEmpty() && newReftables.isEmpty())
  561. return new PackListImpl(NO_PACKS.packs, NO_PACKS.reftables);
  562. if (!foundNew) {
  563. old.clearDirty();
  564. return old;
  565. }
  566. Collections.sort(newReftables, reftableComparator());
  567. return new PackListImpl(
  568. newPacks.toArray(new DfsPackFile[0]),
  569. newReftables.toArray(new DfsReftable[0]));
  570. }
  571. private static Map<DfsPackDescription, DfsPackFile> packMap(PackList old) {
  572. Map<DfsPackDescription, DfsPackFile> forReuse = new HashMap<>();
  573. for (DfsPackFile p : old.packs) {
  574. if (!p.invalid()) {
  575. forReuse.put(p.desc, p);
  576. }
  577. }
  578. return forReuse;
  579. }
  580. private static Map<DfsPackDescription, DfsReftable> reftableMap(PackList old) {
  581. Map<DfsPackDescription, DfsReftable> forReuse = new HashMap<>();
  582. for (DfsReftable p : old.reftables) {
  583. if (!p.invalid()) {
  584. forReuse.put(p.desc, p);
  585. }
  586. }
  587. return forReuse;
  588. }
  589. /**
  590. * Get comparator to sort {@link DfsReftable} by priority.
  591. *
  592. * @return comparator to sort {@link DfsReftable} by priority.
  593. */
  594. protected Comparator<DfsReftable> reftableComparator() {
  595. return Comparator.comparing(
  596. DfsReftable::getPackDescription,
  597. DfsPackDescription.reftableComparator());
  598. }
  599. /**
  600. * Clears the cached list of packs, forcing them to be scanned again.
  601. */
  602. protected void clearCache() {
  603. packList.set(NO_PACKS);
  604. }
  605. /** {@inheritDoc} */
  606. @Override
  607. public void close() {
  608. packList.set(NO_PACKS);
  609. }
  610. /** Snapshot of packs scanned in a single pass. */
  611. public abstract static class PackList {
  612. /** All known packs, sorted. */
  613. public final DfsPackFile[] packs;
  614. /** All known reftables, sorted. */
  615. public final DfsReftable[] reftables;
  616. private long lastModified = -1;
  617. PackList(DfsPackFile[] packs, DfsReftable[] reftables) {
  618. this.packs = packs;
  619. this.reftables = reftables;
  620. }
  621. /** @return last modified time of all packs, in milliseconds. */
  622. public long getLastModified() {
  623. if (lastModified < 0) {
  624. long max = 0;
  625. for (DfsPackFile pack : packs) {
  626. max = Math.max(max, pack.getPackDescription().getLastModified());
  627. }
  628. lastModified = max;
  629. }
  630. return lastModified;
  631. }
  632. abstract boolean dirty();
  633. abstract void clearDirty();
  634. /**
  635. * Mark pack list as dirty.
  636. * <p>
  637. * Used when the caller knows that new data might have been written to the
  638. * repository that could invalidate open readers depending on this pack list,
  639. * for example if refs are newly scanned.
  640. */
  641. public abstract void markDirty();
  642. }
  643. private static final class PackListImpl extends PackList {
  644. private volatile boolean dirty;
  645. PackListImpl(DfsPackFile[] packs, DfsReftable[] reftables) {
  646. super(packs, reftables);
  647. }
  648. @Override
  649. boolean dirty() {
  650. return dirty;
  651. }
  652. @Override
  653. void clearDirty() {
  654. dirty = false;
  655. }
  656. @Override
  657. public void markDirty() {
  658. dirty = true;
  659. }
  660. }
  661. }