| /* |
| * Copyright (C) 2011, 2013 Google Inc., and others. |
| * and other copyright owners as documented in the project's IP log. |
| * |
| * This program and the accompanying materials are made available |
| * under the terms of the Eclipse Distribution License v1.0 which |
| * accompanies this distribution, is reproduced below, and is |
| * available at http://www.eclipse.org/org/documents/edl-v10.php |
| * |
| * All rights reserved. |
| * |
| * Redistribution and use in source and binary forms, with or |
| * without modification, are permitted provided that the following |
| * conditions are met: |
| * |
| * - Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * |
| * - Redistributions in binary form must reproduce the above |
| * copyright notice, this list of conditions and the following |
| * disclaimer in the documentation and/or other materials provided |
| * with the distribution. |
| * |
| * - Neither the name of the Eclipse Foundation, Inc. nor the |
| * names of its contributors may be used to endorse or promote |
| * products derived from this software without specific prior |
| * written permission. |
| * |
| * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND |
| * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, |
| * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES |
| * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE |
| * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR |
| * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
| * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT |
| * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; |
| * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER |
| * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, |
| * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) |
| * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF |
| * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| */ |
| |
| 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 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 implements Comparable<DfsPackDescription> { |
| 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. |
| */ |
| public DfsPackDescription(DfsRepositoryDescription repoDesc, String name) { |
| this.repoDesc = repoDesc; |
| int dot = name.lastIndexOf('.'); |
| this.packName = (dot < 0) ? name : name.substring(0, dot); |
| |
| int extCnt = PackExt.values().length; |
| sizeMap = new long[extCnt]; |
| blockSizeMap = new int[extCnt]; |
| } |
| |
| /** @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(); |
| } |
| |
| /** |
| * @param ext |
| * the file extension |
| * @return whether the pack file extensions is known to exist. |
| */ |
| public boolean hasFileExt(PackExt ext) { |
| return (extensions & ext.getBit()) != 0; |
| } |
| |
| /** |
| * @param ext |
| * the file extension |
| * @return name of the file. |
| */ |
| public String getFileName(PackExt ext) { |
| return packName + '.' + ext.getExtension(); |
| } |
| |
| /** |
| * @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)); |
| } |
| |
| /** @return the source of the pack. */ |
| public PackSource getPackSource() { |
| return packSource; |
| } |
| |
| /** |
| * @param source |
| * the source of the pack. |
| * @return {@code this} |
| */ |
| public DfsPackDescription setPackSource(PackSource source) { |
| packSource = source; |
| return this; |
| } |
| |
| /** @return time the pack was created, in milliseconds. */ |
| public long getLastModified() { |
| return lastModified; |
| } |
| |
| /** |
| * @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; |
| } |
| |
| /** @return minUpdateIndex for the reftable, if present. */ |
| public long getMinUpdateIndex() { |
| return minUpdateIndex; |
| } |
| |
| /** |
| * @param min |
| * minUpdateIndex for the reftable, or 0. |
| * @return {@code this} |
| */ |
| public DfsPackDescription setMinUpdateIndex(long min) { |
| minUpdateIndex = min; |
| return this; |
| } |
| |
| /** @return maxUpdateIndex for the reftable, if present. */ |
| public long getMaxUpdateIndex() { |
| return maxUpdateIndex; |
| } |
| |
| /** |
| * @param max |
| * maxUpdateIndex for the reftable, or 0. |
| * @return {@code this} |
| */ |
| public DfsPackDescription setMaxUpdateIndex(long max) { |
| maxUpdateIndex = max; |
| return this; |
| } |
| |
| /** |
| * @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; |
| } |
| |
| /** |
| * @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; |
| } |
| |
| /** |
| * @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; |
| } |
| |
| /** |
| * @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; |
| } |
| |
| /** |
| * @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; |
| } |
| |
| /** |
| * @return estimated size of the .pack file in bytes. If 0 the pack file |
| * size is unknown. |
| */ |
| public long getEstimatedPackSize() { |
| return estimatedPackSize; |
| } |
| |
| /** @return number of objects in the pack. */ |
| public long getObjectCount() { |
| return objectCount; |
| } |
| |
| /** |
| * @param cnt |
| * number of objects in the pack. |
| * @return {@code this} |
| */ |
| public DfsPackDescription setObjectCount(long cnt) { |
| objectCount = Math.max(0, cnt); |
| return this; |
| } |
| |
| /** @return number of delta compressed objects in the pack. */ |
| public long getDeltaCount() { |
| return deltaCount; |
| } |
| |
| /** |
| * @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; |
| } |
| |
| /** |
| * @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; |
| } |
| |
| /** @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; |
| } |
| |
| /** @return the version of the index file written. */ |
| public int getIndexVersion() { |
| return indexVersion; |
| } |
| |
| /** |
| * @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; |
| } |
| |
| /** |
| * Sort packs according to the optimal lookup ordering. |
| * <p> |
| * This method 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 b |
| * the other pack. |
| */ |
| @Override |
| public int compareTo(DfsPackDescription b) { |
| // Cluster by PackSource, pushing UNREACHABLE_GARBAGE to the end. |
| PackSource as = getPackSource(); |
| PackSource bs = b.getPackSource(); |
| if (as != null && bs != null) { |
| int cmp = as.category - bs.category; |
| if (cmp != 0) |
| return cmp; |
| } |
| |
| // 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 != null && as == bs && isGC(as)) { |
| int cmp = Long.signum(getFileSize(PACK) - b.getFileSize(PACK)); |
| if (cmp != 0) { |
| return cmp; |
| } |
| } |
| |
| // Newer packs should sort first. |
| int cmp = Long.signum(b.getLastModified() - 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(getObjectCount() - b.getObjectCount()); |
| } |
| |
| static boolean isGC(PackSource s) { |
| switch (s) { |
| case GC: |
| case GC_REST: |
| case GC_TXN: |
| return true; |
| default: |
| return false; |
| } |
| } |
| |
| @Override |
| public String toString() { |
| return getFileName(PackExt.PACK); |
| } |
| } |