clearcase-server/src/jetbrains/buildServer/buildTriggers/vcs/clearcase/ClearCaseSupport.java (948 lines of code) (raw):
/*
* Copyright 2000-2022 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<BuildServerListener> 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<CCModificationKey, VcsChange> key2changes, final Set<String> addFileActivities, final Set<VcsChange> 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<CCModificationKey, VcsChange> key2changes, final Set<String> 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<CCModificationKey, VcsChange> 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<CCModificationKey, VcsChange> 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<byte[]> result = new Ref<byte[]>();
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<byte[]> result = new Ref<byte[]>();
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<InvalidProperty> process(Map<String, String> properties) {
final ArrayList<InvalidProperty> validationResult = new ArrayList<InvalidProperty>();
//collect all validation errors
final ValidationComposite composite = createValidationComposite(null, null);
//transform to expected
final Map<IValidation, Collection<InvalidProperty>> result = composite.validate(properties);
for (final Map.Entry<IValidation, Collection<InvalidProperty>> 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<String> result = new Ref<String>();
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<String> getVersionComparator() {
return new Comparator<String>() {
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<IValidation, Collection<InvalidProperty>> validationErrors = composite.validate(vcsRoot.getProperties());
//format exception if something is
final StringBuffer readableOut = new StringBuffer();
if (!validationErrors.isEmpty()) {
for (final Map.Entry<IValidation, Collection<InvalidProperty>> 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<String, String> properties, Collection<InvalidProperty> 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<String, String> getDefaultVcsProperties() {
return new HashMap<String, String>() {{
put(Constants.BRANCH_PROVIDER, Constants.BRANCH_PROVIDER_AUTO);
}};
}
public Collection<VcsClientMapping> 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<String> 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<ModificationData> 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<List<ModificationData>> result = new Ref<List<ModificationData>>();
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<ModificationData> collectChangesWithConnection(VcsRoot root, Revision fromVersion, Revision currentVersion, ClearCaseConnection connection) throws VcsException {
try {
final ArrayList<ModificationData> list = new ArrayList<ModificationData>();
final MultiMap<CCModificationKey, VcsChange> key2changes = new MultiMap<CCModificationKey, VcsChange>();
final Set<String> addFileActivities = new HashSet<String>();
final Set<VcsChange> zeroToOneChangedFiles = new HashSet<VcsChange>();
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<VcsChange> changes = key2changes.get(key);
if (addFileActivities.contains(key.getActivity())) {
FilterUtil.filterCollection(changes, new Filter<VcsChange>() {
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<ModificationData>() {
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<String> parameters = new ArrayList<String>();
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<String> 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<Boolean>() {
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<Collection<String>> 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<String, String> getCheckoutProperties(@NotNull final VcsRoot root) {
final Map<String, String> result = new HashMap<String, String>(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<Collection<String>>() {
public void consume(@Nullable final Collection<String> 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<String>(branches), ",")); // join branches in nomalized order
}
}
});
}
catch (final Exception e) {
LOG.warnAndDebugDetails("Failed to get repository properties", e);
}
return result;
}
@NotNull
public List<ModificationData> 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.<ModificationData>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<ModificationData> 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<VcsFileData> listFiles(@NotNull final VcsRoot root, @NotNull final String directoryPath) throws VcsException {
final Ref<Collection<VcsFileData>> result = new Ref<Collection<VcsFileData>>();
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.<VcsFileData>emptySet());
return;
}
final String dirPathWithVersion = dirFullPath + CCParseUtil.CC_VERSION_SEPARATOR + dirVersion.getWholeName();
final Collection<VcsFileData> files = new ArrayList<VcsFileData>();
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<String> detectBranches(@NotNull final ViewPath viewPath) throws VcsException, IOException {
return ClearCaseInteractiveProcessPool.doWithProcess(viewPath.getWholePath(), new ClearCaseInteractiveProcessPool.ProcessComputable<SortedSet<String>>() {
public SortedSet<String> 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<String> 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<Pattern> patterns = new ArrayList<Pattern>();
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()]);
}
}