Merge "RefDirectory: remove ref lock file for following ref dir removal" into stable-4.4
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ConfigTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ConfigTest.java
index 6d07d34..aebbafe 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ConfigTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ConfigTest.java
@@ -48,6 +48,11 @@
package org.eclipse.jgit.lib;
+import static java.util.concurrent.TimeUnit.DAYS;
+import static java.util.concurrent.TimeUnit.HOURS;
+import static java.util.concurrent.TimeUnit.MILLISECONDS;
+import static java.util.concurrent.TimeUnit.MINUTES;
+import static java.util.concurrent.TimeUnit.SECONDS;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
@@ -64,6 +69,7 @@
import java.util.Iterator;
import java.util.LinkedList;
import java.util.Set;
+import java.util.concurrent.TimeUnit;
import org.eclipse.jgit.api.MergeCommand.FastForwardMode;
import org.eclipse.jgit.errors.ConfigInvalidException;
@@ -782,10 +788,31 @@ public void testIncludeEmptyValue() throws ConfigInvalidException {
@Test
public void testIncludeValuePathNotFound() throws ConfigInvalidException {
+ // we do not expect an exception, included path not found are ignored
String notFound = "/not/found";
- expectedEx.expect(ConfigInvalidException.class);
- expectedEx.expectMessage(notFound);
- parse("[include]\npath=" + notFound + "\n");
+ Config parsed = parse("[include]\npath=" + notFound + "\n");
+ assertEquals(1, parsed.getSections().size());
+ assertEquals(notFound, parsed.getString("include", null, "path"));
+ }
+
+ @Test
+ public void testIncludeValuePathWithTilde() throws ConfigInvalidException {
+ // we do not expect an exception, included path not supported are
+ // ignored
+ String notSupported = "~/someFile";
+ Config parsed = parse("[include]\npath=" + notSupported + "\n");
+ assertEquals(1, parsed.getSections().size());
+ assertEquals(notSupported, parsed.getString("include", null, "path"));
+ }
+
+ @Test
+ public void testIncludeValuePathRelative() throws ConfigInvalidException {
+ // we do not expect an exception, included path not supported are
+ // ignored
+ String notSupported = "someRelativeFile";
+ Config parsed = parse("[include]\npath=" + notSupported + "\n");
+ assertEquals(1, parsed.getSections().size());
+ assertEquals(notSupported, parsed.getString("include", null, "path"));
}
@Test
@@ -849,4 +876,93 @@ private static Config parse(final String content, Config baseConfig)
c.fromText(content);
return c;
}
+
+ @Test
+ public void testTimeUnit() throws ConfigInvalidException {
+ assertEquals(0, parseTime("0", MILLISECONDS));
+ assertEquals(2, parseTime("2ms", MILLISECONDS));
+ assertEquals(200, parseTime("200 milliseconds", MILLISECONDS));
+
+ assertEquals(0, parseTime("0s", SECONDS));
+ assertEquals(2, parseTime("2s", SECONDS));
+ assertEquals(231, parseTime("231sec", SECONDS));
+ assertEquals(1, parseTime("1second", SECONDS));
+ assertEquals(300, parseTime("300 seconds", SECONDS));
+
+ assertEquals(2, parseTime("2m", MINUTES));
+ assertEquals(2, parseTime("2min", MINUTES));
+ assertEquals(1, parseTime("1 minute", MINUTES));
+ assertEquals(10, parseTime("10 minutes", MINUTES));
+
+ assertEquals(5, parseTime("5h", HOURS));
+ assertEquals(5, parseTime("5hr", HOURS));
+ assertEquals(1, parseTime("1hour", HOURS));
+ assertEquals(48, parseTime("48hours", HOURS));
+
+ assertEquals(5, parseTime("5 h", HOURS));
+ assertEquals(5, parseTime("5 hr", HOURS));
+ assertEquals(1, parseTime("1 hour", HOURS));
+ assertEquals(48, parseTime("48 hours", HOURS));
+ assertEquals(48, parseTime("48 \t \r hours", HOURS));
+
+ assertEquals(4, parseTime("4d", DAYS));
+ assertEquals(1, parseTime("1day", DAYS));
+ assertEquals(14, parseTime("14days", DAYS));
+
+ assertEquals(7, parseTime("1w", DAYS));
+ assertEquals(7, parseTime("1week", DAYS));
+ assertEquals(14, parseTime("2w", DAYS));
+ assertEquals(14, parseTime("2weeks", DAYS));
+
+ assertEquals(30, parseTime("1mon", DAYS));
+ assertEquals(30, parseTime("1month", DAYS));
+ assertEquals(60, parseTime("2mon", DAYS));
+ assertEquals(60, parseTime("2months", DAYS));
+
+ assertEquals(365, parseTime("1y", DAYS));
+ assertEquals(365, parseTime("1year", DAYS));
+ assertEquals(365 * 2, parseTime("2years", DAYS));
+ }
+
+ private long parseTime(String value, TimeUnit unit)
+ throws ConfigInvalidException {
+ Config c = parse("[a]\na=" + value + "\n");
+ return c.getTimeUnit("a", null, "a", 0, unit);
+ }
+
+ @Test
+ public void testTimeUnitDefaultValue() throws ConfigInvalidException {
+ // value not present
+ assertEquals(20, parse("[a]\na=0\n").getTimeUnit("a", null, "b", 20,
+ MILLISECONDS));
+ // value is empty
+ assertEquals(20, parse("[a]\na=\" \"\n").getTimeUnit("a", null, "a", 20,
+ MILLISECONDS));
+
+ // value is not numeric
+ assertEquals(20, parse("[a]\na=test\n").getTimeUnit("a", null, "a", 20,
+ MILLISECONDS));
+ }
+
+ @Test
+ public void testTimeUnitInvalid() throws ConfigInvalidException {
+ expectedEx.expect(IllegalArgumentException.class);
+ expectedEx
+ .expectMessage("Invalid time unit value: a.a=1 monttthhh");
+ parseTime("1 monttthhh", DAYS);
+ }
+
+ @Test
+ public void testTimeUnitInvalidWithSection() throws ConfigInvalidException {
+ Config c = parse("[a \"b\"]\na=1 monttthhh\n");
+ expectedEx.expect(IllegalArgumentException.class);
+ expectedEx.expectMessage("Invalid time unit value: a.b.a=1 monttthhh");
+ c.getTimeUnit("a", "b", "a", 0, DAYS);
+ }
+
+ @Test
+ public void testTimeUnitNegative() throws ConfigInvalidException {
+ expectedEx.expect(IllegalArgumentException.class);
+ parseTime("-1", MILLISECONDS);
+ }
}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/RepositoryCacheConfigTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/RepositoryCacheConfigTest.java
new file mode 100644
index 0000000..52cc9fb
--- /dev/null
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/RepositoryCacheConfigTest.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 2016 Ericsson
+ * 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.lib;
+
+import static org.eclipse.jgit.lib.RepositoryCacheConfig.AUTO_CLEANUP_DELAY;
+import static org.eclipse.jgit.lib.RepositoryCacheConfig.NO_CLEANUP;
+import static org.junit.Assert.assertEquals;
+
+import java.util.concurrent.TimeUnit;
+
+import org.eclipse.jgit.errors.ConfigInvalidException;
+import org.junit.Before;
+import org.junit.Test;
+
+public class RepositoryCacheConfigTest {
+
+ private RepositoryCacheConfig config;
+
+ @Before
+ public void setUp() {
+ config = new RepositoryCacheConfig();
+ }
+
+ @Test
+ public void testDefaultValues() {
+ assertEquals(TimeUnit.HOURS.toMillis(1), config.getExpireAfter());
+ assertEquals(config.getExpireAfter() / 10, config.getCleanupDelay());
+ }
+
+ @Test
+ public void testCleanupDelay() {
+ config.setCleanupDelay(TimeUnit.HOURS.toMillis(1));
+ assertEquals(TimeUnit.HOURS.toMillis(1), config.getCleanupDelay());
+ }
+
+ @Test
+ public void testAutoCleanupDelay() {
+ config.setExpireAfter(TimeUnit.MINUTES.toMillis(20));
+ config.setCleanupDelay(AUTO_CLEANUP_DELAY);
+ assertEquals(TimeUnit.MINUTES.toMillis(20), config.getExpireAfter());
+ assertEquals(config.getExpireAfter() / 10, config.getCleanupDelay());
+ }
+
+ @Test
+ public void testAutoCleanupDelayShouldBeMax10minutes() {
+ config.setExpireAfter(TimeUnit.HOURS.toMillis(10));
+ assertEquals(TimeUnit.HOURS.toMillis(10), config.getExpireAfter());
+ assertEquals(TimeUnit.MINUTES.toMillis(10), config.getCleanupDelay());
+ }
+
+ @Test
+ public void testDisabledCleanupDelay() {
+ config.setCleanupDelay(NO_CLEANUP);
+ assertEquals(NO_CLEANUP, config.getCleanupDelay());
+ }
+
+ @Test
+ public void testFromConfig() throws ConfigInvalidException {
+ Config otherConfig = new Config();
+ otherConfig.fromText("[core]\nrepositoryCacheExpireAfter=1000\n"
+ + "repositoryCacheCleanupDelay=500");
+ config.fromConfig(otherConfig);
+ assertEquals(1000, config.getExpireAfter());
+ assertEquals(500, config.getCleanupDelay());
+ }
+}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/RepositoryCacheTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/RepositoryCacheTest.java
index a1cec2d..6bea320 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/RepositoryCacheTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/RepositoryCacheTest.java
@@ -195,17 +195,81 @@ public void testRepositoryUsageCountWithRegisteredRepository() {
assertEquals(0, ((Repository) db).useCnt.get());
}
- public void testRepositoryUnregisteringWhenClosing() throws Exception {
+ @Test
+ public void testRepositoryNotUnregisteringWhenClosing() throws Exception {
FileKey loc = FileKey.exact(db.getDirectory(), db.getFS());
Repository d2 = RepositoryCache.open(loc);
assertEquals(1, d2.useCnt.get());
assertThat(RepositoryCache.getRegisteredKeys(),
hasItem(FileKey.exact(db.getDirectory(), db.getFS())));
assertEquals(1, RepositoryCache.getRegisteredKeys().size());
-
d2.close();
-
assertEquals(0, d2.useCnt.get());
- assertEquals(0, RepositoryCache.getRegisteredKeys().size());
+ assertEquals(1, RepositoryCache.getRegisteredKeys().size());
+ assertTrue(RepositoryCache.isCached(d2));
+ }
+
+ @Test
+ public void testRepositoryUnregisteringWhenExpired() throws Exception {
+ Repository repoA = createBareRepository();
+ Repository repoB = createBareRepository();
+ Repository repoC = createBareRepository();
+ RepositoryCache.register(repoA);
+ RepositoryCache.register(repoB);
+ RepositoryCache.register(repoC);
+
+ assertEquals(3, RepositoryCache.getRegisteredKeys().size());
+ assertTrue(RepositoryCache.isCached(repoA));
+ assertTrue(RepositoryCache.isCached(repoB));
+ assertTrue(RepositoryCache.isCached(repoC));
+
+ // fake that repoA was closed more than 1 hour ago (default expiration
+ // time)
+ repoA.close();
+ repoA.closedAt.set(System.currentTimeMillis() - 65 * 60 * 1000);
+ // close repoB but this one will not be expired
+ repoB.close();
+
+ assertEquals(3, RepositoryCache.getRegisteredKeys().size());
+ assertTrue(RepositoryCache.isCached(repoA));
+ assertTrue(RepositoryCache.isCached(repoB));
+ assertTrue(RepositoryCache.isCached(repoC));
+
+ RepositoryCache.clearExpired();
+
+ assertEquals(2, RepositoryCache.getRegisteredKeys().size());
+ assertFalse(RepositoryCache.isCached(repoA));
+ assertTrue(RepositoryCache.isCached(repoB));
+ assertTrue(RepositoryCache.isCached(repoC));
+ }
+
+ @Test
+ public void testReconfigure() throws InterruptedException {
+ RepositoryCache.register(db);
+ assertTrue(RepositoryCache.isCached(db));
+ db.close();
+ assertTrue(RepositoryCache.isCached(db));
+
+ // Actually, we would only need to validate that
+ // WorkQueue.getExecutor().scheduleWithFixedDelay is called with proper
+ // values but since we do not have a mock library, we test
+ // reconfiguration from a black box perspective. I.e. reconfigure
+ // expireAfter and cleanupDelay to 1 ms and wait until the Repository
+ // is evicted to prove that reconfiguration worked.
+ RepositoryCacheConfig config = new RepositoryCacheConfig();
+ config.setExpireAfter(1);
+ config.setCleanupDelay(1);
+ config.install();
+
+ // Instead of using a fixed waiting time, start with small and increase:
+ // sleep 1, 2, 4, 8, 16, ..., 1024 ms
+ // This wait will time out after 2048 ms
+ for (int i = 0; i <= 10; i++) {
+ Thread.sleep(1 << i);
+ if (!RepositoryCache.isCached(db)) {
+ return;
+ }
+ }
+ fail("Repository should have been evicted from cache");
}
}
diff --git a/org.eclipse.jgit/.settings/.api_filters b/org.eclipse.jgit/.settings/.api_filters
index c0dbc77..bacfb33 100644
--- a/org.eclipse.jgit/.settings/.api_filters
+++ b/org.eclipse.jgit/.settings/.api_filters
@@ -1,5 +1,13 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<component id="org.eclipse.jgit" version="2">
+ <resource path="META-INF/MANIFEST.MF">
+ <filter id="924844039">
+ <message_arguments>
+ <message_argument value="4.4.1"/>
+ <message_argument value="4.4.0"/>
+ </message_arguments>
+ </filter>
+ </resource>
<resource path="src/org/eclipse/jgit/attributes/AttributesNode.java" type="org.eclipse.jgit.attributes.AttributesNode">
<filter comment="moved to new AttributesManager" id="338792546">
<message_arguments>
diff --git a/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties b/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties
index b71e48b..21fbaa4 100644
--- a/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties
+++ b/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties
@@ -185,6 +185,7 @@
corruptObjectTruncatedInName=truncated in name
corruptObjectTruncatedInObjectId=truncated in object id
corruptObjectZeroId=entry points to null SHA-1
+corruptUseCnt=close() called when useCnt is already zero
couldNotCheckOutBecauseOfConflicts=Could not check out because of conflicts
couldNotDeleteLockFileShouldNotHappen=Could not delete lock file. Should not happen
couldNotDeleteTemporaryIndexFileShouldNotHappen=Could not delete temporary index file. Should not happen
@@ -339,7 +340,6 @@
invalidIdLength=Invalid id length {0}; should be {1}
invalidIgnoreParamSubmodule=Found invalid ignore param for submodule {0}.
invalidIgnoreRule=Exception caught while parsing ignore rule ''{0}''.
-invalidIncludedPathInConfigFile=Invalid included path in config file: {0}
invalidIntegerValue=Invalid integer value: {0}.{1}={2}
invalidKey=Invalid key: {0}
invalidLineInConfigFile=Invalid line in config file
@@ -361,6 +361,8 @@
invalidStageForPath=Invalid stage {0} for path {1}
invalidTagOption=Invalid tag option: {0}
invalidTimeout=Invalid timeout: {0}
+invalidTimeUnitValue2=Invalid time unit value: {0}.{1}={2}
+invalidTimeUnitValue3=Invalid time unit value: {0}.{1}.{2}={3}
invalidURL=Invalid URL {0}
invalidWildcards=Invalid wildcards {0}
invalidRefSpec=Invalid refspec {0}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java
index 7c10178..b7ef085 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java
@@ -245,6 +245,7 @@ public static JGitText get() {
/***/ public String corruptObjectTruncatedInObjectId;
/***/ public String corruptObjectZeroId;
/***/ public String corruptPack;
+ /***/ public String corruptUseCnt;
/***/ public String couldNotCheckOutBecauseOfConflicts;
/***/ public String couldNotDeleteLockFileShouldNotHappen;
/***/ public String couldNotDeleteTemporaryIndexFileShouldNotHappen;
@@ -398,7 +399,6 @@ public static JGitText get() {
/***/ public String invalidIdLength;
/***/ public String invalidIgnoreParamSubmodule;
/***/ public String invalidIgnoreRule;
- /***/ public String invalidIncludedPathInConfigFile;
/***/ public String invalidIntegerValue;
/***/ public String invalidKey;
/***/ public String invalidLineInConfigFile;
@@ -419,6 +419,8 @@ public static JGitText get() {
/***/ public String invalidStageForPath;
/***/ public String invalidTagOption;
/***/ public String invalidTimeout;
+ /***/ public String invalidTimeUnitValue2;
+ /***/ public String invalidTimeUnitValue3;
/***/ public String invalidURL;
/***/ public String invalidWildcards;
/***/ public String invalidRefSpec;
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/BatchingProgressMonitor.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/BatchingProgressMonitor.java
index 39856c0..a3859ab 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/BatchingProgressMonitor.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/BatchingProgressMonitor.java
@@ -43,55 +43,11 @@
package org.eclipse.jgit.lib;
-import java.util.concurrent.Executors;
import java.util.concurrent.Future;
-import java.util.concurrent.ScheduledThreadPoolExecutor;
-import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;
/** ProgressMonitor that batches update events. */
public abstract class BatchingProgressMonitor implements ProgressMonitor {
- private static final ScheduledThreadPoolExecutor alarmQueue;
-
- static final Object alarmQueueKiller;
-
- static {
- // To support garbage collection, start our thread but
- // swap out the thread factory. When our class is GC'd
- // the alarmQueueKiller will finalize and ask the executor
- // to shutdown, ending the worker.
- //
- int threads = 1;
- alarmQueue = new ScheduledThreadPoolExecutor(threads,
- new ThreadFactory() {
- private final ThreadFactory baseFactory = Executors
- .defaultThreadFactory();
-
- public Thread newThread(Runnable taskBody) {
- Thread thr = baseFactory.newThread(taskBody);
- thr.setName("JGit-AlarmQueue"); //$NON-NLS-1$
- thr.setDaemon(true);
- return thr;
- }
- });
- alarmQueue.setContinueExistingPeriodicTasksAfterShutdownPolicy(false);
- alarmQueue.setExecuteExistingDelayedTasksAfterShutdownPolicy(false);
- alarmQueue.prestartAllCoreThreads();
-
- // Now that the threads are running, its critical to swap out
- // our own thread factory for one that isn't in the ClassLoader.
- // This allows the class to GC.
- //
- alarmQueue.setThreadFactory(Executors.defaultThreadFactory());
-
- alarmQueueKiller = new Object() {
- @Override
- protected void finalize() {
- alarmQueue.shutdownNow();
- }
- };
- }
-
private long delayStartTime;
private TimeUnit delayStartUnit = TimeUnit.MILLISECONDS;
@@ -219,7 +175,7 @@ private static class Task implements Runnable {
void delay(long time, TimeUnit unit) {
display = false;
- timerFuture = alarmQueue.schedule(this, time, unit);
+ timerFuture = WorkQueue.getExecutor().schedule(this, time, unit);
}
public void run() {
@@ -254,7 +210,8 @@ void update(BatchingProgressMonitor pm, int completed) {
private void restartTimer() {
display = false;
- timerFuture = alarmQueue.schedule(this, 1, TimeUnit.SECONDS);
+ timerFuture = WorkQueue.getExecutor().schedule(this, 1,
+ TimeUnit.SECONDS);
}
void end(BatchingProgressMonitor pm) {
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 b8eba3a..1e1d147 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/Config.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Config.java
@@ -52,13 +52,17 @@
package org.eclipse.jgit.lib;
import java.io.File;
+import java.io.FileNotFoundException;
import java.io.IOException;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Set;
+import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
import org.eclipse.jgit.errors.ConfigInvalidException;
import org.eclipse.jgit.events.ConfigChangedEvent;
@@ -79,7 +83,7 @@ public class Config {
private static final long KiB = 1024;
private static final long MiB = 1024 * KiB;
private static final long GiB = 1024 * MiB;
- private static final int MAX_DEPTH = 999;
+ private static final int MAX_DEPTH = 10;
/** the change listeners */
private final ListenerList listeners = new ListenerList();
@@ -490,6 +494,123 @@ public String getString(final String section, String subsection,
}
/**
+ * Parse a numerical time unit, such as "1 minute", from the configuration.
+ *
+ * @param section
+ * section the key is in.
+ * @param subsection
+ * subsection the key is in, or null if not in a subsection.
+ * @param name
+ * the key name.
+ * @param defaultValue
+ * default value to return if no value was present.
+ * @param wantUnit
+ * the units of {@code defaultValue} and the return value, as
+ * well as the units to assume if the value does not contain an
+ * indication of the units.
+ * @return the value, or {@code defaultValue} if not set, expressed in
+ * {@code units}.
+ * @since 4.4
+ */
+ public long getTimeUnit(String section, String subsection, String name,
+ long defaultValue, TimeUnit wantUnit) {
+ String valueString = getString(section, subsection, name);
+
+ if (valueString == null) {
+ return defaultValue;
+ }
+
+ String s = valueString.trim();
+ if (s.length() == 0) {
+ return defaultValue;
+ }
+
+ if (s.startsWith("-")/* negative */) { //$NON-NLS-1$
+ throw notTimeUnit(section, subsection, name, valueString);
+ }
+
+ Matcher m = Pattern.compile("^(0|[1-9][0-9]*)\\s*(.*)$") //$NON-NLS-1$
+ .matcher(valueString);
+ if (!m.matches()) {
+ return defaultValue;
+ }
+
+ String digits = m.group(1);
+ String unitName = m.group(2).trim();
+
+ TimeUnit inputUnit;
+ int inputMul;
+
+ if (unitName.isEmpty()) {
+ inputUnit = wantUnit;
+ inputMul = 1;
+
+ } else if (match(unitName, "ms", "milliseconds")) { //$NON-NLS-1$ //$NON-NLS-2$
+ inputUnit = TimeUnit.MILLISECONDS;
+ inputMul = 1;
+
+ } else if (match(unitName, "s", "sec", "second", "seconds")) { //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$
+ inputUnit = TimeUnit.SECONDS;
+ inputMul = 1;
+
+ } else if (match(unitName, "m", "min", "minute", "minutes")) { //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$
+ inputUnit = TimeUnit.MINUTES;
+ inputMul = 1;
+
+ } else if (match(unitName, "h", "hr", "hour", "hours")) { //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$
+ inputUnit = TimeUnit.HOURS;
+ inputMul = 1;
+
+ } else if (match(unitName, "d", "day", "days")) { //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+ inputUnit = TimeUnit.DAYS;
+ inputMul = 1;
+
+ } else if (match(unitName, "w", "week", "weeks")) { //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+ inputUnit = TimeUnit.DAYS;
+ inputMul = 7;
+
+ } else if (match(unitName, "mon", "month", "months")) { //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+ inputUnit = TimeUnit.DAYS;
+ inputMul = 30;
+
+ } else if (match(unitName, "y", "year", "years")) { //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+ inputUnit = TimeUnit.DAYS;
+ inputMul = 365;
+
+ } else {
+ throw notTimeUnit(section, subsection, name, valueString);
+ }
+
+ try {
+ return wantUnit.convert(Long.parseLong(digits) * inputMul,
+ inputUnit);
+ } catch (NumberFormatException nfe) {
+ throw notTimeUnit(section, subsection, unitName, valueString);
+ }
+ }
+
+ private static boolean match(final String a, final String... cases) {
+ for (final String b : cases) {
+ if (b != null && b.equalsIgnoreCase(a)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private IllegalArgumentException notTimeUnit(String section,
+ String subsection, String name, String valueString) {
+ if (subsection != null) {
+ return new IllegalArgumentException(
+ MessageFormat.format(JGitText.get().invalidTimeUnitValue3,
+ section, subsection, name, valueString));
+ }
+ return new IllegalArgumentException(
+ MessageFormat.format(JGitText.get().invalidTimeUnitValue2,
+ section, name, valueString));
+ }
+
+ /**
* @param section
* section to search for.
* @return set of all subsections of specified section within this
@@ -1127,9 +1248,15 @@ private void addIncludedConfig(final List<ConfigLine> newEntries,
decoded = RawParseUtils.decode(bytes);
}
newEntries.addAll(fromTextRecurse(decoded, depth + 1));
+ } catch (FileNotFoundException fnfe) {
+ if (path.exists()) {
+ throw new ConfigInvalidException(MessageFormat
+ .format(JGitText.get().cannotReadFile, path), fnfe);
+ }
} catch (IOException ioe) {
- throw new ConfigInvalidException(MessageFormat.format(
- JGitText.get().invalidIncludedPathInConfigFile, path));
+ throw new ConfigInvalidException(
+ MessageFormat.format(JGitText.get().cannotReadFile, path),
+ ioe);
}
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/Repository.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Repository.java
index 5703ddd..7ec2499 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/Repository.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Repository.java
@@ -63,6 +63,7 @@
import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicLong;
import org.eclipse.jgit.annotations.NonNull;
import org.eclipse.jgit.annotations.Nullable;
@@ -93,6 +94,8 @@
import org.eclipse.jgit.util.RawParseUtils;
import org.eclipse.jgit.util.SystemReader;
import org.eclipse.jgit.util.io.SafeBufferedOutputStream;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
/**
* Represents a Git repository.
@@ -103,6 +106,8 @@
* This class is thread-safe.
*/
public abstract class Repository implements AutoCloseable {
+ private static Logger LOG = LoggerFactory.getLogger(Repository.class);
+
private static final ListenerList globalListeners = new ListenerList();
/** @return the global listener list observing all events in this JVM. */
@@ -113,6 +118,8 @@ public static ListenerList getGlobalListenerList() {
/** Use counter */
final AtomicInteger useCnt = new AtomicInteger(1);
+ final AtomicLong closedAt = new AtomicLong();
+
/** Metadata directory holding the repository's critical files. */
private final File gitDir;
@@ -863,9 +870,24 @@ public void incrementOpen() {
/** Decrement the use count, and maybe close resources. */
public void close() {
- if (useCnt.decrementAndGet() == 0) {
- doClose();
- RepositoryCache.unregister(this);
+ int newCount = useCnt.decrementAndGet();
+ if (newCount == 0) {
+ if (RepositoryCache.isCached(this)) {
+ closedAt.set(System.currentTimeMillis());
+ } else {
+ doClose();
+ }
+ } else if (newCount == -1) {
+ // should not happen, only log when useCnt became negative to
+ // minimize number of log entries
+ LOG.warn(JGitText.get().corruptUseCnt);
+ if (LOG.isDebugEnabled()) {
+ IllegalStateException e = new IllegalStateException();
+ LOG.debug("", e); //$NON-NLS-1$
+ }
+ if (RepositoryCache.isCached(this)) {
+ closedAt.set(System.currentTimeMillis());
+ }
}
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RepositoryCache.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/RepositoryCache.java
index 22b5fcd..29ef084 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RepositoryCache.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/RepositoryCache.java
@@ -52,17 +52,26 @@
import java.util.Iterator;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.ScheduledThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+import org.eclipse.jgit.annotations.NonNull;
import org.eclipse.jgit.errors.RepositoryNotFoundException;
import org.eclipse.jgit.internal.storage.file.FileRepository;
import org.eclipse.jgit.util.FS;
import org.eclipse.jgit.util.IO;
import org.eclipse.jgit.util.RawParseUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
/** Cache of active {@link Repository} instances. */
public class RepositoryCache {
private static final RepositoryCache cache = new RepositoryCache();
+ private final static Logger LOG = LoggerFactory
+ .getLogger(RepositoryCache.class);
+
/**
* Open an existing repository, reusing a cached instance if possible.
* <p>
@@ -138,10 +147,10 @@ public static void register(final Repository db) {
* @param db
* repository to unregister.
*/
- public static void close(final Repository db) {
+ public static void close(@NonNull final Repository db) {
if (db.getDirectory() != null) {
FileKey key = FileKey.exact(db.getDirectory(), db.getFS());
- cache.unregisterAndCloseRepository(key);
+ cache.unregisterAndCloseRepository(key, db);
}
}
@@ -187,20 +196,69 @@ public static Collection<Key> getRegisteredKeys() {
return cache.getKeys();
}
+ static boolean isCached(@NonNull Repository repo) {
+ File gitDir = repo.getDirectory();
+ if (gitDir == null) {
+ return false;
+ }
+ FileKey key = new FileKey(gitDir, repo.getFS());
+ Reference<Repository> repoRef = cache.cacheMap.get(key);
+ return repoRef != null && repoRef.get() == repo;
+ }
+
/** Unregister all repositories from the cache. */
public static void clear() {
cache.clearAll();
}
+ static void clearExpired() {
+ cache.clearAllExpired();
+ }
+
+ static void reconfigure(RepositoryCacheConfig repositoryCacheConfig) {
+ cache.configureEviction(repositoryCacheConfig);
+ }
+
private final ConcurrentHashMap<Key, Reference<Repository>> cacheMap;
private final Lock[] openLocks;
+ private ScheduledFuture<?> cleanupTask;
+
+ private volatile long expireAfter;
+
private RepositoryCache() {
cacheMap = new ConcurrentHashMap<Key, Reference<Repository>>();
openLocks = new Lock[4];
- for (int i = 0; i < openLocks.length; i++)
+ for (int i = 0; i < openLocks.length; i++) {
openLocks[i] = new Lock();
+ }
+ configureEviction(new RepositoryCacheConfig());
+ }
+
+ private void configureEviction(
+ RepositoryCacheConfig repositoryCacheConfig) {
+ expireAfter = repositoryCacheConfig.getExpireAfter();
+ ScheduledThreadPoolExecutor scheduler = WorkQueue.getExecutor();
+ synchronized (scheduler) {
+ if (cleanupTask != null) {
+ cleanupTask.cancel(false);
+ }
+ long delay = repositoryCacheConfig.getCleanupDelay();
+ if (delay == RepositoryCacheConfig.NO_CLEANUP) {
+ return;
+ }
+ cleanupTask = scheduler.scheduleWithFixedDelay(new Runnable() {
+ @Override
+ public void run() {
+ try {
+ cache.clearAllExpired();
+ } catch (Throwable e) {
+ LOG.error(e.getMessage(), e);
+ }
+ }
+ }, delay, delay, TimeUnit.MILLISECONDS);
+ }
}
@SuppressWarnings("resource")
@@ -239,10 +297,20 @@ private Repository unregisterRepository(final Key location) {
return oldRef != null ? oldRef.get() : null;
}
- private void unregisterAndCloseRepository(final Key location) {
- Repository oldDb = unregisterRepository(location);
- if (oldDb != null) {
- oldDb.close();
+ private boolean isExpired(Repository db) {
+ return db != null && db.useCnt.get() == 0
+ && (System.currentTimeMillis() - db.closedAt.get() > expireAfter);
+ }
+
+ private void unregisterAndCloseRepository(final Key location,
+ Repository db) {
+ synchronized (lockFor(location)) {
+ if (isExpired(db)) {
+ Repository oldDb = unregisterRepository(location);
+ if (oldDb != null) {
+ oldDb.close();
+ }
+ }
}
}
@@ -250,6 +318,15 @@ private Collection<Key> getKeys() {
return new ArrayList<Key>(cacheMap.keySet());
}
+ private void clearAllExpired() {
+ for (Reference<Repository> ref : cacheMap.values()) {
+ Repository db = ref.get();
+ if (isExpired(db)) {
+ RepositoryCache.close(db);
+ }
+ }
+ }
+
private void clearAll() {
for (int stage = 0; stage < 2; stage++) {
for (Iterator<Map.Entry<Key, Reference<Repository>>> i = cacheMap
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RepositoryCacheConfig.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/RepositoryCacheConfig.java
new file mode 100644
index 0000000..428dea3
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/RepositoryCacheConfig.java
@@ -0,0 +1,152 @@
+/*
+ * Copyright (C) 2016 Ericsson
+ * 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.lib;
+
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Configuration parameters for JVM-wide repository cache used by JGit.
+ *
+ * @since 4.4
+ */
+public class RepositoryCacheConfig {
+
+ /**
+ * Set cleanupDelayMillis to this value in order to switch off time-based
+ * cache eviction. The JVM can still expire cache entries when heap memory
+ * runs low.
+ */
+ public static final long NO_CLEANUP = 0;
+
+ /**
+ * Set cleanupDelayMillis to this value in order to auto-set it to minimum
+ * of 1/10 of expireAfterMillis and 10 minutes
+ */
+ public static final long AUTO_CLEANUP_DELAY = -1;
+
+ private long expireAfterMillis;
+
+ private long cleanupDelayMillis;
+
+ /** Create a default configuration. */
+ public RepositoryCacheConfig() {
+ expireAfterMillis = TimeUnit.HOURS.toMillis(1);
+ cleanupDelayMillis = AUTO_CLEANUP_DELAY;
+ }
+
+ /**
+ * @return the time an unused repository should expired and be evicted from
+ * the RepositoryCache in milliseconds. <b>Default is 1 hour.</b>
+ */
+ public long getExpireAfter() {
+ return expireAfterMillis;
+ }
+
+ /**
+ * @param expireAfterMillis
+ * the time an unused repository should expired and be evicted
+ * from the RepositoryCache in milliseconds.
+ */
+ public void setExpireAfter(long expireAfterMillis) {
+ this.expireAfterMillis = expireAfterMillis;
+ }
+
+ /**
+ * @return the delay between the periodic cleanup of expired repository in
+ * milliseconds. <b>Default is minimum of 1/10 of expireAfterMillis
+ * and 10 minutes</b>
+ */
+ public long getCleanupDelay() {
+ if (cleanupDelayMillis < 0) {
+ return Math.min(expireAfterMillis / 10,
+ TimeUnit.MINUTES.toMillis(10));
+ }
+ return cleanupDelayMillis;
+ }
+
+ /**
+ * @param cleanupDelayMillis
+ * the delay between the periodic cleanup of expired repository
+ * in milliseconds. Set it to {@link #AUTO_CLEANUP_DELAY} to
+ * automatically derive cleanup delay from expireAfterMillis.
+ * <p>
+ * Set it to {@link #NO_CLEANUP} in order to switch off cache
+ * expiration.
+ * <p>
+ * If cache expiration is switched off the JVM still can evict
+ * cache entries when the JVM is running low on available heap
+ * memory.
+ */
+ public void setCleanupDelay(long cleanupDelayMillis) {
+ this.cleanupDelayMillis = cleanupDelayMillis;
+ }
+
+ /**
+ * Update properties by setting fields from the configuration.
+ * <p>
+ * If a property is not defined in the configuration, then it is left
+ * unmodified.
+ *
+ * @param config
+ * configuration to read properties from.
+ * @return {@code this}.
+ */
+ public RepositoryCacheConfig fromConfig(Config config) {
+ setExpireAfter(
+ config.getTimeUnit("core", null, "repositoryCacheExpireAfter", //$NON-NLS-1$//$NON-NLS-2$
+ getExpireAfter(), TimeUnit.MILLISECONDS));
+ setCleanupDelay(
+ config.getTimeUnit("core", null, "repositoryCacheCleanupDelay", //$NON-NLS-1$ //$NON-NLS-2$
+ AUTO_CLEANUP_DELAY, TimeUnit.MILLISECONDS));
+ return this;
+ }
+
+ /**
+ * Install this configuration as the live settings.
+ * <p>
+ * The new configuration is applied immediately.
+ */
+ public void install() {
+ RepositoryCache.reconfigure(this);
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/WorkQueue.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/WorkQueue.java
new file mode 100644
index 0000000..9735d19
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/WorkQueue.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2008-2016, 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.lib;
+
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledThreadPoolExecutor;
+import java.util.concurrent.ThreadFactory;
+
+/**
+ * Simple work queue to run tasks in the background
+ */
+class WorkQueue {
+ private static final ScheduledThreadPoolExecutor executor;
+
+ static final Object executorKiller;
+
+ static {
+ // To support garbage collection, start our thread but
+ // swap out the thread factory. When our class is GC'd
+ // the executorKiller will finalize and ask the executor
+ // to shutdown, ending the worker.
+ //
+ int threads = 1;
+ executor = new ScheduledThreadPoolExecutor(threads,
+ new ThreadFactory() {
+ private final ThreadFactory baseFactory = Executors
+ .defaultThreadFactory();
+
+ public Thread newThread(Runnable taskBody) {
+ Thread thr = baseFactory.newThread(taskBody);
+ thr.setName("JGit-WorkQueue"); //$NON-NLS-1$
+ thr.setDaemon(true);
+ return thr;
+ }
+ });
+ executor.setRemoveOnCancelPolicy(true);
+ executor.setContinueExistingPeriodicTasksAfterShutdownPolicy(false);
+ executor.setExecuteExistingDelayedTasksAfterShutdownPolicy(false);
+ executor.prestartAllCoreThreads();
+
+ // Now that the threads are running, its critical to swap out
+ // our own thread factory for one that isn't in the ClassLoader.
+ // This allows the class to GC.
+ //
+ executor.setThreadFactory(Executors.defaultThreadFactory());
+
+ executorKiller = new Object() {
+ @Override
+ protected void finalize() {
+ executor.shutdownNow();
+ }
+ };
+ }
+
+ static ScheduledThreadPoolExecutor getExecutor() {
+ return executor;
+ }
+}
diff --git a/pom.xml b/pom.xml
index 17995b6..81c04ce 100644
--- a/pom.xml
+++ b/pom.xml
@@ -188,9 +188,10 @@
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
-
<maven.build.timestamp.format>yyyyMMddHHmm</maven.build.timestamp.format>
<bundle-manifest>${project.build.directory}/META-INF/MANIFEST.MF</bundle-manifest>
+ <!-- set JDK_HOME to JAVA_HOME path of JDK7 installation in order to compile against JDK 7 class library -->
+ <JDK_HOME>${JAVA_HOME}</JDK_HOME>
<jgit-last-release-version>4.2.0.201601211800-r</jgit-last-release-version>
<jsch-version>0.1.53</jsch-version>
@@ -264,6 +265,9 @@
<encoding>UTF-8</encoding>
<source>1.7</source>
<target>1.7</target>
+ <compilerArguments>
+ <bootclasspath>${JDK_HOME}${file.separator}jre${file.separator}lib${file.separator}rt.jar${path.separator}${JDK_HOME}${file.separator}jre${file.separator}lib${file.separator}jsse.jar${path.separator}${JDK_HOME}${file.separator}jre${file.separator}lib${file.separator}jce.jar</bootclasspath>
+ </compilerArguments>
</configuration>
</plugin>