Mercurial > hg > mercurial
view mercurial-server/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/MercurialVcsSupport.java @ 426:c91c4f1ebd53
Settings -> HgVcsRoot
author | Dmitry Neverov <dmitry.neverov@jetbrains.com> |
---|---|
date | Fri, 11 May 2012 15:55:57 +0400 |
parents | e33c3e4918f5 |
children | 04eab204ba39 |
line wrap: on
line source
/* * Copyright 2000-2011 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.mercurial; import jetbrains.buildServer.BuildAgent; import jetbrains.buildServer.Used; import jetbrains.buildServer.buildTriggers.vcs.AbstractVcsPropertiesProcessor; import jetbrains.buildServer.buildTriggers.vcs.mercurial.command.*; import jetbrains.buildServer.buildTriggers.vcs.mercurial.command.exception.UnknownRevisionException; import jetbrains.buildServer.buildTriggers.vcs.mercurial.command.exception.UnrelatedRepositoryException; import jetbrains.buildServer.log.Loggers; import jetbrains.buildServer.serverSide.*; import jetbrains.buildServer.util.EventDispatcher; import jetbrains.buildServer.util.FileUtil; import jetbrains.buildServer.util.StringUtil; import jetbrains.buildServer.util.cache.ResetCacheRegister; import jetbrains.buildServer.vcs.*; import jetbrains.buildServer.vcs.impl.VcsRootImpl; import jetbrains.buildServer.vcs.patches.PatchBuilder; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.io.File; import java.io.FileFilter; import java.io.FileInputStream; import java.io.IOException; import java.util.*; /** * Mercurial VCS plugin for TeamCity works as follows: * <ul> * <li>clones repository to internal storage * <li>before any operation with working copy of repository pulls changes from the original repository * <li>executes corresponding hg command * </ul> * * <p>Working copy of repository is created in the $TEAMCITY_DATA_PATH/system/caches/hg_<hash code> folder. * <p>Personal builds (remote runs) are not yet supported, they require corresponding functionality from the IDE. */ public class MercurialVcsSupport extends ServerVcsSupport implements LabelingSupport, VcsFileContentProvider, BranchSupport, CollectChangesBetweenRoots, BuildPatchByCheckoutRules { private final VcsManager myVcsManager; private final File myDefaultWorkFolderParent; private final MirrorManager myMirrorManager; private final ServerPluginConfig myConfig; private final HgPathProvider myHgPathProvider; private final RepoFactory myRepoFactory; private final FileFilter myIgnoreDotHgFilter = new IgnoreDotHgFilter(); private final FileFilter myAcceptAllFilter = new AcceptAllFilter(); public MercurialVcsSupport(@NotNull final VcsManager vcsManager, @NotNull final SBuildServer server, @NotNull final EventDispatcher<BuildServerListener> dispatcher, @NotNull final ResetCacheRegister resetCacheHandlerManager, @NotNull final ServerPluginConfig config, @NotNull final HgPathProvider hgPathProvider, @NotNull final RepoFactory repoFactory, @NotNull final MirrorManager mirrorManager) { myVcsManager = vcsManager; myConfig = config; myDefaultWorkFolderParent = myConfig.getCachesDir(); myMirrorManager = mirrorManager; myHgPathProvider = hgPathProvider; myRepoFactory = repoFactory; resetCacheHandlerManager.registerHandler(new MercurialResetCacheHandler(myMirrorManager)); dispatcher.addListener(new BuildServerAdapter() { @Override public void cleanupFinished() { server.getExecutor().submit(new Cleanup(myVcsManager, myMirrorManager, myConfig, myHgPathProvider)); } @Override public void serverShutdown() { myRepoFactory.dispose(); } @Override public void sourcesVersionReleased(@NotNull final BuildAgent agent) { super.sourcesVersionReleased(agent); server.getExecutor().submit(new Runnable() { public void run() { deleteWithLocking(myMirrorManager.getMirrors()); } }); } }); logUsedHg(); } private void logUsedHg() { String hgPath = myConfig.getHgPath(); if (hgPath != null) Loggers.VCS.info("Use server-wide hg path " + hgPath + ", path in the VCS root settings will be ignored"); else Loggers.VCS.info("Server-wide hg path is not set, will use path from the VCS root settings"); } private void deleteWithLocking(Collection<File> filesForDelete) { for (File f : filesForDelete) { lockWorkDir(f); try { FileUtil.delete(f); } finally { unlockWorkDir(f); } } } private List<VcsChange> toVcsChanges(final List<FileStatus> modifiedFiles, String prevVer, String curVer, CheckoutRules rules) { List<VcsChange> files = new ArrayList<VcsChange>(); for (FileStatus mf: modifiedFiles) { final String path = rules.map(mf.getPath()); if (shouldInclude(path)) files.add(toVcsChange(mf, prevVer, curVer, path)); } return files; } private boolean shouldInclude(String path) { return path != null; } private VcsChange toVcsChange(FileStatus mf, String prevVer, String curVer, String mappedPath) { String normalizedPath = PathUtil.normalizeSeparator(mf.getPath()); VcsChangeInfo.Type changeType = getChangeType(mf.getStatus()); if (changeType == null) { Loggers.VCS.warn("Unable to convert status: " + mf.getStatus() + " to VCS change type"); changeType = VcsChangeInfo.Type.NOT_CHANGED; } return new VcsChange(changeType, mf.getStatus().getName(), normalizedPath, mappedPath, prevVer, curVer); } private VcsChangeInfo.Type getChangeType(final Status status) { switch (status) { case ADDED:return VcsChangeInfo.Type.ADDED; case MODIFIED:return VcsChangeInfo.Type.CHANGED; case REMOVED:return VcsChangeInfo.Type.REMOVED; } return null; } @NotNull public byte[] getContent(@NotNull final VcsModification vcsModification, @NotNull final VcsChangeInfo change, @NotNull final VcsChangeInfo.ContentType contentType, @NotNull 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(@NotNull final String filePath, @NotNull final VcsRoot vcsRoot, @NotNull final String version) throws VcsException { ChangeSet cset = new ChangeSet(version); HgVcsRoot root = createHgRoot(vcsRoot); syncRepository(root, cset); HgRepo repo = createRepo(root); File parentDir = repo.cat().files(filePath).atRevision(cset).call(); File file = new File(parentDir, filePath); try { return FileUtil.loadFileBytes(file); } catch (IOException e) { throw new VcsException("Failed to load content of file " + filePath + " at revision " + version, e); } finally { deleteTmpDir(parentDir); } } @NotNull public String getName() { return Constants.VCS_NAME; } @NotNull @Used("jsp") public String getDisplayName() { return "Mercurial"; } @Nullable public PropertiesProcessor getVcsPropertiesProcessor() { return new AbstractVcsPropertiesProcessor() { public Collection<InvalidProperty> process(final Map<String, String> properties) { List<InvalidProperty> result = new ArrayList<InvalidProperty>(); if (isEmpty(properties.get(Constants.HG_COMMAND_PATH_PROP))) { result.add(new InvalidProperty(Constants.HG_COMMAND_PATH_PROP, "Path to 'hg' command must be specified")); } if (isEmpty(properties.get(Constants.REPOSITORY_PROP))) { result.add(new InvalidProperty(Constants.REPOSITORY_PROP, "Repository must be specified")); } return result; } }; } @NotNull public String getVcsSettingsJspFilePath() { return "mercurialSettings.jsp"; } @NotNull public String getCurrentVersion(@NotNull final VcsRoot root) throws VcsException { HgVcsRoot hgRoot = createHgRoot(root); syncRepository(hgRoot); HgRepo repo = createRepo(hgRoot); Map<String, ChangeSet> result = repo.branches().call(); if (!result.containsKey(hgRoot.getBranchName())) { throw new VcsException("Unable to find current version for the branch: " + hgRoot.getBranchName()); } return result.get(hgRoot.getBranchName()).getFullVersion(); } public boolean sourcesUpdatePossibleIfChangesNotFound(@NotNull final VcsRoot root) { return false; } @NotNull public String describeVcsRoot(final VcsRoot vcsRoot) { return "mercurial: " + vcsRoot.getProperty(Constants.REPOSITORY_PROP); } @Override public TestConnectionSupport getTestConnectionSupport() { return new TestConnectionSupport() { public String testConnection(@NotNull final VcsRoot vcsRoot) throws VcsException { HgVcsRoot root = createHgRoot(vcsRoot); String idResult = createRepo(root).id() .repository(root.getRepository()) .withAuthSettings(root.getAuthSettings()) .call(); StringBuilder res = new StringBuilder(); res.append(quoteIfNeeded(root.getHgCommandPath())); res.append(" identify "); res.append(quoteIfNeeded(root.getAuthSettings().getRepositoryUrlWithHiddenPassword(root.getRepository()))); res.append('\n').append(idResult); return res.toString(); } }; } private String quoteIfNeeded(@NotNull String str) { if (str.indexOf(' ') != -1) { return "\"" + str + "\""; } return str; } @Nullable public Map<String, String> getDefaultVcsProperties() { Map<String, String> defaults = new HashMap<String, String>(); defaults.put(Constants.HG_COMMAND_PATH_PROP, "hg"); defaults.put(Constants.UNCOMPRESSED_TRANSFER, "false"); return defaults; } public String getVersionDisplayName(@NotNull final String version, @NotNull final VcsRoot root) throws VcsException { return new ChangeSet(version).getId(); } @NotNull public Comparator<String> getVersionComparator() { // comparator is called when TeamCity needs to sort modifications in the order of their appearance, // currently we sort changes by revision number, not sure however that this is a good idea, // probably it would be better to sort them by timestamp (and to add timestamp into the version). return new Comparator<String>() { public int compare(final String o1, final String o2) { try { return new ChangeSet(o1).getRevNumber() - new ChangeSet(o2).getRevNumber(); } catch (Exception e) { return 1; } } }; } // builds patch from version to version private void buildIncrementalPatch(@NotNull final HgVcsRoot root, @NotNull final ChangeSet fromVer, @NotNull final ChangeSet toVer, @NotNull final PatchBuilder builder, @NotNull final CheckoutRules checkoutRules) throws VcsException, IOException { HgRepo repo = createRepo(root); List<FileStatus> modifiedFiles = repo.status().fromRevision(fromVer).toRevision(toVer).call(); List<String> notDeletedFiles = new ArrayList<String>(); for (FileStatus f: modifiedFiles) { if (f.getStatus() != Status.REMOVED) { notDeletedFiles.add(f.getPath()); } } if (notDeletedFiles.isEmpty()) return; File parentDir = repo.cat().files(notDeletedFiles).atRevision(toVer).call(); try { for (FileStatus f: modifiedFiles) { String mappedPath = checkoutRules.map(f.getPath()); if (mappedPath == null) continue; // skip final File virtualFile = new File(mappedPath); if (f.getStatus() == Status.REMOVED) { builder.deleteFile(virtualFile, true); } else { File realFile = new File(parentDir, f.getPath()); FileInputStream is = new FileInputStream(realFile); try { builder.changeOrCreateBinaryFile(virtualFile, null, is, realFile.length()); } finally { is.close(); } } } } finally { deleteTmpDir(parentDir); } } private void deleteTmpDir(File parentDir) { boolean dirDeleted = FileUtil.delete(parentDir); if (!dirDeleted) { Loggers.VCS.warn("Can not delete directory \"" + parentDir.getAbsolutePath() + "\""); } } // builds patch by exporting files using specified version private void buildFullPatch(@NotNull final HgVcsRoot root, @NotNull final ChangeSet toVer, @NotNull final PatchBuilder builder, @NotNull final CheckoutRules checkoutRules) throws IOException, VcsException { File tempDir = HgFileUtil.createTempDir(); try { HgRepo repo = createRepo(root); if (repo.hasSubreposAtRevision(toVer)) { Loggers.VCS.debug("Repository '" + root.getRepository() + "' has submodules at revision " + toVer.getId() + ", use 'hg clone' to build clean patch"); File mirrorDir = getWorkingDir(root); HgRepo cloneOfTheMirror = createRepo(root, tempDir); cloneOfTheMirror.doClone().fromRepository(mirrorDir) .setUpdateWorkingDir(false) .setUsePullProtocol(false) .useUncompressedTransfer(false) .call(); cloneOfTheMirror.setDefaultPath(root.getRepository()); cloneOfTheMirror.update().toRevision(toVer).call(); buildPatchFromDirectory(builder, tempDir, checkoutRules, myIgnoreDotHgFilter); } else { Loggers.VCS.debug("Repository '" + root.getRepository() + "' doesn't have submodules at revision " + toVer.getId() + ", use 'hg archive' to build clean patch"); repo.archive().revision(toVer).toDir(tempDir).call(); buildPatchFromDirectory(builder, tempDir, checkoutRules, myAcceptAllFilter); } } finally { FileUtil.delete(tempDir); } } private void buildPatchFromDirectory(final PatchBuilder builder, final File repRoot, final CheckoutRules checkoutRules, @NotNull final FileFilter filter) throws IOException { buildPatchFromDirectory(repRoot, builder, repRoot, checkoutRules, filter); } private void buildPatchFromDirectory(File curDir, final PatchBuilder builder, final File repRoot, final CheckoutRules checkoutRules, @NotNull final FileFilter filter) throws IOException { File[] files = curDir.listFiles(filter); if (files != null) { for (File realFile: files) { String relPath = realFile.getAbsolutePath().substring(repRoot.getAbsolutePath().length()); String mappedPath = checkoutRules.map(relPath); if (mappedPath != null && mappedPath.length() > 0) { final File virtualFile = new File(mappedPath); if (realFile.isDirectory()) { builder.createDirectory(virtualFile); buildPatchFromDirectory(realFile, builder, repRoot, checkoutRules, filter); } else { final FileInputStream is = new FileInputStream(realFile); try { builder.createBinaryFile(virtualFile, null, is, realFile.length()); } finally { is.close(); } } } else { if (realFile.isDirectory()) { buildPatchFromDirectory(realFile, builder, repRoot, checkoutRules, filter); } } } } } /* clone the repo if it doesn't exist, pull the repo if it doesn't contain specified changeSet */ private void syncRepository(@NotNull final HgVcsRoot root, @NotNull final ChangeSet cset) throws VcsException { File workingDir = getWorkingDir(root); lockWorkDir(workingDir); HgRepo repo = createRepo(root); try { if (repo.isValidRepository()) { if (repo.containsRevision(cset)) return; try { repo.pull().fromRepository(root.getRepository()) .withTimeout(myConfig.getPullTimeout()) .call(); } catch (UnrelatedRepositoryException e) { Loggers.VCS.warn("Repository at " + root.getRepository() + " is unrelated, clone it again"); myMirrorManager.forgetDir(workingDir); syncRepository(root, cset); } } else { repo.doClone().fromRepository(root.getRepository()) .useUncompressedTransfer(root.isUncompressedTransfer()) .setUpdateWorkingDir(false) .call(); repo.setDefaultPath(root.getRepository()); } } finally { unlockWorkDir(workingDir); } } public void syncRepository(@NotNull final HgVcsRoot root) throws VcsException { File workingDir = getWorkingDir(root); lockWorkDir(workingDir); HgRepo repo = createRepo(root); try { if (repo.isValidRepository()) { try { repo.pull().fromRepository(root.getRepository()) .withTimeout(myConfig.getPullTimeout()) .call(); } catch (UnrelatedRepositoryException e) { Loggers.VCS.warn("Repository at " + root.getRepository() + " is unrelated, clone it again"); myMirrorManager.forgetDir(workingDir); syncRepository(root); } } else { repo.doClone().fromRepository(root.getRepository()) .setUpdateWorkingDir(false) .useUncompressedTransfer(root.isUncompressedTransfer()) .call(); repo.setDefaultPath(root.getRepository()); } } finally { unlockWorkDir(workingDir); } } @Override public LabelingSupport getLabelingSupport() { return this; } @NotNull public VcsFileContentProvider getContentProvider() { return this; } @NotNull public String getRemoteRunOnBranchPattern() { return "remote-run/*"; } @NotNull public RepositoryState getCurrentState(@NotNull VcsRoot root) throws VcsException { return RepositoryStateFactory.createRepositoryState(getBranchesRevisions(root)); } @NotNull private Map<String, String> getBranchesRevisions(@NotNull VcsRoot root) throws VcsException { HgVcsRoot hgRoot = createHgRoot(root); syncRepository(hgRoot); HgRepo repo = createRepo(hgRoot); Map<String, String> result = new HashMap<String, String>(); for (Map.Entry<String, ChangeSet> entry : repo.branches().call().entrySet()) { result.put(entry.getKey(), entry.getValue().getId()); } return result; } @NotNull public Map<String, String> getBranchRootOptions(@NotNull VcsRoot root, @NotNull String branchName) { final Map<String, String> options = new HashMap<String, String>(root.getProperties()); options.put(Constants.BRANCH_NAME_PROP, branchName); return options; } @Nullable public PersonalBranchDescription getPersonalBranchDescription(@NotNull VcsRoot root, @NotNull String branchName) throws VcsException { HgVcsRoot hgRoot = createHgRoot(root); VcsRoot branchRoot = createBranchRoot(root, branchName); String baseVersion = getCurrentVersion(root); String branchVersion = getCurrentVersion(branchRoot); String mergeBase = getMergeBase(hgRoot, baseVersion, branchVersion); if (mergeBase == null) return null; List<ChangeSet> changeSets = createRepo(hgRoot).log() .fromRevision(mergeBase) .toRevision(branchVersion) .showCommitsFromAllBranches() .call(); if (changeSets.size() > 1) {//when branch points to the commit in original branch we get 1 cset String branchId = changeSets.get(1).getId(); String username = changeSets.get(changeSets.size() - 1).getUser(); return new PersonalBranchDescription(branchId, username); } else { return null; } } private VcsRoot createBranchRoot(VcsRoot original, String branchName) { VcsRootImpl result = new VcsRootImpl(original.getId(), original.getProperties()); result.addProperty(Constants.BRANCH_NAME_PROP, branchName); return result; } @NotNull public List<ModificationData> collectChanges(@NotNull VcsRoot fromRoot, @NotNull String fromRootRevision, @NotNull VcsRoot toRoot, @Nullable String toRootRevision, @NotNull CheckoutRules checkoutRules) throws VcsException { HgVcsRoot hgRoot = createHgRoot(toRoot); syncRepository(hgRoot); String toRevision = toRootRevision != null ? toRootRevision : getCurrentVersion(toRoot); String mergeBase = getMergeBase(hgRoot, fromRootRevision, toRevision); if (mergeBase == null) return Collections.emptyList(); return collectChanges(toRoot, mergeBase, toRootRevision, checkoutRules); } @Nullable private String getMergeBase(@NotNull HgVcsRoot root, @NotNull String revision1, @NotNull String revision2) throws VcsException { String result = createRepo(root).mergeBase() .revision1(revision1) .revision2(revision2) .call(); if (result == null) result = getMinusNthCommit(root, 10); return result; } @Nullable private String getMinusNthCommit(@NotNull HgVcsRoot root, int n) throws VcsException { LogCommand log = createRepo(root).log() .inBranch(root.getBranchName()) .toNamedRevision(root.getBranchName()); if (n > 0) log.setLimit(n); List<ChangeSet> changeSets = log.call(); if (changeSets.isEmpty()) return null; return changeSets.get(0).getId(); } @NotNull public CollectChangesPolicy getCollectChangesPolicy() { return this; } public List<ModificationData> collectChanges(@NotNull VcsRoot root, @NotNull String fromVersion, @Nullable String currentVersion, @NotNull CheckoutRules checkoutRules) throws VcsException { HgVcsRoot hgRoot = createHgRoot(root); syncRepository(hgRoot); List<ModificationData> result = new ArrayList<ModificationData>(); for (ChangeSet cset : getChangesets(hgRoot, fromVersion, currentVersion)) { result.add(createModificationData(cset, root, checkoutRules)); } return result; } private ModificationData createModificationData(@NotNull final ChangeSet cset, @NotNull final VcsRoot root, @NotNull final CheckoutRules checkoutRules) { List<ChangeSetRevision> parents = cset.getParents(); if (parents.isEmpty()) throw new IllegalStateException("Commit " + cset.getId() + " has no parents"); List<VcsChange> files = toVcsChanges(cset.getModifiedFiles(), parents.get(0).getFullVersion(), cset.getFullVersion(), checkoutRules); final ModificationData result = new ModificationData(cset.getTimestamp(), files, cset.getDescription(), cset.getUser(), root, cset.getFullVersion(), cset.getId()); for (ChangeSetRevision parent : parents) { result.addParentRevision(parent.getFullVersion()); } if (result.getParentRevisions().size() > 1) result.setCanBeIgnored(false); return result; } @NotNull private List<ChangeSet> getChangesets(@NotNull final HgVcsRoot root, @NotNull final String fromVersion, @Nullable final String toVersion) throws VcsException { if (toVersion == null) return Collections.emptyList(); String fromCommit = new ChangeSetRevision(fromVersion).getId(); String toCommit = new ChangeSetRevision(toVersion).getId(); try { List<ChangeSet> changesets = createRepo(root).collectChanges() .fromRevision(fromCommit) .toRevision(toCommit) .call(); Iterator<ChangeSet> iter = changesets.iterator(); while (iter.hasNext()) { ChangeSet cset = iter.next(); if (cset.getId().equals(fromCommit)) iter.remove();//skip already reported changes } return changesets; } catch (UnknownRevisionException e) { Loggers.VCS.warn("Revision '" + e.getRevision() + "' is unknown, will return no changes"); return Collections.emptyList(); } } @NotNull public BuildPatchPolicy getBuildPatchPolicy() { return this; } public void buildPatch(@NotNull VcsRoot root, @Nullable String fromVersion, @NotNull String toVersion, @NotNull PatchBuilder builder, @NotNull CheckoutRules checkoutRules) throws IOException, VcsException { HgVcsRoot hgRoot = createHgRoot(root); syncRepository(hgRoot); ChangeSet to = new ChangeSet(toVersion); if (fromVersion == null) { buildFullPatch(hgRoot, new ChangeSet(toVersion), builder, checkoutRules); } else { ChangeSet from = new ChangeSet(fromVersion); HgRepo repo = createRepo(hgRoot); if (!repo.containsRevision(from)) { Loggers.VCS.info("Cannot find revision " + fromVersion + " in repository " + hgRoot.getRepository() + ", will build a full patch"); cleanCheckoutDir(builder, checkoutRules); buildFullPatch(hgRoot, to, builder, checkoutRules); } else { buildIncrementalPatch(hgRoot, new ChangeSet(fromVersion), new ChangeSet(toVersion), builder, checkoutRules); } } } private void cleanCheckoutDir(@NotNull PatchBuilder builder, @NotNull CheckoutRules checkoutRules) throws IOException { builder.deleteDirectory(new File(checkoutRules.map("")), true); } private void lockWorkDir(@NotNull File workDir) { myMirrorManager.lockDir(workDir); } private void unlockWorkDir(@NotNull File workDir) { myMirrorManager.unlockDir(workDir); } @Override public boolean allowSourceCaching() { // since a copy of repository for each VCS root is already stored on disk // we do not need separate cache for our patches return false; } public String label(@NotNull String label, @NotNull String version, @NotNull VcsRoot root, @NotNull CheckoutRules checkoutRules) throws VcsException { File tmpDir = null; try { tmpDir = createLabelingTmpDir(); HgVcsRoot hgRoot = createHgRoot(root); hgRoot.setCustomWorkingDir(tmpDir); syncRepository(hgRoot); HgRepo repo = createRepo(hgRoot); repo.update().branch(hgRoot.getBranchName()).call(); String fixedTagname = fixTagName(label); repo.tag().revision(version) .tagName(fixedTagname) .byUser(hgRoot.getUserForTag()) .call(); repo.push().toRepository(hgRoot.getRepository()).call(); return fixedTagname; } finally { if (tmpDir != null) FileUtil.delete(tmpDir); } } private String fixTagName(final String label) { // according to Mercurial documentation http://hgbook.red-bean.com/hgbookch8.html#x12-1570008 // tag name must not contain: // Colon (ASCII 58, ':') // Carriage return (ASCII 13, '\r') // Newline (ASCII 10, '\n') // all these characters will be replaced with _ (underscore) return label.replace(':', '_').replace('\r', '_').replace('\n', '_'); } private File getWorkingDir(HgVcsRoot root) { File customDir = root.getCustomWorkingDir(); return customDir != null ? customDir : myMirrorManager.getMirrorDir(root.getRepository()); } private HgVcsRoot createHgRoot(@NotNull VcsRoot root) throws VcsException { HgVcsRoot hgRoot = new HgVcsRoot(myHgPathProvider, root); String customClonePath = hgRoot.getCustomClonePath(); if (!StringUtil.isEmptyOrSpaces(customClonePath) && !myDefaultWorkFolderParent.equals(new File(customClonePath).getAbsoluteFile())) { File parentDir = new File(customClonePath); createClonedRepositoryParentDir(parentDir); // take last part of repository path String repPath = hgRoot.getRepositoryUrlWithCredentials(); String[] splitted = repPath.split("[/\\\\]"); if (splitted.length > 0) { repPath = splitted[splitted.length-1]; } File customWorkingDir = new File(parentDir, repPath); hgRoot.setCustomWorkingDir(customWorkingDir); } return hgRoot; } private void createClonedRepositoryParentDir(final File parentDir) throws VcsException { if (!parentDir.exists() && !parentDir.mkdirs()) { throw new VcsException("Failed to create parent directory for cloned repository: " + parentDir.getAbsolutePath()); } } public boolean isAgentSideCheckoutAvailable() { return true; } private File createLabelingTmpDir() throws VcsException { try { return FileUtil.createTempDirectory("mercurial", "label"); } catch (IOException e) { throw new VcsException("Unable to create temporary directory"); } } /* for tests only */ public MirrorManager getMirrorManager() { return myMirrorManager; } @Override public boolean isDAGBasedVcs() { return true; } private static class IgnoreDotHgFilter implements FileFilter { public boolean accept(final File file) { return !(file.isDirectory() && ".hg".equals(file.getName())); } } private static class AcceptAllFilter implements FileFilter { public boolean accept(File pathname) { return true; } } private ServerHgRepo createRepo(@NotNull HgVcsRoot root) throws VcsException { return myRepoFactory.create(getWorkingDir(root), root.getHgCommandPath(), root.getAuthSettings()); } private HgRepo createRepo(@NotNull HgVcsRoot root, @NotNull File customDir) throws VcsException { return myRepoFactory.create(customDir, root.getHgCommandPath(), root.getAuthSettings()); } @NotNull public String getBranchName(@NotNull final VcsRoot root) { try { HgVcsRoot hgRoot = createHgRoot(root); return hgRoot.getBranchName(); } catch (VcsException e) { return "default"; } } @NotNull @Override public Map<String, String> getVcsRepositoryProperties(@NotNull VcsRoot root) { Map<String, String> rootProperties = root.getProperties(); Map<String, String> repositoryProperties = new HashMap<String, String>(); repositoryProperties.put(Constants.REPOSITORY_PROP, rootProperties.get(Constants.REPOSITORY_PROP)); return repositoryProperties; } @Override public ListFilesPolicy getListFilesPolicy(@NotNull VcsRoot root) { try { HgVcsRoot hgRoot = createHgRoot(root); HgRepo repo = createRepo(hgRoot); return new ListFilesSupport(this, hgRoot, repo); } catch (VcsException e) { return null; } } }