From 29b9c91291d134727fa3cfaf71f70e40b821b545 Mon Sep 17 00:00:00 2001 From: cminter Date: Tue, 19 Mar 2013 12:01:59 -0700 Subject: [PATCH] separate ANT task functionality from 'git-describe' functionality 1. separated ANT task from 'git-describe' functionality and organized using a Builder that upon build() returns a GitDescribe object 2. using Repository setWorkTree().findGitDir() instead of Repository.setGitDir() which eliminates this code needing to do search for '.git' --- src/org/mdonoughe/GitDescribe.java | 261 ++++++++++++++++++++++++ src/org/mdonoughe/JGitDescribeTask.java | 224 +------------------- 2 files changed, 267 insertions(+), 218 deletions(-) create mode 100644 src/org/mdonoughe/GitDescribe.java diff --git a/src/org/mdonoughe/GitDescribe.java b/src/org/mdonoughe/GitDescribe.java new file mode 100644 index 0000000..918aca7 --- /dev/null +++ b/src/org/mdonoughe/GitDescribe.java @@ -0,0 +1,261 @@ +/* + * Originally written by Matthew Donoughe [mdonoughe], source... + * https://github.com/mdonoughe/jgit-describe + * Updated to latest JGIT by Shane Mc Cormack [ShaneMcC], source... + * https://github.com/ShaneMcC/jgit-describe + */ +package org.mdonoughe; + +import java.io.File; +import java.io.IOException; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Queue; +import java.util.Set; + +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.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.eclipse.jgit.lib.RepositoryBuilder; +import org.eclipse.jgit.revwalk.FollowFilter; + +public class GitDescribe { + private String bestTag; + private int distance; + private String hash; + private boolean dirty; + private boolean showDirty; + + private GitDescribe(final String bestTag, final int distance, + final String hash, final boolean dirty, final boolean showDirty) { + this.bestTag= bestTag; + this.distance= distance; + this.hash= hash; + this.dirty= dirty; + this.showDirty= showDirty; + } + + public String getBestTag() { return bestTag; } + public int getDistance() { return distance; } + public String getHash() { return hash; } + public boolean isDirty() { return dirty; } + + @Override + public String toString() { + String dirtySuffix= (showDirty && dirty) ? "-dirty" : ""; + return String.format("%s-%d-g%s%s", bestTag, distance, hash, dirtySuffix); + } + + static class Builder { + private File workTreeDir; + private int shalength= 7; + private String ref= "HEAD"; + private String subdir; + private boolean showDirty= false; + + public Builder(final File workTreeDir) { + this.workTreeDir= workTreeDir; + } + + public Builder setShalength(final int shalength) { + this.shalength= shalength; + return this; + } + + public Builder setRef(final String ref) { + this.ref= ref; + return this; + } + + public Builder setSubdir(final String subdir) { + this.subdir= subdir; + return this; + } + + public Builder setShowDirty(final boolean showDirty) { + this.showDirty= showDirty; + return this; + } + + public GitDescribe build() throws Exception { + Repository repository = null; + try { + RepositoryBuilder builder = new RepositoryBuilder(); + repository = builder.setWorkTree(workTreeDir) + .findGitDir().build(); + } catch(IOException e) { + throw new Exception("Could not open repository", e); + } + + RevWalk walk = null; + RevCommit start = null; + try { + walk = getWalk(repository); + start = walk.parseCommit(repository.resolve(ref)); + walk.markStart(start); + if (subdir != null) { + final RevCommit next = walk.next(); + if (next != null) { + walk = getWalk(repository); + start = walk.parseCommit(next); + } + } + } catch (IOException e) { + throw new Exception("Could not find target", e); + } + + final Map tags = new HashMap(); + + for (Map.Entry tag : repository.getTags().entrySet()) { + try { + RevTag r = walk.parseTag(tag.getValue().getObjectId()); + ObjectId taggedCommit = r.getObject().getId(); + tags.put(taggedCommit, tag.getKey()); + } catch (IOException e) { + // Theres really no need to panic yet. + } + } + + // No tags found. Panic. + if (tags.isEmpty()) { + throw new Exception("No tags found."); + } + + final List taggedParents = taggedParentCommits(walk, start, tags); + RevCommit best = null; + int bestDistance = 0; + for (RevCommit commit : taggedParents) { + int distance = distanceBetween(start, commit); + if (best == null || (distance < bestDistance)) { + best = commit; + bestDistance = distance; + } + } + + String bestTag= (best != null) ? tags.get(best.getId()) : ""; + String hash= start.getId().abbreviate(shalength).name(); + boolean dirty= false; + try { + Git git= new Git(repository); + Status status= git.status().call(); + dirty= ! status.isClean(); + } catch (GitAPIException e) { /*ignore*/ } + + return new GitDescribe(bestTag, bestDistance, hash, dirty, showDirty); + } + + /** + * Get a Revision Walker instance set up with the correct tree filter. + * + * @param repository Repository that should be walked. + * @return RevWalker instance with Tree Filter if required. + * @throws BuildException If the given subdir is invalid. + */ + private RevWalk getWalk(final Repository repository) throws Exception { + RevWalk walk = null; + walk = new RevWalk(repository); + + if (subdir != null) { + final String parent = repository.getDirectory().getParent() + File.separator; + for (String sd : subdir.split(";")) { + sd = sd.replaceFirst("^" + Pattern.quote(parent), ""); + if (!new File(parent + sd).exists()) { + throw new Exception("'"+sd+"' does not appear to be a subdir of this repo."); + } + // jgit is stupid on windows.... + final String filterDir = (File.separatorChar == '\\') ? sd.replace('\\', '/') : sd; + walk.setTreeFilter(FollowFilter.create(filterDir)); + } + } + + return walk; + } + + /** + * This does something. I think it gets every possible parent tag this + * commit has, then later we look for which is closest and use that as + * the tag to describe. Or something like that. + * + * @param walk + * @param child + * @param tagmap + * @return + * @throws BuildException + */ + private List taggedParentCommits(final RevWalk walk, final RevCommit child, final Map tagmap) { + final Queue q = new LinkedList(); + q.add(child); + final List taggedcommits = new LinkedList(); + final Set seen = new HashSet(); + + while (q.size() > 0) { + final RevCommit commit = q.remove(); + if (tagmap.containsKey(commit.getId())) { + taggedcommits.add(commit); + // don't consider commits that are farther away than this tag + continue; + } + for (RevCommit p : commit.getParents()) { + if (!seen.contains(p.getId())) { + seen.add(p.getId()); + try { + q.add(walk.parseCommit(p.getId())); + } catch (IOException e) { + throw new RuntimeException("Parent not found", e); + } + } + } + } + return taggedcommits; + } + + /** + * Calculate the distance between 2 given commits, parent and child. + * + * @param child Commit to calculate distance to (The latest commit) + * @param parent Commit to calculate distance from (The last tag) + * @return Numeric value between the 2 commits. + */ + private int distanceBetween(final RevCommit child, final RevCommit parent) { + final Set seen = new HashSet(); + final Queue q1 = new LinkedList(); + final Queue q2 = new LinkedList(); + + q1.add(child); + int distance = 1; + while ((q1.size() > 0) || (q2.size() > 0)) { + if (q1.size() == 0) { + distance++; + q1.addAll(q2); + q2.clear(); + } + final RevCommit commit = q1.remove(); + if (commit.getParents() == null) { + return 0; + } else { + for (RevCommit p : commit.getParents()) { + if (p.getId().equals(parent.getId())) { + return distance; + } + if (!seen.contains(p.getId())) { + q2.add(p); + } + } + } + seen.add(commit.getId()); + } + return distance; + } + } +} \ No newline at end of file diff --git a/src/org/mdonoughe/JGitDescribeTask.java b/src/org/mdonoughe/JGitDescribeTask.java index 88c26ca..a0965e9 100644 --- a/src/org/mdonoughe/JGitDescribeTask.java +++ b/src/org/mdonoughe/JGitDescribeTask.java @@ -1,29 +1,12 @@ package org.mdonoughe; import java.io.File; -import java.io.IOException; -import java.io.BufferedReader; -import java.io.FileReader; -import java.util.HashMap; -import java.util.HashSet; -import java.util.LinkedList; -import java.util.List; -import java.util.Map; -import java.util.Queue; -import java.util.Set; import java.util.regex.Pattern; import org.apache.tools.ant.BuildException; import org.apache.tools.ant.Task; -import org.apache.tools.ant.types.Path; - -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.eclipse.jgit.lib.RepositoryBuilder; import org.eclipse.jgit.revwalk.FollowFilter; /** @@ -46,59 +29,6 @@ public class JGitDescribeTask extends Task { /** Check specifically for the last commit in this subdir. */ private String subdir; - /** - * Take a file, and parse its contents into a String. - * - * @param file File to read. - * @return String content of file. - */ - private String fileAsString(final File file) throws IOException { - final StringBuffer fileData = new StringBuffer(1000); - final BufferedReader reader = new BufferedReader(new FileReader(file)); - char[] buf = new char[1024]; - int numRead = 0; - while ((numRead = reader.read(buf)) != -1 ) { - fileData.append(buf, 0, numRead); - } - reader.close(); - return fileData.toString(); - } - - /** - * Take a file, and try and resolve it as a .git directory. - * - * @param file File to parse - * @return File possibly pointing to a .git directory. - * If the given file is already a directory, it will be returned, - * If it is a file, it will be attempted to be parsed as .git-file - * to find a directory. This is done recursively until a valid - * directory is found, or a file can't be parsed. (In which case - * the last successful directory will be returned, or the file given). - * Ultimately, a File object will be returned, that will either be what - * was passed into us, or a directory that may or may not be a real .git - */ - public File getGitDir(final File file) { - if (!file.isDirectory()) { - try { - final String content = fileAsString(file); - final String[] bits = content.split(":", 2); - if (bits.length > 1) { - final File res; - // Is this a relative path or an absolute path? - if (bits[1].trim().charAt(0) == '.') { - res = new File(file.getParent() + File.separatorChar + bits[1].trim()); - } else { - res = new File(bits[1].trim()); - } - return getGitDir(res); - } - } catch (final IOException ioe) { - System.out.println("IOE: " + ioe.getMessage()); - } - } - return file; - } - /** * Set the .git directory * @@ -148,7 +78,7 @@ public void setSubDir(final String newSubDir) { * Create a new instance of JGitDescribeTask */ public JGitDescribeTask() { - dir = new File(".git"); + dir = new File("."); shalength = 7; ref = "HEAD"; } @@ -187,154 +117,12 @@ public void execute() throws BuildException { throw new BuildException("\"property\" attribute must be set!"); } - final File gitDir = getGitDir(dir); - - if (!gitDir.exists() || !gitDir.isDirectory() || !new File(gitDir, "config").exists()) { - throw new BuildException("directory " + dir + " ("+gitDir.toString()+") does not appear to be a valid .git directory."); - } - - Repository repository = null; - try { - RepositoryBuilder builder = new RepositoryBuilder(); - repository = builder.setGitDir(gitDir).build(); - } catch(IOException e) { - throw new BuildException("Could not open repository", e); - } - - RevWalk walk = null; - RevCommit start = null; try { - walk = getWalk(repository); - start = walk.parseCommit(repository.resolve(ref)); - walk.markStart(start); - if (subdir != null) { - final RevCommit next = walk.next(); - if (next != null) { - walk = getWalk(repository); - start = walk.parseCommit(next); - } - } - } catch (IOException e) { - throw new BuildException("Could not find target", e); - } - - final Map tags = new HashMap(); - - for (Map.Entry tag : repository.getTags().entrySet()) { - try { - RevTag r = walk.parseTag(tag.getValue().getObjectId()); - ObjectId taggedCommit = r.getObject().getId(); - tags.put(taggedCommit, tag.getKey()); - } catch (IOException e) { - // Theres really no need to panic yet. - } - } - - // No tags found. Panic. - if (tags.isEmpty()) { - throw new BuildException("No tags found."); - } - - final List taggedParents = taggedParentCommits(walk, start, tags); - RevCommit best = null; - int bestDistance = 0; - for (RevCommit commit : taggedParents) { - int distance = distanceBetween(start, commit); - if (best == null || (distance < bestDistance)) { - best = commit; - bestDistance = distance; - } - } - - final StringBuilder sb = new StringBuilder(); - if (best != null) { - sb.append(tags.get(best.getId())); - if (bestDistance > 0) { - sb.append("-"); - sb.append(bestDistance); - sb.append("-g"); - } - } - if (bestDistance > 0) { - sb.append(start.getId().abbreviate(shalength).name()); - } - - getProject().setProperty(property, sb.toString()); - } - - /** - * This does something. I think it gets every possible parent tag this - * commit has, then later we look for which is closest and use that as - * the tag to describe. Or something like that. - * - * @param walk - * @param child - * @param tagmap - * @return - * @throws BuildException - */ - private List taggedParentCommits(final RevWalk walk, final RevCommit child, final Map tagmap) throws BuildException { - final Queue q = new LinkedList(); - q.add(child); - final List taggedcommits = new LinkedList(); - final Set seen = new HashSet(); - - while (q.size() > 0) { - final RevCommit commit = q.remove(); - if (tagmap.containsKey(commit.getId())) { - taggedcommits.add(commit); - // don't consider commits that are farther away than this tag - continue; - } - for (RevCommit p : commit.getParents()) { - if (!seen.contains(p.getId())) { - seen.add(p.getId()); - try { - q.add(walk.parseCommit(p.getId())); - } catch (IOException e) { - throw new BuildException("Parent not found", e); - } - } - } - } - return taggedcommits; - } - - /** - * Calculate the distance between 2 given commits, parent and child. - * - * @param child Commit to calculate distance to (The latest commit) - * @param parent Commit to calculate distance from (The last tag) - * @return Numeric value between the 2 commits. - */ - private int distanceBetween(final RevCommit child, final RevCommit parent) { - final Set seen = new HashSet(); - final Queue q1 = new LinkedList(); - final Queue q2 = new LinkedList(); - - q1.add(child); - int distance = 1; - while ((q1.size() > 0) || (q2.size() > 0)) { - if (q1.size() == 0) { - distance++; - q1.addAll(q2); - q2.clear(); - } - final RevCommit commit = q1.remove(); - if (commit.getParents() == null) { - return 0; - } else { - for (RevCommit p : commit.getParents()) { - if (p.getId().equals(parent.getId())) { - return distance; - } - if (!seen.contains(p.getId())) { - q2.add(p); - } - } - } - seen.add(commit.getId()); + GitDescribe gitDescribe= new GitDescribe.Builder(dir).setRef(ref) + .setShalength(shalength).setSubdir(subdir).build(); + getProject().setProperty(property, gitDescribe.toString()); + } catch (Exception e) { + throw new BuildException(e.getMessage()); } - return distance; } }