/* * Copyright (C) 2011, Google Inc. 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 java.util.stream.Collectors.joining; import static org.eclipse.jgit.internal.storage.pack.PackExt.BITMAP_INDEX; import static org.eclipse.jgit.internal.storage.pack.PackExt.INDEX; import java.io.FileNotFoundException; import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.atomic.AtomicReference; import org.eclipse.jgit.internal.storage.file.BasePackIndexWriter; import org.eclipse.jgit.internal.storage.file.PackBitmapIndexWriterV1; import org.eclipse.jgit.internal.storage.pack.PackIndexWriter; import org.eclipse.jgit.internal.storage.pack.PackBitmapIndexWriter; import org.eclipse.jgit.internal.storage.pack.PackExt; import org.eclipse.jgit.lib.AnyObjectId; import org.eclipse.jgit.lib.ObjectDatabase; import org.eclipse.jgit.lib.ObjectInserter; import org.eclipse.jgit.lib.ObjectReader; import org.eclipse.jgit.util.io.CountingOutputStream; /** * Manages objects stored in * {@link org.eclipse.jgit.internal.storage.dfs.DfsPackFile} on a storage * system. */ public abstract class DfsObjDatabase extends ObjectDatabase { private static final PackList NO_PACKS = new PackList( new DfsPackFile[0], new DfsReftable[0]) { @Override boolean dirty() { return true; } @Override void clearDirty() { // Always dirty. } @Override public void markDirty() { // Always dirty. } }; /** * Sources for a pack file. *
* Note: When sorting packs by source, do not use the default * comparator based on {@link Enum#compareTo}. Prefer {@link * #DEFAULT_COMPARATOR} or your own {@link ComparatorBuilder}. */ public enum PackSource { /** The pack is created by ObjectInserter due to local activity. */ INSERT, /** * The pack is created by PackParser due to a network event. *
* A received pack can be from either a push into the repository, or a * fetch into the repository, the direction doesn't matter. A received * pack was built by the remote Git implementation and may not match the * storage layout preferred by this version. Received packs are likely * to be either compacted or garbage collected in the future. */ RECEIVE, /** * The pack was created by compacting multiple packs together. *
* Packs created by compacting multiple packs together aren't nearly as * efficient as a fully garbage collected repository, but may save disk * space by reducing redundant copies of base objects. * * @see DfsPackCompactor */ COMPACT, /** * Pack was created by Git garbage collection by this implementation. *
* This source is only used by the {@link DfsGarbageCollector} when it * builds a pack file by traversing the object graph and copying all * reachable objects into a new pack stream. * * @see DfsGarbageCollector */ GC, /** Created from non-heads by {@link DfsGarbageCollector}. */ GC_REST, /** * Pack was created by Git garbage collection. *
* This pack contains only unreachable garbage that was found during the * last GC pass. It is retained in a new pack until it is safe to prune * these objects from the repository. */ UNREACHABLE_GARBAGE; /** * Default comparator for sources. *
* Sorts generally newer, smaller types such as {@code INSERT} and {@code
* RECEIVE} earlier; older, larger types such as {@code GC} later; and
* {@code UNREACHABLE_GARBAGE} at the end.
*/
public static final Comparator
* Sources in the input will sort after sources listed in previous calls
* to this method.
*
* @param sources
* sources in this equivalence class.
* @return this.
*/
public ComparatorBuilder add(PackSource... sources) {
for (PackSource s : sources) {
ranks.put(s, Integer.valueOf(counter));
}
counter++;
return this;
}
/**
* Build the comparator.
*
* @return new comparator instance.
* @throws IllegalArgumentException
* not all {@link PackSource} instances were explicitly assigned
* an equivalence class.
*/
public Comparator
* An optimal comparator will find more objects without having to load large
* idx files from storage only to find that they don't contain the object.
* See {@link DfsPackDescription#objectLookupComparator()} for the default
* heuristics.
*
* @param packComparator
* comparator.
*/
public void setPackComparator(Comparator
* This differs from ObjectDatabase's implementation in that we can selectively
* ignore unreachable (garbage) objects.
*
* @param objectId
* identity of the object to test for existence of.
* @param avoidUnreachableObjects
* if true, ignore objects that are unreachable.
* @return true if the specified object is stored in this database.
* @throws java.io.IOException
* the object store cannot be accessed.
*/
public boolean has(AnyObjectId objectId, boolean avoidUnreachableObjects)
throws IOException {
try (ObjectReader or = newReader()) {
or.setAvoidUnreachableObjects(avoidUnreachableObjects);
return or.has(objectId);
}
}
/**
* Generate a new unique name for a pack file.
*
* @param source
* where the pack stream is created.
* @return a unique name for the pack file. Must not collide with any other
* pack file name in the same DFS.
* @throws java.io.IOException
* a new unique pack description cannot be generated.
*/
protected abstract DfsPackDescription newPack(PackSource source)
throws IOException;
/**
* Generate a new unique name for a pack file.
*
*
* Default implementation of this method would be equivalent to
* {@code newPack(source).setEstimatedPackSize(estimatedPackSize)}. But the
* clients can override this method to use the given
* {@code estimatedPackSize} value more efficiently in the process of
* creating a new
* {@link org.eclipse.jgit.internal.storage.dfs.DfsPackDescription} object.
*
* @param source
* where the pack stream is created.
* @param estimatedPackSize
* the estimated size of the pack.
* @return a unique name for the pack file. Must not collide with any other
* pack file name in the same DFS.
* @throws java.io.IOException
* a new unique pack description cannot be generated.
*/
protected DfsPackDescription newPack(PackSource source,
long estimatedPackSize) throws IOException {
DfsPackDescription pack = newPack(source);
pack.setEstimatedPackSize(estimatedPackSize);
return pack;
}
/**
* Commit a pack and index pair that was written to the DFS.
*
* Committing the pack/index pair makes them visible to readers. The JGit
* DFS code always writes the pack, then the index. This allows a simple
* commit process to do nothing if readers always look for both files to
* exist and the DFS performs atomic creation of the file (e.g. stream to a
* temporary file and rename to target on close).
*
* During pack compaction or GC the new pack file may be replacing other
* older files. Implementations should remove those older files (if any) as
* part of the commit of the new file.
*
* This method is a trivial wrapper around
* {@link #commitPackImpl(Collection, Collection)} that calls the
* implementation and fires events.
*
* @param desc
* description of the new packs.
* @param replaces
* if not null, list of packs to remove.
* @throws java.io.IOException
* the packs cannot be committed. On failure a rollback must
* also be attempted by the caller.
*/
protected void commitPack(Collection
* JGit DFS always writes the pack first, then the index. If the pack does
* not yet exist, then neither does the index. A safe DFS implementation
* would try to remove both files to ensure they are really gone.
*
* A rollback does not support failures, as it only occurs when there is
* already a failure in progress. A DFS implementor may wish to log
* warnings/error messages when a rollback fails, but should not send new
* exceptions up the Java callstack.
*
* @param desc
* pack to delete.
*/
protected abstract void rollbackPack(Collection
* The returned list must support random access and must be mutable by the
* caller. It is sorted in place using the natural sorting of the returned
* DfsPackDescription objects.
*
* @return available packs. May be empty if there are no packs.
* @throws java.io.IOException
* the packs cannot be listed and the object database is not
* functional to the caller.
*/
protected abstract List
* Used when the caller knows that new data might have been written to the
* repository that could invalidate open readers depending on this pack list,
* for example if refs are newly scanned.
*/
public abstract void markDirty();
}
private static final class PackListImpl extends PackList {
private volatile boolean dirty;
PackListImpl(DfsPackFile[] packs, DfsReftable[] reftables) {
super(packs, reftables);
}
@Override
boolean dirty() {
return dirty;
}
@Override
void clearDirty() {
dirty = false;
}
@Override
public void markDirty() {
dirty = true;
}
}
/**
* Returns a writer to store the bitmap index in this object database.
*
* @param pack
* Pack file to which the bitmaps are associated.
* @return a writer to store bitmaps associated with the pack
* @throws IOException
* when some I/O problem occurs while creating or writing to
* output stream
*/
public PackBitmapIndexWriter getPackBitmapIndexWriter(
DfsPackDescription pack) throws IOException {
return (bitmaps, packDataChecksum) -> {
try (DfsOutputStream out = writeFile(pack, BITMAP_INDEX)) {
CountingOutputStream cnt = new CountingOutputStream(out);
PackBitmapIndexWriterV1 iw = new PackBitmapIndexWriterV1(cnt);
iw.write(bitmaps, packDataChecksum);
pack.addFileExt(BITMAP_INDEX);
pack.setFileSize(BITMAP_INDEX, cnt.getCount());
pack.setBlockSize(BITMAP_INDEX, out.blockSize());
}
};
}
/**
* Returns a writer to store the pack index in this object database.
*
* @param pack
* Pack file to which the index is associated.
* @param indexVersion
* which version of the index to write
* @return a writer to store the index associated with the pack
* @throws IOException
* when some I/O problem occurs while creating or writing to
* output stream
*/
public PackIndexWriter getPackIndexWriter(
DfsPackDescription pack, int indexVersion)
throws IOException {
return (objectsToStore, packDataChecksum) -> {
try (DfsOutputStream out = writeFile(pack, INDEX);
CountingOutputStream cnt = new CountingOutputStream(out)) {
final PackIndexWriter iw = BasePackIndexWriter
.createVersion(cnt,
indexVersion);
iw.write(objectsToStore, packDataChecksum);
pack.addFileExt(INDEX);
pack.setFileSize(INDEX, cnt.getCount());
pack.setBlockSize(INDEX, out.blockSize());
pack.setIndexVersion(indexVersion);
}
};
}
}