/* * Copyright 2000-2018 JetBrains s.r.o. * * 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.clearcase; import com.intellij.execution.ExecutionException; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.util.Ref; import com.intellij.util.Consumer; import java.io.File; import java.io.IOException; import java.text.ParseException; import java.util.*; import java.util.regex.Pattern; import java.util.regex.PatternSyntaxException; import jetbrains.buildServer.Used; import jetbrains.buildServer.buildTriggers.vcs.AbstractVcsPropertiesProcessor; import jetbrains.buildServer.buildTriggers.vcs.clearcase.ClearCaseValidation.IValidation; import jetbrains.buildServer.buildTriggers.vcs.clearcase.ClearCaseValidation.ValidationComposite; import jetbrains.buildServer.buildTriggers.vcs.clearcase.configSpec.ConfigSpec; import jetbrains.buildServer.buildTriggers.vcs.clearcase.configSpec.ConfigSpecLoadRule; import jetbrains.buildServer.buildTriggers.vcs.clearcase.configSpec.ConfigSpecParseUtil; import jetbrains.buildServer.buildTriggers.vcs.clearcase.process.ClearCaseInteractiveProcess; import jetbrains.buildServer.buildTriggers.vcs.clearcase.process.ClearCaseInteractiveProcessPool; import jetbrains.buildServer.buildTriggers.vcs.clearcase.structure.ClearCaseStructureCache; import jetbrains.buildServer.buildTriggers.vcs.clearcase.versionTree.Version; import jetbrains.buildServer.serverSide.*; import jetbrains.buildServer.util.*; import jetbrains.buildServer.util.filters.Filter; import jetbrains.buildServer.util.filters.FilterUtil; import jetbrains.buildServer.vcs.*; import jetbrains.buildServer.vcs.clearcase.CCException; import jetbrains.buildServer.vcs.clearcase.CCSnapshotView; import jetbrains.buildServer.vcs.clearcase.Constants; import jetbrains.buildServer.vcs.clearcase.Util; import jetbrains.buildServer.vcs.patches.PatchBuilder; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.springframework.beans.factory.annotation.Autowired; import static jetbrains.buildServer.vcs.clearcase.Constants.*; public class ClearCaseSupport extends ServerVcsSupport implements VcsPersonalSupport, LabelingSupport, VcsFileContentProvider, CollectSingleStateChangesByIncludeRules, BuildPatchByIncludeRules, TestConnectionSupport, BuildStartContextProcessor, VcsRootBasedMappingProvider, CollectSingleStateChangesBetweenRoots, ListDirectChildrenPolicy { private static final Logger LOG = Logger.getInstance(ClearCaseSupport.class.getName()); private static final boolean USE_CC_CACHE = !TeamCityProperties.getBoolean("clearcase.disable.caches"); private static final Pattern COLON_OR_SEMICOLON_PATTERN = Pattern.compile("[;:]"); private @Nullable ClearCaseStructureCache myCache; private static ClearCaseSupport ourDefault; public static ClearCaseSupport getDefault() { return ourDefault; } public ClearCaseSupport() { ourDefault = this; myCache = null; } public ClearCaseSupport(File baseDir) { this(); if (baseDir != null) { myCache = new ClearCaseStructureCache(baseDir, this); } } @Autowired public ClearCaseSupport(final @NotNull SBuildServer server, final @NotNull ServerPaths serverPaths, final @NotNull EventDispatcher dispatcher) { this(); File cachesRootDir = new File(new File(serverPaths.getCachesDir()), "clearCase"); if (!cachesRootDir.exists() && !cachesRootDir.mkdirs()) { myCache = null; return; } myCache = new ClearCaseStructureCache(cachesRootDir, this); if (USE_CC_CACHE) { myCache.register(server, dispatcher); } server.registerExtension(BuildStartContextProcessor.class, this.getClass().getName(), this); } @NotNull public static ViewPath getViewPath(@NotNull final VcsRoot vcsRoot) throws VcsException, IOException { final String viewPath = vcsRoot.getProperty(Constants.VIEW_PATH); if (viewPath != null && trim(viewPath).length() != 0) { return getViewPath(viewPath); } return new ViewPath(vcsRoot.getProperty(Constants.CC_VIEW_PATH), vcsRoot.getProperty(Constants.RELATIVE_PATH)); } @NotNull public static ViewPath getRootPath(@NotNull final VcsRoot vcsRoot) throws VcsException, IOException { final ViewPath viewPath = getViewPath(vcsRoot); final String vobRelativePath; final String relativePath = viewPath.getRelativePathWithinTheView(); int pos = relativePath.indexOf(File.separatorChar); if (pos < 0) { pos = relativePath.length(); } else { if (relativePath.substring(0, pos).equals(Constants.VOBS_NAME_ONLY)) { pos = relativePath.indexOf(File.separatorChar, pos + 1); if (pos < 0) pos = relativePath.length(); } } vobRelativePath = relativePath.substring(0, pos); return new ViewPath(viewPath.getClearCaseViewPath(), vobRelativePath); } @NotNull public static ViewPath getViewPath(@NotNull final String viewPath) throws VcsException, IOException { final String ccViewRoot = ClearCaseConnection.getClearCaseViewRoot(viewPath); return new ViewPath(ccViewRoot, getRelativePath(new File(ccViewRoot), new File(viewPath))); } @Nullable private static String getRelativePath(@NotNull final File parent, @NotNull final File subFile) throws VcsException { final StringBuilder sb = new StringBuilder(""); File file = subFile; boolean first = true; while (file != null && !CCPathElement.areFilesEqual(file, parent)) { if (!first) { sb.insert(0, File.separatorChar); } else { first = false; } sb.insert(0, file.getName()); file = file.getParentFile(); } if (file == null) return null; return sb.toString(); } public void withConnection(final VcsRoot root, final FileRule includeRule, @Nullable final ConfigSpecLoadRule loadRule, @NotNull final ConnectionProcessor processor) throws VcsException, IOException { withConnection(root, includeRule, false, loadRule, processor); } public void withConnection(final VcsRoot root, final FileRule includeRule, final boolean checkCSChange, @Nullable final ConfigSpecLoadRule loadRule, @NotNull final ConnectionProcessor processor) throws VcsException, IOException { final ViewPath viewPath = getViewPath(root); if (includeRule.getFrom().length() > 0) { viewPath.setIncludeRuleFrom(includeRule); } doWithConnection(viewPath, root, checkCSChange, processor); } public void doWithConnection(final ViewPath viewPath, final VcsRoot root, final boolean checkCSChange, final ConnectionProcessor processor) throws IOException, VcsException { ClearCaseInteractiveProcessPool.doWithProcess(viewPath, new ClearCaseInteractiveProcessPool.ProcessRunnable() { public void run(@NotNull final ClearCaseInteractiveProcess process) throws IOException, VcsException { processor.process(new ClearCaseConnection(viewPath, process, myCache, root, checkCSChange)); } }); } private ChangedFilesProcessor createCollectingChangesFileProcessor(final MultiMap key2changes, final Set addFileActivities, final Set zeroToOneChangedFiles, final ClearCaseConnection connection) { return new ChangedFilesProcessor() { public void processChangedDirectory(@NotNull final HistoryElement element) throws IOException, VcsException { LOG.debug("Processing changed directory " + element.getLogRepresentation()); CCParseUtil.processChangedDirectory(element, connection, createChangedStructureProcessor(element, key2changes, addFileActivities, connection)); } public void processDestroyedFileVersion(@NotNull final HistoryElement element) { } public void processChangedFile(@NotNull final HistoryElement element) throws VcsException, IOException { if (element.getObjectVersionInt() > getMaxVersionToIgnore(element)) { String pathWithoutVersion = connection.getParentRelativePathWithVersions(element.getObjectName(), true); final String versionAfterChange = pathWithoutVersion + CCParseUtil.CC_VERSION_SEPARATOR + element.getObjectVersion(); final String versionBeforeChange = pathWithoutVersion + CCParseUtil.CC_VERSION_SEPARATOR + element.getPreviousVersion(connection, false); final VcsChange change = addChange(element, element.getObjectName(), connection, VcsChangeInfo.Type.CHANGED, versionBeforeChange, versionAfterChange, key2changes); if (element.getObjectVersionInt() == 1) { zeroToOneChangedFiles.add(change); } LOG.debug("Change was detected: changed file " + element.getLogRepresentation()); } } }; } private int getMaxVersionToIgnore(final HistoryElement element) { return Constants.MAIN.equals(element.getObjectLastBranch()) ? 1 : 0; } private ChangedStructureProcessor createChangedStructureProcessor(final HistoryElement element, final MultiMap key2changes, final Set addFileActivities, final ClearCaseConnection connection) { return new ChangedStructureProcessor() { public void fileAdded(@NotNull final SimpleDirectoryChildElement simpleChild) throws VcsException, IOException { final DirectoryChildElement child = simpleChild.createFullElement(connection); if (child != null && connection.versionIsInsideView(child.getPathWithoutVersion(), child.getStringVersion(), true)) { addChange(element, child.getFullPath(), connection, VcsChangeInfo.Type.ADDED, null, getVersion(child, connection), key2changes); addFileActivities.add(element.getActivity()); LOG.debug("Change was detected: added file \"" + child.getFullPath() + "\""); } } public void fileDeleted(@NotNull final SimpleDirectoryChildElement simpleChild) throws VcsException, IOException { final DirectoryChildElement child = simpleChild.createFullElement(connection); if (child != null && connection.versionIsInsideView(child.getPathWithoutVersion(), child.getStringVersion(), true)) { addChange(element, child.getFullPath(), connection, VcsChangeInfo.Type.REMOVED, getVersion(child, connection), null, key2changes); LOG.debug("Change was detected: deleted file \"" + child.getFullPath() + "\""); } } public void directoryDeleted(@NotNull final SimpleDirectoryChildElement simpleChild) throws VcsException, IOException { final DirectoryChildElement child = simpleChild.createFullElement(connection); if (child != null && connection.versionIsInsideView(child.getPathWithoutVersion(), child.getStringVersion(), false)) { addChange(element, child.getFullPath(), connection, VcsChangeInfo.Type.DIRECTORY_REMOVED, getVersion(child, connection), null, key2changes); LOG.debug("Change was detected: deleted directory \"" + child.getFullPath() + "\""); } } public void directoryAdded(@NotNull final SimpleDirectoryChildElement simpleChild) throws VcsException, IOException { final DirectoryChildElement child = simpleChild.createFullElement(connection); if (child != null && connection.versionIsInsideView(child.getPathWithoutVersion(), child.getStringVersion(), false)) { addChange(element, child.getFullPath(), connection, VcsChangeInfo.Type.DIRECTORY_ADDED, null, getVersion(child, connection), key2changes); LOG.debug("Change was detected: added directory \"" + child.getFullPath() + "\""); } } }; } private String getVersion(final DirectoryChildElement child, final ClearCaseConnection connection) throws VcsException { return connection.getObjectRelativePathWithVersions(child.getFullPath(), DirectoryChildElement.Type.FILE.equals(child.getType())); } private VcsChange addChange(final HistoryElement element, final String childFullPath, final ClearCaseConnection connection, final VcsChangeInfo.Type type, final String beforeVersion, final String afterVersion, final MultiMap key2changes) throws VcsException { final CCModificationKey modificationKey = new CCModificationKey(Revision.fromChange(element.getChangeInfo()), element.getUser(), element.getActivity()); final VcsChange change = createChange(type, connection, beforeVersion, afterVersion, childFullPath); key2changes.putValue(modificationKey, change); final CCModificationKey realKey = findKey(modificationKey, key2changes); if (realKey != null) { realKey.getCommentHolder().update(element.getActivity(), element.getComment(), connection.getVersionDescription(childFullPath, !isFile(type))); if (!modificationKey.getVersion().beforeOrEquals(realKey.getVersion())) { // must keep the greatest eventId realKey.setVersion(modificationKey.getVersion()); } } return change; } @Nullable private CCModificationKey findKey(final CCModificationKey modificationKey, final MultiMap key2changes) { for (CCModificationKey key : key2changes.keySet()) { if (key.equals(modificationKey)) return key; } return null; } private VcsChange createChange(final VcsChangeInfo.Type type, ClearCaseConnection connection, final String beforeVersion, final String afterVersion, final String childFullPath) throws VcsException { String relativePath = connection.getObjectRelativePathWithoutVersions(childFullPath, isFile(type)); return new VcsChange(type, relativePath, relativePath, beforeVersion, afterVersion); } private boolean isFile(final VcsChangeInfo.Type type) { switch (type) { case ADDED: case CHANGED: case REMOVED: { return true; } default: { return false; } } } public void buildPatch(final VcsRoot root, final Revision fromVersion, final Revision toVersion, final PatchBuilder builder, final IncludeRule includeRule) throws IOException, VcsException { withConnection(root, includeRule, true, null, new ConnectionProcessor() { public void process(@NotNull final ClearCaseConnection connection) throws VcsException, IOException { buildPatchForConnection(builder, fromVersion, toVersion, connection); } }); } private void buildPatchForConnection(PatchBuilder builder, Revision fromVersion, Revision toVersion, ClearCaseConnection connection) throws IOException, VcsException { try { final boolean useCache = USE_CC_CACHE && !connection.getConfigSpec().hasLabelBasedVersionSelector(); new CCPatchProvider(connection, useCache).buildPatch(builder, fromVersion, toVersion); } catch (final ExecutionException e) { throw new VcsException(e); } } private static byte[] getFileContent(final ClearCaseConnection connection, final String filePath) throws VcsException { try { final File tempFile = FileUtil.createTempFile("cc", "tmp"); FileUtil.delete(tempFile); try { connection.loadFileContent(tempFile, filePath); if (tempFile.isFile()) { return FileUtil.loadFileBytes(tempFile); } else { throw new VcsException("Cannot get content of " + filePath); } } finally { FileUtil.delete(tempFile); } } catch (ExecutionException e) { throw new VcsException(e); } catch (InterruptedException e) { throw new VcsException(e); } catch (IOException e) { throw new VcsException(e); } } @NotNull public byte[] getContent(@NotNull final VcsModification vcsModification, @NotNull final VcsChangeInfo change, @NotNull final VcsChangeInfo.ContentType contentType, @NotNull final VcsRoot vcsRoot) throws VcsException { final Ref result = new Ref(); try { withConnection(vcsRoot, IncludeRule.createDefaultInstance(), null, new ConnectionProcessor() { public void process(@NotNull final ClearCaseConnection connection) throws VcsException { final String filePath = new File(connection.getViewWholePath()).getParent() + File.separator + (contentType == VcsChangeInfo.ContentType.BEFORE_CHANGE ? change.getBeforeChangeRevisionNumber() : change.getAfterChangeRevisionNumber()); result.set(getFileContent(connection, filePath)); } }); } catch (IOException e) { throw new VcsException(e); } return result.get(); } @NotNull public byte[] getContent(@NotNull final String filePath, @NotNull final VcsRoot versionedRoot, @NotNull final String version) throws VcsException { final Ref result = new Ref(); try { withConnection(versionedRoot, IncludeRule.createDefaultInstance(), null, new ConnectionProcessor() { public void process(@NotNull final ClearCaseConnection connection) throws VcsException { final String preparedPath = CCPathElement.normalizeSeparators(filePath); try { connection.collectChangesToIgnore(Revision.fromNotNullString(version)); result.set(doGetContent(preparedPath, connection)); } catch (final ParseException e) { throw new VcsException(e); } catch (final VcsException e) { // http://youtrack.jetbrains.com/issue/TW-20973 int sepPos = preparedPath.indexOf(File.separator); while (sepPos != -1) { try { result.set(doGetContent(preparedPath.substring(sepPos + 1), connection)); return; } catch (final VcsException ignore) {} sepPos = preparedPath.indexOf(File.separator, sepPos + 1); } throw e; } } }); } catch (IOException e) { throw new VcsException(e); } return result.get(); } @NotNull private static byte[] doGetContent(@NotNull final String filePath, @NotNull final ClearCaseConnection connection) throws VcsException { final String path = new File(connection.getViewWholePath()).getParent() + File.separator + connection.getObjectRelativePathWithVersions(connection.getViewWholePath() + File.separator + filePath, true); return getFileContent(connection, path); } @NotNull public String getName() { return Constants.NAME; } @NotNull @Used("jsp") public String getDisplayName() { return "ClearCase"; } @NotNull public PropertiesProcessor getVcsPropertiesProcessor() { return new AbstractVcsPropertiesProcessor() { public Collection process(Map properties) { final ArrayList validationResult = new ArrayList(); //collect all validation errors final ValidationComposite composite = createValidationComposite(null, null); //transform to expected final Map> result = composite.validate(properties); for (final Map.Entry> entry : result.entrySet()) { validationResult.addAll(entry.getValue()); } return validationResult; } }; } @NotNull public String getVcsSettingsJspFilePath() { return "clearcaseSettings.jsp"; } @NotNull @Override @SuppressWarnings("deprecation") public String getCurrentVersion(@NotNull final VcsRoot root) throws VcsException { final Ref result = new Ref(); try { withConnection(root, IncludeRule.createDefaultInstance(), null, new ConnectionProcessor() { public void process(@NotNull final ClearCaseConnection connection) throws VcsException, IOException { result.set(connection.getCurrentRevision().asString()); } }); } catch (IOException e) { throw new VcsException(e); } return result.get(); } @Override public boolean isCurrentVersionExpensive() { return false; } @NotNull public String getVersionDisplayName(@NotNull final String version, @NotNull final VcsRoot root) throws VcsException { try { return Revision.fromNotNullString(version).asDisplayString(); } catch (final ParseException e) { throw new VcsException(e); } } @NotNull public Comparator getVersionComparator() { return new Comparator() { public int compare(@NotNull final String versionString1, @NotNull final String versionString2) { try { final Revision version1 = Revision.fromNotNullString(versionString1); final Revision version2 = Revision.fromNotNullString(versionString2); return version1.beforeOrEquals(version2) ? (version2.beforeOrEquals(version1) ? 0 : -1) : 1; } catch (final ParseException e) { ExceptionUtil.rethrowAsRuntimeException(e); return 0; } } }; } @Override public boolean isAgentSideCheckoutAvailable() { return true; } @NotNull public String describeVcsRoot(@NotNull VcsRoot vcsRoot) { try { return Constants.NAME + ": " + getViewPath(vcsRoot).getWholePath(); } catch (Exception e) { return Constants.NAME; } } public String testConnection(final @NotNull VcsRoot vcsRoot) throws VcsException { final String[] validationResult = new String[] { "Passed" }; //validate in general final ValidationComposite composite = createValidationComposite(vcsRoot, validationResult); //fire validation final Map> validationErrors = composite.validate(vcsRoot.getProperties()); //format exception if something is final StringBuffer readableOut = new StringBuffer(); if (!validationErrors.isEmpty()) { for (final Map.Entry> entry : validationErrors.entrySet()) { for (final InvalidProperty prop : entry.getValue()) { readableOut.append(String.format("%s\n", prop.getInvalidReason())); } } throw new VcsException(readableOut.toString()); } //all ok return validationResult[0]; } @NotNull private ValidationComposite createValidationComposite(@Nullable final VcsRoot vcsRoot, @Nullable final String[] validationResult) { return new ValidationComposite( new ClearCaseValidation.ClearcaseViewRootPathValidator(), new ClearCaseValidation.ClearcaseViewRelativePathValidator(), new ClearCaseValidation.CleartoolValidator(), new ClearCaseValidation.ClearcaseConfigurationValidator(), new ClearCaseValidation.ClearcaseViewValidator(), new IValidation() { public boolean validate(Map properties, Collection validationResultBuffer) { if (vcsRoot == null || validationResult == null) return true; try { validationResult[0] = ClearCaseConnection.testConnection(vcsRoot); } catch (final Exception e) { validationResultBuffer.add( //it fired by "Relative path..." setting because others already checked hard I guess... new InvalidProperty(Constants.RELATIVE_PATH, String.format(Messages.getString("ClearCaseSupport.clearcase_view_relative_path_is_not_under_configspec_loading_rules"), e.getMessage())) ); LOG.info(e.toString()); LOG.debug(e.toString(), e); return false; } return true; } public String getDescription() { return "Summary functionality check"; } } ); } @Nullable public Map getDefaultVcsProperties() { return new HashMap() {{ put(Constants.BRANCH_PROVIDER, Constants.BRANCH_PROVIDER_AUTO); }}; } public Collection getClientMapping(@NotNull final VcsRoot vcsRoot) throws VcsException { try { return Collections.singleton(new VcsClientMapping(getViewPath(vcsRoot).getWholePath(), "")); } catch (IOException e) { throw new VcsException(e); } } @NotNull public Collection mapFullPath(@NotNull final VcsRootEntry rootEntry, @NotNull final String fullPath) { final ViewPath viewPath; try { viewPath = getViewPath(rootEntry.getVcsRoot()); } catch (final Exception e) { LOG.debug("CC.MapFullPath: View path not defined: " + e.getLocalizedMessage()); return Collections.emptySet(); } final File viewPathFile = cutOffVobsDir(viewPath.getWholePath(), viewPath); if (viewPathFile == null) { // actually impossible LOG.debug("CC.MapFullPath: Unknown error"); return Collections.emptySet(); } final File fullPathFile = cutOffVobsDir(fullPath, viewPath); if (fullPathFile == null) { LOG.debug("CC.MapFullPath: File \"" + fullPath + "\" is not under view \"" + viewPath.getWholePath() + "\""); return Collections.emptySet(); } final String relativePath = getRelativePathIfAncestor(viewPathFile, fullPathFile); if (relativePath != null) { LOG.debug("CC.MapFullPath: File \"" + fullPathFile.getAbsolutePath() + "\" is under \"" + viewPathFile.getAbsolutePath() + "\" result is \"" + relativePath + "\""); return Collections.singleton(relativePath); } else { LOG.debug("CC.MapFullPath: File \"" + fullPathFile.getAbsolutePath() + "\" is not under \"" + viewPathFile.getAbsolutePath() + "\""); return Collections.emptySet(); } } @Nullable private static File cutOffVobsDir(@NotNull final String filePath, @NotNull final ViewPath viewPath) { final File clearCaseViewPathFile = viewPath.getClearCaseViewPathFile(); final File file = new File(filePath); String relativePath = isAbsolute(file) ? getRelativePathIfAncestor(clearCaseViewPathFile, file) : file.getPath(); if (relativePath == null) return null; if (StringUtil.startsWithIgnoreCase(relativePath.replace('\\','/'), Constants.VOBS)) { relativePath = relativePath.substring(Constants.VOBS.length()); } return new File(clearCaseViewPathFile, relativePath); } // File path can be "/bla-bla-bla" when server is on Windows and can be "C:\bla-bla-bla" when server is on Linux/Mac OS - we must handle this private static boolean isAbsolute(@NotNull final File file) { return file.isAbsolute() || file.getPath().startsWith(File.separator) || file.getPath().startsWith(":" + File.separator, 1); } @Nullable private static String getRelativePathIfAncestor(@NotNull final File parentFile, @NotNull final File childFile) { return FileUtil.isAncestor(parentFile, childFile, false) ? FileUtil.getRelativePath(parentFile.getAbsolutePath(), childFile.getAbsolutePath(), File.separatorChar) : null; } @Override public VcsPersonalSupport getPersonalSupport() { return this; } @Override public LabelingSupport getLabelingSupport() { return this; } @NotNull public VcsFileContentProvider getContentProvider() { return this; } @NotNull public CollectChangesPolicy getCollectChangesPolicy() { return this; } @NotNull public BuildPatchPolicy getBuildPatchPolicy() { return this; } public List collectChanges(final VcsRoot root, final Revision fromVersion, final Revision currentVersion, final IncludeRule includeRule) throws VcsException { LOG.debug(String.format("Attempt connect to '%s'", root.describe(true))); final Ref> result = new Ref>(); try { withConnection(root, includeRule, null, new ConnectionProcessor() { public void process(@NotNull final ClearCaseConnection connection) throws VcsException { try { result.set(collectChangesWithConnection(root, fromVersion, currentVersion, connection)); } catch (VcsException e) { LOG.debug(String.format("Could not establish connection: %s", e.getMessage())); throw e; } } }); } catch (IOException e) { throw new VcsException(e); } return result.get(); } private List collectChangesWithConnection(VcsRoot root, Revision fromVersion, Revision currentVersion, ClearCaseConnection connection) throws VcsException { try { final ArrayList list = new ArrayList(); final MultiMap key2changes = new MultiMap(); final Set addFileActivities = new HashSet(); final Set zeroToOneChangedFiles = new HashSet(); final ChangedFilesProcessor fileProcessor = createCollectingChangesFileProcessor(key2changes, addFileActivities, zeroToOneChangedFiles, connection); try { LOG.debug("Collecting changes..."); CCParseUtil.processChangedFiles(connection, fromVersion, currentVersion, fileProcessor); for (CCModificationKey key : key2changes.keySet()) { final List changes = key2changes.get(key); if (addFileActivities.contains(key.getActivity())) { FilterUtil.filterCollection(changes, new Filter() { public boolean accept(@NotNull final VcsChange data) { return !zeroToOneChangedFiles.contains(data); } }); } if (changes.isEmpty()) continue; final DateRevision version = key.getVersion(); list.add(new ModificationData( version.getDate(), changes, key.getCommentHolder().toString(), key.getUser(), root, version.asString(), version.asDisplayString() )); } } catch (final Exception e) { throw new VcsException(e); } Collections.sort(list, new Comparator() { public int compare(final ModificationData o1, final ModificationData o2) { return o1.getVcsDate().compareTo(o2.getVcsDate()); } }); return list; } finally { LOG.debug("Collecting changes was finished."); } } @NotNull public String label(@NotNull final String label, @NotNull final String version, @NotNull final VcsRoot root, @NotNull final CheckoutRules checkoutRules) throws VcsException { try { final Revision revision = Revision.fromNotNullString(version); createLabel(label, root); final VersionProcessor labeler = getClearCaseLabeler(label); final ConnectionProcessor childrenProcessor = getChildrenProcessor(revision, labeler); for (IncludeRule includeRule : checkoutRules.getRootIncludeRules()) { withConnection(root, includeRule, null, childrenProcessor); withRootConnection(root, getParentsProcessor(revision, labeler, createPath(root, includeRule))); } return label; } catch (ParseException e) { throw new VcsException(e); } catch (IOException e) { throw new VcsException(e); } } private String createPath(@NotNull final VcsRoot root, @NotNull final IncludeRule includeRule) throws VcsException { try { final ViewPath viewPath = getViewPath(root); viewPath.setIncludeRuleFrom(includeRule); return viewPath.getWholePath(); } catch (IOException e) { throw new VcsException(e); } } private ConnectionProcessor getParentsProcessor(final Revision version, final VersionProcessor labeler, final String path) { return new ConnectionProcessor() { public void process(@NotNull final ClearCaseConnection connection) throws VcsException { connection.processAllParents(version, labeler, path); } }; } private ConnectionProcessor getChildrenProcessor(final Revision version, final VersionProcessor labeler) { return new ConnectionProcessor() { public void process(@NotNull final ClearCaseConnection connection) throws VcsException { connection.processAllVersions(version, labeler, true, true); } }; } private void withRootConnection(@NotNull final VcsRoot root, @NotNull final ConnectionProcessor processor) throws VcsException, IOException { doWithConnection(getRootPath(root), root, false, processor); } private VersionProcessor getClearCaseLabeler(@NotNull final String label) { return new VersionProcessor() { public void processFile(final String fileFullPath, final String relPath, final String pname, final String version, final ClearCaseConnection clearCaseConnection, final boolean text, final boolean executable) throws VcsException { try { clearCaseConnection.mklabel(version, fileFullPath, label, false); } catch (IOException e) { throw new VcsException(e); } } public void processDirectory(final String fileFullPath, final String relPath, final String pname, final String version, final ClearCaseConnection clearCaseConnection) throws VcsException { try { clearCaseConnection.mklabel(version, fileFullPath, label, true); } catch (IOException e) { throw new VcsException(e); } } public void finishProcessingDirectory() { } }; } private void createLabel(final String label, final VcsRoot root) throws VcsException { try { ClearCaseInteractiveProcessPool.doWithProcess(getViewPath(root).getWholePath(), new ClearCaseInteractiveProcessPool.ProcessRunnable() { public void run(@NotNull final ClearCaseInteractiveProcess process) throws VcsException { final boolean useGlobalLabel = "true".equals(root.getProperty(Constants.USE_GLOBAL_LABEL)); try { final List parameters = new ArrayList(); parameters.add("mklbtype"); if (useGlobalLabel) { parameters.add("-global"); } if (ClearCaseConnection.isLabelExists(process, label)) { parameters.add("-replace"); } parameters.add("-c"); parameters.add("Label created by TeamCity"); if (useGlobalLabel) { final String globalLabelsVob = root.getProperty(Constants.GLOBAL_LABELS_VOB); parameters.add(label + "@" + globalLabelsVob); } else { parameters.add(label); } try { process.executeAndReturnProcessInput(makeArray(parameters)).close(); } catch (final IOException ignore) {} } catch (final Exception e) { if (!e.getLocalizedMessage().contains("already exists")) { throw new VcsException(e);//e; } } } }); } catch (final IOException e) { throw new VcsException(e); } } @NotNull public static String[] makeArray(@NotNull final List parameters) { final String[] array = new String[parameters.size()]; for (int i = 0; i < parameters.size(); i++) { array[i] = parameters.get(i); } return array; } public boolean sourcesUpdatePossibleIfChangesNotFound(@NotNull final VcsRoot root) { if (TeamCityProperties.getBoolean("clearcase.sources.update.is.not.possible.if.changes.not.found")) { return false; } try { final ViewPath viewPath = getViewPath(root); return ClearCaseInteractiveProcessPool.doWithProcess(viewPath.getWholePath(), new ClearCaseInteractiveProcessPool.ProcessComputable() { public Boolean compute(@NotNull final ClearCaseInteractiveProcess process) throws IOException, VcsException { final ConfigSpec spec = ConfigSpecParseUtil.getConfigSpec(viewPath, process); // as far we still cannot detect label moving, // have to use all resources set for the patch creation return spec != null && spec.hasLabelBasedVersionSelector(); } }); } catch (final VcsException e) { LOG.warnAndDebugDetails("Failed to run sourcesUpdatePossibleIfChangesNotFound()", e); } catch (final IOException e) { LOG.warnAndDebugDetails("Failed to run sourcesUpdatePossibleIfChangesNotFound()", e); } return false; } public static void consumeBranches(@NotNull final VcsRoot root, @NotNull final Consumer> consumer) { if (BRANCH_PROVIDER_CUSTOM.equals(root.getProperty(BRANCH_PROVIDER, BRANCH_PROVIDER_AUTO))) { consumer.consume(StringUtil.split(StringUtil.emptyIfNull(root.getProperty(BRANCHES)))); } else { consumer.consume(null); } } @NotNull @Override public Map getCheckoutProperties(@NotNull final VcsRoot root) { final Map result = new HashMap(4); try { final ViewPath viewPath = getViewPath(root); result.put(CC_VIEW_PATH, viewPath.getClearCaseViewPath()); // normalized path result.put(RELATIVE_PATH, viewPath.getRelativePathWithinTheView()); // normalized path consumeBranches(root, new Consumer>() { public void consume(@Nullable final Collection branches) { if (branches == null) { // auto detecting result.put(BRANCH_PROVIDER, BRANCH_PROVIDER_AUTO); } else { result.put(BRANCH_PROVIDER, BRANCH_PROVIDER_CUSTOM); result.put(BRANCHES, StringUtil.join(new TreeSet(branches), ",")); // join branches in nomalized order } } }); } catch (final Exception e) { LOG.warnAndDebugDetails("Failed to get repository properties", e); } return result; } @NotNull public List collectChanges(@Nullable final VcsRoot fromRoot, @NotNull final String fromVersion, @NotNull final VcsRoot toRoot, @Nullable final String toVersion, @NotNull final CheckoutRules checkoutRules) throws VcsException { try { return fromRoot == null || getViewPath(fromRoot).equals(getViewPath(toRoot)) ? VcsSupportUtil.collectBuildChanges(toRoot, fromVersion, toVersion, checkoutRules, this) : Collections.emptyList(); } catch (IOException e) { throw new VcsException(e); } } @NotNull public IncludeRuleChangeCollector getChangeCollector(@NotNull final VcsRoot root, @NotNull final String fromVersion, @Nullable final String currentVersion) throws VcsException { return new IncludeRuleChangeCollector() { @NotNull public List collectChanges(@NotNull final IncludeRule includeRule) throws VcsException { try { return ClearCaseSupport.this.collectChanges(root, Revision.fromNotNullString(fromVersion), Revision.fromString(currentVersion), includeRule); } catch (final ParseException e) { throw new VcsException(e); } } public void dispose() {} }; } @NotNull public IncludeRulePatchBuilder getPatchBuilder(@NotNull final VcsRoot root, @Nullable final String fromVersion, @NotNull final String toVersion) { return new IncludeRulePatchBuilder() { public void buildPatch(@NotNull final PatchBuilder builder, @NotNull final IncludeRule includeRule) throws IOException, VcsException { try { ClearCaseSupport.this.buildPatch(root, Revision.fromString(fromVersion), Revision.fromNotNullString(toVersion), builder, includeRule); } catch (final ParseException e) { throw new VcsException(e); } } public void dispose() {} }; } @Override public TestConnectionSupport getTestConnectionSupport() { return this; } @Override public ListFilesPolicy getListFilesPolicy() { return this; } @NotNull public Collection listFiles(@NotNull final VcsRoot root, @NotNull final String directoryPath) throws VcsException { final Ref> result = new Ref>(); try { withConnection(root, IncludeRule.createDefaultInstance(), null, new ConnectionProcessor() { public void process(@NotNull final ClearCaseConnection connection) throws VcsException, IOException { final String dirFullPath = new File(getViewPath(root).getWholePath(), directoryPath).getAbsolutePath(); final Version dirVersion = connection.getLastVersion(dirFullPath, false); if (dirVersion == null) { result.set(Collections.emptySet()); return; } final String dirPathWithVersion = dirFullPath + CCParseUtil.CC_VERSION_SEPARATOR + dirVersion.getWholeName(); final Collection files = new ArrayList(); for (final SimpleDirectoryChildElement child : connection.getChildren(dirPathWithVersion)) { files.add(new VcsFileData(child.getName(), child.getType() == SimpleDirectoryChildElement.Type.DIRECTORY)); } result.set(files); } }); } catch (IOException e) { throw new VcsException(e); } return result.get(); } @NotNull public static SortedSet detectBranches(@NotNull final ViewPath viewPath) throws VcsException, IOException { return ClearCaseInteractiveProcessPool.doWithProcess(viewPath.getWholePath(), new ClearCaseInteractiveProcessPool.ProcessComputable>() { public SortedSet compute(@NotNull final ClearCaseInteractiveProcess process) throws IOException, VcsException { return ConfigSpecParseUtil.getConfigSpec(viewPath, process).getBranches(); } }); } public static interface ConnectionProcessor { void process(@NotNull ClearCaseConnection connection) throws VcsException, IOException; } public void updateParameters(@NotNull BuildStartContext context) { final SRunningBuild build = context.getBuild(); try { //collect all clearcase's roots and populate current ConfigSpecs for each for (final VcsRootEntry entry : build.getVcsRootEntries()) { if (getName().equals(entry.getVcsRoot().getVcsName())) { //looking for clearcase only final String viewPath = trim(entry.getVcsRoot().getProperty(Constants.CC_VIEW_PATH)); final File viewRoot = new File(viewPath); if (viewRoot.exists()) { try { final CCSnapshotView ccView = CCSnapshotView.init(viewRoot); LOG.debug(String.format("The \"%s\" view initialized", ccView)); final StringBuffer specsBuffer = new StringBuffer(); for (String spec : ccView.getConfigSpec()) { specsBuffer.append(spec).append("\n"); } //pass config spec to agents final String configSpecParameterName = getConfigSpecParameterName(entry.getVcsRoot()); final String configSpecParameterValue = specsBuffer.toString(); context.addSharedParameter(configSpecParameterName, configSpecParameterValue); LOG.debug(String.format("added SharedParameter: %s=\"%s\"", configSpecParameterName, configSpecParameterValue)); //pass tag to agents final String originalViewTagParameterName = getOriginalViewTagParameterName(entry.getVcsRoot()); String originalViewTagParameterValue = ccView.getTag().trim(); context.addSharedParameter(originalViewTagParameterName, originalViewTagParameterValue); LOG.debug(String.format("added SharedParameter: %s=\"%s\"", originalViewTagParameterName, originalViewTagParameterValue)); } catch (CCException e) { LOG.error(e.getMessage(), e); } } else { LOG.error(String.format("The view's root directory \"%s\" does not exist. Could not get ConfigSpec of this VcsRoot", viewPath)); } } } } catch (Throwable e) { LOG.error(e.getMessage(), e); } } public static String getOriginalViewTagParameterName(VcsRoot root) { return String.format("%s", String.format(Constants.AGENT_SOURCE_VIEW_TAG_PROP_PATTERN, root.getId())); } public static String getConfigSpecParameterName(VcsRoot root) { return String.format("%s", String.format(Constants.AGENT_CONFIGSPECS_SYS_PROP_PATTERN, root.getId())); } @NotNull public Collection getParametersAvailableOnAgent(@NotNull SBuild build) { return Collections.emptyList(); } static String trim(String string) { if (string != null) { return string.trim(); } return null; } public boolean isClearCaseClientNotFound() { return !Util.canRun(Constants.CLEARTOOL_CHECK_AVAILABLE_COMMAND); } @NotNull public Pattern[] getIgnoreErrorPatterns() { final ArrayList patterns = new ArrayList(); final String prop = TeamCityProperties.getPropertyOrNull(Constants.TEAMCITY_PROPERTY_IGNORE_ERROR_PATTERN); if (prop != null && prop.trim().length() > 0) { for (String pstr : COLON_OR_SEMICOLON_PATTERN.split(prop)) { if (pstr.trim().length() > 0) { try { patterns.add(Pattern.compile(pstr, Pattern.DOTALL | Pattern.MULTILINE)); } catch (PatternSyntaxException e) { LOG.error(e.toString()); } } } } return patterns.toArray(new Pattern[patterns.size()]); } }