Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
658047c24e | ||
|
|
d8118cbfd9 | ||
|
|
cdef7fe78f | ||
|
|
9395f4e310 | ||
|
|
3536943567 | ||
|
|
a58e369ada | ||
|
|
a076eb5986 | ||
|
|
77820bb25b | ||
|
|
8e1b8230c4 | ||
|
|
3cfa8af217 | ||
|
|
d6817a9b8d | ||
|
|
06db1d5d45 | ||
|
|
909fde9477 | ||
|
|
2c0503102f | ||
|
|
03ace4ca6c |
@@ -1,4 +1,7 @@
|
||||
# Maven
|
||||
pom.xml.versionsBackup
|
||||
|
||||
# Eclipse
|
||||
target
|
||||
.project
|
||||
.classpath
|
||||
|
||||
@@ -2,10 +2,10 @@
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<groupId>me.brianlong</groupId>
|
||||
<groupId>com.inteligr8</groupId>
|
||||
<artifactId>git-utils</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
<version>1.1-SNAPSHOT</version>
|
||||
<version>1.1.1</version>
|
||||
|
||||
<name>Git Utilities</name>
|
||||
|
||||
@@ -71,4 +71,13 @@
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
<distributionManagement>
|
||||
<repository>
|
||||
<id>inteligr8-releases</id>
|
||||
<name>Inteligr8 Releases</name>
|
||||
<url>http://repos.yateslong.us/nexus/repository/inteligr8-public</url>
|
||||
<layout>default</layout>
|
||||
</repository>
|
||||
</distributionManagement>
|
||||
</project>
|
||||
|
||||
+13
-1
@@ -1,4 +1,6 @@
|
||||
package me.brianlong.git;
|
||||
package com.inteligr8.git;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.eclipse.jgit.api.CloneCommand;
|
||||
import org.eclipse.jgit.api.Git;
|
||||
@@ -6,6 +8,8 @@ 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.RemoteConfig;
|
||||
import org.eclipse.jgit.transport.URIish;
|
||||
|
||||
public class CachedGit extends Git {
|
||||
|
||||
@@ -21,6 +25,14 @@ public class CachedGit extends Git {
|
||||
super(repo);
|
||||
}
|
||||
|
||||
public URIish getRemoteUrl(String remoteName) throws GitAPIException {
|
||||
List<RemoteConfig> configs = this.remoteList().call();
|
||||
for (RemoteConfig config : configs)
|
||||
if (config.getName().equals(remoteName))
|
||||
return config.getURIs().iterator().next();
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
LocalRepositoryCache.getInstance().release(this);
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package me.brianlong.git;
|
||||
package com.inteligr8.git;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Iterator;
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package me.brianlong.git;
|
||||
package com.inteligr8.git;
|
||||
|
||||
import org.eclipse.jgit.api.CloneCommand;
|
||||
import org.eclipse.jgit.api.FetchCommand;
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package me.brianlong.git;
|
||||
package com.inteligr8.git;
|
||||
|
||||
import java.util.Map.Entry;
|
||||
|
||||
+88
-58
@@ -1,39 +1,36 @@
|
||||
package me.brianlong.git;
|
||||
package com.inteligr8.git;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URISyntaxException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.eclipse.jgit.api.CloneCommand;
|
||||
import org.eclipse.jgit.api.Git;
|
||||
import org.eclipse.jgit.api.ListBranchCommand;
|
||||
import org.eclipse.jgit.api.CreateBranchCommand.SetupUpstreamMode;
|
||||
import org.eclipse.jgit.api.ListBranchCommand.ListMode;
|
||||
import org.eclipse.jgit.api.ResetCommand.ResetType;
|
||||
import org.eclipse.jgit.api.errors.CheckoutConflictException;
|
||||
import org.eclipse.jgit.api.errors.GitAPIException;
|
||||
import org.eclipse.jgit.api.errors.InvalidRefNameException;
|
||||
import org.eclipse.jgit.api.errors.InvalidRemoteException;
|
||||
import org.eclipse.jgit.api.errors.RefAlreadyExistsException;
|
||||
import org.eclipse.jgit.api.errors.RefNotFoundException;
|
||||
import org.eclipse.jgit.api.errors.TransportException;
|
||||
import org.eclipse.jgit.lib.Constants;
|
||||
import org.eclipse.jgit.lib.ObjectId;
|
||||
import org.eclipse.jgit.lib.Ref;
|
||||
import org.eclipse.jgit.lib.Repository;
|
||||
import org.eclipse.jgit.transport.RemoteConfig;
|
||||
import org.eclipse.jgit.transport.URIish;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class ExtendedGit extends CachedGit {
|
||||
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(ExtendedGit.class);
|
||||
private final Pattern gitUrlPattern = Pattern.compile("(((ssh|http(s)?)://([^@]+@)?([^:/]+)(:[0-9]+)?/)|git@([^:]+):)([\\w\\.@\\:/\\-~]+)(\\.git)");
|
||||
|
||||
public ExtendedGit(Git git) {
|
||||
super(git.getRepository());
|
||||
@@ -47,68 +44,87 @@ 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())
|
||||
this.logger.debug("Remote URL: " + gitUrl);
|
||||
public Collection<RemoteConfig> findRemotesByHostname(String hostname) throws GitAPIException {
|
||||
List<RemoteConfig> matchingRemotes = new LinkedList<>();
|
||||
|
||||
Matcher matcher = this.gitUrlPattern.matcher(gitUrl);
|
||||
if (!matcher.find())
|
||||
throw new URISyntaxException(gitUrl, "The Git URL does not match the expected regular expression: " + this.gitUrlPattern.toString());
|
||||
return matcher.group(9);
|
||||
List<RemoteConfig> remotes = this.remoteList().call();
|
||||
for (RemoteConfig remote : remotes) {
|
||||
for (URIish uri : remote.getURIs()) {
|
||||
GitUrl url = new GitUrl(uri);
|
||||
if (hostname.equalsIgnoreCase(url.getHostname())) {
|
||||
matchingRemotes.add(remote);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return matchingRemotes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a local branch, if one does not already exist.
|
||||
* Checks out the repository, targeting the remote branch.
|
||||
* Resets the local branch up to the remote branch.
|
||||
*/
|
||||
public Ref normalize(String localBranchName, String exactRemoteBranchName)
|
||||
throws RefAlreadyExistsException, RefNotFoundException, InvalidRefNameException, CheckoutConflictException, GitAPIException, IOException {
|
||||
public Collection<String> findRemoteNames(String branchName) throws IOException, GitAPIException {
|
||||
List<String> remoteNames = new LinkedList<>();
|
||||
|
||||
List<RemoteConfig> remotes = this.remoteList().call();
|
||||
for (RemoteConfig remote : remotes)
|
||||
if (this.getRepository().exactRef(Constants.R_REMOTES + remote.getName() + "/" + branchName) != null)
|
||||
remoteNames.add(remote.getName());
|
||||
|
||||
return remoteNames;
|
||||
}
|
||||
|
||||
public Collection<Ref> findRemoteRefsByName(String branchName) throws IOException, GitAPIException {
|
||||
List<Ref> refs = new LinkedList<>();
|
||||
|
||||
List<RemoteConfig> remotes = this.remoteList().call();
|
||||
for (RemoteConfig remote : remotes) {
|
||||
Ref ref = this.getRepository().exactRef(Constants.R_REMOTES + remote.getName() + "/" + branchName);
|
||||
if (ref != null)
|
||||
refs.add(ref);
|
||||
}
|
||||
|
||||
return refs;
|
||||
}
|
||||
|
||||
public Ref resetAndCheckout(Ref ref) throws IOException, GitAPIException {
|
||||
if (!ref.getName().startsWith(Constants.R_HEADS))
|
||||
throw new IllegalArgumentException("The '" + ref + "' is not a local ref");
|
||||
return this._resetAndCheckout(ref);
|
||||
|
||||
}
|
||||
|
||||
public Ref resetAndCheckout(String branchName) throws IOException, GitAPIException {
|
||||
if (this.logger.isTraceEnabled())
|
||||
this.logger.trace("normalize('" + localBranchName + "', '" + exactRemoteBranchName + "')");
|
||||
this.logger.trace("resetOrCheckout('" + branchName + "')");
|
||||
|
||||
Ref localRef = this.getRepository().exactRef(Constants.R_HEADS + localBranchName);
|
||||
if (localRef == null) {
|
||||
if (this.logger.isDebugEnabled())
|
||||
this.logger.debug("normalize('" + localBranchName + "', '" + exactRemoteBranchName + "'): local branch does not yet exist");
|
||||
localRef = this.branchCreate()
|
||||
.setUpstreamMode(SetupUpstreamMode.NOTRACK)
|
||||
.setName(localBranchName)
|
||||
.setStartPoint(exactRemoteBranchName)
|
||||
.call();
|
||||
if (this.logger.isDebugEnabled())
|
||||
this.logger.debug("normalize('" + localBranchName + "', '" + exactRemoteBranchName + "'): created local branch: " + localRef.getName());
|
||||
Ref localRef = this.getRepository().exactRef(Constants.R_HEADS + branchName);
|
||||
if (localRef == null)
|
||||
throw new IllegalArgumentException("The '" + branchName + "' branch must already exist");
|
||||
return this._resetAndCheckout(localRef);
|
||||
}
|
||||
|
||||
private Ref _resetAndCheckout(Ref ref) throws IOException, GitAPIException {
|
||||
Ref localRef = ref;
|
||||
|
||||
if (!this.status().call().isClean()) {
|
||||
if (this.logger.isTraceEnabled())
|
||||
this.logger.trace("resetOrCheckout('" + ref + "'): repo is dirty");
|
||||
localRef = this.reset().setMode(ResetType.HARD).call();
|
||||
}
|
||||
|
||||
Ref checkoutRef = this.checkout()
|
||||
.setName(localBranchName)
|
||||
.setStartPoint(exactRemoteBranchName)
|
||||
.call();
|
||||
if (this.logger.isDebugEnabled())
|
||||
this.logger.debug("normalize('" + localBranchName + "', '" + exactRemoteBranchName + "'): checked out");
|
||||
|
||||
if (!localRef.getObjectId().getName().equals(checkoutRef.getObjectId().getName())) {
|
||||
this.logger.warn("A checkout did not move the local branch to the proper commit; performing reset: " + localRef.getName());
|
||||
this.reset()
|
||||
.setMode(ResetType.HARD)
|
||||
.setRef(exactRemoteBranchName)
|
||||
.call();
|
||||
if (this.logger.isDebugEnabled())
|
||||
this.logger.debug("normalize('" + localBranchName + "', '" + exactRemoteBranchName + "'): reset");
|
||||
if (!this.getRepository().getFullBranch().equals(localRef.getName())) {
|
||||
if (this.logger.isTraceEnabled())
|
||||
this.logger.trace("resetOrCheckout('" + ref + "'): repo is on a different branch");
|
||||
localRef = this.checkout().setName(localRef.getName()).call();
|
||||
}
|
||||
|
||||
return checkoutRef;
|
||||
return localRef;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method retrieves all branches, but excludes remote branches that are tracked with a local branch.
|
||||
* @return
|
||||
*/
|
||||
@Deprecated
|
||||
public List<Ref> getAllUniqueBranches() throws GitAPIException {
|
||||
return this.getAllUniqueBranches(null);
|
||||
}
|
||||
@@ -117,6 +133,7 @@ public class ExtendedGit extends CachedGit {
|
||||
* This method retrieves all branches, but excludes remote branches that are tracked with a local branch.
|
||||
* @return
|
||||
*/
|
||||
@Deprecated
|
||||
public List<Ref> getAllUniqueBranches(String containsCommitish) throws GitAPIException {
|
||||
if (this.logger.isTraceEnabled())
|
||||
this.logger.trace("getAllUniqueBranches('" + containsCommitish + "')");
|
||||
@@ -185,5 +202,18 @@ public class ExtendedGit extends CachedGit {
|
||||
|
||||
return trimmedBranches;
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
private boolean allSameCommit(Collection<Ref> refs) {
|
||||
if (refs.size() < 2)
|
||||
return true;
|
||||
|
||||
Iterator<Ref> i = refs.iterator();
|
||||
ObjectId commit = i.next().getObjectId();
|
||||
while (i.hasNext())
|
||||
if (!commit.equals(i.next().getObjectId()))
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
package com.inteligr8.git;
|
||||
|
||||
import java.net.URISyntaxException;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.eclipse.jgit.transport.URIish;
|
||||
|
||||
public class GitUrl {
|
||||
|
||||
private static final Pattern gitUrlPattern = Pattern.compile("(((ssh|http(s)?)://([^@]+@)?([^:/]+)(:[0-9]+)?/)|git@([^:]+):)([\\w\\.@\\:/\\-~]+)(\\.git)");
|
||||
|
||||
private final String url;
|
||||
private final String hostname;
|
||||
private final String fullyQualifiedRepositoryName;
|
||||
|
||||
public GitUrl(URIish url) {
|
||||
this.url = url.toString();
|
||||
|
||||
Matcher matcher = gitUrlPattern.matcher(url.toString());
|
||||
if (!matcher.find())
|
||||
throw new IllegalArgumentException("The '" + url + "' URL does not match the expected pattern: " + gitUrlPattern.toString());
|
||||
|
||||
this.hostname = matcher.group(6) != null ? matcher.group(6) : matcher.group(8);
|
||||
this.fullyQualifiedRepositoryName = matcher.group(9);
|
||||
}
|
||||
|
||||
public GitUrl(String url) throws URISyntaxException {
|
||||
this(new URIish(url));
|
||||
}
|
||||
|
||||
public String getUrl() {
|
||||
return this.url;
|
||||
}
|
||||
|
||||
public String getHostname() {
|
||||
return this.hostname;
|
||||
}
|
||||
|
||||
public String getFullyQualifiedRepositoryName() {
|
||||
return this.fullyQualifiedRepositoryName;
|
||||
}
|
||||
|
||||
}
|
||||
+79
-61
@@ -1,4 +1,4 @@
|
||||
package me.brianlong.git;
|
||||
package com.inteligr8.git;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Collection;
|
||||
@@ -17,24 +17,25 @@ public class LRUExpiringHashMap<K extends Serializable, V> implements ListeningM
|
||||
|
||||
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 long expirationTimeMillis;
|
||||
|
||||
public LRUExpiringHashMap(int expirationTimeInMinutes) {
|
||||
this.map = new LinkedHashMap<ExpiringHashKey, V>();
|
||||
this.map = new LinkedHashMap<>();
|
||||
this.expirationTimeMillis = expirationTimeInMinutes * 60L + 1000L;
|
||||
}
|
||||
|
||||
public LRUExpiringHashMap(int expirationTimeInMinutes, int initialCapacity) {
|
||||
this.map = new LinkedHashMap<ExpiringHashKey, V>(initialCapacity);
|
||||
this.map = new LinkedHashMap<>(initialCapacity);
|
||||
this.expirationTimeMillis = expirationTimeInMinutes * 60L + 1000L;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addListener(MapListener<K, V> 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<K extends Serializable, V> 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<K, V> 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<K, V> listener : this.listeners)
|
||||
listener.added(new ExpiringHashMapEntry(key, value));
|
||||
return oldValue;
|
||||
return oldValue == null ? null : oldValue.getValue();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clear() {
|
||||
for (Entry<ExpiringHashKey, V> entry : this.map.entrySet()) {
|
||||
for (MapListener<K, V> listener : this.listeners)
|
||||
listener.cleared(new ExpiringHashMapEntry(entry.getKey().getKey(), entry.getValue()));
|
||||
}
|
||||
List<ExpiringHashMapEntry> entries = new LinkedList<>();
|
||||
|
||||
this.map.clear();
|
||||
synchronized (this.sync) {
|
||||
for (Entry<K, ExpiringValue> entry : this.map.entrySet())
|
||||
entries.add(new ExpiringHashMapEntry(entry.getKey(), entry.getValue().getValue()));
|
||||
this.map.clear();
|
||||
}
|
||||
|
||||
for (ExpiringHashMapEntry entry : entries)
|
||||
for (MapListener<K, V> 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<K, V> 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<K extends Serializable, V> implements ListeningM
|
||||
}
|
||||
|
||||
public void exipriationCheck() {
|
||||
Iterator<Entry<ExpiringHashKey, V>> i = this.map.entrySet().iterator();
|
||||
for (Entry<ExpiringHashKey, V> entry = i.next(); i.hasNext(); entry = i.next()) {
|
||||
if (entry.getKey().isExpired()) {
|
||||
i.remove();
|
||||
for (MapListener<K, V> listener : this.listeners)
|
||||
if (listener instanceof ExpiringMapListener)
|
||||
((ExpiringMapListener<K, V>)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<ExpiringHashMapEntry> entries = new LinkedList<>();
|
||||
|
||||
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();
|
||||
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)
|
||||
if (listener instanceof ExpiringMapListener)
|
||||
((ExpiringMapListener<K, V>)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<K, V> listener : this.listeners)
|
||||
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 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();
|
||||
}
|
||||
|
||||
}
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package me.brianlong.git;
|
||||
package com.inteligr8.git;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
@@ -0,0 +1,273 @@
|
||||
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.lib.Constants;
|
||||
import org.eclipse.jgit.transport.CredentialsProvider;
|
||||
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<String, CachedGit> cachedGits = new LRUExpiringHashMap<>(30);
|
||||
private final Map<String, Semaphore> gitUrlSemaphores = new HashMap<>();
|
||||
final File cacheDirectory = new File(System.getProperty("java.io.tmpdir"), "git");
|
||||
private int simultaneousThreadsPerGitRepo = 1;
|
||||
|
||||
private LocalRepositoryCache() {
|
||||
this.cacheDirectory.mkdir();
|
||||
this.cachedGits.addListener(new RepositoryCacheMapListener<CachedGit>());
|
||||
}
|
||||
|
||||
/**
|
||||
* This is helpful for the tweaking performance based on the environment of
|
||||
* execution. A value of 1 disables concurrency which results in the
|
||||
* persistence of Git Repositories between executions and disables cache
|
||||
* expiration. This is great for development, testing, and command line
|
||||
* usage. It is not recommended with web service frontends.
|
||||
*/
|
||||
public void setSimultaneousThreadsPerGitRepository(int threads) {
|
||||
this.simultaneousThreadsPerGitRepo = threads;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void finalize() throws Throwable {
|
||||
try {
|
||||
this.destroy();
|
||||
} finally {
|
||||
super.finalize();
|
||||
}
|
||||
}
|
||||
|
||||
private void destroy() {
|
||||
if (this.logger.isDebugEnabled())
|
||||
this.logger.debug("clearing local repo cache");
|
||||
this.clear();
|
||||
}
|
||||
|
||||
public CachedGit acquire(String url) throws InterruptedException, InvalidRemoteException, RefNotFoundException, TransportException, GitAPIException {
|
||||
return this.acquire(url, null, null);
|
||||
}
|
||||
|
||||
public CachedGit acquire(String url, CredentialsProvider creds) throws InterruptedException, InvalidRemoteException, RefNotFoundException, TransportException, GitAPIException {
|
||||
return this.acquire(url, creds, null);
|
||||
}
|
||||
|
||||
public CachedGit acquire(String url, String branch) throws InterruptedException, InvalidRemoteException, RefNotFoundException, TransportException, GitAPIException {
|
||||
return this.acquire(url, null, branch);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @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 + "')");
|
||||
|
||||
Semaphore semaphore = null;
|
||||
synchronized (this) {
|
||||
semaphore = this.gitUrlSemaphores.get(url);
|
||||
if (semaphore == null)
|
||||
this.gitUrlSemaphores.put(url, semaphore = new Semaphore(this.simultaneousThreadsPerGitRepo));
|
||||
}
|
||||
|
||||
File gitRepoDirectory = null;
|
||||
|
||||
semaphore.acquire();
|
||||
try {
|
||||
CachedGit git = this.cachedGits.remove(url);
|
||||
if (git == null) {
|
||||
String directoryBaseName;
|
||||
if (this.simultaneousThreadsPerGitRepo == 1)
|
||||
directoryBaseName = new GitUrl(url).getFullyQualifiedRepositoryName().replace('/', '_');
|
||||
else directoryBaseName = UUID.randomUUID().toString();
|
||||
|
||||
gitRepoDirectory = new File(this.cacheDirectory, directoryBaseName + ".git");
|
||||
if (this.logger.isDebugEnabled())
|
||||
this.logger.debug("Git directory cache for clone: " + gitRepoDirectory);
|
||||
|
||||
if (gitRepoDirectory.exists()) {
|
||||
if (this.logger.isInfoEnabled())
|
||||
this.logger.info("Git Repository already exists; reusing: " + gitRepoDirectory);
|
||||
git = creds != null ? new CredentialedGit(Git.open(gitRepoDirectory), creds) : new ExtendedGit(Git.open(gitRepoDirectory));
|
||||
}
|
||||
}
|
||||
|
||||
if (git == null) {
|
||||
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: " + new GitUrl(git.getRemoteUrl("origin")).getFullyQualifiedRepositoryName());
|
||||
} else {
|
||||
if (this.logger.isDebugEnabled())
|
||||
this.logger.debug("resetting Git");
|
||||
git.reset().setMode(ResetType.HARD).call();
|
||||
|
||||
if (branch != null && !branch.equals(git.getRepository().getBranch())) {
|
||||
if (this.logger.isDebugEnabled())
|
||||
this.logger.debug("switching Git branches: " + branch);
|
||||
if (git.getRepository().exactRef(Constants.R_HEADS + branch) == null) {
|
||||
git.checkout().setName(branch).setStartPoint("origin/" + branch).setCreateBranch(true).call();
|
||||
} else {
|
||||
git.checkout().setName(branch).call();
|
||||
}
|
||||
}
|
||||
|
||||
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 IllegalArgumentException("The Git Repository URL is not properly formatted", 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())
|
||||
this.logger.trace("release('" + git.getRepository().getIdentifier() + "')");
|
||||
|
||||
try {
|
||||
String url = git.getRemoteUrl("origin").toString();
|
||||
|
||||
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 void clear() {
|
||||
if (this.logger.isTraceEnabled())
|
||||
this.logger.trace("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) {
|
||||
File gitDir = git.getRepository().getDirectory();
|
||||
File workingTreeDir = git.getRepository().getWorkTree();
|
||||
git.getRepository().close();
|
||||
|
||||
try {
|
||||
if (this.logger.isDebugEnabled())
|
||||
this.logger.debug("deleting: " + gitDir);
|
||||
FileUtils.delete(gitDir, FileUtils.RECURSIVE);
|
||||
} catch (IOException ie) {
|
||||
this.logger.warn("Failed to delete a git directory: " + gitDir);
|
||||
if (this.logger.isDebugEnabled())
|
||||
this.logger.debug(ie.getMessage(), ie);
|
||||
}
|
||||
|
||||
try {
|
||||
if (this.logger.isDebugEnabled())
|
||||
this.logger.debug("deleting: " + workingTreeDir);
|
||||
FileUtils.delete(workingTreeDir, FileUtils.RECURSIVE);
|
||||
} catch (IOException ie) {
|
||||
this.logger.warn("Failed to delete a git directory: " + workingTreeDir);
|
||||
if (this.logger.isDebugEnabled())
|
||||
this.logger.debug(ie.getMessage(), ie);
|
||||
}
|
||||
|
||||
if (this.logger.isInfoEnabled())
|
||||
this.logger.info("Deleted Git Repository: " + gitDir);
|
||||
}
|
||||
|
||||
|
||||
|
||||
private class RepositoryCacheMapListener<T extends CachedGit> implements ExpiringMapListener<String, T> {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(LocalRepositoryCache.class);
|
||||
|
||||
@Override
|
||||
public void accessed(Entry<String, T> entry) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void added(Entry<String, T> entry) {
|
||||
// a clean one or one returned after being previously removed
|
||||
}
|
||||
|
||||
@Override
|
||||
public void expired(Entry<String, T> entry ) {
|
||||
if (this.logger.isTraceEnabled())
|
||||
this.logger.trace("expired('" + entry.getKey() + "', '" + entry.getValue().getRepository().getIdentifier() + "')");
|
||||
expunge(entry.getValue());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removed(Entry<String, T> entry) {
|
||||
// expected to be removed only temporarily...for use elsewhere; do not close
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cleared(Entry<String, T> entry) {
|
||||
if (this.logger.isTraceEnabled())
|
||||
this.logger.trace("cleared('" + entry.getKey() + "', '" + entry.getValue().getRepository().getIdentifier() + "')");
|
||||
expunge(entry.getValue());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package me.brianlong.git;
|
||||
package com.inteligr8.git;
|
||||
|
||||
import java.util.Map.Entry;
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package me.brianlong.git;
|
||||
package com.inteligr8.git;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
@@ -1,186 +0,0 @@
|
||||
package me.brianlong.git;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
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.transport.CredentialsProvider;
|
||||
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<String, Git> cachedGits = new LRUExpiringHashMap<String, Git>(30);
|
||||
// private final Map<String, String> gitIdsToUrls = new HashMap<String, String>();
|
||||
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.isDebugEnabled())
|
||||
this.logger.debug("clearing local repo cache");
|
||||
this.clear();
|
||||
}
|
||||
|
||||
public synchronized ExtendedGit acquire(String url) throws GitAPIException, InvalidRemoteException, RefNotFoundException {
|
||||
return this.acquire(url, null, null);
|
||||
}
|
||||
|
||||
public synchronized ExtendedGit acquire(String url, CredentialsProvider creds) throws GitAPIException, InvalidRemoteException, RefNotFoundException {
|
||||
return this.acquire(url, creds, null);
|
||||
}
|
||||
|
||||
public synchronized ExtendedGit acquire(String url, String branch) throws GitAPIException, InvalidRemoteException, RefNotFoundException {
|
||||
return this.acquire(url, null, branch);
|
||||
}
|
||||
|
||||
public synchronized ExtendedGit acquire(String url, CredentialsProvider creds, String branch) throws GitAPIException, InvalidRemoteException, RefNotFoundException {
|
||||
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");
|
||||
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();
|
||||
// }
|
||||
}
|
||||
|
||||
public synchronized void release(Git 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);
|
||||
}
|
||||
|
||||
public synchronized void clear() {
|
||||
if (this.logger.isTraceEnabled())
|
||||
this.logger.trace("clear()");
|
||||
|
||||
// this.cachedGits.clear();
|
||||
}
|
||||
|
||||
private void expunge(Git git) {
|
||||
// gitIdsToUrls.remove(git.getRepository().getIdentifier());
|
||||
|
||||
File gitDir = git.getRepository().getDirectory();
|
||||
File workingTreeDir = git.getRepository().getWorkTree();
|
||||
git.getRepository().close();
|
||||
|
||||
try {
|
||||
if (this.logger.isDebugEnabled())
|
||||
this.logger.debug("deleting: " + gitDir);
|
||||
FileUtils.delete(gitDir, FileUtils.RECURSIVE);
|
||||
} catch (IOException ie) {
|
||||
this.logger.warn("Failed to delete a git directory: " + gitDir);
|
||||
if (this.logger.isDebugEnabled())
|
||||
this.logger.debug(ie.getMessage(), ie);
|
||||
gitDir.deleteOnExit();
|
||||
}
|
||||
|
||||
try {
|
||||
if (this.logger.isDebugEnabled())
|
||||
this.logger.debug("deleting: " + workingTreeDir);
|
||||
FileUtils.delete(workingTreeDir, FileUtils.RECURSIVE);
|
||||
} catch (IOException ie) {
|
||||
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");
|
||||
}
|
||||
|
||||
|
||||
|
||||
private class RepositoryCacheMapListener implements ExpiringMapListener<String, Git> {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(LocalRepositoryCache.class);
|
||||
|
||||
@Override
|
||||
public void accessed(Entry<String, Git> entry) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void added(Entry<String, Git> entry) {
|
||||
// a clean one or one returned after being previously removed
|
||||
}
|
||||
|
||||
@Override
|
||||
public void expired(Entry<String, Git> entry) {
|
||||
if (this.logger.isTraceEnabled())
|
||||
this.logger.trace("expired('" + entry.getKey() + "', '" + entry.getValue().getRepository().getIdentifier() + "')");
|
||||
expunge(entry.getValue());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removed(Entry<String, Git> entry) {
|
||||
// expected to be removed only temporarily...for use elsewhere; do not close
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cleared(Entry<String, Git> entry) {
|
||||
if (this.logger.isTraceEnabled())
|
||||
this.logger.trace("cleared('" + entry.getKey() + "', '" + entry.getValue().getRepository().getIdentifier() + "')");
|
||||
expunge(entry.getValue());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
+12
-10
@@ -1,7 +1,8 @@
|
||||
package me.brianlong.git;
|
||||
package com.inteligr8.git;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
@@ -10,8 +11,6 @@ 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.eclipse.jgit.transport.CredentialsProvider;
|
||||
import org.eclipse.jgit.transport.UsernamePasswordCredentialsProvider;
|
||||
import org.eclipse.jgit.util.FileUtils;
|
||||
import org.junit.AfterClass;
|
||||
import org.junit.Assert;
|
||||
@@ -19,9 +18,6 @@ import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
|
||||
public class CommandUnitTest {
|
||||
|
||||
private final static CredentialsProvider gitCreds = new UsernamePasswordCredentialsProvider(
|
||||
System.getProperty("git.username"), System.getProperty("git.token"));
|
||||
|
||||
private static File tmpdir;
|
||||
private static Git git;
|
||||
@@ -31,8 +27,7 @@ public class CommandUnitTest {
|
||||
tmpdir = new File(System.getProperty("java.io.tmpdir"), "git-" + UUID.randomUUID().toString() + ".tmp");
|
||||
|
||||
git = new CloneCommand()
|
||||
.setURI("git@github.com:bmlong137/env-docker-adbp.git")
|
||||
.setCredentialsProvider(gitCreds)
|
||||
.setURI("git@bitbucket.org:inteligr8/git-utils.git")
|
||||
.setDirectory(tmpdir)
|
||||
.call();
|
||||
}
|
||||
@@ -45,10 +40,17 @@ public class CommandUnitTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void lotsOfBranches() throws GitAPIException {
|
||||
public void listOfBranches() throws GitAPIException, IOException {
|
||||
List<Ref> remoteBranches = git.branchList().setListMode(ListMode.REMOTE).call();
|
||||
Assert.assertNotNull(remoteBranches);
|
||||
Assert.assertTrue(remoteBranches.size() > 5);
|
||||
Assert.assertTrue(remoteBranches.contains(git.getRepository().findRef("origin/develop")));
|
||||
Assert.assertTrue(remoteBranches.contains(git.getRepository().findRef("origin/stable")));
|
||||
|
||||
List<Ref> localBranches = git.branchList().call();
|
||||
Assert.assertNotNull(localBranches);
|
||||
Assert.assertEquals(1, localBranches.size());
|
||||
|
||||
Assert.assertTrue(Collections.disjoint(remoteBranches, localBranches));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,196 @@
|
||||
package com.inteligr8.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.eclipse.jgit.transport.CredentialsProvider;
|
||||
import org.eclipse.jgit.transport.UsernamePasswordCredentialsProvider;
|
||||
import org.junit.After;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
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("bitbucket.inteligr8.username"), System.getProperty("bitbucket.inteligr8.token"));
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 tryBadHostRepo() throws GitAPIException, InterruptedException {
|
||||
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 tryNonExistentRepoViaSsh() throws GitAPIException, InterruptedException {
|
||||
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 tryNonExistentRepo() throws GitAPIException, InterruptedException {
|
||||
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 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, InterruptedException {
|
||||
Git git = LocalRepositoryCache.getInstance().acquire("https://bitbucket.org/inteligr8/git-utils.git");
|
||||
try {
|
||||
this.validateGenericGitRepo(git);
|
||||
} finally {
|
||||
git.close();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void cachePublicRepoViaSsh() throws GitAPIException, InterruptedException {
|
||||
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 {
|
||||
this.validateGenericGitRepo(git);
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,127 +0,0 @@
|
||||
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.eclipse.jgit.transport.CredentialsProvider;
|
||||
import org.eclipse.jgit.transport.UsernamePasswordCredentialsProvider;
|
||||
import org.junit.AfterClass;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
public class LocalRepositoryCacheUnitTest {
|
||||
|
||||
private final static CredentialsProvider gitCreds = new UsernamePasswordCredentialsProvider(
|
||||
System.getProperty("git.username"), System.getProperty("git.token"));
|
||||
|
||||
@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", gitCreds);
|
||||
}
|
||||
|
||||
@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", gitCreds);
|
||||
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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -6,7 +6,7 @@
|
||||
</Console>
|
||||
</Appenders>
|
||||
<Loggers>
|
||||
<Logger name="me.brianlong" level="trace" additivity="false">
|
||||
<Logger name="com.inteligr8.git" level="trace" additivity="false">
|
||||
<AppenderRef ref="STDOUT" />
|
||||
</Logger>
|
||||
<Logger name="org.apache.http" level="trace" additivity="false">
|
||||
|
||||
Reference in New Issue
Block a user