From ef829b45f28c10b6674657274939173f7b9f611d Mon Sep 17 00:00:00 2001 From: Brian Long Date: Mon, 7 Dec 2020 13:46:48 -0500 Subject: [PATCH] major refactoring --- .../me/brianlong/git/CompositeIterator.java | 42 ++++ .../me/brianlong/git/CredentialedGit.java | 2 +- .../java/me/brianlong/git/ExtendedGit.java | 127 ++++++++++ .../brianlong/git/LocalRepositoryCache.java | 10 +- .../git/UniquePriorityFifoQueue.java | 236 ++++++++++++++++++ 5 files changed, 411 insertions(+), 6 deletions(-) create mode 100644 src/main/java/me/brianlong/git/CompositeIterator.java create mode 100644 src/main/java/me/brianlong/git/ExtendedGit.java create mode 100644 src/main/java/me/brianlong/git/UniquePriorityFifoQueue.java diff --git a/src/main/java/me/brianlong/git/CompositeIterator.java b/src/main/java/me/brianlong/git/CompositeIterator.java new file mode 100644 index 0000000..860994b --- /dev/null +++ b/src/main/java/me/brianlong/git/CompositeIterator.java @@ -0,0 +1,42 @@ +package me.brianlong.git; + +import java.util.Collection; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.NoSuchElementException; + +public class CompositeIterator implements Iterator { + + private final List> iterators = new LinkedList>(); + private final Iterator> iteratorIterators; + private Iterator iterator; + + public CompositeIterator(@SuppressWarnings("unchecked") Collection... cs) { + for (Collection c : cs) + if (c != null && !c.isEmpty()) + this.iterators.add(c.iterator()); + this.iteratorIterators = this.iterators.iterator(); + } + + @Override + public boolean hasNext() { + if (this.iterator == null) { + if (!this.iteratorIterators.hasNext()) + return false; + this.iterator = this.iteratorIterators.next(); + } + + while (!this.iterator.hasNext() && this.iteratorIterators.hasNext()) + this.iterator = this.iteratorIterators.next(); + return this.iterator.hasNext(); + } + + @Override + public E next() { + if (!this.hasNext()) + throw new NoSuchElementException(); + return this.iterator.next(); + } + +} diff --git a/src/main/java/me/brianlong/git/CredentialedGit.java b/src/main/java/me/brianlong/git/CredentialedGit.java index 2c62b05..ce980c7 100644 --- a/src/main/java/me/brianlong/git/CredentialedGit.java +++ b/src/main/java/me/brianlong/git/CredentialedGit.java @@ -12,7 +12,7 @@ import org.eclipse.jgit.api.errors.TransportException; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.transport.CredentialsProvider; -public class CredentialedGit extends CachedGit { +public class CredentialedGit extends ExtendedGit { private CredentialsProvider credProvider; diff --git a/src/main/java/me/brianlong/git/ExtendedGit.java b/src/main/java/me/brianlong/git/ExtendedGit.java new file mode 100644 index 0000000..ff65e34 --- /dev/null +++ b/src/main/java/me/brianlong/git/ExtendedGit.java @@ -0,0 +1,127 @@ +package me.brianlong.git; + +import java.net.URISyntaxException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashSet; +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.ListBranchCommand.ListMode; +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.Constants; +import org.eclipse.jgit.lib.Ref; +import org.eclipse.jgit.lib.Repository; +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()); + } + + public ExtendedGit(CloneCommand clone) throws TransportException, InvalidRemoteException, GitAPIException { + super(clone.call().getRepository()); + } + + public ExtendedGit(Repository repo) { + 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); + + 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); + } + + /** + * This method retrieves all branches, but excludes remote branches that are tracked with a local branch. + * @return + */ + public List getAllUniqueBranches() throws GitAPIException { + return this.getAllUniqueBranches(null); + } + + /** + * This method retrieves all branches, but excludes remote branches that are tracked with a local branch. + * @return + */ + public List getAllUniqueBranches(String containsCommitish) throws GitAPIException { + ListBranchCommand command = this.branchList().setListMode(ListMode.ALL); + if (containsCommitish != null) + command.setContains(containsCommitish); + List branches = command.call(); + + Set abbrevBranchNames = new HashSet(branches.size()); + List trimmedBranches = new ArrayList(branches.size()); + + // sort them by local, then remote; keep order the same otherwise + Collections.sort(branches, new Comparator() { + @Override + public int compare(Ref o1, Ref o2) { + if (o1 == null) return -1; + else if (o2 == null) return 1; + else { + boolean o1local = o1.getName().startsWith(Constants.R_HEADS); + boolean o2local = o2.getName().startsWith(Constants.R_HEADS); + if (o1local && o2local) return 0; + else if (o1local) return -1; + else if (o2local) return 1; + else return 0; + } + } + }); + + for (Ref branch : branches) { + String fqBranchName = branch.getName(); + + String abbrevBranchName = null; + if (fqBranchName.startsWith(Constants.R_REMOTES)) { + if (this.logger.isDebugEnabled()) + this.logger.debug("Ref is a remote branch: " + fqBranchName); + abbrevBranchName = this.getRepository().shortenRemoteBranchName(fqBranchName); + } else if (fqBranchName.startsWith(Constants.R_HEADS)) { + if (this.logger.isDebugEnabled()) + this.logger.debug("Ref is a local branch: " + fqBranchName); + abbrevBranchName = Repository.shortenRefName(fqBranchName); + } else { + if (this.logger.isDebugEnabled()) + this.logger.debug("Ref is not a local or remote branch: " + fqBranchName); + } + + if (abbrevBranchName != null) { + if (abbrevBranchNames.contains(abbrevBranchName)) { + if (this.logger.isDebugEnabled()) + this.logger.debug("Branch already found; ignoring ..."); + } else { + abbrevBranchNames.add(abbrevBranchName); + trimmedBranches.add(branch); + } + } + } + + return trimmedBranches; + } + +} diff --git a/src/main/java/me/brianlong/git/LocalRepositoryCache.java b/src/main/java/me/brianlong/git/LocalRepositoryCache.java index ecca892..4946216 100644 --- a/src/main/java/me/brianlong/git/LocalRepositoryCache.java +++ b/src/main/java/me/brianlong/git/LocalRepositoryCache.java @@ -48,19 +48,19 @@ public class LocalRepositoryCache { this.clear(); } - public synchronized Git acquire(String url) throws GitAPIException, InvalidRemoteException, RefNotFoundException { + public synchronized ExtendedGit 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 { + public synchronized ExtendedGit 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 { + public synchronized ExtendedGit 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 { + 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 + "')"); @@ -78,7 +78,7 @@ public class LocalRepositoryCache { if (this.logger.isDebugEnabled()) this.logger.debug("cloning Git repository: " + url); - Git git = creds != null ? new CredentialedGit(clone, creds) : new CachedGit(clone); + ExtendedGit git = creds != null ? new CredentialedGit(clone, creds) : new ExtendedGit(clone); if (this.logger.isInfoEnabled()) this.logger.info("Cloned Git Repository"); return git; diff --git a/src/main/java/me/brianlong/git/UniquePriorityFifoQueue.java b/src/main/java/me/brianlong/git/UniquePriorityFifoQueue.java new file mode 100644 index 0000000..40ab2a6 --- /dev/null +++ b/src/main/java/me/brianlong/git/UniquePriorityFifoQueue.java @@ -0,0 +1,236 @@ +package me.brianlong.git; + +import java.util.Arrays; +import java.util.Collection; +import java.util.Comparator; +import java.util.Iterator; +import java.util.NoSuchElementException; +import java.util.Queue; +import java.util.TreeSet; + +public class UniquePriorityFifoQueue implements Queue { + + private final Counter counter = new SimpleCounter(); + private final Counter fakeCounter = new FakeCounter(); + private final TreeSet> elements; + + public UniquePriorityFifoQueue() { + this.elements = new TreeSet>(); + } + + public UniquePriorityFifoQueue(Collection c) { + this.elements = new TreeSet>(); + for (T element : c) + this.elements.add(new PriorityFifoElement(element, this.counter)); + } + + public UniquePriorityFifoQueue(final Comparator comparator) { + this.elements = new TreeSet>(new Comparator>() { + @Override + public int compare(PriorityFifoElement o1, PriorityFifoElement o2) { + int compare = comparator.compare(o1.element, o2.element); + if (compare != 0) return compare; + return o1.compareTo(o2); + } + }); + } + + @Override + public boolean add(T e) { + return this.elements.add(new PriorityFifoElement(e, this.counter)); + } + + @Override + public boolean addAll(Collection c) { + if (c != null) for (T e : c) + this.elements.add(new PriorityFifoElement(e, this.counter)); + return true; + } + + @Override + public void clear() { + this.elements.clear(); + } + + @SuppressWarnings("unchecked") + @Override + public boolean contains(Object o) { + return this.elements.contains(new PriorityFifoElement((T)o, this.fakeCounter)); + } + + @Override + public boolean containsAll(Collection c) { + if (c != null) for (Object o : c) + if (!this.contains(o)) + return false; + return true; + } + + @Override + public T element() { + return this.elements.iterator().next().element; + } + + @Override + public boolean isEmpty() { + return this.elements.isEmpty(); + } + + @Override + public Iterator iterator() { + final Iterator> i = this.elements.iterator(); + return new Iterator() { + @Override + public boolean hasNext() { + return i.hasNext(); + } + + @Override + public T next() { + return i.next().element; + } + + @Override + public void remove() { + i.remove(); + } + }; + } + + @Override + public boolean offer(T e) { + return this.elements.add(new PriorityFifoElement(e, this.counter)); + } + + @Override + public T peek() { + Iterator i = this.iterator(); + return i.hasNext() ? i.next() : null; + } + + @Override + public T poll() { + PriorityFifoElement element = this.elements.pollFirst(); + return element == null ? null : element.element; + } + + @Override + public T remove() { + PriorityFifoElement element = this.elements.pollFirst(); + if (element == null) + throw new NoSuchElementException(); + return element.element; + } + + @SuppressWarnings("unchecked") + @Override + public boolean remove(Object o) { + return this.elements.remove(new PriorityFifoElement((T)o, this.fakeCounter)); + } + + @Override + public boolean removeAll(Collection c) { + if (c != null) for (Object o : c) + this.remove(o); + return true; + } + + @Override + public boolean retainAll(Collection c) { + Iterator> i = this.elements.iterator(); + while (i.hasNext()) { + T e = i.next().element; + if (!c.contains(e)) + i.remove(); + } + + return true; + } + + @Override + public int size() { + return this.elements.size(); + } + + @SuppressWarnings("unchecked") + @Override + public Object[] toArray() { + Object[] objs = this.elements.toArray(); + Object[] ts = new Object[objs.length]; + for (int i = 0; i < objs.length; i++) + ts[i] = ((PriorityFifoElement)objs[i]).element; + return ts; + } + + @SuppressWarnings("unchecked") + public U[] toArray(U[] a) { + if (a.length < this.elements.size()) + a = Arrays.copyOf(a, this.elements.size()); + + int i = 0; + for (PriorityFifoElement element : this.elements) + a[i] = (U)element.element; + return a; + } + + + + private class PriorityFifoElement implements Comparable> { + + private final E element; + private final int fifoId; + + public PriorityFifoElement(E element, Counter counter) { + this.element = element; + this.fifoId = counter.next(); + } + + @Override + public int compareTo(PriorityFifoElement o) { + if (o == null) return 1; + else return this.fifoId - o.fifoId; + } + + @Override + public int hashCode() { + return this.element.hashCode(); + } + + @SuppressWarnings("rawtypes") + @Override + public boolean equals(Object obj) { + if (obj instanceof PriorityFifoElement) { + return this.element.equals(((PriorityFifoElement)obj).element); + } else { + return false; + } + } + + } + + private interface Counter { + + int next(); + + } + + public class FakeCounter implements Counter { + + public int next() { + return 0; + } + + } + + public class SimpleCounter implements Counter { + + private int count = 0; + + public synchronized int next() { + this.count++; + return this.count; + } + + } + +}