blob: 663190a2333828f939a09c586486d4e8ebd06c71 [file] [log] [blame]
/*
* 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.commitgraph.CommitGraphWriter;
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 CommitGraphWriter.Stats commitGraphStats;
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());
}
/**
* Get stats from the sibling commit graph, if created.
*
* @return stats from the sibling commit graph, if created.
*/
public CommitGraphWriter.Stats getCommitGraphStats() {
return commitGraphStats;
}
void setCommitGraphStats(CommitGraphWriter.Stats stats) {
this.commitGraphStats = stats;
}
/**
* 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;
}
@Override
public int hashCode() {
return packName.hashCode();
}
@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;
}
}
@Override
public String toString() {
return getFileName(PackExt.PACK);
}
}