/* * Copyright 2009 Francois Retief * * 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 jetbrains.buildServer.buildTriggers.vcs.accurev; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.NodeList; import jetbrains.buildServer.buildTriggers.vcs.accurev.command.*; import jetbrains.buildServer.buildTriggers.vcs.AbstractVcsPropertiesProcessor; import jetbrains.buildServer.log.Loggers; import jetbrains.buildServer.serverSide.InvalidProperty; import jetbrains.buildServer.serverSide.PropertiesProcessor; import jetbrains.buildServer.serverSide.BuildServerListener; import jetbrains.buildServer.buildTriggers.vcs.accurev.command.AcSpecialLogin; import jetbrains.buildServer.util.FileUtil; import jetbrains.buildServer.util.EventDispatcher; import jetbrains.buildServer.vcs.*; import jetbrains.buildServer.vcs.patches.PatchBuilder; import jetbrains.buildServer.AgentSideCheckoutAbility; import jetbrains.buildServer.Used; import jetbrains.buildServer.serverSide.SBuildServer; import jetbrains.buildServer.serverSide.ServerPaths; import java.io.ByteArrayInputStream; import java.io.File; import java.io.FileFilter; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.util.*; import com.accurev.common.data.*; import com.accurev.common.process.*; import com.accurev.common.utils.*; import javax.xml.xpath.XPath; import javax.xml.xpath.XPathExpressionException; import javax.xml.xpath.XPathConstants; import javax.xml.xpath.XPathFactory; // TODO 18SEP09 DCN setSessionToken and login lines are all commented out due to authentication failure /** * @author Francois Retief */ public class AccuRevVcsSupport extends VcsSupport implements AgentSideCheckoutAbility, LabelingSupport, CurrentVersionIsExpensiveVcsSupport { //private VcsManager acVcsManager; private File acDefaultWorkFolderParent; public AccuRevVcsSupport(@NotNull final VcsManager vcsManager, @NotNull ServerPaths paths, @NotNull SBuildServer server, EventDispatcher eventDispatcher) { vcsManager.registerVcsSupport(this); //acVcsManager = vcsManager; acDefaultWorkFolderParent = new File(paths.getCachesDir()); AccurevPromoter promoter = new AccurevPromoter(); eventDispatcher.addListener(promoter); } private Settings createSettings(final VcsRoot root) throws VcsException { return new Settings(acDefaultWorkFolderParent, root); } @NotNull public String getName() { return Constants.VCS_NAME; } @Used("jsp") public String getDisplayName() { return "AccuRev"; } public String getVcsSettingsJspFilePath() { return "accurevSettings.jsp"; } @Nullable public Map getDefaultVcsProperties() { Map properties = new HashMap(); properties.put(Constants.SERVER_PORT, "5050"); properties.put(Constants.VERBOSITY, "1"); properties.put(Constants.USE_REF_TREE, "true"); // Get the default server settings from the acclient file. String currentServer = SecurityUtils.getCurrentServer(); if (currentServer != null) { String[] sp = currentServer.split(":"); if (sp.length > 0) properties.put(Constants.SERVER_NAME, sp[0]); if (sp.length > 1) properties.put(Constants.SERVER_PORT, sp[1]); } properties.put(Constants.COMMAND_DIR, Settings.getDefaultAccuRevDir()); return properties; } @Nullable public PropertiesProcessor getVcsPropertiesProcessor() { return new AbstractVcsPropertiesProcessor() { public Collection process(final Map properties) { List result = new ArrayList(); if (isEmpty(properties.get(Constants.DEPOT))) { result.add(new InvalidProperty(Constants.DEPOT, "Depot must be specified")); } if (isEmpty(properties.get(Constants.WATCHSTREAM))) { result.add(new InvalidProperty(Constants.WATCHSTREAM, "Stream must be specified")); } //---------------------------------------------------------------------------------- if (isEmpty(properties.get(Constants.USERNAME))) { result.add(new InvalidProperty(Constants.USERNAME, "User name must be specified")); } //Password property is not checked because accurev allows for Null passwords if (isEmpty(properties.get(Constants.SERVER_NAME))) { result.add(new InvalidProperty(Constants.USERNAME, "Server name must be specified")); } checkPortNumberProperty(Constants.SERVER_PORT, properties.get(Constants.SERVER_PORT), result); checkDirectoryProperty(Constants.COMMAND_DIR, properties.get(Constants.COMMAND_DIR), result); if(result.size() > 0) { return result;//something has already gone wrong and it is not worth continuing. } Settings settings = new Settings(acDefaultWorkFolderParent, properties); //------------------------------------------------------------------------------------- //check that the username and password are valid AcSpecialLogin specialLogin = null; try { specialLogin = new AcSpecialLogin(settings); specialLogin.login(); } catch (VcsException e) { String message = e.getMessage(); if(message.indexOf("Failed authentication") > 0) { String errorMsg = "Username and/or password incorrect"; result.add(new InvalidProperty(Constants.USERNAME, errorMsg)); result.add(new InvalidProperty(Constants.PASSWORD, errorMsg)); } else { String errorMsg = "An error occured whilst testing login credentials:"; errorMsg += "stdErr:" + specialLogin.getStdErr() + " : "; errorMsg += "stdOut:" + specialLogin.getStdOut() + " : "; result.add(new InvalidProperty(Constants.USERNAME, errorMsg)); result.add(new InvalidProperty(Constants.PASSWORD, errorMsg)); } } if(result.size() > 0) { return result;//we are unable to log in, there is no point in proceeding. } //----------------------------------------------------------------------------------- //check that WATCHSTREAM exists and is not a passthrough stream try { AcRunProcess cmd = AcRunProcess.getInstance(settings, acDefaultWorkFolderParent); boolean watchStreamExists = cmd.doesStreamExist(settings.getDepot(), settings.getWatchStream()); if(!watchStreamExists) { result.add(new InvalidProperty(Constants.WATCHSTREAM, "The selected Stream does not exist ")); } boolean isPassthroughStream = cmd.isPassThroughStream(settings.getDepot(), settings.getWatchStream()); if(isPassthroughStream) { result.add(new InvalidProperty(Constants.WATCHSTREAM, "Passthrough streams cannot be used as watch stream")); } } catch (VcsException e) { result.add(new InvalidProperty(Constants.WATCHSTREAM, "An Exception was encountered whilst validating the watchstream: " + e.getMessage())); } //---------------------------------------------------------------------------------- //check that POPULATESTREAM exists try { AcRunProcess cmd = AcRunProcess.getInstance(settings, acDefaultWorkFolderParent); boolean populateStreamExists = cmd.doesStreamExist(settings.getDepot(), settings.getPopulateStream()); if(!populateStreamExists) { result.add(new InvalidProperty(Constants.POPULATESTREAM, "The selected Stream does not exist ")); } }catch(VcsException e) { result.add(new InvalidProperty(Constants.POPULATESTREAM, "An Exception was encountered whilst validating the populatestream: " + e.getMessage())); } return result; } }; } public String getVersionDisplayName(final String version, final VcsRoot root) throws VcsException { return version; } @NotNull public Comparator getVersionComparator() { return new Comparator() { public int compare(final String o1, final String o2) { long tx1 = Long.parseLong(o1); long tx2 = Long.parseLong(o2); return Long.signum(tx1 - tx2); } }; } @NotNull public String getCurrentVersion(final VcsRoot root) throws VcsException { // TODO DCN 18SEP09 Not exactly sure what we're supposed to return here, given that we're not supposed to implement this with "expensive" mode. Settings settings = createSettings(root); /* RunProcess.setAccuRevExecutable(settings.getExecutablePath().getAbsolutePath()); AcSecurityProcess sec = new AcSecurityProcess(); sec.setSessionToken(new SessionToken(settings.getServerName() + ":" + settings.getServerPort(), "", settings.getUsername())); String token = ""; if (sec.getSecurityInfo().startsWith(sec.NotAuthenticated)) token = sec.Login(settings.getUsername(), settings.getPassword()); AcRunProcess cmd = new AcRunProcess(acDefaultWorkFolderParent); cmd.setSessionToken(new SessionToken(settings.getServerName() + ":" + settings.getServerPort(), token, settings.getUsername())); */ AcRunProcess cmd = AcRunProcess.getInstance(settings, acDefaultWorkFolderParent); return cmd.getLastTransactionId(settings.getDepot()); } public String describeVcsRoot(final VcsRoot vcsRoot) { String depot = vcsRoot.getProperty(Constants.DEPOT); String stream = vcsRoot.getProperty(Constants.WATCHSTREAM); String describeRoot = "AccuRev:" + depot + "/" + stream; return describeRoot; } private String fixSnapshotName(final String label) { if (label.length() <= 0) return label; // According to the AccuRev CLI user's guide (page 5): // - the snapshot name has a maximum length of 79 characters // - can ONLY contain A-Z,a-z,0-9,[-_.+@] and SPACE // - cannot start with a digit or '.' // - cannot contain forward-slash or back-slash characters String symbols = "-_.+@ "; StringBuilder sb = new StringBuilder(); int labelLength = (label.length() < 79) ? label.length() : 79; for (int i = 0; i < labelLength; ++i) { char c = label.charAt(i); if (Character.isLetterOrDigit(c) || symbols.indexOf((int)c) > -1) { sb.append(c); } else { // All other characters gets replaced with '_' sb.append('_'); } } if (sb.charAt(0) == '.') sb.setCharAt(0, '_'); if (Character.isDigit(sb.charAt(0))) sb.insert(0, '_'); return sb.toString(); } public String label(@NotNull String label, @NotNull String version, @NotNull VcsRoot root, @NotNull CheckoutRules checkoutRules) throws VcsException { Loggers.VCS.debug("label stream: '" + label + "', " + version); Settings settings = createSettings(root); String snapshotName = fixSnapshotName(label); AcRunProcess cmd = AcRunProcess.getInstance(settings, acDefaultWorkFolderParent); //cmd.setSessionToken(new SessionToken(settings.getServerName() + ":" + settings.getServerPort(), "", settings.getUsername())); cmd.createSnapshot(snapshotName, settings.getWatchStream(), version); return snapshotName; } @Override public LabelingSupport getLabelingSupport() { return this; } @Override public boolean ignoreServerCachesFor(VcsRoot root) { return super.ignoreServerCachesFor(root); } @Override public boolean sourcesUpdatePossibleIfChangesNotFound(VcsRoot root) { boolean base = super.sourcesUpdatePossibleIfChangesNotFound(root); Loggers.VCS.debug("sourcesUpdatePossibleIfChangesNotFound: value = " + base); return true; } public boolean isAgentSideCheckoutAvailable() { return true; } public boolean isTestConnectionSupported() { return true; } public boolean isCurrentVersionExpensive() { return true; } @Nullable public String testConnection(final VcsRoot vcsRoot) throws VcsException { Settings settings = createSettings(vcsRoot); String cmdPath = ""; cmdPath = settings.getExecutablePath().getAbsolutePath(); int status = RunProcess.checkAccuRevExecutable(cmdPath); if (status != RunProcess.ERR_SUCCESS) { final String message = "Unable to find the AccuRev executable. " + "(" + CommandUtil.errorToString(status) + ")" + "\n" + cmdPath; Loggers.VCS.warn(message); throw new VcsException(message); } RunProcess.setAccuRevExecutable(cmdPath); Loggers.VCS.debug("Executable: " + RunProcess.getAccuRevExecutable()); AcSecurityProcess sec = new AcSecurityProcess();//DON'T REPLACE WITH AcRunProcess.getInstance(). This is a special method. sec.setSessionToken(new SessionToken(settings.getServerName() + ":" + settings.getServerPort(), "", settings.getUsername())); sec.Logout(); // Ensure we are logged out, to verify login details String token = sec.Login(settings.getUsername(), settings.getPassword()); sec.setSessionToken(new SessionToken(settings.getServerName() + ":" + settings.getServerPort(), token, settings.getUsername())); int[] ver = sec.getAccuRevServerVersion(); StringBuilder vb = new StringBuilder(); for (int i = 0; i < ver.length; ++i) { if (i > 0) vb.append("."); vb.append(ver[i]); } if ((ver[0] == 4 && ver[1] < 7) || (ver[0] < 4)) { final String message = "AccuRev Server should be at least v4.7.0 or later, but found v" + vb.toString(); throw new VcsException(message); } Loggers.VCS.debug("AccuRev Server version: " + vb.toString()); AcRunProcess cmd = new AcRunProcess(acDefaultWorkFolderParent); cmd.setSessionToken(new SessionToken(settings.getServerName() + ":" + settings.getServerPort(), token, settings.getUsername())); if (!cmd.doesStreamExist(settings.getDepot(), settings.getWatchStream())) { throw new VcsException("Stream cannot be found."); } else if(cmd.isPassThroughStream(settings.getDepot(), settings.getWatchStream())) { throw new VcsException("The specified watch stream is a passthrough stream. This is not allowed"); } return null; // test connection success } public List collectBuildChanges(final VcsRoot root, @NotNull final String fromVersion, @NotNull final String toVersion, final CheckoutRules checkoutRules) throws VcsException { return null; //VcsSupportUtil.collectBuildChanges(root, fromVersion, toVersion, checkoutRules, this); } /* public List collectBuildChanges(final VcsRoot root, @NotNull final String fromVersion, @NotNull final String toVersion, final IncludeRule includeRule) throws VcsException { return null; }*/ public List collectBuildChanges(final VcsRoot root, @NotNull final String fromVersion, final CheckoutRules checkoutRules) throws VcsException { /* TODO 25SEP09 DCN - Had to remove include rules due to issues experienced with multi-VCS roots. Not sure how to resolve just yet */ // Loggers.VCS.debug("collectBuildChanges: includeRule = " + includeRule); // Loggers.VCS.debug("collectBuildChanges: includeRule.getFrom() = " + includeRule.getFrom()); Settings settings = createSettings(root); List result = new ArrayList(); /* RunProcess.setAccuRevExecutable(settings.getExecutablePath().getAbsolutePath()); AcSecurityProcess sec = new AcSecurityProcess(); sec.setSessionToken(new SessionToken(settings.getServerName() + ":" + settings.getServerPort(), "", settings.getUsername())); String token = ""; if (sec.getSecurityInfo().startsWith(sec.NotAuthenticated)) token = sec.Login(settings.getUsername(), settings.getPassword()); AcRunProcess run = new AcRunProcess(acDefaultWorkFolderParent); run.setSessionToken(new SessionToken(settings.getServerName() + ":" + settings.getServerPort(), token, settings.getUsername())); */ AcRunProcess run = AcRunProcess.getInstance(settings, acDefaultWorkFolderParent); String depot = settings.getDepot(); String stream = settings.getWatchStream(); String currentVersion = run.getLastTransactionId(depot); // Testing: display the input values Loggers.VCS.debug("collectBuildChanges: from " + fromVersion + " to " + currentVersion); if (Long.parseLong(fromVersion) == Long.parseLong(currentVersion)) { return result; } try { Document xmlChanges = run.getListOfChangedElements(stream, currentVersion, fromVersion); XPathFactory factory=XPathFactory.newInstance(); XPath xPath=factory.newXPath(); NodeList nodes = (NodeList) xPath.evaluate("/AcResponse/Element/Change", xmlChanges, XPathConstants.NODESET); Loggers.VCS.debug("Number of changes: " + nodes.getLength()); // Display the string. if (nodes.getLength() == 0) return result; // ---------------------- // ---------------------- collectStreamChanges(root, depot, stream, result, xmlChanges, fromVersion, currentVersion, run, xPath); if(settings.getHideChanges()) { ModificationData highestModData = null; Integer highestTransId = 0; for(ModificationData modData : result) { Integer transId = new Integer(modData.getVersion()); if(transId > highestTransId) { highestTransId = transId; highestModData = modData; } } if( highestModData != null) { Date changeDate = highestModData.getVcsDate(); String userName = highestModData.getUserName(); String displayVersion = highestModData.getVersion(); String txVersion = currentVersion; // DCN 05APR2011 We use the depot-wide high watermark to ensure that the workspace is populated to the same Transaction number as the Collect changes List changes = highestModData.getChanges(); String description = "Changes from Ignored Stream (" + settings.getWatchStream() + ")" ; ModificationData mod = new ModificationData(changeDate, changes, description, userName, root, txVersion, displayVersion); result.clear(); result.add(mod); } return result; } Loggers.VCS.debug("Number of transaction mods: " + result.size()); // Display the string. NodeList inheritedVersionNodes = (NodeList) xPath.evaluate("/AcResponse/Element/Change[not(@VcsStatus) or @VcsStatus != 'Matched']", xmlChanges, XPathConstants.NODESET); if (inheritedVersionNodes.getLength() != 0) { List changes = new ArrayList(); // Transaction: version tags for (int i = 0; i changes = cmd.getUpdateStreamInfo(depot, stream, toVer, fromVer); for (XMLTag change : changes) { String what = change.getAttributeValue("What"); if (what.equals("version")) { XMLTag stream2 = change.getTag("Stream2"); String depotName = settings.getDepot(); String streamNameVersion = stream2.getAttributeValue("Version"); String elementName = stream2.getAttributeValue("Name"); byte[] fileContent = catCmd.getFileContentByFilePath(streamNameVersion, elementName, depotName); builder.changeOrCreateBinaryFile(new File(elementName), null, new ByteArrayInputStream(fileContent), fileContent.length ); } else if (what.equals("now visible")) { XMLTag stream2 = change.getTag("Stream2"); String elementName = stream2.getAttributeValue("Name"); ElementStatusData elStat = cmd.getElementInfo(depot, stream, elementName); if (elStat.isDirectory()) { builder.createDirectory(new File(elementName)); } else { String depotName = settings.getDepot(); String streamNameVersion = stream2.getAttributeValue("Version"); byte[] fileContent = catCmd.getFileContentByFilePath(streamNameVersion, elementName, depotName); builder.createBinaryFile(new File(elementName), null, new ByteArrayInputStream(fileContent), fileContent.length ); } } else if (what.equals("no longer visible")) { XMLTag stream2 = change.getTag("Stream2"); String elementName = stream2.getAttributeValue("Name"); ElementStatusData elStat = cmd.getElementInfo(depot, stream, elementName); if (elStat.isDirectory()) { builder.deleteDirectory(new File(elementName), false); } else { builder.deleteFile(new File(elementName), false); } } else if (what.equals("moved")) { XMLTag stream1 = change.getTag("Stream1"); String elementName1 = stream1.getAttributeValue("Name"); XMLTag stream2 = change.getTag("Stream2"); String elementName2 = stream2.getAttributeValue("Name"); ElementStatusData elStat = cmd.getElementInfo(depot, stream, elementName2); if (elStat.isDirectory()) { builder.renameDirectory(new File(elementName1), new File(elementName2), false); } else { builder.renameFile(new File(elementName1), new File(elementName2), false); } } else { Loggers.VCS.error("AccuRev: Unknown change type found: " + what); } } } // builds patch by exporting files using specified version public void buildFullPatch(final Settings settings, final String token, @NotNull final String toVersion, final PatchBuilder builder) throws IOException, VcsException { File tempDir = FileUtil.createTempDirectory("accurev", toVersion); try { /*the following section is required for backward compatibility. * The previous version of this plugin did not have a populateStream variable. */ String workingStream = ""; if (settings.getPopulateStream() != null) { workingStream = settings.getPopulateStream(); } else { workingStream = settings.getWatchStream(); } String checkoutDir = tempDir.getAbsolutePath(); if (settings.getSubDirectory() != null) { checkoutDir += File.separatorChar + settings.getSubDirectory(); File f = new File(checkoutDir); if(!f.exists()) { if (!f.mkdir()) { throw new VcsException("Unable to create Directory " + checkoutDir); } } } AcRunProcess cmd = AcRunProcess.getInstance(settings, acDefaultWorkFolderParent); //cmd.setSessionToken(new SessionToken(settings.getServerName() + ":" + settings.getServerPort(), token, settings.getUsername())); cmd.populateStream(settings.getDepot(), workingStream,checkoutDir ); buildPatchFromDirectory(builder, tempDir, null); } finally { FileUtil.delete(tempDir); } } protected void buildPatchFromDirectory(final PatchBuilder builder, final File repRoot, final FileFilter filter) throws IOException { buildPatchFromDirectory(repRoot, builder, repRoot, filter); } private void buildPatchFromDirectory(File curDir, final PatchBuilder builder, final File repRoot, final FileFilter filter) throws IOException { File[] files = curDir.listFiles(filter); if (files == null) return; for (File realFile: files) { String relPath = realFile.getAbsolutePath().substring(repRoot.getAbsolutePath().length()); final File virtualFile = new File(relPath); if (realFile.isDirectory()) { builder.createDirectory(virtualFile); buildPatchFromDirectory(realFile, builder, repRoot, filter); } else { final FileInputStream is = new FileInputStream(realFile); try { builder.createBinaryFile(virtualFile, null, is, realFile.length()); } finally { is.close(); } } } } @NotNull public byte[] getContent(final VcsModification vcsModification, final VcsChangeInfo change, final VcsChangeInfo.ContentType contentType, final VcsRoot vcsRoot) throws VcsException { String version = contentType == VcsChangeInfo.ContentType.AFTER_CHANGE ? change.getAfterChangeRevisionNumber() : change.getBeforeChangeRevisionNumber(); return getContent(change.getRelativeFileName(), vcsRoot, version); } @NotNull public byte[] getContent(final String filePath, final VcsRoot vcsRoot, final String version) throws VcsException { Settings settings = createSettings(vcsRoot); RunProcess.setAccuRevExecutable(settings.getExecutablePath().getAbsolutePath()); AcSecurityProcess sec = new AcSecurityProcess(); sec.setSessionToken(new SessionToken(settings.getServerName() + ":" + settings.getServerPort(), "", settings.getUsername())); String token = ""; if (sec.getSecurityInfo().startsWith(sec.NotAuthenticated)) token = sec.Login(settings.getUsername(), settings.getPassword()); AcCatProcess cmd = new AcCatProcess(); cmd.setSessionToken(new SessionToken(settings.getServerName() + ":" + settings.getServerPort(), token, settings.getUsername())); String depotName = settings.getDepot(); String streamNameVersion = version; String elementName = filePath; return cmd.getFileContentByFilePath(streamNameVersion, elementName, depotName); } public VcsChange createChangeItem(XPath xPath, Element acChangeNode, String virtualVersion) throws XPathExpressionException { Element oldVer = (Element) xPath.evaluate("Stream1", acChangeNode, XPathConstants.NODE); Element newVer = (Element) xPath.evaluate("Stream2", acChangeNode, XPathConstants.NODE); String verFullPath = (String) ((newVer != null) ? newVer : oldVer).getAttribute("Name"); if ( verFullPath.length() == 0 ) { verFullPath = (String) ((newVer != null) ? newVer : oldVer).getAttribute("name"); } String filePath = verFullPath.substring(1); // strip the /./ from the beginning Loggers.VCS.debug("Version = " + filePath); String fileName = filePath; String relativeFileName = filePath; String afterNum; String beforeNum; String changeName = null; VcsChangeInfo.Type changeType; Boolean verIsDir = ((String) ((newVer != null) ? newVer : oldVer).getAttribute("IsDir")).equals("yes"); if (newVer == null) { changeType = verIsDir ? VcsChangeInfo.Type.DIRECTORY_REMOVED : VcsChangeInfo.Type.REMOVED; beforeNum = (String) oldVer.getAttribute("Version"); afterNum = null; } else if (oldVer != null) { changeType = verIsDir ? VcsChangeInfo.Type.DIRECTORY_CHANGED : VcsChangeInfo.Type.CHANGED; beforeNum = (String) oldVer.getAttribute("Version"); afterNum = (String) newVer.getAttribute("Version"); if (beforeNum.equals(afterNum)) changeType = VcsChangeInfo.Type.NOT_CHANGED; } else { changeType = verIsDir ? VcsChangeInfo.Type.DIRECTORY_ADDED : VcsChangeInfo.Type.ADDED; beforeNum = null; afterNum = (String) newVer.getAttribute("Version"); } VcsChange change = new VcsChange(changeType, changeName, fileName, relativeFileName, beforeNum, (virtualVersion != null) ? virtualVersion : afterNum); return change; } public void collectStreamChanges(VcsRoot root, String depot, String watchStream, List result, Document xmlChanges, String fromVersion, String currentVersion, AcRunProcess run, XPath xPath) throws VcsException, XPathExpressionException { Vector vec = run.getRevisionsBetween( depot, watchStream, fromVersion, currentVersion); Loggers.VCS.debug("Number of transactions on " + watchStream + ": " + vec.size()); // Display the string. for (TransactionData tx : vec) { if (tx.getTranId().equals(fromVersion)) continue; List changes = new ArrayList(); // Transaction: version tags Vector versions = tx.getVersions(); for (VersionData version : versions) { String fullRealNum = version.getRealStreamNum() + "/" + version.getRealVersionNum(); Loggers.VCS.debug("ElementID: " + version.getVerEID() + ", Real version num:" + fullRealNum); String expression = "/AcResponse/Element/Change[*/@eid='" + version.getVerEID() + "' and @What!='eid']"; NodeList nodeList = (NodeList)xPath.evaluate(expression, xmlChanges, XPathConstants.NODESET); for ( int i = 0; i < nodeList.getLength( ); ++i ) { Element node = ( Element )nodeList.item( i ); if (node == null) continue; Element oldVer = (Element) xPath.evaluate("Stream1", node, XPathConstants.NODE); Element newVer = (Element) xPath.evaluate("Stream2", node, XPathConstants.NODE); String verFullPath = (String) ((newVer != null) ? newVer : oldVer).getAttribute("Name"); if ( verFullPath.length( ) > 0 ) { node.setAttribute("VcsStatus", "Matched"); changes.add(createChangeItem(xPath, node, version.getVerVirtualName())); break; } } } if (changes.size()!= 0) { Date changeDate = new Date(Long.parseLong(tx.getTranTime()) * 1000L); // Transaction: time String description = tx.getComment(); String user = tx.getTranUser(); String txVersion = currentVersion; // DCN 05APR2011 We use the depot-wide high watermark to ensure that the workspace is populated to the same Transaction number as the Collect changes String displayVersion = tx.getTranId(); ModificationData mod = new ModificationData(changeDate, changes, description, user, root, txVersion, displayVersion); result.add(mod); } } NodeList inheritedVersionNodes = (NodeList) xPath.evaluate("/AcResponse/Element/Change[not(@VcsStatus) or @VcsStatus != 'Matched']", xmlChanges, XPathConstants.NODESET); Loggers.VCS.debug("Number of changes left: " + inheritedVersionNodes.getLength()); // Display the string. if (inheritedVersionNodes.getLength() != 0) { String parentStream = run.getParentStreamName(depot, watchStream); Loggers.VCS.debug("Parent stream: " + parentStream); if (parentStream != null) { collectStreamChanges(root, depot, parentStream, result, xmlChanges, fromVersion, currentVersion, run, xPath); } } } }