blob: 605658e544de2e206272919b134c1cf7c0a27a08 [file] [log] [blame]
/*
* 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.
*/
private static final Pattern BRANCH_PATTERN = Pattern.compile("\\s*URL:\\s*("
+ URL_REGEX + ")\\s*");
/**
* A pattern that matches the Revision line in svn info output.
*/
private static final Pattern REVISION_PATTERN = Pattern.compile("\\s*Revision:\\s*(\\d+)\\s*");
/**
* A pattern that matches the Repository Root line in svn info output.
*/
private static final Pattern ROOT_PATTERN = Pattern.compile("\\s*Repository Root:\\s*("
+ URL_REGEX + ")\\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;
}
static boolean looksLikeGit(File dir) {
dir = dir.getAbsoluteFile();
while (dir != null) {
if (new File(dir, ".git").isDirectory()) {
return true;
}
dir = dir.getParentFile();
}
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()) {
branchUrl = m.group(1);
} else if ((m = REVISION_PATTERN.matcher(line)) != null && m.matches()) {
revision = m.group(1);
}
}
} 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;
}
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");
}
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);
if (fileprop != null) {
getProject().setNewProperty(fileprop,
info.branch.replace('/', '-') + "-" + info.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;
}
}