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<E> implements Iterator<E> {
+	
+	private final List<Iterator<E>> iterators = new LinkedList<Iterator<E>>();
+	private final Iterator<Iterator<E>> iteratorIterators;
+	private Iterator<E> iterator;
+	
+	public CompositeIterator(@SuppressWarnings("unchecked") Collection<E>... cs) {
+		for (Collection<E> 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<Ref> 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<Ref> getAllUniqueBranches(String containsCommitish) throws GitAPIException {
+		ListBranchCommand command = this.branchList().setListMode(ListMode.ALL);
+		if (containsCommitish != null)
+			command.setContains(containsCommitish);
+		List<Ref> branches = command.call();
+		
+		Set<String> abbrevBranchNames = new HashSet<String>(branches.size());
+		List<Ref> trimmedBranches = new ArrayList<Ref>(branches.size());
+		
+		// sort them by local, then remote; keep order the same otherwise
+		Collections.sort(branches, new Comparator<Ref>() {
+			@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<T> implements Queue<T> {
+	
+	private final Counter counter = new SimpleCounter();
+	private final Counter fakeCounter = new FakeCounter();
+	private final TreeSet<PriorityFifoElement<T>> elements;
+
+	public UniquePriorityFifoQueue() {
+		this.elements = new TreeSet<PriorityFifoElement<T>>();
+	}
+	
+	public UniquePriorityFifoQueue(Collection<? extends T> c) {
+		this.elements = new TreeSet<PriorityFifoElement<T>>();
+		for (T element : c)
+			this.elements.add(new PriorityFifoElement<T>(element, this.counter));
+	}
+	
+	public UniquePriorityFifoQueue(final Comparator<? super T> comparator) {
+		this.elements = new TreeSet<PriorityFifoElement<T>>(new Comparator<PriorityFifoElement<T>>() {
+			@Override
+			public int compare(PriorityFifoElement<T> o1, PriorityFifoElement<T> 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<T>(e, this.counter));
+	}
+	
+	@Override
+	public boolean addAll(Collection<? extends T> c) {
+		if (c != null) for (T e : c)
+			this.elements.add(new PriorityFifoElement<T>(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>((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<T> iterator() {
+		final Iterator<PriorityFifoElement<T>> i = this.elements.iterator();
+		return new Iterator<T>() {
+			@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<T>(e, this.counter));
+	}
+	
+	@Override
+	public T peek() {
+		Iterator<T> i = this.iterator();
+		return i.hasNext() ? i.next() : null;
+	}
+	
+	@Override
+	public T poll() {
+		PriorityFifoElement<T> element = this.elements.pollFirst();
+		return element == null ? null : element.element;
+	}
+	
+	@Override
+	public T remove() {
+		PriorityFifoElement<T> 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>((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<PriorityFifoElement<T>> 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<T>)objs[i]).element;
+		return ts;
+	}
+	
+	@SuppressWarnings("unchecked")
+	public <U extends Object> U[] toArray(U[] a) {
+		if (a.length < this.elements.size())
+			a = Arrays.copyOf(a, this.elements.size());
+
+		int i = 0;
+		for (PriorityFifoElement<T> element : this.elements)
+			a[i] = (U)element.element;
+		return a;
+	}
+	
+	
+	
+	private class PriorityFifoElement<E> implements Comparable<PriorityFifoElement<E>> {
+		
+		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<E> 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;
+		}
+		
+	}
+
+}