package com.palantir.semver; import java.io.IOException; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import org.eclipse.jgit.errors.IncorrectObjectTypeException; import org.eclipse.jgit.errors.MissingObjectException; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.Ref; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.revwalk.RevCommit; import org.eclipse.jgit.revwalk.RevSort; import org.eclipse.jgit.revwalk.RevWalk; import com.google.common.collect.Maps; import com.google.common.collect.Sets; public class Tags { private Tags() { // prevents instantiation } public static TagVersionAndCount getLatestTagVersionAndCount(Repository repo, String prefix) throws MissingObjectException, IncorrectObjectTypeException, IOException { TagAndVersion latestTag = getLatestTag(repo, prefix); if (latestTag == null) { throw new SemverGitflowPlugin.VersionApplicationException( "Cannot find any matching tags in history. You must have tags of form v0.1.2 in order to use semver"); } else { int count = getNumberOfCommitsSinceTag(repo, latestTag.tag); return new TagVersionAndCount(latestTag.version.getOriginalVersion(), count); } } public static TagVersionAndCount getTopoTagVersionAndCount(Repository repo, String prefix) throws MissingObjectException, IncorrectObjectTypeException, IOException { TagAndVersion latestTag = getLatestTopoTag(repo, prefix); if (latestTag == null) { throw new SemverGitflowPlugin.VersionApplicationException( "Cannot find any matching tags in history. You must have tags of form v0.1.2 in order to use semver"); } else { int count = getNumberOfCommitsSinceTag(repo, latestTag.tag); return new TagVersionAndCount(latestTag.version.getOriginalVersion(), count); } } private static int getNumberOfCommitsSinceTag(Repository repo, String lastTag) throws MissingObjectException, IncorrectObjectTypeException, IOException { try { ObjectId lastTagObjectId = getObjectIdForTag(repo, lastTag); ObjectId headObjectId = GitRepos.getHeadObjectId(repo); return getCountBetweenCommits(repo, headObjectId, lastTagObjectId); } catch (NullPointerException e) { return 0; } } private static TagAndVersion getLatestTopoTag(Repository repo, String prefix) throws MissingObjectException, IncorrectObjectTypeException, IOException { Map<ObjectId, Set<String>> allTags = getAllTags(repo); Map<ObjectId, Set<String>> allTagsPrefixed = Maps.newHashMap(); if (prefix != null) { for (Entry<ObjectId, Set<String>> entry : allTags.entrySet()) { Set<String> tags = Sets.newHashSet(); for (String tag : entry.getValue()) { if (tag.startsWith(prefix)) { tags.add(tag); } } if (tags.size() != 0) { allTagsPrefixed.put(entry.getKey(), tags); } } } return findLatestTopoTag(repo, allTagsPrefixed, prefix); } private static TagAndVersion getLatestTag(Repository repo, String prefix) throws MissingObjectException, IncorrectObjectTypeException, IOException { Map<ObjectId, Set<String>> allTags = getAllTags(repo); return findLatestTag(repo, allTags, prefix); } private static int getCountBetweenCommits(Repository repo, ObjectId headObjectId, ObjectId lastTagObjectId) throws MissingObjectException, IncorrectObjectTypeException, IOException { RevWalk walk = new RevWalk(repo); RevCommit startingPoint = walk.parseCommit(headObjectId); walk.markStart(startingPoint); RevCommit end = walk.lookupCommit(lastTagObjectId); walk.sort(RevSort.TOPO); int commitCount = 0; for (RevCommit c = walk.next(); nonNullOrEnd(end, c); c = walk.next()) { commitCount++; } return commitCount; } private static boolean nonNullOrEnd(RevCommit end, RevCommit c) { return (c != null) && !c.equals(end); } private static ObjectId getObjectIdForTag(Repository repo, String tag) { Ref ref = repo.getTags().get(tag); repo.peel(ref); if (ref.getPeeledObjectId() == null) { return ref.getObjectId(); } else { return ref.getPeeledObjectId(); } } private static TagAndVersion findLatestTopoTag(Repository repo, Map<ObjectId, Set<String>> allTags, String prefix) throws MissingObjectException, IncorrectObjectTypeException, IOException { try { RevWalk walk = new RevWalk(repo); walk.markStart(walk.parseCommit(GitRepos.getHeadObjectId(repo))); for (RevCommit commit : walk) { ObjectId commitId = commit.getId(); // Find the very first tag in history if (allTags.containsKey(commitId)) { List<TagAndVersion> foundTags = new LinkedList<TagAndVersion>(); // If there are more than one tag for this commit, choose the lexographically superior one for (String tagName : allTags.get(commitId)) { String tagVersion = GitRepos.stripVFromVersionString(tagName); if (prefix == null) { foundTags.add(new TagAndVersion(tagName, SemanticVersions.parse(tagVersion))); } else { foundTags.add(new TagAndVersion(tagName, SemanticVersions.parse(prefix, tagVersion))); } } Collections.sort(foundTags); return foundTags.get(foundTags.size() - 1); } } // No tags found - return null return null; } catch (NullPointerException e) { return new TagAndVersion("0.0.0", new DefaultSemanticVersion( "0.0.0", 0, 0, 0, null, null)); } } private static TagAndVersion findLatestTag(Repository repo, Map<ObjectId, Set<String>> allTags, String prefix) throws MissingObjectException, IncorrectObjectTypeException, IOException { try { RevWalk walk = new RevWalk(repo); walk.markStart(walk.parseCommit(GitRepos.getHeadObjectId(repo))); return getLatestTagFromWalk(walk, allTags, prefix); } catch (NullPointerException e) { return new TagAndVersion("0.0.0", new DefaultSemanticVersion( "0.0.0", 0, 0, 0, null, null)); } } private static TagAndVersion getLatestTagFromWalk(RevWalk walk, Map<ObjectId, Set<String>> tags, String prefix) { List<TagAndVersion> foundTags = findAllTagsOnWalk(walk, tags, prefix); if (foundTags.isEmpty()) { return null; } else { Collections.sort(foundTags); return foundTags.get(foundTags.size() - 1); } } private static List<TagAndVersion> findAllTagsOnWalk(RevWalk walk, Map<ObjectId, Set<String>> tags, String prefix) { List<TagAndVersion> foundTags = new LinkedList<TagAndVersion>(); for (RevCommit commit : walk) { ObjectId commitId = commit.getId(); if (tags.containsKey(commitId)) { addTagsToListForCommitId(foundTags, tags, commitId, prefix); } } return foundTags; } private static void addTagsToListForCommitId(List<TagAndVersion> foundTags, Map<ObjectId, Set<String>> tags, ObjectId commitId, String prefix) { for (String tagName : tags.get(commitId)) { String tagVersion = GitRepos.stripVFromVersionString(tagName); if (prefix == null) { if (SemanticVersions.isValid(tagVersion)) { foundTags.add(new TagAndVersion(tagName, SemanticVersions.parse(tagVersion))); } } else { if (SemanticVersions.isValid(prefix, tagVersion)) { foundTags.add(new TagAndVersion(tagName, SemanticVersions.parse(prefix, tagVersion))); } } } } private static Map<ObjectId, Set<String>> getAllTags(Repository repo) { Map<ObjectId, Set<String>> map = new HashMap<ObjectId, Set<String>>(); Map<String, Ref> refs = repo.getTags(); for (Map.Entry<String, Ref> tag : refs.entrySet()) { ObjectId idForTag = getIdForTag(repo, tag); if (map.containsKey(idForTag)) { map.get(idForTag).add(tag.getKey()); } else { Set<String> tags = new HashSet<String>(); tags.add(tag.getKey()); map.put(idForTag, tags); } } return map; } private static ObjectId getIdForTag(Repository repo, Map.Entry<String, Ref> tag) { Ref ref = repo.peel(tag.getValue()); if (ref.getPeeledObjectId() == null) { return ref.getObjectId(); } else { return ref.getPeeledObjectId(); } } private static class TagAndVersion implements Comparable<TagAndVersion> { final String tag; final SemanticVersion version; TagAndVersion(String tag, SemanticVersion version) { this.tag = tag; this.version = version; } @Override public int compareTo(TagAndVersion other) { return version.compareTo(other.version); } } }