| /* |
| * Copyright (C) 2011, 2013 Google Inc., and others. and others |
| * |
| * This program and the accompanying materials are made available under the |
| * terms of the Eclipse Distribution License v. 1.0 which is available at |
| * https://www.eclipse.org/org/documents/edl-v10.php. |
| * |
| * SPDX-License-Identifier: BSD-3-Clause |
| */ |
| |
| package org.eclipse.jgit.internal.storage.dfs; |
| |
| import static org.eclipse.jgit.internal.storage.pack.PackExt.PACK; |
| import static org.eclipse.jgit.internal.storage.pack.PackExt.REFTABLE; |
| |
| import java.util.Arrays; |
| import java.util.Comparator; |
| |
| import org.eclipse.jgit.annotations.NonNull; |
| import org.eclipse.jgit.internal.storage.dfs.DfsObjDatabase.PackSource; |
| import org.eclipse.jgit.internal.storage.pack.PackExt; |
| import org.eclipse.jgit.internal.storage.reftable.ReftableWriter; |
| import org.eclipse.jgit.storage.pack.PackStatistics; |
| |
| /** |
| * Description of a DFS stored pack/index file. |
| * <p> |
| * Implementors may extend this class and add additional data members. |
| * <p> |
| * Instances of this class are cached with the DfsPackFile, and should not be |
| * modified once initialized and presented to the JGit DFS library. |
| */ |
| public class DfsPackDescription { |
| /** |
| * Comparator for packs when looking up objects in indexes. |
| * <p> |
| * This comparator tries to position packs in the order readers should examine |
| * them when looking for objects by SHA-1. The default tries to sort packs |
| * with more recent modification dates before older packs, and packs with |
| * fewer objects before packs with more objects. |
| * <p> |
| * Uses {@link PackSource#DEFAULT_COMPARATOR} for the portion of comparison |
| * where packs are sorted by source. |
| * |
| * @return comparator. |
| */ |
| public static Comparator<DfsPackDescription> objectLookupComparator() { |
| return objectLookupComparator(PackSource.DEFAULT_COMPARATOR); |
| } |
| |
| /** |
| * Comparator for packs when looking up objects in indexes. |
| * <p> |
| * This comparator tries to position packs in the order readers should examine |
| * them when looking for objects by SHA-1. The default tries to sort packs |
| * with more recent modification dates before older packs, and packs with |
| * fewer objects before packs with more objects. |
| * |
| * @param packSourceComparator |
| * comparator for the {@link PackSource}, used as the first step in |
| * comparison. |
| * @return comparator. |
| */ |
| public static Comparator<DfsPackDescription> objectLookupComparator( |
| Comparator<PackSource> packSourceComparator) { |
| return Comparator.comparing( |
| DfsPackDescription::getPackSource, packSourceComparator) |
| .thenComparing((a, b) -> { |
| PackSource as = a.getPackSource(); |
| PackSource bs = b.getPackSource(); |
| |
| // Tie break GC type packs by smallest first. There should be at most |
| // one of each source, but when multiple exist concurrent GCs may have |
| // run. Preferring the smaller file selects higher quality delta |
| // compression, placing less demand on the DfsBlockCache. |
| if (as == bs && isGC(as)) { |
| int cmp = Long.signum(a.getFileSize(PACK) - b.getFileSize(PACK)); |
| if (cmp != 0) { |
| return cmp; |
| } |
| } |
| |
| // Newer packs should sort first. |
| int cmp = Long.signum(b.getLastModified() - a.getLastModified()); |
| if (cmp != 0) { |
| return cmp; |
| } |
| |
| // Break ties on smaller index. Readers may get lucky and find |
| // the object they care about in the smaller index. This also pushes |
| // big historical packs to the end of the list, due to more objects. |
| return Long.signum(a.getObjectCount() - b.getObjectCount()); |
| }); |
| } |
| |
| static Comparator<DfsPackDescription> reftableComparator() { |
| return (a, b) -> { |
| // GC, COMPACT reftables first by reversing default order. |
| int c = PackSource.DEFAULT_COMPARATOR.reversed() |
| .compare(a.getPackSource(), b.getPackSource()); |
| if (c != 0) { |
| return c; |
| } |
| |
| // Lower maxUpdateIndex first. |
| c = Long.signum(a.getMaxUpdateIndex() - b.getMaxUpdateIndex()); |
| if (c != 0) { |
| return c; |
| } |
| |
| // Older reftable first. |
| return Long.signum(a.getLastModified() - b.getLastModified()); |
| }; |
| } |
| |
| static Comparator<DfsPackDescription> reuseComparator() { |
| return (a, b) -> { |
| PackSource as = a.getPackSource(); |
| PackSource bs = b.getPackSource(); |
| |
| if (as == bs && DfsPackDescription.isGC(as)) { |
| // Push smaller GC files last; these likely have higher quality |
| // delta compression and the contained representation should be |
| // favored over other files. |
| return Long.signum(b.getFileSize(PACK) - a.getFileSize(PACK)); |
| } |
| |
| // DfsPackDescription.compareTo already did a reasonable sort. |
| // Rely on Arrays.sort being stable, leaving equal elements. |
| return 0; |
| }; |
| } |
| |
| private final DfsRepositoryDescription repoDesc; |
| private final String packName; |
| private PackSource packSource; |
| private long lastModified; |
| private long[] sizeMap; |
| private int[] blockSizeMap; |
| private long objectCount; |
| private long deltaCount; |
| private long minUpdateIndex; |
| private long maxUpdateIndex; |
| |
| private PackStatistics packStats; |
| private ReftableWriter.Stats refStats; |
| private int extensions; |
| private int indexVersion; |
| private long estimatedPackSize; |
| |
| /** |
| * Initialize a description by pack name and repository. |
| * <p> |
| * The corresponding index file is assumed to exist. If this is not true |
| * implementors must extend the class and override |
| * {@link #getFileName(PackExt)}. |
| * <p> |
| * Callers should also try to fill in other fields if they are reasonably |
| * free to access at the time this instance is being initialized. |
| * |
| * @param name |
| * name of the pack file. Must end with ".pack". |
| * @param repoDesc |
| * description of the repo containing the pack file. |
| * @param packSource |
| * the source of the pack. |
| */ |
| public DfsPackDescription(DfsRepositoryDescription repoDesc, String name, |
| @NonNull PackSource packSource) { |
| this.repoDesc = repoDesc; |
| int dot = name.lastIndexOf('.'); |
| this.packName = (dot < 0) ? name : name.substring(0, dot); |
| this.packSource = packSource; |
| |
| int extCnt = PackExt.values().length; |
| sizeMap = new long[extCnt]; |
| blockSizeMap = new int[extCnt]; |
| } |
| |
| /** |
| * Get description of the repository. |
| * |
| * @return description of the repository. |
| */ |
| public DfsRepositoryDescription getRepositoryDescription() { |
| return repoDesc; |
| } |
| |
| /** |
| * Adds the pack file extension to the known list. |
| * |
| * @param ext |
| * the file extension |
| */ |
| public void addFileExt(PackExt ext) { |
| extensions |= ext.getBit(); |
| } |
| |
| /** |
| * Whether the pack file extension is known to exist. |
| * |
| * @param ext |
| * the file extension |
| * @return whether the pack file extension is known to exist. |
| */ |
| public boolean hasFileExt(PackExt ext) { |
| return (extensions & ext.getBit()) != 0; |
| } |
| |
| /** |
| * Get file name |
| * |
| * @param ext |
| * the file extension |
| * @return name of the file. |
| */ |
| public String getFileName(PackExt ext) { |
| return packName + '.' + ext.getExtension(); |
| } |
| |
| /** |
| * Get cache key for use by the block cache. |
| * |
| * @param ext |
| * the file extension. |
| * @return cache key for use by the block cache. |
| */ |
| public DfsStreamKey getStreamKey(PackExt ext) { |
| return DfsStreamKey.of(getRepositoryDescription(), getFileName(ext), |
| ext); |
| } |
| |
| /** |
| * Get the source of the pack. |
| * |
| * @return the source of the pack. |
| */ |
| @NonNull |
| public PackSource getPackSource() { |
| return packSource; |
| } |
| |
| /** |
| * Set the source of the pack. |
| * |
| * @param source |
| * the source of the pack. |
| * @return {@code this} |
| */ |
| public DfsPackDescription setPackSource(@NonNull PackSource source) { |
| packSource = source; |
| return this; |
| } |
| |
| /** |
| * Get time the pack was created, in milliseconds. |
| * |
| * @return time the pack was created, in milliseconds. |
| */ |
| public long getLastModified() { |
| return lastModified; |
| } |
| |
| /** |
| * Set time the pack was created, in milliseconds. |
| * |
| * @param timeMillis |
| * time the pack was created, in milliseconds. 0 if not known. |
| * @return {@code this} |
| */ |
| public DfsPackDescription setLastModified(long timeMillis) { |
| lastModified = timeMillis; |
| return this; |
| } |
| |
| /** |
| * Get minUpdateIndex for the reftable, if present. |
| * |
| * @return minUpdateIndex for the reftable, if present. |
| */ |
| public long getMinUpdateIndex() { |
| return minUpdateIndex; |
| } |
| |
| /** |
| * Set minUpdateIndex for the reftable. |
| * |
| * @param min |
| * minUpdateIndex for the reftable. |
| * @return {@code this} |
| */ |
| public DfsPackDescription setMinUpdateIndex(long min) { |
| minUpdateIndex = min; |
| return this; |
| } |
| |
| /** |
| * Get maxUpdateIndex for the reftable, if present. |
| * |
| * @return maxUpdateIndex for the reftable, if present. |
| */ |
| public long getMaxUpdateIndex() { |
| return maxUpdateIndex; |
| } |
| |
| /** |
| * Set maxUpdateIndex for the reftable. |
| * |
| * @param max |
| * maxUpdateIndex for the reftable. |
| * @return {@code this} |
| */ |
| public DfsPackDescription setMaxUpdateIndex(long max) { |
| maxUpdateIndex = max; |
| return this; |
| } |
| |
| /** |
| * Set size of the file in bytes. |
| * |
| * @param ext |
| * the file extension. |
| * @param bytes |
| * size of the file in bytes. If 0 the file is not known and will |
| * be determined on first read. |
| * @return {@code this} |
| */ |
| public DfsPackDescription setFileSize(PackExt ext, long bytes) { |
| int i = ext.getPosition(); |
| if (i >= sizeMap.length) { |
| sizeMap = Arrays.copyOf(sizeMap, i + 1); |
| } |
| sizeMap[i] = Math.max(0, bytes); |
| return this; |
| } |
| |
| /** |
| * Get size of the file, in bytes. |
| * |
| * @param ext |
| * the file extension. |
| * @return size of the file, in bytes. If 0 the file size is not yet known. |
| */ |
| public long getFileSize(PackExt ext) { |
| int i = ext.getPosition(); |
| return i < sizeMap.length ? sizeMap[i] : 0; |
| } |
| |
| /** |
| * Get blockSize of the file, in bytes. |
| * |
| * @param ext |
| * the file extension. |
| * @return blockSize of the file, in bytes. If 0 the blockSize size is not |
| * yet known and may be discovered when opening the file. |
| */ |
| public int getBlockSize(PackExt ext) { |
| int i = ext.getPosition(); |
| return i < blockSizeMap.length ? blockSizeMap[i] : 0; |
| } |
| |
| /** |
| * Set blockSize of the file, in bytes. |
| * |
| * @param ext |
| * the file extension. |
| * @param blockSize |
| * blockSize of the file, in bytes. If 0 the blockSize is not |
| * known and will be determined on first read. |
| * @return {@code this} |
| */ |
| public DfsPackDescription setBlockSize(PackExt ext, int blockSize) { |
| int i = ext.getPosition(); |
| if (i >= blockSizeMap.length) { |
| blockSizeMap = Arrays.copyOf(blockSizeMap, i + 1); |
| } |
| blockSizeMap[i] = Math.max(0, blockSize); |
| return this; |
| } |
| |
| /** |
| * Set estimated size of the .pack file in bytes. |
| * |
| * @param estimatedPackSize |
| * estimated size of the .pack file in bytes. If 0 the pack file |
| * size is unknown. |
| * @return {@code this} |
| */ |
| public DfsPackDescription setEstimatedPackSize(long estimatedPackSize) { |
| this.estimatedPackSize = Math.max(0, estimatedPackSize); |
| return this; |
| } |
| |
| /** |
| * Get estimated size of the .pack file in bytes. |
| * |
| * @return estimated size of the .pack file in bytes. If 0 the pack file |
| * size is unknown. |
| */ |
| public long getEstimatedPackSize() { |
| return estimatedPackSize; |
| } |
| |
| /** |
| * Get number of objects in the pack. |
| * |
| * @return number of objects in the pack. |
| */ |
| public long getObjectCount() { |
| return objectCount; |
| } |
| |
| /** |
| * Set number of objects in the pack. |
| * |
| * @param cnt |
| * number of objects in the pack. |
| * @return {@code this} |
| */ |
| public DfsPackDescription setObjectCount(long cnt) { |
| objectCount = Math.max(0, cnt); |
| return this; |
| } |
| |
| /** |
| * Get number of delta compressed objects in the pack. |
| * |
| * @return number of delta compressed objects in the pack. |
| */ |
| public long getDeltaCount() { |
| return deltaCount; |
| } |
| |
| /** |
| * Set number of delta compressed objects in the pack. |
| * |
| * @param cnt |
| * number of delta compressed objects in the pack. |
| * @return {@code this} |
| */ |
| public DfsPackDescription setDeltaCount(long cnt) { |
| deltaCount = Math.max(0, cnt); |
| return this; |
| } |
| |
| /** |
| * Get statistics from PackWriter, if the pack was built with it. |
| * |
| * @return statistics from PackWriter, if the pack was built with it. |
| * Generally this is only available for packs created by |
| * DfsGarbageCollector or DfsPackCompactor, and only when the pack |
| * is being committed to the repository. |
| */ |
| public PackStatistics getPackStats() { |
| return packStats; |
| } |
| |
| DfsPackDescription setPackStats(PackStatistics stats) { |
| this.packStats = stats; |
| setFileSize(PACK, stats.getTotalBytes()); |
| setObjectCount(stats.getTotalObjects()); |
| setDeltaCount(stats.getTotalDeltas()); |
| return this; |
| } |
| |
| /** |
| * Get stats from the sibling reftable, if created. |
| * |
| * @return stats from the sibling reftable, if created. |
| */ |
| public ReftableWriter.Stats getReftableStats() { |
| return refStats; |
| } |
| |
| void setReftableStats(ReftableWriter.Stats stats) { |
| this.refStats = stats; |
| setMinUpdateIndex(stats.minUpdateIndex()); |
| setMaxUpdateIndex(stats.maxUpdateIndex()); |
| setFileSize(REFTABLE, stats.totalBytes()); |
| setBlockSize(REFTABLE, stats.refBlockSize()); |
| } |
| |
| /** |
| * Discard the pack statistics, if it was populated. |
| * |
| * @return {@code this} |
| */ |
| public DfsPackDescription clearPackStats() { |
| packStats = null; |
| refStats = null; |
| return this; |
| } |
| |
| /** |
| * Get the version of the index file written. |
| * |
| * @return the version of the index file written. |
| */ |
| public int getIndexVersion() { |
| return indexVersion; |
| } |
| |
| /** |
| * Set the version of the index file written. |
| * |
| * @param version |
| * the version of the index file written. |
| * @return {@code this} |
| */ |
| public DfsPackDescription setIndexVersion(int version) { |
| indexVersion = version; |
| return this; |
| } |
| |
| /** {@inheritDoc} */ |
| @Override |
| public int hashCode() { |
| return packName.hashCode(); |
| } |
| |
| /** {@inheritDoc} */ |
| @Override |
| public boolean equals(Object b) { |
| if (b instanceof DfsPackDescription) { |
| DfsPackDescription desc = (DfsPackDescription) b; |
| return packName.equals(desc.packName) && |
| getRepositoryDescription().equals(desc.getRepositoryDescription()); |
| } |
| return false; |
| } |
| |
| static boolean isGC(PackSource s) { |
| switch (s) { |
| case GC: |
| case GC_REST: |
| return true; |
| default: |
| return false; |
| } |
| } |
| |
| /** {@inheritDoc} */ |
| @Override |
| public String toString() { |
| return getFileName(PackExt.PACK); |
| } |
| } |