| /* |
| * Copyright 2008 Google Inc. |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); you may not |
| * use this file except in compliance with the License. You may obtain a copy of |
| * the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT |
| * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the |
| * License for the specific language governing permissions and limitations under |
| * the License. |
| */ |
| package com.google.gwt.ant.taskdefs; |
| |
| import org.apache.tools.ant.BuildException; |
| import org.apache.tools.ant.Task; |
| |
| import java.io.File; |
| import java.io.IOException; |
| import java.io.LineNumberReader; |
| import java.io.StringReader; |
| import java.util.regex.Matcher; |
| import java.util.regex.Pattern; |
| |
| /** |
| * A Svn interface task, because the initial solution of <exec> and |
| * <propertyregex> is unhappy in ant 1.6.5, and while that's old, it's not quite |
| * "too old" for us to care. |
| */ |
| public class SvnInfo extends Task { |
| |
| /** |
| * Structured svn info. |
| */ |
| static class Info { |
| /** |
| * The relative path of this svn working copy within the root of the remote |
| * repository. That is, if the root is "http://example.com/svn" and the |
| * working copy URL is "http://example.com/svn/tags/w00t", this will be set |
| * to "tags/w00t". |
| */ |
| public final String branch; |
| |
| /** |
| * The revision of this working copy. Initially set to the value parsed from |
| * "svn info" given a more detailed value via "svnversion". |
| */ |
| public String revision; |
| |
| public Info(String branch, String revision) { |
| this.branch = branch; |
| this.revision = revision; |
| } |
| } |
| |
| /** |
| * A regex that matches a URL. |
| */ |
| static final String URL_REGEX = "\\w+://\\S*"; |
| |
| /** |
| * A pattern that matches the URL line in svn info output. Note that it |
| * <i>also</i> matches Repository Root; to support i18n subversion clients, |
| * we're positionally dependent that URL will be the first match, and |
| * Repository Root the second. |
| */ |
| private static final Pattern BRANCH_PATTERN = Pattern.compile("[^:]*:\\s*(" |
| + URL_REGEX + ")\\s*"); |
| |
| /** |
| * A pattern that matches the Revision line in svn info output. <i>Also</i> |
| * matches Last Changed Rev; we're positionally dependent (revision is |
| * earlier) to support internationalized svn client output. |
| */ |
| private static final Pattern REVISION_PATTERN = Pattern.compile("[^:]*:\\s*(\\d+)\\s*"); |
| |
| /** |
| * A pattern that matches the Repository Root line in svn info output. |
| */ |
| private static final Pattern ROOT_PATTERN = Pattern.compile("[^:]*:\\s*(" |
| + URL_REGEX + "/svn)\\s*"); |
| |
| /** |
| * Returns true if this git working copy matches the specified svn revision, |
| * and also has no local modifications. |
| */ |
| static boolean doesGitWorkingCopyMatchSvnRevision(File dir, String svnRevision) { |
| String workingRev = getGitWorkingRev(dir); |
| String targetRev = getGitRevForSvnRev(dir, svnRevision); |
| if (!workingRev.equals(targetRev)) { |
| return false; |
| } |
| String status = getGitStatus(dir); |
| return status.contains("nothing to commit (working directory clean)"); |
| } |
| |
| /** |
| * Returns the git commit number matching the specified svn revision. |
| */ |
| static String getGitRevForSvnRev(File dir, String svnRevision) { |
| String output = CommandRunner.getCommandOutput(dir, "git", "svn", |
| "find-rev", "r" + svnRevision); |
| output = output.trim(); |
| if (output.length() == 0) { |
| throw new BuildException("git svn find-rev didn't give any answer"); |
| } |
| return output; |
| } |
| |
| /** |
| * Runs "git status" and returns the result. |
| */ |
| static String getGitStatus(File dir) { |
| // git status returns 1 for a status code, so just don't check it. |
| String output = CommandRunner.getCommandOutput(false, dir, "git", "status"); |
| if (output.length() == 0) { |
| throw new BuildException("git status didn't give any answer"); |
| } |
| return output; |
| } |
| |
| /** |
| * Runs "git svn info", returning the output as a string. |
| */ |
| static String getGitSvnInfo(File dir) { |
| String output = CommandRunner.getCommandOutput(dir, "git", "svn", "info"); |
| if (output.length() == 0) { |
| throw new BuildException("git svn info didn't give any answer"); |
| } |
| return output; |
| } |
| |
| /** |
| * Returns the current git commit number of the working copy. |
| */ |
| static String getGitWorkingRev(File dir) { |
| String output = CommandRunner.getCommandOutput(dir, "git", "rev-list", |
| "--max-count=1", "HEAD"); |
| output = output.trim(); |
| if (output.length() == 0) { |
| throw new BuildException("git rev-list didn't give any answer"); |
| } |
| return output; |
| } |
| |
| /** |
| * Runs "svn info", returning the output as a string. |
| */ |
| static String getSvnInfo(File dir) { |
| String output = CommandRunner.getCommandOutput(dir, "svn", "info"); |
| if (output.length() == 0) { |
| throw new BuildException("svn info didn't give any answer"); |
| } |
| return output; |
| } |
| |
| /** |
| * Runs "svnversion", returning the output as a string. |
| */ |
| static String getSvnVersion(File dir) { |
| String output = CommandRunner.getCommandOutput(dir, "svnversion", "."); |
| output = output.trim(); |
| if (output.length() == 0) { |
| throw new BuildException("svnversion didn't give any answer"); |
| } |
| return output; |
| } |
| |
| /** |
| * Determine if this directory is a part of a .git repository. |
| * |
| * @param dir working directory to start looking for the repository. |
| * @return <code>true</code> if a .git repo is found. Returns |
| * <code>false</false> if a .git repo cannot be found, or if |
| * this directory is part of a subversion repository. |
| */ |
| static boolean looksLikeGit(File dir) { |
| if (looksLikeSvn(dir)) { |
| return false; |
| } |
| File gitDir = findGitDir(dir); |
| |
| if (gitDir != null && gitDir.isDirectory()) { |
| return new File(gitDir, "svn").isDirectory(); |
| } |
| return false; |
| } |
| |
| /** |
| * Returns <code>true</code> if the specified directory looks like an svn |
| * working copy. |
| */ |
| static boolean looksLikeSvn(File dir) { |
| return new File(dir, ".svn").isDirectory(); |
| } |
| |
| /** |
| * Parses the output of running "svn info". |
| */ |
| static Info parseInfo(String svnInfo) { |
| String rootUrl = null; |
| String branchUrl = null; |
| String revision = null; |
| LineNumberReader lnr = new LineNumberReader(new StringReader(svnInfo)); |
| try { |
| for (String line = lnr.readLine(); line != null; line = lnr.readLine()) { |
| Matcher m; |
| if ((m = ROOT_PATTERN.matcher(line)) != null && m.matches()) { |
| rootUrl = m.group(1); |
| } else if ((m = BRANCH_PATTERN.matcher(line)) != null && m.matches()) { |
| if (branchUrl == null) { |
| branchUrl = m.group(1); |
| } // else skip the 2nd and later matches |
| } else if ((m = REVISION_PATTERN.matcher(line)) != null && m.matches()) { |
| if (revision == null) { |
| revision = m.group(1); |
| } // else skip the 2nd and later matches |
| } |
| } |
| } catch (IOException e) { |
| throw new BuildException("Should never happen", e); |
| } |
| |
| if (rootUrl == null) { |
| throw new BuildException("svn info didn't get root URL: " + svnInfo); |
| } |
| if (branchUrl == null) { |
| throw new BuildException("svn info didn't get branch URL: " + svnInfo); |
| } |
| if (revision == null) { |
| throw new BuildException("svn info didn't get revision: " + svnInfo); |
| } |
| rootUrl = removeTrailingSlash(rootUrl); |
| branchUrl = removeTrailingSlash(branchUrl); |
| if (!branchUrl.startsWith(rootUrl)) { |
| throw new BuildException("branch URL (" + branchUrl + ") and root URL (" |
| + rootUrl + ") did not match"); |
| } |
| |
| String branch; |
| if (branchUrl.length() == rootUrl.length()) { |
| branch = ""; |
| } else { |
| branch = branchUrl.substring(rootUrl.length() + 1); |
| } |
| return new Info(branch, revision); |
| } |
| |
| static String removeTrailingSlash(String url) { |
| if (url.endsWith("/")) { |
| return url.substring(0, url.length() - 1); |
| } |
| return url; |
| } |
| |
| /** |
| * Find the GIT working directory. |
| * |
| * First checks for the presence of the env variable GIT_DIR, then, looks up |
| * the the tree for a directory named '.git'. |
| * |
| * @param dir Current working directory |
| * @return An object representing the .git directory. Returns |
| * <code>null</code> if none can be found. |
| */ |
| private static File findGitDir(File dir) { |
| String gitDirPath = System.getenv("GIT_DIR"); |
| if (gitDirPath != null) { |
| File gitDir = new File(gitDirPath).getAbsoluteFile(); |
| if (gitDir.isDirectory()) { |
| return gitDir; |
| } |
| } |
| |
| dir = dir.getAbsoluteFile(); |
| while (dir != null) { |
| File gitDir = new File(dir, ".git"); |
| if (gitDir.isDirectory()) { |
| return gitDir; |
| } |
| dir = dir.getParentFile(); |
| } |
| return null; |
| } |
| |
| private String fileprop; |
| |
| private String outprop; |
| |
| private String workdir; |
| |
| public SvnInfo() { |
| super(); |
| } |
| |
| @Override |
| public void execute() throws BuildException { |
| if (outprop == null) { |
| throw new BuildException( |
| "<svninfo> task requires an outputproperty attribute"); |
| } |
| if (workdir == null) { |
| workdir = getProject().getProperty("basedir"); |
| } |
| File workDirFile = new File(workdir); |
| if (!workDirFile.isDirectory()) { |
| throw new BuildException(workdir + " is not a directory"); |
| } |
| |
| if (getProject().getProperty(outprop) == null) { |
| Info info; |
| if (looksLikeSvn(workDirFile)) { |
| info = parseInfo(getSvnInfo(workDirFile)); |
| |
| // Use svnversion to get a more exact revision string. |
| info.revision = getSvnVersion(workDirFile); |
| } else if (looksLikeGit(workDirFile)) { |
| info = parseInfo(getGitSvnInfo(workDirFile)); |
| |
| // Add a 'M' tag if this working copy is not pristine. |
| if (!doesGitWorkingCopyMatchSvnRevision(workDirFile, info.revision)) { |
| info.revision += "M"; |
| } |
| } else { |
| info = new Info("unknown", "unknown"); |
| } |
| getProject().setNewProperty(outprop, info.branch + "@" + info.revision); |
| } else { |
| String propval = getProject().getProperty(outprop); |
| if (!propval.matches("[^@]+@[0-9]+")) { |
| throw new BuildException( |
| "predefined " + outprop + |
| "should look like branch-spec@revison-number"); |
| } |
| } |
| if (fileprop != null) { |
| String outpropval = getProject().getProperty(outprop); |
| int atIndex = outpropval.indexOf('@'); |
| String branch = outpropval.substring(0, atIndex); |
| String revision = outpropval.substring(atIndex + 1); |
| |
| getProject().setNewProperty(fileprop, |
| branch.replace('/', '-') + "-" + revision.replace(':', '-')); |
| } |
| } |
| |
| /** |
| * Establishes the directory used as the SVN workspace to fetch version |
| * information. |
| * |
| * @param srcdir workspace directory name |
| */ |
| public void setDirectory(String srcdir) { |
| workdir = srcdir; |
| } |
| |
| /** |
| * Establishes the property containing the SVN output string, branch@rev. |
| * |
| * @param propname Name of a property |
| */ |
| public void setOutputFileProperty(String propname) { |
| fileprop = propname; |
| } |
| |
| /** |
| * Establishes the property containing the SVN output string, branch@rev. |
| * |
| * @param propname Name of a property |
| */ |
| public void setOutputProperty(String propname) { |
| outprop = propname; |
| } |
| } |