Java tutorial
/* * This file is part of git-commit-id-plugin by Konrad 'ktoso' Malawski <konrad.malawski@java.pl> * * git-commit-id-plugin is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * git-commit-id-plugin is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with git-commit-id-plugin. If not, see <http://www.gnu.org/licenses/>. */ package pl.project13.jgit; import java.io.IOException; import java.util.*; import java.util.regex.Pattern; import org.eclipse.jgit.api.Git; import org.eclipse.jgit.api.Status; import org.eclipse.jgit.api.errors.GitAPIException; import org.eclipse.jgit.errors.IncorrectObjectTypeException; 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.RevTag; import org.eclipse.jgit.revwalk.RevWalk; import org.jetbrains.annotations.NotNull; import pl.project13.jgit.dummy.DatedRevTag; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Function; import com.google.common.base.Predicate; import com.google.common.collect.Collections2; import com.google.common.collect.Lists; import pl.project13.maven.git.log.LoggerBridge; public class JGitCommon { private final LoggerBridge log; public JGitCommon(LoggerBridge log) { this.log = log; } public Collection<String> getTags(Repository repo, final ObjectId headId) throws GitAPIException { RevWalk walk = null; try { Git git = Git.wrap(repo); walk = new RevWalk(repo); List<Ref> tagRefs = git.tagList().call(); final RevWalk finalWalk = walk; Collection<Ref> tagsForHeadCommit = Collections2.filter(tagRefs, new Predicate<Ref>() { @Override public boolean apply(Ref tagRef) { boolean lightweightTag = tagRef.getObjectId().equals(headId); try { // TODO make this configurable (most users shouldn't really care too much what kind of tag it is though) return lightweightTag || finalWalk.parseTag(tagRef.getObjectId()).getObject().getId().equals(headId); // or normal tag } catch (IOException e) { return false; } } }); Collection<String> tags = Collections2.transform(tagsForHeadCommit, new Function<Ref, String>() { @Override public String apply(Ref input) { return input.getName().replaceAll("refs/tags/", ""); } }); return tags; } finally { if (walk != null) { walk.dispose(); } } } public String getClosestTagName(@NotNull Repository repo) { Map<ObjectId, List<DatedRevTag>> map = getClosestTagAsMap(repo); for (Map.Entry<ObjectId, List<DatedRevTag>> entry : map.entrySet()) { return trimFullTagName(entry.getValue().get(0).tagName); } return ""; } public String getClosestTagCommitCount(@NotNull Repository repo, RevCommit headCommit) { HashMap<ObjectId, List<String>> map = transformRevTagsMapToDateSortedTagNames(getClosestTagAsMap(repo)); ObjectId obj = (ObjectId) map.keySet().toArray()[0]; RevWalk walk = new RevWalk(repo); RevCommit commit = walk.lookupCommit(obj); walk.dispose(); int distance = distanceBetween(repo, headCommit, commit); return String.valueOf(distance); } private Map<ObjectId, List<DatedRevTag>> getClosestTagAsMap(@NotNull Repository repo) { Map<ObjectId, List<DatedRevTag>> mapWithClosestTagOnly = new HashMap<>(); String matchPattern = ".*"; Map<ObjectId, List<DatedRevTag>> commitIdsToTags = getCommitIdsToTags(repo, true, matchPattern); LinkedHashMap<ObjectId, List<DatedRevTag>> sortedCommitIdsToTags = sortByDatedRevTag(commitIdsToTags); for (Map.Entry<ObjectId, List<DatedRevTag>> entry : sortedCommitIdsToTags.entrySet()) { mapWithClosestTagOnly.put(entry.getKey(), entry.getValue()); break; } return mapWithClosestTagOnly; } private LinkedHashMap<ObjectId, List<DatedRevTag>> sortByDatedRevTag(Map<ObjectId, List<DatedRevTag>> map) { List<Map.Entry<ObjectId, List<DatedRevTag>>> list = new ArrayList<>(map.entrySet()); Collections.sort(list, new Comparator<Map.Entry<ObjectId, List<DatedRevTag>>>() { public int compare(Map.Entry<ObjectId, List<DatedRevTag>> m1, Map.Entry<ObjectId, List<DatedRevTag>> m2) { // we need to sort the DatedRevTags to a commit first, otherwise we may get problems when we have two tags for the same commit Collections.sort(m1.getValue(), datedRevTagComparator()); Collections.sort(m2.getValue(), datedRevTagComparator()); DatedRevTag datedRevTag1 = m1.getValue().get(0); DatedRevTag datedRevTag2 = m2.getValue().get(0); return datedRevTagComparator().compare(datedRevTag1, datedRevTag2); } }); LinkedHashMap<ObjectId, List<DatedRevTag>> result = new LinkedHashMap<>(); for (Map.Entry<ObjectId, List<DatedRevTag>> entry : list) { result.put(entry.getKey(), entry.getValue()); } return result; } protected Map<ObjectId, List<DatedRevTag>> getCommitIdsToTags(@NotNull Repository repo, boolean includeLightweightTags, String matchPattern) { Map<ObjectId, List<DatedRevTag>> commitIdsToTags = new HashMap<>(); try (RevWalk walk = new RevWalk(repo)) { walk.markStart(walk.parseCommit(repo.resolve("HEAD"))); List<Ref> tagRefs = Git.wrap(repo).tagList().call(); Pattern regex = Pattern.compile(matchPattern); log.info("Tag refs [{}]", tagRefs); for (Ref tagRef : tagRefs) { walk.reset(); String name = tagRef.getName(); if (!regex.matcher(name).matches()) { log.info("Skipping tagRef with name [{}] as it doesn't match [{}]", name, matchPattern); continue; } ObjectId resolvedCommitId = repo.resolve(name); // TODO that's a bit of a hack... try { final RevTag revTag = walk.parseTag(resolvedCommitId); ObjectId taggedCommitId = revTag.getObject().getId(); log.info("Resolved tag [{}] [{}], points at [{}] ", revTag.getTagName(), revTag.getTaggerIdent(), taggedCommitId); // sometimes a tag, may point to another tag, so we need to unpack it while (isTagId(taggedCommitId)) { taggedCommitId = walk.parseTag(taggedCommitId).getObject().getId(); } if (commitIdsToTags.containsKey(taggedCommitId)) { commitIdsToTags.get(taggedCommitId).add(new DatedRevTag(revTag)); } else { commitIdsToTags.put(taggedCommitId, new ArrayList<>(Collections.singletonList(new DatedRevTag(revTag)))); } } catch (IncorrectObjectTypeException ex) { // it's an lightweight tag! (yeah, really) if (includeLightweightTags) { // --tags means "include lightweight tags" log.info("Including lightweight tag [{}]", name); DatedRevTag datedRevTag = new DatedRevTag(resolvedCommitId, name); if (commitIdsToTags.containsKey(resolvedCommitId)) { commitIdsToTags.get(resolvedCommitId).add(datedRevTag); } else { commitIdsToTags.put(resolvedCommitId, new ArrayList<>(Collections.singletonList(datedRevTag))); } } } catch (Exception ignored) { log.info("Failed while parsing [{}] -- ", tagRef, ignored); } } for (Map.Entry<ObjectId, List<DatedRevTag>> entry : commitIdsToTags.entrySet()) { log.info("key [{}], tags => [{}] ", entry.getKey(), entry.getValue()); } return commitIdsToTags; } catch (Exception e) { log.info("Unable to locate tags", e); } return Collections.emptyMap(); } /** Checks if the given object id resolved to a tag object */ private boolean isTagId(ObjectId objectId) { return objectId.toString().startsWith("tag "); } protected HashMap<ObjectId, List<String>> transformRevTagsMapToDateSortedTagNames( Map<ObjectId, List<DatedRevTag>> commitIdsToTags) { HashMap<ObjectId, List<String>> commitIdsToTagNames = new HashMap<>(); for (Map.Entry<ObjectId, List<DatedRevTag>> objectIdListEntry : commitIdsToTags.entrySet()) { List<String> tagNames = transformRevTagsMapEntryToDateSortedTagNames(objectIdListEntry); commitIdsToTagNames.put(objectIdListEntry.getKey(), tagNames); } return commitIdsToTagNames; } private List<String> transformRevTagsMapEntryToDateSortedTagNames( Map.Entry<ObjectId, List<DatedRevTag>> objectIdListEntry) { List<DatedRevTag> tags = objectIdListEntry.getValue(); List<DatedRevTag> newTags = new ArrayList<>(tags); Collections.sort(newTags, datedRevTagComparator()); List<String> tagNames = Lists.transform(newTags, new Function<DatedRevTag, String>() { @Override public String apply(DatedRevTag input) { return trimFullTagName(input.tagName); } }); return tagNames; } private Comparator<DatedRevTag> datedRevTagComparator() { return new Comparator<DatedRevTag>() { @Override public int compare(DatedRevTag revTag, DatedRevTag revTag2) { return revTag2.date.compareTo(revTag.date); } }; } @VisibleForTesting protected String trimFullTagName(@NotNull String tagName) { return tagName.replaceFirst("refs/tags/", ""); } public List<RevCommit> findCommitsUntilSomeTag(Repository repo, RevCommit head, @NotNull Map<ObjectId, List<String>> tagObjectIdToName) { try (RevWalk revWalk = new RevWalk(repo)) { revWalk.markStart(head); for (RevCommit commit : revWalk) { ObjectId objId = commit.getId(); if (tagObjectIdToName.size() > 0) { List<String> maybeList = tagObjectIdToName.get(objId); if (maybeList != null && maybeList.get(0) != null) { return Collections.singletonList(commit); } } } return Collections.emptyList(); } catch (Exception e) { throw new RuntimeException("Unable to find commits until some tag", e); } } /** * Calculates the distance (number of commits) between the given parent and child commits. * * @return distance (number of commits) between the given commits * @see <a href="https://github.com/mdonoughe/jgit-describe/blob/master/src/org/mdonoughe/JGitDescribeTask.java">mdonoughe/jgit-describe/blob/master/src/org/mdonoughe/JGitDescribeTask.java</a> */ protected int distanceBetween(@NotNull Repository repo, @NotNull RevCommit child, @NotNull RevCommit parent) { try (RevWalk revWalk = new RevWalk(repo)) { revWalk.markStart(child); Set<ObjectId> seena = new HashSet<>(); Set<ObjectId> seenb = new HashSet<>(); Queue<RevCommit> q = new ArrayDeque<>(); q.add(revWalk.parseCommit(child)); int distance = 0; ObjectId parentId = parent.getId(); while (q.size() > 0) { RevCommit commit = q.remove(); ObjectId commitId = commit.getId(); if (seena.contains(commitId)) { continue; } seena.add(commitId); if (parentId.equals(commitId)) { // don't consider commits that are included in this commit seeAllParents(revWalk, commit, seenb); // remove things we shouldn't have included for (ObjectId oid : seenb) { if (seena.contains(oid)) { distance--; } } seena.addAll(seenb); continue; } for (ObjectId oid : commit.getParents()) { if (!seena.contains(oid)) { q.add(revWalk.parseCommit(oid)); } } distance++; } return distance; } catch (Exception e) { throw new RuntimeException( String.format("Unable to calculate distance between [%s] and [%s]", child, parent), e); } } private void seeAllParents(@NotNull RevWalk revWalk, RevCommit child, @NotNull Set<ObjectId> seen) throws IOException { Queue<RevCommit> q = new ArrayDeque<>(); q.add(child); while (q.size() > 0) { RevCommit commit = q.remove(); for (ObjectId oid : commit.getParents()) { if (seen.contains(oid)) { continue; } seen.add(oid); q.add(revWalk.parseCommit(oid)); } } } public static boolean isRepositoryInDirtyState(Repository repo) throws GitAPIException { Git git = Git.wrap(repo); Status status = git.status().call(); // Git describe doesn't mind about untracked files when checking if // repo is dirty. JGit does this, so we cannot use the isClean method // to get the same behaviour. Instead check dirty state without // status.getUntracked().isEmpty() boolean isDirty = !(status.getAdded().isEmpty() && status.getChanged().isEmpty() && status.getRemoved().isEmpty() && status.getMissing().isEmpty() && status.getModified().isEmpty() && status.getConflicting().isEmpty()); return isDirty; } }