diff --git a/org.eclipse.jgit.pgm/resources/org/eclipse/jgit/pgm/CLIText.properties b/org.eclipse.jgit.pgm/resources/org/eclipse/jgit/pgm/CLIText.properties index e879d6b60..2fff6d463 100644 --- a/org.eclipse.jgit.pgm/resources/org/eclipse/jgit/pgm/CLIText.properties +++ b/org.eclipse.jgit.pgm/resources/org/eclipse/jgit/pgm/CLIText.properties @@ -14,6 +14,7 @@ branchCreatedFrom =branch: Created from {0} branchIsNotAnAncestorOfYourCurrentHEAD=The branch '{0}' is not an ancestor of your current HEAD.\nIf you are sure you want to delete it, run 'jgit branch -D {0}'. branchNotFound=branch '{0}' not found. cacheTreePathInfo="{0}": {1} entries, {2} children +configFileNotFound=configuration file {0} not found cannotBeRenamed={0} cannot be renamed cannotChekoutNoHeadsAdvertisedByRemote=cannot checkout; no HEAD advertised by remote cannotCreateCommand=Cannot create command {0} @@ -61,6 +62,7 @@ metaVar_bucket=BUCKET metaVar_command=command metaVar_commitOrTag=COMMIT|TAG metaVar_commitish=commit-ish +metaVar_configFile=FILE metaVar_connProp=conn.prop metaVar_directory=DIRECTORY metaVar_file=FILE @@ -138,6 +140,7 @@ usage_approveDestructionOfRepository=approve destruction of repository usage_beMoreVerbose=be more verbose usage_beVerbose=be verbose usage_cloneRepositoryIntoNewDir=Clone a repository into a new directory +usage_configFile=configuration file usage_configureTheServiceInDaemonServicename=configure the service in daemon.servicename usage_deleteBranchEvenIfNotMerged=delete branch (even if not merged) usage_deleteFullyMergedBranch=delete fully merged branch diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/CLIText.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/CLIText.java index bae895cc7..14dcb1f50 100644 --- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/CLIText.java +++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/CLIText.java @@ -67,6 +67,7 @@ public static CLIText get() { /***/ public String branchIsNotAnAncestorOfYourCurrentHEAD; /***/ public String branchNotFound; /***/ public String cacheTreePathInfo; + /***/ public String configFileNotFound; /***/ public String cannotBeRenamed; /***/ public String cannotChekoutNoHeadsAdvertisedByRemote; /***/ public String cannotCreateCommand; diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Daemon.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Daemon.java index f015a9e7b..3cca87abe 100644 --- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Daemon.java +++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Daemon.java @@ -48,13 +48,22 @@ import java.text.MessageFormat; import java.util.ArrayList; import java.util.List; +import java.util.concurrent.Executors; +import org.eclipse.jgit.storage.file.FileBasedConfig; +import org.eclipse.jgit.storage.file.WindowCache; +import org.eclipse.jgit.storage.file.WindowCacheConfig; +import org.eclipse.jgit.storage.pack.PackConfig; +import org.eclipse.jgit.transport.DaemonService; +import org.eclipse.jgit.util.FS; import org.kohsuke.args4j.Argument; import org.kohsuke.args4j.Option; -import org.eclipse.jgit.transport.DaemonService; @Command(common = true, usage = "usage_exportRepositoriesOverGit") class Daemon extends TextBuiltin { + @Option(name = "--config-file", metaVar = "metaVar_configFile", usage = "usage_configFile") + File configFile; + @Option(name = "--port", metaVar = "metaVar_port", usage = "usage_portNumberToListenOn") int port = org.eclipse.jgit.transport.Daemon.DEFAULT_PORT; @@ -89,12 +98,38 @@ protected boolean requiresRepository() { @Override protected void run() throws Exception { + PackConfig packConfig = new PackConfig(); + + if (configFile != null) { + if (!configFile.exists()) { + throw die(MessageFormat.format( + CLIText.get().configFileNotFound, // + configFile.getAbsolutePath())); + } + + FileBasedConfig cfg = new FileBasedConfig(configFile, FS.DETECTED); + cfg.load(); + + WindowCacheConfig wcc = new WindowCacheConfig(); + wcc.fromConfig(cfg); + WindowCache.reconfigure(wcc); + + packConfig.fromConfig(cfg); + } + + int threads = packConfig.getThreads(); + if (threads <= 0) + threads = Runtime.getRuntime().availableProcessors(); + if (1 < threads) + packConfig.setExecutor(Executors.newFixedThreadPool(threads)); + final org.eclipse.jgit.transport.Daemon d; d = new org.eclipse.jgit.transport.Daemon( host != null ? new InetSocketAddress(host, port) : new InetSocketAddress(port)); d.setExportAll(exportAll); + d.setPackConfig(packConfig); if (0 <= timeout) d.setTimeout(timeout); diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/PackWriterTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/PackWriterTest.java index 9e663d7b4..5685ccac6 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/PackWriterTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/PackWriterTest.java @@ -66,6 +66,7 @@ import org.eclipse.jgit.revwalk.RevObject; import org.eclipse.jgit.revwalk.RevWalk; import org.eclipse.jgit.storage.file.PackIndex.MutableEntry; +import org.eclipse.jgit.storage.pack.PackConfig; import org.eclipse.jgit.storage.pack.PackWriter; import org.eclipse.jgit.transport.IndexPack; import org.eclipse.jgit.util.JGitTestUtil; @@ -78,6 +79,8 @@ public class PackWriterTest extends SampleDataRepositoryTestCase { private static final List EMPTY_LIST_REVS = Collections . emptyList(); + private PackConfig config; + private PackWriter writer; private ByteArrayOutputStream os; @@ -96,16 +99,23 @@ public void setUp() throws Exception { packBase = new File(trash, "tmp_pack"); packFile = new File(trash, "tmp_pack.pack"); indexFile = new File(trash, "tmp_pack.idx"); - writer = new PackWriter(db); + config = new PackConfig(db); + } + + public void tearDown() throws Exception { + if (writer != null) + writer.release(); + super.tearDown(); } /** * Test constructor for exceptions, default settings, initialization. */ public void testContructor() { + writer = new PackWriter(config, db.newObjectReader()); assertEquals(false, writer.isDeltaBaseAsOffset()); - assertEquals(true, writer.isReuseDeltas()); - assertEquals(true, writer.isReuseObjects()); + assertEquals(true, config.isReuseDeltas()); + assertEquals(true, config.isReuseObjects()); assertEquals(0, writer.getObjectsNumber()); } @@ -113,13 +123,17 @@ public void testContructor() { * Change default settings and verify them. */ public void testModifySettings() { - writer.setDeltaBaseAsOffset(true); - writer.setReuseDeltas(false); - writer.setReuseObjects(false); + config.setReuseDeltas(false); + config.setReuseObjects(false); + config.setDeltaBaseAsOffset(false); + assertEquals(false, config.isReuseDeltas()); + assertEquals(false, config.isReuseObjects()); + assertEquals(false, config.isDeltaBaseAsOffset()); + writer = new PackWriter(config, db.newObjectReader()); + writer.setDeltaBaseAsOffset(true); assertEquals(true, writer.isDeltaBaseAsOffset()); - assertEquals(false, writer.isReuseDeltas()); - assertEquals(false, writer.isReuseObjects()); + assertEquals(false, config.isDeltaBaseAsOffset()); } /** @@ -188,7 +202,7 @@ public void testIgnoreNonExistingObjects() throws IOException { * @throws IOException */ public void testWritePack1() throws IOException { - writer.setReuseDeltas(false); + config.setReuseDeltas(false); writeVerifyPack1(); } @@ -199,8 +213,8 @@ public void testWritePack1() throws IOException { * @throws IOException */ public void testWritePack1NoObjectReuse() throws IOException { - writer.setReuseDeltas(false); - writer.setReuseObjects(false); + config.setReuseDeltas(false); + config.setReuseObjects(false); writeVerifyPack1(); } @@ -231,7 +245,7 @@ public void testWritePack2DeltasReuseRefs() throws IOException { * @throws IOException */ public void testWritePack2DeltasReuseOffsets() throws IOException { - writer.setDeltaBaseAsOffset(true); + config.setDeltaBaseAsOffset(true); writeVerifyPack2(true); } @@ -265,7 +279,7 @@ public void testWritePack2DeltasCRC32Copy() throws IOException { * */ public void testWritePack3() throws MissingObjectException, IOException { - writer.setReuseDeltas(false); + config.setReuseDeltas(false); final ObjectId forcedOrder[] = new ObjectId[] { ObjectId.fromString("82c6b885ff600be425b4ea96dee75dca255b69e7"), ObjectId.fromString("c59759f143fb1fe21c197981df75a7ee00290799"), @@ -363,7 +377,7 @@ public void testWritePack4SizeThinVsNoThin() throws Exception { } public void testWriteIndex() throws Exception { - writer.setIndexVersion(2); + config.setIndexVersion(2); writeVerifyPack4(false); // Validate that IndexPack came up with the right CRC32 value. @@ -419,7 +433,7 @@ private void writeVerifyPack1() throws IOException { } private void writeVerifyPack2(boolean deltaReuse) throws IOException { - writer.setReuseDeltas(deltaReuse); + config.setReuseDeltas(deltaReuse); final LinkedList interestings = new LinkedList(); interestings.add(ObjectId .fromString("82c6b885ff600be425b4ea96dee75dca255b69e7")); @@ -482,6 +496,7 @@ private void createVerifyOpenPack(final Collection interestings, final boolean ignoreMissingUninteresting) throws MissingObjectException, IOException { NullProgressMonitor m = NullProgressMonitor.INSTANCE; + writer = new PackWriter(config, db.newObjectReader()); writer.setThin(thin); writer.setIgnoreMissingUninteresting(ignoreMissingUninteresting); writer.preparePack(m, interestings, uninterestings); @@ -493,6 +508,7 @@ private void createVerifyOpenPack(final Collection interestings, private void createVerifyOpenPack(final Iterator objectSource) throws MissingObjectException, IOException { NullProgressMonitor m = NullProgressMonitor.INSTANCE; + writer = new PackWriter(config, db.newObjectReader()); writer.preparePack(objectSource); writer.writePack(m, m, os); writer.release(); diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/Config.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Config.java index ccb251691..2e1ab9a07 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/Config.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Config.java @@ -216,6 +216,21 @@ public int getInt(final String section, String subsection, , section, name)); } + /** + * Obtain an integer value from the configuration. + * + * @param section + * section the key is grouped within. + * @param name + * name of the key to get. + * @param defaultValue + * default value to return if no value was present. + * @return an integer value from the configuration, or defaultValue. + */ + public long getLong(String section, String name, long defaultValue) { + return getLong(section, null, name, defaultValue); + } + /** * Obtain an integer value from the configuration. * diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/DeltaCache.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/DeltaCache.java index b6a7436f1..93eab19e2 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/DeltaCache.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/DeltaCache.java @@ -55,9 +55,9 @@ class DeltaCache { private long used; - DeltaCache(PackWriter pw) { - size = pw.getDeltaCacheSize(); - entryLimit = pw.getDeltaCacheLimit(); + DeltaCache(PackConfig pc) { + size = pc.getDeltaCacheSize(); + entryLimit = pc.getDeltaCacheLimit(); queue = new ReferenceQueue(); } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/DeltaTask.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/DeltaTask.java new file mode 100644 index 000000000..5e551e9d4 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/DeltaTask.java @@ -0,0 +1,88 @@ +/* + * Copyright (C) 2010, Google Inc. + * 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.storage.pack; + +import java.util.concurrent.Callable; + +import org.eclipse.jgit.lib.ObjectReader; +import org.eclipse.jgit.lib.ProgressMonitor; + +final class DeltaTask implements Callable { + private final PackConfig config; + + private final ObjectReader templateReader; + + private final DeltaCache dc; + + private final ProgressMonitor pm; + + private final int batchSize; + + private final int start; + + private final ObjectToPack[] list; + + DeltaTask(PackConfig config, ObjectReader reader, DeltaCache dc, + ProgressMonitor pm, int batchSize, int start, ObjectToPack[] list) { + this.config = config; + this.templateReader = reader; + this.dc = dc; + this.pm = pm; + this.batchSize = batchSize; + this.start = start; + this.list = list; + } + + public Object call() throws Exception { + final ObjectReader or = templateReader.newReader(); + try { + DeltaWindow dw; + dw = new DeltaWindow(config, dc, or); + dw.search(pm, list, start, batchSize); + } finally { + or.release(); + } + return null; + } +} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/DeltaWindow.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/DeltaWindow.java index 6521e6d3e..c96105650 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/DeltaWindow.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/DeltaWindow.java @@ -60,7 +60,7 @@ class DeltaWindow { private static final int NEXT_SRC = 1; - private final PackWriter writer; + private final PackConfig config; private final DeltaCache deltaCache; @@ -101,8 +101,8 @@ class DeltaWindow { /** Used to compress cached deltas. */ private Deflater deflater; - DeltaWindow(PackWriter pw, DeltaCache dc, ObjectReader or) { - writer = pw; + DeltaWindow(PackConfig pc, DeltaCache dc, ObjectReader or) { + config = pc; deltaCache = dc; reader = or; @@ -117,12 +117,12 @@ class DeltaWindow { // PackWriter has a minimum of 2 for the window size, but then // users might complain that JGit is creating a bigger pack file. // - window = new DeltaWindowEntry[pw.getDeltaSearchWindowSize() + 1]; + window = new DeltaWindowEntry[config.getDeltaSearchWindowSize() + 1]; for (int i = 0; i < window.length; i++) window[i] = new DeltaWindowEntry(); - maxMemory = pw.getDeltaSearchMemoryLimit(); - maxDepth = pw.getMaxDeltaDepth(); + maxMemory = config.getDeltaSearchMemoryLimit(); + maxDepth = config.getMaxDeltaDepth(); } void search(ProgressMonitor monitor, ObjectToPack[] toSearch, int off, @@ -442,7 +442,7 @@ private byte[] buffer(DeltaWindowEntry ent) throws MissingObjectException, IncorrectObjectTypeException, IOException, LargeObjectException { byte[] buf = ent.buffer; if (buf == null) { - buf = writer.buffer(reader, ent.object); + buf = PackWriter.buffer(config, reader, ent.object); if (0 < maxMemory) loaded += buf.length; ent.buffer = buf; @@ -452,7 +452,7 @@ private byte[] buffer(DeltaWindowEntry ent) throws MissingObjectException, private Deflater deflater() { if (deflater == null) - deflater = new Deflater(writer.getCompressionLevel()); + deflater = new Deflater(config.getCompressionLevel()); else deflater.reset(); return deflater; diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/PackConfig.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/PackConfig.java index 814ab8f29..9bda76d96 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/PackConfig.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/PackConfig.java @@ -1,5 +1,6 @@ /* - * Copyright (C) 2010, Google Inc. + * Copyright (C) 2008-2010, Google Inc. + * Copyright (C) 2008, Marek Zawirski * and other copyright owners as documented in the project's IP log. * * This program and the accompanying materials are made available @@ -43,51 +44,578 @@ package org.eclipse.jgit.storage.pack; -import static java.util.zip.Deflater.DEFAULT_COMPRESSION; +import java.util.concurrent.Executor; +import java.util.zip.Deflater; + import org.eclipse.jgit.lib.Config; -import org.eclipse.jgit.lib.Config.SectionParser; +import org.eclipse.jgit.lib.Repository; +import org.eclipse.jgit.storage.file.PackIndexWriter; -class PackConfig { - /** Key for {@link Config#get(SectionParser)}. */ - static final Config.SectionParser KEY = new SectionParser() { - public PackConfig parse(final Config cfg) { - return new PackConfig(cfg); - } - }; +/** + * Configuration used by a {@link PackWriter} 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; - final int deltaWindow; + /** + * Default value of objects reuse option: {@value} + * + * @see #setReuseObjects(boolean) + */ + public static final boolean DEFAULT_REUSE_OBJECTS = true; - final long deltaWindowMemory; + /** + * Default value of delta compress option: {@value} + * + * @see #setDeltaCompress(boolean) + */ + public static final boolean DEFAULT_DELTA_COMPRESS = true; - final int deltaDepth; + /** + * Default value of delta base as offset option: {@value} + * + * @see #setDeltaBaseAsOffset(boolean) + */ + public static final boolean DEFAULT_DELTA_BASE_AS_OFFSET = false; - final long deltaCacheSize; + /** + * Default value of maximum delta chain depth: {@value} + * + * @see #setMaxDeltaDepth(int) + */ + public static final int DEFAULT_MAX_DELTA_DEPTH = 50; - final int deltaCacheLimit; + /** + * Default window size during packing: {@value} + * + * @see #setDeltaSearchWindowSize(int) + */ + public static final int DEFAULT_DELTA_SEARCH_WINDOW_SIZE = 10; - final int compression; + /** + * Default big file threshold: {@value} + * + * @see #setBigFileThreshold(long) + */ + public static final long DEFAULT_BIG_FILE_THRESHOLD = 50 * 1024 * 1024; - final int indexVersion; + /** + * Default delta cache size: {@value} + * + * @see #setDeltaCacheSize(long) + */ + public static final long DEFAULT_DELTA_CACHE_SIZE = 50 * 1024 * 1024; - final long bigFileThreshold; + /** + * Default delta cache limit: {@value} + * + * @see #setDeltaCacheLimit(int) + */ + public static final int DEFAULT_DELTA_CACHE_LIMIT = 100; - final int threads; + /** + * Default index version: {@value} + * + * @see #setIndexVersion(int) + */ + public static final int DEFAULT_INDEX_VERSION = 2; - private PackConfig(Config rc) { - deltaWindow = rc.getInt("pack", "window", PackWriter.DEFAULT_DELTA_SEARCH_WINDOW_SIZE); - deltaWindowMemory = rc.getLong("pack", null, "windowmemory", 0); - deltaCacheSize = rc.getLong("pack", null, "deltacachesize", PackWriter.DEFAULT_DELTA_CACHE_SIZE); - deltaCacheLimit = rc.getInt("pack", "deltacachelimit", PackWriter.DEFAULT_DELTA_CACHE_LIMIT); - deltaDepth = rc.getInt("pack", "depth", PackWriter.DEFAULT_MAX_DELTA_DEPTH); - compression = compression(rc); - indexVersion = rc.getInt("pack", "indexversion", 2); - bigFileThreshold = rc.getLong("core", null, "bigfilethreshold", PackWriter.DEFAULT_BIG_FILE_THRESHOLD); - threads = rc.getInt("pack", "threads", 0); + + private int compressionLevel = Deflater.DEFAULT_COMPRESSION; + + private boolean reuseDeltas = DEFAULT_REUSE_DELTAS; + + private boolean reuseObjects = DEFAULT_REUSE_OBJECTS; + + 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 long bigFileThreshold = DEFAULT_BIG_FILE_THRESHOLD; + + private int threads; + + private Executor executor; + + private int indexVersion = DEFAULT_INDEX_VERSION; + + + /** Create a default configuration. */ + public PackConfig() { + // Fields are initialized to defaults. } - private static int compression(Config rc) { - if (rc.getString("pack", null, "compression") != null) - return rc.getInt("pack", "compression", DEFAULT_COMPRESSION); - return rc.getInt("core", "compression", DEFAULT_COMPRESSION); + /** + * 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 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); + } + + /** + * 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; + } + + /** + * 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; + } + + /** + * 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 long 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(long bigFileThreshold) { + this.bigFileThreshold = bigFileThreshold; + } + + /** + * 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 <= 0 the number of available + * processors for this JVM is used. + */ + public void setThreads(int threads) { + this.threads = threads; + } + + /** @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(final int version) { + indexVersion = version; + } + + /** + * 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(final Config rc) { + setMaxDeltaDepth(rc.getInt("pack", "depth", getMaxDeltaDepth())); + setDeltaSearchWindowSize(rc.getInt("pack", "window", getDeltaSearchWindowSize())); + setDeltaSearchMemoryLimit(rc.getLong("pack", "windowmemory", getDeltaSearchMemoryLimit())); + setDeltaCacheSize(rc.getLong("pack", "deltacachesize", getDeltaCacheSize())); + setDeltaCacheLimit(rc.getInt("pack", "deltacachelimit", getDeltaCacheLimit())); + setCompressionLevel(rc.getInt("pack", "compression", + rc.getInt("core", "compression", getCompressionLevel()))); + setIndexVersion(rc.getInt("pack", "indexversion", getIndexVersion())); + setBigFileThreshold(rc.getLong("core", "bigfilethreshold", getBigFileThreshold())); + setThreads(rc.getInt("pack", "threads", getThreads())); + + // These variables aren't standardized + // + setReuseDeltas(rc.getBoolean("pack", "reusedeltas", isReuseDeltas())); + setReuseObjects(rc.getBoolean("pack", "reuseobjects", isReuseObjects())); + setDeltaCompress(rc.getBoolean("pack", "deltacompression", isDeltaCompress())); } } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/PackWriter.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/PackWriter.java index a9cd45b1d..df5594cf4 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/PackWriter.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/PackWriter.java @@ -58,8 +58,12 @@ import java.util.Comparator; import java.util.Iterator; import java.util.List; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Executor; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; +import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; import java.util.zip.Deflater; import java.util.zip.DeflaterOutputStream; @@ -71,7 +75,6 @@ import org.eclipse.jgit.errors.MissingObjectException; import org.eclipse.jgit.errors.StoredObjectRepresentationNotAvailableException; import org.eclipse.jgit.lib.AnyObjectId; -import org.eclipse.jgit.lib.Config; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.NullProgressMonitor; import org.eclipse.jgit.lib.ObjectId; @@ -123,47 +126,6 @@ *

*/ public class PackWriter { - /** - * Default value of deltas reuse option. - * - * @see #setReuseDeltas(boolean) - */ - public static final boolean DEFAULT_REUSE_DELTAS = true; - - /** - * Default value of objects reuse option. - * - * @see #setReuseObjects(boolean) - */ - public static final boolean DEFAULT_REUSE_OBJECTS = true; - - /** - * Default value of delta base as offset option. - * - * @see #setDeltaBaseAsOffset(boolean) - */ - public static final boolean DEFAULT_DELTA_BASE_AS_OFFSET = false; - - /** - * Default value of maximum delta chain depth. - * - * @see #setMaxDeltaDepth(int) - */ - public static final int DEFAULT_MAX_DELTA_DEPTH = 50; - - /** - * Default window size during packing. - * - * @see #setDeltaSearchWindowSize(int) - */ - public static final int DEFAULT_DELTA_SEARCH_WINDOW_SIZE = 10; - - static final long DEFAULT_BIG_FILE_THRESHOLD = 50 * 1024 * 1024; - - static final long DEFAULT_DELTA_CACHE_SIZE = 50 * 1024 * 1024; - - static final int DEFAULT_DELTA_CACHE_LIMIT = 100; - private static final int PACK_VERSION_GENERATED = 2; @SuppressWarnings("unchecked") @@ -181,8 +143,6 @@ public class PackWriter { // edge objects for thin packs private final ObjectIdSubclassMap edgeObjects = new ObjectIdSubclassMap(); - private int compressionLevel; - private Deflater myDeflater; private final ObjectReader reader; @@ -190,33 +150,15 @@ public class PackWriter { /** {@link #reader} recast to the reuse interface, if it supports it. */ private final ObjectReuseAsIs reuseSupport; + private final PackConfig config; + private List sortedByName; private byte packcsum[]; - private boolean reuseDeltas = DEFAULT_REUSE_DELTAS; + private boolean deltaBaseAsOffset; - private boolean reuseObjects = DEFAULT_REUSE_OBJECTS; - - private boolean deltaBaseAsOffset = DEFAULT_DELTA_BASE_AS_OFFSET; - - private boolean deltaCompress = true; - - 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 indexVersion; - - private long bigFileThreshold = DEFAULT_BIG_FILE_THRESHOLD; - - private int threads = 1; + private boolean reuseDeltas; private boolean thin; @@ -245,7 +187,7 @@ public PackWriter(final Repository repo) { * reader to read from the repository with. */ public PackWriter(final ObjectReader reader) { - this(null, reader); + this(new PackConfig(), reader); } /** @@ -260,105 +202,38 @@ public PackWriter(final ObjectReader reader) { * reader to read from the repository with. */ public PackWriter(final Repository repo, final ObjectReader reader) { + this(new PackConfig(repo), reader); + } + + /** + * Create writer with a specified configuration. + *

+ * Objects for packing are specified in {@link #preparePack(Iterator)} or + * {@link #preparePack(ProgressMonitor, Collection, Collection)}. + * + * @param config + * configuration for the pack writer. + * @param reader + * reader to read from the repository with. + */ + public PackWriter(final PackConfig config, final ObjectReader reader) { + this.config = config; this.reader = reader; if (reader instanceof ObjectReuseAsIs) reuseSupport = ((ObjectReuseAsIs) reader); else reuseSupport = null; - final PackConfig pc = configOf(repo).get(PackConfig.KEY); - deltaSearchWindowSize = pc.deltaWindow; - deltaSearchMemoryLimit = pc.deltaWindowMemory; - deltaCacheSize = pc.deltaCacheSize; - deltaCacheLimit = pc.deltaCacheLimit; - maxDeltaDepth = pc.deltaDepth; - compressionLevel = pc.compression; - indexVersion = pc.indexVersion; - bigFileThreshold = pc.bigFileThreshold; - threads = pc.threads; - } - - private static Config configOf(final Repository repo) { - if (repo == null) - return new Config(); - return repo.getConfig(); - } - - /** - * Check whether object is configured to reuse deltas existing in - * repository. - *

- * Default setting: {@link #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 this 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. Exception is however - * thin-pack (see - * {@link #preparePack(ProgressMonitor, Collection, Collection)} and - * {@link #preparePack(Iterator)}) where base object must exist on other - * side machine. - *

- * When raw delta data is directly copied from a pack file, checksum is - * computed to verify data. - *

- *

- * Default setting: {@link #DEFAULT_REUSE_DELTAS} - *

- * - * @param reuseDeltas - * boolean indicating whether or not try to reuse deltas. - */ - public void setReuseDeltas(boolean reuseDeltas) { - this.reuseDeltas = reuseDeltas; - } - - /** - * Checks whether object is configured to reuse existing objects - * representation in repository. - *

- * Default setting: {@link #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 this writer. If enabled, - * writer searches for representation in a pack file. If possible, - * compressed data is directly copied from such a pack file. Data checksum - * is verified. - *

- * Default setting: {@link #DEFAULT_REUSE_OBJECTS} - *

- * - * @param reuseObjects - * boolean indicating whether or not writer should reuse existing - * objects representation. - */ - public void setReuseObjects(boolean reuseObjects) { - this.reuseObjects = reuseObjects; + deltaBaseAsOffset = config.isDeltaBaseAsOffset(); + reuseDeltas = config.isReuseDeltas(); } /** * Check whether writer can store delta base as an offset (new style * reducing pack size) or should store it as an object id (legacy style, * compatible with old readers). - *

- * Default setting: {@link #DEFAULT_DELTA_BASE_AS_OFFSET} - *

+ * + * Default setting: {@value PackConfig#DEFAULT_DELTA_BASE_AS_OFFSET} * * @return true if delta base is stored as an offset; false if it is stored * as an object id. @@ -371,9 +246,8 @@ public boolean isDeltaBaseAsOffset() { * 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: {@link #DEFAULT_DELTA_BASE_AS_OFFSET} - *

+ * + * Default setting: {@value PackConfig#DEFAULT_DELTA_BASE_AS_OFFSET} * * @param deltaBaseAsOffset * boolean indicating whether delta base can be stored as an @@ -383,235 +257,6 @@ public void setDeltaBaseAsOffset(boolean deltaBaseAsOffset) { this.deltaBaseAsOffset = deltaBaseAsOffset; } - /** - * Check whether the writer will create new deltas on the fly. - *

- * Default setting: true - *

- * - * @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. - * - * @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 this writer. Generated chains - * are not longer than this value. - *

- * Default setting: {@link #DEFAULT_MAX_DELTA_DEPTH} - *

- * - * @return maximum delta chain depth. - */ - public int getMaxDeltaDepth() { - return maxDeltaDepth; - } - - /** - * Set up maximum depth of delta chain for this 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: {@link #DEFAULT_MAX_DELTA_DEPTH} - *

- * - * @param maxDeltaDepth - * maximum delta chain depth. - */ - public void setMaxDeltaDepth(int maxDeltaDepth) { - this.maxDeltaDepth = maxDeltaDepth; - } - - /** - * 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. - * - * @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: {@link #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. - * - * @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. - * - * @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. - * - * @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. - * - * @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. - * - * @return the configured big file threshold. - */ - public long getBigFileThreshold() { - return bigFileThreshold; - } - - /** - * Set the maximum file size that should be considered for deltas. - * - * @param bigFileThreshold - * the limit, in bytes. - */ - public void setBigFileThreshold(long bigFileThreshold) { - this.bigFileThreshold = bigFileThreshold; - } - - /** - * Get the compression level applied to objects in the pack. - * - * @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. - * - * @param level - * compression level, must be a valid level recognized by the - * {@link java.util.zip.Deflater} class. Typically this setting - * is {@link java.util.zip.Deflater#BEST_SPEED}. - */ - public void setCompressionLevel(int level) { - compressionLevel = level; - } - - /** @return number of threads used for delta compression. */ - 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. - * - * @param threads - * number of threads to use. If <= 0 the number of available - * processors for this JVM is used. - */ - public void setThread(int threads) { - this.threads = threads; - } - /** @return true if this writer is producing a thin pack. */ public boolean isThin() { return thin; @@ -651,18 +296,6 @@ public void setIgnoreMissingUninteresting(final boolean ignore) { ignoreMissingUninteresting = ignore; } - /** - * Set the pack index file format version this instance will create. - * - * @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(final int version) { - indexVersion = version; - } - /** * Returns objects number in a pack file that was created by this writer. * @@ -791,6 +424,7 @@ public ObjectId computeName() { public void writeIndex(final OutputStream indexStream) throws IOException { final List list = sortByName(); final PackIndexWriter iw; + int indexVersion = config.getIndexVersion(); if (indexVersion <= 0) iw = PackIndexWriter.createOldestPossible(indexStream, list); else @@ -842,9 +476,9 @@ public void writePack(ProgressMonitor compressMonitor, if (writeMonitor == null) writeMonitor = NullProgressMonitor.INSTANCE; - if ((reuseDeltas || reuseObjects) && reuseSupport != null) + if ((reuseDeltas || config.isReuseObjects()) && reuseSupport != null) searchForReuse(); - if (deltaCompress) + if (config.isDeltaCompress()) searchForDeltas(compressMonitor); final PackOutputStream out = new PackOutputStream(writeMonitor, @@ -954,7 +588,7 @@ private boolean loadSize(ObjectToPack e) throws MissingObjectException, // If its too big for us to handle, skip over it. // - if (bigFileThreshold <= sz || Integer.MAX_VALUE <= sz) + if (config.getBigFileThreshold() <= sz || Integer.MAX_VALUE <= sz) return false; // If its too tiny for the delta compression to work, skip it. @@ -970,21 +604,19 @@ private void searchForDeltas(final ProgressMonitor monitor, final ObjectToPack[] list, final int cnt) throws MissingObjectException, IncorrectObjectTypeException, LargeObjectException, IOException { + int threads = config.getThreads(); if (threads == 0) threads = Runtime.getRuntime().availableProcessors(); - if (threads <= 1 || cnt <= 2 * getDeltaSearchWindowSize()) { - DeltaCache dc = new DeltaCache(this); - DeltaWindow dw = new DeltaWindow(this, dc, reader); + if (threads <= 1 || cnt <= 2 * config.getDeltaSearchWindowSize()) { + DeltaCache dc = new DeltaCache(config); + DeltaWindow dw = new DeltaWindow(config, dc, reader); dw.search(monitor, list, 0, cnt); return; } - final List errors = Collections - .synchronizedList(new ArrayList()); - final DeltaCache dc = new ThreadSafeDeltaCache(this); + final DeltaCache dc = new ThreadSafeDeltaCache(config); final ProgressMonitor pm = new ThreadSafeProgressMonitor(monitor); - final ExecutorService pool = Executors.newFixedThreadPool(threads); // Guess at the size of batch we want. Because we don't really // have a way for a thread to steal work from another thread if @@ -992,9 +624,10 @@ private void searchForDeltas(final ProgressMonitor monitor, // are a bit smaller. // int estSize = cnt / (threads * 2); - if (estSize < 2 * getDeltaSearchWindowSize()) - estSize = 2 * getDeltaSearchWindowSize(); + if (estSize < 2 * config.getDeltaSearchWindowSize()) + estSize = 2 * config.getDeltaSearchWindowSize(); + final List myTasks = new ArrayList(threads * 2); for (int i = 0; i < cnt;) { final int start = i; final int batchSize; @@ -1019,39 +652,67 @@ private void searchForDeltas(final ProgressMonitor monitor, batchSize = end - start; } i += batchSize; - - pool.submit(new Runnable() { - public void run() { - try { - final ObjectReader or = reader.newReader(); - try { - DeltaWindow dw; - dw = new DeltaWindow(PackWriter.this, dc, or); - dw.search(pm, list, start, batchSize); - } finally { - or.release(); - } - } catch (Throwable err) { - errors.add(err); - } - } - }); + myTasks.add(new DeltaTask(config, reader, dc, pm, batchSize, start, list)); } - // Tell the pool to stop. - // - pool.shutdown(); - for (;;) { + final Executor executor = config.getExecutor(); + final List errors = Collections + .synchronizedList(new ArrayList()); + if (executor instanceof ExecutorService) { + // Caller supplied us a service, use it directly. + // + runTasks((ExecutorService) executor, myTasks, errors); + + } else if (executor == null) { + // Caller didn't give us a way to run the tasks, spawn up a + // temporary thread pool and make sure it tears down cleanly. + // + ExecutorService pool = Executors.newFixedThreadPool(threads); try { - if (pool.awaitTermination(60, TimeUnit.SECONDS)) - break; - } catch (InterruptedException e) { + runTasks(pool, myTasks, errors); + } finally { + pool.shutdown(); + for (;;) { + try { + if (pool.awaitTermination(60, TimeUnit.SECONDS)) + break; + } catch (InterruptedException e) { + throw new IOException( + JGitText.get().packingCancelledDuringObjectsWriting); + } + } + } + } else { + // The caller gave us an executor, but it might not do + // asynchronous execution. Wrap everything and hope it + // can schedule these for us. + // + final CountDownLatch done = new CountDownLatch(myTasks.size()); + for (final DeltaTask task : myTasks) { + executor.execute(new Runnable() { + public void run() { + try { + task.call(); + } catch (Throwable failure) { + errors.add(failure); + } finally { + done.countDown(); + } + } + }); + } + try { + done.await(); + } catch (InterruptedException ie) { + // We can't abort the other tasks as we have no handle. + // Cross our fingers and just break out anyway. + // throw new IOException( JGitText.get().packingCancelledDuringObjectsWriting); } } - // If any thread threw an error, try to report it back as + // If any task threw an error, try to report it back as // though we weren't using a threaded search algorithm. // if (!errors.isEmpty()) { @@ -1069,6 +730,28 @@ public void run() { } } + private void runTasks(ExecutorService pool, List tasks, + List errors) throws IOException { + List> futures = new ArrayList>(tasks.size()); + for (DeltaTask task : tasks) + futures.add(pool.submit(task)); + + try { + for (Future f : futures) { + try { + f.get(); + } catch (ExecutionException failed) { + errors.add(failed.getCause()); + } + } + } catch (InterruptedException ie) { + for (Future f : futures) + f.cancel(true); + throw new IOException( + JGitText.get().packingCancelledDuringObjectsWriting); + } + } + private void writeObjects(ProgressMonitor writeMonitor, PackOutputStream out) throws IOException { for (List list : objectsLists) { @@ -1196,8 +879,8 @@ private void writeDeltaObjectDeflate(PackOutputStream out, private TemporaryBuffer.Heap delta(final ObjectToPack otp) throws IOException { - DeltaIndex index = new DeltaIndex(buffer(reader, otp.getDeltaBaseId())); - byte[] res = buffer(reader, otp); + DeltaIndex index = new DeltaIndex(buffer(otp.getDeltaBaseId())); + byte[] res = buffer(otp); // We never would have proposed this pair if the delta would be // larger than the unpacked version of the object. So using it @@ -1208,7 +891,12 @@ private TemporaryBuffer.Heap delta(final ObjectToPack otp) return delta; } - byte[] buffer(ObjectReader or, AnyObjectId objId) throws IOException { + private byte[] buffer(AnyObjectId objId) throws IOException { + return buffer(config, reader, objId); + } + + static byte[] buffer(PackConfig config, ObjectReader or, AnyObjectId objId) + throws IOException { ObjectLoader ldr = or.open(objId); if (!ldr.isLarge()) return ldr.getCachedBytes(); @@ -1221,7 +909,7 @@ byte[] buffer(ObjectReader or, AnyObjectId objId) throws IOException { // If it really is too big to work with, abort out now. // long sz = ldr.getSize(); - if (getBigFileThreshold() <= sz || Integer.MAX_VALUE < sz) + if (config.getBigFileThreshold() <= sz || Integer.MAX_VALUE < sz) throw new LargeObjectException(objId.copy()); // Its considered to be large by the loader, but we really @@ -1248,7 +936,7 @@ byte[] buffer(ObjectReader or, AnyObjectId objId) throws IOException { private Deflater deflater() { if (myDeflater == null) - myDeflater = new Deflater(compressionLevel); + myDeflater = new Deflater(config.getCompressionLevel()); return myDeflater; } @@ -1404,7 +1092,7 @@ else if (PACK_DELTA < nFmt && otp.isDeltaRepresentation()) otp.clearDeltaBase(); otp.clearReuseAsIs(); } - } else if (nFmt == PACK_WHOLE && reuseObjects) { + } else if (nFmt == PACK_WHOLE && config.isReuseObjects()) { otp.clearDeltaBase(); otp.setReuseAsIs(); otp.setWeight(nWeight); diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/ThreadSafeDeltaCache.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/ThreadSafeDeltaCache.java index 141289190..2492a05e1 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/ThreadSafeDeltaCache.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/ThreadSafeDeltaCache.java @@ -48,8 +48,8 @@ class ThreadSafeDeltaCache extends DeltaCache { private final ReentrantLock lock; - ThreadSafeDeltaCache(PackWriter pw) { - super(pw); + ThreadSafeDeltaCache(PackConfig pc) { + super(pc); lock = new ReentrantLock(); } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/BasePackPushConnection.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/BasePackPushConnection.java index 297105d46..0838f29c1 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/BasePackPushConnection.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/BasePackPushConnection.java @@ -231,7 +231,8 @@ private void writePack(final Map refUpdates, List newObjects = new ArrayList(refUpdates.size()); final long start; - final PackWriter writer = new PackWriter(local); + final PackWriter writer = new PackWriter(transport.getPackConfig(), + local.newObjectReader()); try { for (final Ref r : getRefs()) diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/BundleWriter.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/BundleWriter.java index 79fa58c36..b513412da 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/BundleWriter.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/BundleWriter.java @@ -61,6 +61,7 @@ import org.eclipse.jgit.lib.Ref; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.revwalk.RevCommit; +import org.eclipse.jgit.storage.pack.PackConfig; import org.eclipse.jgit.storage.pack.PackWriter; /** @@ -81,12 +82,14 @@ * overall bundle size. */ public class BundleWriter { - private final PackWriter packWriter; + private final Repository db; private final Map include; private final Set assume; + private PackConfig packConfig; + /** * Create a writer for a bundle. * @@ -94,11 +97,22 @@ public class BundleWriter { * repository where objects are stored. */ public BundleWriter(final Repository repo) { - packWriter = new PackWriter(repo); + db = repo; include = new TreeMap(); assume = new HashSet(); } + /** + * Set the configuration used by the pack generator. + * + * @param pc + * configuration controlling packing parameters. If null the + * source repository's settings will be used. + */ + public void setPackConfig(PackConfig pc) { + this.packConfig = pc; + } + /** * Include an object (and everything reachable from it) in the bundle. * @@ -166,6 +180,10 @@ public void assume(final RevCommit c) { */ public void writeBundle(ProgressMonitor monitor, OutputStream os) throws IOException { + PackConfig pc = packConfig; + if (pc == null) + pc = new PackConfig(db); + PackWriter packWriter = new PackWriter(pc, db.newObjectReader()); try { final HashSet inc = new HashSet(); final HashSet exc = new HashSet(); diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/Daemon.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/Daemon.java index aa2e2521c..0bc5fb3a2 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/Daemon.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/Daemon.java @@ -63,6 +63,7 @@ import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.lib.RepositoryCache; import org.eclipse.jgit.lib.RepositoryCache.FileKey; +import org.eclipse.jgit.storage.pack.PackConfig; import org.eclipse.jgit.util.FS; /** Basic daemon for the anonymous git:// transport protocol. */ @@ -90,6 +91,8 @@ public class Daemon { private int timeout; + private PackConfig packConfig; + /** Configure a daemon to listen on any available network port. */ public Daemon() { this(null); @@ -120,6 +123,7 @@ protected void execute(final DaemonClient dc, final UploadPack rp = new UploadPack(db); final InputStream in = dc.getInputStream(); rp.setTimeout(Daemon.this.getTimeout()); + rp.setPackConfig(Daemon.this.packConfig); rp.upload(in, dc.getOutputStream(), null); } }, new DaemonService("receive-pack", "receivepack") { @@ -242,6 +246,17 @@ public void setTimeout(final int seconds) { timeout = seconds; } + /** + * Set the configuration used by the pack generator. + * + * @param pc + * configuration controlling packing parameters. If null the + * source repository's settings will be used. + */ + public void setPackConfig(PackConfig pc) { + this.packConfig = pc; + } + /** * Start this daemon on a background thread. * diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/Transport.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/Transport.java index a8e47afd3..500cf0cff 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/Transport.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/Transport.java @@ -66,6 +66,7 @@ import org.eclipse.jgit.lib.Ref; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.lib.TransferConfig; +import org.eclipse.jgit.storage.pack.PackConfig; import org.eclipse.jgit.util.FS; /** @@ -554,6 +555,9 @@ private static String findTrackingRefName(final String remoteName, /** Timeout in seconds to wait before aborting an IO read or write. */ private int timeout; + /** Pack configuration used by this transport to make pack file. */ + private PackConfig packConfig; + /** * Create a new transport instance. * @@ -791,6 +795,32 @@ public void setTimeout(final int seconds) { timeout = seconds; } + /** + * Get the configuration used by the pack generator to make packs. + * + * If {@link #setPackConfig(PackConfig)} was previously given null a new + * PackConfig is created on demand by this method using the source + * repository's settings. + * + * @return the pack configuration. Never null. + */ + public PackConfig getPackConfig() { + if (packConfig == null) + packConfig = new PackConfig(local); + return packConfig; + } + + /** + * Set the configuration used by the pack generator. + * + * @param pc + * configuration controlling packing parameters. If null the + * source repository's settings will be used. + */ + public void setPackConfig(PackConfig pc) { + packConfig = pc; + } + /** * Fetch objects and refs from the remote repository to the local one. *

diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/UploadPack.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/UploadPack.java index 02ce251be..16d56df66 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/UploadPack.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/UploadPack.java @@ -69,6 +69,7 @@ import org.eclipse.jgit.revwalk.RevObject; import org.eclipse.jgit.revwalk.RevTag; import org.eclipse.jgit.revwalk.RevWalk; +import org.eclipse.jgit.storage.pack.PackConfig; import org.eclipse.jgit.storage.pack.PackWriter; import org.eclipse.jgit.transport.BasePackFetchConnection.MultiAck; import org.eclipse.jgit.transport.RefAdvertiser.PacketLineOutRefAdvertiser; @@ -102,6 +103,9 @@ public class UploadPack { /** Revision traversal support over {@link #db}. */ private final RevWalk walk; + /** Configuration to pass into the PackWriter. */ + private PackConfig packConfig; + /** Timeout in seconds to wait for client interaction. */ private int timeout; @@ -258,6 +262,17 @@ public void setRefFilter(final RefFilter refFilter) { this.refFilter = refFilter != null ? refFilter : RefFilter.DEFAULT; } + /** + * Set the configuration used by the pack generator. + * + * @param pc + * configuration controlling packing parameters. If null the + * source repository's settings will be used. + */ + public void setPackConfig(PackConfig pc) { + this.packConfig = pc; + } + /** * Execute the upload task on the socket. * @@ -548,8 +563,6 @@ private boolean wantSatisfied(final RevCommit want) throws IOException { } private void sendPack() throws IOException { - final boolean thin = options.contains(OPTION_THIN_PACK); - final boolean progress = !options.contains(OPTION_NO_PROGRESS); final boolean sideband = options.contains(OPTION_SIDE_BAND) || options.contains(OPTION_SIDE_BAND_64K); @@ -563,15 +576,18 @@ private void sendPack() throws IOException { packOut = new SideBandOutputStream(SideBandOutputStream.CH_DATA, bufsz, rawOut); - if (progress) + if (!options.contains(OPTION_NO_PROGRESS)) pm = new SideBandProgressMonitor(new SideBandOutputStream( SideBandOutputStream.CH_PROGRESS, bufsz, rawOut)); } - final PackWriter pw = new PackWriter(db, walk.getObjectReader()); + PackConfig cfg = packConfig; + if (cfg == null) + cfg = new PackConfig(db); + final PackWriter pw = new PackWriter(cfg, walk.getObjectReader()); try { pw.setDeltaBaseAsOffset(options.contains(OPTION_OFS_DELTA)); - pw.setThin(thin); + pw.setThin(options.contains(OPTION_THIN_PACK)); pw.preparePack(pm, wantAll, commonBase); if (options.contains(OPTION_INCLUDE_TAG)) { for (final Ref r : refs.values()) { diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/WalkPushConnection.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/WalkPushConnection.java index bbc918f25..9ce0ec1b3 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/WalkPushConnection.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/WalkPushConnection.java @@ -103,6 +103,9 @@ class WalkPushConnection extends BaseConnection implements PushConnection { /** Database connection to the remote repository. */ private final WalkRemoteObjectDatabase dest; + /** The configured transport we were constructed by. */ + private final Transport transport; + /** * Packs already known to reside in the remote repository. *

@@ -123,9 +126,9 @@ class WalkPushConnection extends BaseConnection implements PushConnection { WalkPushConnection(final WalkTransport walkTransport, final WalkRemoteObjectDatabase w) { - Transport t = (Transport)walkTransport; - local = t.local; - uri = t.getURI(); + transport = (Transport) walkTransport; + local = transport.local; + uri = transport.getURI(); dest = w; } @@ -209,7 +212,8 @@ private void sendpack(final List updates, String pathPack = null; String pathIdx = null; - final PackWriter pw = new PackWriter(local); + final PackWriter writer = new PackWriter(transport.getPackConfig(), + local.newObjectReader()); try { final List need = new ArrayList(); final List have = new ArrayList(); @@ -220,20 +224,20 @@ private void sendpack(final List updates, if (r.getPeeledObjectId() != null) have.add(r.getPeeledObjectId()); } - pw.preparePack(monitor, need, have); + writer.preparePack(monitor, need, have); // We don't have to continue further if the pack will // be an empty pack, as the remote has all objects it // needs to complete this change. // - if (pw.getObjectsNumber() == 0) + if (writer.getObjectsNumber() == 0) return; packNames = new LinkedHashMap(); for (final String n : dest.getPackNames()) packNames.put(n, n); - final String base = "pack-" + pw.computeName().name(); + final String base = "pack-" + writer.computeName().name(); final String packName = base + ".pack"; pathPack = "pack/" + packName; pathIdx = "pack/" + base + ".idx"; @@ -254,7 +258,7 @@ private void sendpack(final List updates, OutputStream os = dest.writeFile(pathPack, monitor, wt + "..pack"); try { os = new BufferedOutputStream(os); - pw.writePack(monitor, monitor, os); + writer.writePack(monitor, monitor, os); } finally { os.close(); } @@ -262,7 +266,7 @@ private void sendpack(final List updates, os = dest.writeFile(pathIdx, monitor, wt + "..idx"); try { os = new BufferedOutputStream(os); - pw.writeIndex(os); + writer.writeIndex(os); } finally { os.close(); } @@ -282,7 +286,7 @@ private void sendpack(final List updates, throw new TransportException(uri, JGitText.get().cannotStoreObjects, err); } finally { - pw.release(); + writer.release(); } }