PackConfig.java

/*
 * Copyright (C) 2008-2010, Google Inc.
 * Copyright (C) 2008, Marek Zawirski <marek.zawirski@gmail.com> 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.storage.pack;

import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_CORE_SECTION;
import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_BIGFILE_THRESHOLD;
import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_BITMAP_CONTIGUOUS_COMMIT_COUNT;
import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_BITMAP_DISTANT_COMMIT_SPAN;
import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_BITMAP_EXCESSIVE_BRANCH_COUNT;
import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_BITMAP_INACTIVE_BRANCH_AGE_INDAYS;
import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_BITMAP_RECENT_COMMIT_COUNT;
import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_BUILD_BITMAPS;
import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_COMPRESSION;
import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_CUT_DELTACHAINS;
import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_DELTA_CACHE_LIMIT;
import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_DELTA_CACHE_SIZE;
import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_DELTA_COMPRESSION;
import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_DEPTH;
import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_INDEXVERSION;
import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_MIN_SIZE_PREVENT_RACYPACK;
import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_REUSE_DELTAS;
import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_REUSE_OBJECTS;
import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_SEARCH_FOR_REUSE_TIMEOUT;
import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_SINGLE_PACK;
import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_THREADS;
import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_WAIT_PREVENT_RACYPACK;
import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_WINDOW;
import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_WINDOW_MEMORY;
import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_PACK_SECTION;

import java.time.Duration;
import java.util.concurrent.Executor;
import java.util.concurrent.TimeUnit;
import java.util.zip.Deflater;

import org.eclipse.jgit.internal.storage.file.PackIndexWriter;
import org.eclipse.jgit.lib.Config;
import org.eclipse.jgit.lib.Repository;

/**
 * Configuration used by a pack writer when constructing the stream.
 *
 * A configuration may be modified once created, but should not be modified
 * while it is being used by a PackWriter. If a configuration is not modified it
 * is safe to share the same configuration instance between multiple concurrent
 * threads executing different PackWriters.
 */
public class PackConfig {
	/**
	 * Default value of deltas reuse option: {@value}
	 *
	 * @see #setReuseDeltas(boolean)
	 */
	public static final boolean DEFAULT_REUSE_DELTAS = true;

	/**
	 * Default value of objects reuse option: {@value}
	 *
	 * @see #setReuseObjects(boolean)
	 */
	public static final boolean DEFAULT_REUSE_OBJECTS = true;

	/**
	 * Default value of keep old packs option: {@value}
	 * @see #setPreserveOldPacks(boolean)
	 * @since 4.7
	 */
	public static final boolean DEFAULT_PRESERVE_OLD_PACKS = false;

	/**
	 * Default value of prune old packs option: {@value}
	 * @see #setPrunePreserved(boolean)
	 * @since 4.7
	 */
	public static final boolean DEFAULT_PRUNE_PRESERVED = false;

	/**
	 * Default value of delta compress option: {@value}
	 *
	 * @see #setDeltaCompress(boolean)
	 */
	public static final boolean DEFAULT_DELTA_COMPRESS = true;

	/**
	 * Default value of delta base as offset option: {@value}
	 *
	 * @see #setDeltaBaseAsOffset(boolean)
	 */
	public static final boolean DEFAULT_DELTA_BASE_AS_OFFSET = false;

	/**
	 * Default value of maximum delta chain depth: {@value}
	 *
	 * @see #setMaxDeltaDepth(int)
	 */
	public static final int DEFAULT_MAX_DELTA_DEPTH = 50;

	/**
	 * Default window size during packing: {@value}
	 *
	 * @see #setDeltaSearchWindowSize(int)
	 */
	public static final int DEFAULT_DELTA_SEARCH_WINDOW_SIZE = 10;

	private static final int MB = 1 << 20;

	/**
	 * Default big file threshold: {@value}
	 *
	 * @see #setBigFileThreshold(int)
	 */
	public static final int DEFAULT_BIG_FILE_THRESHOLD = 50 * MB;

	/**
	 * Default if we wait before opening a newly written pack to prevent its
	 * lastModified timestamp could be racy
	 *
	 * @since 5.1.8
	 */
	public static final boolean DEFAULT_WAIT_PREVENT_RACY_PACK = false;

	/**
	 * Default if we wait before opening a newly written pack to prevent its
	 * lastModified timestamp could be racy
	 *
	 * @since 5.1.8
	 */
	public static final long DEFAULT_MINSIZE_PREVENT_RACY_PACK = 100 * MB;

	/**
	 * Default delta cache size: {@value}
	 *
	 * @see #setDeltaCacheSize(long)
	 */
	public static final long DEFAULT_DELTA_CACHE_SIZE = 50 * 1024 * 1024;

	/**
	 * Default delta cache limit: {@value}
	 *
	 * @see #setDeltaCacheLimit(int)
	 */
	public static final int DEFAULT_DELTA_CACHE_LIMIT = 100;

	/**
	 * Default index version: {@value}
	 *
	 * @see #setIndexVersion(int)
	 */
	public static final int DEFAULT_INDEX_VERSION = 2;

	/**
	 * Default value of the build bitmaps option: {@value}
	 *
	 * @see #setBuildBitmaps(boolean)
	 * @since 3.0
	 */
	public static final boolean DEFAULT_BUILD_BITMAPS = true;

	/**
	 * Default count of most recent commits to select for bitmaps. Only applies
	 * when bitmaps are enabled: {@value}
	 *
	 * @see #setBitmapContiguousCommitCount(int)
	 * @since 4.2
	 */
	public static final int DEFAULT_BITMAP_CONTIGUOUS_COMMIT_COUNT = 100;

	/**
	 * Count at which the span between selected commits changes from
	 * "bitmapRecentCommitSpan" to "bitmapDistantCommitSpan". Only applies when
	 * bitmaps are enabled: {@value}
	 *
	 * @see #setBitmapRecentCommitCount(int)
	 * @since 4.2
	 */
	public static final int DEFAULT_BITMAP_RECENT_COMMIT_COUNT = 20000;

	/**
	 * Default spacing between commits in recent history when selecting commits
	 * for bitmaps. Only applies when bitmaps are enabled: {@value}
	 *
	 * @see #setBitmapRecentCommitSpan(int)
	 * @since 4.2
	 */
	public static final int DEFAULT_BITMAP_RECENT_COMMIT_SPAN = 100;

	/**
	 * Default spacing between commits in distant history when selecting commits
	 * for bitmaps. Only applies when bitmaps are enabled: {@value}
	 *
	 * @see #setBitmapDistantCommitSpan(int)
	 * @since 4.2
	 */
	public static final int DEFAULT_BITMAP_DISTANT_COMMIT_SPAN = 5000;

	/**
	 * Default count of branches required to activate inactive branch commit
	 * selection. If the number of branches is less than this then bitmaps for
	 * the entire commit history of all branches will be created, otherwise
	 * branches marked as "inactive" will have coverage for only partial
	 * history: {@value}
	 *
	 * @see #setBitmapExcessiveBranchCount(int)
	 * @since 4.2
	 */
	public static final int DEFAULT_BITMAP_EXCESSIVE_BRANCH_COUNT = 100;

	/**
	 * Default age at which a branch is considered inactive. Age is taken as the
	 * number of days ago that the most recent commit was made to a branch. Only
	 * affects bitmap processing if bitmaps are enabled and the
	 * "excessive branch count" has been exceeded: {@value}
	 *
	 * @see #setBitmapInactiveBranchAgeInDays(int)
	 * @since 4.2
	 */
	public static final int DEFAULT_BITMAP_INACTIVE_BRANCH_AGE_IN_DAYS = 90;

	/**
	 * Default max time to spend during the search for reuse phase. This
	 * optimization is disabled by default: {@value}
	 *
	 * @see #setSearchForReuseTimeout(Duration)
	 * @since 5.13
	 */
	public static final Duration DEFAULT_SEARCH_FOR_REUSE_TIMEOUT = Duration
			.ofSeconds(Integer.MAX_VALUE);

	private int compressionLevel = Deflater.DEFAULT_COMPRESSION;

	private boolean reuseDeltas = DEFAULT_REUSE_DELTAS;

	private boolean reuseObjects = DEFAULT_REUSE_OBJECTS;

	private boolean preserveOldPacks = DEFAULT_PRESERVE_OLD_PACKS;

	private boolean prunePreserved = DEFAULT_PRUNE_PRESERVED;

	private boolean deltaBaseAsOffset = DEFAULT_DELTA_BASE_AS_OFFSET;

	private boolean deltaCompress = DEFAULT_DELTA_COMPRESS;

	private int maxDeltaDepth = DEFAULT_MAX_DELTA_DEPTH;

	private int deltaSearchWindowSize = DEFAULT_DELTA_SEARCH_WINDOW_SIZE;

	private long deltaSearchMemoryLimit;

	private long deltaCacheSize = DEFAULT_DELTA_CACHE_SIZE;

	private int deltaCacheLimit = DEFAULT_DELTA_CACHE_LIMIT;

	private int bigFileThreshold = DEFAULT_BIG_FILE_THRESHOLD;

	private boolean waitPreventRacyPack = DEFAULT_WAIT_PREVENT_RACY_PACK;

	private long minSizePreventRacyPack = DEFAULT_MINSIZE_PREVENT_RACY_PACK;

	private int threads;

	private Executor executor;

	private int indexVersion = DEFAULT_INDEX_VERSION;

	private boolean buildBitmaps = DEFAULT_BUILD_BITMAPS;

	private int bitmapContiguousCommitCount = DEFAULT_BITMAP_CONTIGUOUS_COMMIT_COUNT;

	private int bitmapRecentCommitCount = DEFAULT_BITMAP_RECENT_COMMIT_COUNT;

	private int bitmapRecentCommitSpan = DEFAULT_BITMAP_RECENT_COMMIT_SPAN;

	private int bitmapDistantCommitSpan = DEFAULT_BITMAP_DISTANT_COMMIT_SPAN;

	private int bitmapExcessiveBranchCount = DEFAULT_BITMAP_EXCESSIVE_BRANCH_COUNT;

	private int bitmapInactiveBranchAgeInDays = DEFAULT_BITMAP_INACTIVE_BRANCH_AGE_IN_DAYS;

	private Duration searchForReuseTimeout = DEFAULT_SEARCH_FOR_REUSE_TIMEOUT;

	private boolean cutDeltaChains;

	private boolean singlePack;

	/**
	 * Create a default configuration.
	 */
	public PackConfig() {
		// Fields are initialized to defaults.
	}

	/**
	 * Create a configuration honoring the repository's settings.
	 *
	 * @param db
	 *            the repository to read settings from. The repository is not
	 *            retained by the new configuration, instead its settings are
	 *            copied during the constructor.
	 */
	public PackConfig(Repository db) {
		fromConfig(db.getConfig());
	}

	/**
	 * Create a configuration honoring settings in a
	 * {@link org.eclipse.jgit.lib.Config}.
	 *
	 * @param cfg
	 *            the source to read settings from. The source is not retained
	 *            by the new configuration, instead its settings are copied
	 *            during the constructor.
	 */
	public PackConfig(Config cfg) {
		fromConfig(cfg);
	}

	/**
	 * Copy an existing configuration to a new instance.
	 *
	 * @param cfg
	 *            the source configuration to copy from.
	 */
	public PackConfig(PackConfig cfg) {
		this.compressionLevel = cfg.compressionLevel;
		this.reuseDeltas = cfg.reuseDeltas;
		this.reuseObjects = cfg.reuseObjects;
		this.preserveOldPacks = cfg.preserveOldPacks;
		this.prunePreserved = cfg.prunePreserved;
		this.deltaBaseAsOffset = cfg.deltaBaseAsOffset;
		this.deltaCompress = cfg.deltaCompress;
		this.maxDeltaDepth = cfg.maxDeltaDepth;
		this.deltaSearchWindowSize = cfg.deltaSearchWindowSize;
		this.deltaSearchMemoryLimit = cfg.deltaSearchMemoryLimit;
		this.deltaCacheSize = cfg.deltaCacheSize;
		this.deltaCacheLimit = cfg.deltaCacheLimit;
		this.bigFileThreshold = cfg.bigFileThreshold;
		this.waitPreventRacyPack = cfg.waitPreventRacyPack;
		this.minSizePreventRacyPack = cfg.minSizePreventRacyPack;
		this.threads = cfg.threads;
		this.executor = cfg.executor;
		this.indexVersion = cfg.indexVersion;
		this.buildBitmaps = cfg.buildBitmaps;
		this.bitmapContiguousCommitCount = cfg.bitmapContiguousCommitCount;
		this.bitmapRecentCommitCount = cfg.bitmapRecentCommitCount;
		this.bitmapRecentCommitSpan = cfg.bitmapRecentCommitSpan;
		this.bitmapDistantCommitSpan = cfg.bitmapDistantCommitSpan;
		this.bitmapExcessiveBranchCount = cfg.bitmapExcessiveBranchCount;
		this.bitmapInactiveBranchAgeInDays = cfg.bitmapInactiveBranchAgeInDays;
		this.cutDeltaChains = cfg.cutDeltaChains;
		this.singlePack = cfg.singlePack;
		this.searchForReuseTimeout = cfg.searchForReuseTimeout;
	}

	/**
	 * Check whether to reuse deltas existing in repository.
	 *
	 * Default setting: {@value #DEFAULT_REUSE_DELTAS}
	 *
	 * @return true if object is configured to reuse deltas; false otherwise.
	 */
	public boolean isReuseDeltas() {
		return reuseDeltas;
	}

	/**
	 * Set reuse deltas configuration option for the writer.
	 *
	 * When enabled, writer will search for delta representation of object in
	 * repository and use it if possible. Normally, only deltas with base to
	 * another object existing in set of objects to pack will be used. The
	 * exception however is thin-packs where the base object may exist on the
	 * other side.
	 *
	 * When raw delta data is directly copied from a pack file, its checksum is
	 * computed to verify the data is not corrupt.
	 *
	 * Default setting: {@value #DEFAULT_REUSE_DELTAS}
	 *
	 * @param reuseDeltas
	 *            boolean indicating whether or not try to reuse deltas.
	 */
	public void setReuseDeltas(boolean reuseDeltas) {
		this.reuseDeltas = reuseDeltas;
	}

	/**
	 * Checks whether to reuse existing objects representation in repository.
	 *
	 * Default setting: {@value #DEFAULT_REUSE_OBJECTS}
	 *
	 * @return true if writer is configured to reuse objects representation from
	 *         pack; false otherwise.
	 */
	public boolean isReuseObjects() {
		return reuseObjects;
	}

	/**
	 * Set reuse objects configuration option for the writer.
	 *
	 * If enabled, writer searches for compressed representation in a pack file.
	 * If possible, compressed data is directly copied from such a pack file.
	 * Data checksum is verified.
	 *
	 * Default setting: {@value #DEFAULT_REUSE_OBJECTS}
	 *
	 * @param reuseObjects
	 *            boolean indicating whether or not writer should reuse existing
	 *            objects representation.
	 */
	public void setReuseObjects(boolean reuseObjects) {
		this.reuseObjects = reuseObjects;
	}

	/**
	 * Checks whether to preserve old packs in a preserved directory
	 *
	 * Default setting: {@value #DEFAULT_PRESERVE_OLD_PACKS}
	 *
	 * @return true if repacking will preserve old pack files.
	 * @since 4.7
	 */
	public boolean isPreserveOldPacks() {
		return preserveOldPacks;
	}

	/**
	 * Set preserve old packs configuration option for repacking.
	 *
	 * If enabled, old pack files are moved into a preserved subdirectory instead
	 * of being deleted
	 *
	 * Default setting: {@value #DEFAULT_PRESERVE_OLD_PACKS}
	 *
	 * @param preserveOldPacks
	 *            boolean indicating whether or not preserve old pack files
	 * @since 4.7
	 */
	public void setPreserveOldPacks(boolean preserveOldPacks) {
		this.preserveOldPacks = preserveOldPacks;
	}

	/**
	 * Checks whether to remove preserved pack files in a preserved directory
	 *
	 * Default setting: {@value #DEFAULT_PRUNE_PRESERVED}
	 *
	 * @return true if repacking will remove preserved pack files.
	 * @since 4.7
	 */
	public boolean isPrunePreserved() {
		return prunePreserved;
	}

	/**
	 * Set prune preserved configuration option for repacking.
	 *
	 * If enabled, preserved pack files are removed from a preserved subdirectory
	 *
	 * Default setting: {@value #DEFAULT_PRESERVE_OLD_PACKS}
	 *
	 * @param prunePreserved
	 *            boolean indicating whether or not preserve old pack files
	 * @since 4.7
	 */
	public void setPrunePreserved(boolean prunePreserved) {
		this.prunePreserved = prunePreserved;
	}

	/**
	 * True if writer can use offsets to point to a delta base.
	 *
	 * If true the writer may choose to use an offset to point to a delta base
	 * in the same pack, this is a newer style of reference that saves space.
	 * False if the writer has to use the older (and more compatible style) of
	 * storing the full ObjectId of the delta base.
	 *
	 * Default setting: {@value #DEFAULT_DELTA_BASE_AS_OFFSET}
	 *
	 * @return true if delta base is stored as an offset; false if it is stored
	 *         as an ObjectId.
	 */
	public boolean isDeltaBaseAsOffset() {
		return deltaBaseAsOffset;
	}

	/**
	 * Set writer delta base format.
	 *
	 * Delta base can be written as an offset in a pack file (new approach
	 * reducing file size) or as an object id (legacy approach, compatible with
	 * old readers).
	 *
	 * Default setting: {@value #DEFAULT_DELTA_BASE_AS_OFFSET}
	 *
	 * @param deltaBaseAsOffset
	 *            boolean indicating whether delta base can be stored as an
	 *            offset.
	 */
	public void setDeltaBaseAsOffset(boolean deltaBaseAsOffset) {
		this.deltaBaseAsOffset = deltaBaseAsOffset;
	}

	/**
	 * Check whether the writer will create new deltas on the fly.
	 *
	 * Default setting: {@value #DEFAULT_DELTA_COMPRESS}
	 *
	 * @return true if the writer will create a new delta when either
	 *         {@link #isReuseDeltas()} is false, or no suitable delta is
	 *         available for reuse.
	 */
	public boolean isDeltaCompress() {
		return deltaCompress;
	}

	/**
	 * Set whether or not the writer will create new deltas on the fly.
	 *
	 * Default setting: {@value #DEFAULT_DELTA_COMPRESS}
	 *
	 * @param deltaCompress
	 *            true to create deltas when {@link #isReuseDeltas()} is false,
	 *            or when a suitable delta isn't available for reuse. Set to
	 *            false to write whole objects instead.
	 */
	public void setDeltaCompress(boolean deltaCompress) {
		this.deltaCompress = deltaCompress;
	}

	/**
	 * Get maximum depth of delta chain set up for the writer.
	 *
	 * Generated chains are not longer than this value.
	 *
	 * Default setting: {@value #DEFAULT_MAX_DELTA_DEPTH}
	 *
	 * @return maximum delta chain depth.
	 */
	public int getMaxDeltaDepth() {
		return maxDeltaDepth;
	}

	/**
	 * Set up maximum depth of delta chain for the writer.
	 *
	 * Generated chains are not longer than this value. Too low value causes low
	 * compression level, while too big makes unpacking (reading) longer.
	 *
	 * Default setting: {@value #DEFAULT_MAX_DELTA_DEPTH}
	 *
	 * @param maxDeltaDepth
	 *            maximum delta chain depth.
	 */
	public void setMaxDeltaDepth(int maxDeltaDepth) {
		this.maxDeltaDepth = maxDeltaDepth;
	}

	/**
	 * Whether existing delta chains should be cut at
	 * {@link #getMaxDeltaDepth()}.
	 *
	 * @return true if existing delta chains should be cut at
	 *         {@link #getMaxDeltaDepth()}. Default is false, allowing existing
	 *         chains to be of any length.
	 * @since 3.0
	 */
	public boolean getCutDeltaChains() {
		return cutDeltaChains;
	}

	/**
	 * Enable cutting existing delta chains at {@link #getMaxDeltaDepth()}.
	 *
	 * By default this is disabled and existing chains are kept at whatever
	 * length a prior packer was configured to create. This allows objects to be
	 * packed one with a large depth (for example 250), and later to quickly
	 * repack the repository with a shorter depth (such as 50), but reusing the
	 * complete delta chains created by the earlier 250 depth.
	 *
	 * @param cut
	 *            true to cut existing chains.
	 * @since 3.0
	 */
	public void setCutDeltaChains(boolean cut) {
		cutDeltaChains = cut;
	}

	/**
	 * Whether all of refs/* should be packed in a single pack.
	 *
	 * @return true if all of refs/* should be packed in a single pack. Default
	 *         is false, packing a separate GC_REST pack for references outside
	 *         of refs/heads/* and refs/tags/*.
	 * @since 4.9
	 */
	public boolean getSinglePack() {
		return singlePack;
	}

	/**
	 * If {@code true}, packs a single GC pack for all objects reachable from
	 * refs/*. Otherwise packs the GC pack with objects reachable from
	 * refs/heads/* and refs/tags/*, and a GC_REST pack with the remaining
	 * reachable objects. Disabled by default, packing GC and GC_REST.
	 *
	 * @param single
	 *            true to pack a single GC pack rather than GC and GC_REST packs
	 * @since 4.9
	 */
	public void setSinglePack(boolean single) {
		singlePack = single;
	}

	/**
	 * Get the number of objects to try when looking for a delta base.
	 *
	 * This limit is per thread, if 4 threads are used the actual memory used
	 * will be 4 times this value.
	 *
	 * Default setting: {@value #DEFAULT_DELTA_SEARCH_WINDOW_SIZE}
	 *
	 * @return the object count to be searched.
	 */
	public int getDeltaSearchWindowSize() {
		return deltaSearchWindowSize;
	}

	/**
	 * Set the number of objects considered when searching for a delta base.
	 *
	 * Default setting: {@value #DEFAULT_DELTA_SEARCH_WINDOW_SIZE}
	 *
	 * @param objectCount
	 *            number of objects to search at once. Must be at least 2.
	 */
	public void setDeltaSearchWindowSize(int objectCount) {
		if (objectCount <= 2)
			setDeltaCompress(false);
		else
			deltaSearchWindowSize = objectCount;
	}

	/**
	 * Get maximum number of bytes to put into the delta search window.
	 *
	 * Default setting is 0, for an unlimited amount of memory usage. Actual
	 * memory used is the lower limit of either this setting, or the sum of
	 * space used by at most {@link #getDeltaSearchWindowSize()} objects.
	 *
	 * This limit is per thread, if 4 threads are used the actual memory limit
	 * will be 4 times this value.
	 *
	 * @return the memory limit.
	 */
	public long getDeltaSearchMemoryLimit() {
		return deltaSearchMemoryLimit;
	}

	/**
	 * Set the maximum number of bytes to put into the delta search window.
	 *
	 * Default setting is 0, for an unlimited amount of memory usage. If the
	 * memory limit is reached before {@link #getDeltaSearchWindowSize()} the
	 * window size is temporarily lowered.
	 *
	 * @param memoryLimit
	 *            Maximum number of bytes to load at once, 0 for unlimited.
	 */
	public void setDeltaSearchMemoryLimit(long memoryLimit) {
		deltaSearchMemoryLimit = memoryLimit;
	}

	/**
	 * Get the size of the in-memory delta cache.
	 *
	 * This limit is for the entire writer, even if multiple threads are used.
	 *
	 * Default setting: {@value #DEFAULT_DELTA_CACHE_SIZE}
	 *
	 * @return maximum number of bytes worth of delta data to cache in memory.
	 *         If 0 the cache is infinite in size (up to the JVM heap limit
	 *         anyway). A very tiny size such as 1 indicates the cache is
	 *         effectively disabled.
	 */
	public long getDeltaCacheSize() {
		return deltaCacheSize;
	}

	/**
	 * Set the maximum number of bytes of delta data to cache.
	 *
	 * During delta search, up to this many bytes worth of small or hard to
	 * compute deltas will be stored in memory. This cache speeds up writing by
	 * allowing the cached entry to simply be dumped to the output stream.
	 *
	 * Default setting: {@value #DEFAULT_DELTA_CACHE_SIZE}
	 *
	 * @param size
	 *            number of bytes to cache. Set to 0 to enable an infinite
	 *            cache, set to 1 (an impossible size for any delta) to disable
	 *            the cache.
	 */
	public void setDeltaCacheSize(long size) {
		deltaCacheSize = size;
	}

	/**
	 * Maximum size in bytes of a delta to cache.
	 *
	 * Default setting: {@value #DEFAULT_DELTA_CACHE_LIMIT}
	 *
	 * @return maximum size (in bytes) of a delta that should be cached.
	 */
	public int getDeltaCacheLimit() {
		return deltaCacheLimit;
	}

	/**
	 * Set the maximum size of a delta that should be cached.
	 *
	 * During delta search, any delta smaller than this size will be cached, up
	 * to the {@link #getDeltaCacheSize()} maximum limit. This speeds up writing
	 * by allowing these cached deltas to be output as-is.
	 *
	 * Default setting: {@value #DEFAULT_DELTA_CACHE_LIMIT}
	 *
	 * @param size
	 *            maximum size (in bytes) of a delta to be cached.
	 */
	public void setDeltaCacheLimit(int size) {
		deltaCacheLimit = size;
	}

	/**
	 * Get the maximum file size that will be delta compressed.
	 *
	 * Files bigger than this setting will not be delta compressed, as they are
	 * more than likely already highly compressed binary data files that do not
	 * delta compress well, such as MPEG videos.
	 *
	 * Default setting: {@value #DEFAULT_BIG_FILE_THRESHOLD}
	 *
	 * @return the configured big file threshold.
	 */
	public int getBigFileThreshold() {
		return bigFileThreshold;
	}

	/**
	 * Set the maximum file size that should be considered for deltas.
	 *
	 * Default setting: {@value #DEFAULT_BIG_FILE_THRESHOLD}
	 *
	 * @param bigFileThreshold
	 *            the limit, in bytes.
	 */
	public void setBigFileThreshold(int bigFileThreshold) {
		this.bigFileThreshold = bigFileThreshold;
	}

	/**
	 * Get whether we wait before opening a newly written pack to prevent its
	 * lastModified timestamp could be racy
	 *
	 * @return whether we wait before opening a newly written pack to prevent
	 *         its lastModified timestamp could be racy
	 * @since 5.1.8
	 */
	public boolean isWaitPreventRacyPack() {
		return waitPreventRacyPack;
	}

	/**
	 * Get whether we wait before opening a newly written pack to prevent its
	 * lastModified timestamp could be racy. Returns {@code true} if
	 * {@code waitToPreventRacyPack = true} and
	 * {@code packSize > minSizePreventRacyPack}, {@code false} otherwise.
	 *
	 * @param packSize
	 *            size of the pack file
	 *
	 * @return whether we wait before opening a newly written pack to prevent
	 *         its lastModified timestamp could be racy
	 * @since 5.1.8
	 */
	public boolean doWaitPreventRacyPack(long packSize) {
		return isWaitPreventRacyPack()
				&& packSize > getMinSizePreventRacyPack();
	}

	/**
	 * Set whether we wait before opening a newly written pack to prevent its
	 * lastModified timestamp could be racy
	 *
	 * @param waitPreventRacyPack
	 *            whether we wait before opening a newly written pack to prevent
	 *            its lastModified timestamp could be racy
	 * @since 5.1.8
	 */
	public void setWaitPreventRacyPack(boolean waitPreventRacyPack) {
		this.waitPreventRacyPack = waitPreventRacyPack;
	}

	/**
	 * Get minimum packfile size for which we wait before opening a newly
	 * written pack to prevent its lastModified timestamp could be racy if
	 * {@code isWaitToPreventRacyPack} is {@code true}.
	 *
	 * @return minimum packfile size, default is 100 MiB
	 *
	 * @since 5.1.8
	 */
	public long getMinSizePreventRacyPack() {
		return minSizePreventRacyPack;
	}

	/**
	 * Set minimum packfile size for which we wait before opening a newly
	 * written pack to prevent its lastModified timestamp could be racy if
	 * {@code isWaitToPreventRacyPack} is {@code true}.
	 *
	 * @param minSizePreventRacyPack
	 *            minimum packfile size, default is 100 MiB
	 *
	 * @since 5.1.8
	 */
	public void setMinSizePreventRacyPack(long minSizePreventRacyPack) {
		this.minSizePreventRacyPack = minSizePreventRacyPack;
	}

	/**
	 * Get the compression level applied to objects in the pack.
	 *
	 * Default setting: {@value java.util.zip.Deflater#DEFAULT_COMPRESSION}
	 *
	 * @return current compression level, see {@link java.util.zip.Deflater}.
	 */
	public int getCompressionLevel() {
		return compressionLevel;
	}

	/**
	 * Set the compression level applied to objects in the pack.
	 *
	 * Default setting: {@value java.util.zip.Deflater#DEFAULT_COMPRESSION}
	 *
	 * @param level
	 *            compression level, must be a valid level recognized by the
	 *            {@link java.util.zip.Deflater} class.
	 */
	public void setCompressionLevel(int level) {
		compressionLevel = level;
	}

	/**
	 * Get the number of threads used during delta compression.
	 *
	 * Default setting: 0 (auto-detect processors)
	 *
	 * @return number of threads used for delta compression. 0 will auto-detect
	 *         the threads to the number of available processors.
	 */
	public int getThreads() {
		return threads;
	}

	/**
	 * Set the number of threads to use for delta compression.
	 *
	 * During delta compression, if there are enough objects to be considered
	 * the writer will start up concurrent threads and allow them to compress
	 * different sections of the repository concurrently.
	 *
	 * An application thread pool can be set by {@link #setExecutor(Executor)}.
	 * If not set a temporary pool will be created by the writer, and torn down
	 * automatically when compression is over.
	 *
	 * Default setting: 0 (auto-detect processors)
	 *
	 * @param threads
	 *            number of threads to use. If &lt;= 0 the number of available
	 *            processors for this JVM is used.
	 */
	public void setThreads(int threads) {
		this.threads = threads;
	}

	/**
	 * Get the preferred thread pool to execute delta search on.
	 *
	 * @return the preferred thread pool to execute delta search on.
	 */
	public Executor getExecutor() {
		return executor;
	}

	/**
	 * Set the executor to use when using threads.
	 *
	 * During delta compression if the executor is non-null jobs will be queued
	 * up on it to perform delta compression in parallel. Aside from setting the
	 * executor, the caller must set {@link #setThreads(int)} to enable threaded
	 * delta search.
	 *
	 * @param executor
	 *            executor to use for threads. Set to null to create a temporary
	 *            executor just for the writer.
	 */
	public void setExecutor(Executor executor) {
		this.executor = executor;
	}

	/**
	 * Get the pack index file format version this instance creates.
	 *
	 * Default setting: {@value #DEFAULT_INDEX_VERSION}
	 *
	 * @return the index version, the special version 0 designates the oldest
	 *         (most compatible) format available for the objects.
	 * @see PackIndexWriter
	 */
	public int getIndexVersion() {
		return indexVersion;
	}

	/**
	 * Set the pack index file format version this instance will create.
	 *
	 * Default setting: {@value #DEFAULT_INDEX_VERSION}
	 *
	 * @param version
	 *            the version to write. The special version 0 designates the
	 *            oldest (most compatible) format available for the objects.
	 * @see PackIndexWriter
	 */
	public void setIndexVersion(int version) {
		indexVersion = version;
	}

	/**
	 * True if writer is allowed to build bitmaps for indexes.
	 *
	 * Default setting: {@value #DEFAULT_BUILD_BITMAPS}
	 *
	 * @return true if delta base is the writer can choose to output an index
	 *         with bitmaps.
	 * @since 3.0
	 */
	public boolean isBuildBitmaps() {
		return buildBitmaps;
	}

	/**
	 * Set writer to allow building bitmaps for supported pack files.
	 *
	 * Index files can include bitmaps to speed up future ObjectWalks.
	 *
	 * Default setting: {@value #DEFAULT_BUILD_BITMAPS}
	 *
	 * @param buildBitmaps
	 *            boolean indicating whether bitmaps may be included in the
	 *            index.
	 * @since 3.0
	 */
	public void setBuildBitmaps(boolean buildBitmaps) {
		this.buildBitmaps = buildBitmaps;
	}

	/**
	 * Get the count of most recent commits for which to build bitmaps.
	 *
	 * Default setting: {@value #DEFAULT_BITMAP_CONTIGUOUS_COMMIT_COUNT}
	 *
	 * @return the count of most recent commits for which to build bitmaps
	 * @since 4.2
	 */
	public int getBitmapContiguousCommitCount() {
		return bitmapContiguousCommitCount;
	}

	/**
	 * Set the count of most recent commits for which to build bitmaps.
	 *
	 * Default setting: {@value #DEFAULT_BITMAP_CONTIGUOUS_COMMIT_COUNT}
	 *
	 * @param count
	 *            the count of most recent commits for which to build bitmaps
	 * @since 4.2
	 */
	public void setBitmapContiguousCommitCount(int count) {
		bitmapContiguousCommitCount = count;
	}

	/**
	 * Get the count at which to switch from "bitmapRecentCommitSpan" to
	 * "bitmapDistantCommitSpan".
	 *
	 * Default setting: {@value #DEFAULT_BITMAP_RECENT_COMMIT_COUNT}
	 *
	 * @return the count for switching between recent and distant spans
	 * @since 4.2
	 */
	public int getBitmapRecentCommitCount() {
		return bitmapRecentCommitCount;
	}

	/**
	 * Set the count at which to switch from "bitmapRecentCommitSpan" to
	 * "bitmapDistantCommitSpan".
	 *
	 * Default setting: {@value #DEFAULT_BITMAP_RECENT_COMMIT_COUNT}
	 *
	 * @param count
	 *            the count for switching between recent and distant spans
	 * @since 4.2
	 */
	public void setBitmapRecentCommitCount(int count) {
		bitmapRecentCommitCount = count;
	}

	/**
	 * Get the span of commits when building bitmaps for recent history.
	 *
	 * Default setting: {@value #DEFAULT_BITMAP_RECENT_COMMIT_SPAN}
	 *
	 * @return the span of commits when building bitmaps for recent history
	 * @since 4.2
	 */
	public int getBitmapRecentCommitSpan() {
		return bitmapRecentCommitSpan;
	}

	/**
	 * Set the span of commits when building bitmaps for recent history.
	 *
	 * Default setting: {@value #DEFAULT_BITMAP_RECENT_COMMIT_SPAN}
	 *
	 * @param span
	 *            the span of commits when building bitmaps for recent history
	 * @since 4.2
	 */
	public void setBitmapRecentCommitSpan(int span) {
		bitmapRecentCommitSpan = span;
	}

	/**
	 * Get the span of commits when building bitmaps for distant history.
	 *
	 * Default setting: {@value #DEFAULT_BITMAP_DISTANT_COMMIT_SPAN}
	 *
	 * @return the span of commits when building bitmaps for distant history
	 * @since 4.2
	 */
	public int getBitmapDistantCommitSpan() {
		return bitmapDistantCommitSpan;
	}

	/**
	 * Set the span of commits when building bitmaps for distant history.
	 *
	 * Default setting: {@value #DEFAULT_BITMAP_DISTANT_COMMIT_SPAN}
	 *
	 * @param span
	 *            the span of commits when building bitmaps for distant history
	 * @since 4.2
	 */
	public void setBitmapDistantCommitSpan(int span) {
		bitmapDistantCommitSpan = span;
	}

	/**
	 * Get the count of branches deemed "excessive". If the count of branches in
	 * a repository exceeds this number and bitmaps are enabled, "inactive"
	 * branches will have fewer bitmaps than "active" branches.
	 *
	 * Default setting: {@value #DEFAULT_BITMAP_EXCESSIVE_BRANCH_COUNT}
	 *
	 * @return the count of branches deemed "excessive"
	 * @since 4.2
	 */
	public int getBitmapExcessiveBranchCount() {
		return bitmapExcessiveBranchCount;
	}

	/**
	 * Set the count of branches deemed "excessive". If the count of branches in
	 * a repository exceeds this number and bitmaps are enabled, "inactive"
	 * branches will have fewer bitmaps than "active" branches.
	 *
	 * Default setting: {@value #DEFAULT_BITMAP_EXCESSIVE_BRANCH_COUNT}
	 *
	 * @param count
	 *            the count of branches deemed "excessive"
	 * @since 4.2
	 */
	public void setBitmapExcessiveBranchCount(int count) {
		bitmapExcessiveBranchCount = count;
	}

	/**
	 * Get the age in days that marks a branch as "inactive".
	 *
	 * Default setting: {@value #DEFAULT_BITMAP_INACTIVE_BRANCH_AGE_IN_DAYS}
	 *
	 * @return the age in days that marks a branch as "inactive"
	 * @since 4.2
	 */
	public int getBitmapInactiveBranchAgeInDays() {
		return bitmapInactiveBranchAgeInDays;
	}

	/**
	 * Get the max time to spend during the search for reuse phase.
	 *
	 * Default setting: {@value #DEFAULT_SEARCH_FOR_REUSE_TIMEOUT}
	 *
	 * @return the maximum time to spend during the search for reuse phase.
	 * @since 5.13
	 */
	public Duration getSearchForReuseTimeout() {
		return searchForReuseTimeout;
	}

	/**
	 * Set the age in days that marks a branch as "inactive".
	 *
	 * Default setting: {@value #DEFAULT_BITMAP_INACTIVE_BRANCH_AGE_IN_DAYS}
	 *
	 * @param ageInDays
	 *            the age in days that marks a branch as "inactive"
	 * @since 4.2
	 */
	public void setBitmapInactiveBranchAgeInDays(int ageInDays) {
		bitmapInactiveBranchAgeInDays = ageInDays;
	}

	/**
	 * Set the max time to spend during the search for reuse phase.
	 *
	 * @param timeout
	 *            max time allowed during the search for reuse phase
	 *
	 *            Default setting: {@value #DEFAULT_SEARCH_FOR_REUSE_TIMEOUT}
	 * @since 5.13
	 */
	public void setSearchForReuseTimeout(Duration timeout) {
		searchForReuseTimeout = timeout;
	}

	/**
	 * Update properties by setting fields from the configuration.
	 *
	 * If a property's corresponding variable is not defined in the supplied
	 * configuration, then it is left unmodified.
	 *
	 * @param rc
	 *            configuration to read properties from.
	 */
	public void fromConfig(Config rc) {
		setMaxDeltaDepth(rc.getInt(CONFIG_PACK_SECTION, CONFIG_KEY_DEPTH,
				getMaxDeltaDepth()));
		setDeltaSearchWindowSize(rc.getInt(CONFIG_PACK_SECTION,
				CONFIG_KEY_WINDOW, getDeltaSearchWindowSize()));
		setDeltaSearchMemoryLimit(rc.getLong(CONFIG_PACK_SECTION,
				CONFIG_KEY_WINDOW_MEMORY, getDeltaSearchMemoryLimit()));
		setDeltaCacheSize(rc.getLong(CONFIG_PACK_SECTION,
				CONFIG_KEY_DELTA_CACHE_SIZE, getDeltaCacheSize()));
		setDeltaCacheLimit(rc.getInt(CONFIG_PACK_SECTION,
				CONFIG_KEY_DELTA_CACHE_LIMIT, getDeltaCacheLimit()));
		setCompressionLevel(rc.getInt(CONFIG_PACK_SECTION,
				CONFIG_KEY_COMPRESSION, rc.getInt(CONFIG_CORE_SECTION,
						CONFIG_KEY_COMPRESSION, getCompressionLevel())));
		setIndexVersion(rc.getInt(CONFIG_PACK_SECTION,
				CONFIG_KEY_INDEXVERSION,
				getIndexVersion()));
		setBigFileThreshold(rc.getInt(CONFIG_CORE_SECTION,
				CONFIG_KEY_BIGFILE_THRESHOLD, getBigFileThreshold()));
		setThreads(rc.getInt(CONFIG_PACK_SECTION, CONFIG_KEY_THREADS,
				getThreads()));

		// These variables aren't standardized
		setReuseDeltas(rc.getBoolean(CONFIG_PACK_SECTION,
				CONFIG_KEY_REUSE_DELTAS, isReuseDeltas()));
		setReuseObjects(rc.getBoolean(CONFIG_PACK_SECTION,
				CONFIG_KEY_REUSE_OBJECTS, isReuseObjects()));
		setDeltaCompress(rc.getBoolean(CONFIG_PACK_SECTION,
				CONFIG_KEY_DELTA_COMPRESSION, isDeltaCompress()));
		setCutDeltaChains(rc.getBoolean(CONFIG_PACK_SECTION,
				CONFIG_KEY_CUT_DELTACHAINS, getCutDeltaChains()));
		setSinglePack(rc.getBoolean(CONFIG_PACK_SECTION,
				CONFIG_KEY_SINGLE_PACK,
				getSinglePack()));
		setBuildBitmaps(rc.getBoolean(CONFIG_PACK_SECTION,
				CONFIG_KEY_BUILD_BITMAPS, isBuildBitmaps()));
		setBitmapContiguousCommitCount(rc.getInt(CONFIG_PACK_SECTION,
				CONFIG_KEY_BITMAP_CONTIGUOUS_COMMIT_COUNT,
				getBitmapContiguousCommitCount()));
		setBitmapRecentCommitCount(rc.getInt(CONFIG_PACK_SECTION,
				CONFIG_KEY_BITMAP_RECENT_COMMIT_COUNT,
				getBitmapRecentCommitCount()));
		setBitmapRecentCommitSpan(rc.getInt(CONFIG_PACK_SECTION,
				CONFIG_KEY_BITMAP_RECENT_COMMIT_COUNT,
				getBitmapRecentCommitSpan()));
		setBitmapDistantCommitSpan(rc.getInt(CONFIG_PACK_SECTION,
				CONFIG_KEY_BITMAP_DISTANT_COMMIT_SPAN,
				getBitmapDistantCommitSpan()));
		setBitmapExcessiveBranchCount(rc.getInt(CONFIG_PACK_SECTION,
				CONFIG_KEY_BITMAP_EXCESSIVE_BRANCH_COUNT,
				getBitmapExcessiveBranchCount()));
		setBitmapInactiveBranchAgeInDays(rc.getInt(CONFIG_PACK_SECTION,
				CONFIG_KEY_BITMAP_INACTIVE_BRANCH_AGE_INDAYS,
				getBitmapInactiveBranchAgeInDays()));
		setSearchForReuseTimeout(Duration.ofSeconds(rc.getTimeUnit(
				CONFIG_PACK_SECTION, null,
				CONFIG_KEY_SEARCH_FOR_REUSE_TIMEOUT,
				getSearchForReuseTimeout().getSeconds(), TimeUnit.SECONDS)));
		setWaitPreventRacyPack(rc.getBoolean(CONFIG_PACK_SECTION,
				CONFIG_KEY_WAIT_PREVENT_RACYPACK, isWaitPreventRacyPack()));
		setMinSizePreventRacyPack(rc.getLong(CONFIG_PACK_SECTION,
				CONFIG_KEY_MIN_SIZE_PREVENT_RACYPACK,
				getMinSizePreventRacyPack()));
	}

	/** {@inheritDoc} */
	@Override
	public String toString() {
		final StringBuilder b = new StringBuilder();
		b.append("maxDeltaDepth=").append(getMaxDeltaDepth()); //$NON-NLS-1$
		b.append(", deltaSearchWindowSize=").append(getDeltaSearchWindowSize()); //$NON-NLS-1$
		b.append(", deltaSearchMemoryLimit=") //$NON-NLS-1$
				.append(getDeltaSearchMemoryLimit());
		b.append(", deltaCacheSize=").append(getDeltaCacheSize()); //$NON-NLS-1$
		b.append(", deltaCacheLimit=").append(getDeltaCacheLimit()); //$NON-NLS-1$
		b.append(", compressionLevel=").append(getCompressionLevel()); //$NON-NLS-1$
		b.append(", indexVersion=").append(getIndexVersion()); //$NON-NLS-1$
		b.append(", bigFileThreshold=").append(getBigFileThreshold()); //$NON-NLS-1$
		b.append(", threads=").append(getThreads()); //$NON-NLS-1$
		b.append(", reuseDeltas=").append(isReuseDeltas()); //$NON-NLS-1$
		b.append(", reuseObjects=").append(isReuseObjects()); //$NON-NLS-1$
		b.append(", deltaCompress=").append(isDeltaCompress()); //$NON-NLS-1$
		b.append(", buildBitmaps=").append(isBuildBitmaps()); //$NON-NLS-1$
		b.append(", bitmapContiguousCommitCount=") //$NON-NLS-1$
				.append(getBitmapContiguousCommitCount());
		b.append(", bitmapRecentCommitCount=") //$NON-NLS-1$
				.append(getBitmapRecentCommitCount());
		b.append(", bitmapRecentCommitSpan=") //$NON-NLS-1$
				.append(getBitmapRecentCommitSpan());
		b.append(", bitmapDistantCommitSpan=") //$NON-NLS-1$
				.append(getBitmapDistantCommitSpan());
		b.append(", bitmapExcessiveBranchCount=") //$NON-NLS-1$
				.append(getBitmapExcessiveBranchCount());
		b.append(", bitmapInactiveBranchAge=") //$NON-NLS-1$
				.append(getBitmapInactiveBranchAgeInDays());
		b.append(", searchForReuseTimeout") //$NON-NLS-1$
				.append(getSearchForReuseTimeout());
		b.append(", singlePack=").append(getSinglePack()); //$NON-NLS-1$
		return b.toString();
	}
}