refactored based on new LocalRepositoryCache unit tests

This commit is contained in:
Brian Long 2021-01-07 11:18:31 -05:00
parent 2c0503102f
commit 909fde9477
6 changed files with 299 additions and 143 deletions

View File

@ -21,6 +21,10 @@ public class CachedGit extends Git {
super(repo); super(repo);
} }
public String getFirstRemoteUrl() throws GitAPIException {
return this.remoteList().call().iterator().next().getURIs().iterator().next().toString();
}
@Override @Override
public void close() { public void close() {
LocalRepositoryCache.getInstance().release(this); LocalRepositoryCache.getInstance().release(this);

View File

@ -0,0 +1,15 @@
package com.inteligr8.git;
import org.slf4j.Logger;
public class DeveloperException extends RuntimeException {
private static final long serialVersionUID = 1600616227925437703L;
private static final String MESSAGE = "This is a bug that needs to be addressed by the developer";
public DeveloperException(Logger logger, Throwable cause) {
super(MESSAGE, cause);
logger.error(MESSAGE);
}
}

View File

@ -47,10 +47,6 @@ public class ExtendedGit extends CachedGit {
super(repo); super(repo);
} }
public String getFirstRemoteUrl() throws GitAPIException {
return this.remoteList().call().iterator().next().getURIs().iterator().next().toString();
}
public String getRepositoryFullyQualifiedName() throws GitAPIException, URISyntaxException { public String getRepositoryFullyQualifiedName() throws GitAPIException, URISyntaxException {
String gitUrl = this.getFirstRemoteUrl(); String gitUrl = this.getFirstRemoteUrl();
if (this.logger.isDebugEnabled()) if (this.logger.isDebugEnabled())

View File

@ -17,24 +17,25 @@ public class LRUExpiringHashMap<K extends Serializable, V> implements ListeningM
private final Logger logger = LoggerFactory.getLogger(LRUExpiringHashMap.class); private final Logger logger = LoggerFactory.getLogger(LRUExpiringHashMap.class);
private final Map<ExpiringHashKey, V> map; private final Object sync = new Object();
private final LinkedHashMap<K, ExpiringValue> map;
private final List<MapListener<K, V>> listeners = new LinkedList<MapListener<K, V>>(); private final List<MapListener<K, V>> listeners = new LinkedList<MapListener<K, V>>();
private final long expirationTimeMillis; private final long expirationTimeMillis;
public LRUExpiringHashMap(int expirationTimeInMinutes) { public LRUExpiringHashMap(int expirationTimeInMinutes) {
this.map = new LinkedHashMap<ExpiringHashKey, V>(); this.map = new LinkedHashMap<>();
this.expirationTimeMillis = expirationTimeInMinutes * 60L + 1000L; this.expirationTimeMillis = expirationTimeInMinutes * 60L + 1000L;
} }
public LRUExpiringHashMap(int expirationTimeInMinutes, int initialCapacity) { public LRUExpiringHashMap(int expirationTimeInMinutes, int initialCapacity) {
this.map = new LinkedHashMap<ExpiringHashKey, V>(initialCapacity); this.map = new LinkedHashMap<>(initialCapacity);
this.expirationTimeMillis = expirationTimeInMinutes * 60L + 1000L; this.expirationTimeMillis = expirationTimeInMinutes * 60L + 1000L;
} }
@Override @Override
public void addListener(MapListener<K, V> listener) { public void addListener(MapListener<K, V> listener) {
if (this.logger.isDebugEnabled()) if (this.logger.isDebugEnabled())
this.logger.debug("adding listener"); this.logger.debug("adding listener: " + listener.getClass());
this.listeners.add(listener); this.listeners.add(listener);
} }
@ -76,47 +77,60 @@ public class LRUExpiringHashMap<K extends Serializable, V> implements ListeningM
@Override @Override
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public V get(Object key) { public V get(Object key) {
ExpiringValue evalue;
synchronized (this.sync) {
if (!this.map.containsKey(key)) if (!this.map.containsKey(key))
return null; return null;
// remove and put to move the entry to the end of the map; helping with finding expired entries // remove and put to move the entry to the end of the map; helping with finding expired entries
V value = this.map.remove(key); evalue = this.map.remove(key);
this.map.put(new ExpiringHashKey((K)key, System.currentTimeMillis() + this.expirationTimeMillis), value); this.map.put((K)key, new ExpiringValue(evalue.getValue(), System.currentTimeMillis() + this.expirationTimeMillis));
}
for (MapListener<K, V> listener : this.listeners) for (MapListener<K, V> listener : this.listeners)
listener.accessed(new ExpiringHashMapEntry((K)key, value)); listener.accessed(new ExpiringHashMapEntry((K)key, evalue.getValue()));
return value; return evalue.getValue();
} }
@Override @Override
public V put(K key, V value) { public V put(K key, V value) {
ExpiringHashKey ehkey = new ExpiringHashKey(key, System.currentTimeMillis() + this.expirationTimeMillis); ExpiringValue evalue = new ExpiringValue(value, System.currentTimeMillis() + this.expirationTimeMillis);
V oldValue = this.map.put(ehkey, value); ExpiringValue oldValue = this.map.put(key, evalue);
for (MapListener<K, V> listener : this.listeners) for (MapListener<K, V> listener : this.listeners)
listener.added(new ExpiringHashMapEntry(key, value)); listener.added(new ExpiringHashMapEntry(key, value));
return oldValue; return oldValue == null ? null : oldValue.getValue();
} }
@Override @Override
public void clear() { public void clear() {
for (Entry<ExpiringHashKey, V> entry : this.map.entrySet()) { List<ExpiringHashMapEntry> entries = new LinkedList<>();
for (MapListener<K, V> listener : this.listeners)
listener.cleared(new ExpiringHashMapEntry(entry.getKey().getKey(), entry.getValue())); synchronized (this.sync) {
for (Entry<K, ExpiringValue> entry : this.map.entrySet())
entries.add(new ExpiringHashMapEntry(entry.getKey(), entry.getValue().getValue()));
this.map.clear();
} }
this.map.clear(); for (ExpiringHashMapEntry entry : entries)
for (MapListener<K, V> listener : this.listeners)
listener.cleared(entry);
} }
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
@Override @Override
public V remove(Object key) { public V remove(Object key) {
ExpiringValue evalue;
synchronized (this.sync) {
if (!this.map.containsKey(key)) if (!this.map.containsKey(key))
return null; return null;
evalue = this.map.remove(key);
}
V value = this.map.remove(key);
for (MapListener<K, V> listener : this.listeners) for (MapListener<K, V> listener : this.listeners)
listener.removed(new ExpiringHashMapEntry((K)key, value)); listener.removed(new ExpiringHashMapEntry((K)key, evalue.getValue()));
return value; return evalue.getValue();
} }
@Override @Override
@ -135,28 +149,49 @@ public class LRUExpiringHashMap<K extends Serializable, V> implements ListeningM
} }
public void exipriationCheck() { public void exipriationCheck() {
Iterator<Entry<ExpiringHashKey, V>> i = this.map.entrySet().iterator(); // since these should be order, we could break out of this loop once we reach an unexpired entry
for (Entry<ExpiringHashKey, V> entry = i.next(); i.hasNext(); entry = i.next()) { List<ExpiringHashMapEntry> entries = new LinkedList<>();
if (entry.getKey().isExpired()) {
synchronized (this.sync) {
Iterator<Entry<K, ExpiringValue>> i = this.map.entrySet().iterator();
for (Entry<K, ExpiringValue> entry = i.next(); i.hasNext(); entry = i.next()) {
if (entry.getValue().isExpired()) {
i.remove(); i.remove();
entries.add(new ExpiringHashMapEntry(entry.getKey(), entry.getValue().getValue()));
} else {
// ordered map; ordered by time of entry
// since expiration timers are constant, we can skip looping
break;
}
}
}
for (ExpiringHashMapEntry entry : entries)
for (MapListener<K, V> listener : this.listeners) for (MapListener<K, V> listener : this.listeners)
if (listener instanceof ExpiringMapListener) if (listener instanceof ExpiringMapListener)
((ExpiringMapListener<K, V>)listener).expired(new ExpiringHashMapEntry(entry.getKey().getKey(), entry.getValue())); ((ExpiringMapListener<K, V>)listener).expired(entry);
}
}
} }
public void expire(K key) { public void expire(K key) {
if (this.logger.isDebugEnabled()) if (this.logger.isDebugEnabled())
this.logger.debug("expiring key from map: " + key); this.logger.debug("expiring key from map: " + key);
ExpiringValue evalue;
synchronized (this.sync) {
if (!this.map.containsKey(key)) if (!this.map.containsKey(key))
return; return;
evalue = this.map.remove(key);
}
V value = this.map.remove(key);
for (MapListener<K, V> listener : this.listeners) for (MapListener<K, V> listener : this.listeners)
if (listener instanceof ExpiringMapListener) if (listener instanceof ExpiringMapListener)
((ExpiringMapListener<K, V>)listener).expired(new ExpiringHashMapEntry(key, value)); ((ExpiringMapListener<K, V>)listener).expired(new ExpiringHashMapEntry(key, evalue.getValue()));
}
@Override
public String toString() {
return this.map.toString();
} }
@ -207,44 +242,27 @@ public class LRUExpiringHashMap<K extends Serializable, V> implements ListeningM
} }
private class ExpiringHashKey implements Serializable { private class ExpiringValue {
private static final long serialVersionUID = -6511298315143655313L;
private long expirationTimeInMillis; private long expirationTimeInMillis;
private K key; private V value;
public ExpiringHashKey(K key, long expirationTimeInMillis) { public ExpiringValue(V value, long expirationTimeInMillis) {
this.key = key; this.value = value;
this.expirationTimeInMillis = expirationTimeInMillis; this.expirationTimeInMillis = expirationTimeInMillis;
} }
public K getKey() { public V getValue() {
return this.key; return this.value;
} }
public boolean isExpired() { public boolean isExpired() {
return this.expirationTimeInMillis <= System.currentTimeMillis(); return this.expirationTimeInMillis <= System.currentTimeMillis();
} }
@Override
public int hashCode() {
return this.key.hashCode();
}
@Override @Override
public String toString() { public String toString() {
return this.key.toString(); return this.value.toString();
}
@Override
@SuppressWarnings("unchecked")
public boolean equals(Object obj) {
if (obj instanceof LRUExpiringHashMap.ExpiringHashKey) {
return this.key.equals(((ExpiringHashKey)obj).key);
} else {
return this.key.equals(obj);
}
} }
} }

View File

@ -2,14 +2,20 @@ package com.inteligr8.git;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.net.URISyntaxException;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry; import java.util.Map.Entry;
import java.util.UUID; import java.util.UUID;
import java.util.concurrent.Semaphore;
import org.eclipse.jgit.api.CloneCommand; import org.eclipse.jgit.api.CloneCommand;
import org.eclipse.jgit.api.Git; import org.eclipse.jgit.api.Git;
import org.eclipse.jgit.api.ResetCommand.ResetType;
import org.eclipse.jgit.api.errors.GitAPIException; import org.eclipse.jgit.api.errors.GitAPIException;
import org.eclipse.jgit.api.errors.InvalidRemoteException; import org.eclipse.jgit.api.errors.InvalidRemoteException;
import org.eclipse.jgit.api.errors.RefNotFoundException; import org.eclipse.jgit.api.errors.RefNotFoundException;
import org.eclipse.jgit.api.errors.TransportException;
import org.eclipse.jgit.transport.CredentialsProvider; import org.eclipse.jgit.transport.CredentialsProvider;
import org.eclipse.jgit.util.FileUtils; import org.eclipse.jgit.util.FileUtils;
import org.slf4j.Logger; import org.slf4j.Logger;
@ -23,15 +29,18 @@ public class LocalRepositoryCache {
return INSTANCE; return INSTANCE;
} }
private final Logger logger = LoggerFactory.getLogger(LocalRepositoryCache.class); private final Logger logger = LoggerFactory.getLogger(LocalRepositoryCache.class);
private final LRUExpiringHashMap<String, Git> cachedGits = new LRUExpiringHashMap<String, Git>(30); private final LRUExpiringHashMap<String, CachedGit> cachedGits = new LRUExpiringHashMap<>(30);
// private final Map<String, String> gitIdsToUrls = new HashMap<String, String>(); private final Map<String, Semaphore> gitUrlSemaphores = new HashMap<>();
private final File cacheDirectory = new File(System.getProperty("java.io.tmpdir"), "git"); final File cacheDirectory = new File(System.getProperty("java.io.tmpdir"), "git");
private final int simultaneousProcessesPerGitRepo = 1;
private LocalRepositoryCache() { private LocalRepositoryCache() {
this.cacheDirectory.mkdir(); this.cacheDirectory.mkdir();
this.cachedGits.addListener(new RepositoryCacheMapListener()); this.cachedGits.addListener(new RepositoryCacheMapListener<CachedGit>());
} }
@Override @Override
@ -49,27 +58,49 @@ public class LocalRepositoryCache {
this.clear(); this.clear();
} }
public synchronized ExtendedGit acquire(String url) throws GitAPIException, InvalidRemoteException, RefNotFoundException { public CachedGit acquire(String url) throws InterruptedException, InvalidRemoteException, RefNotFoundException, TransportException, GitAPIException {
return this.acquire(url, null, null); return this.acquire(url, null, null);
} }
public synchronized ExtendedGit acquire(String url, CredentialsProvider creds) throws GitAPIException, InvalidRemoteException, RefNotFoundException { public CachedGit acquire(String url, CredentialsProvider creds) throws InterruptedException, InvalidRemoteException, RefNotFoundException, TransportException, GitAPIException {
return this.acquire(url, creds, null); return this.acquire(url, creds, null);
} }
public synchronized ExtendedGit acquire(String url, String branch) throws GitAPIException, InvalidRemoteException, RefNotFoundException { public CachedGit acquire(String url, String branch) throws InterruptedException, InvalidRemoteException, RefNotFoundException, TransportException, GitAPIException {
return this.acquire(url, null, branch); return this.acquire(url, null, branch);
} }
public synchronized ExtendedGit acquire(String url, CredentialsProvider creds, String branch) throws GitAPIException, InvalidRemoteException, RefNotFoundException { /**
*
* @param url A URL for the Git Repository.
* @param creds A username/password for the Git Repository.
* @param branch A branch name of the branch in the Git Repository to start working on.
* @return A JGit object.
* @throws InterruptedException The JVM was interrupted while the thread was waiting for another request on the same Git Repository
* @throws InvalidRemoteException The URL refers to nothing or an invalid Git Repository
* @throws RefNotFoundException The branch does not exist in the Git Repository represented by the URL
* @throws TransportException A disk or network error or hiccup occurred.
* @throws GitAPIException A Git API, library, or protocol related issue occurred
*/
public CachedGit acquire(String url, CredentialsProvider creds, String branch)
throws InterruptedException, InvalidRemoteException, RefNotFoundException, TransportException, GitAPIException {
if (this.logger.isTraceEnabled()) if (this.logger.isTraceEnabled())
this.logger.trace("acquire('" + url + "', " + creds + ", '" + branch + "')"); this.logger.trace("acquire('" + url + "', " + creds + ", '" + branch + "')");
// Git git = this.cachedGits.remove(url); Semaphore semaphore = null;
// if (git == null) { synchronized (this) {
if (this.logger.isDebugEnabled()) semaphore = this.gitUrlSemaphores.get(url);
this.logger.debug("creating temporary Git directory"); if (semaphore == null)
this.gitUrlSemaphores.put(url, semaphore = new Semaphore(this.simultaneousProcessesPerGitRepo));
}
semaphore.acquire();
try {
CachedGit git = this.cachedGits.remove(url);
if (git == null) {
File gitRepoDirectory = new File(this.cacheDirectory, UUID.randomUUID().toString() + ".git"); File gitRepoDirectory = new File(this.cacheDirectory, UUID.randomUUID().toString() + ".git");
if (this.logger.isDebugEnabled())
this.logger.debug("Git directory cache for clone: " + gitRepoDirectory);
CloneCommand clone = new CloneCommand() CloneCommand clone = new CloneCommand()
.setURI(url) .setURI(url)
@ -78,45 +109,72 @@ public class LocalRepositoryCache {
clone.setBranch(branch); clone.setBranch(branch);
if (this.logger.isDebugEnabled()) if (this.logger.isDebugEnabled())
this.logger.debug("cloning Git repository: " + url); this.logger.debug("Cloning Git repository: " + url);
ExtendedGit git = creds != null ? new CredentialedGit(clone, creds) : new ExtendedGit(clone); git = creds != null ? new CredentialedGit(clone, creds) : new ExtendedGit(clone);
if (this.logger.isInfoEnabled()) if (this.logger.isInfoEnabled())
this.logger.info("Cloned Git Repository"); this.logger.info("Cloned Git Repository: " + ((ExtendedGit)git).getRepositoryFullyQualifiedName());
return git; } else {
// git = creds != null ? new CredentialedGit(clone, creds) : new CachedGit(clone); if (this.logger.isDebugEnabled())
// this.gitIdsToUrls.put(git.getRepository().getIdentifier(), url); this.logger.debug("reseting Git");
// } else { git.reset().setMode(ResetType.HARD).call();
// if (branch != null) {
// if (this.logger.isDebugEnabled()) if (branch != null) {
// this.logger.debug("switching Git branches: " + branch); if (this.logger.isDebugEnabled())
// git.checkout().setName(branch).call(); this.logger.debug("switching Git branches: " + branch);
// } git.checkout().setName(branch).call();
//
// if (this.logger.isDebugEnabled())
// this.logger.debug("updating Git branch: " + branch);
// git.pull().call();
// }
} }
public synchronized void release(Git git) { if (this.logger.isDebugEnabled())
this.logger.debug("updating Git branch: " + git.getRepository().getBranch());
git.pull().call();
}
return git;
} catch (URISyntaxException use) {
semaphore.release();
throw new DeveloperException(this.logger, use);
} catch (IOException ie) {
semaphore.release();
throw new TransportException("A I/O issue occurred", ie);
} catch (GitAPIException gae) {
semaphore.release();
throw gae;
}
}
public void release(CachedGit git) {
if (this.logger.isTraceEnabled()) if (this.logger.isTraceEnabled())
this.logger.trace("release('" + git.getRepository().getIdentifier() + "')"); this.logger.trace("release('" + git.getRepository().getIdentifier() + "')");
// String url = this.gitIdsToUrls.get(git.getRepository().getIdentifier()); try {
// this.cachedGits.put(url, git); String url = git.getFirstRemoteUrl();
this.expunge(git);
synchronized (this) {
Semaphore semaphore = this.gitUrlSemaphores.get(url);
if (semaphore != null)
semaphore.release();
} }
public synchronized void clear() { this.cachedGits.put(url, git);
} catch (GitAPIException gae) {
if (this.logger.isDebugEnabled())
this.logger.debug("A Git repository was never released", gae);
this.logger.warn("A Git repository was never released, potentially blocking all future processing by this tool until a restart: " + git.toString());
}
}
public void clear() {
if (this.logger.isTraceEnabled()) if (this.logger.isTraceEnabled())
this.logger.trace("clear()"); this.logger.trace("clear()");
// this.cachedGits.clear(); this.cachedGits.clear();
for (Semaphore semaphore : this.gitUrlSemaphores.values())
; // FIXME interrupt all the threads; releasing semaphores will not be enough
this.gitUrlSemaphores.clear();
} }
private void expunge(Git git) { private void expunge(Git git) {
// gitIdsToUrls.remove(git.getRepository().getIdentifier());
File gitDir = git.getRepository().getDirectory(); File gitDir = git.getRepository().getDirectory();
File workingTreeDir = git.getRepository().getWorkTree(); File workingTreeDir = git.getRepository().getWorkTree();
git.getRepository().close(); git.getRepository().close();
@ -129,7 +187,6 @@ public class LocalRepositoryCache {
this.logger.warn("Failed to delete a git directory: " + gitDir); this.logger.warn("Failed to delete a git directory: " + gitDir);
if (this.logger.isDebugEnabled()) if (this.logger.isDebugEnabled())
this.logger.debug(ie.getMessage(), ie); this.logger.debug(ie.getMessage(), ie);
gitDir.deleteOnExit();
} }
try { try {
@ -140,42 +197,41 @@ public class LocalRepositoryCache {
this.logger.warn("Failed to delete a git directory: " + workingTreeDir); this.logger.warn("Failed to delete a git directory: " + workingTreeDir);
if (this.logger.isDebugEnabled()) if (this.logger.isDebugEnabled())
this.logger.debug(ie.getMessage(), ie); this.logger.debug(ie.getMessage(), ie);
workingTreeDir.deleteOnExit();
} }
if (this.logger.isInfoEnabled()) if (this.logger.isInfoEnabled())
this.logger.info("Deleted Git Repository"); this.logger.info("Deleted Git Repository: " + gitDir);
} }
private class RepositoryCacheMapListener implements ExpiringMapListener<String, Git> { private class RepositoryCacheMapListener<T extends CachedGit> implements ExpiringMapListener<String, T> {
private final Logger logger = LoggerFactory.getLogger(LocalRepositoryCache.class); private final Logger logger = LoggerFactory.getLogger(LocalRepositoryCache.class);
@Override @Override
public void accessed(Entry<String, Git> entry) { public void accessed(Entry<String, T> entry) {
} }
@Override @Override
public void added(Entry<String, Git> entry) { public void added(Entry<String, T> entry) {
// a clean one or one returned after being previously removed // a clean one or one returned after being previously removed
} }
@Override @Override
public void expired(Entry<String, Git> entry) { public void expired(Entry<String, T> entry ) {
if (this.logger.isTraceEnabled()) if (this.logger.isTraceEnabled())
this.logger.trace("expired('" + entry.getKey() + "', '" + entry.getValue().getRepository().getIdentifier() + "')"); this.logger.trace("expired('" + entry.getKey() + "', '" + entry.getValue().getRepository().getIdentifier() + "')");
expunge(entry.getValue()); expunge(entry.getValue());
} }
@Override @Override
public void removed(Entry<String, Git> entry) { public void removed(Entry<String, T> entry) {
// expected to be removed only temporarily...for use elsewhere; do not close // expected to be removed only temporarily...for use elsewhere; do not close
} }
@Override @Override
public void cleared(Entry<String, Git> entry) { public void cleared(Entry<String, T> entry) {
if (this.logger.isTraceEnabled()) if (this.logger.isTraceEnabled())
this.logger.trace("cleared('" + entry.getKey() + "', '" + entry.getValue().getRepository().getIdentifier() + "')"); this.logger.trace("cleared('" + entry.getKey() + "', '" + entry.getValue().getRepository().getIdentifier() + "')");
expunge(entry.getValue()); expunge(entry.getValue());

View File

@ -9,20 +9,27 @@ import org.eclipse.jgit.api.errors.TransportException;
import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.transport.CredentialsProvider; import org.eclipse.jgit.transport.CredentialsProvider;
import org.eclipse.jgit.transport.UsernamePasswordCredentialsProvider; import org.eclipse.jgit.transport.UsernamePasswordCredentialsProvider;
import org.junit.AfterClass; import org.junit.After;
import org.junit.Assert; import org.junit.Assert;
import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import com.inteligr8.git.LocalRepositoryCache;
public class LocalRepositoryCacheUnitTest { public class LocalRepositoryCacheUnitTest {
private final static CredentialsProvider badCreds = new UsernamePasswordCredentialsProvider("not-a-valid-user", "not-a-valid-password");
private final static CredentialsProvider gitCreds = new UsernamePasswordCredentialsProvider( private final static CredentialsProvider gitCreds = new UsernamePasswordCredentialsProvider(
System.getProperty("git.username"), System.getProperty("git.token")); System.getProperty("bitbucket.inteligr8.username"), System.getProperty("bitbucket.inteligr8.token"));
@AfterClass private int cachedFiles;
public static void cleanup() {
@Before
public void compileStats() {
this.cachedFiles = LocalRepositoryCache.getInstance().cacheDirectory.listFiles().length;
}
@After
public void cleanupAndValidate() {
LocalRepositoryCache.getInstance().clear(); LocalRepositoryCache.getInstance().clear();
Assert.assertEquals(this.cachedFiles, LocalRepositoryCache.getInstance().cacheDirectory.listFiles().length);
} }
/** /**
@ -30,7 +37,7 @@ public class LocalRepositoryCacheUnitTest {
* is thrown. It is wrapped inside a JGit TransportException. * is thrown. It is wrapped inside a JGit TransportException.
*/ */
@Test(expected = TransportException.class) @Test(expected = TransportException.class)
public void cacheBadHostRepo() throws GitAPIException { public void tryBadHostRepo() throws GitAPIException, InterruptedException {
LocalRepositoryCache.getInstance().acquire("https://host-does-not-exist.com/bmlong137/does-not-exist.git"); LocalRepositoryCache.getInstance().acquire("https://host-does-not-exist.com/bmlong137/does-not-exist.git");
} }
@ -42,7 +49,7 @@ public class LocalRepositoryCacheUnitTest {
* more like cacheNonExistentRepoAuth() than cacheNonExistentRepo(). * more like cacheNonExistentRepoAuth() than cacheNonExistentRepo().
*/ */
@Test(expected = InvalidRemoteException.class) @Test(expected = InvalidRemoteException.class)
public void cacheNonExistentRepoViaSsh() throws GitAPIException { public void tryNonExistentRepoViaSsh() throws GitAPIException, InterruptedException {
LocalRepositoryCache.getInstance().acquire("git@github.com:bmlong137/does-not-exist.git"); LocalRepositoryCache.getInstance().acquire("git@github.com:bmlong137/does-not-exist.git");
} }
@ -52,7 +59,7 @@ public class LocalRepositoryCacheUnitTest {
* which is wrapped inside a JGit TransportException. * which is wrapped inside a JGit TransportException.
*/ */
@Test(expected = TransportException.class) @Test(expected = TransportException.class)
public void cacheNonExistentRepo() throws GitAPIException { public void tryNonExistentRepo() throws GitAPIException, InterruptedException {
LocalRepositoryCache.getInstance().acquire("https://github.com/bmlong137/does-not-exist.git"); LocalRepositoryCache.getInstance().acquire("https://github.com/bmlong137/does-not-exist.git");
} }
@ -61,23 +68,24 @@ public class LocalRepositoryCacheUnitTest {
* thrown. It is wrapped inside a JGit InvalidRemoteException. * thrown. It is wrapped inside a JGit InvalidRemoteException.
*/ */
@Test(expected = InvalidRemoteException.class) @Test(expected = InvalidRemoteException.class)
public void cacheNonExistentRepoAuth() throws GitAPIException { public void tryNonExistentRepoAuth() throws GitAPIException, InterruptedException {
LocalRepositoryCache.getInstance().acquire("https://github.com/bmlong137/does-not-exist.git", gitCreds); LocalRepositoryCache.getInstance().acquire("https://bitbucket.org/inteligr8/does-not-exist.git", gitCreds);
} }
@Test /**
public void cachePublicRepo() throws GitAPIException { * Invalid credentials and repo doesn't actually exist
Git git = LocalRepositoryCache.getInstance().acquire("https://github.com/bmlong137/maven-file-management.git"); */
try { @Test(expected = InvalidRemoteException.class)
this.validateGenericGitRepo(git); public void tryInvalidCredsOnNoRepo() throws GitAPIException, InterruptedException {
} finally { LocalRepositoryCache.getInstance().acquire("https://bitbucket.org/inteligr8/does-not-exist.git", badCreds);
git.close();
}
} }
/**
* Invalid credentials and public repo exists
*/
@Test @Test
public void cachePublicRepoViaSsh() throws GitAPIException { public void cacheInvalidCredsOnPublicRepo() throws GitAPIException, InterruptedException {
Git git = LocalRepositoryCache.getInstance().acquire("git@github.com:bmlong137/maven-file-management.git"); Git git = LocalRepositoryCache.getInstance().acquire("https://bitbucket.org/inteligr8/git-utils.git", badCreds);
try { try {
this.validateGenericGitRepo(git); this.validateGenericGitRepo(git);
} finally { } finally {
@ -86,18 +94,16 @@ public class LocalRepositoryCacheUnitTest {
} }
/** /**
* Since "github-api" isn't a public repo, it requires authentication * Invalid credentials and private repo exists
* to even check to see if it is private. This causes a JSchException
* which is wrapped inside a JGit TransportException.
*/ */
@Test(expected = TransportException.class) @Test(expected = TransportException.class)
public void cachePrivateRepoUnauth() throws GitAPIException { public void tryInvalidCredsOnPrivateRepo() throws GitAPIException, InterruptedException {
LocalRepositoryCache.getInstance().acquire("https://github.com/bmlong137/github-api.git"); LocalRepositoryCache.getInstance().acquire("https://bitbucket.org/inteligr8/propagate-branches.git", badCreds);
} }
@Test @Test
public void cachePrivateRepo() throws GitAPIException { public void cachePublicRepo() throws GitAPIException, InterruptedException {
Git git = LocalRepositoryCache.getInstance().acquire("https://github.com/bmlong137/github-api.git", gitCreds); Git git = LocalRepositoryCache.getInstance().acquire("https://bitbucket.org/inteligr8/git-utils.git");
try { try {
this.validateGenericGitRepo(git); this.validateGenericGitRepo(git);
} finally { } finally {
@ -106,12 +112,73 @@ public class LocalRepositoryCacheUnitTest {
} }
@Test @Test
public void cachePublicRepoBranch() throws GitAPIException, IOException { public void cachePublicRepoViaSsh() throws GitAPIException, InterruptedException {
Git git = LocalRepositoryCache.getInstance().acquire("https://github.com/bmlong137/maven-file-management.git", "master"); Git git = LocalRepositoryCache.getInstance().acquire("git@bitbucket.org:inteligr8/git-utils.git");
try {
this.validateGenericGitRepo(git);
} finally {
git.close();
}
}
/**
* Since "propagate-branches" isn't a public repo, it requires authentication
* to even check to see if it is private. This causes a JSchException
* which is wrapped inside a JGit TransportException.
*/
@Test(expected = TransportException.class)
public void tryPrivateRepoUnauth() throws GitAPIException, InterruptedException {
LocalRepositoryCache.getInstance().acquire("https://bitbucket.org/inteligr8/propagate-branches.git");
}
@Test
public void cachePrivateRepo() throws GitAPIException, InterruptedException {
Git git = LocalRepositoryCache.getInstance().acquire("https://bitbucket.org/inteligr8/propagate-branches.git", gitCreds);
try {
this.validateGenericGitRepo(git);
} finally {
git.close();
}
}
@Test
public void cachePublicRepoBranch() throws GitAPIException, InterruptedException, IOException {
Git git = LocalRepositoryCache.getInstance().acquire("https://bitbucket.org/inteligr8/git-utils.git", "stable");
try { try {
this.validateGenericGitRepo(git); this.validateGenericGitRepo(git);
Assert.assertEquals("master", git.getRepository().getBranch()); Assert.assertEquals("stable", git.getRepository().getBranch());
} finally {
git.close();
}
}
@Test
public void cacheReuse() throws GitAPIException, InterruptedException, IOException {
Git git = LocalRepositoryCache.getInstance().acquire("https://bitbucket.org/inteligr8/git-utils.git");
String repoId = git.getRepository().getIdentifier();
git.close();
Assert.assertEquals(this.cachedFiles+1, LocalRepositoryCache.getInstance().cacheDirectory.listFiles().length);
Thread.sleep(1000L);
Assert.assertEquals(this.cachedFiles+1, LocalRepositoryCache.getInstance().cacheDirectory.listFiles().length);
git = LocalRepositoryCache.getInstance().acquire("https://bitbucket.org/inteligr8/git-utils.git");
try {
Assert.assertEquals(repoId, git.getRepository().getIdentifier());
git.checkout().setName("develop").setStartPoint("origin/develop").setCreateBranch(true).call();
Assert.assertEquals("develop", git.getRepository().getBranch());
} finally {
git.close();
}
Assert.assertEquals(this.cachedFiles+1, LocalRepositoryCache.getInstance().cacheDirectory.listFiles().length);
Thread.sleep(1000L);
Assert.assertEquals(this.cachedFiles+1, LocalRepositoryCache.getInstance().cacheDirectory.listFiles().length);
git = LocalRepositoryCache.getInstance().acquire("https://bitbucket.org/inteligr8/git-utils.git", "stable");
try {
Assert.assertEquals("stable", git.getRepository().getBranch());
} finally { } finally {
git.close(); git.close();
} }