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