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.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.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, GitCredentials 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, GitCredentials 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 { private final Logger logger = LoggerFactory.getLogger(LocalRepositoryCache.class); @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) { if (this.logger.isTraceEnabled()) this.logger.trace("expired('" + entry.getKey() + "', '" + entry.getValue().getRepository().getIdentifier() + "')"); 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) { if (this.logger.isTraceEnabled()) this.logger.trace("cleared('" + entry.getKey() + "', '" + entry.getValue().getRepository().getIdentifier() + "')"); expunge(entry.getValue()); } } }