NIHVIVO-1229 Create a java utility that will extract release and revision information from Subversion, or else from a text file.
This commit is contained in:
parent
f17e49969d
commit
5a7e301711
3 changed files with 429 additions and 0 deletions
|
@ -0,0 +1,109 @@
|
||||||
|
/* $This file is distributed under the terms of the license in /doc/license.txt$ */
|
||||||
|
|
||||||
|
package edu.cornell.mannlib.vitro.utilities.revisioninfo;
|
||||||
|
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse the response that we got from SVN info.
|
||||||
|
*
|
||||||
|
* Not thread-safe.
|
||||||
|
*/
|
||||||
|
public class InfoResponseParser {
|
||||||
|
private static final Pattern URL_PATTERN = Pattern.compile("URL: (\\S+)");
|
||||||
|
private static final Pattern ROOT_PATTERN = Pattern
|
||||||
|
.compile("Repository Root: (\\S+)");
|
||||||
|
|
||||||
|
private static final String TRUNK_PREFIX = "/trunk";
|
||||||
|
private static final String TAGS_PREFIX = "/tags/";
|
||||||
|
private static final String BRANCHES_PREFIX = "/branches/";
|
||||||
|
|
||||||
|
private final String infoResponse;
|
||||||
|
private String path;
|
||||||
|
|
||||||
|
public InfoResponseParser(String infoResponse) {
|
||||||
|
this.infoResponse = infoResponse;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String parse() {
|
||||||
|
try {
|
||||||
|
path = figurePath();
|
||||||
|
System.err.println("path=" + path);
|
||||||
|
|
||||||
|
if (isTrunkPath()) {
|
||||||
|
return "trunk";
|
||||||
|
} else if (isTagPath()) {
|
||||||
|
return "tag " + getTagName();
|
||||||
|
} else if (isBranchPath()) {
|
||||||
|
return "branch " + getBranchName();
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
System.err.println(e); // TODO
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String figurePath() throws Exception {
|
||||||
|
if (infoResponse == null) {
|
||||||
|
throw new Exception("infoResponse is null.");
|
||||||
|
}
|
||||||
|
|
||||||
|
String url = getUrlFromResponse();
|
||||||
|
String root = getRootFromResponse();
|
||||||
|
System.err.println("url=" + url); // TODO
|
||||||
|
System.err.println("root=" + root); // TODO
|
||||||
|
|
||||||
|
if (!url.startsWith(root)) {
|
||||||
|
throw new Exception("url doesn't start with root.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return url.substring(root.length());
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getUrlFromResponse() throws Exception {
|
||||||
|
return findNonEmptyMatch(URL_PATTERN, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getRootFromResponse() throws Exception {
|
||||||
|
return findNonEmptyMatch(ROOT_PATTERN, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String findNonEmptyMatch(Pattern pattern, int groupIndex)
|
||||||
|
throws Exception {
|
||||||
|
Matcher matcher = pattern.matcher(infoResponse);
|
||||||
|
if (!matcher.find()) {
|
||||||
|
throw new Exception("no match with '" + pattern + "'.");
|
||||||
|
}
|
||||||
|
|
||||||
|
String value = matcher.group(groupIndex);
|
||||||
|
if ((value == null) || (value.isEmpty())) {
|
||||||
|
throw new Exception("match with '" + pattern + "' is empty.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isTrunkPath() {
|
||||||
|
return path.equals(TRUNK_PREFIX);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isTagPath() {
|
||||||
|
return path.startsWith(TAGS_PREFIX);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getTagName() {
|
||||||
|
return path.substring(TAGS_PREFIX.length());
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isBranchPath() {
|
||||||
|
return path.startsWith(BRANCHES_PREFIX);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getBranchName() {
|
||||||
|
return path.substring(BRANCHES_PREFIX.length());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,161 @@
|
||||||
|
/* $This file is distributed under the terms of the license in /doc/license.txt$ */
|
||||||
|
|
||||||
|
package edu.cornell.mannlib.vitro.utilities.revisioninfo;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.StringWriter;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A harness that runs a system-level command.
|
||||||
|
*
|
||||||
|
* No provision is made for standard input.
|
||||||
|
*
|
||||||
|
* The standard output and standard error streams are asynchronously read, so
|
||||||
|
* the sub-process will not block on full buffers. Warning: if either of these
|
||||||
|
* streams contain more data than can fit into a String, then we will have a
|
||||||
|
* problem.
|
||||||
|
*/
|
||||||
|
public class ProcessRunner {
|
||||||
|
private int returnCode;
|
||||||
|
private String stdOut = "";
|
||||||
|
private String stdErr = "";
|
||||||
|
private File workingDirectory;
|
||||||
|
|
||||||
|
private final Map<String, String> environmentAdditions = new HashMap<String, String>();
|
||||||
|
|
||||||
|
/** Set the directory that the command will run in. */
|
||||||
|
public void setWorkingDirectory(File workingDirectory) {
|
||||||
|
this.workingDirectory = workingDirectory;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Add (or replace) any environment variable. */
|
||||||
|
public void setEnvironmentVariable(String key, String value) {
|
||||||
|
this.environmentAdditions.put(key, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Run the command.
|
||||||
|
*
|
||||||
|
* @param command
|
||||||
|
* a list containing the operating system program and its
|
||||||
|
* arguments. See
|
||||||
|
* {@link java.lang.ProcessBuilder#ProcessBuilder(List)}.
|
||||||
|
*/
|
||||||
|
public void run(List<String> command) throws ProcessException {
|
||||||
|
try {
|
||||||
|
ProcessBuilder builder = new ProcessBuilder(command);
|
||||||
|
|
||||||
|
if (workingDirectory != null) {
|
||||||
|
builder.directory(workingDirectory);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!environmentAdditions.isEmpty()) {
|
||||||
|
builder.environment().putAll(this.environmentAdditions);
|
||||||
|
}
|
||||||
|
|
||||||
|
Process process = builder.start();
|
||||||
|
StreamEater outputEater = new StreamEater(process.getInputStream());
|
||||||
|
StreamEater errorEater = new StreamEater(process.getErrorStream());
|
||||||
|
|
||||||
|
this.returnCode = process.waitFor();
|
||||||
|
|
||||||
|
outputEater.join();
|
||||||
|
this.stdOut = outputEater.getContents();
|
||||||
|
|
||||||
|
errorEater.join();
|
||||||
|
this.stdErr = errorEater.getContents();
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new ProcessException("Exception when handling sub-process:",
|
||||||
|
e);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
throw new ProcessException("Exception when handling sub-process:",
|
||||||
|
e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getReturnCode() {
|
||||||
|
return returnCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getStdErr() {
|
||||||
|
return stdErr;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getStdOut() {
|
||||||
|
return stdOut;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A thread that reads an InputStream until it reaches end of file, then
|
||||||
|
* closes the stream.
|
||||||
|
*/
|
||||||
|
private static class StreamEater extends Thread {
|
||||||
|
private final InputStream stream;
|
||||||
|
|
||||||
|
private final StringWriter contents = new StringWriter();
|
||||||
|
|
||||||
|
private final byte[] buffer = new byte[4096];
|
||||||
|
|
||||||
|
public StreamEater(InputStream stream) {
|
||||||
|
this.stream = stream;
|
||||||
|
this.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
try {
|
||||||
|
int howMany = 0;
|
||||||
|
while (true) {
|
||||||
|
howMany = stream.read(buffer);
|
||||||
|
if (howMany > 0) {
|
||||||
|
contents.write(new String(buffer, 0, howMany));
|
||||||
|
} else if (howMany == 0) {
|
||||||
|
Thread.yield();
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
} finally {
|
||||||
|
try {
|
||||||
|
stream.close();
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getContents() {
|
||||||
|
return contents.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates a problem when dealing with a spawned sub-process.
|
||||||
|
*/
|
||||||
|
public static class ProcessException extends Exception {
|
||||||
|
|
||||||
|
public ProcessException() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public ProcessException(String message) {
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ProcessException(Throwable cause) {
|
||||||
|
super(cause);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ProcessException(String message, Throwable cause) {
|
||||||
|
super(message, cause);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,159 @@
|
||||||
|
/* $This file is distributed under the terms of the license in /doc/license.txt$ */
|
||||||
|
|
||||||
|
package edu.cornell.mannlib.vitro.utilities.revisioninfo;
|
||||||
|
|
||||||
|
import java.io.BufferedReader;
|
||||||
|
import java.io.EOFException;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileReader;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import edu.cornell.mannlib.vitro.utilities.revisioninfo.ProcessRunner.ProcessException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get release and revision information to display on screen.
|
||||||
|
*
|
||||||
|
* Ask Subversion for the information. If Subversion is available, and if this
|
||||||
|
* is a working directory, then we can build the info from the responses we get
|
||||||
|
* from "svn info" and "svnversion".
|
||||||
|
*
|
||||||
|
* If that doesn't work, read the information from the ".revisionInfo" file in
|
||||||
|
* the product directory. Presumably, that file was created when the source was
|
||||||
|
* exported from Subversion.
|
||||||
|
*
|
||||||
|
* If that doesn't work either, return something like this:
|
||||||
|
* "productName ~ unknown ~ unknown"
|
||||||
|
*/
|
||||||
|
public class RevisionInfoBuilder {
|
||||||
|
private static final String SVN_DIRECTORY_NAME = ".svn";
|
||||||
|
private static final String[] SVNVERSION_COMMAND = { "svnversion" };
|
||||||
|
private static final String[] SVN_INFO_COMMAND = { "svn", "info" };
|
||||||
|
private static final String INFO_LINE_DELIMITER = " ~ ";
|
||||||
|
private static final String REVISION_INFO_FILENAME = ".revisionInfo";
|
||||||
|
|
||||||
|
private final String productName;
|
||||||
|
private final File productDirectory;
|
||||||
|
|
||||||
|
public RevisionInfoBuilder(String[] args) {
|
||||||
|
if (args.length != 2) {
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
"RevisionInfoBuilder requires 2 arguments, not "
|
||||||
|
+ args.length);
|
||||||
|
}
|
||||||
|
|
||||||
|
productName = args[0];
|
||||||
|
productDirectory = new File(args[1]);
|
||||||
|
|
||||||
|
if (!productDirectory.isDirectory()) {
|
||||||
|
throw new IllegalArgumentException("Directory '"
|
||||||
|
+ productDirectory.getAbsolutePath() + "' does not exist.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String buildInfo() {
|
||||||
|
String infoLine;
|
||||||
|
infoLine = buildInfoFromSubversion();
|
||||||
|
if (infoLine == null) {
|
||||||
|
infoLine = buildInfoFromFile();
|
||||||
|
}
|
||||||
|
if (infoLine == null) {
|
||||||
|
infoLine = buildDummyInfo();
|
||||||
|
}
|
||||||
|
return infoLine;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String buildInfoFromSubversion() {
|
||||||
|
if (!isThisASubversionWorkspace()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
String release = assembleReleaseNameFromSubversion();
|
||||||
|
String revision = obtainRevisionLevelFromSubversion();
|
||||||
|
System.err.println("release=" + release); // TODO
|
||||||
|
System.err.println("revision=" + revision); // TODO
|
||||||
|
return buildLine(release, revision);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isThisASubversionWorkspace() {
|
||||||
|
File svnDirectory = new File(productDirectory, SVN_DIRECTORY_NAME);
|
||||||
|
return svnDirectory.isDirectory();
|
||||||
|
}
|
||||||
|
|
||||||
|
private String assembleReleaseNameFromSubversion() {
|
||||||
|
String infoResponse = runSubProcess(SVN_INFO_COMMAND);
|
||||||
|
return new InfoResponseParser(infoResponse).parse();
|
||||||
|
}
|
||||||
|
|
||||||
|
private String obtainRevisionLevelFromSubversion() {
|
||||||
|
String response = runSubProcess(SVNVERSION_COMMAND);
|
||||||
|
return (response == null) ? null : response.trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
private String runSubProcess(String[] cmdArray) {
|
||||||
|
List<String> command = Arrays.asList(cmdArray);
|
||||||
|
try {
|
||||||
|
ProcessRunner runner = new ProcessRunner();
|
||||||
|
runner.setWorkingDirectory(productDirectory);
|
||||||
|
|
||||||
|
runner.run(command);
|
||||||
|
|
||||||
|
int rc = runner.getReturnCode();
|
||||||
|
if (rc != 0) {
|
||||||
|
throw new ProcessRunner.ProcessException("Return code from "
|
||||||
|
+ command + " was " + rc);
|
||||||
|
}
|
||||||
|
|
||||||
|
String output = runner.getStdOut();
|
||||||
|
// System.err.println(command + " response was '" + output + "'");
|
||||||
|
return output;
|
||||||
|
} catch (ProcessException e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String buildInfoFromFile() {
|
||||||
|
try {
|
||||||
|
File revisionInfoFile = new File(productDirectory,
|
||||||
|
REVISION_INFO_FILENAME);
|
||||||
|
BufferedReader reader = new BufferedReader(new FileReader(
|
||||||
|
revisionInfoFile));
|
||||||
|
|
||||||
|
String release = reader.readLine();
|
||||||
|
if (release == null) {
|
||||||
|
throw new EOFException("No release line in file.");
|
||||||
|
}
|
||||||
|
|
||||||
|
String revision = reader.readLine();
|
||||||
|
if (revision == null) {
|
||||||
|
throw new EOFException("No revision line in file.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return buildLine(release, revision);
|
||||||
|
} catch (IOException e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String buildDummyInfo() {
|
||||||
|
return buildLine("unknown", "unknown");
|
||||||
|
}
|
||||||
|
|
||||||
|
private String buildLine(String release, String revision) {
|
||||||
|
return productName + INFO_LINE_DELIMITER + release.trim()
|
||||||
|
+ INFO_LINE_DELIMITER + revision.trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
|
try {
|
||||||
|
System.out.println(new RevisionInfoBuilder(args).buildInfo());
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace(System.err);
|
||||||
|
System.err.println("usage: RevisionInfoBuilder [product_name] "
|
||||||
|
+ "[product_directory] [supplied_values]");
|
||||||
|
System.exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Loading…
Add table
Reference in a new issue