0001: package org.antmod.scm.impl;
0002:
0003: import java.io.BufferedReader;
0004: import java.io.File;
0005: import java.io.IOException;
0006: import java.io.StringReader;
0007: import java.util.ArrayList;
0008: import java.util.Collections;
0009: import java.util.StringTokenizer;
0010:
0011: import org.antmod.conf.AntmodProperties;
0012: import org.antmod.scm.ScmDifference;
0013: import org.antmod.scm.ScmSystem;
0014: import org.antmod.scm.ScmUrl;
0015: import org.antmod.scm.ScmVersion;
0016: import org.antmod.util.ProcessLauncher;
0017: import org.apache.commons.io.FileUtils;
0018: import org.apache.commons.io.HexDump;
0019: import org.apache.commons.lang.StringUtils;
0020: import org.apache.tools.ant.BuildException;
0021:
0022: /**
0023: * Subversion repository provider, providing access to this
0024: * compelling Source Configuration Management alternative for CVS.
0025: * <p/>
0026: * This Subversion repository provider is a thin layer on top
0027: * of the "svn" commandline executable, and as such
0028: * requires the "svn" executable to be in the PATH
0029: * of the system.
0030: *
0031: * @author Klaas Waslander
0032: */
0033: public class SvnSystemImpl implements ScmSystem {
0034:
0035: public final static char REVISION_NAME_SEPARATOR = '_';
0036: public final static char REVISION_VERSION_SEPARATOR = '.';
0037:
0038: private ScmUrl url;
0039: private String standardOutput;
0040: private String errorOutput;
0041:
0042: /**
0043: * Public default onstructor.
0044: */
0045: public SvnSystemImpl() {
0046: }
0047:
0048: public String getStandardOutput() {
0049: return standardOutput;
0050: }
0051:
0052: public String getErrorOutput() {
0053: return errorOutput;
0054: }
0055:
0056: /* (non-Javadoc)
0057: * @see org.antmod.scm.ScmSystem#getUrl()
0058: */
0059: public ScmUrl getUrl() {
0060: return url;
0061: }
0062:
0063: /* (non-Javadoc)
0064: * @see org.antmod.scm.ScmSystem#setUrl(org.antmod.scm.ScmUrl)
0065: */
0066: public void setUrl(ScmUrl providerUrl) {
0067: this .url = providerUrl;
0068: }
0069:
0070: /**
0071: * Imports given file into Subversion, with recursivity either on or off.
0072: * @param file The file/directory to be imported
0073: * @param recursive Whether to recurse into subdirectories, if any.
0074: */
0075: private void doImport(File file, boolean recursive) {
0076: if (!file.isDirectory()) {
0077: throw new IllegalArgumentException(
0078: "Cannot import a file, it needs to be a directory: "
0079: + file.getPath());
0080: }
0081: ArrayList svnCommand = new ArrayList();
0082:
0083: String creationMessage = "Creation of new module "
0084: + file.getName();
0085: String svnModulePath = renderUrlToSvnArg(getUrl()) + '/'
0086: + file.getName();
0087:
0088: // create import command for adding to repository
0089: svnCommand.add("import");
0090: addAuthenticationArgs(svnCommand);
0091: svnCommand.add("-m");
0092: svnCommand.add(creationMessage);
0093: if (!recursive) {
0094: svnCommand.add("--non-recursive");
0095: }
0096: svnCommand.add(".");
0097: svnCommand.add(svnModulePath + "/trunk");
0098:
0099: // run import command in the same directory
0100: run(file, svnCommand);
0101:
0102: // create initial tags and branches directory in repository
0103: svnCommand = new ArrayList();
0104: svnCommand.add("mkdir");
0105: addAuthenticationArgs(svnCommand);
0106: svnCommand.add("-m");
0107: svnCommand.add(creationMessage);
0108: svnCommand.add(svnModulePath + "/branches");
0109: run(null, svnCommand);
0110:
0111: svnCommand.set(svnCommand.size() - 1, svnModulePath + "/tags");
0112: run(null, svnCommand);
0113: }
0114:
0115: public void doAdd(File file, boolean recursive) {
0116: // check for parent ".svn" folder
0117: File parentDir = file.getParentFile();
0118:
0119: if (!isCheckoutDir(parentDir)) {
0120: // silently switch to importing...
0121: doImport(file, recursive);
0122: } else {
0123: // recursively add, 'recursive' flag ignored!
0124: ArrayList svnCommand = new ArrayList();
0125: svnCommand.add("add");
0126: svnCommand.add(file.getName());
0127: run(parentDir, svnCommand);
0128: }
0129: }
0130:
0131: /**
0132: * Implements Subversion checkout of a module.
0133: */
0134: public void doCheckout(String moduleName, File destDir,
0135: ScmVersion version, boolean reallyQuiet) {
0136: doSvnRetrieve("checkout", moduleName, destDir, version,
0137: reallyQuiet);
0138: }
0139:
0140: /**
0141: * Implements Subversion export of a module.
0142: */
0143: public void doExport(String moduleName, File destDir,
0144: ScmVersion version, boolean reallyQuiet) {
0145: doSvnRetrieve("export", moduleName, destDir, version,
0146: reallyQuiet);
0147: }
0148:
0149: public void doCheckoutOrUpdate(String packageName, File destDir,
0150: ScmVersion version, boolean reallyQuiet) {
0151: if (isCheckoutDir(destDir)) {
0152: doUpdate(packageName, destDir, version, reallyQuiet);
0153: } else {
0154: doCheckout(packageName, destDir, version, reallyQuiet);
0155: }
0156: }
0157:
0158: private void doSvnRetrieve(String svnCommandName,
0159: String moduleName, File destDir, ScmVersion version,
0160: boolean reallyQuiet) {
0161: if (destDir == null) {
0162: throw new IllegalArgumentException(
0163: "destDir attribute for Subversion "
0164: + svnCommandName + " must not be null");
0165: }
0166: if (destDir.getParentFile() == null
0167: || !destDir.getParentFile().exists()) {
0168: throw new IllegalArgumentException(
0169: "destDir parent directory (basedir for the "
0170: + svnCommandName + ") for Subversion "
0171: + svnCommandName
0172: + " does not exist on filesystem: "
0173: + destDir.getParentFile());
0174: }
0175:
0176: ArrayList svnCommand = new ArrayList();
0177: svnCommand.add(svnCommandName);
0178:
0179: // add authentication tokens...
0180: addAuthenticationArgs(svnCommand);
0181:
0182: // checkout proper module and version
0183: svnCommand.add(renderUrlToSvnArg(getUrl()) + '/'
0184: + renderSvnPath(moduleName, version));
0185:
0186: // checkout in the proper target directory locally
0187: svnCommand.add(destDir.getName());
0188:
0189: boolean suppressErrorOutput = reallyQuiet;
0190: boolean suppressStandardOutput = reallyQuiet;
0191: //String output =
0192: run(destDir.getParentFile(), svnCommand, suppressErrorOutput,
0193: suppressStandardOutput);
0194: /*
0195: if (!reallyQuiet && !StringUtils.isBlank(output)) {
0196: System.err.println(output.trim());
0197: this.standardOutput = null;
0198: }
0199: */
0200: if (!suppressStandardOutput) {
0201: this .standardOutput = null;
0202: }
0203: }
0204:
0205: /**
0206: * Internal utility to add username, password and non-interactive flags
0207: * to the svn commandline in the given stringbuffer.
0208: */
0209: private void addAuthenticationArgs(ArrayList list) {
0210: // add authentication parameters if any...
0211: if (getUrl().getUser() != null) {
0212: list.add("--username");
0213: list.add(getUrl().getUser());
0214: }
0215: if (getUrl().getPassword() != null) {
0216: list.add("--password");
0217: list.add(getUrl().getPassword());
0218:
0219: // non-interactive option of svn checkout command, only if password is known!!!
0220: list.add("--non-interactive");
0221: }
0222:
0223: }
0224:
0225: public void doMerge(File moduleDir, ScmVersion version) {
0226: doMerge(moduleDir, version, false);
0227: }
0228:
0229: public void doMerge(File moduleDir, ScmVersion version,
0230: boolean reallyQuiet) {
0231: if (moduleDir == null) {
0232: throw new IllegalArgumentException(
0233: "moduleDir attribute for Subversion merge must not be null");
0234: }
0235: if (!moduleDir.exists()) {
0236: throw new IllegalArgumentException(
0237: "moduleDir for Subversion merge does not exist on filesystem: "
0238: + moduleDir.getPath());
0239: }
0240: if (!isCheckoutDir(moduleDir)) {
0241: throw new IllegalArgumentException("Directory "
0242: + moduleDir.getPath()
0243: + " is not an existing Subversion checkout.");
0244: }
0245: ScmVersion localVersion = getLocalVersion(moduleDir);
0246:
0247: ArrayList svnCommand = new ArrayList();
0248: svnCommand.add("merge");
0249:
0250: // add authentication tokens...
0251: addAuthenticationArgs(svnCommand);
0252:
0253: // add the two versions which are different
0254: svnCommand.add(renderUrlToSvnArg(getUrl()) + '/'
0255: + renderSvnPath(moduleDir.getName(), localVersion));
0256: svnCommand.add(renderUrlToSvnArg(getUrl()) + '/'
0257: + renderSvnPath(moduleDir.getName(), version));
0258:
0259: // run the merge
0260: boolean suppressErrorOutput = reallyQuiet;
0261: boolean suppressStandardOutput = reallyQuiet;
0262: run(moduleDir, svnCommand, suppressErrorOutput,
0263: suppressStandardOutput);
0264: if (!suppressStandardOutput) {
0265: this .standardOutput = null;
0266: }
0267: }
0268:
0269: public void doUpdate(File file, ScmVersion version) {
0270: doUpdate(file.getName(), file, version, false);
0271: }
0272:
0273: /** this method not yet exposed yet */
0274: private void doUpdate(String packageName, File file,
0275: ScmVersion version, boolean reallyQuiet) {
0276: if (file == null) {
0277: throw new IllegalArgumentException(
0278: "file attribute for Subversion update must not be null");
0279: }
0280: if (!file.exists()) {
0281: throw new IllegalArgumentException(
0282: "file for Subversion update does not exist on filesystem: "
0283: + file.getPath());
0284: }
0285: if (!isCheckoutDir(file)) {
0286: throw new IllegalArgumentException("Directory "
0287: + file.getPath()
0288: + " is not an existing Subversion checkout.");
0289: }
0290:
0291: // check whether the proper SVN url is checked out currently...
0292: String currentCheckoutUrl = readCheckoutUrl(file);
0293: boolean differentUrl = false;
0294: if (currentCheckoutUrl != null
0295: && (currentCheckoutUrl.indexOf(this .url.getPath()) < 0 || (this .url
0296: .getHost() != null && currentCheckoutUrl
0297: .indexOf(this .url.getHost()) < 0))) {
0298: differentUrl = true;
0299: }
0300:
0301: // check whether local version changed
0302: ScmVersion localVersion = null;
0303: boolean differentVersion = false;
0304: if (version != null) {
0305: localVersion = getLocalVersion(version.getModuleName(),
0306: file);
0307: if (localVersion == null) {
0308: throw new IllegalStateException(
0309: "Local version of \""
0310: + file.getName()
0311: + "\" unknown! Most likely it is an invalid Subversion checkout.");
0312: }
0313: if (!version.equals(localVersion)) {
0314: differentVersion = true;
0315: }
0316: }
0317:
0318: // switch to proper version or url if needed
0319: if (differentVersion || differentUrl) {
0320: // if no local changes, remove existing checkout and replace with new one
0321: String localSvnChanges = getLocalSvnChanges(file);
0322: if (!StringUtils.isBlank(localSvnChanges)) {
0323: if (differentVersion) {
0324: System.err
0325: .println("Subversion update from version "
0326: + localVersion
0327: + " to "
0328: + version
0329: + " not possible because of local changes:");
0330: } else {
0331: System.err
0332: .println("Subversion update from url "
0333: + currentCheckoutUrl
0334: + " to "
0335: + renderUrlToSvnArg(this .url)
0336: + " not possible because of local changes:");
0337: }
0338: System.err.println(localSvnChanges);
0339: this .errorOutput = null;
0340: return;
0341: } else {
0342: if (differentVersion) {
0343: System.err
0344: .println("Changing existing checkout of \""
0345: + file.getName()
0346: + "\" from version \""
0347: + localVersion + "\" to \""
0348: + version + "\"");
0349: } else {
0350: System.err
0351: .println("Changing existing checkout of \""
0352: + file.getName() + "\" from url \""
0353: + currentCheckoutUrl + "\" to \""
0354: + renderUrlToSvnArg(this .url)
0355: + "\"");
0356: }
0357:
0358: // remove existing checkout, by cleaning module directory
0359: deleteSvnFilesInDirectory(file);
0360:
0361: // checkout new version
0362: doCheckout(packageName, file, version, reallyQuiet);
0363: return;
0364: }
0365: }
0366:
0367: // continue with svn update
0368: ArrayList svnCommand = new ArrayList();
0369: svnCommand.add("update");
0370:
0371: // add authentication tokens...
0372: addAuthenticationArgs(svnCommand);
0373:
0374: boolean suppressErrorOutput = reallyQuiet;
0375: boolean suppressStandardOutput = reallyQuiet;
0376: if (file.isDirectory()) {
0377: run(file, svnCommand, suppressErrorOutput,
0378: suppressStandardOutput);
0379: } else {
0380: svnCommand.add(file.getName());
0381: run(file.getParentFile(), svnCommand, suppressErrorOutput,
0382: suppressStandardOutput);
0383: }
0384: /*
0385: if (!reallyQuiet && !StringUtils.isBlank(resultOutput)) {
0386: System.err.println(resultOutput.trim());
0387: this.standardOutput = null;
0388: }
0389: */
0390: if (!suppressStandardOutput) {
0391: this .standardOutput = null;
0392: }
0393: }
0394:
0395: private void deleteSvnFilesInDirectory(File dir) {
0396: // list svn files
0397: ArrayList svnCommand = new ArrayList(1);
0398: svnCommand.add("list");
0399: String svnResult = run(dir, svnCommand);
0400: StringTokenizer st = new StringTokenizer(svnResult, "\n");
0401: while (st.hasMoreTokens()) {
0402: File moduleFile = new File(dir, st.nextToken().trim());
0403: if (moduleFile.exists()) {
0404: if (moduleFile.isDirectory()) {
0405: try {
0406: FileUtils.deleteDirectory(moduleFile);
0407: } catch (IOException e) {
0408: e.printStackTrace();
0409: }
0410: } else {
0411: moduleFile.delete();
0412: }
0413: }
0414: }
0415:
0416: // delete the ".svn" folder
0417: try {
0418: FileUtils.deleteDirectory(new File(dir, ".svn"));
0419: } catch (IOException e) {
0420: e.printStackTrace();
0421: }
0422: }
0423:
0424: /**
0425: * Commit the given file or a while directory to Subversion.
0426: * @param file
0427: */
0428: public void doCommit(File file, String message) {
0429: if (file == null) {
0430: throw new IllegalArgumentException(
0431: "file attribute for Subversion commit must not be null");
0432: }
0433: if (message == null) {
0434: throw new IllegalArgumentException(
0435: "message attribute for Subversion commit must not be null");
0436: }
0437:
0438: if (!isCheckoutDir(file)) {
0439: System.err.println("Nothing to commit in \""
0440: + file.getPath()
0441: + "\", it is not an svn working copy.");
0442: return;
0443: }
0444:
0445: ArrayList svnCommand = new ArrayList();
0446: svnCommand.add("commit");
0447:
0448: // add authentication arguments...
0449: addAuthenticationArgs(svnCommand);
0450:
0451: // message...
0452: svnCommand.add("-m");
0453: svnCommand.add(message);
0454:
0455: // commit either whole directory, or just one file.
0456: if (file.isDirectory()) {
0457: run(file, svnCommand);
0458: } else {
0459: svnCommand.add(file.getName());
0460: run(file.getParentFile(), svnCommand);
0461: }
0462: }
0463:
0464: /**
0465: * Returns the latest file revision.
0466: */
0467: public String getRevisionNumber(File file) {
0468: if (file.isDirectory()) {
0469: throw new IllegalArgumentException(
0470: "File for 'getRevisionNumber' is a directory: "
0471: + file);
0472: }
0473:
0474: ArrayList svnCommand = new ArrayList(1);
0475: svnCommand.add("info");
0476: svnCommand.add(file.getName());
0477: String info = run(file.getParentFile(), svnCommand, true);
0478: String searchKey = "Last Changed Rev:";
0479: int index = info.indexOf(searchKey);
0480: if (index > 0) {
0481: int endIndex = info.indexOf("\n", index);
0482: if (endIndex > 0) {
0483: return info.substring(index + searchKey.length(),
0484: endIndex).trim();
0485: }
0486: }
0487: return null;
0488: }
0489:
0490: /**
0491: * If the given module directory is not a tag, returns the latest version for that directory.
0492: * @return null if no latest version is found
0493: */
0494: public ScmVersion getLatestVersion(File moduleDir) {
0495: ScmVersion localVersion = getLocalVersion(moduleDir.getName(),
0496: moduleDir);
0497: if (localVersion == null) {
0498: return null;
0499: }
0500: if (localVersion.isTag()) {
0501: throw new RuntimeException(
0502: "getLatestVersion is not possible on a Subversion tag.");
0503: }
0504:
0505: if (!new File(moduleDir, "module.xml").exists()) {
0506: throw new RuntimeException(
0507: "FATAL: File module.xml does not exist in the module "
0508: + moduleDir.getName() + " !!!");
0509: }
0510:
0511: ScmVersion[] revs = getVersionsInBranch(new File(moduleDir,
0512: "module.xml"), localVersion);
0513: ScmVersion latestRev = null;
0514: if (revs.length > 0) {
0515: return revs[0];
0516: } else {
0517: return null;
0518: }
0519: }
0520:
0521: /**
0522: * Returns the currently checked out version of the module in the given directory.
0523: * @param moduleDir The directory where the module is currently checked out
0524: * @return The current local version of the module
0525: * @throws org.apache.tools.ant.BuildException
0526: */
0527: public ScmVersion getLocalVersion(File moduleDir)
0528: throws BuildException {
0529: return getLocalVersion(moduleDir.getName(), moduleDir);
0530: }
0531:
0532: public ScmVersion getLocalVersion(String moduleName, File moduleDir)
0533: throws BuildException {
0534: if (moduleName == null) {
0535: moduleName = moduleDir.getName();
0536: }
0537: String checkoutUrl = readCheckoutUrl(moduleDir);
0538: if (checkoutUrl != null) {
0539: return parseSvnPath(moduleName, checkoutUrl);
0540: } else {
0541: return null;
0542: }
0543: }
0544:
0545: /**
0546: * Reads the current Subversion checkout URL where the given module directory
0547: * was checked out from.
0548: */
0549: private String readCheckoutUrl(File moduleDir) {
0550: File svnDir = new File(moduleDir, ".svn");
0551: if (!svnDir.exists()) {
0552: return null;
0553: }
0554:
0555: /* OLD WAY OF RETRIEVING URL, USING SLOW "svn info" COMMAND
0556: ArrayList svnCommand = new ArrayList(1);
0557: svnCommand.add("info");
0558: String info = run(moduleDir, svnCommand);
0559: int urlIndex = info.indexOf("URL:");
0560: if (urlIndex > 0) {
0561: int endOfUrl = info.indexOf("\n", urlIndex);
0562: if (endOfUrl > 0) {
0563: return info.substring(urlIndex + 4, endOfUrl).trim();
0564: }
0565: }
0566: */
0567: try {
0568: String entries = FileUtils.readFileToString(new File(
0569: svnDir, "entries"), System
0570: .getProperty("file.encoding"));
0571:
0572: if (entries.startsWith("<?xml")) {
0573: // Subversion 1.3 or earler: xml format...
0574: int urlIndex = entries.indexOf("url=\"");
0575: if (urlIndex > 0) {
0576: entries = entries.substring(urlIndex + 5);
0577: int endOfUrl = entries.indexOf("\"");
0578: if (endOfUrl > 0) {
0579: return entries.substring(0, endOfUrl);
0580: }
0581: }
0582: } else {
0583: // Subversion 1.4+ : uses non-XML format...
0584: int urlIndex = -1;
0585: for (int fifthLine = 4; fifthLine-- > 0;) {
0586: urlIndex = entries.indexOf("\n", urlIndex + 1);
0587: if (urlIndex < 0) {
0588: return null;
0589: }
0590: }
0591: // we arrived at the fifth line!
0592: int endOfUrl = entries.indexOf("\n", urlIndex + 1);
0593: if (endOfUrl > 0) {
0594: return entries.substring(urlIndex + 1, endOfUrl)
0595: .trim();
0596: }
0597: }
0598: } catch (IOException e) {
0599: e.printStackTrace();
0600: }
0601: return null;
0602: }
0603:
0604: /**
0605: * Returns all available versions for the given file in the given branch,
0606: * with the newest number first and the oldest number last (oldest is usually the ".0" version).
0607: */
0608: public ScmVersion[] getVersionsInBranch(File file, ScmVersion branch) {
0609: if (file == null) {
0610: throw new IllegalArgumentException(
0611: "File attribute for Subversion 'getVersionsInBranch' must not be null");
0612: }
0613: File moduleDir = file.getParentFile();
0614: String moduleName = moduleDir.getName();
0615:
0616: ArrayList svnCommand = new ArrayList();
0617: svnCommand.add("list");
0618: addAuthenticationArgs(svnCommand);
0619:
0620: // list all tags or branches and filter the ones we want to return
0621: String modulePath = renderUrlToSvnArg(getUrl()) + '/'
0622: + moduleName;
0623: boolean returnTags = false;
0624: if (branch == null || branch.isTrunk()) {
0625: // get 2 digit versions in 'branches'
0626: modulePath += "/branches";
0627: } else {
0628: // get 3 digit versions in 'tags'
0629: modulePath += "/tags";
0630: returnTags = true;
0631: }
0632: svnCommand.add(modulePath);
0633:
0634: ArrayList result = new ArrayList();
0635:
0636: //
0637: // get svn output and PARSE it
0638: //
0639: String svnResult = run(null, svnCommand);
0640: StringTokenizer st = new StringTokenizer(svnResult, "\n");
0641: String branchVersionString = branch
0642: .toString(REVISION_VERSION_SEPARATOR);
0643: String versionString;
0644: while (st.hasMoreTokens()) {
0645: versionString = st.nextToken().trim();
0646: if (versionString.endsWith("/")) {
0647: versionString = versionString.substring(0,
0648: versionString.length() - 1);
0649: }
0650: if (returnTags) {
0651: if (versionString.startsWith(branchVersionString)
0652: && countVersionChars(
0653: REVISION_VERSION_SEPARATOR,
0654: versionString) == 2) {
0655: result
0656: .add(new ScmVersion(moduleName,
0657: versionString));
0658: }
0659: } else {
0660: if (countVersionChars(REVISION_VERSION_SEPARATOR,
0661: versionString) == 1) {
0662: result
0663: .add(new ScmVersion(moduleName,
0664: versionString));
0665: }
0666: }
0667: }
0668:
0669: // sort and return array
0670: Collections.sort(result);
0671: ScmVersion[] array = new ScmVersion[result.size()];
0672: result.toArray(array);
0673: return array;
0674: }
0675:
0676: /**
0677: * Check whether the given checkout directory is up-to-date
0678: * when comparing it to the repository contents.
0679: * @param checkoutDir The directory with locally checked out contents
0680: * @return Whether the checkoutDir is up-to-date
0681: */
0682: public boolean isUpToDate(File checkoutDir) {
0683: return StringUtils.isBlank(getLocalSvnChanges(checkoutDir));
0684: }
0685:
0686: private String getLocalSvnChanges(File checkoutDir) {
0687: ArrayList svnCommand = new ArrayList(2);
0688: svnCommand.add("status");
0689: svnCommand.add("-q");
0690: return run(checkoutDir, svnCommand);
0691: }
0692:
0693: /**
0694: * Implementation of the ScmDifference interface for returning cvs diff entries in this class.
0695: * @author Klaas Waslander
0696: */
0697: private static class ScmDifferenceImpl implements ScmDifference {
0698: String filename;
0699: boolean conflict = false;
0700: StringBuffer log = new StringBuffer();
0701:
0702: ScmDifferenceImpl(String filename) {
0703: this .filename = filename;
0704: }
0705:
0706: public String getFilename() {
0707: return filename;
0708: }
0709:
0710: public boolean hasConflict() {
0711: return conflict;
0712: }
0713:
0714: private void setConflict(boolean conflict) {
0715: this .conflict = conflict;
0716: }
0717:
0718: public String getLog() {
0719: return log.toString();
0720: }
0721:
0722: private void addLogLine(String logLine) {
0723: this .log.append(logLine);
0724: this .log.append(HexDump.EOL);
0725: }
0726: }
0727:
0728: /**
0729: * Returns the files that have changed between the two given Subversion versions.
0730: */
0731: public ScmDifference[] getDifferences(ScmVersion version1,
0732: ScmVersion version2) {
0733: if (version1.getModuleName() == null) {
0734: throw new IllegalArgumentException(
0735: "Modulename attribute (of version1) for svndiffs must not be null");
0736: }
0737: if (version2.getModuleName() == null) {
0738: throw new IllegalArgumentException(
0739: "Modulename attribute (of version2) for svndiffs must not be null");
0740: }
0741: if (!version1.getModuleName().equals(version2.getModuleName())) {
0742: throw new IllegalArgumentException("Module of version1 \""
0743: + version1.getModuleName()
0744: + "\" is not the same as module of version2 \""
0745: + version2.getModuleName() + "\"");
0746: }
0747:
0748: // build Subversion commandline
0749: ArrayList svnCommand = new ArrayList();
0750: svnCommand.add("diff");
0751: addAuthenticationArgs(svnCommand);
0752: svnCommand.add(renderUrlToSvnArg(getUrl()) + '/'
0753: + renderSvnPath(version1.getModuleName(), version1));
0754: svnCommand.add(renderUrlToSvnArg(getUrl()) + '/'
0755: + renderSvnPath(version2.getModuleName(), version2));
0756:
0757: ArrayList result = new ArrayList();
0758:
0759: //
0760: // get Subversion output and PARSE it
0761: //
0762: String svnResult = run(null, svnCommand);
0763: try {
0764: BufferedReader reader = new BufferedReader(
0765: new StringReader(svnResult));
0766: String line;
0767: ScmDifferenceImpl currentEntry = null;
0768:
0769: while ((line = reader.readLine()) != null) {
0770: if (line.startsWith("Index:")) {
0771: // strip off "Index:"
0772: String file = line.substring(7).trim();
0773: // add diff entry object
0774: currentEntry = new ScmDifferenceImpl(file);
0775: result.add(currentEntry);
0776:
0777: } else if (currentEntry != null) {
0778: currentEntry.addLogLine(line);
0779:
0780: if (line.startsWith("! ")) {
0781: currentEntry.setConflict(true);
0782: }
0783: }
0784: }
0785: } catch (IOException ioe) {
0786: ioe.printStackTrace();
0787: }
0788:
0789: ScmDifference[] array = new ScmDifference[result.size()];
0790: result.toArray(array);
0791: return array;
0792: }
0793:
0794: /**
0795: * Creates a new branch in the trunk of the given module.
0796: */
0797: public String createBranchInTrunk(ScmVersion newBranchForModule) {
0798: if (newBranchForModule == null) {
0799: throw new IllegalArgumentException(
0800: "newBranchForModule attribute for createBranchInTrunk must not be null");
0801: }
0802: ArrayList svnCommand = new ArrayList();
0803: svnCommand.add("copy");
0804: addAuthenticationArgs(svnCommand);
0805:
0806: // message
0807: svnCommand.add("-m");
0808: svnCommand.add("Create branch " + newBranchForModule);
0809:
0810: // from trunk
0811: svnCommand.add(renderUrlToSvnArg(getUrl())
0812: + '/'
0813: + renderSvnPath(newBranchForModule.getModuleName(),
0814: null));
0815:
0816: // to new branch
0817: svnCommand.add(renderUrlToSvnArg(getUrl()) + '/'
0818: + renderSvnPath(null, newBranchForModule));
0819:
0820: return run(null, svnCommand);
0821: }
0822:
0823: /**
0824: * Creates a new tag in the given BRANCH of the given module.
0825: */
0826: public String createTagInBranch(ScmVersion existingBranch,
0827: ScmVersion newTag) {
0828: if (existingBranch == null) {
0829: throw new IllegalArgumentException(
0830: "existingBranch attribute for createTagInBranch must not be null");
0831: }
0832: if (newTag == null) {
0833: throw new IllegalArgumentException(
0834: "newTag attribute for createTagInBranch must not be null");
0835: }
0836: if (existingBranch.getModuleName() == null) {
0837: throw new IllegalArgumentException(
0838: "moduleName of existing branch for createTagInBranch must not be null");
0839: }
0840:
0841: ArrayList svnCommand = new ArrayList();
0842: svnCommand.add("copy");
0843: addAuthenticationArgs(svnCommand);
0844:
0845: // message
0846: svnCommand.add("-m");
0847: svnCommand.add("Create tag " + newTag + " in branch "
0848: + existingBranch);
0849:
0850: // from branch
0851: svnCommand.add(renderUrlToSvnArg(getUrl()) + '/'
0852: + renderSvnPath(null, existingBranch));
0853:
0854: // to new tag
0855: svnCommand.add(renderUrlToSvnArg(getUrl()) + '/'
0856: + renderSvnPath(null, newTag));
0857:
0858: return run(null, svnCommand);
0859: }
0860:
0861: /**
0862: * Utility method for counting the number of given characters in the given string.
0863: */
0864: private static int countVersionChars(char c, String s) {
0865: // count the occurences of char c in string s
0866: return StringUtils.countMatches(s, String.valueOf(c));
0867: }
0868:
0869: /**
0870: * Runs the given svn command in the given directory, and returns standard output.
0871: * @param baseDir The directory where the command should run
0872: * @param commandList The command arguments (passed as arguments to the actual svn command)
0873: * @return The standard output, if any
0874: */
0875: private String run(File baseDir, ArrayList commandList) {
0876: return run(baseDir, commandList, false);
0877: }
0878:
0879: private String run(File baseDir, ArrayList commandList,
0880: boolean suppressErrorOutput) {
0881: return run(baseDir, commandList, suppressErrorOutput, true);
0882: }
0883:
0884: private String run(File baseDir, ArrayList commandList,
0885: boolean suppressErrorOutput, boolean suppressStandardOutput) {
0886: return run(baseDir, commandList, suppressErrorOutput,
0887: suppressStandardOutput, false);
0888: }
0889:
0890: private String run(File baseDir, ArrayList commandList,
0891: boolean suppressErrorOutput,
0892: boolean suppressStandardOutput, boolean cleaningUp) {
0893: // work with copy for local changes
0894: commandList = new ArrayList(commandList);
0895: ArrayList orgCommandList = new ArrayList(commandList);
0896:
0897: // make it quiet if not done already
0898: String cmd = (String) commandList.get(0);
0899: //System.err.println("SVN COMMAND: " + cmd);
0900: if (!cmd.equals("info") && !cmd.equals("list")
0901: && !cmd.equals("diff") && !cmd.equals("cleanup")
0902: && !cmd.equals("merge") && !cmd.equals("update")
0903: && !cmd.equals("checkout")
0904: && !commandList.contains("-q")
0905: && !commandList.contains("--quiet")) {
0906: commandList.add("-q");
0907: }
0908:
0909: // of course, prepend the "svn" executable to invoke
0910: commandList.add(0, AntmodProperties
0911: .getProperty("antmod.scm.svn.executable"));
0912:
0913: // create launcher
0914: ProcessLauncher launcher = new ProcessLauncher(commandList,
0915: baseDir);
0916:
0917: final boolean suppressStd = suppressStandardOutput;
0918: final StringBuffer stdOut = new StringBuffer();
0919: final StringBuffer stdErr = new StringBuffer();
0920: stdOut.setLength(0);
0921: stdErr.setLength(0);
0922: launcher
0923: .addOutputListener(new ProcessLauncher.OutputListener() {
0924: public void standardOutput(char[] output) {
0925: stdOut.append(output);
0926:
0927: if (!suppressStd) {
0928: System.err.print(output);
0929: }
0930: }
0931:
0932: public void errorOutput(char[] output) {
0933: stdErr.append(output);
0934: }
0935: });
0936:
0937: // launch and wait until done...
0938: launcher.launch();
0939:
0940: // detect whether to try "svn cleanup" once
0941: if (!cleaningUp && stdErr.length() > 0
0942: && !cmd.equals("cleanup")
0943: && stdErr.indexOf("locked") > 0) {
0944: System.err.println("Removing lock from \""
0945: + baseDir.getName() + "\" using 'svn cleanup'...");
0946: ArrayList cleanupCommand = new ArrayList();
0947: cleanupCommand.add("cleanup");
0948: run(baseDir, cleanupCommand, false);
0949:
0950: // try one more time now!
0951: return run(baseDir, orgCommandList, suppressErrorOutput,
0952: suppressStandardOutput, true);
0953: }
0954:
0955: // if there is error output, log it for now
0956: if (!suppressErrorOutput && stdErr.length() > 0) {
0957: System.err.println("'" + launcher.getCommandLine()
0958: + "' produced error output:");
0959: System.err.println(stdErr.toString());
0960: }
0961:
0962: // return standard output
0963: this .standardOutput = stdOut.toString();
0964: this .errorOutput = stdErr.toString();
0965: return stdOut.toString();
0966: }
0967:
0968: static ScmVersion parseSvnPath(String moduleName, String svnPath) {
0969: // find modulename in url
0970: String moduleNameSearch = '/' + moduleName + '/';
0971: int moduleNameIndex = svnPath.lastIndexOf(moduleNameSearch);
0972: if (moduleNameIndex < 0) {
0973: throw new IllegalArgumentException("Subversion URL \""
0974: + svnPath + "\" does not contain modulename \""
0975: + moduleName + "\"");
0976: }
0977:
0978: // extract version
0979: String versionString = svnPath.substring(moduleNameIndex
0980: + moduleNameSearch.length());
0981: if (!versionString.startsWith("trunk")) {
0982: int nextSlash = versionString.indexOf("/");
0983: if (nextSlash < 0) {
0984: throw new IllegalArgumentException(
0985: "Subversion URL \""
0986: + svnPath
0987: + "\" does not have version in URL after tags/branches part.");
0988: }
0989: versionString = versionString.substring(nextSlash + 1);
0990: }
0991: return new ScmVersion(moduleName, versionString);
0992: }
0993:
0994: static String renderSvnPath(String moduleName, ScmVersion ver) {
0995: if (StringUtils.isBlank(moduleName)
0996: && StringUtils.isBlank(ver.getModuleName())) {
0997: throw new RuntimeException(
0998: "Modulename unknown - proper Subversion repository path not possible.");
0999: }
1000: if (moduleName == null) {
1001: moduleName = ver.getModuleName();
1002: }
1003:
1004: if (ver == null) {
1005: return moduleName + "/trunk";
1006: }
1007:
1008: if (ver.isTag()) {
1009: return moduleName + "/tags/"
1010: + ver.toString(REVISION_VERSION_SEPARATOR);
1011: } else if (ver.isBranch()) {
1012: return moduleName + "/branches/"
1013: + ver.toString(REVISION_VERSION_SEPARATOR);
1014: } else {
1015: return moduleName + "/trunk";
1016: }
1017: }
1018:
1019: static String renderUrlToSvnArg(ScmUrl url) {
1020: StringBuffer result = new StringBuffer();
1021: result.append(url.getProtocol());
1022: result.append("://");
1023: if (url.getProtocol().equals("file")) {
1024: result.append('/');
1025: } else {
1026: result.append(url.getHost());
1027: }
1028: result.append(url.getPath());
1029: return result.toString();
1030: }
1031:
1032: public boolean isCheckoutDir(File directory) {
1033: return new File(directory, ".svn").exists();
1034: }
1035: }
|