commit 13e190fd5c98d690d31996522355df97ae13909a Author: Brian Long Date: Tue Dec 1 10:18:56 2020 -0500 initial checkin diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..1e8f8f8 --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +# Maven +target + +# Eclipse +.project +.classpath +.settings + diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..4fc919e --- /dev/null +++ b/pom.xml @@ -0,0 +1,77 @@ + + 4.0.0 + me.brianlong + github-api + jar + 1.0-SNAPSHOT + + GitHub API & Utilities + + + 1.8 + 1.8 + UTF-8 + + + + + org.eclipse.jgit + org.eclipse.jgit + 5.9.0.202009080501-r + + + org.eclipse.jgit + org.eclipse.jgit.ssh.jsch + 5.9.0.202009080501-r + + + org.apache.httpcomponents + httpclient + 4.5.12 + + + com.fasterxml.jackson.jaxrs + jackson-jaxrs-json-provider + 2.11.0 + + + com.fasterxml.jackson.datatype + jackson-datatype-jsr310 + 2.11.0 + + + junit + junit + 4.13 + test + + + org.apache.logging.log4j + log4j-slf4j-impl + 2.13.2 + test + + + + + + + org.apache.maven.plugins + maven-surefire-plugin + + + unit-tests + test + test + + + **/*IT.class + + + + + + + + diff --git a/src/jetty/log4j2.xml b/src/jetty/log4j2.xml new file mode 100644 index 0000000..ba7d307 --- /dev/null +++ b/src/jetty/log4j2.xml @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/src/main/java/me/brianlong/git/CachedGit.java b/src/main/java/me/brianlong/git/CachedGit.java new file mode 100644 index 0000000..0ad4815 --- /dev/null +++ b/src/main/java/me/brianlong/git/CachedGit.java @@ -0,0 +1,29 @@ +package me.brianlong.git; + +import org.eclipse.jgit.api.CloneCommand; +import org.eclipse.jgit.api.Git; +import org.eclipse.jgit.api.errors.GitAPIException; +import org.eclipse.jgit.api.errors.InvalidRemoteException; +import org.eclipse.jgit.api.errors.TransportException; +import org.eclipse.jgit.lib.Repository; + +public class CachedGit extends Git { + + public CachedGit(Git git) { + super(git.getRepository()); + } + + public CachedGit(CloneCommand clone) throws TransportException, InvalidRemoteException, GitAPIException { + super(clone.call().getRepository()); + } + + public CachedGit(Repository repo) { + super(repo); + } + + @Override + public void close() { + LocalRepositoryCache.getInstance().release(this); + } + +} diff --git a/src/main/java/me/brianlong/git/CredentialedGit.java b/src/main/java/me/brianlong/git/CredentialedGit.java new file mode 100644 index 0000000..2c62b05 --- /dev/null +++ b/src/main/java/me/brianlong/git/CredentialedGit.java @@ -0,0 +1,53 @@ +package me.brianlong.git; + +import org.eclipse.jgit.api.CloneCommand; +import org.eclipse.jgit.api.FetchCommand; +import org.eclipse.jgit.api.Git; +import org.eclipse.jgit.api.LsRemoteCommand; +import org.eclipse.jgit.api.PullCommand; +import org.eclipse.jgit.api.PushCommand; +import org.eclipse.jgit.api.errors.GitAPIException; +import org.eclipse.jgit.api.errors.InvalidRemoteException; +import org.eclipse.jgit.api.errors.TransportException; +import org.eclipse.jgit.lib.Repository; +import org.eclipse.jgit.transport.CredentialsProvider; + +public class CredentialedGit extends CachedGit { + + private CredentialsProvider credProvider; + + public CredentialedGit(CloneCommand clone, GitCredentials creds) throws TransportException, InvalidRemoteException, GitAPIException { + this(clone.setCredentialsProvider(creds.toJGitCredentialsProvider()).call(), creds); + } + + public CredentialedGit(Git git, GitCredentials creds) { + super(git.getRepository()); + this.credProvider = creds.toJGitCredentialsProvider(); + } + + public CredentialedGit(Repository repo, GitCredentials creds) { + super(repo); + this.credProvider = creds.toJGitCredentialsProvider(); + } + + @Override + public FetchCommand fetch() { + return super.fetch().setCredentialsProvider(this.credProvider); + } + + @Override + public LsRemoteCommand lsRemote() { + return super.lsRemote().setCredentialsProvider(this.credProvider); + } + + @Override + public PullCommand pull() { + return super.pull().setCredentialsProvider(this.credProvider); + } + + @Override + public PushCommand push() { + return super.push().setCredentialsProvider(this.credProvider); + } + +} diff --git a/src/main/java/me/brianlong/git/ExpiringMapListener.java b/src/main/java/me/brianlong/git/ExpiringMapListener.java new file mode 100644 index 0000000..08802a4 --- /dev/null +++ b/src/main/java/me/brianlong/git/ExpiringMapListener.java @@ -0,0 +1,9 @@ +package me.brianlong.git; + +import java.util.Map.Entry; + +public interface ExpiringMapListener extends MapListener { + + void expired(Entry entry); + +} diff --git a/src/main/java/me/brianlong/git/GitCredentials.java b/src/main/java/me/brianlong/git/GitCredentials.java new file mode 100644 index 0000000..e9c34e4 --- /dev/null +++ b/src/main/java/me/brianlong/git/GitCredentials.java @@ -0,0 +1,26 @@ +package me.brianlong.git; + +import org.apache.http.auth.AuthScope; +import org.apache.http.auth.UsernamePasswordCredentials; +import org.apache.http.impl.client.BasicCredentialsProvider; +import org.eclipse.jgit.transport.UsernamePasswordCredentialsProvider; + +public class GitCredentials extends UsernamePasswordCredentials { + + private static final long serialVersionUID = -8811637815236347412L; + + public GitCredentials(String username, String passwordOrToken) { + super(username, passwordOrToken); + } + + public org.apache.http.client.CredentialsProvider toHttpClientCredentialsProvider() { + org.apache.http.client.CredentialsProvider credProvider = new BasicCredentialsProvider(); + credProvider.setCredentials(AuthScope.ANY, this); + return credProvider; + } + + public org.eclipse.jgit.transport.CredentialsProvider toJGitCredentialsProvider() { + return new UsernamePasswordCredentialsProvider(this.getUserName(), this.getPassword()); + } + +} diff --git a/src/main/java/me/brianlong/git/LRUExpiringHashMap.java b/src/main/java/me/brianlong/git/LRUExpiringHashMap.java new file mode 100644 index 0000000..6064626 --- /dev/null +++ b/src/main/java/me/brianlong/git/LRUExpiringHashMap.java @@ -0,0 +1,242 @@ +package me.brianlong.git; + +import java.io.Serializable; +import java.util.Collection; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Set; + +// TODO need to implement the actual expiration schedule +public class LRUExpiringHashMap implements ListeningMap { + + private final Map map; + private final List> listeners = new LinkedList>(); + private final long expirationTimeMillis; + + public LRUExpiringHashMap(int expirationTimeInMinutes) { + this.map = new LinkedHashMap(); + this.expirationTimeMillis = expirationTimeInMinutes * 60L + 1000L; + } + + public LRUExpiringHashMap(int expirationTimeInMinutes, int initialCapacity) { + this.map = new LinkedHashMap(initialCapacity); + this.expirationTimeMillis = expirationTimeInMinutes * 60L + 1000L; + } + + @Override + public void addListener(MapListener listener) { + this.listeners.add(listener); + } + + @Override + public boolean containsKey(Object key) { + return this.map.containsKey(key); + } + + @Override + public boolean containsValue(Object value) { + return this.map.containsValue(value); + } + + @Override + public Set> entrySet() { + throw new UnsupportedOperationException(); + } + + @Override + public Set keySet() { + throw new UnsupportedOperationException(); + } + + @Override + public Collection values() { + throw new UnsupportedOperationException(); + } + + @Override + public boolean isEmpty() { + return this.map.isEmpty(); + } + + @Override + public int size() { + return this.map.size(); + } + + @Override + @SuppressWarnings("unchecked") + public V get(Object key) { + 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 + V value = this.map.remove(key); + this.map.put(new ExpiringHashKey((K)key, System.currentTimeMillis() + this.expirationTimeMillis), value); + + for (MapListener listener : this.listeners) + listener.accessed(new ExpiringHashMapEntry((K)key, value)); + return value; + } + + @Override + public V put(K key, V value) { + ExpiringHashKey ehkey = new ExpiringHashKey(key, System.currentTimeMillis() + this.expirationTimeMillis); + V oldValue = this.map.put(ehkey, value); + for (MapListener listener : this.listeners) + listener.added(new ExpiringHashMapEntry(key, value)); + return oldValue; + } + + @Override + public void clear() { + for (Entry entry : this.map.entrySet()) { + for (MapListener listener : this.listeners) + listener.cleared(new ExpiringHashMapEntry(entry.getKey().getKey(), entry.getValue())); + } + + this.map.clear(); + } + + @SuppressWarnings("unchecked") + @Override + public V remove(Object key) { + if (!this.map.containsKey(key)) + return null; + + V value = this.map.remove(key); + for (MapListener listener : this.listeners) + listener.removed(new ExpiringHashMapEntry((K)key, value)); + return value; + } + + @Override + public void putAll(Map m) { + throw new UnsupportedOperationException(); + } + + @Override + public int hashCode() { + return this.map.hashCode(); + } + + @Override + public boolean equals(Object obj) { + return this.map.equals(obj); + } + + 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())); + } + } + } + + public void expire(K key) { + if (!this.map.containsKey(key)) + return; + + V value = this.map.remove(key); + for (MapListener listener : this.listeners) + if (listener instanceof ExpiringMapListener) + ((ExpiringMapListener)listener).expired(new ExpiringHashMapEntry(key, value)); + } + + + + private class ExpiringHashMapEntry implements Entry { + + private final K key; + private V value; + + public ExpiringHashMapEntry(K key, V value) { + this.key = key; + this.value = value; + } + + @Override + public K getKey() { + return this.key; + } + + @Override + public V getValue() { + return this.value; + } + + @Override + public V setValue(V value) { + return this.value = value; + } + + @Override + public boolean equals(Object obj) { + if (obj instanceof Entry) { + return this.key.equals(((Entry)obj).getKey()) && this.value.equals(((Entry)obj).getValue()); + } else { + return false; + } + } + + @Override + public int hashCode() { + return this.key.hashCode() + this.value.hashCode(); + } + + @Override + public String toString() { + return "{" + this.key + ", " + this.value + "}"; + } + + } + + private class ExpiringHashKey implements Serializable { + + private static final long serialVersionUID = -6511298315143655313L; + + private long expirationTimeInMillis; + private K key; + + public ExpiringHashKey(K key, long expirationTimeInMillis) { + this.key = key; + this.expirationTimeInMillis = expirationTimeInMillis; + } + + public K getKey() { + return this.key; + } + + 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); + } + } + + } + +} diff --git a/src/main/java/me/brianlong/git/ListeningMap.java b/src/main/java/me/brianlong/git/ListeningMap.java new file mode 100644 index 0000000..f3cbf53 --- /dev/null +++ b/src/main/java/me/brianlong/git/ListeningMap.java @@ -0,0 +1,9 @@ +package me.brianlong.git; + +import java.util.Map; + +public interface ListeningMap extends Map { + + void addListener(MapListener listener); + +} diff --git a/src/main/java/me/brianlong/git/LocalRepositoryCache.java b/src/main/java/me/brianlong/git/LocalRepositoryCache.java new file mode 100644 index 0000000..1ccce39 --- /dev/null +++ b/src/main/java/me/brianlong/git/LocalRepositoryCache.java @@ -0,0 +1,151 @@ +package me.brianlong.git; + +import java.io.File; +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; +import java.util.Map.Entry; +import java.util.UUID; + +import org.eclipse.jgit.api.CloneCommand; +import org.eclipse.jgit.api.Git; +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.util.FileUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class LocalRepositoryCache { + + private static final LocalRepositoryCache INSTANCE = new LocalRepositoryCache(); + + public static LocalRepositoryCache getInstance() { + 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 LocalRepositoryCache() { + this.cacheDirectory.mkdir(); + this.cachedGits.addListener(new RepositoryCacheMapListener()); + } + + @Override + protected void finalize() throws Throwable { + try { + this.destroy(); + } finally { + super.finalize(); + } + } + + private void destroy() { + if (this.logger.isInfoEnabled()) + this.logger.info("destroy()"); + this.clear(); + } + + public synchronized Git acquire(String url) throws GitAPIException, InvalidRemoteException, RefNotFoundException { + return this.acquire(url, null, null); + } + + public synchronized Git acquire(String url, GitCredentials creds) throws GitAPIException, InvalidRemoteException, RefNotFoundException { + return this.acquire(url, creds, null); + } + + public synchronized Git acquire(String url, String branch) throws GitAPIException, InvalidRemoteException, RefNotFoundException { + return this.acquire(url, null, branch); + } + + public synchronized Git acquire(String url, GitCredentials creds, String branch) throws GitAPIException, InvalidRemoteException, RefNotFoundException { + Git git = this.cachedGits.remove(url); + if (git == null) { + File gitRepoDirectory = new File(this.cacheDirectory, UUID.randomUUID().toString() + ".git"); + + CloneCommand clone = new CloneCommand() + .setURI(url) + .setDirectory(gitRepoDirectory); + if (branch != null) + clone.setBranch(branch); + git = creds != null ? new CredentialedGit(clone, creds) : new CachedGit(clone); + this.gitIdsToUrls.put(git.getRepository().getIdentifier(), url); + } else { + if (branch != null) + git.checkout().setName(branch).call(); + git.pull().call(); + } + + return git; + } + + public synchronized void release(Git git) { + String sshUrl = this.gitIdsToUrls.get(git.getRepository().getIdentifier()); + this.cachedGits.put(sshUrl, git); + } + + public synchronized void clear() { + this.cachedGits.clear(); + } + + + + private class RepositoryCacheMapListener implements ExpiringMapListener { + + @Override + public void accessed(Entry entry) { + } + + @Override + public void added(Entry entry) { + // a clean one or one returned after being previously removed + } + + @Override + public void expired(Entry entry) { + this.expunge(entry.getValue()); + } + + @Override + public void removed(Entry entry) { + // expected to be removed only temporarily...for use elsewhere; do not close + } + + @Override + public void cleared(Entry entry) { + this.expunge(entry.getValue()); + } + + private void expunge(Git git) { + gitIdsToUrls.remove(git.getRepository().getIdentifier()); + + File gitDir = git.getRepository().getDirectory(); + File workingTreeDir = git.getRepository().getWorkTree(); + git.getRepository().close(); + + try { + FileUtils.delete(gitDir, FileUtils.RECURSIVE); + } catch (IOException ie) { + logger.warn("Failed to delete a git directory: " + gitDir); + if (logger.isDebugEnabled()) + logger.debug(ie.getMessage(), ie); + gitDir.deleteOnExit(); + } + + try { + FileUtils.delete(workingTreeDir, FileUtils.RECURSIVE); + } catch (IOException ie) { + logger.warn("Failed to delete a git directory: " + workingTreeDir); + if (logger.isDebugEnabled()) + logger.debug(ie.getMessage(), ie); + workingTreeDir.deleteOnExit(); + } + } + + } + +} diff --git a/src/main/java/me/brianlong/git/MapListener.java b/src/main/java/me/brianlong/git/MapListener.java new file mode 100644 index 0000000..47298f9 --- /dev/null +++ b/src/main/java/me/brianlong/git/MapListener.java @@ -0,0 +1,15 @@ +package me.brianlong.git; + +import java.util.Map.Entry; + +public interface MapListener { + + void added(Entry entry); + + void accessed(Entry entry); + + void removed(Entry entry); + + void cleared(Entry entry); + +} diff --git a/src/main/java/me/brianlong/github/model/CreateReference.java b/src/main/java/me/brianlong/github/model/CreateReference.java new file mode 100644 index 0000000..40459cd --- /dev/null +++ b/src/main/java/me/brianlong/github/model/CreateReference.java @@ -0,0 +1,30 @@ +package me.brianlong.github.model; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; + +@JsonIgnoreProperties(ignoreUnknown = true) +public class CreateReference { + + @JsonProperty(required = true) + private String ref; + @JsonProperty(required = true) + private String sha; + + public String getRef() { + return this.ref; + } + + public void setRef(String ref) { + this.ref = ref; + } + + public String getSha() { + return this.sha; + } + + public void setSha(String sha) { + this.sha = sha; + } + +} diff --git a/src/main/java/me/brianlong/github/model/PullRequest.java b/src/main/java/me/brianlong/github/model/PullRequest.java new file mode 100644 index 0000000..6bacabb --- /dev/null +++ b/src/main/java/me/brianlong/github/model/PullRequest.java @@ -0,0 +1,68 @@ +package me.brianlong.github.model; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; + +@JsonIgnoreProperties(ignoreUnknown = true) +public class PullRequest { + + @JsonProperty(required = true) + private String title; + @JsonProperty(required = true) + private String head; + @JsonProperty(required = true) + private String base; + private String body; + @JsonProperty(value = "maintainer_can_modify") + private Boolean maintainerCanModify; + private Boolean draft; + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public String getHead() { + return head; + } + + public void setHead(String head) { + this.head = head; + } + + public String getBase() { + return base; + } + + public void setBase(String base) { + this.base = base; + } + + public String getBody() { + return body; + } + + public void setBody(String body) { + this.body = body; + } + + public Boolean getMaintainerCanModify() { + return maintainerCanModify; + } + + public void setMaintainerCanModify(Boolean maintainerCanModify) { + this.maintainerCanModify = maintainerCanModify; + } + + public Boolean getDraft() { + return draft; + } + + public void setDraft(Boolean draft) { + this.draft = draft; + } + +} diff --git a/src/main/java/me/brianlong/http/PreemptiveAuthInterceptor.java b/src/main/java/me/brianlong/http/PreemptiveAuthInterceptor.java new file mode 100644 index 0000000..46f5a4b --- /dev/null +++ b/src/main/java/me/brianlong/http/PreemptiveAuthInterceptor.java @@ -0,0 +1,37 @@ +package me.brianlong.http; + +import java.io.IOException; + +import org.apache.http.HttpException; +import org.apache.http.HttpHost; +import org.apache.http.HttpRequest; +import org.apache.http.HttpRequestInterceptor; +import org.apache.http.auth.AuthScope; +import org.apache.http.auth.AuthState; +import org.apache.http.auth.Credentials; +import org.apache.http.client.CredentialsProvider; +import org.apache.http.client.protocol.HttpClientContext; +import org.apache.http.impl.auth.BasicScheme; +import org.apache.http.protocol.HttpContext; +import org.apache.http.protocol.HttpCoreContext; + +public class PreemptiveAuthInterceptor implements HttpRequestInterceptor { + + public void process(final HttpRequest request, final HttpContext context) throws HttpException, IOException { + AuthState authState = (AuthState) context.getAttribute(HttpClientContext.TARGET_AUTH_STATE); + + // If no auth scheme available yet, try to initialize it + // preemptively + if (authState.getAuthScheme() == null) { + CredentialsProvider credsProvider = (CredentialsProvider) context.getAttribute(HttpClientContext.CREDS_PROVIDER); + HttpHost targetHost = (HttpHost) context.getAttribute(HttpCoreContext.HTTP_TARGET_HOST); + Credentials creds = credsProvider.getCredentials(new AuthScope(targetHost.getHostName(), targetHost.getPort())); + if (creds == null) { + throw new HttpException("No credentials for preemptive authentication"); + } + authState.update(new BasicScheme(), creds); + } + + } + +} diff --git a/src/test/java/me/brianlong/git/CommandUnitTest.java b/src/test/java/me/brianlong/git/CommandUnitTest.java new file mode 100644 index 0000000..1d09b46 --- /dev/null +++ b/src/test/java/me/brianlong/git/CommandUnitTest.java @@ -0,0 +1,51 @@ +package me.brianlong.git; + +import java.io.File; +import java.io.IOException; +import java.util.List; + +import org.eclipse.jgit.api.CloneCommand; +import org.eclipse.jgit.api.Git; +import org.eclipse.jgit.api.ListBranchCommand.ListMode; +import org.eclipse.jgit.api.errors.GitAPIException; +import org.eclipse.jgit.lib.Ref; +import org.junit.AfterClass; +import org.junit.Assert; +import org.junit.BeforeClass; +import org.junit.Test; + +public class CommandUnitTest { + + private final static GitHubTestCredentials githubCreds = new GitHubTestCredentials(); + + private static File tmpdir; + private static Git git; + + @BeforeClass + public static void init() throws GitAPIException, IOException { + tmpdir = new File(System.getProperty("java.io.tmpdir"), "git.tmp"); + tmpdir.mkdirs(); + + git = new CloneCommand() + .setURI("git@github.com:bmlong137/env-docker-adbp.git") + .setCredentialsProvider(githubCreds.toJGitCredentialsProvider()) + .setDirectory(tmpdir) + .call(); + } + + @AfterClass + public static void cleanup() { + git.getRepository().close(); + git.close(); + + tmpdir.deleteOnExit(); + } + + @Test + public void lotsOfBranches() throws GitAPIException { + List remoteBranches = git.branchList().setListMode(ListMode.REMOTE).call(); + Assert.assertNotNull(remoteBranches); + Assert.assertTrue(remoteBranches.size() > 5); + } + +} diff --git a/src/test/java/me/brianlong/git/GitHubTestCredentials.java b/src/test/java/me/brianlong/git/GitHubTestCredentials.java new file mode 100644 index 0000000..b705316 --- /dev/null +++ b/src/test/java/me/brianlong/git/GitHubTestCredentials.java @@ -0,0 +1,11 @@ +package me.brianlong.git; + +public class GitHubTestCredentials extends GitCredentials { + + private static final long serialVersionUID = -4460599306349661667L; + + public GitHubTestCredentials() { + super(System.getProperty("github.username"), System.getProperty("github.token")); + } + +} diff --git a/src/test/java/me/brianlong/git/LocalRepositoryCacheUnitTest.java b/src/test/java/me/brianlong/git/LocalRepositoryCacheUnitTest.java new file mode 100644 index 0000000..77817e8 --- /dev/null +++ b/src/test/java/me/brianlong/git/LocalRepositoryCacheUnitTest.java @@ -0,0 +1,124 @@ +package me.brianlong.git; + +import java.io.IOException; + +import org.eclipse.jgit.api.Git; +import org.eclipse.jgit.api.errors.GitAPIException; +import org.eclipse.jgit.api.errors.InvalidRemoteException; +import org.eclipse.jgit.api.errors.TransportException; +import org.eclipse.jgit.lib.Repository; +import org.junit.AfterClass; +import org.junit.Assert; +import org.junit.Test; + +public class LocalRepositoryCacheUnitTest { + + private final static GitHubTestCredentials githubCreds = new GitHubTestCredentials(); + + @AfterClass + public static void cleanup() { + LocalRepositoryCache.getInstance().clear(); + } + + /** + * Since "host-does-not-exist.com" does not exist, an UnknownHostException + * is thrown. It is wrapped inside a JGit TransportException. + */ + @Test(expected = TransportException.class) + public void cacheBadHostRepo() throws GitAPIException { + LocalRepositoryCache.getInstance().acquire("https://host-does-not-exist.com/bmlong137/does-not-exist.git"); + } + + /** + * Since "does-not-exist" isn't a public repo, it requires authentication + * to even check to see if it is private. This causes a + * NoRemoteRepositoryException which is wrapped inside a JGit + * InvalidRemoteException. This is because SSH authentication makes this + * more like cacheNonExistentRepoAuth() than cacheNonExistentRepo(). + */ + @Test(expected = InvalidRemoteException.class) + public void cacheNonExistentRepoViaSsh() throws GitAPIException { + LocalRepositoryCache.getInstance().acquire("git@github.com:bmlong137/does-not-exist.git"); + } + + /** + * Since "does-not-exist" 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 cacheNonExistentRepo() throws GitAPIException { + LocalRepositoryCache.getInstance().acquire("https://github.com/bmlong137/does-not-exist.git"); + } + + /** + * Since "does-not-exist" isn't a repo, a NoRemoteRepositoryException is + * 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", githubCreds); + } + + @Test + public void cachePublicRepo() throws GitAPIException { + Git git = LocalRepositoryCache.getInstance().acquire("https://github.com/bmlong137/maven-file-management.git"); + try { + this.validateGenericGitRepo(git); + } finally { + git.close(); + } + } + + @Test + public void cachePublicRepoViaSsh() throws GitAPIException { + Git git = LocalRepositoryCache.getInstance().acquire("git@github.com:bmlong137/maven-file-management.git"); + try { + this.validateGenericGitRepo(git); + } finally { + git.close(); + } + } + + /** + * Since "github-api" 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"); + } + + @Test + public void cachePrivateRepo() throws GitAPIException { + Git git = LocalRepositoryCache.getInstance().acquire("https://github.com/bmlong137/github-api.git", githubCreds); + try { + this.validateGenericGitRepo(git); + } finally { + git.close(); + } + } + + @Test + public void cachePublicRepoBranch() throws GitAPIException, IOException { + Git git = LocalRepositoryCache.getInstance().acquire("https://github.com/bmlong137/maven-file-management.git", "master"); + try { + this.validateGenericGitRepo(git); + + Assert.assertEquals("master", git.getRepository().getBranch()); + } finally { + git.close(); + } + } + + private void validateGenericGitRepo(Git git) throws GitAPIException { + Assert.assertNotNull(git); + + Repository repo = git.getRepository(); + Assert.assertTrue(repo.getDirectory().exists()); + Assert.assertTrue(repo.getWorkTree().exists()); + Assert.assertTrue(repo.getWorkTree().listFiles().length > 0); + } + +} diff --git a/src/test/resources/log4j2.xml b/src/test/resources/log4j2.xml new file mode 100644 index 0000000..9ee57d9 --- /dev/null +++ b/src/test/resources/log4j2.xml @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + +