diff --git a/src/main/java/com/inteligr8/git/CachedGit.java b/src/main/java/com/inteligr8/git/CachedGit.java index 01e50fb..001b346 100644 --- a/src/main/java/com/inteligr8/git/CachedGit.java +++ b/src/main/java/com/inteligr8/git/CachedGit.java @@ -21,6 +21,10 @@ public class CachedGit extends Git { super(repo); } + public String getFirstRemoteUrl() throws GitAPIException { + return this.remoteList().call().iterator().next().getURIs().iterator().next().toString(); + } + @Override public void close() { LocalRepositoryCache.getInstance().release(this); diff --git a/src/main/java/com/inteligr8/git/DeveloperException.java b/src/main/java/com/inteligr8/git/DeveloperException.java new file mode 100644 index 0000000..906c20d --- /dev/null +++ b/src/main/java/com/inteligr8/git/DeveloperException.java @@ -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); + } + +} diff --git a/src/main/java/com/inteligr8/git/ExtendedGit.java b/src/main/java/com/inteligr8/git/ExtendedGit.java index e4a3348..427f5fb 100644 --- a/src/main/java/com/inteligr8/git/ExtendedGit.java +++ b/src/main/java/com/inteligr8/git/ExtendedGit.java @@ -47,10 +47,6 @@ public class ExtendedGit extends CachedGit { super(repo); } - public String getFirstRemoteUrl() throws GitAPIException { - return this.remoteList().call().iterator().next().getURIs().iterator().next().toString(); - } - public String getRepositoryFullyQualifiedName() throws GitAPIException, URISyntaxException { String gitUrl = this.getFirstRemoteUrl(); if (this.logger.isDebugEnabled()) diff --git a/src/main/java/com/inteligr8/git/LRUExpiringHashMap.java b/src/main/java/com/inteligr8/git/LRUExpiringHashMap.java index 886941b..d98b7b8 100644 --- a/src/main/java/com/inteligr8/git/LRUExpiringHashMap.java +++ b/src/main/java/com/inteligr8/git/LRUExpiringHashMap.java @@ -17,24 +17,25 @@ public class LRUExpiringHashMap implements ListeningM private final Logger logger = LoggerFactory.getLogger(LRUExpiringHashMap.class); - private final Map map; + private final Object sync = new Object(); + private final LinkedHashMap map; private final List> listeners = new LinkedList>(); private final long expirationTimeMillis; public LRUExpiringHashMap(int expirationTimeInMinutes) { - this.map = new LinkedHashMap(); + this.map = new LinkedHashMap<>(); this.expirationTimeMillis = expirationTimeInMinutes * 60L + 1000L; } public LRUExpiringHashMap(int expirationTimeInMinutes, int initialCapacity) { - this.map = new LinkedHashMap(initialCapacity); + this.map = new LinkedHashMap<>(initialCapacity); this.expirationTimeMillis = expirationTimeInMinutes * 60L + 1000L; } @Override public void addListener(MapListener listener) { if (this.logger.isDebugEnabled()) - this.logger.debug("adding listener"); + this.logger.debug("adding listener: " + listener.getClass()); this.listeners.add(listener); } @@ -76,47 +77,60 @@ public class LRUExpiringHashMap implements ListeningM @Override @SuppressWarnings("unchecked") public V get(Object key) { - if (!this.map.containsKey(key)) - return null; + ExpiringValue evalue; - // remove and put to move the entry to the end of the map; helping with finding expired entries - V value = this.map.remove(key); - this.map.put(new ExpiringHashKey((K)key, System.currentTimeMillis() + this.expirationTimeMillis), value); + synchronized (this.sync) { + if (!this.map.containsKey(key)) + return null; + + // remove and put to move the entry to the end of the map; helping with finding expired entries + evalue = this.map.remove(key); + this.map.put((K)key, new ExpiringValue(evalue.getValue(), System.currentTimeMillis() + this.expirationTimeMillis)); + } for (MapListener listener : this.listeners) - listener.accessed(new ExpiringHashMapEntry((K)key, value)); - return value; + listener.accessed(new ExpiringHashMapEntry((K)key, evalue.getValue())); + return evalue.getValue(); } @Override public V put(K key, V value) { - ExpiringHashKey ehkey = new ExpiringHashKey(key, System.currentTimeMillis() + this.expirationTimeMillis); - V oldValue = this.map.put(ehkey, value); + ExpiringValue evalue = new ExpiringValue(value, System.currentTimeMillis() + this.expirationTimeMillis); + ExpiringValue oldValue = this.map.put(key, evalue); for (MapListener listener : this.listeners) listener.added(new ExpiringHashMapEntry(key, value)); - return oldValue; + return oldValue == null ? null : oldValue.getValue(); } @Override public void clear() { - for (Entry entry : this.map.entrySet()) { - for (MapListener listener : this.listeners) - listener.cleared(new ExpiringHashMapEntry(entry.getKey().getKey(), entry.getValue())); - } + List entries = new LinkedList<>(); - this.map.clear(); + synchronized (this.sync) { + for (Entry entry : this.map.entrySet()) + entries.add(new ExpiringHashMapEntry(entry.getKey(), entry.getValue().getValue())); + this.map.clear(); + } + + for (ExpiringHashMapEntry entry : entries) + for (MapListener listener : this.listeners) + listener.cleared(entry); } @SuppressWarnings("unchecked") @Override public V remove(Object key) { - if (!this.map.containsKey(key)) - return null; + ExpiringValue evalue; + + synchronized (this.sync) { + if (!this.map.containsKey(key)) + return null; + evalue = this.map.remove(key); + } - V value = this.map.remove(key); for (MapListener listener : this.listeners) - listener.removed(new ExpiringHashMapEntry((K)key, value)); - return value; + listener.removed(new ExpiringHashMapEntry((K)key, evalue.getValue())); + return evalue.getValue(); } @Override @@ -135,28 +149,49 @@ public class LRUExpiringHashMap implements ListeningM } public void exipriationCheck() { - Iterator> i = this.map.entrySet().iterator(); - for (Entry entry = i.next(); i.hasNext(); entry = i.next()) { - if (entry.getKey().isExpired()) { - i.remove(); - for (MapListener listener : this.listeners) - if (listener instanceof ExpiringMapListener) - ((ExpiringMapListener)listener).expired(new ExpiringHashMapEntry(entry.getKey().getKey(), entry.getValue())); + // since these should be order, we could break out of this loop once we reach an unexpired entry + List entries = new LinkedList<>(); + + synchronized (this.sync) { + Iterator> i = this.map.entrySet().iterator(); + for (Entry entry = i.next(); i.hasNext(); entry = i.next()) { + if (entry.getValue().isExpired()) { + 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 listener : this.listeners) + if (listener instanceof ExpiringMapListener) + ((ExpiringMapListener)listener).expired(entry); } public void expire(K key) { if (this.logger.isDebugEnabled()) this.logger.debug("expiring key from map: " + key); - - if (!this.map.containsKey(key)) - return; + + ExpiringValue evalue; - V value = this.map.remove(key); + synchronized (this.sync) { + if (!this.map.containsKey(key)) + return; + evalue = this.map.remove(key); + } + for (MapListener listener : this.listeners) if (listener instanceof ExpiringMapListener) - ((ExpiringMapListener)listener).expired(new ExpiringHashMapEntry(key, value)); + ((ExpiringMapListener)listener).expired(new ExpiringHashMapEntry(key, evalue.getValue())); + } + + @Override + public String toString() { + return this.map.toString(); } @@ -207,44 +242,27 @@ public class LRUExpiringHashMap implements ListeningM } - private class ExpiringHashKey implements Serializable { - - private static final long serialVersionUID = -6511298315143655313L; + private class ExpiringValue { private long expirationTimeInMillis; - private K key; + private V value; - public ExpiringHashKey(K key, long expirationTimeInMillis) { - this.key = key; + public ExpiringValue(V value, long expirationTimeInMillis) { + this.value = value; this.expirationTimeInMillis = expirationTimeInMillis; } - public K getKey() { - return this.key; + public V getValue() { + return this.value; } public boolean isExpired() { return this.expirationTimeInMillis <= System.currentTimeMillis(); } - @Override - public int hashCode() { - return this.key.hashCode(); - } - @Override public String toString() { - return this.key.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); - } + return this.value.toString(); } } diff --git a/src/main/java/com/inteligr8/git/LocalRepositoryCache.java b/src/main/java/com/inteligr8/git/LocalRepositoryCache.java index 47f1dfd..1b1c07d 100644 --- a/src/main/java/com/inteligr8/git/LocalRepositoryCache.java +++ b/src/main/java/com/inteligr8/git/LocalRepositoryCache.java @@ -2,14 +2,20 @@ package com.inteligr8.git; import java.io.File; import java.io.IOException; +import java.net.URISyntaxException; +import java.util.HashMap; +import java.util.Map; import java.util.Map.Entry; import java.util.UUID; +import java.util.concurrent.Semaphore; import org.eclipse.jgit.api.CloneCommand; 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.InvalidRemoteException; import org.eclipse.jgit.api.errors.RefNotFoundException; +import org.eclipse.jgit.api.errors.TransportException; import org.eclipse.jgit.transport.CredentialsProvider; import org.eclipse.jgit.util.FileUtils; import org.slf4j.Logger; @@ -23,15 +29,18 @@ public class LocalRepositoryCache { return INSTANCE; } + + private final Logger logger = LoggerFactory.getLogger(LocalRepositoryCache.class); - private final LRUExpiringHashMap cachedGits = new LRUExpiringHashMap(30); -// private final Map gitIdsToUrls = new HashMap(); - private final File cacheDirectory = new File(System.getProperty("java.io.tmpdir"), "git"); + private final LRUExpiringHashMap cachedGits = new LRUExpiringHashMap<>(30); + private final Map gitUrlSemaphores = new HashMap<>(); + final File cacheDirectory = new File(System.getProperty("java.io.tmpdir"), "git"); + private final int simultaneousProcessesPerGitRepo = 1; private LocalRepositoryCache() { this.cacheDirectory.mkdir(); - this.cachedGits.addListener(new RepositoryCacheMapListener()); + this.cachedGits.addListener(new RepositoryCacheMapListener()); } @Override @@ -49,74 +58,123 @@ public class LocalRepositoryCache { 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); } - 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); } - 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); } - 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()) this.logger.trace("acquire('" + url + "', " + creds + ", '" + branch + "')"); -// Git git = this.cachedGits.remove(url); -// if (git == null) { - if (this.logger.isDebugEnabled()) - this.logger.debug("creating temporary Git directory"); - File gitRepoDirectory = new File(this.cacheDirectory, UUID.randomUUID().toString() + ".git"); - - CloneCommand clone = new CloneCommand() - .setURI(url) - .setDirectory(gitRepoDirectory); - if (branch != null) - clone.setBranch(branch); - - if (this.logger.isDebugEnabled()) - this.logger.debug("cloning Git repository: " + url); - ExtendedGit git = creds != null ? new CredentialedGit(clone, creds) : new ExtendedGit(clone); - if (this.logger.isInfoEnabled()) - this.logger.info("Cloned Git Repository"); + Semaphore semaphore = null; + synchronized (this) { + semaphore = this.gitUrlSemaphores.get(url); + 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"); + if (this.logger.isDebugEnabled()) + this.logger.debug("Git directory cache for clone: " + gitRepoDirectory); + + CloneCommand clone = new CloneCommand() + .setURI(url) + .setDirectory(gitRepoDirectory); + if (branch != null) + clone.setBranch(branch); + + if (this.logger.isDebugEnabled()) + this.logger.debug("Cloning Git repository: " + url); + git = creds != null ? new CredentialedGit(clone, creds) : new ExtendedGit(clone); + if (this.logger.isInfoEnabled()) + this.logger.info("Cloned Git Repository: " + ((ExtendedGit)git).getRepositoryFullyQualifiedName()); + } else { + if (this.logger.isDebugEnabled()) + this.logger.debug("reseting Git"); + git.reset().setMode(ResetType.HARD).call(); + + if (branch != null) { + if (this.logger.isDebugEnabled()) + this.logger.debug("switching Git branches: " + branch); + git.checkout().setName(branch).call(); + } + + if (this.logger.isDebugEnabled()) + this.logger.debug("updating Git branch: " + git.getRepository().getBranch()); + git.pull().call(); + } + return git; -// git = creds != null ? new CredentialedGit(clone, creds) : new CachedGit(clone); -// this.gitIdsToUrls.put(git.getRepository().getIdentifier(), url); -// } else { -// if (branch != null) { -// if (this.logger.isDebugEnabled()) -// 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(); -// } + } 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 synchronized void release(Git git) { + public void release(CachedGit git) { if (this.logger.isTraceEnabled()) this.logger.trace("release('" + git.getRepository().getIdentifier() + "')"); -// String url = this.gitIdsToUrls.get(git.getRepository().getIdentifier()); -// this.cachedGits.put(url, git); - this.expunge(git); + try { + String url = git.getFirstRemoteUrl(); + + synchronized (this) { + Semaphore semaphore = this.gitUrlSemaphores.get(url); + if (semaphore != null) + semaphore.release(); + } + + 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 synchronized void clear() { + public void clear() { if (this.logger.isTraceEnabled()) 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) { -// gitIdsToUrls.remove(git.getRepository().getIdentifier()); - File gitDir = git.getRepository().getDirectory(); File workingTreeDir = git.getRepository().getWorkTree(); git.getRepository().close(); @@ -129,7 +187,6 @@ public class LocalRepositoryCache { this.logger.warn("Failed to delete a git directory: " + gitDir); if (this.logger.isDebugEnabled()) this.logger.debug(ie.getMessage(), ie); - gitDir.deleteOnExit(); } try { @@ -140,42 +197,41 @@ public class LocalRepositoryCache { this.logger.warn("Failed to delete a git directory: " + workingTreeDir); if (this.logger.isDebugEnabled()) this.logger.debug(ie.getMessage(), ie); - workingTreeDir.deleteOnExit(); } if (this.logger.isInfoEnabled()) - this.logger.info("Deleted Git Repository"); + this.logger.info("Deleted Git Repository: " + gitDir); } - private class RepositoryCacheMapListener implements ExpiringMapListener { + private class RepositoryCacheMapListener implements ExpiringMapListener { private final Logger logger = LoggerFactory.getLogger(LocalRepositoryCache.class); @Override - public void accessed(Entry entry) { + public void accessed(Entry entry) { } @Override - public void added(Entry entry) { + public void added(Entry entry) { // a clean one or one returned after being previously removed } @Override - public void expired(Entry entry) { + public void expired(Entry entry ) { if (this.logger.isTraceEnabled()) this.logger.trace("expired('" + entry.getKey() + "', '" + entry.getValue().getRepository().getIdentifier() + "')"); expunge(entry.getValue()); } @Override - public void removed(Entry entry) { + public void removed(Entry entry) { // expected to be removed only temporarily...for use elsewhere; do not close } @Override - public void cleared(Entry entry) { + public void cleared(Entry entry) { if (this.logger.isTraceEnabled()) this.logger.trace("cleared('" + entry.getKey() + "', '" + entry.getValue().getRepository().getIdentifier() + "')"); expunge(entry.getValue()); diff --git a/src/test/java/com/inteligr8/git/LocalRepositoryCacheUnitTest.java b/src/test/java/com/inteligr8/git/LocalRepositoryCacheUnitTest.java index 3bc7600..ec398b2 100644 --- a/src/test/java/com/inteligr8/git/LocalRepositoryCacheUnitTest.java +++ b/src/test/java/com/inteligr8/git/LocalRepositoryCacheUnitTest.java @@ -9,20 +9,27 @@ import org.eclipse.jgit.api.errors.TransportException; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.transport.CredentialsProvider; import org.eclipse.jgit.transport.UsernamePasswordCredentialsProvider; -import org.junit.AfterClass; +import org.junit.After; import org.junit.Assert; +import org.junit.Before; import org.junit.Test; -import com.inteligr8.git.LocalRepositoryCache; - 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( - System.getProperty("git.username"), System.getProperty("git.token")); + System.getProperty("bitbucket.inteligr8.username"), System.getProperty("bitbucket.inteligr8.token")); - @AfterClass - public static void cleanup() { + private int cachedFiles; + + @Before + public void compileStats() { + this.cachedFiles = LocalRepositoryCache.getInstance().cacheDirectory.listFiles().length; + } + @After + public void cleanupAndValidate() { 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. */ @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"); } @@ -42,7 +49,7 @@ public class LocalRepositoryCacheUnitTest { * more like cacheNonExistentRepoAuth() than cacheNonExistentRepo(). */ @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"); } @@ -52,7 +59,7 @@ public class LocalRepositoryCacheUnitTest { * which is wrapped inside a JGit TransportException. */ @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"); } @@ -61,13 +68,42 @@ public class LocalRepositoryCacheUnitTest { * thrown. It is wrapped inside a JGit InvalidRemoteException. */ @Test(expected = InvalidRemoteException.class) - public void cacheNonExistentRepoAuth() throws GitAPIException { - LocalRepositoryCache.getInstance().acquire("https://github.com/bmlong137/does-not-exist.git", gitCreds); + public void tryNonExistentRepoAuth() throws GitAPIException, InterruptedException { + LocalRepositoryCache.getInstance().acquire("https://bitbucket.org/inteligr8/does-not-exist.git", gitCreds); + } + + /** + * Invalid credentials and repo doesn't actually exist + */ + @Test(expected = InvalidRemoteException.class) + public void tryInvalidCredsOnNoRepo() throws GitAPIException, InterruptedException { + LocalRepositoryCache.getInstance().acquire("https://bitbucket.org/inteligr8/does-not-exist.git", badCreds); + } + + /** + * Invalid credentials and public repo exists + */ + @Test + public void cacheInvalidCredsOnPublicRepo() throws GitAPIException, InterruptedException { + Git git = LocalRepositoryCache.getInstance().acquire("https://bitbucket.org/inteligr8/git-utils.git", badCreds); + try { + this.validateGenericGitRepo(git); + } finally { + git.close(); + } + } + + /** + * Invalid credentials and private repo exists + */ + @Test(expected = TransportException.class) + public void tryInvalidCredsOnPrivateRepo() throws GitAPIException, InterruptedException { + LocalRepositoryCache.getInstance().acquire("https://bitbucket.org/inteligr8/propagate-branches.git", badCreds); } @Test - public void cachePublicRepo() throws GitAPIException { - Git git = LocalRepositoryCache.getInstance().acquire("https://github.com/bmlong137/maven-file-management.git"); + public void cachePublicRepo() throws GitAPIException, InterruptedException { + Git git = LocalRepositoryCache.getInstance().acquire("https://bitbucket.org/inteligr8/git-utils.git"); try { this.validateGenericGitRepo(git); } finally { @@ -76,8 +112,8 @@ public class LocalRepositoryCacheUnitTest { } @Test - public void cachePublicRepoViaSsh() throws GitAPIException { - Git git = LocalRepositoryCache.getInstance().acquire("git@github.com:bmlong137/maven-file-management.git"); + public void cachePublicRepoViaSsh() throws GitAPIException, InterruptedException { + Git git = LocalRepositoryCache.getInstance().acquire("git@bitbucket.org:inteligr8/git-utils.git"); try { this.validateGenericGitRepo(git); } finally { @@ -86,18 +122,18 @@ public class LocalRepositoryCacheUnitTest { } /** - * Since "github-api" isn't a public repo, it requires authentication + * 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 cachePrivateRepoUnauth() throws GitAPIException { - LocalRepositoryCache.getInstance().acquire("https://github.com/bmlong137/github-api.git"); + public void tryPrivateRepoUnauth() throws GitAPIException, InterruptedException { + LocalRepositoryCache.getInstance().acquire("https://bitbucket.org/inteligr8/propagate-branches.git"); } @Test - public void cachePrivateRepo() throws GitAPIException { - Git git = LocalRepositoryCache.getInstance().acquire("https://github.com/bmlong137/github-api.git", gitCreds); + public void cachePrivateRepo() throws GitAPIException, InterruptedException { + Git git = LocalRepositoryCache.getInstance().acquire("https://bitbucket.org/inteligr8/propagate-branches.git", gitCreds); try { this.validateGenericGitRepo(git); } finally { @@ -106,12 +142,43 @@ public class LocalRepositoryCacheUnitTest { } @Test - public void cachePublicRepoBranch() throws GitAPIException, IOException { - Git git = LocalRepositoryCache.getInstance().acquire("https://github.com/bmlong137/maven-file-management.git", "master"); + public void cachePublicRepoBranch() throws GitAPIException, InterruptedException, IOException { + Git git = LocalRepositoryCache.getInstance().acquire("https://bitbucket.org/inteligr8/git-utils.git", "stable"); try { 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 { git.close(); }