Mercurial > hg > mercurial
changeset 380:3bc8125671b8
Merge branch Eluru-6.5.x
author | Dmitry Neverov <dmitry.neverov@jetbrains.com> |
---|---|
date | Wed, 15 Feb 2012 13:23:53 +0400 |
parents | a4829fde54f5 (diff) 55c2c88a2d82 (current diff) |
children | 44a16c27bed6 |
files | mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/BaseCommand.java mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/CommandResult.java mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/LogCommand.java mercurial-server/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/CommandFactoryImpl.java mercurial-server/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/MercurialVcsSupport.java mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/MercurialVcsSupportTest.java |
diffstat | 100 files changed, 6267 insertions(+), 3268 deletions(-) [+] |
line wrap: on
line diff
--- a/build.xml Wed Feb 15 12:07:12 2012 +0400 +++ b/build.xml Wed Feb 15 13:23:53 2012 +0400 @@ -1,72 +1,72 @@ -<project name="Mercurial VCS Support" default="dist" basedir="."> - <property file="mercurial.properties"/> - <import file="mercurial.xml"/> - - <property name="distPath" value="${basedir}/dist"/> - - <property name="plugin.name" value="mercurial"/> - - <property name="build.number" value=""/> - <tstamp> - <format property="timestamp" pattern="yyyyMMddhhmmss"/> - </tstamp> - <property name="snapshot.build.number" value="SNAPSHOT-${timestamp}"/> - <property name="build.vcs.number" value=""/> - - <condition property="plugin.version" value="${snapshot.build.number}" else="${build.number}"> - <matches pattern="snapshot-.*" string="${build.number}" casesensitive="false"/> - </condition> - - <import file="teamcity-common.xml"/> - - <target name="package" depends="define.version"> - <package.teamcity.plugin name="${plugin.name}" - server.output="${mercurial-server.output.dir}" - agent.output="${mercurial-agent.output.dir}" - common.output="${mercurial-common.output.dir}" - plugin.descriptor.file="${basedir}/teamcity-plugin.xml" - plugin.version="${plugin.version}"/> - </target> - - <target name="define.version" depends="define.version.if.under.teamcity"> - <tstamp> - <format property="current.time" pattern="yyyyMMddHHmm"/> - </tstamp> - <property name="plugin.version" value="SNAPSHOT-${current.time}"/> - </target> - - <target name="define.version.if.under.teamcity" if="build.number"> - <property name="plugin.version" value="${build.number}"/> - </target> - - <target name="dist" depends="check.teamcitydistribution,all,package"/> - - <target name="deploy" depends="dist"> - <deploy.teamcity.plugin name="${plugin.name}"/> - </target> - - <taskdef name="testng" classname="org.testng.TestNGAntTask" classpath="${basedir}/mercurial-tests/lib/testng-5.7-jdk15.jar"/> - - <path id="tests_classpath"> - <pathelement location="${agent.home.dir}/lib/runtime-util.jar"/> - <pathelement location="${agent.home.dir}/lib/buildServerRuntimeUtil.jar"/> - <path refid="mercurial-tests.runtime.module.classpath"/> - </path> - - <target name="run-tests" depends="clean, init, compile.module.mercurial-tests"> - <property name="suspend" value="n"/> - - <testng haltonfailure="no" failureProperty="failure_found" listener="org.testng.reporters.TestHTMLReporter" - outputdir="${basedir}/test-output" classpathref="tests_classpath" dumpcommand="true" workingDir="${basedir}"> - - <jvmarg value="-ea"/> - <!--<jvmarg value="-Xrunjdwp:transport=dt_socket,server=y,suspend=${suspend},address=5555"/>--> - - <sysproperty key="java.awt.headless" value="true"/> - - <xmlfileset dir="${basedir}/mercurial-tests/src"> - <include name="testng.xml"/> - </xmlfileset> - </testng> - </target> -</project> +<project name="Mercurial VCS Support" default="dist" basedir="."> + <property file="mercurial.properties"/> + <import file="mercurial.xml"/> + + <property name="distPath" value="${basedir}/dist"/> + + <property name="plugin.name" value="mercurial"/> + + <property name="build.number" value=""/> + <tstamp> + <format property="timestamp" pattern="yyyyMMddhhmmss"/> + </tstamp> + <property name="snapshot.build.number" value="SNAPSHOT-${timestamp}"/> + <property name="build.vcs.number" value=""/> + + <condition property="plugin.version" value="${snapshot.build.number}" else="${build.number}"> + <matches pattern="snapshot-.*" string="${build.number}" casesensitive="false"/> + </condition> + + <import file="teamcity-common.xml"/> + + <target name="package" depends="define.version"> + <package.teamcity.plugin name="${plugin.name}" + server.output="${mercurial-server.output.dir}" + agent.output="${mercurial-agent.output.dir}" + common.output="${mercurial-common.output.dir}" + plugin.descriptor.file="${basedir}/teamcity-plugin.xml" + plugin.version="${plugin.version}"/> + </target> + + <target name="define.version" depends="define.version.if.under.teamcity"> + <tstamp> + <format property="current.time" pattern="yyyyMMddHHmm"/> + </tstamp> + <property name="plugin.version" value="SNAPSHOT-${current.time}"/> + </target> + + <target name="define.version.if.under.teamcity" if="build.number"> + <property name="plugin.version" value="${build.number}"/> + </target> + + <target name="dist" depends="check.teamcitydistribution,all,package"/> + + <target name="deploy" depends="dist"> + <deploy.teamcity.plugin name="${plugin.name}"/> + </target> + + <taskdef name="testng" classname="org.testng.TestNGAntTask" classpath="${basedir}/mercurial-tests/lib/testng-5.7-jdk15.jar"/> + + <path id="tests_classpath"> + <pathelement location="${agent.home.dir}/lib/runtime-util.jar"/> + <pathelement location="${agent.home.dir}/lib/buildServerRuntimeUtil.jar"/> + <path refid="mercurial-tests.runtime.module.classpath"/> + </path> + + <target name="run-tests" depends="clean, init, compile.module.mercurial-tests"> + <property name="suspend" value="n"/> + + <testng haltonfailure="no" failureProperty="failure_found" listener="org.testng.reporters.TestHTMLReporter" + outputdir="${basedir}/test-output" classpathref="tests_classpath" dumpcommand="true" workingDir="${basedir}"> + + <jvmarg value="-ea"/> + <!--<jvmarg value="-Xrunjdwp:transport=dt_socket,server=y,suspend=${suspend},address=5555"/>--> + + <sysproperty key="java.awt.headless" value="true"/> + + <xmlfileset dir="${basedir}/mercurial-tests/src"> + <include name="testng.xml"/> + </xmlfileset> + </testng> + </target> +</project>
--- a/mercurial-agent/src/META-INF/build-agent-plugin-mercurial.xml Wed Feb 15 12:07:12 2012 +0400 +++ b/mercurial-agent/src/META-INF/build-agent-plugin-mercurial.xml Wed Feb 15 13:23:53 2012 +0400 @@ -1,6 +1,10 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd"> - -<beans default-autowire="constructor"> - <bean id="mercurialAgent" class="jetbrains.buildServer.buildTriggers.vcs.mercurial.MercurialAgentSideVcsSupport" /> -</beans> +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd"> + +<beans default-autowire="constructor"> + <bean id="mercurialAgent" class="jetbrains.buildServer.buildTriggers.vcs.mercurial.MercurialAgentSideVcsSupport" /> + <bean id="hgPathProvider" class="jetbrains.buildServer.buildTriggers.vcs.mercurial.AgentHgPathProvider" /> + <bean id="hgDetector" class="jetbrains.buildServer.buildTriggers.vcs.mercurial.HgDetector" /> + <bean id="pluginConfig" class="jetbrains.buildServer.buildTriggers.vcs.mercurial.AgentPluginConfigImpl"/> + <bean id="mirrorManager" class="jetbrains.buildServer.buildTriggers.vcs.mercurial.MirrorManagerImpl" /> +</beans>
--- a/mercurial-agent/src/build-agent-plugin.xml Wed Feb 15 12:07:12 2012 +0400 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,7 +0,0 @@ -<!-- -PicoContainer configuration for old fashioned TeamCity agent plugins. -In TeamCity 4.0 Spring configuration must be created instead. ---> -<container> - <component class="jetbrains.buildServer.buildTriggers.vcs.mercurial.MercurialAgentSideVcsSupport" /> -</container> \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mercurial-agent/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/AgentHgPathProvider.java Wed Feb 15 13:23:53 2012 +0400 @@ -0,0 +1,32 @@ +package jetbrains.buildServer.buildTriggers.vcs.mercurial; + +import jetbrains.buildServer.agent.BuildAgentConfiguration; +import jetbrains.buildServer.buildTriggers.vcs.mercurial.command.Settings; +import jetbrains.buildServer.parameters.ProcessingResult; +import jetbrains.buildServer.parameters.ValueResolver; +import org.jetbrains.annotations.NotNull; + +/** + * @author dmitry.neverov + */ +public class AgentHgPathProvider implements HgPathProvider { + + private final ValueResolver myResolver; + + + public AgentHgPathProvider(@NotNull final BuildAgentConfiguration agentConfig) { + myResolver = agentConfig.getParametersResolver(); + } + + + public String getHgPath(@NotNull final Settings settings) { + String pathFromRoot = settings.getHgPath(); + return resolve(pathFromRoot); + } + + + private String resolve(@NotNull final String value) { + ProcessingResult result = myResolver.resolve(value); + return result.getResult(); + } +}
--- a/mercurial-agent/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/AgentPluginConfig.java Wed Feb 15 12:07:12 2012 +0400 +++ b/mercurial-agent/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/AgentPluginConfig.java Wed Feb 15 13:23:53 2012 +0400 @@ -1,10 +1,14 @@ package jetbrains.buildServer.buildTriggers.vcs.mercurial; +import jetbrains.buildServer.agent.AgentRunningBuild; +import org.jetbrains.annotations.NotNull; + /** * @author dmitry.neverov */ public interface AgentPluginConfig extends PluginConfig { - boolean isUseLocalMirrors(); + boolean isUseLocalMirrors(@NotNull AgentRunningBuild build); + int getPullTimeout(@NotNull AgentRunningBuild build); }
--- a/mercurial-agent/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/AgentPluginConfigImpl.java Wed Feb 15 12:07:12 2012 +0400 +++ b/mercurial-agent/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/AgentPluginConfigImpl.java Wed Feb 15 13:23:53 2012 +0400 @@ -1,9 +1,12 @@ package jetbrains.buildServer.buildTriggers.vcs.mercurial; import jetbrains.buildServer.agent.AgentRunningBuild; +import jetbrains.buildServer.agent.BuildAgentConfiguration; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import java.io.File; + /** * @author dmitry.neverov */ @@ -12,18 +15,23 @@ private static final String PULL_TIMEOUT_SECONDS = "teamcity.hg.pull.timeout.seconds"; private final int DEFAULT_PULL_TIMEOUT_SECONDS = 3600; - private final AgentRunningBuild myBuild; + private final BuildAgentConfiguration myAgentConfig; - public AgentPluginConfigImpl(@NotNull AgentRunningBuild build) { - myBuild = build; + public AgentPluginConfigImpl(@NotNull BuildAgentConfiguration agentConfig) { + myAgentConfig = agentConfig; } - public boolean isUseLocalMirrors() { - return "true".equals(myBuild.getSharedConfigParameters().get("teamcity.hg.use.local.mirrors")); + public boolean isUseLocalMirrors(@NotNull AgentRunningBuild build) { + return "true".equals(build.getSharedConfigParameters().get("teamcity.hg.use.local.mirrors")); } - public int getPullTimeout() { - Integer timeout = parseTimeout(myBuild.getSharedConfigParameters().get(PULL_TIMEOUT_SECONDS)); + @NotNull + public File getCachesDir() { + return myAgentConfig.getCacheDirectory("mercurial"); + } + + public int getPullTimeout(@NotNull AgentRunningBuild build) { + Integer timeout = parseTimeout(build.getSharedConfigParameters().get(PULL_TIMEOUT_SECONDS)); if (timeout != null) return timeout; return DEFAULT_PULL_TIMEOUT_SECONDS; @@ -38,7 +46,7 @@ if (timeout > 0) return timeout; else - return null; + return null; } catch (NumberFormatException e) { return null; }
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mercurial-agent/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/HgDetector.java Wed Feb 15 13:23:53 2012 +0400 @@ -0,0 +1,90 @@ +package jetbrains.buildServer.buildTriggers.vcs.mercurial; + +import com.intellij.openapi.diagnostic.Logger; +import jetbrains.buildServer.agent.AgentLifeCycleAdapter; +import jetbrains.buildServer.agent.AgentLifeCycleListener; +import jetbrains.buildServer.agent.BuildAgent; +import jetbrains.buildServer.agent.BuildAgentConfiguration; +import jetbrains.buildServer.buildTriggers.vcs.mercurial.command.VersionCommand; +import jetbrains.buildServer.util.EventDispatcher; +import jetbrains.buildServer.vcs.VcsException; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.io.File; +import java.util.Arrays; +import java.util.List; + +/** + * @author dmitry.neverov + */ +public class HgDetector extends AgentLifeCycleAdapter { + + final static String AGENT_HG_PATH_PROPERTY = "teamcity.hg.agent.path"; + private final static Logger LOG = Logger.getInstance(HgDetector.class.getName()); + private final static HgVersion LEAST_SUPPORTED_VERSION = new HgVersion(1, 5, 2); + private final List<String> myHgPaths = Arrays.asList("hg"); + + + public HgDetector(@NotNull final EventDispatcher<AgentLifeCycleListener> dispatcher) { + dispatcher.addListener(this); + } + + + @Override + public void beforeAgentConfigurationLoaded(@NotNull final BuildAgent agent) { + BuildAgentConfiguration config = agent.getConfiguration(); + String agentHgPath = config.getConfigurationParameters().get(AGENT_HG_PATH_PROPERTY); + File workDir = config.getTempDirectory(); + if (agentHgPath == null) { + String detectedHg = detectHg(workDir); + if (detectedHg != null) { + LOG.info("Detect installed mercurial at path " + detectedHg + ", provide it as a property " + AGENT_HG_PATH_PROPERTY); + config.addConfigurationParameter(AGENT_HG_PATH_PROPERTY, "hg"); + } else { + LOG.info("Cannot detect installed mercurial"); + } + } else { + if (!canRunHg(agentHgPath, workDir, true)) + LOG.warn("Mercurial executable at path " + agentHgPath + " cannot be run or not compatible with TeamCity"); + } + } + + + @Nullable + private String detectHg(@NotNull final File workDir) { + for (String path : myHgPaths) { + if (canRunHg(path, workDir)) + return path; + } + return null; + } + + + private boolean canRunHg(@NotNull final String hgPath, @NotNull final File workDir) { + return canRunHg(hgPath, workDir, false); + } + + private boolean canRunHg(@NotNull final String hgPath, @NotNull final File workDir, boolean logWarnings) { + VersionCommand versionCommand = new VersionCommand(hgPath, workDir); + try { + HgVersion version = versionCommand.execute(); + if (isCompatible(version)) { + return true; + } else { + if (logWarnings) + LOG.warn("Mercurial version at path " + hgPath + " is " + version + ", required version is " + LEAST_SUPPORTED_VERSION + "+"); + return false; + } + } catch (VcsException e) { + if (logWarnings) + LOG.warn("Error while trying to get hg version, hg path " + hgPath + ", error: " + e.getMessage()); + return false; + } + } + + + private boolean isCompatible(@NotNull final HgVersion version) { + return version.isEqualsOrGreaterThan(LEAST_SUPPORTED_VERSION); + } +}
--- a/mercurial-agent/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/MercurialAgentSideVcsSupport.java Wed Feb 15 12:07:12 2012 +0400 +++ b/mercurial-agent/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/MercurialAgentSideVcsSupport.java Wed Feb 15 13:23:53 2012 +0400 @@ -16,58 +16,33 @@ package jetbrains.buildServer.buildTriggers.vcs.mercurial; import jetbrains.buildServer.agent.AgentRunningBuild; -import jetbrains.buildServer.agent.BuildAgentConfiguration; -import jetbrains.buildServer.agent.BuildProgressLogger; import jetbrains.buildServer.agent.vcs.AgentVcsSupport; import jetbrains.buildServer.agent.vcs.IncludeRuleUpdater; import jetbrains.buildServer.agent.vcs.UpdateByIncludeRules2; import jetbrains.buildServer.agent.vcs.UpdatePolicy; -import jetbrains.buildServer.buildTriggers.vcs.mercurial.command.*; -import jetbrains.buildServer.util.FileUtil; import jetbrains.buildServer.vcs.CheckoutRules; -import jetbrains.buildServer.vcs.IncludeRule; import jetbrains.buildServer.vcs.VcsException; import jetbrains.buildServer.vcs.VcsRoot; import org.jetbrains.annotations.NotNull; import java.io.File; -import java.io.IOException; -import java.util.Collections; - -import static jetbrains.buildServer.buildTriggers.vcs.mercurial.command.CommandUtil.removePrivateData; public class MercurialAgentSideVcsSupport extends AgentVcsSupport implements UpdateByIncludeRules2 { + private final AgentPluginConfig myConfig; + private final HgPathProvider myHgPathProvider; private final MirrorManager myMirrorManager; - public MercurialAgentSideVcsSupport(BuildAgentConfiguration agentConfiguration) { - myMirrorManager = new MirrorManager(agentConfiguration.getCacheDirectory("mercurial")); + public MercurialAgentSideVcsSupport(@NotNull final AgentPluginConfig pluginConfig, + @NotNull final HgPathProvider hgPathProvider, + @NotNull final MirrorManager mirrorManager) { + myConfig = pluginConfig; + myHgPathProvider = hgPathProvider; + myMirrorManager = mirrorManager; } public IncludeRuleUpdater getUpdater(@NotNull final VcsRoot vcsRoot, @NotNull final CheckoutRules checkoutRules, @NotNull final String toVersion, @NotNull final File checkoutDirectory, @NotNull final AgentRunningBuild build, boolean cleanCheckoutRequested) throws VcsException { - final AgentPluginConfig config = new AgentPluginConfigImpl(build); - final BuildProgressLogger logger = build.getBuildLogger(); - return new IncludeRuleUpdater() { - public void process(@NotNull final IncludeRule includeRule, @NotNull final File workingDir) throws VcsException { - try { - checkRuleIsValid(includeRule); - Settings settings = new Settings(vcsRoot); - if (config.isUseLocalMirrors()) { - updateLocalMirror(vcsRoot, logger, config); - } - updateRepository(workingDir, settings, logger, config); - updateWorkingDir(settings, workingDir, toVersion, logger); - } catch (Exception e) { - if (e instanceof VcsException) - throw (VcsException) e; - else - throw new VcsException(e); - } - } - - public void dispose() throws VcsException { - } - }; + return new MercurialIncludeRuleUpdater(myConfig, myMirrorManager, myHgPathProvider, vcsRoot, toVersion, build); } @NotNull @@ -81,90 +56,4 @@ public UpdatePolicy getUpdatePolicy() { return this; } - - private void initRepository(File workingDir, Settings settings, BuildProgressLogger logger, boolean useLocalMirrors) throws VcsException { - try { - String defaultPullUrl = getDefaultPullUrl(settings, useLocalMirrors); - logger.message("Init repository at " + workingDir.getAbsolutePath() + ", remote repository is " + - removePrivateData(defaultPullUrl, Collections.singleton(settings.getPassword()))); - new Init(settings, workingDir, defaultPullUrl).execute(); - } catch (IOException e) { - throw new VcsException("Error while initializing repository at " + workingDir.getAbsolutePath(), e); - } - } - - private void updateRepository(File workingDir, Settings settings, BuildProgressLogger logger, AgentPluginConfig config) throws VcsException, IOException { - if (!Settings.isValidRepository(workingDir)) { - initRepository(workingDir, settings, logger, config.isUseLocalMirrors()); - } else { - ensureUseRightRepository(workingDir, settings, logger, config.isUseLocalMirrors()); - } - String defaultPullUrl = getDefaultPullUrl(settings, config.isUseLocalMirrors()); - logger.message("Start pulling changes from " + removePrivateData(defaultPullUrl, Collections.singleton(settings.getPassword()))); - new PullCommand(settings, workingDir, defaultPullUrl).execute(config.getPullTimeout()); - logger.message("Changes successfully pulled"); - } - - private void ensureUseRightRepository(File workingDir, Settings settings, BuildProgressLogger logger, boolean useLocalMirrors) throws VcsException { - boolean clonedFromWrongRepository = useLocalMirrors && !isClonedFromLocalMirror(settings, workingDir) - || !useLocalMirrors && isClonedFromLocalMirror(settings, workingDir); - - if (clonedFromWrongRepository) { - String rightRepository = useLocalMirrors ? "local mirror" : "remote repository"; - String wrongRepository = useLocalMirrors ? "remote repository" : "local mirror"; - logger.message("Repository in working directory is cloned from " + wrongRepository + ", clone it from " + rightRepository); - FileUtil.delete(workingDir); - initRepository(workingDir, settings, logger, useLocalMirrors); - } - } - - private void updateLocalMirror(VcsRoot root, BuildProgressLogger logger, AgentPluginConfig config) throws VcsException, IOException { - Settings settings = new Settings(root); - File mirrorDir = myMirrorManager.getMirrorDir(settings.getRepositoryUrl()); - logger.message("Update local mirror at " + mirrorDir); - if (!Settings.isValidRepository(mirrorDir)) { - initRepository(mirrorDir, settings, logger, false); - } - final String defaultPullUrl = getDefaultPullUrl(settings, true); - logger.message("Start pulling changes from " + removePrivateData(defaultPullUrl, Collections.singleton(settings.getPassword()))); - new PullCommand(settings, mirrorDir).execute(config.getPullTimeout()); - logger.message("Local mirror changes successfully pulled"); - } - - private void updateWorkingDir(final Settings settings, File workingDir, @NotNull final String version, final BuildProgressLogger logger) throws VcsException { - logger.message("Updating folder " + workingDir.getAbsolutePath() + " to revision " + version); - UpdateCommand uc = new UpdateCommand(settings, workingDir); - ChangeSet cs = new ChangeSet(version); - uc.setToId(cs.getId()); - uc.execute(); - logger.message("Folder successfully updated"); - } - - private String getDefaultPullUrl(Settings settings, boolean useLocalMirror) throws IOException { - if (useLocalMirror) { - File mirrorDir = myMirrorManager.getMirrorDir(settings.getRepositoryUrl()); - return mirrorDir.getCanonicalPath(); - } else { - return settings.getRepositoryUrl(); - } - } - - private void checkRuleIsValid(IncludeRule includeRule) throws VcsException { - if (includeRule.getTo() != null && includeRule.getTo().length() > 0) { - if (!".".equals(includeRule.getFrom()) && includeRule.getFrom().length() != 0) { - throw new VcsException("Invalid include rule: " + includeRule.toString() + ", Mercurial plugin supports mapping of the form: +:.=>dir only."); - } - } - } - - public boolean isClonedFromLocalMirror(Settings settings, File workingDir) { - try { - File mirrorDir = myMirrorManager.getMirrorDir(settings.getRepositoryUrl()); - File hgrc = new File(workingDir, ".hg" + File.separator + "hgrc"); - String config = FileUtil.readText(hgrc); - return config.contains("default = " + mirrorDir.getCanonicalPath()); - } catch (Exception e) { - return false; - } - } }
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mercurial-agent/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/MercurialIncludeRuleUpdater.java Wed Feb 15 13:23:53 2012 +0400 @@ -0,0 +1,335 @@ +package jetbrains.buildServer.buildTriggers.vcs.mercurial; + +import com.intellij.openapi.util.Pair; +import jetbrains.buildServer.agent.AgentRunningBuild; +import jetbrains.buildServer.agent.BuildProgressLogger; +import jetbrains.buildServer.agent.vcs.IncludeRuleUpdater; +import jetbrains.buildServer.buildTriggers.vcs.mercurial.command.*; +import jetbrains.buildServer.util.FileUtil; +import jetbrains.buildServer.vcs.IncludeRule; +import jetbrains.buildServer.vcs.VcsException; +import jetbrains.buildServer.vcs.VcsRoot; +import org.jetbrains.annotations.NotNull; + +import java.io.File; +import java.io.IOException; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +import static com.intellij.openapi.util.io.FileUtil.delete; +import static jetbrains.buildServer.buildTriggers.vcs.mercurial.command.CommandUtil.removePrivateData; +import static jetbrains.buildServer.util.FileUtil.isEmptyDir; + +/** + * @author dmitry.neverov + */ +public class MercurialIncludeRuleUpdater implements IncludeRuleUpdater { + + private final AgentPluginConfig myConfig; + private final MirrorManager myMirrorManager; + private final HgPathProvider myHgPathProvider; + private final VcsRoot myRoot; + private final Settings mySettings; + private final String myToVersion; + private final BuildProgressLogger myLogger; + private final boolean myUseLocalMirrors; + private int myPullTimeout; + + public MercurialIncludeRuleUpdater(@NotNull final AgentPluginConfig pluginConfig, + @NotNull final MirrorManager mirrorManager, + @NotNull final HgPathProvider hgPathProvider, + @NotNull final VcsRoot root, + @NotNull final String toVersion, + @NotNull final AgentRunningBuild build) { + myConfig = pluginConfig; + myMirrorManager = mirrorManager; + myHgPathProvider = hgPathProvider; + myRoot = root; + mySettings = new Settings(myHgPathProvider, myRoot); + myToVersion = toVersion; + myLogger = build.getBuildLogger(); + myUseLocalMirrors = myConfig.isUseLocalMirrors(build); + myPullTimeout = myConfig.getPullTimeout(build); + } + + + public void process(@NotNull IncludeRule rule, @NotNull File workingDir) throws VcsException { + try { + checkRuleIsValid(rule); + if (myUseLocalMirrors) + updateLocalMirror(); + updateRepository(workingDir); + updateWorkingDir(workingDir); + } catch (Exception e) { + throwVcsException(e); + } + } + + + public void dispose() throws VcsException { + } + + + private void throwVcsException(Exception e) throws VcsException { + if (e instanceof VcsException) + throw (VcsException) e; + else + throw new VcsException(e); + } + + + private void initRepository(Settings settings, File workingDir, boolean useLocalMirrors) throws VcsException { + try { + String defaultPullUrl = getDefaultPullUrl(settings, useLocalMirrors); + myLogger.message("Init repository at " + workingDir.getAbsolutePath() + ", remote repository is " + + removePrivateData(defaultPullUrl, Collections.singleton(settings.getPassword()))); + new Init(settings, workingDir, defaultPullUrl).execute(); + } catch (IOException e) { + throw new VcsException("Error while initializing repository at " + workingDir.getAbsolutePath(), e); + } + } + + + private void updateRepository(File workingDir) throws VcsException, IOException { + String defaultPullUrl = getDefaultPullUrl(mySettings, myUseLocalMirrors); + if (isEmptyDir(workingDir)) { + workingDir.mkdirs(); + myLogger.message("Start cloning from " + removePrivateData(defaultPullUrl, Collections.singleton(mySettings.getPassword()))); + CloneCommand clone = new CloneCommand(mySettings, workingDir); + clone.setRepository(defaultPullUrl); + clone.setUsePullProtocol(false); + clone.setUpdateWorkingDir(false); + clone.execute(); + myLogger.message("Repository successfully cloned"); + } else { + if (!Settings.isValidRepository(workingDir)) { + initRepository(mySettings, workingDir, myUseLocalMirrors); + } else { + ensureUseRightRepository(workingDir); + } + myLogger.message("Start pulling changes from " + removePrivateData(defaultPullUrl, Collections.singleton(mySettings.getPassword()))); + new PullCommand(mySettings, workingDir, defaultPullUrl).execute(myPullTimeout); + myLogger.message("Changes successfully pulled"); + } + } + + + private void ensureUseRightRepository(File workingDir) throws VcsException { + boolean clonedFromWrongRepository = myUseLocalMirrors && !isClonedFromLocalMirror(workingDir) + || !myUseLocalMirrors && isClonedFromLocalMirror(workingDir); + + if (clonedFromWrongRepository) { + String rightRepository = myUseLocalMirrors ? "local mirror" : "remote repository"; + String wrongRepository = myUseLocalMirrors ? "remote repository" : "local mirror"; + myLogger.message("Repository in working directory is cloned from " + wrongRepository + ", clone it from " + rightRepository); + FileUtil.delete(workingDir); + initRepository(mySettings, workingDir, myUseLocalMirrors); + } + } + + + private void updateLocalMirror() throws VcsException, IOException { + Settings settings = new Settings(myHgPathProvider, myRoot); + File mirrorDir = myMirrorManager.getMirrorDir(settings.getRepository()); + myLogger.message("Update local mirror at " + mirrorDir); + if (!Settings.isValidRepository(mirrorDir)) { + initRepository(settings, mirrorDir, false); + } + final String defaultPullUrl = getDefaultPullUrl(settings, true); + myLogger.message("Start pulling changes from " + removePrivateData(defaultPullUrl, Collections.singleton(settings.getPassword()))); + new PullCommand(settings, mirrorDir).execute(myPullTimeout); + myLogger.message("Local mirror changes successfully pulled"); + } + + + private void updateWorkingDir(@NotNull final File workingDir) throws VcsException, IOException { + String workingDirRevision = getWorkingDirRevision(mySettings, workingDir); + if (isInitialClone(workingDirRevision)) { + doUpdateWorkingDir(workingDir); + } else { + Map<String, Pair<String, String>> currentSubrepos = getSubrepositories(workingDir, workingDirRevision); + if (currentSubrepos.isEmpty()) { + doUpdateWorkingDir(workingDir); + } else { + Map<String, Pair<String, String>> toVersionSubrepos = getSubrepositories(workingDir, myToVersion); + Map<String, Pair<String, String>> subrepositoriesWithChangedUrls = getSubrepositoriesWithChangedUrls(currentSubrepos, toVersionSubrepos); + if (subrepositoriesWithChangedUrls.isEmpty()) { + doUpdateWorkingDir(workingDir); + } else { + logSubrepositoriesUrlsChanged(workingDirRevision, myToVersion, subrepositoriesWithChangedUrls); + FileUtil.delete(workingDir); + updateRepository(workingDir); + doUpdateWorkingDir(workingDir); + } + } + } + } + + + private void logSubrepositoriesUrlsChanged(@NotNull final String fromVersion, + @NotNull final String toVersion, + @NotNull final Map<String, Pair<String, String>> subrepositoriesWithChangedUrls) { + StringBuilder sb = new StringBuilder(); + sb.append("Subrepositories' URLs were changed between revisions ").append(fromVersion).append("..").append(toVersion).append(":\n"); + for (Map.Entry<String, Pair<String, String>> entry : subrepositoriesWithChangedUrls.entrySet()) { + String path = entry.getKey(); + String oldUrl = entry.getValue().first; + String newUrl = entry.getValue().second; + sb.append("path: \"").append(path).append("\", old URL: \"").append(oldUrl).append("\", new URL: \"").append(newUrl).append("\";\n"); + } + sb.append("do clean checkout."); + myLogger.warning(sb.toString()); + } + + + /*returns map: relative path -> (repository url, repository revision)*/ + private Map<String, Pair<String, String>> getSubrepositories(@NotNull final File workingDir, @NotNull final String revision) throws VcsException, IOException { + CatCommand cc = new CatCommand(mySettings, workingDir); + cc.setRevId(revision); + File catDir = null; + try { + catDir = cc.execute(Arrays.asList(".hgsub", ".hgsubstate"), false); + File hgsub = new File(catDir, ".hgsub"); + File hgsubstate = new File(catDir, ".hgsubstate"); + return readSubrepositories(hgsub, hgsubstate); + } catch (VcsException e) { + return Collections.emptyMap(); + } finally { + if (catDir != null) + delete(catDir); + } + } + + + private Map<String, Pair<String, String>> readSubrepositories(@NotNull final File hgsub, @NotNull final File hgsubstate) throws IOException { + if (hgsub.exists() && hgsubstate.exists()) { + Map<String, String> path2repo = readHgsub(hgsub); + Map<String, String> path2revision = readHgsubstate(hgsubstate); + Map<String, Pair<String, String>> result = new HashMap<String, Pair<String, String>>(); + for (Map.Entry<String, String> entry : path2repo.entrySet()) { + String path = entry.getKey(); + String repo = entry.getValue(); + String revision = path2revision.get(path); + if (revision != null) { + result.put(path, Pair.create(repo, revision)); + } else { + myLogger.warning("Cannot find revision for subrepository at path " + path + " skip it"); + } + } + return result; + } else { + return Collections.emptyMap(); + } + } + + + /*returns map: relative path -> repository url */ + private Map<String, String> readHgsub(@NotNull final File hgsub) throws IOException { + Map<String, String> result = new HashMap<String, String>(); + for (String line : FileUtil.readFile(hgsub)) { + String[] parts = line.split(" = "); + if (parts.length == 2) { + result.put(parts[0], parts[1]); + } else { + myLogger.warning("Cannot parse the line '" + line + "' from .hgsub, skip it"); + } + } + return result; + } + + + /*returns map: relative path -> revision */ + private Map<String, String> readHgsubstate(@NotNull final File hgsubstate) throws IOException { + Map<String, String> result = new HashMap<String, String>(); + for (String line : FileUtil.readFile(hgsubstate)) { + String[] parts = line.split(" "); + if (parts.length == 2) { + result.put(parts[1], parts[0]); + } else { + myLogger.warning("Cannot parse the line '" + line + "' from .hgsubstate, skip it"); + } + } + return result; + } + + + /*returns map repository path -> (old url, new url)*/ + private Map<String, Pair<String, String>> getSubrepositoriesWithChangedUrls(@NotNull final Map<String, Pair<String, String>> subrepos1, @NotNull final Map<String, Pair<String, String>> subrepos2) { + Map<String, Pair<String, String>> result = new HashMap<String, Pair<String, String>>(); + for (Map.Entry<String, Pair<String, String>> entry : subrepos1.entrySet()) { + String path = entry.getKey(); + String url1 = entry.getValue().first; + Pair<String, String> urlRevision = subrepos2.get(path); + if (urlRevision != null && !url1.equals(urlRevision.first)) + result.put(path, Pair.create(url1, urlRevision.first)); + } + return result; + } + + + private boolean isInitialClone(@NotNull final String workingDirRevision) { + return "000000000000".equals(workingDirRevision); + } + + + private String getWorkingDirRevision(@NotNull final Settings settings, @NotNull final File workingDir) throws VcsException { + IdentifyCommand id = new IdentifyCommand(settings, workingDir); + id.setInLocalRepository(true); + return id.execute(); + } + + + private void doUpdateWorkingDir(@NotNull final File workingDir) throws VcsException { + myLogger.message("Updating folder " + workingDir.getAbsolutePath() + " to revision " + myToVersion); + UpdateCommand update = new UpdateCommand(mySettings, workingDir); + addUpdateAuthConfigParams(update); + update.setToId(new ChangeSet(myToVersion).getId()); + update.execute(); + myLogger.message("Folder successfully updated"); + } + + + private void addUpdateAuthConfigParams(@NotNull UpdateCommand update) { + String username = mySettings.getUsername(); + String password = mySettings.getPassword(); + if (username == null || password == null) + return; + update.withConfig("auth.tc.prefix", "*") + .withConfig("auth.tc.username", username) + .withConfig("auth.tc.password", password) + .withConfig("auth.tc.schemes", "http https"); + } + + + private String getDefaultPullUrl(Settings settings, boolean useLocalMirror) throws IOException { + if (useLocalMirror) { + File mirrorDir = myMirrorManager.getMirrorDir(settings.getRepository()); + return mirrorDir.getCanonicalPath(); + } else { + return settings.getRepositoryUrlWithCredentials(); + } + } + + + private void checkRuleIsValid(IncludeRule includeRule) throws VcsException { + if (includeRule.getTo() != null && includeRule.getTo().length() > 0) { + if (!".".equals(includeRule.getFrom()) && includeRule.getFrom().length() != 0) { + throw new VcsException("Invalid include rule: " + includeRule.toString() + ", Mercurial plugin supports mapping of the form: +:.=>dir only."); + } + } + } + + + public boolean isClonedFromLocalMirror(@NotNull final File workingDir) { + try { + File mirrorDir = myMirrorManager.getMirrorDir(mySettings.getRepository()); + File hgrc = new File(workingDir, ".hg" + File.separator + "hgrc"); + String config = FileUtil.readText(hgrc); + return config.contains("default = " + mirrorDir.getCanonicalPath()); + } catch (Exception e) { + return false; + } + } +}
--- a/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/Constants.java Wed Feb 15 12:07:12 2012 +0400 +++ b/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/Constants.java Wed Feb 15 13:23:53 2012 +0400 @@ -1,30 +1,31 @@ -/* - * 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.vcs.VcsRoot; - -public interface Constants { - String VCS_NAME = "mercurial"; - String REPOSITORY_PROP = "repositoryPath"; - String BRANCH_NAME_PROP = "branchName"; - String HG_COMMAND_PATH_PROP = "hgCommandPath"; - String HG_PATH_ENV = "TEAMCITY_HG_PATH"; - String SERVER_CLONE_PATH_PROP = "serverClonePath"; - String USERNAME = "username"; - String PASSWORD = VcsRoot.SECURE_PROPERTY_PREFIX + "password"; - String UNCOMPRESSED_TRANSFER = "uncompressedTransfer"; -} +/* + * 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.vcs.VcsRoot; + +public interface Constants { + String VCS_NAME = "mercurial"; + String REPOSITORY_PROP = "repositoryPath"; + String BRANCH_NAME_PROP = "branchName"; + String HG_COMMAND_PATH_PROP = "hgCommandPath"; + String HG_PATH_ENV = "TEAMCITY_HG_PATH"; + String SERVER_CLONE_PATH_PROP = "serverClonePath"; + String USERNAME = "username"; + String PASSWORD = VcsRoot.SECURE_PROPERTY_PREFIX + "password"; + String UNCOMPRESSED_TRANSFER = "uncompressedTransfer"; + String USER_FOR_TAG = "tagUsername"; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/HgPathProvider.java Wed Feb 15 13:23:53 2012 +0400 @@ -0,0 +1,13 @@ +package jetbrains.buildServer.buildTriggers.vcs.mercurial; + +import jetbrains.buildServer.buildTriggers.vcs.mercurial.command.Settings; +import org.jetbrains.annotations.NotNull; + +/** + * @author dmitry.neverov + */ +public interface HgPathProvider { + + String getHgPath(@NotNull Settings settings); + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/HgVersion.java Wed Feb 15 13:23:53 2012 +0400 @@ -0,0 +1,104 @@ +package jetbrains.buildServer.buildTriggers.vcs.mercurial; + +import org.jetbrains.annotations.NotNull; + +/** + * @author dmitry.neverov + */ +public class HgVersion implements Comparable<HgVersion> { + + private static final String PREFIX = "Mercurial Distributed SCM (version "; + + private final int myMajor; + private final int myMinor; + private final int myThird; + + + public HgVersion(int major, int minor, int third) { + myMajor = major; + myMinor = minor; + myThird = third; + } + + + public static HgVersion parse(@NotNull final String version) { + Parser p = new Parser(version); + if (!p.skipString(PREFIX)) + throw new IllegalArgumentException("Incorrect version format: " + version); + int major = p.readInt(); + p.skipString("."); + int minor = p.readInt(); + int third = p.skipString(".") ? p.readInt() : 0; + return new HgVersion(major, minor, third); + } + + + public boolean isEqualsOrGreaterThan(HgVersion other) { + return compareTo(other) >= 0; + } + + + @Override + public String toString() { + return myMajor + "." + myMinor + "." + myThird; + } + + @Override + public boolean equals(Object o) { + return (o instanceof HgVersion) && this.compareTo((HgVersion) o) == 0; + } + + @Override + public int hashCode() { + int result = myMajor; + result = 31 * result + myMinor; + result = 31 * result + myThird; + return result; + } + + public int compareTo(HgVersion other) { + int d = myMajor - other.myMajor; + if (d != 0) + return d; + + d = myMinor - other.myMinor; + if (d != 0) + return d; + + return myThird - other.myThird; + } + + + private static final class Parser { + + private final String myString; + private int myIndex = 0; + + Parser(@NotNull String string) { + myString = string; + } + + + boolean skipString(String str) { + if (myIndex == myString.length() || myIndex + str.length() > myString.length()) + return false; + String substr = myString.substring(myIndex, myIndex + str.length()); + if (substr.equals(str)) { + myIndex = myIndex + str.length(); + return true; + } else { + return false; + } + } + + + int readInt() { + int result = 0; + while (myIndex < myString.length() && Character.isDigit(myString.codePointAt(myIndex))) { + result = result * 10 + Character.digit(myString.codePointAt(myIndex), 10); + myIndex++; + } + return result; + } + } +}
--- a/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/MirrorManager.java Wed Feb 15 12:07:12 2012 +0400 +++ b/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/MirrorManager.java Wed Feb 15 13:23:53 2012 +0400 @@ -1,47 +1,15 @@ package jetbrains.buildServer.buildTriggers.vcs.mercurial; -import com.intellij.openapi.diagnostic.Logger; -import jetbrains.buildServer.util.FileUtil; -import jetbrains.buildServer.util.Hash; import org.jetbrains.annotations.NotNull; import java.io.File; -import java.io.IOException; -import java.util.ArrayList; -import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.concurrent.locks.ReadWriteLock; -import java.util.concurrent.locks.ReentrantReadWriteLock; /** - * Manages local mirrors of remote repositories. - * Each unique url get unique local mirror. Each mirror is used for one url only. * @author dmitry.neverov */ -public final class MirrorManager { - - private static Logger LOG = Logger.getInstance(MirrorManager.class.getName()); - private static final String MIRROR_DIR_PREFIX = "hg_"; - private static final String MAPPING_FILE_NAME = "map"; - - private final ReadWriteLock myLock = new ReentrantReadWriteLock(); - private final File myRootDir; - /*Only one thread read or write to this file, it is protected by myLock.writeLock()*/ - private final File myMappingFile; - /*Protected by myLock*/ - private final Map<String, File> myMirrors = new HashMap<String, File>(); - private HashCalculator myHash = new StandartHash(); - - /** - * @param rootDir root directory where all mirrors are stored - */ - public MirrorManager(File rootDir) { - myRootDir = rootDir; - myMappingFile = new File(myRootDir, MAPPING_FILE_NAME); - readMappingFromFile(); - } - +public interface MirrorManager { /** * Get directory of local mirror repository for specified url, if directory is not exists it is created @@ -49,197 +17,29 @@ * @return see above */ @NotNull - public File getMirrorDir(@NotNull final String url) { - File result = getMirrorDirWithLock(url); - if (result == null) { - result = createDirFor(url); - } - return result; - } - + public File getMirrorDir(@NotNull final String url); /** * Get all local mirror repository dirs * @return see above */ @NotNull - public List<File> getMirrors() { - myLock.readLock().lock(); - try { - return new ArrayList<File>(myMirrors.values()); - } finally { - myLock.readLock().unlock(); - } - } - - - //for tests only - void setHashCalculator(HashCalculator hash) { - myHash = hash; - } - - - private File createDirFor(String url) { - File result; - myLock.writeLock().lock(); - try { - File mirrorDir = getUniqueDir(url); - result = saveMappingIfAbsent(url, mirrorDir); - } finally { - myLock.writeLock().unlock(); - } - if (!result.exists()) { - result.mkdirs(); - } - return result; - } - - - private File getMirrorDirWithLock(String url) { - myLock.readLock().lock(); - try { - return myMirrors.get(url); - } finally { - myLock.readLock().unlock(); - } - } - - - //should be called with myLock.writeLock() held - private File saveMappingIfAbsent(String url, File mirrorDir) { - File existing = myMirrors.get(url); - if (existing != null) { - return existing; - } else { - myMirrors.put(url, mirrorDir); - saveMappingToFile(); - return mirrorDir; - } - } - - - private File getUniqueDir(String url) { - myLock.readLock().lock(); - try { - String dirName = MIRROR_DIR_PREFIX + hash(normalize(url)); - File result = PathUtil.getCanonicalFile(new File(myRootDir, dirName)); - while (isUsedForOtherUrl(result, url)) { - dirName = MIRROR_DIR_PREFIX + hash(result.getName()); - result = PathUtil.getCanonicalFile(new File(myRootDir, dirName)); - } - return result; - } finally { - myLock.readLock().unlock(); - } - } - - - private boolean isUsedForOtherUrl(File repositoryDir, String url) { - myLock.readLock().lock(); - try { - for (Map.Entry<String, File> mirror : myMirrors.entrySet()) { - String mirrorUrl = mirror.getKey(); - File mirrorDir = mirror.getValue(); - if (mirrorDir.equals(repositoryDir) && !mirrorUrl.equals(url)) { - return true; - } - } - return false; - } finally { - myLock.readLock().unlock(); - } - } - + public List<File> getMirrors(); - private String hash(String value) { - return String.valueOf(myHash.calc(value)); - } - - - private static String normalize(final String path) { - String normalized = PathUtil.normalizeSeparator(path); - if (path.endsWith("/")) { - return normalized.substring(0, normalized.length()-1); - } - return normalized; - } - - - private void readMappingFromFile() { - myLock.writeLock().lock(); - try { - LOG.debug("Parse mapping file " + myMappingFile.getAbsolutePath()); - for (String line : readLines()) { - int separatorIndex = line.lastIndexOf(" = "); - if (separatorIndex == -1) { - if (!line.equals("")) - LOG.warn("Cannot parse mapping '" + line + "', skip it."); - } else { - String url = line.substring(0, separatorIndex); - String dirName = line.substring(separatorIndex + 3); - File repositoryDir = PathUtil.getCanonicalFile(new File(myRootDir, dirName)); - if (isUsedForOtherUrl(repositoryDir, url)) { - LOG.error("Skip mapping " + line + ": " + dirName + " is used for url other than " + url); - } else { - myMirrors.put(url, PathUtil.getCanonicalFile(new File(myRootDir, dirName))); - } - } - } - } finally { - myLock.writeLock().unlock(); - } - } + /** + * Forget specified dir. After call to this method with non-empty dir, + * all urls which were mapped to this dir will be mapped to another. + * If dir is empty, subsequent call getMirrorDir(dir) will return the + * same dir. + * + * @param dir dir of interest + */ + public void forgetDir(@NotNull final File dir); - /*Should be called with myLock.writeLock() held*/ - private List<String> readLines() { - if (myMappingFile.exists()) { - try { - return FileUtil.readFile(myMappingFile); - } catch (IOException e) { - LOG.error("Error while reading a mapping file at " + myMappingFile.getAbsolutePath() + " starting with empty mapping", e); - return new ArrayList<String>(); - } - } else { - LOG.debug("No mapping file found at " + myMappingFile.getAbsolutePath() + " starting with empty mapping"); - File parentDir = myMappingFile.getParentFile(); - if (!parentDir.exists() && !parentDir.mkdirs()) { - LOG.error("Cannot create local mirrors dir at " + parentDir.getAbsolutePath()); - } else { - try { - if (!myMappingFile.createNewFile()) - LOG.warn("Someone else creates a mapping file " + myMappingFile.getAbsolutePath() + ", will use it"); - } catch (IOException e) { - LOG.error("Cannot create a mapping file at " + myMappingFile.getAbsolutePath(), e); - } - } - return new ArrayList<String>(); - } - } + @NotNull + public Map<String, File> getMappings(); + public void lockDir(@NotNull File dir); - private void saveMappingToFile() { - myLock.writeLock().lock(); - try { - StringBuilder sb = new StringBuilder(); - for (Map.Entry<String, File> mirror : myMirrors.entrySet()) { - String url = mirror.getKey(); - String dir = mirror.getValue().getName(); - sb.append(url).append(" = ").append(dir).append("\n"); - } - FileUtil.writeFile(myMappingFile, sb.toString()); - } finally { - myLock.writeLock().unlock(); - } - } - - - final static class StandartHash implements HashCalculator { - public long calc(String value) { - return Hash.calc(value); - } - } - - public static interface HashCalculator { - long calc(String value); - } + public void unlockDir(@NotNull File dir); }
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/MirrorManagerImpl.java Wed Feb 15 13:23:53 2012 +0400 @@ -0,0 +1,331 @@ +package jetbrains.buildServer.buildTriggers.vcs.mercurial; + +import com.intellij.openapi.diagnostic.Logger; +import jetbrains.buildServer.util.FileUtil; +import jetbrains.buildServer.util.Hash; +import org.jetbrains.annotations.NotNull; + +import java.io.File; +import java.io.IOException; +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReadWriteLock; +import java.util.concurrent.locks.ReentrantLock; +import java.util.concurrent.locks.ReentrantReadWriteLock; + +import static jetbrains.buildServer.util.FileUtil.isEmptyDir; + +/** + * Manages local mirrors of remote repositories. + * Each unique url get unique local mirror. Each mirror is used for one url only. + * @author dmitry.neverov + */ +public final class MirrorManagerImpl implements MirrorManager { + + private static Logger LOG = Logger.getInstance(MirrorManagerImpl.class.getName()); + private static final String MIRROR_DIR_PREFIX = "hg_"; + private static final String MAPPING_FILE_NAME = "map"; + + private final ReadWriteLock myLock = new ReentrantReadWriteLock(); + private final File myRootDir; + /*Only one thread read or write to this file, it is protected by myLock.writeLock()*/ + private final File myMappingFile; + /*Protected by myLock*/ + private final Map<String, File> myMirrors = new HashMap<String, File>(); + private HashCalculator myHash = new StandartHash(); + + private final ConcurrentMap<String, Lock> myDirLocks = new ConcurrentHashMap<String, Lock>(); + + public MirrorManagerImpl(@NotNull PluginConfig config) { + myRootDir = config.getCachesDir(); + myMappingFile = new File(myRootDir, MAPPING_FILE_NAME); + readMappingFromFile(); + } + + + /** + * Get directory of local mirror repository for specified url, if directory is not exists it is created + * @param url url of interest + * @return see above + */ + @NotNull + public File getMirrorDir(@NotNull final String url) { + File result = getMirrorDirWithLock(url); + if (result == null) { + result = createDirFor(url); + } + return result; + } + + + /** + * Get all local mirror repository dirs + * @return see above + */ + @NotNull + public List<File> getMirrors() { + myLock.readLock().lock(); + try { + return new ArrayList<File>(myMirrors.values()); + } finally { + myLock.readLock().unlock(); + } + } + + + @NotNull + public Map<String, File> getMappings() { + myLock.readLock().lock(); + try { + return new HashMap<String, File>(myMirrors); + } finally { + myLock.readLock().unlock(); + } + } + + public void lockDir(@NotNull final File dir) { + lockFor(dir).lock(); + } + + public void unlockDir(@NotNull final File dir) { + lockFor(dir).unlock(); + } + + private Lock lockFor(final File dir) { + String path = dir.getAbsolutePath(); + Lock lock = myDirLocks.get(path); + if (lock == null) { + lock = new ReentrantLock(); + Lock curLock = myDirLocks.putIfAbsent(path, lock); + if (curLock != null) + lock = curLock; + } + return lock; + } + + /** + * Forget specified dir. After call to this method with non-empty dir, + * all urls which were mapped to this dir will be mapped to another. + * If dir is empty, subsequent call getMirrorDir(dir) will return the + * same dir. + * + * @param dir dir of interest + */ + public void forgetDir(@NotNull final File dir) { + myLock.writeLock().lock(); + try { + removeMappingsToDir(dir); + saveMappingToFile(); + } finally { + myLock.writeLock().unlock(); + } + } + + private void removeMappingsToDir(@NotNull final File dir) { + Set<String> keysToRemove = getUrlsMappedToDir(dir); + for (String key : keysToRemove) { + myMirrors.remove(key); + } + } + + private Set<String> getUrlsMappedToDir(@NotNull final File dir) { + Set<String> urlsMappedToDir = new HashSet<String>(); + for (Map.Entry<String, File> entry : myMirrors.entrySet()) { + File f = entry.getValue(); + if (f.equals(dir)) + urlsMappedToDir.add(entry.getKey()); + } + return urlsMappedToDir; + } + + + //for tests only + void setHashCalculator(HashCalculator hash) { + myHash = hash; + } + + + private File createDirFor(String url) { + File result; + myLock.writeLock().lock(); + try { + File mirrorDir = getUniqueDir(url); + result = saveMappingIfAbsent(url, mirrorDir); + } finally { + myLock.writeLock().unlock(); + } + if (!result.exists()) { + result.mkdirs(); + } + return result; + } + + + private File getMirrorDirWithLock(String url) { + myLock.readLock().lock(); + try { + return myMirrors.get(url); + } finally { + myLock.readLock().unlock(); + } + } + + + //should be called with myLock.writeLock() held + private File saveMappingIfAbsent(String url, File mirrorDir) { + File existing = myMirrors.get(url); + if (existing != null) { + return existing; + } else { + myMirrors.put(url, mirrorDir); + saveMappingToFile(); + return mirrorDir; + } + } + + + private File getUniqueDir(String url) { + myLock.readLock().lock(); + try { + String dirName = MIRROR_DIR_PREFIX + hash(normalize(url)); + File result = PathUtil.getCanonicalFile(new File(myRootDir, dirName)); + while (isUsedForOtherUrl(result, url) || !isEmptyDir(result)) { + dirName = MIRROR_DIR_PREFIX + hash(result.getName()); + result = PathUtil.getCanonicalFile(new File(myRootDir, dirName)); + } + return result; + } finally { + myLock.readLock().unlock(); + } + } + + + private boolean isUsedForOtherUrl(File repositoryDir, String url) { + myLock.readLock().lock(); + try { + for (Map.Entry<String, File> mirror : myMirrors.entrySet()) { + String mirrorUrl = mirror.getKey(); + File mirrorDir = mirror.getValue(); + if (mirrorDir.equals(repositoryDir) && !mirrorUrl.equals(url)) { + return true; + } + } + return false; + } finally { + myLock.readLock().unlock(); + } + } + + + private String hash(String value) { + return String.valueOf(myHash.calc(value)); + } + + + private static String normalize(final String path) { + String normalized = PathUtil.normalizeSeparator(path); + if (path.endsWith("/")) { + return normalized.substring(0, normalized.length()-1); + } + return normalized; + } + + + private void readMappingFromFile() { + myLock.writeLock().lock(); + try { + LOG.debug("Parse mapping file " + myMappingFile.getAbsolutePath()); + for (String line : readLines()) { + int separatorIndex = line.lastIndexOf(" = "); + if (separatorIndex == -1) { + if (!line.equals("")) + LOG.warn("Cannot parse mapping '" + line + "', skip it."); + } else { + String url = line.substring(0, separatorIndex); + String dirName = line.substring(separatorIndex + 3); + File repositoryDir = PathUtil.getCanonicalFile(new File(myRootDir, dirName)); + if (isUsedForOtherUrl(repositoryDir, url)) { + LOG.error("Skip mapping " + line + ": " + dirName + " is used for url other than " + url); + } else { + myMirrors.put(url, PathUtil.getCanonicalFile(new File(myRootDir, dirName))); + } + } + } + } finally { + myLock.writeLock().unlock(); + } + } + + /*Should be called with myLock.writeLock() held*/ + private List<String> readLines() { + if (myMappingFile.exists()) { + try { + return FileUtil.readFile(myMappingFile); + } catch (IOException e) { + LOG.error("Error while reading a mapping file at " + myMappingFile.getAbsolutePath() + " starting with empty mapping", e); + return new ArrayList<String>(); + } + } else { + LOG.debug("No mapping file found at " + myMappingFile.getAbsolutePath() + " starting with empty mapping"); + File parentDir = myMappingFile.getParentFile(); + if (!parentDir.exists() && !parentDir.mkdirs()) { + LOG.error("Cannot create local mirrors dir at " + parentDir.getAbsolutePath()); + } else { + try { + if (!myMappingFile.createNewFile()) + LOG.warn("Someone else creates a mapping file " + myMappingFile.getAbsolutePath() + ", will use it"); + } catch (IOException e) { + LOG.error("Cannot create a mapping file at " + myMappingFile.getAbsolutePath(), e); + } + } + return new ArrayList<String>(); + } + } + + + private void saveMappingToFile() { + myLock.writeLock().lock(); + try { + StringBuilder sb = new StringBuilder(); + for (Map.Entry<String, File> mirror : myMirrors.entrySet()) { + String url = mirror.getKey(); + String dir = mirror.getValue().getName(); + sb.append(url).append(" = ").append(dir).append("\n"); + } + FileUtil.writeFile(myMappingFile, sb.toString()); + } finally { + myLock.writeLock().unlock(); + } + } + + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + myLock.readLock().lock(); + try { + Iterator<Map.Entry<String, File>> iter = myMirrors.entrySet().iterator(); + while (iter.hasNext()) { + Map.Entry<String, File> entry = iter.next(); + sb.append("[").append(entry.getKey()).append("]").append("->").append(entry.getValue().getAbsolutePath()); + if (iter.hasNext()) + sb.append("\n"); + } + } finally { + myLock.readLock().unlock(); + } + return sb.toString(); + } + + final static class StandartHash implements HashCalculator { + public long calc(String value) { + return Hash.calc(value); + } + } + + public static interface HashCalculator { + long calc(String value); + } +}
--- a/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/PathUtil.java Wed Feb 15 12:07:12 2012 +0400 +++ b/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/PathUtil.java Wed Feb 15 13:23:53 2012 +0400 @@ -1,37 +1,37 @@ -/* - * 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 org.jetbrains.annotations.NotNull; - -import java.io.File; -import java.io.IOException; - -public class PathUtil { - @NotNull - public static String normalizeSeparator(@NotNull String repPath) { - return repPath.replace('\\', '/'); - } - - @NotNull - public static File getCanonicalFile(@NotNull File file) { - try { - return file.getCanonicalFile(); - } catch (IOException e) { - return file.getAbsoluteFile(); - } - } -} +/* + * 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 org.jetbrains.annotations.NotNull; + +import java.io.File; +import java.io.IOException; + +public class PathUtil { + @NotNull + public static String normalizeSeparator(@NotNull String repPath) { + return repPath.replace('\\', '/'); + } + + @NotNull + public static File getCanonicalFile(@NotNull File file) { + try { + return file.getCanonicalFile(); + } catch (IOException e) { + return file.getAbsoluteFile(); + } + } +}
--- a/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/PluginConfig.java Wed Feb 15 12:07:12 2012 +0400 +++ b/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/PluginConfig.java Wed Feb 15 13:23:53 2012 +0400 @@ -1,10 +1,15 @@ package jetbrains.buildServer.buildTriggers.vcs.mercurial; +import org.jetbrains.annotations.NotNull; + +import java.io.File; + /** * @author dmitry.neverov */ public interface PluginConfig { - int getPullTimeout(); + @NotNull + File getCachesDir(); }
--- a/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/ArchiveCommand.java Wed Feb 15 12:07:12 2012 +0400 +++ b/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/ArchiveCommand.java Wed Feb 15 13:23:53 2012 +0400 @@ -20,7 +20,9 @@ import java.io.File; -public class ArchiveCommand extends BaseCommand { +import static jetbrains.buildServer.buildTriggers.vcs.mercurial.command.CommandExecutionSettingsBuilder.with; + +public class ArchiveCommand extends VcsRootCommand { private File myDestDir; private String myToId; @@ -45,8 +47,7 @@ setRevision(cli); setDestination(cli); - CommandResult res = runCommand(cli); - failIfNotEmptyStdErr(cli, res); + runCommand(cli, with().failureWhenStderrNotEmpty()); deleteHgArchival(); }
--- a/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/BaseCommand.java Wed Feb 15 12:07:12 2012 +0400 +++ b/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/BaseCommand.java Wed Feb 15 13:23:53 2012 +0400 @@ -1,91 +1,60 @@ -/* - * 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.command; - -import com.intellij.execution.configurations.GeneralCommandLine; -import jetbrains.buildServer.util.StringUtil; -import jetbrains.buildServer.vcs.VcsException; -import org.jetbrains.annotations.NotNull; - -import java.io.File; -import java.util.Collections; -import java.util.Set; - -/** - * @author pavel - */ -public class BaseCommand { - private Settings mySettings; - private String myWorkDirectory; - - public BaseCommand(@NotNull final Settings settings, @NotNull File workingDir) { - mySettings = settings; - myWorkDirectory = workingDir.getAbsolutePath(); - } - - - public Settings getSettings() { - return mySettings; - } - - /** - * Sets new working directory, by default working directory is taken from the Settings#getLocalRepositoryDir - * @param workDirectory work dir - */ - public void setWorkDirectory(final String workDirectory) { - myWorkDirectory = workDirectory; - } - - public String getWorkDirectory() { - return myWorkDirectory; - } - - protected GeneralCommandLine createCommandLine() { - GeneralCommandLine cli = createCL(); - cli.setWorkDirectory(myWorkDirectory); - cli.setPassParentEnvs(true); - return cli; - } - - protected GeneralCommandLine createCL() { - GeneralCommandLine cl = new MercurialCommandLine(getPrivateData()); - cl.setExePath(getSettings().getHgCommandPath()); - return cl; - } - - protected CommandResult runCommand(@NotNull GeneralCommandLine cli) throws VcsException { - return CommandUtil.runCommand(cli, getPrivateData()); - } - - protected CommandResult runCommand(@NotNull GeneralCommandLine cli, boolean logErrorsInDebug) throws VcsException { - return CommandUtil.runCommand(cli, CommandUtil.DEFAULT_COMMAND_TIMEOUT_SEC, getPrivateData(), logErrorsInDebug); - } - - protected CommandResult runCommand(@NotNull GeneralCommandLine cli, int executionTimeout) throws VcsException { - return CommandUtil.runCommand(cli, executionTimeout, getPrivateData()); - } - - protected void failIfNotEmptyStdErr(@NotNull GeneralCommandLine cli, @NotNull CommandResult res) throws VcsException { - if (!StringUtil.isEmpty(res.getStderr())) { - CommandUtil.commandFailed(cli.getCommandLineString(), res); - } - } - - public Set<String> getPrivateData() { - String password = mySettings.getPassword(); - return password != null ? Collections.singleton(password) : Collections.<String>emptySet(); - } -} +/* + * 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.command; + +import com.intellij.execution.configurations.GeneralCommandLine; +import org.jetbrains.annotations.NotNull; + +import java.io.File; +import java.util.Set; + +import static java.util.Collections.emptySet; + +/** + * @author pavel + */ +public class BaseCommand { + + private final String myHgPath; + private final File myWorkDirectory; + + public BaseCommand(@NotNull final String hgPath, @NotNull File workingDir) { + myHgPath = hgPath; + myWorkDirectory = workingDir; + } + + + public File getWorkDirectory() { + return myWorkDirectory; + } + + protected GeneralCommandLine createCommandLine() { + GeneralCommandLine cli = createCL(); + cli.setWorkDirectory(myWorkDirectory.getAbsolutePath()); + cli.setPassParentEnvs(true); + return cli; + } + + protected GeneralCommandLine createCL() { + GeneralCommandLine cl = new MercurialCommandLine(getPrivateData()); + cl.setExePath(myHgPath); + return cl; + } + + protected Set<String> getPrivateData() { + return emptySet(); + } +}
--- a/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/BranchesCommand.java Wed Feb 15 12:07:12 2012 +0400 +++ b/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/BranchesCommand.java Wed Feb 15 13:23:53 2012 +0400 @@ -1,60 +1,60 @@ -/* - * 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.command; - -import com.intellij.execution.configurations.GeneralCommandLine; -import jetbrains.buildServer.vcs.VcsException; -import org.jetbrains.annotations.NotNull; - -import java.io.File; -import java.util.HashMap; -import java.util.Map; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -/** - * @author Pavel.Sher - * Date: 26.10.2008 - */ -public class BranchesCommand extends BaseCommand { - - public BranchesCommand(@NotNull Settings settings, @NotNull File workingDir) { - super(settings, workingDir); - } - - /** - * Returns map of branch name to latest changeset in that branch - * @return see above - * @throws jetbrains.buildServer.vcs.VcsException if error occurs - */ - public Map<String, ChangeSet> execute() throws VcsException { - GeneralCommandLine cli = createCommandLine(); - cli.addParameter("branches"); - CommandResult res = runCommand(cli); - String stdout = res.getStdout(); - Map<String, ChangeSet> result = new HashMap<String, ChangeSet>(); - Pattern branchPattern = Pattern.compile("(.*)[\\s]+([0-9]+:[A-Za-z0-9]+).*"); - for (String line: stdout.split("[\r\n]+")) { - Matcher matcher = branchPattern.matcher(line); - if (matcher.matches()) { - String branchName = matcher.group(1).trim(); - String changeId = matcher.group(2).trim(); - result.put(branchName, new ChangeSet(changeId)); - } - } - return result; - } -} +/* + * 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.command; + +import com.intellij.execution.configurations.GeneralCommandLine; +import jetbrains.buildServer.vcs.VcsException; +import org.jetbrains.annotations.NotNull; + +import java.io.File; +import java.util.HashMap; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * @author Pavel.Sher + * Date: 26.10.2008 + */ +public class BranchesCommand extends VcsRootCommand { + + public BranchesCommand(@NotNull Settings settings, @NotNull File workingDir) { + super(settings, workingDir); + } + + /** + * Returns map of branch name to latest changeset in that branch + * @return see above + * @throws jetbrains.buildServer.vcs.VcsException if error occurs + */ + public Map<String, ChangeSet> execute() throws VcsException { + GeneralCommandLine cli = createCommandLine(); + cli.addParameter("branches"); + CommandResult res = runCommand(cli); + String stdout = res.getStdout(); + Map<String, ChangeSet> result = new HashMap<String, ChangeSet>(); + Pattern branchPattern = Pattern.compile("(.*)[\\s]+([0-9]+:[A-Za-z0-9]+).*"); + for (String line: stdout.split("[\r\n]+")) { + Matcher matcher = branchPattern.matcher(line); + if (matcher.matches()) { + String branchName = matcher.group(1).trim(); + String changeId = matcher.group(2).trim(); + result.put(branchName, new ChangeSet(changeId)); + } + } + return result; + } +}
--- a/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/CatCommand.java Wed Feb 15 12:07:12 2012 +0400 +++ b/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/CatCommand.java Wed Feb 15 13:23:53 2012 +0400 @@ -1,88 +1,105 @@ -/* - * 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.command; - -import com.intellij.execution.configurations.GeneralCommandLine; -import jetbrains.buildServer.util.FileUtil; -import jetbrains.buildServer.vcs.VcsException; -import org.jetbrains.annotations.NotNull; - -import java.io.File; -import java.io.IOException; -import java.util.LinkedList; -import java.util.List; -import java.util.Queue; - -public class CatCommand extends BaseCommand { - private String myRevId; - private final static int MAX_CMD_LEN = 900; - private boolean myLogErrorsInDebug = false; - - public CatCommand(@NotNull Settings settings, @NotNull File workingDir) { - super(settings, workingDir); - } - - public void setRevId(final String revId) { - myRevId = revId; - } - - public void setLogErrorsInDebug(boolean doLogErrorsInDebug) { - myLogErrorsInDebug = doLogErrorsInDebug; - } - - public File execute(List<String> relPaths) throws VcsException { - File tempDir; - try { - tempDir = FileUtil.createTempDirectory("mercurial", "catresult"); - } catch (IOException e) { - throw new VcsException("Unable to create temporary directory"); - } - for (String path: relPaths) { - final File parentFile = new File(tempDir, path).getParentFile(); - if (!parentFile.isDirectory() && !parentFile.mkdirs()) { - throw new VcsException("Failed to create directory: " + parentFile.getAbsolutePath()); - } - } - - Queue<String> paths = new LinkedList<String>(relPaths); - while (!paths.isEmpty()) { - GeneralCommandLine cli = createCommandLine(tempDir); - int cmdSize = cli.getCommandLineString().length(); - - do { - String path = paths.poll(); - cli.addParameter(path); - cmdSize += path.length(); - } while (cmdSize < MAX_CMD_LEN && !paths.isEmpty()); - - runCommand(cli, myLogErrorsInDebug); - } - - return tempDir; - } - - private GeneralCommandLine createCommandLine(final File tempDir) { - GeneralCommandLine cli = createCommandLine(); - cli.addParameter("cat"); - cli.addParameter("-o"); - cli.addParameter(tempDir.getAbsolutePath() + File.separator + "%p"); - if (myRevId != null) { - cli.addParameter("-r"); - cli.addParameter(myRevId); - } - return cli; - } -} +/* + * 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.command; + +import com.intellij.execution.configurations.GeneralCommandLine; +import jetbrains.buildServer.util.FileUtil; +import jetbrains.buildServer.vcs.VcsException; +import org.jetbrains.annotations.NotNull; + +import java.io.File; +import java.io.IOException; +import java.util.LinkedList; +import java.util.List; +import java.util.Queue; + +import static com.intellij.openapi.util.io.FileUtil.delete; +import static jetbrains.buildServer.buildTriggers.vcs.mercurial.command.CommandExecutionSettingsBuilder.with; + +public class CatCommand extends VcsRootCommand { + private String myRevId; + private final static int MAX_CMD_LEN = 900; + + public CatCommand(@NotNull Settings settings, @NotNull File workingDir) { + super(settings, workingDir); + } + + public void setRevId(final String revId) { + myRevId = revId; + } + + public File execute(List<String> relPaths) throws VcsException { + return execute(relPaths, true); + } + + public File execute(List<String> relPaths, boolean checkFailure) throws VcsException { + File tempDir = null; + try { + tempDir = createTmpDir(); + createDirectories(relPaths, tempDir); + catFiles(relPaths, checkFailure, tempDir); + return tempDir; + } catch (VcsException e) { + if (tempDir != null) + delete(tempDir); + throw e; + } + } + + private File createTmpDir() throws VcsException { + try { + return FileUtil.createTempDirectory("mercurial", "catresult"); + } catch (IOException e) { + throw new VcsException("Unable to create temporary directory"); + } + } + + private void createDirectories(List<String> relPaths, File tempDir) throws VcsException { + for (String path: relPaths) { + File parentFile = new File(tempDir, path).getParentFile(); + if (!parentFile.isDirectory() && !parentFile.mkdirs()) + throw new VcsException("Failed to create directory: " + parentFile.getAbsolutePath()); + } + } + + private void catFiles(List<String> relPaths, boolean checkFailure, File tempDir) throws VcsException { + Queue<String> paths = new LinkedList<String>(relPaths); + while (!paths.isEmpty()) { + GeneralCommandLine cli = createCommandLine(tempDir); + int cmdSize = cli.getCommandLineString().length(); + + do { + String path = paths.poll(); + cli.addParameter(path); + cmdSize += path.length(); + } while (cmdSize < MAX_CMD_LEN && !paths.isEmpty()); + + runCommand(cli, with().checkForFailure(checkFailure)); + } + } + + private GeneralCommandLine createCommandLine(final File tempDir) { + GeneralCommandLine cli = createCommandLine(); + cli.addParameter("cat"); + cli.addParameter("-o"); + cli.addParameter(tempDir.getAbsolutePath() + File.separator + "%p"); + if (myRevId != null) { + cli.addParameter("-r"); + cli.addParameter(myRevId); + } + return cli; + } +}
--- a/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/ChangeSet.java Wed Feb 15 12:07:12 2012 +0400 +++ b/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/ChangeSet.java Wed Feb 15 13:23:53 2012 +0400 @@ -1,115 +1,113 @@ -/* - * 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.command; - -import org.jetbrains.annotations.NotNull; - -import java.util.ArrayList; -import java.util.Date; -import java.util.List; - -/** - * Represents Mercurial change set - */ -public class ChangeSet extends ChangeSetRevision { - @NotNull private String myUser; - @NotNull private Date myTimestamp; - private String myDescription; - private List<ChangeSetRevision> myParents = new ArrayList<ChangeSetRevision>(); - private List<ModifiedFile> myModifiedFiles = new ArrayList<ModifiedFile>(); - - - public ChangeSet(final int revNumber, @NotNull final String id) { - super(revNumber, id); - } - - /** - * Constructor for version in the form revnum:changeset_id or just changeset_id (in this case rev number is set to -1) - * @param fullVersion full changeset version as reported by hg log command - */ - public ChangeSet(@NotNull final String fullVersion) { - super(fullVersion); - } - - public void setUser(@NotNull final String user) { - myUser = user; - } - - public void setTimestamp(@NotNull final Date timestamp) { - myTimestamp = timestamp; - } - - public void setDescription(final String description) { - myDescription = description; - } - - public void addParent(@NotNull ChangeSetRevision rev) { - myParents.add(rev); - } - - /** - * Returns user who made changeset - * @return user who made changeset - */ - @NotNull - public String getUser() { - return myUser; - } - - /** - * Returns changeset timestamp - * @return changeset timestamp - */ - @NotNull - public Date getTimestamp() { - return myTimestamp; - } - - /** - * Returns changeset summary specified by user - * @return changeset summary - */ - public String getDescription() { - return myDescription; - } - - /** - * Returns parrents of this change set - * @return see above - */ - @NotNull - public List<ChangeSetRevision> getParents() { - return myParents; - } - - /** - * Check if changeset is initial changeset (has no parents) - * @return true if changeset is initial changeset - */ - public boolean isInitial() { - return getParents().isEmpty(); - } - - public void setModifiedFiles(@NotNull final List<ModifiedFile> files) { - myModifiedFiles = files; - } - - @NotNull - public List<ModifiedFile> getModifiedFiles() { - return myModifiedFiles; - } - -} +/* + * 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.command; + +import org.jetbrains.annotations.NotNull; + +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + +/** + * Represents Mercurial change set + */ +public class ChangeSet extends ChangeSetRevision { + @NotNull private String myUser; + @NotNull private Date myTimestamp; + private String myDescription; + private List<ChangeSetRevision> myParents = new ArrayList<ChangeSetRevision>(); + private List<ModifiedFile> myModifiedFiles = new ArrayList<ModifiedFile>(); + + public ChangeSet(final int revNumber, @NotNull final String id) { + super(revNumber, id); + } + + /** + * Constructor for version in the form revnum:changeset_id or just changeset_id (in this case rev number is set to -1) + * @param fullVersion full changeset version as reported by hg log command + */ + public ChangeSet(@NotNull final String fullVersion) { + super(fullVersion); + } + + public void setUser(@NotNull final String user) { + myUser = user; + } + + public void setTimestamp(@NotNull final Date timestamp) { + myTimestamp = timestamp; + } + + public void setDescription(final String description) { + myDescription = description; + } + + public void addParent(@NotNull ChangeSetRevision rev) { + myParents.add(rev); + } + + /** + * Returns user who made changeset + * @return user who made changeset + */ + @NotNull + public String getUser() { + return myUser; + } + + /** + * Returns changeset timestamp + * @return changeset timestamp + */ + @NotNull + public Date getTimestamp() { + return myTimestamp; + } + + /** + * Returns changeset summary specified by user + * @return changeset summary + */ + public String getDescription() { + return myDescription; + } + + /** + * Returns parrents of this change set + * @return see above + */ + @NotNull + public List<ChangeSetRevision> getParents() { + return myParents; + } + + /** + * Check if changeset is initial changeset (has no parents) + * @return true if changeset is initial changeset + */ + public boolean isInitial() { + return getParents().isEmpty(); + } + + public void setModifiedFiles(@NotNull final List<ModifiedFile> files) { + myModifiedFiles = files; + } + + @NotNull + public List<ModifiedFile> getModifiedFiles() { + return myModifiedFiles; + } +}
--- a/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/ChangeSetRevision.java Wed Feb 15 12:07:12 2012 +0400 +++ b/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/ChangeSetRevision.java Wed Feb 15 13:23:53 2012 +0400 @@ -1,99 +1,99 @@ -/* - * 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.command; - -import org.jetbrains.annotations.NotNull; - -/** - * @author Pavel.Sher - */ -public class ChangeSetRevision { - private final int myRevNumber; - @NotNull - private final String myId; - - public ChangeSetRevision(final int revNumber, @NotNull final String id) { - myRevNumber = revNumber; - myId = id; - } - - /** - * Constructor for version in the form revnum:changeset_id or just changeset_id (in this case rev number is set to -1) - * @param fullVersion full changeset version as reported by hg log command - */ - public ChangeSetRevision(@NotNull final String fullVersion) { - try { - int colon = fullVersion.indexOf(":"); - if (colon != -1) { - myRevNumber = Integer.parseInt(fullVersion.substring(0, colon)); - myId = fullVersion.substring(colon+1); - } else { - myRevNumber = -1; - myId = fullVersion; - } - } catch (Throwable e) { - throw new IllegalArgumentException(e); - } - } - - /** - * Returns changeset revision id - * @return changeset revision id - */ - @NotNull - public String getId() { - return myId; - } - - /** - * Returns changeset revision number - * @return changeset revision number - */ - public int getRevNumber() { - return myRevNumber; - } - - /** - * Returns full changeset version as reported by hg log command: revnum:revid - * @return full changeset version as reported by hg log - */ - @NotNull - public String getFullVersion() { - return myRevNumber + ":" + myId; - } - - @Override - public boolean equals(final Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - - final ChangeSetRevision that = (ChangeSetRevision) o; - - return myRevNumber == that.myRevNumber && myId.equals(that.myId); - } - - @Override - public int hashCode() { - int result = myRevNumber; - result = 31 * result + myId.hashCode(); - return result; - } - - @Override - public String toString() { - return "change:" + myId; - } -} +/* + * 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.command; + +import org.jetbrains.annotations.NotNull; + +/** + * @author Pavel.Sher + */ +public class ChangeSetRevision { + private final int myRevNumber; + @NotNull + private final String myId; + + public ChangeSetRevision(final int revNumber, @NotNull final String id) { + myRevNumber = revNumber; + myId = id; + } + + /** + * Constructor for version in the form revnum:changeset_id or just changeset_id (in this case rev number is set to -1) + * @param fullVersion full changeset version as reported by hg log command + */ + public ChangeSetRevision(@NotNull final String fullVersion) { + try { + int colon = fullVersion.indexOf(":"); + if (colon != -1) { + myRevNumber = Integer.parseInt(fullVersion.substring(0, colon)); + myId = fullVersion.substring(colon+1); + } else { + myRevNumber = -1; + myId = fullVersion; + } + } catch (Throwable e) { + throw new IllegalArgumentException(e); + } + } + + /** + * Returns changeset revision id + * @return changeset revision id + */ + @NotNull + public String getId() { + return myId; + } + + /** + * Returns changeset revision number + * @return changeset revision number + */ + public int getRevNumber() { + return myRevNumber; + } + + /** + * Returns full changeset version as reported by hg log command: revnum:revid + * @return full changeset version as reported by hg log + */ + @NotNull + public String getFullVersion() { + return myRevNumber + ":" + myId; + } + + @Override + public boolean equals(final Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + final ChangeSetRevision that = (ChangeSetRevision) o; + + return myRevNumber == that.myRevNumber && myId.equals(that.myId); + } + + @Override + public int hashCode() { + int result = myRevNumber; + result = 31 * result + myId.hashCode(); + return result; + } + + @Override + public String toString() { + return "change:" + myId; + } +}
--- a/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/ChangedFilesCommand.java Wed Feb 15 12:07:12 2012 +0400 +++ b/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/ChangedFilesCommand.java Wed Feb 15 13:23:53 2012 +0400 @@ -1,75 +1,75 @@ -/* - * 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.command; - -import com.intellij.execution.configurations.GeneralCommandLine; -import jetbrains.buildServer.util.FileUtil; -import jetbrains.buildServer.vcs.VcsException; -import org.jetbrains.annotations.NotNull; - -import java.io.File; -import java.io.IOException; -import java.util.List; - -/** - * @author Pavel.Sher - */ -public class ChangedFilesCommand extends BaseCommand { - private String myRevId; - - public ChangedFilesCommand(@NotNull Settings settings, @NotNull File workingDir) { - super(settings, workingDir); - } - - public void setRevId(@NotNull final String revId) { - myRevId = revId; - } - - public List<ModifiedFile> execute() throws VcsException { - File styleFile; - try { - styleFile = getStyleFile(); - } catch (IOException e) { - throw new VcsException("Unable to create style file: " + e.toString(), e); - } - try { - GeneralCommandLine cli = createCommandLine(); - cli.addParameter("log"); - cli.addParameter("-r"); - cli.addParameter(myRevId + ":" + myRevId); - cli.addParameter("--style=" + styleFile.getAbsolutePath()); - - CommandResult res = runCommand(cli); - return parseFiles(res.getStdout()); - } finally { - FileUtil.delete(styleFile); - } - } - - private File getStyleFile() throws IOException { - File styleFile = FileUtil.createTempFile("hg", "style"); - FileUtil.writeFile(styleFile, - "changeset = \"{file_mods}{file_adds}{file_dels}\"\n" + - "file_add = \"A {file_add}\\n\"\n" + - "file_del = \"R {file_del}\\n\"\n" + - "file_mod = \"M {file_mod}\\n\""); - return styleFile; - } - - private List<ModifiedFile> parseFiles(final String stdout) { - return StatusCommand.parseFiles(stdout); - } -} +/* + * 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.command; + +import com.intellij.execution.configurations.GeneralCommandLine; +import jetbrains.buildServer.util.FileUtil; +import jetbrains.buildServer.vcs.VcsException; +import org.jetbrains.annotations.NotNull; + +import java.io.File; +import java.io.IOException; +import java.util.List; + +/** + * @author Pavel.Sher + */ +public class ChangedFilesCommand extends VcsRootCommand { + private String myRevId; + + public ChangedFilesCommand(@NotNull Settings settings, @NotNull File workingDir) { + super(settings, workingDir); + } + + public void setRevId(@NotNull final String revId) { + myRevId = revId; + } + + public List<ModifiedFile> execute() throws VcsException { + File styleFile; + try { + styleFile = getStyleFile(); + } catch (IOException e) { + throw new VcsException("Unable to create style file: " + e.toString(), e); + } + try { + GeneralCommandLine cli = createCommandLine(); + cli.addParameter("log"); + cli.addParameter("-r"); + cli.addParameter(myRevId + ":" + myRevId); + cli.addParameter("--style=" + styleFile.getAbsolutePath()); + + CommandResult res = runCommand(cli); + return parseFiles(res.getStdout()); + } finally { + FileUtil.delete(styleFile); + } + } + + private File getStyleFile() throws IOException { + File styleFile = FileUtil.createTempFile("hg", "style"); + FileUtil.writeFile(styleFile, + "changeset = \"{file_mods}{file_adds}{file_dels}\"\n" + + "file_add = \"A {file_add}\\n\"\n" + + "file_del = \"R {file_del}\\n\"\n" + + "file_mod = \"M {file_mod}\\n\""); + return styleFile; + } + + private List<ModifiedFile> parseFiles(final String stdout) { + return StatusCommand.parseFiles(stdout); + } +}
--- a/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/CloneCommand.java Wed Feb 15 12:07:12 2012 +0400 +++ b/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/CloneCommand.java Wed Feb 15 13:23:53 2012 +0400 @@ -1,79 +1,81 @@ -/* - * 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.command; - -import com.intellij.execution.configurations.GeneralCommandLine; -import jetbrains.buildServer.vcs.VcsException; -import org.jetbrains.annotations.NotNull; - -import java.io.File; - -public class CloneCommand extends BaseCommand{ - private String myToId; - private boolean myUpdateWorkingDir = true; - private String myRepository; - private File myWorkingDir; - private boolean myUsePullProtocol = true; - - public CloneCommand(@NotNull Settings settings, @NotNull File workingDir) { - super(settings, workingDir); - myWorkingDir = workingDir; - myRepository = getSettings().getRepositoryUrl(); - } - - /** - * Sets repository to clone, by default uses repository from the specified settings - * @param repo repository path - */ - public void setRepository(@NotNull String repo) { - myRepository = repo; - } - - public void setToId(final String toId) { - myToId = toId; - } - - public void setUpdateWorkingDir(final boolean updateWorkingDir) { - myUpdateWorkingDir = updateWorkingDir; - } - - public void setUsePullProtocol(boolean usePullProtocol) { - myUsePullProtocol = usePullProtocol; - } - - public void execute() throws VcsException { - GeneralCommandLine cli = createCommandLine(); - File parent = myWorkingDir.getParentFile(); - cli.setWorkDirectory(parent.getAbsolutePath()); - cli.addParameter("clone"); - if (myToId != null) { - cli.addParameter("-r"); - cli.addParameter(myToId); - } - if (myUsePullProtocol) - cli.addParameter("--pull"); - if (!myUpdateWorkingDir) { - cli.addParameter("-U"); - } - if (getSettings().isUncompressedTransfer()) { - cli.addParameter("--uncompressed"); - } - cli.addParameter(myRepository); - cli.addParameter(myWorkingDir.getName()); - - runCommand(cli, 24*3600); // some repositories are quite large, we set timeout to 24 hours - } -} +/* + * 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.command; + +import com.intellij.execution.configurations.GeneralCommandLine; +import jetbrains.buildServer.vcs.VcsException; +import org.jetbrains.annotations.NotNull; + +import java.io.File; + +import static jetbrains.buildServer.buildTriggers.vcs.mercurial.command.CommandExecutionSettingsBuilder.with; + +public class CloneCommand extends VcsRootCommand { + private String myToId; + private boolean myUpdateWorkingDir = true; + private String myRepository; + private File myWorkingDir; + private boolean myUsePullProtocol = true; + + public CloneCommand(@NotNull Settings settings, @NotNull File workingDir) { + super(settings, workingDir); + myWorkingDir = workingDir; + myRepository = getSettings().getRepositoryUrlWithCredentials(); + } + + /** + * Sets repository to clone, by default uses repository from the specified settings + * @param repo repository path + */ + public void setRepository(@NotNull String repo) { + myRepository = repo; + } + + public void setToId(final String toId) { + myToId = toId; + } + + public void setUpdateWorkingDir(final boolean updateWorkingDir) { + myUpdateWorkingDir = updateWorkingDir; + } + + public void setUsePullProtocol(boolean usePullProtocol) { + myUsePullProtocol = usePullProtocol; + } + + public void execute() throws VcsException { + GeneralCommandLine cli = createCommandLine(); + File parent = myWorkingDir.getParentFile(); + cli.setWorkDirectory(parent.getAbsolutePath()); + cli.addParameter("clone"); + if (myToId != null) { + cli.addParameter("-r"); + cli.addParameter(myToId); + } + if (myUsePullProtocol) + cli.addParameter("--pull"); + if (!myUpdateWorkingDir) { + cli.addParameter("-U"); + } + if (getSettings().isUncompressedTransfer()) { + cli.addParameter("--uncompressed"); + } + cli.addParameter(myRepository); + cli.addParameter(myWorkingDir.getName()); + + runCommand(cli, with().timeout(24 * 3600)); // some repositories are quite large, we set timeout to 24 hours + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/CommandExecutionSettings.java Wed Feb 15 13:23:53 2012 +0400 @@ -0,0 +1,40 @@ +package jetbrains.buildServer.buildTriggers.vcs.mercurial.command; + +import org.jetbrains.annotations.NotNull; + +import java.util.Set; + +/** + * @author dmitry.neverov + */ +public class CommandExecutionSettings { + + private final int myTimeout; + private final Set<String> myPrivateData; + private final boolean myCheckForFailure; + private final boolean myFailWhenStderrNotEmpty; + + CommandExecutionSettings(int timeout, @NotNull Set<String> privateData, boolean checkForFailure, boolean failWhenStderrNotEmpty) { + myTimeout = timeout; + myPrivateData = privateData; + myCheckForFailure = checkForFailure; + myFailWhenStderrNotEmpty = failWhenStderrNotEmpty; + } + + public int timeout() { + return myTimeout; + } + + @NotNull + public Set<String> privateData() { + return myPrivateData; + } + + public boolean shouldCheckForFailure() { + return myCheckForFailure; + } + + public boolean shouldFailWithNonEmptyStderr() { + return myFailWhenStderrNotEmpty; + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/CommandExecutionSettingsBuilder.java Wed Feb 15 13:23:53 2012 +0400 @@ -0,0 +1,47 @@ +package jetbrains.buildServer.buildTriggers.vcs.mercurial.command; + +import org.jetbrains.annotations.NotNull; + +import java.util.Collections; +import java.util.Set; + +/** + * @author dmitry.neverov + */ +public class CommandExecutionSettingsBuilder { + + private static final int DEFAULT_COMMAND_TIMEOUT_SEC = 3600; + + private int myTimeout = DEFAULT_COMMAND_TIMEOUT_SEC; + private Set<String> myPrivateData = Collections.emptySet(); + private boolean myCheckForFailure = true; + private boolean myFailWhenStderrNotEmpty = false; + + public static CommandExecutionSettingsBuilder with() { + return new CommandExecutionSettingsBuilder(); + } + + public CommandExecutionSettings build() { + return new CommandExecutionSettings(myTimeout, myPrivateData, myCheckForFailure, myFailWhenStderrNotEmpty); + } + + public CommandExecutionSettingsBuilder timeout(int timeout) { + myTimeout = timeout; + return this; + } + + public CommandExecutionSettingsBuilder privateData(@NotNull Set<String> privateData) { + myPrivateData = privateData; + return this; + } + + public CommandExecutionSettingsBuilder checkForFailure(boolean checkForFailure) { + myCheckForFailure = checkForFailure; + return this; + } + + public CommandExecutionSettingsBuilder failureWhenStderrNotEmpty() { + myFailWhenStderrNotEmpty = true; + return this; + } +}
--- a/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/CommandResult.java Wed Feb 15 12:07:12 2012 +0400 +++ b/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/CommandResult.java Wed Feb 15 13:23:53 2012 +0400 @@ -1,24 +1,37 @@ package jetbrains.buildServer.buildTriggers.vcs.mercurial.command; +import com.intellij.openapi.diagnostic.Logger; import jetbrains.buildServer.ExecResult; +import jetbrains.buildServer.util.StringUtil; +import jetbrains.buildServer.vcs.VcsException; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import java.util.Collections; import java.util.Set; +import static com.intellij.openapi.util.text.StringUtil.isEmpty; import static jetbrains.buildServer.buildTriggers.vcs.mercurial.command.CommandUtil.removePrivateData; /** - * Decorator for ExecResult that filters out private data from stdout and strerr. + * Mercurial command result. Filters out private data from stdout and detects errors. * * @author dmitry.neverov */ public class CommandResult { + private final Logger myLogger; + private final String myCommand; private final ExecResult myDelegate; private final Set<String> myPrivateData; - public CommandResult(@NotNull final ExecResult execResult, @NotNull final Set<String> privateData) { + public CommandResult(@NotNull Logger logger, @NotNull String command, @NotNull ExecResult execResult) { + this(logger, command, execResult, Collections.<String>emptySet()); + } + + public CommandResult(@NotNull Logger logger, @NotNull String command, @NotNull ExecResult execResult, @NotNull Set<String> privateData) { + myLogger = logger; + myCommand = command; myDelegate = execResult; myPrivateData = privateData; } @@ -28,18 +41,125 @@ return removePrivateData(myDelegate.getStdout(), myPrivateData); } + public void checkCommandFailed() throws VcsException { + checkFailure(false); + } + + public void checkFailure(boolean failWhenStderrIsNonEmpty) throws VcsException { + rethrowDetectedError(); + if (isFailure()) + logAndThrowError(); + String stderr = getStderr(); + if (!isEmpty(stderr)) { + if (failWhenStderrIsNonEmpty) + logAndThrowError(); + else + logStderr(stderr); + } + } + + private void logAndThrowError() throws VcsException { + String message = createCommandLogMessage(); + myLogger.warn(message); + if (hasImportantException()) + myLogger.error("Error during executing '" + getCommand() + "'", getException()); + throw new VcsException(message); + } + + private void logStderr(String stderr) { + myLogger.warn("Error output produced by: " + getCommand()); + myLogger.warn(stderr); + } + @NotNull - public String getStderr() { + private String getStderr() { return removePrivateData(myDelegate.getStderr(), myPrivateData); } @Nullable - public Throwable getException() { + private Throwable getException() { return myDelegate.getException(); } - public int getExitCode() { - return myDelegate.getExitCode(); + private boolean isFailure() { + //A non-zero exit code is not an error: + //http://mercurial.selenic.com/bts/issue186 + //http://mercurial.selenic.com/bts/issue2189 + //E.g. pull command in hg 2.1 exits with 1 if no new changes were pulled + return getException() != null; + } + + private boolean shouldDetectErrors() { + return isFailure() || myDelegate.getExitCode() != 0; + } + + @NotNull + private String getCommand() { + return removePrivateData(myCommand, myPrivateData); + } + + private boolean hasImportantException() { + Throwable exception = getException(); + return exception instanceof NullPointerException; + } + + private String createCommandLogMessage() { + String stderr = getStderr(); + String stdout = getStdout(); + String exceptionMessage = getExceptionMessage(); + return "'" + getCommand() + "' command failed.\n" + + (!StringUtil.isEmpty(stdout) ? "stdout: " + stdout + "\n" : "") + + (!StringUtil.isEmpty(stderr) ? "stderr: " + stderr + "\n" : "") + + (exceptionMessage != null ? "exception: " + exceptionMessage : ""); + } + + @Nullable + private String getExceptionMessage() { + Throwable exception = getException(); + if (exception == null) + return null; + String message = exception.getMessage(); + if (message == null) + message = exception.getClass().getName(); + return message; + } + + private void rethrowDetectedError() throws VcsException { + if (!shouldDetectErrors()) + return; + String stderr = getStderr().trim(); + checkUnrelatedRepository(stderr); + checkUnknownRevision(stderr); + checkFileNotUnderTheRoot(stderr); + } + + private void checkUnrelatedRepository(@NotNull final String stderr) throws UnrelatedRepositoryException { + if (stderr.contains("abort: repository is unrelated")) + throw new UnrelatedRepositoryException(); + } + + private void checkUnknownRevision(@NotNull final String stderr) throws UnknownRevisionException { + final String message = "abort: unknown revision '"; + int idx = stderr.indexOf(message); + if (idx != -1) { + int startIdx = idx + message.length(); + int endIdx = stderr.indexOf("'", startIdx); + String revision = stderr.substring(startIdx, endIdx); + throw new UnknownRevisionException(revision); + } + } + + private void checkFileNotUnderTheRoot(@NotNull final String stderr) throws VcsException { + final String prefix = "abort: "; + int idx = stderr.indexOf("abort: "); + if (idx != -1) { + int startIdx = idx + prefix.length(); + int endIdx = stderr.indexOf(" not under root"); + if (endIdx != -1) { + String path = stderr.substring(startIdx, endIdx); + throw new UnknownFileException(path); + } + } } public byte[] getByteOut() {
--- a/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/CommandUtil.java Wed Feb 15 12:07:12 2012 +0400 +++ b/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/CommandUtil.java Wed Feb 15 13:23:53 2012 +0400 @@ -1,138 +1,73 @@ -/* - * 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.command; - -import com.intellij.execution.configurations.GeneralCommandLine; -import jetbrains.buildServer.ExecResult; -import jetbrains.buildServer.SimpleCommandLineProcessRunner; -import jetbrains.buildServer.log.Loggers; -import jetbrains.buildServer.util.StringUtil; -import jetbrains.buildServer.vcs.VcsException; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.util.Collections; -import java.util.Set; - -public class CommandUtil { - public static final int DEFAULT_COMMAND_TIMEOUT_SEC = 3600; - - public static void checkCommandFailed(@NotNull String cmdName, @NotNull CommandResult res) throws VcsException { - checkCommandFailed(cmdName, res, false); - } - - private static void checkCommandFailed(@NotNull String cmdName, @NotNull CommandResult res, boolean logErrorsInDebug) throws VcsException { - if (logErrorsInDebug) { - if (res.getExitCode() != 0 || res.getException() != null) - Loggers.VCS.debug(createCommandLogMessage(cmdName, res)); - } else { - if (res.getExitCode() != 0 || res.getException() != null) - commandFailed(cmdName, res); - if (res.getStderr().length() > 0) { - Loggers.VCS.warn("Error output produced by: " + cmdName); - Loggers.VCS.warn(res.getStderr()); - } - } - } - - public static void commandFailed(final String cmdName, final CommandResult res) throws VcsException { - final String message = createCommandLogMessage(cmdName, res); - Loggers.VCS.warn(message); - if (hasImportantException(res)) { - Loggers.VCS.error("Error during executing '" + cmdName + "'", res.getException()); - } - throw new VcsException(message); - } - - private static String createCommandLogMessage(final String cmdName, final CommandResult res) { - String stderr = res.getStderr(); - String stdout = res.getStdout(); - String exceptionMessage = getExceptionMessage(res); - return "'" + cmdName + "' command failed.\n" + - (!StringUtil.isEmpty(stdout) ? "stdout: " + stdout + "\n" : "") + - (!StringUtil.isEmpty(stderr) ? "stderr: " + stderr + "\n" : "") + - (exceptionMessage != null ? "exception: " + exceptionMessage : ""); - } - - @Nullable - private static String getExceptionMessage(CommandResult result) { - Throwable exception = result.getException(); - String message = null; - if (exception != null) { - message = exception.getMessage(); - if (message == null) { - message = exception.getClass().getName(); - } - } - return message; - } - - private static boolean hasImportantException(CommandResult result) { - Throwable exception = result.getException(); - if (exception != null) { - return exception instanceof NullPointerException; - } else { - return false; - } - } - - public static CommandResult runCommand(@NotNull GeneralCommandLine cli) throws VcsException { - return runCommand(cli, DEFAULT_COMMAND_TIMEOUT_SEC, Collections.<String>emptySet()); - } - - public static CommandResult runCommand(@NotNull GeneralCommandLine cli, @NotNull Set<String> privateData) throws VcsException { - return runCommand(cli, DEFAULT_COMMAND_TIMEOUT_SEC, privateData); - } - - public static CommandResult runCommand(@NotNull GeneralCommandLine cli, final int executionTimeout, @NotNull Set<String> privateData) throws VcsException { - return runCommand(cli, executionTimeout, privateData, false); - } - - public static CommandResult runCommand(@NotNull GeneralCommandLine cli, final int executionTimeout, @NotNull Set<String> privateData, boolean logErrorsInDebug) throws VcsException { - final String cmdStr = removePrivateData(cli.getCommandLineString(), privateData); - Loggers.VCS.debug("Run command: " + cmdStr); - CommandResult res = run(cli, executionTimeout, cmdStr, privateData); - CommandUtil.checkCommandFailed(cmdStr, res, logErrorsInDebug); - Loggers.VCS.debug("Command " + cmdStr + " output:\n" + res.getStdout()); - return res; - } - - private static CommandResult run(@NotNull final GeneralCommandLine cli, final int executionTimeout, @NotNull final String cmdStr, @NotNull final Set<String> privateData) { - final long start = System.currentTimeMillis(); - ExecResult res = SimpleCommandLineProcessRunner.runCommand(cli, null, new SimpleCommandLineProcessRunner.RunCommandEventsAdapter() { - @Override - public Integer getOutputIdleSecondsTimeout() { - return executionTimeout; - } - @Override - public void onProcessFinished(Process ps) { - long duration = System.currentTimeMillis() - start; - Loggers.VCS.debug("Command " + cmdStr + " took " + duration + "ms"); - } - }); - return new CommandResult(res, privateData); - } - - public static String removePrivateData(final String str, final Set<String> privateData) { - String result = str; - for (String data: privateData) { - if (data == null || data.length() == 0) continue; - result = result.replace(data, "******"); - } - - return result; - } -} +/* + * 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.command; + +import com.intellij.execution.configurations.GeneralCommandLine; +import jetbrains.buildServer.ExecResult; +import jetbrains.buildServer.SimpleCommandLineProcessRunner; +import jetbrains.buildServer.log.Loggers; +import jetbrains.buildServer.vcs.VcsException; +import org.jetbrains.annotations.NotNull; + +import java.util.Set; + +import static jetbrains.buildServer.buildTriggers.vcs.mercurial.command.CommandExecutionSettingsBuilder.with; + +public class CommandUtil { + + public static CommandResult runCommand(@NotNull GeneralCommandLine cli) throws VcsException { + return runCommand(cli, with()); + } + + public static CommandResult runCommand(@NotNull GeneralCommandLine cli, @NotNull CommandExecutionSettingsBuilder executionSettingsBuilder) throws VcsException { + return runCommand(cli, executionSettingsBuilder.build()); + } + + private static CommandResult runCommand(@NotNull GeneralCommandLine cli, @NotNull CommandExecutionSettings executionSettings) throws VcsException { + final String command = removePrivateData(cli.getCommandLineString(), executionSettings.privateData()); + Loggers.VCS.debug("Run command: " + command); + CommandResult res = run(cli, executionSettings.timeout(), command, executionSettings.privateData()); + if (executionSettings.shouldCheckForFailure() || executionSettings.shouldFailWithNonEmptyStderr()) + res.checkFailure(executionSettings.shouldFailWithNonEmptyStderr()); + Loggers.VCS.debug("Command " + command + " output:\n" + res.getStdout()); + return res; + } + + private static CommandResult run(@NotNull final GeneralCommandLine cli, final int executionTimeout, @NotNull final String command, @NotNull final Set<String> privateData) { + final long start = System.currentTimeMillis(); + ExecResult res = SimpleCommandLineProcessRunner.runCommand(cli, null, new SimpleCommandLineProcessRunner.RunCommandEventsAdapter() { + @Override + public Integer getOutputIdleSecondsTimeout() { + return executionTimeout; + } + @Override + public void onProcessFinished(Process ps) { + long duration = System.currentTimeMillis() - start; + Loggers.VCS.debug("Command " + command + " took " + duration + "ms"); + } + }); + return new CommandResult(Loggers.VCS, command, res, privateData); + } + + public static String removePrivateData(final String str, final Set<String> privateData) { + String result = str; + for (String data: privateData) { + if (data == null || data.length() == 0) continue; + result = result.replace(data, "******"); + } + return result; + } +}
--- a/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/IdentifyCommand.java Wed Feb 15 12:07:12 2012 +0400 +++ b/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/IdentifyCommand.java Wed Feb 15 13:23:53 2012 +0400 @@ -1,61 +1,71 @@ -/* - * 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.command; - -import com.intellij.execution.configurations.GeneralCommandLine; -import jetbrains.buildServer.vcs.VcsException; -import org.jetbrains.annotations.NotNull; - -import java.io.File; - -/** - * @author Pavel.Sher - * Date: 16.07.2008 - */ -public class IdentifyCommand extends BaseCommand { - - private boolean myInLocalRepository = false; - private ChangeSet myChangeSet; - - public IdentifyCommand(@NotNull Settings settings, @NotNull File workingDir) { - super(settings, workingDir); - } - - public void setInLocalRepository(boolean inLocalRepository) { - myInLocalRepository = inLocalRepository; - } - - public void setChangeSet(ChangeSet changeSet) { - myChangeSet = changeSet; - } - - public String execute() throws VcsException { - GeneralCommandLine cli = createCL(); - cli.addParameter("identify"); - if (myInLocalRepository) { - cli.setWorkDirectory(this.getWorkDirectory()); - } else { - cli.addParameter(getSettings().getRepositoryUrl()); - } - if (myChangeSet != null) { - cli.addParameter("--rev"); - cli.addParameter(myChangeSet.getId()); - } - CommandResult res = runCommand(cli); - failIfNotEmptyStdErr(cli, res); - return res.getStdout(); - } -} +/* + * 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.command; + +import com.intellij.execution.configurations.GeneralCommandLine; +import jetbrains.buildServer.vcs.VcsException; +import org.jetbrains.annotations.NotNull; + +import java.io.File; + +import static jetbrains.buildServer.buildTriggers.vcs.mercurial.command.CommandExecutionSettingsBuilder.with; + +/** + * @author Pavel.Sher + * Date: 16.07.2008 + */ +public class IdentifyCommand extends VcsRootCommand { + + private boolean myInLocalRepository = false; + private ChangeSet myChangeSet; + private Integer myRevisionNumber; + + public IdentifyCommand(@NotNull Settings settings, @NotNull File workingDir) { + super(settings, workingDir); + } + + public void setInLocalRepository(boolean inLocalRepository) { + myInLocalRepository = inLocalRepository; + } + + public void setChangeSet(ChangeSet changeSet) { + myChangeSet = changeSet; + } + + public void setRevisionNumber(int revisionNumber) { + myRevisionNumber = revisionNumber; + } + + public String execute() throws VcsException { + GeneralCommandLine cli = createCL(); + cli.addParameter("identify"); + if (myInLocalRepository) { + cli.setWorkDirectory(this.getWorkDirectory().getAbsolutePath()); + } else { + cli.addParameter(getSettings().getRepositoryUrlWithCredentials()); + } + if (myChangeSet != null) { + cli.addParameter("--rev"); + cli.addParameter(myChangeSet.getId()); + } else if (myRevisionNumber != null) { + cli.addParameter("--rev"); + cli.addParameter(myRevisionNumber.toString()); + } + CommandResult res = runCommand(cli, with().failureWhenStderrNotEmpty()); + String output = res.getStdout().trim(); + return output.contains(" ") ? output.substring(0, output.indexOf(" ")) : output; + } +}
--- a/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/Init.java Wed Feb 15 12:07:12 2012 +0400 +++ b/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/Init.java Wed Feb 15 13:23:53 2012 +0400 @@ -10,7 +10,7 @@ /** * @author dmitry.neverov */ -public class Init extends BaseCommand { +public class Init extends VcsRootCommand { private final String myDefaultPullUrl; @@ -20,7 +20,7 @@ } public void execute() throws VcsException { - new File(getWorkDirectory()).mkdirs(); + getWorkDirectory().mkdirs(); GeneralCommandLine cli = createCommandLine(); cli.addParameter("init"); runCommand(cli);
--- a/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/LogCommand.java Wed Feb 15 12:07:12 2012 +0400 +++ b/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/LogCommand.java Wed Feb 15 13:23:53 2012 +0400 @@ -1,220 +1,255 @@ -/* - * 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.command; - -import com.intellij.execution.configurations.GeneralCommandLine; -import com.intellij.openapi.util.JDOMUtil; -import jetbrains.buildServer.vcs.VcsException; -import org.jdom.Document; -import org.jdom.Element; -import org.jdom.JDOMException; -import org.jetbrains.annotations.NotNull; - -import java.io.File; -import java.io.IOException; -import java.io.UnsupportedEncodingException; -import java.text.ParseException; -import java.text.SimpleDateFormat; -import java.util.*; - -public class LogCommand extends BaseCommand { - - private static final String DATE_FORMAT = "EEE MMM d HH:mm:ss yyyy Z"; - - private String myFromId; - private String myToId; - private Integer myLimit = null; - private String myBranchName; - private final File myTemplate; - - public LogCommand(@NotNull Settings settings, @NotNull File workingDir, @NotNull final File template) { - super(settings, workingDir); - myTemplate = template; - myBranchName = settings.getBranchName(); - } - - public void setFromRevId(String id) { - myFromId = id; - } - - public void setToRevId(String id) { - myToId = id; - } - - public void setLimit(final int limit) { - myLimit = limit; - } - - public void setBranchName(String branchName) { - myBranchName = branchName; - } - - public List<ChangeSet> execute() throws VcsException { - GeneralCommandLine cli = createCommandLine(); - cli.addParameters("--encoding", "UTF-8"); - cli.addParameter("log"); - cli.addParameter("--style=" + myTemplate.getAbsolutePath()); - if (myBranchName != null) { - cli.addParameter("-b"); - cli.addParameter(getSettings().getBranchName()); - } - cli.addParameter("-r"); - String from = myFromId; - if (from == null) from = "0"; - String to = myToId; - if (to == null) to = "tip"; - cli.addParameter(from + ":" + to); - if (myLimit != null) { - cli.addParameter("--limit"); - cli.addParameter(myLimit.toString()); - } - CommandResult res = runCommand(cli); - String output = getStdout(res); - try { - return parseChangeSetsXml(output); - } catch (Exception e) { - throw new VcsException("Error while parsing log output:\n" + output, e); - } - } - - private String getStdout(CommandResult res) throws VcsException { - try { - return new String(res.getByteOut(), "UTF-8"); - } catch (UnsupportedEncodingException e) { - throw new VcsException("Error while reading output", e); - } - } - - - private List<ChangeSet> parseChangeSetsXml(@NotNull final String xml) throws JDOMException, IOException, ParseException { - if ("".equals(xml)) - return Collections.emptyList(); - Document doc = loadDocument(xml); - Element log = doc.getRootElement(); - return parseLog(log); - } - - private Document loadDocument(@NotNull final String xml) throws IOException, JDOMException { - String validXml = makeValidXml(xml); - return JDOMUtil.loadDocument(validXml); - } - - - private String makeValidXml(@NotNull final String xml) { - String trimmed = xml.trim(); - if (trimmed.endsWith("</log>")) - return trimmed; - else - return trimmed + "</log>"; - } - - - private List<ChangeSet> parseLog(@NotNull final Element logElement) throws ParseException { - List<ChangeSet> result = new ArrayList<ChangeSet>(); - for (Object o : logElement.getChildren("logentry")) { - Element entry = (Element) o; - result.add(parseLogEntry(entry)); - } - return result; - } - - - - private ChangeSet parseLogEntry(@NotNull final Element logEntry) throws ParseException { - ChangeSet cset = new ChangeSet(getRevision(logEntry), getId(logEntry)); - addParents(cset, logEntry); - cset.setUser(getAuthor(logEntry)); - cset.setDescription(getDescription(logEntry)); - cset.setTimestamp(getDate(logEntry)); - cset.setModifiedFiles(getModifiedFiles(logEntry)); - return cset; - } - - - private int getRevision(@NotNull final Element logEntry) { - return Integer.parseInt(logEntry.getAttribute("revision").getValue()); - } - - - private String getId(@NotNull final Element logEntry) { - return logEntry.getAttribute("shortnode").getValue(); - } - - - private void addParents(@NotNull final ChangeSet cset, @NotNull final Element logEntry) { - List parents = logEntry.getChildren("parent"); - for (Object p : parents) { - Element parent = (Element) p; - ChangeSetRevision parentCset = getParent(parent); - cset.addParent(parentCset); - } - } - - - private ChangeSetRevision getParent(@NotNull final Element parent) { - return new ChangeSetRevision(getRevision(parent), getId(parent)); - } - - - private String getAuthor(@NotNull final Element logEntry) { - Element author = logEntry.getChild("author"); - return author.getAttribute("original").getValue(); - } - - - private String getDescription(@NotNull final Element logEntry) { - Element msg = logEntry.getChild("msg"); - return msg.getText(); - } - - - private Date getDate(@NotNull final Element logEntry) throws ParseException { - Element date = logEntry.getChild("date"); - return new SimpleDateFormat(DATE_FORMAT, Locale.ENGLISH).parse(date.getText()); - } - - - private List<ModifiedFile> getModifiedFiles(@NotNull final Element logEntry) { - List<ModifiedFile> result = new ArrayList<ModifiedFile>(); - Element paths = logEntry.getChild("paths"); - for (Object o : paths.getChildren("path")) { - Element path = (Element) o; - result.add(getModifiedFile(path)); - } - return result; - } - - - private ModifiedFile getModifiedFile(@NotNull final Element path) { - String filePath = path.getText(); - ModifiedFile.Status status = getStatus(path); - return new ModifiedFile(status, filePath); - } - - - private ModifiedFile.Status getStatus(@NotNull final Element path) { - String action = path.getAttribute("action").getValue(); - if (action.equals("A")) { - return ModifiedFile.Status.ADDED; - } else if (action.equals("M")) { - return ModifiedFile.Status.MODIFIED; - } else if (action.equals("R")) { - return ModifiedFile.Status.REMOVED; - } else { - return ModifiedFile.Status.UNKNOWN; - } - } -} +/* + * 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.command; + +import com.intellij.execution.configurations.GeneralCommandLine; +import com.intellij.openapi.util.JDOMUtil; +import jetbrains.buildServer.vcs.VcsException; +import org.jdom.Document; +import org.jdom.Element; +import org.jdom.JDOMException; +import org.jetbrains.annotations.NotNull; + +import java.io.File; +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.*; + +public class LogCommand extends VcsRootCommand { + + private final static String ZERO_PARENT_ID = "0000000000000000000000000000000000000000"; + private static final String DATE_FORMAT = "EEE MMM d HH:mm:ss yyyy Z"; + + private String myFromId; + private String myToId; + private Integer myLimit = null; + private String myBranchName; + private boolean myCalculateParents = true; + private String myRevsets; + private final File myTemplate; + + public LogCommand(@NotNull Settings settings, @NotNull File workingDir, @NotNull final File template) { + super(settings, workingDir); + myTemplate = template; + myBranchName = settings.getBranchName(); + } + + public void setFromRevId(String id) { + myFromId = id; + } + + public void setToRevId(String id) { + myToId = id; + } + + public void setLimit(final int limit) { + myLimit = limit; + } + + public void showCommitsFromAllBranches() { + myBranchName = null; + } + + public void setCalculateParents(boolean doCalculate) { + myCalculateParents = doCalculate; + } + + public void setRevsets(String revsets) { + myRevsets = revsets; + } + + public List<ChangeSet> execute() throws VcsException { + GeneralCommandLine cli = createCommandLine(); + cli.addParameters("--encoding", "UTF-8"); + cli.addParameter("log"); + cli.addParameter("-v"); + cli.addParameter("--style=" + myTemplate.getAbsolutePath()); + if (myBranchName != null) { + cli.addParameter("-b"); + cli.addParameter(getSettings().getBranchName()); + } + cli.addParameter("-r"); + if (myRevsets != null) { + cli.addParameter(myRevsets); + } else { + String from = myFromId != null ? myFromId : "0"; + String to = myToId != null ? myToId : "tip"; + cli.addParameter(from + ":" + to); + } + if (myLimit != null) { + cli.addParameter("--limit"); + cli.addParameter(myLimit.toString()); + } + + CommandResult res = runCommand(cli); + String output = getStdout(res); + try { + List<ChangeSet> changes = parseChangeSetsXml(output); + if (myCalculateParents) + assignTrivialParents(changes); + return changes; + } catch (Exception e) { + throw new VcsException("Error while parsing log output:\n" + output, e); + } + } + + private String getStdout(CommandResult res) throws VcsException { + try { + return new String(res.getByteOut(), "UTF-8"); + } catch (UnsupportedEncodingException e) { + throw new VcsException("Error while reading output", e); + } + } + + + private List<ChangeSet> parseChangeSetsXml(@NotNull final String xml) throws JDOMException, IOException, ParseException { + if ("".equals(xml)) + return Collections.emptyList(); + Document doc = loadDocument(xml); + Element log = doc.getRootElement(); + return parseLog(log); + } + + private Document loadDocument(@NotNull final String xml) throws IOException, JDOMException { + String validXml = makeValidXml(xml); + return JDOMUtil.loadDocument(validXml); + } + + private String makeValidXml(@NotNull final String xml) { + String trimmed = xml.trim(); + if (trimmed.endsWith("</log>")) + return xml; + else + return xml + "</log>"; + } + + + private List<ChangeSet> parseLog(@NotNull final Element logElement) throws ParseException { + List<ChangeSet> result = new ArrayList<ChangeSet>(); + for (Object o : logElement.getChildren("logentry")) { + Element entry = (Element) o; + result.add(parseLogEntry(entry)); + } + return result; + } + + + private ChangeSet parseLogEntry(@NotNull final Element logEntry) throws ParseException { + ChangeSet cset = new ChangeSet(getRevision(logEntry), getId(logEntry)); + addParents(cset, logEntry); + cset.setUser(getAuthor(logEntry)); + cset.setDescription(getDescription(logEntry)); + cset.setTimestamp(getDate(logEntry)); + cset.setModifiedFiles(getModifiedFiles(logEntry)); + return cset; + } + + + private int getRevision(@NotNull final Element logEntry) { + return Integer.parseInt(logEntry.getAttribute("revision").getValue()); + } + + + private String getId(@NotNull final Element logEntry) { + return logEntry.getAttribute("shortnode").getValue(); + } + + + private void addParents(@NotNull final ChangeSet cset, @NotNull final Element logEntry) { + List parents = logEntry.getChildren("parent"); + for (Object p : parents) { + Element parent = (Element) p; + ChangeSetRevision parentCset = getParent(parent); + cset.addParent(parentCset); + } + } + + + private ChangeSetRevision getParent(@NotNull final Element parent) { + return new ChangeSetRevision(getRevision(parent), getId(parent)); + } + + + private String getAuthor(@NotNull final Element logEntry) { + Element author = logEntry.getChild("author"); + return author.getAttribute("original").getValue(); + } + + + private String getDescription(@NotNull final Element logEntry) { + Element msg = logEntry.getChild("msg"); + return msg.getText(); + } + + + private Date getDate(@NotNull final Element logEntry) throws ParseException { + Element date = logEntry.getChild("date"); + return new SimpleDateFormat(DATE_FORMAT, Locale.ENGLISH).parse(date.getText()); + } + + + private List<ModifiedFile> getModifiedFiles(@NotNull final Element logEntry) { + List<ModifiedFile> result = new ArrayList<ModifiedFile>(); + Element paths = logEntry.getChild("paths"); + for (Object o : paths.getChildren("path")) { + Element path = (Element) o; + result.add(getModifiedFile(path)); + } + return result; + } + + + private ModifiedFile getModifiedFile(@NotNull final Element path) { + String filePath = path.getText(); + ModifiedFile.Status status = getStatus(path); + return new ModifiedFile(status, filePath); + } + + + private ModifiedFile.Status getStatus(@NotNull final Element path) { + String action = path.getAttribute("action").getValue(); + if (action.equals("A")) { + return ModifiedFile.Status.ADDED; + } else if (action.equals("M")) { + return ModifiedFile.Status.MODIFIED; + } else if (action.equals("R")) { + return ModifiedFile.Status.REMOVED; + } else { + return ModifiedFile.Status.UNKNOWN; + } + } + + private void assignTrivialParents(final @NotNull List<ChangeSet> csets) throws VcsException { + for (ChangeSet cset : csets) { + if (cset.getParents().isEmpty()) { + int parentRevNumber = cset.getRevNumber() - 1; + String parentId = getIdOf(parentRevNumber); + cset.addParent(new ChangeSetRevision(parentRevNumber, parentId)); + } + } + } + + private String getIdOf(int revNumber) throws VcsException { + if (revNumber < 0) + return ZERO_PARENT_ID; + IdentifyCommand identify = new IdentifyCommand(getSettings(), getWorkDirectory()); + identify.setInLocalRepository(true); + identify.setRevisionNumber(revNumber); + return identify.execute(); + } +}
--- a/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/MercurialCommandLine.java Wed Feb 15 12:07:12 2012 +0400 +++ b/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/MercurialCommandLine.java Wed Feb 15 13:23:53 2012 +0400 @@ -4,12 +4,17 @@ import jetbrains.buildServer.util.StringUtil; import org.jetbrains.annotations.NotNull; +import java.util.Collections; import java.util.Set; public class MercurialCommandLine extends GeneralCommandLine { private final Set<String> myPrivateData; + public MercurialCommandLine() { + this(Collections.<String>emptySet()); + } + public MercurialCommandLine(@NotNull Set<String> privateData) { myPrivateData = privateData; }
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/MergeBaseCommand.java Wed Feb 15 13:23:53 2012 +0400 @@ -0,0 +1,25 @@ +package jetbrains.buildServer.buildTriggers.vcs.mercurial.command; + +import jetbrains.buildServer.vcs.VcsException; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Analog of git merge-base. It returns a last common ancestor between two revisions. + * + * @author dmitry.neverov + */ +public interface MergeBaseCommand { + + /** + * Returns hash of least common ancestor between two revisions or null + * if common ancestor is not found + * @param revision1 first revision + * @param revision2 second revision + * @return see above + * @throws VcsException if some commands fail + */ + @Nullable + String execute(@NotNull String revision1, @NotNull String revision2) throws VcsException; + +}
--- a/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/ModifiedFile.java Wed Feb 15 12:07:12 2012 +0400 +++ b/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/ModifiedFile.java Wed Feb 15 13:23:53 2012 +0400 @@ -1,86 +1,86 @@ -/* - * 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.command; - -import org.jetbrains.annotations.NotNull; - -/** - * Represents repository modified file - */ -public class ModifiedFile { - /** - * Type of modification - */ - public static enum Status { - ADDED("added"), - MODIFIED("modified"), - REMOVED("removed"), - UNKNOWN("unknown"); - private String myName; - - Status(@NotNull final String name) { - myName = name; - } - - @NotNull - public String getName() { - return myName; - } - } - - @NotNull private Status myStatus; - @NotNull private String myPath; - - public ModifiedFile(@NotNull final Status status, @NotNull final String path) { - myStatus = status; - myPath = path; - } - - /** - * Returns type of modification - * @return type of modification - */ - @NotNull - public Status getStatus() { - return myStatus; - } - - /** - * Returns file path - * @return file path - */ - @NotNull - public String getPath() { - return myPath; - } - - @Override - public boolean equals(final Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - - final ModifiedFile that = (ModifiedFile) o; - - return myPath.equals(that.myPath) && myStatus == that.myStatus; - } - - @Override - public int hashCode() { - int result = myStatus.hashCode(); - result = 31 * result + myPath.hashCode(); - return result; - } -} +/* + * 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.command; + +import org.jetbrains.annotations.NotNull; + +/** + * Represents repository modified file + */ +public class ModifiedFile { + /** + * Type of modification + */ + public static enum Status { + ADDED("added"), + MODIFIED("modified"), + REMOVED("removed"), + UNKNOWN("unknown"); + private String myName; + + Status(@NotNull final String name) { + myName = name; + } + + @NotNull + public String getName() { + return myName; + } + } + + @NotNull private Status myStatus; + @NotNull private String myPath; + + public ModifiedFile(@NotNull final Status status, @NotNull final String path) { + myStatus = status; + myPath = path; + } + + /** + * Returns type of modification + * @return type of modification + */ + @NotNull + public Status getStatus() { + return myStatus; + } + + /** + * Returns file path + * @return file path + */ + @NotNull + public String getPath() { + return myPath; + } + + @Override + public boolean equals(final Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + final ModifiedFile that = (ModifiedFile) o; + + return myPath.equals(that.myPath) && myStatus == that.myStatus; + } + + @Override + public int hashCode() { + int result = myStatus.hashCode(); + result = 31 * result + myPath.hashCode(); + return result; + } +}
--- a/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/PullCommand.java Wed Feb 15 12:07:12 2012 +0400 +++ b/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/PullCommand.java Wed Feb 15 13:23:53 2012 +0400 @@ -1,47 +1,62 @@ -/* - * 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.command; - -import com.intellij.execution.configurations.GeneralCommandLine; -import jetbrains.buildServer.vcs.VcsException; -import org.jetbrains.annotations.NotNull; - -import java.io.File; - -/** - * @author Pavel.Sher - * Date: 14.07.2008 - */ -public class PullCommand extends BaseCommand { - - private final String myPullUrl; - - public PullCommand(@NotNull Settings settings, @NotNull File workingDir) { - this(settings, workingDir, settings.getRepository()); - } - - public PullCommand(@NotNull Settings settings, @NotNull File workingDir, @NotNull String pullUrl) { - super(settings, workingDir); - myPullUrl = pullUrl; - } - - public void execute(int timeout) throws VcsException { - GeneralCommandLine cli = createCommandLine(); - cli.addParameter("pull"); - cli.addParameter(myPullUrl); - runCommand(cli, timeout); - } -} +/* + * 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.command; + +import com.intellij.execution.configurations.GeneralCommandLine; +import jetbrains.buildServer.vcs.VcsException; +import org.jetbrains.annotations.NotNull; + +import java.io.File; + +import static com.intellij.openapi.util.io.FileUtil.delete; +import static jetbrains.buildServer.buildTriggers.vcs.mercurial.command.CommandExecutionSettingsBuilder.with; + +/** + * @author Pavel.Sher + * Date: 14.07.2008 + */ +public class PullCommand extends VcsRootCommand { + + private final String myPullUrl; + + public PullCommand(@NotNull Settings settings, @NotNull File workingDir) { + this(settings, workingDir, settings.getRepositoryUrlWithCredentials()); + } + + public PullCommand(@NotNull Settings settings, @NotNull File workingDir, @NotNull String pullUrl) { + super(settings, workingDir); + myPullUrl = pullUrl; + } + + public void execute(int timeout) throws VcsException { + ensureRepositoryIsNotLocked(); + GeneralCommandLine cli = createCommandLine(); + cli.addParameter("pull"); + cli.addParameter(myPullUrl); + runCommand(cli, with().timeout(timeout)); + } + + private void ensureRepositoryIsNotLocked() { + File lock = getRepositoryLock(); + if (lock.exists()) + delete(lock); + } + + @NotNull + private File getRepositoryLock() { + return new File(getWorkDirectory(), ".hg" + File.separator + "store" + File.separator + "lock"); + } +}
--- a/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/PushCommand.java Wed Feb 15 12:07:12 2012 +0400 +++ b/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/PushCommand.java Wed Feb 15 13:23:53 2012 +0400 @@ -1,48 +1,49 @@ -/* - * 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.command; - -import com.intellij.execution.configurations.GeneralCommandLine; -import jetbrains.buildServer.vcs.VcsException; -import org.jetbrains.annotations.NotNull; - -import java.io.File; - -/** - * @author pavel - */ -public class PushCommand extends BaseCommand { - private boolean myForced; - - public PushCommand(@NotNull Settings settings, @NotNull File workingDir) { - super(settings, workingDir); - } - - public void setForce(boolean force) { - myForced = force; - } - - public void execute() throws VcsException { - GeneralCommandLine cli = createCommandLine(); - cli.addParameter("push"); - if (myForced) { - cli.addParameter("-f"); - } - cli.addParameter(getSettings().getRepositoryUrl()); - CommandResult res = runCommand(cli); - failIfNotEmptyStdErr(cli, res); - } -} +/* + * 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.command; + +import com.intellij.execution.configurations.GeneralCommandLine; +import jetbrains.buildServer.vcs.VcsException; +import org.jetbrains.annotations.NotNull; + +import java.io.File; + +import static jetbrains.buildServer.buildTriggers.vcs.mercurial.command.CommandExecutionSettingsBuilder.with; + +/** + * @author pavel + */ +public class PushCommand extends VcsRootCommand { + private boolean myForced; + + public PushCommand(@NotNull Settings settings, @NotNull File workingDir) { + super(settings, workingDir); + } + + public void setForce(boolean force) { + myForced = force; + } + + public void execute() throws VcsException { + GeneralCommandLine cli = createCommandLine(); + cli.addParameter("push"); + if (myForced) { + cli.addParameter("-f"); + } + cli.addParameter(getSettings().getRepositoryUrlWithCredentials()); + runCommand(cli, with().failureWhenStderrNotEmpty()); + } +}
--- a/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/Settings.java Wed Feb 15 12:07:12 2012 +0400 +++ b/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/Settings.java Wed Feb 15 13:23:53 2012 +0400 @@ -1,216 +1,230 @@ -/* - * 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.command; - -import jetbrains.buildServer.buildTriggers.vcs.mercurial.Constants; -import jetbrains.buildServer.buildTriggers.vcs.mercurial.PathUtil; -import jetbrains.buildServer.log.Loggers; -import jetbrains.buildServer.util.StringUtil; -import jetbrains.buildServer.vcs.VcsRoot; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.io.File; -import java.io.IOException; -import java.net.*; -import java.util.HashSet; -import java.util.Set; - -/** - * Represents Mercurial repository settings - */ -public class Settings { - private String myRepository; - private String myHgCommandPath; - private File myCustomWorkingDir; - private String myUsername; - private String myPassword; - private String myBranchName; - private boolean myUncompressedTransfer = false; - private static final String DEFAULT_BRANCH_NAME = "default"; - private String myCustomClonePath; - - public Settings(@NotNull VcsRoot vcsRoot) { - myRepository = vcsRoot.getProperty(Constants.REPOSITORY_PROP); - myHgCommandPath = vcsRoot.getProperty(Constants.HG_COMMAND_PATH_PROP); - myBranchName = vcsRoot.getProperty(Constants.BRANCH_NAME_PROP); - myCustomClonePath = vcsRoot.getProperty(Constants.SERVER_CLONE_PATH_PROP); - myUsername = vcsRoot.getProperty(Constants.USERNAME); - myPassword = vcsRoot.getProperty(Constants.PASSWORD); - myUncompressedTransfer = "true".equals(vcsRoot.getProperty(Constants.UNCOMPRESSED_TRANSFER)); - } - - public String getCustomClonePath() { - return myCustomClonePath; - } - - public String getRepository() { - return myRepository; - } - - /** - * Returns name of the branch to use (returns 'default' if no branch specified) - * @return see above - */ - @NotNull - public String getBranchName() { - return StringUtil.isEmpty(myBranchName) ? DEFAULT_BRANCH_NAME : myBranchName; - } - - /** - * Returns true if current branch is default branch - * @return see above - */ - public boolean isDefaultBranch() { - return getBranchName().equals(DEFAULT_BRANCH_NAME); - } - - public boolean isUncompressedTransfer() { - return myUncompressedTransfer; - } - - /** - * Returns path to hg command - * @return path to hg command - */ - @NotNull - public String getHgCommandPath() { - return myHgCommandPath; - } - - public String getUsername() { - return myUsername; - } - - public String getPassword() { - return myPassword; - } - - private final static Set<String> AUTH_PROTOS = new HashSet<String>(); - static { - AUTH_PROTOS.add("http"); - AUTH_PROTOS.add("https"); - AUTH_PROTOS.add("ssh"); - } - - /** - * Returns URL to use for push command - * @return URL to use for push command - */ - public String getRepositoryUrl() { - if (isRequireCredentials()) { - if (containsCredentials(myRepository)) return myRepository; - try { - return createURLWithCredentials(myRepository); - } catch (MalformedURLException e) { - Loggers.VCS.warn("Error while parsing url " + myRepository, e); - } - return myRepository; - } else { - return myRepository; - } - } - - private boolean containsCredentials(final String repository) { - try { - URL url = new URL(null, repository, new FakeStreamHandler()); - String userInfo = url.getUserInfo(); - return userInfo != null && userInfo.contains(":"); - } catch (MalformedURLException e) { - return false; - } - } - - private String createURLWithCredentials(final String originalUrl) throws MalformedURLException { - String userInfo = createUserInfo(); - if (!"".equals(userInfo)) { - URL url = new URL(null, originalUrl, new FakeStreamHandler()); - return url.getProtocol() + "://" - + userInfo + "@" - + url.getHost() - + (url.getPort() != -1 ? ":" + url.getPort() : "") - + url.getFile() - + (url.getRef() != null ? url.getRef() : ""); - } else { - return originalUrl; - } - } - - private boolean isRequireCredentials() { - for (String scheme : AUTH_PROTOS) { - if (myRepository.startsWith(scheme + ":")) { - return true; - } - } - return false; - } - - private String createUserInfo() { - String userInfo = ""; - if (!StringUtil.isEmpty(myUsername)) { - userInfo += myUsername; - if (!StringUtil.isEmpty(myPassword)) { - userInfo += ":" + myPassword; - } - } - return getEscapedUserInfo(userInfo); - } - - private static String getEscapedUserInfo(String userInfo) { - try { - URI uri = new URI("http", userInfo, "somewhere.com", 80, "", "", ""); - String escapedURI = uri.toASCIIString(); - int from = "http://".length(); - int to = escapedURI.indexOf("somewhere.com") - 1; - String escapedUserInfo = escapedURI.substring(from, to); - escapedUserInfo = escapedUserInfo.replaceAll("&", "%26"); - return escapedUserInfo; - } catch (URISyntaxException e) { - assert false; - } - return userInfo; - } - - /** - * Set custom working dir for vcs root. This option make sence only for server-side checkout - * @param customWorkingDir custom working dir - */ - public void setCustomWorkingDir(@NotNull final File customWorkingDir) { - myCustomWorkingDir = PathUtil.getCanonicalFile(customWorkingDir); - } - - /** - * Returns custom working dir for root or null if default working dir should be used. - * This options make sence only with server-side checkout. - * @return see above - */ - @Nullable - public File getCustomWorkingDir() { - return myCustomWorkingDir; - } - - public static boolean isValidRepository(File dir) { - // need better way to check that repository copy is ok - return dir.isDirectory() && new File(dir, ".hg").isDirectory(); - } - - private class FakeStreamHandler extends URLStreamHandler { - @Override - protected URLConnection openConnection(URL u) throws IOException { - throw new UnsupportedOperationException(); - } - } -} +/* + * 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.command; + +import jetbrains.buildServer.buildTriggers.vcs.mercurial.Constants; +import jetbrains.buildServer.buildTriggers.vcs.mercurial.HgPathProvider; +import jetbrains.buildServer.buildTriggers.vcs.mercurial.PathUtil; +import jetbrains.buildServer.log.Loggers; +import jetbrains.buildServer.util.StringUtil; +import jetbrains.buildServer.vcs.VcsRoot; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.io.File; +import java.io.IOException; +import java.net.*; +import java.util.HashSet; +import java.util.Set; + +/** + * Represents Mercurial repository settings + */ +public class Settings { + + private final HgPathProvider myHgPathProvider; + private String myRepository; + private String myHgCommandPath; + private File myCustomWorkingDir; + private String myUsername; + private String myPassword; + private String myBranchName; + private boolean myUncompressedTransfer = false; + private static final String DEFAULT_BRANCH_NAME = "default"; + private String myCustomClonePath; + private final String myUserForTag; + + public Settings(@NotNull final HgPathProvider hgPathProvider, @NotNull final VcsRoot vcsRoot) { + myHgPathProvider = hgPathProvider; + myRepository = vcsRoot.getProperty(Constants.REPOSITORY_PROP); + myHgCommandPath = vcsRoot.getProperty(Constants.HG_COMMAND_PATH_PROP); + myBranchName = vcsRoot.getProperty(Constants.BRANCH_NAME_PROP); + myCustomClonePath = vcsRoot.getProperty(Constants.SERVER_CLONE_PATH_PROP); + myUsername = vcsRoot.getProperty(Constants.USERNAME); + myPassword = vcsRoot.getProperty(Constants.PASSWORD); + myUncompressedTransfer = "true".equals(vcsRoot.getProperty(Constants.UNCOMPRESSED_TRANSFER)); + myUserForTag = vcsRoot.getProperty(Constants.USER_FOR_TAG); + } + + public String getCustomClonePath() { + return myCustomClonePath; + } + + public String getRepository() { + return myRepository; + } + + /** + * Returns name of the branch to use (returns 'default' if no branch specified) + * @return see above + */ + @NotNull + public String getBranchName() { + return StringUtil.isEmpty(myBranchName) ? DEFAULT_BRANCH_NAME : myBranchName; + } + + /** + * Returns true if current branch is default branch + * @return see above + */ + public boolean isDefaultBranch() { + return getBranchName().equals(DEFAULT_BRANCH_NAME); + } + + public boolean isUncompressedTransfer() { + return myUncompressedTransfer; + } + + /** + * @return path to hg command taking into account server-wide/agent-wide settings + */ + @NotNull + public String getHgCommandPath() { + return myHgPathProvider.getHgPath(this); + } + + /** + * @return path to hg command as it is set in VCS root settings + */ + public String getHgPath() { + return myHgCommandPath; + } + + public String getUsername() { + return myUsername; + } + + public String getPassword() { + return myPassword; + } + + @Nullable + public String getUserForTag() { + return myUserForTag; + } + + private final static Set<String> AUTH_PROTOS = new HashSet<String>(); + static { + AUTH_PROTOS.add("http"); + AUTH_PROTOS.add("https"); + AUTH_PROTOS.add("ssh"); + } + + public String getRepositoryUrlWithCredentials() { + if (isRequireCredentials()) { + if (containsCredentials(myRepository)) + return myRepository; + try { + return createURLWithCredentials(myRepository); + } catch (MalformedURLException e) { + Loggers.VCS.warn("Error while parsing url " + myRepository, e); + } + return myRepository; + } else { + return myRepository; + } + } + + private boolean containsCredentials(final String repository) { + try { + URL url = new URL(null, repository, new FakeStreamHandler()); + String userInfo = url.getUserInfo(); + return userInfo != null && userInfo.contains(":"); + } catch (MalformedURLException e) { + return false; + } + } + + private String createURLWithCredentials(final String originalUrl) throws MalformedURLException { + String userInfo = createUserInfo(); + if (!"".equals(userInfo)) { + URL url = new URL(null, originalUrl, new FakeStreamHandler()); + return url.getProtocol() + "://" + + userInfo + "@" + + url.getHost() + + (url.getPort() != -1 ? ":" + url.getPort() : "") + + url.getFile() + + (url.getRef() != null ? url.getRef() : ""); + } else { + return originalUrl; + } + } + + private boolean isRequireCredentials() { + for (String scheme : AUTH_PROTOS) { + if (myRepository.startsWith(scheme + ":")) { + return true; + } + } + return false; + } + + private String createUserInfo() { + String userInfo = ""; + if (!StringUtil.isEmpty(myUsername)) { + userInfo += myUsername; + if (!StringUtil.isEmpty(myPassword)) { + userInfo += ":" + myPassword; + } + } + return getEscapedUserInfo(userInfo); + } + + private static String getEscapedUserInfo(String userInfo) { + try { + URI uri = new URI("http", userInfo, "somewhere.com", 80, "", "", ""); + String escapedURI = uri.toASCIIString(); + int from = "http://".length(); + int to = escapedURI.indexOf("somewhere.com") - 1; + String escapedUserInfo = escapedURI.substring(from, to); + escapedUserInfo = escapedUserInfo.replaceAll("&", "%26"); + return escapedUserInfo; + } catch (URISyntaxException e) { + assert false; + } + return userInfo; + } + + /** + * Set custom working dir for vcs root. This option make sence only for server-side checkout + * @param customWorkingDir custom working dir + */ + public void setCustomWorkingDir(@NotNull final File customWorkingDir) { + myCustomWorkingDir = PathUtil.getCanonicalFile(customWorkingDir); + } + + /** + * Returns custom working dir for root or null if default working dir should be used. + * This options make sence only with server-side checkout. + * @return see above + */ + @Nullable + public File getCustomWorkingDir() { + return myCustomWorkingDir; + } + + public static boolean isValidRepository(File dir) { + // need better way to check that repository copy is ok + return dir.isDirectory() && new File(dir, ".hg").isDirectory(); + } + + private class FakeStreamHandler extends URLStreamHandler { + @Override + protected URLConnection openConnection(URL u) throws IOException { + throw new UnsupportedOperationException(); + } + } +}
--- a/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/StatusCommand.java Wed Feb 15 12:07:12 2012 +0400 +++ b/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/StatusCommand.java Wed Feb 15 13:23:53 2012 +0400 @@ -1,77 +1,77 @@ -/* - * 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.command; - -import com.intellij.execution.configurations.GeneralCommandLine; -import jetbrains.buildServer.vcs.VcsException; -import org.jetbrains.annotations.NotNull; - -import java.io.File; -import java.util.ArrayList; -import java.util.List; - -public class StatusCommand extends BaseCommand { - private String myFromId; - private String myToId; - - public StatusCommand(@NotNull Settings settings, @NotNull File workingDir) { - super(settings, workingDir); - } - - public void setFromRevId(final String fromId) { - myFromId = fromId; - } - - public void setToRevId(final String toId) { - myToId = toId; - } - - public List<ModifiedFile> execute() throws VcsException { - GeneralCommandLine cli = createCommandLine(); - cli.addParameter("status"); - cli.addParameter("--rev"); - String from = myFromId; - if (from == null) from = "0"; - String to = myToId; - if (to == null) to = "0"; - cli.addParameter(from + ":" + to); - CommandResult res = runCommand(cli); - return parseFiles(res.getStdout()); - } - - public static List<ModifiedFile> parseFiles(final String stdout) { - List<ModifiedFile> result = new ArrayList<ModifiedFile>(); - String[] lines = stdout.split("\n"); - for (String line: lines) { - if (line.length() == 0) continue; - char modifier = line.charAt(0); - String path = line.substring(2); - ModifiedFile.Status status = toStatus(modifier); - if (status == ModifiedFile.Status.UNKNOWN) continue; - result.add(new ModifiedFile(status, path)); - } - return result; - } - - public static ModifiedFile.Status toStatus(final char modifier) { - switch (modifier) { - case 'A': return ModifiedFile.Status.ADDED; - case 'M': return ModifiedFile.Status.MODIFIED; - case 'R': return ModifiedFile.Status.REMOVED; - default: return ModifiedFile.Status.UNKNOWN; - } - } -} +/* + * 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.command; + +import com.intellij.execution.configurations.GeneralCommandLine; +import jetbrains.buildServer.vcs.VcsException; +import org.jetbrains.annotations.NotNull; + +import java.io.File; +import java.util.ArrayList; +import java.util.List; + +public class StatusCommand extends VcsRootCommand { + private String myFromId; + private String myToId; + + public StatusCommand(@NotNull Settings settings, @NotNull File workingDir) { + super(settings, workingDir); + } + + public void setFromRevId(final String fromId) { + myFromId = fromId; + } + + public void setToRevId(final String toId) { + myToId = toId; + } + + public List<ModifiedFile> execute() throws VcsException { + GeneralCommandLine cli = createCommandLine(); + cli.addParameter("status"); + cli.addParameter("--rev"); + String from = myFromId; + if (from == null) from = "0"; + String to = myToId; + if (to == null) to = "0"; + cli.addParameter(from + ":" + to); + CommandResult res = runCommand(cli); + return parseFiles(res.getStdout()); + } + + public static List<ModifiedFile> parseFiles(final String stdout) { + List<ModifiedFile> result = new ArrayList<ModifiedFile>(); + String[] lines = stdout.split("\n"); + for (String line: lines) { + if (line.length() == 0) continue; + char modifier = line.charAt(0); + String path = line.substring(2); + ModifiedFile.Status status = toStatus(modifier); + if (status == ModifiedFile.Status.UNKNOWN) continue; + result.add(new ModifiedFile(status, path)); + } + return result; + } + + public static ModifiedFile.Status toStatus(final char modifier) { + switch (modifier) { + case 'A': return ModifiedFile.Status.ADDED; + case 'M': return ModifiedFile.Status.MODIFIED; + case 'R': return ModifiedFile.Status.REMOVED; + default: return ModifiedFile.Status.UNKNOWN; + } + } +}
--- a/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/TagCommand.java Wed Feb 15 12:07:12 2012 +0400 +++ b/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/TagCommand.java Wed Feb 15 13:23:53 2012 +0400 @@ -1,48 +1,59 @@ -/* - * 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.command; - -import com.intellij.execution.configurations.GeneralCommandLine; -import jetbrains.buildServer.vcs.VcsException; -import org.jetbrains.annotations.NotNull; - -import java.io.File; - -public class TagCommand extends BaseCommand { - private String myTag; - private String myRevId; - - public TagCommand(@NotNull Settings settings, @NotNull File workingDir) { - super(settings, workingDir); - } - - public void setTag(@NotNull final String tag) { - myTag = tag; - } - - public void setRevId(@NotNull final String revId) { - myRevId = revId; - } - - public void execute() throws VcsException { - GeneralCommandLine cli = createCommandLine(); - cli.addParameter("tag"); - cli.addParameter("-r"); - cli.addParameter(myRevId); - cli.addParameter(myTag); - runCommand(cli); - } -} +/* + * 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.command; + +import com.intellij.execution.configurations.GeneralCommandLine; +import jetbrains.buildServer.vcs.VcsException; +import org.jetbrains.annotations.NotNull; + +import java.io.File; + +public class TagCommand extends VcsRootCommand { + private String myTag; + private String myRevId; + private String myUsername; + + public TagCommand(@NotNull Settings settings, @NotNull File workingDir) { + super(settings, workingDir); + } + + public void setTag(@NotNull final String tag) { + myTag = tag; + } + + public void setRevId(@NotNull final String revId) { + myRevId = revId; + } + + public void setUser(@NotNull final String username) { + myUsername = username; + } + + public void execute() throws VcsException { + GeneralCommandLine cli = createCommandLine(); + cli.addParameter("tag"); + setUser(cli); + cli.addParameter("-r"); + cli.addParameter(myRevId); + cli.addParameter(myTag); + runCommand(cli); + } + + private void setUser(GeneralCommandLine cli) { + if (myUsername != null) + cli.addParameters("--user", myUsername); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/UnknownFileException.java Wed Feb 15 13:23:53 2012 +0400 @@ -0,0 +1,22 @@ +package jetbrains.buildServer.buildTriggers.vcs.mercurial.command; + +import jetbrains.buildServer.vcs.VcsException; +import org.jetbrains.annotations.NotNull; + +/** + * @author dmitry.neverov + */ +public class UnknownFileException extends VcsException { + + private final String myPath; + + public UnknownFileException(@NotNull String path) { + super("Unknown file " + path); + myPath = path; + } + + public String getPath() { + return myPath; + } + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/UnknownRevisionException.java Wed Feb 15 13:23:53 2012 +0400 @@ -0,0 +1,23 @@ +package jetbrains.buildServer.buildTriggers.vcs.mercurial.command; + +import jetbrains.buildServer.vcs.VcsException; +import org.jetbrains.annotations.NotNull; + +/** + * @author dmitry.neverov + */ +public class UnknownRevisionException extends VcsException { + + private final String myRevision; + + public UnknownRevisionException(@NotNull final String revision) { + super("Unknown revision " + revision); + myRevision = revision; + } + + @NotNull + public String getRevision() { + return myRevision; + } + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/UnrelatedRepositoryException.java Wed Feb 15 13:23:53 2012 +0400 @@ -0,0 +1,10 @@ +package jetbrains.buildServer.buildTriggers.vcs.mercurial.command; + +import jetbrains.buildServer.vcs.VcsException; + +/** + * @author dmitry.neverov + */ +public class UnrelatedRepositoryException extends VcsException { + +}
--- a/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/UpdateCommand.java Wed Feb 15 12:07:12 2012 +0400 +++ b/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/UpdateCommand.java Wed Feb 15 13:23:53 2012 +0400 @@ -1,50 +1,81 @@ -/* - * 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.command; - -import com.intellij.execution.configurations.GeneralCommandLine; -import jetbrains.buildServer.vcs.VcsException; -import org.jetbrains.annotations.NotNull; - -import java.io.File; - -public class UpdateCommand extends BaseCommand { - - private static final int UPDATE_TIMEOUT_SECONDS = 8 * 3600;//8 hours - - private String myToId; - - public UpdateCommand(@NotNull Settings settings, @NotNull File workingDir) { - super(settings, workingDir); - } - - public void setToId(final String toId) { - myToId = toId; - } - - public void execute() throws VcsException { - GeneralCommandLine cli = createCommandLine(); - cli.addParameter("update"); - cli.addParameter("-C"); - cli.addParameter("-r"); - if (myToId != null) { - cli.addParameter(myToId); - } else { - cli.addParameter(getSettings().getBranchName()); - } - runCommand(cli, UPDATE_TIMEOUT_SECONDS); - } -} +/* + * 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.command; + +import com.intellij.execution.configurations.GeneralCommandLine; +import jetbrains.buildServer.vcs.VcsException; +import org.jetbrains.annotations.NotNull; + +import java.io.File; +import java.util.HashMap; +import java.util.Map; + +import static com.intellij.openapi.util.io.FileUtil.delete; +import static jetbrains.buildServer.buildTriggers.vcs.mercurial.command.CommandExecutionSettingsBuilder.with; + +public class UpdateCommand extends VcsRootCommand { + + private static final int UPDATE_TIMEOUT_SECONDS = 8 * 3600;//8 hours + + private String myToId; + private final Map<String, String> myConfigParams = new HashMap<String, String>(); + + public UpdateCommand(@NotNull Settings settings, @NotNull File workingDir) { + super(settings, workingDir); + } + + public void setToId(final String toId) { + myToId = toId; + } + + public UpdateCommand withConfig(@NotNull String paramName, @NotNull String paramValue) { + myConfigParams.put(paramName, paramValue); + return this; + } + + public void execute() throws VcsException { + ensureWorkingDirIsNotLocked(); + GeneralCommandLine cli = createCommandLine(); + cli.addParameter("update"); + addConfigParams(cli); + cli.addParameter("-C"); + cli.addParameter("-r"); + if (myToId != null) { + cli.addParameter(myToId); + } else { + cli.addParameter(getSettings().getBranchName()); + } + runCommand(cli, with().timeout(UPDATE_TIMEOUT_SECONDS)); + } + + private void ensureWorkingDirIsNotLocked() { + File lock = getWorkingDirLock(); + if (lock.exists()) + delete(lock); + } + + private void addConfigParams(GeneralCommandLine cmd) { + for (Map.Entry<String, String> entry : myConfigParams.entrySet()) { + cmd.addParameter("--config"); + cmd.addParameter(entry.getKey() + "=" + entry.getValue()); + } + } + + @NotNull + private File getWorkingDirLock() { + return new File(getWorkDirectory(), ".hg" + File.separator + "wlock"); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/VcsRootCommand.java Wed Feb 15 13:23:53 2012 +0400 @@ -0,0 +1,42 @@ +package jetbrains.buildServer.buildTriggers.vcs.mercurial.command; + +import com.intellij.execution.configurations.GeneralCommandLine; +import jetbrains.buildServer.vcs.VcsException; +import org.jetbrains.annotations.NotNull; + +import java.io.File; +import java.util.Collections; +import java.util.Set; + +import static jetbrains.buildServer.buildTriggers.vcs.mercurial.command.CommandExecutionSettingsBuilder.with; + +/** + * @author dmitry.neverov + */ +public class VcsRootCommand extends BaseCommand { + + private final Settings mySettings; + + public VcsRootCommand(@NotNull final Settings settings, @NotNull final File workDir) { + super(settings.getHgCommandPath(), workDir); + mySettings = settings; + } + + + public Settings getSettings() { + return mySettings; + } + + protected CommandResult runCommand(@NotNull GeneralCommandLine cli) throws VcsException { + return CommandUtil.runCommand(cli, with()); + } + + protected CommandResult runCommand(@NotNull GeneralCommandLine cli, @NotNull CommandExecutionSettingsBuilder with) throws VcsException { + return CommandUtil.runCommand(cli, with.privateData(getPrivateData())); + } + + protected Set<String> getPrivateData() { + String password = mySettings.getPassword(); + return password != null ? Collections.singleton(password) : Collections.<String>emptySet(); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/VersionCommand.java Wed Feb 15 13:23:53 2012 +0400 @@ -0,0 +1,33 @@ +package jetbrains.buildServer.buildTriggers.vcs.mercurial.command; + +import com.intellij.execution.configurations.GeneralCommandLine; +import jetbrains.buildServer.buildTriggers.vcs.mercurial.HgVersion; +import jetbrains.buildServer.vcs.VcsException; +import org.jetbrains.annotations.NotNull; + +import java.io.File; + +/** + * @author dmitry.neverov + */ +public class VersionCommand extends BaseCommand { + + public VersionCommand(@NotNull final Settings settings, @NotNull File workingDir) { + super(settings.getHgCommandPath(), workingDir); + } + + + public VersionCommand(@NotNull final String hgPath, @NotNull File workingDir) { + super(hgPath, workingDir); + } + + + public HgVersion execute() throws VcsException { + GeneralCommandLine cli = createCommandLine(); + cli.addParameter("version"); + cli.addParameter("--quiet"); + CommandResult result = CommandUtil.runCommand(cli); + return HgVersion.parse(result.getStdout()); + } + +}
--- a/mercurial-server/resources/buildServerResources/mercurialSettings.jsp Wed Feb 15 12:07:12 2012 +0400 +++ b/mercurial-server/resources/buildServerResources/mercurialSettings.jsp Wed Feb 15 13:23:53 2012 +0400 @@ -32,6 +32,10 @@ </td> </tr> <tr> + <th><label for="tagUsername">Username for tags: </label></th> + <td><props:textProperty name="tagUsername"/></td> + </tr> + <tr> <th><label for="uncompressedTransfer">Use uncompressed transfer: </label></th> <td><props:checkboxProperty name="uncompressedTransfer"/> <div class="smallNote" style="margin: 0;">Uncompressed transfer is faster for repositories in the LAN.</div> @@ -40,9 +44,6 @@ </l:settingsGroup> <l:settingsGroup title="Authorization settings"> <tr> - <td colspan="2">Authorization settings can be required if you need to tag / label sources in the remote repository.</td> - </tr> - <tr> <th><label for="username">User name:</label></th> <td><props:textProperty name="username"/></td> </tr> @@ -51,5 +52,5 @@ <td><props:passwordProperty name="secure:password"/></td> </tr> </l:settingsGroup> - + </table>
--- a/mercurial-server/src/META-INF/build-server-plugin-mercurial.xml Wed Feb 15 12:07:12 2012 +0400 +++ b/mercurial-server/src/META-INF/build-server-plugin-mercurial.xml Wed Feb 15 13:23:53 2012 +0400 @@ -1,7 +1,10 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd"> - -<beans default-autowire="constructor"> - <bean id="mercurialServer" class="jetbrains.buildServer.buildTriggers.vcs.mercurial.MercurialVcsSupport" /> - <bean id="config" class="jetbrains.buildServer.buildTriggers.vcs.mercurial.ServerPluginConfigImpl" /> -</beans> +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd"> + +<beans default-autowire="constructor"> + <bean id="mercurialServer" class="jetbrains.buildServer.buildTriggers.vcs.mercurial.MercurialVcsSupport" /> + <bean id="config" class="jetbrains.buildServer.buildTriggers.vcs.mercurial.ServerPluginConfigImpl" /> + <bean id="commandFactory" class="jetbrains.buildServer.buildTriggers.vcs.mercurial.CommandFactoryImpl" /> + <bean id="hgPathProvider" class="jetbrains.buildServer.buildTriggers.vcs.mercurial.ServerHgPathProvider"/> + <bean id="mirrorManager" class="jetbrains.buildServer.buildTriggers.vcs.mercurial.MirrorManagerImpl" /> +</beans>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mercurial-server/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/Cleanup.java Wed Feb 15 13:23:53 2012 +0400 @@ -0,0 +1,135 @@ +package jetbrains.buildServer.buildTriggers.vcs.mercurial; + +import com.intellij.openapi.diagnostic.Logger; +import jetbrains.buildServer.buildTriggers.vcs.mercurial.command.Settings; +import jetbrains.buildServer.serverSide.impl.LogUtil; +import jetbrains.buildServer.util.FileUtil; +import jetbrains.buildServer.vcs.VcsManager; +import jetbrains.buildServer.vcs.VcsRoot; +import org.jetbrains.annotations.NotNull; + +import java.io.File; +import java.io.FileFilter; +import java.util.*; + +import static java.util.Arrays.asList; + +/** + * @author dmitry.neverov + */ +public class Cleanup implements Runnable { + + private static Logger LOG = Logger.getInstance(Cleanup.class.getName()); + + private final VcsManager myVcsManager; + private final MirrorManager myMirrorManager; + private final PluginConfig myConfig; + private final HgPathProvider myHgPathProvider; + + public Cleanup(@NotNull final VcsManager vcsManager, + @NotNull final MirrorManager mirrorManager, + @NotNull final PluginConfig config, + @NotNull final HgPathProvider hgPathProvider) { + myVcsManager = vcsManager; + myMirrorManager = mirrorManager; + myConfig = config; + myHgPathProvider = hgPathProvider; + } + + public void run() { + delete(unusedDirs()); + } + + private Collection<File> unusedDirs() { + List<File> existingDirs = existingDirs(); + List<File> mirrorsInUse = mirrorDirsOfRootsInUse(); + existingDirs.removeAll(mirrorsInUse); + return existingDirs; + } + + private List<File> existingDirs() { + File[] files = listDirs(); + if (files != null) + return new ArrayList<File>(asList(files)); + if (myConfig.getCachesDir().isDirectory()) + LOG.warn("Cannot list files in " + myConfig.getCachesDir()); + return Collections.emptyList(); + } + + private File[] listDirs() { + return myConfig.getCachesDir().listFiles(new FileFilter() { + public boolean accept(File f) { + return f.isDirectory(); + } + }); + } + + private List<File> mirrorDirsOfRootsInUse() { + Map<String, File> mirrorMap = myMirrorManager.getMappings(); + List<File> result = new ArrayList<File>(); + for (VcsRoot root : mercurialVcsRoots()) { + File mirrorDir = mirrorMap.get(urlOf(root)); + if (mirrorDir != null) + result.add(mirrorDir); + } + return result; + } + + private String urlOf(VcsRoot root) { + Settings s = new Settings(myHgPathProvider, root); + return s.getRepository(); + } + + private Collection<VcsRoot> mercurialVcsRoots() { + List<VcsRoot> mercurialRoots = new ArrayList<VcsRoot>(); + for (VcsRoot root : myVcsManager.getAllRegisteredVcsRoots()) { + if (isMercurialRoot(root)) + mercurialRoots.add(root); + } + logRegisteredMercurialRoots(mercurialRoots); + return mercurialRoots; + } + + private boolean isMercurialRoot(VcsRoot root) { + return Constants.VCS_NAME.equals(root.getVcsName()); + } + + private void delete(Collection<File> dirs) { + logUnusedLocalClones(dirs); + for (File dir : dirs) { + myMirrorManager.lockDir(dir); + try { + myMirrorManager.forgetDir(dir); + FileUtil.delete(dir); + } finally { + myMirrorManager.unlockDir(dir); + } + } + } + + private void logRegisteredMercurialRoots(@NotNull List<VcsRoot> roots) { + StringBuilder sb = new StringBuilder(); + sb.append("Registered mercurial roots: "); + Iterator<VcsRoot> iter = roots.iterator(); + while (iter.hasNext()) { + sb.append(LogUtil.describe(iter.next())); + if (iter.hasNext()) + sb.append(", "); + } + LOG.debug(sb.toString()); + } + + private void logUnusedLocalClones(@NotNull Collection<File> dirs) { + if (dirs.isEmpty()) + return; + StringBuilder sb = new StringBuilder(); + sb.append("Unused local clones: "); + Iterator<File> iter = dirs.iterator(); + while (iter.hasNext()) { + sb.append(iter.next().getAbsolutePath()); + if (iter.hasNext()) + sb.append(", "); + } + LOG.info(sb.toString()); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mercurial-server/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/CollectChangesCommand.java Wed Feb 15 13:23:53 2012 +0400 @@ -0,0 +1,17 @@ +package jetbrains.buildServer.buildTriggers.vcs.mercurial; + +import jetbrains.buildServer.buildTriggers.vcs.mercurial.command.ChangeSet; +import jetbrains.buildServer.vcs.VcsException; +import org.jetbrains.annotations.NotNull; + +import java.util.List; + +/** + * @author dmitry.neverov + */ +public interface CollectChangesCommand { + + @NotNull + public List<ChangeSet> execute(@NotNull String fromCommit, @NotNull String toCommit) throws VcsException; + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mercurial-server/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/CollectChangesNoRevsets.java Wed Feb 15 13:23:53 2012 +0400 @@ -0,0 +1,83 @@ +package jetbrains.buildServer.buildTriggers.vcs.mercurial; + +import com.intellij.openapi.util.Pair; +import jetbrains.buildServer.buildTriggers.vcs.mercurial.command.*; +import jetbrains.buildServer.util.graph.DAG; +import jetbrains.buildServer.util.graph.DAGIterator; +import jetbrains.buildServer.util.graph.DAGs; +import jetbrains.buildServer.vcs.VcsException; +import org.jetbrains.annotations.NotNull; + +import java.io.File; +import java.util.*; + +/** + * @author dmitry.neverov + */ +public class CollectChangesNoRevsets implements CollectChangesCommand { + + private final Settings mySettings; + private final File myWorkingDir; + private final File myTemplate; + + public CollectChangesNoRevsets(@NotNull final Settings settings, + @NotNull final File workingDir, + @NotNull final File template) { + mySettings = settings; + myWorkingDir = workingDir; + myTemplate = template; + } + + + @NotNull + public List<ChangeSet> execute(@NotNull final String fromCommit, @NotNull final String toCommit) throws VcsException { + List<ChangeSet> csets = getRevisionsReachableFrom(toCommit); + Map<String, ChangeSet> csetsMap = getChangesetMap(csets); + if (csetsMap.containsKey(fromCommit)) { + DAG<String> dag = DAGs.createFromEdges(getEdges(csets)); + DAGIterator<String> iter = dag.iterator(toCommit); + iter.markUninteresting(fromCommit); + List<ChangeSet> result = new ArrayList<ChangeSet>(); + while (iter.hasNext()) { + String commit = iter.next(); + ChangeSet cset = csetsMap.get(commit); + if (cset == null) + throw new IllegalStateException("Cannot find cset for id " + commit + ", csets map: " + csetsMap); + result.add(cset); + } + Collections.reverse(result); + return result; + } else { + return Collections.emptyList(); + } + } + + + private Map<String, ChangeSet> getChangesetMap(@NotNull final List<ChangeSet> csets) { + Map<String, ChangeSet> result = new HashMap<String, ChangeSet>(); + for (ChangeSet cset : csets) { + result.put(cset.getId(), cset); + } + return result; + } + + + private List<ChangeSet> getRevisionsReachableFrom(@NotNull final String revision) throws VcsException { + LogCommand log = new LogCommand(mySettings, myWorkingDir, myTemplate); + log.setFromRevId(new ChangeSetRevision(revision).getId()); + log.showCommitsFromAllBranches(); + log.setToRevId("0"); + return log.execute(); + } + + + private List<Pair<String, String>> getEdges(List<ChangeSet> csets) { + List<Pair<String, String>> result = new ArrayList<Pair<String, String>>(); + for (ChangeSet cset : csets) { + for (ChangeSetRevision parent : cset.getParents()) { + result.add(Pair.create(cset.getId(), parent.getId())); + } + } + return result; + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mercurial-server/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/CollectChangesWithRevsets.java Wed Feb 15 13:23:53 2012 +0400 @@ -0,0 +1,34 @@ +package jetbrains.buildServer.buildTriggers.vcs.mercurial; + +import jetbrains.buildServer.buildTriggers.vcs.mercurial.command.ChangeSet; +import jetbrains.buildServer.buildTriggers.vcs.mercurial.command.LogCommand; +import jetbrains.buildServer.buildTriggers.vcs.mercurial.command.Settings; +import jetbrains.buildServer.vcs.VcsException; +import org.jetbrains.annotations.NotNull; + +import java.io.File; +import java.util.List; + +/** + * @author dmitry.neverov + */ +public class CollectChangesWithRevsets implements CollectChangesCommand { + + private final Settings mySettings; + private final File myWorkingDir; + private final File myTemplate; + + public CollectChangesWithRevsets(@NotNull final Settings settings, @NotNull final File workingDir, @NotNull final File template) { + mySettings = settings; + myWorkingDir = workingDir; + myTemplate = template; + } + + @NotNull + public List<ChangeSet> execute(@NotNull final String fromCommit, @NotNull final String toCommit) throws VcsException { + LogCommand log = new LogCommand(mySettings, myWorkingDir, myTemplate); + log.showCommitsFromAllBranches(); + log.setRevsets("ancestors(" + toCommit + ") - ancestors(" + fromCommit + ")"); + return log.execute(); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mercurial-server/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/CommandFactory.java Wed Feb 15 13:23:53 2012 +0400 @@ -0,0 +1,25 @@ +package jetbrains.buildServer.buildTriggers.vcs.mercurial; + +import jetbrains.buildServer.buildTriggers.vcs.mercurial.command.LogCommand; +import jetbrains.buildServer.buildTriggers.vcs.mercurial.command.MergeBaseCommand; +import jetbrains.buildServer.buildTriggers.vcs.mercurial.command.Settings; +import jetbrains.buildServer.vcs.VcsException; +import org.jetbrains.annotations.NotNull; + +import java.io.File; + +/** + * @author dmitry.neverov + */ +public interface CommandFactory { + + @NotNull + public MergeBaseCommand createMergeBase(@NotNull Settings settings, @NotNull File workingDir) throws VcsException; + + @NotNull + public LogCommand createLog(@NotNull final Settings settings, @NotNull final File workingDir); + + @NotNull + public CollectChangesCommand getCollectChangesCommand(@NotNull final Settings settings, @NotNull final File workingDir) throws VcsException; + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mercurial-server/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/CommandFactoryImpl.java Wed Feb 15 13:23:53 2012 +0400 @@ -0,0 +1,68 @@ +package jetbrains.buildServer.buildTriggers.vcs.mercurial; + +import jetbrains.buildServer.buildTriggers.vcs.mercurial.command.LogCommand; +import jetbrains.buildServer.buildTriggers.vcs.mercurial.command.MergeBaseCommand; +import jetbrains.buildServer.buildTriggers.vcs.mercurial.command.Settings; +import jetbrains.buildServer.buildTriggers.vcs.mercurial.command.VersionCommand; +import jetbrains.buildServer.util.FileUtil; +import jetbrains.buildServer.vcs.VcsException; +import org.jetbrains.annotations.NotNull; + +import java.io.File; +import java.io.IOException; + +/** + * @author dmitry.neverov + */ +public final class CommandFactoryImpl implements CommandFactory { + + //hg version which supports revsets + private final static HgVersion REVSET_HG_VERSION = new HgVersion(1, 7, 0); + private final static String LOG_TEMPLATE_NAME = "log.template"; + + private final File myDefaultWorkingDir; + private final File myLogTemplate; + + + public CommandFactoryImpl(@NotNull final ServerPluginConfig config) throws IOException { + myDefaultWorkingDir = config.getCachesDir(); + myLogTemplate = createLogTemplate(config.getPluginDataDir()); + } + + + @NotNull + public MergeBaseCommand createMergeBase(@NotNull Settings settings, @NotNull File workingDir) throws VcsException { + HgVersion hgVersion = getHgVersion(settings); + if (hgVersion.isEqualsOrGreaterThan(REVSET_HG_VERSION)) + return new MergeBaseWithRevsets(settings, workingDir, this); + else + return new MergeBaseNoRevsets(settings, workingDir, this); + } + + + @NotNull + public LogCommand createLog(@NotNull final Settings settings, @NotNull final File workingDir) { + return new LogCommand(settings, workingDir, myLogTemplate); + } + + @NotNull + public CollectChangesCommand getCollectChangesCommand(@NotNull final Settings settings, @NotNull final File workingDir) throws VcsException { + HgVersion hgVersion = getHgVersion(settings); + if (hgVersion.isEqualsOrGreaterThan(REVSET_HG_VERSION)) { + return new CollectChangesWithRevsets(settings, workingDir, myLogTemplate); + } else { + return new CollectChangesNoRevsets(settings, workingDir, myLogTemplate); + } + } + + private File createLogTemplate(@NotNull final File templateFileDir) throws IOException { + File template = new File(templateFileDir, LOG_TEMPLATE_NAME); + FileUtil.copyResource(CommandFactoryImpl.class, "/buildServerResources/log.template", template); + return template; + } + + private HgVersion getHgVersion(@NotNull final Settings settings) throws VcsException { + VersionCommand versionCommand = new VersionCommand(settings, myDefaultWorkingDir); + return versionCommand.execute(); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mercurial-server/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/MercurialResetCacheHandler.java Wed Feb 15 13:23:53 2012 +0400 @@ -0,0 +1,88 @@ +package jetbrains.buildServer.buildTriggers.vcs.mercurial; + +import com.intellij.openapi.diagnostic.Logger; +import jetbrains.buildServer.util.cache.ResetCacheHandler; +import org.jetbrains.annotations.NotNull; + +import java.io.File; +import java.util.*; +import java.util.concurrent.atomic.AtomicBoolean; + +import static com.intellij.openapi.util.io.FileUtil.delete; +import static java.util.Collections.singletonList; + +/** + * @author dmitry.neverov + */ +public class MercurialResetCacheHandler implements ResetCacheHandler { + + private static Logger LOG = Logger.getInstance(MercurialResetCacheHandler.class.getName()); + private static final String MERCURIAL_CACHE_NAME = "mercurial"; + + private final MirrorManager myMirrorManager; + private AtomicBoolean myResetRunning = new AtomicBoolean(false); + + public MercurialResetCacheHandler(@NotNull MirrorManager mirrorManager) { + myMirrorManager = mirrorManager; + } + + @NotNull + public List<String> listCaches() { + return singletonList(MERCURIAL_CACHE_NAME); + } + + public boolean isEmpty(@NotNull final String cache) { + return myMirrorManager.getMappings().isEmpty(); + } + + public void resetCache(@NotNull final String cache) { + boolean started = startReset(); + if (!started) { + LOG.info("Mercurial mirrors reset is already running"); + return; + } + resetAllMirrors(); + finishReset(); + } + + private boolean startReset() { + return myResetRunning.compareAndSet(false, true); + } + + private void finishReset() { + myResetRunning.set(false); + } + + private void resetAllMirrors() { + LOG.info("Start reseting mercurial caches"); + for (Map.Entry<String, File> entry : myMirrorManager.getMappings().entrySet()) { + String url = entry.getKey(); + File mirror = entry.getValue(); + try { + lockMirror(url, mirror); + resetMirror(mirror); + } finally { + unlockMirror(url, mirror); + } + } + LOG.info("Mercurial caches reset"); + } + + private void lockMirror(@NotNull final String url, @NotNull final File mirror) { + LOG.debug("Lock mirror of " + url); + myMirrorManager.lockDir(mirror); + LOG.debug("Mirror of " + url + " is locked"); + } + + private void resetMirror(@NotNull final File mirror) { + LOG.debug("Reset mercurial mirror " + mirror.getAbsolutePath()); + myMirrorManager.forgetDir(mirror); + delete(mirror); + LOG.debug("Mercurial mirror " + mirror.getAbsolutePath() + " reset"); + } + + private void unlockMirror(@NotNull final String url, @NotNull final File mirror) { + myMirrorManager.unlockDir(mirror); + LOG.debug("Mirror of " + url + " is unlocked"); + } +}
--- a/mercurial-server/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/MercurialVcsSupport.java Wed Feb 15 12:07:12 2012 +0400 +++ b/mercurial-server/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/MercurialVcsSupport.java Wed Feb 15 13:23:53 2012 +0400 @@ -24,8 +24,7 @@ import jetbrains.buildServer.util.EventDispatcher; import jetbrains.buildServer.util.FileUtil; import jetbrains.buildServer.util.StringUtil; -import jetbrains.buildServer.util.filters.Filter; -import jetbrains.buildServer.util.filters.FilterUtil; +import jetbrains.buildServer.util.cache.ResetCacheRegister; import jetbrains.buildServer.vcs.*; import jetbrains.buildServer.vcs.impl.VcsRootImpl; import jetbrains.buildServer.vcs.patches.PatchBuilder; @@ -37,10 +36,6 @@ import java.io.FileInputStream; import java.io.IOException; import java.util.*; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ConcurrentMap; -import java.util.concurrent.locks.Lock; -import java.util.concurrent.locks.ReentrantLock; import static com.intellij.openapi.util.text.StringUtil.isEmptyOrSpaces; @@ -55,37 +50,36 @@ * <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 { - - private final String LOG_TEMPLATE_NAME = "log.template"; - private ConcurrentMap<String, Lock> myWorkDirLocks= new ConcurrentHashMap<String, Lock>(); - private VcsManager myVcsManager; - private File myDefaultWorkFolderParent; - private MirrorManager myMirrorManager; +public class MercurialVcsSupport extends ServerVcsSupport implements LabelingSupport, VcsFileContentProvider, BranchSupport, + CollectChangesBetweenRoots { + private final VcsManager myVcsManager; + private final File myDefaultWorkFolderParent; + private final MirrorManager myMirrorManager; private final ServerPluginConfig myConfig; - private File myLogTemplate; + private final HgPathProvider myHgPathProvider; + private final CommandFactory myCommandFactory; private final FileFilter myIgnoreDotHgFilter = new IgnoreDotHgFilter(); private final FileFilter myAcceptAllFilter = new AcceptAllFilter(); public MercurialVcsSupport(@NotNull final VcsManager vcsManager, - @NotNull final ServerPaths paths, @NotNull final SBuildServer server, @NotNull final EventDispatcher<BuildServerListener> dispatcher, - @NotNull final ServerPluginConfig config) throws IOException { - myLogTemplate = createLogTemplate(paths.getPluginDataDirectory()); + @NotNull final ResetCacheRegister resetCacheHandlerManager, + @NotNull final ServerPluginConfig config, + @NotNull final HgPathProvider hgPathProvider, + @NotNull final CommandFactory commandFactory, + @NotNull final MirrorManager mirrorManager) { myVcsManager = vcsManager; - myDefaultWorkFolderParent = new File(paths.getCachesDir(), "mercurial"); - myMirrorManager = new MirrorManager(myDefaultWorkFolderParent); myConfig = config; + myDefaultWorkFolderParent = myConfig.getCachesDir(); + myMirrorManager = mirrorManager; + myHgPathProvider = hgPathProvider; + myCommandFactory = commandFactory; + resetCacheHandlerManager.registerHandler(new MercurialResetCacheHandler(myMirrorManager)); dispatcher.addListener(new BuildServerAdapter() { @Override public void cleanupFinished() { - super.cleanupFinished(); - server.getExecutor().submit(new Runnable() { - public void run() { - removeOldWorkFolders(); - } - }); + server.getExecutor().submit(new Cleanup(myVcsManager, myMirrorManager, myConfig, myHgPathProvider)); } @Override @@ -98,6 +92,15 @@ }); } }); + 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) { @@ -111,19 +114,6 @@ } } - private Collection<ModifiedFile> computeModifiedFilesForMergeCommit(final Settings settings, final ChangeSet cur) throws VcsException { - File workingDir = getWorkingDir(settings); - ChangedFilesCommand cfc = new ChangedFilesCommand(settings, workingDir); - cfc.setRevId(cur.getId()); - return cfc.execute(); - } - - private File createLogTemplate(@NotNull final File templateFileDir) throws IOException { - File template = new File(templateFileDir, LOG_TEMPLATE_NAME); - FileUtil.copyResource(MercurialVcsSupport.class, "/buildServerResources/log.template", template); - return template; - } - private List<VcsChange> toVcsChanges(final List<ModifiedFile> modifiedFiles, String prevVer, String curVer, CheckoutRules rules) { List<VcsChange> files = new ArrayList<VcsChange>(); for (ModifiedFile mf: modifiedFiles) { @@ -192,35 +182,6 @@ return new byte[0]; } - /** - * Returns file's content or empty string if it doesn't exist. - * @param path path of the file of interest - * @param settings root settings - * @param cset repository cset (should be present in the repository) - * @return see above - */ - @NotNull - private String getFileContent(@NotNull final String path, @NotNull final Settings settings, @NotNull final ChangeSet cset) throws VcsException { - File dir = getWorkingDir(settings); - CatCommand cat = new CatCommand(settings, dir); - cat.setRevId(cset.getId()); - cat.setLogErrorsInDebug(true); - File parentDir = null; - try { - parentDir = cat.execute(Collections.singletonList(path)); - File f = new File(parentDir, path); - if (f.isFile()) - return FileUtil.readText(f); - else - return ""; - } catch (Exception e) { - return ""; - } finally { - if (parentDir != null) - deleteTmpDir(parentDir); - } - } - @NotNull public String getName() { return Constants.VCS_NAME; @@ -286,7 +247,7 @@ StringBuilder res = new StringBuilder(); res.append(quoteIfNeeded(settings.getHgCommandPath())); res.append(" identify "); - final String obfuscatedUrl = CommandUtil.removePrivateData(settings.getRepositoryUrl(), Collections.singleton(settings.getPassword())); + final String obfuscatedUrl = CommandUtil.removePrivateData(settings.getRepositoryUrlWithCredentials(), Collections.singleton(settings.getPassword())); res.append(quoteIfNeeded(obfuscatedUrl)); res.append('\n').append(id.execute()); return res.toString(); @@ -418,6 +379,33 @@ return !isEmptyOrSpaces(hgsub); } + /** + * Returns file's content or empty string if it doesn't exist. + * @param path path of the file of interest + * @param settings root settings + * @param cset repository cset (should be present in the repository) + * @return see above + */ + @NotNull + private String getFileContent(@NotNull final String path, @NotNull final Settings settings, @NotNull final ChangeSet cset) throws VcsException { + File dir = getWorkingDir(settings); + CatCommand cat = new CatCommand(settings, dir); + cat.setRevId(cset.getId()); + File parentDir = cat.execute(Collections.singletonList(path), false); + try { + File f = new File(parentDir, path); + if (f.isFile()) + return FileUtil.readText(f); + else + return ""; + } catch (Exception e) { + return ""; + } finally { + deleteTmpDir(parentDir); + } + } + + private void buildPatchFromDirectory(final PatchBuilder builder, final File repRoot, final CheckoutRules checkoutRules, @NotNull final FileFilter filter) throws IOException { buildPatchFromDirectory(repRoot, builder, repRoot, checkoutRules, filter); } @@ -459,8 +447,14 @@ try { if (Settings.isValidRepository(workingDir)) { if (!isChangeSetExist(settings, workingDir, cset)) { - PullCommand pull = new PullCommand(settings, workingDir); - pull.execute(myConfig.getPullTimeout()); + try { + PullCommand pull = new PullCommand(settings, workingDir); + pull.execute(myConfig.getPullTimeout()); + } catch (UnrelatedRepositoryException e) { + Loggers.VCS.warn("Repository at " + settings.getRepository() + " is unrelated, clone it again"); + myMirrorManager.forgetDir(workingDir); + syncRepository(settings, cset); + } } } else { CloneCommand cl = new CloneCommand(settings, workingDir); @@ -477,8 +471,14 @@ lockWorkDir(workingDir); try { if (Settings.isValidRepository(workingDir)) { - PullCommand pull = new PullCommand(settings, workingDir); - pull.execute(myConfig.getPullTimeout()); + try { + PullCommand pull = new PullCommand(settings, workingDir); + pull.execute(myConfig.getPullTimeout()); + } catch (UnrelatedRepositoryException e) { + Loggers.VCS.warn("Repository at " + settings.getRepository() + " is unrelated, clone it again"); + myMirrorManager.forgetDir(workingDir); + syncRepository(settings); + } } else { CloneCommand cl = new CloneCommand(settings, workingDir); cl.setUpdateWorkingDir(false); @@ -492,6 +492,7 @@ /** * Check if changeSet is present in local repository. * @param settings root settings + * @param workDir where to run a command * @param cset change set of interest * @return true if changeSet is present in local repository */ @@ -523,7 +524,12 @@ } @NotNull - public Map<String, String> getBranchesRevisions(@NotNull VcsRoot root) throws VcsException { + public RepositoryState getCurrentState(@NotNull VcsRoot root) throws VcsException { + return RepositoryStateFactory.createRepositoryState(getBranchesRevisions(root)); + } + + @NotNull + private Map<String, String> getBranchesRevisions(@NotNull VcsRoot root) throws VcsException { Settings settings = createSettings(root); syncRepository(settings); File workingDir = getWorkingDir(settings); @@ -549,12 +555,15 @@ VcsRoot branchRoot = createBranchRoot(root, branchName); String baseVersion = getCurrentVersion(root); String branchVersion = getCurrentVersion(branchRoot); - String branchPoint = getBranchPoint(settings, baseVersion, branchVersion); + String mergeBase = getMergeBase(settings, baseVersion, branchVersion); - LogCommand lc = new LogCommand(settings, getWorkingDir(settings), myLogTemplate); - lc.setFromRevId(new ChangeSetRevision(branchPoint).getId()); + if (mergeBase == null) + return null; + + LogCommand lc = myCommandFactory.createLog(settings, getWorkingDir(settings)); + lc.setFromRevId(new ChangeSetRevision(mergeBase).getId()); lc.setToRevId(new ChangeSetRevision(branchVersion).getId()); - lc.setBranchName(null);//do not limit output to particular branch, return all commits + lc.showCommitsFromAllBranches(); List<ChangeSet> changeSets = lc.execute(); if (changeSets.size() > 1) {//when branch points to the commit in original branch we get 1 cset String branchId = changeSets.get(1).getId(); @@ -576,76 +585,93 @@ public List<ModificationData> collectChanges(@NotNull VcsRoot fromRoot, @NotNull String fromRootRevision, @NotNull VcsRoot toRoot, @Nullable String toRootRevision, @NotNull CheckoutRules checkoutRules) throws VcsException { - //we get all branches while clone, if vcs roots are related it is doesn't matter in which one search for branch point - Settings settings = createSettings(fromRoot); + Settings settings = createSettings(toRoot); syncRepository(settings); - String branchPoint = getBranchPoint(settings, fromRootRevision, toRootRevision); - return ((CollectChangesByCheckoutRules) getCollectChangesPolicy()).collectChanges(toRoot, branchPoint, toRootRevision, checkoutRules); + String toRevision = toRootRevision != null ? toRootRevision : getCurrentVersion(toRoot); + String mergeBase = getMergeBase(settings, fromRootRevision, toRevision); + if (mergeBase == null) + return Collections.emptyList(); + return collectChanges(toRoot, mergeBase, toRootRevision, checkoutRules); } - private String getBranchPoint(@NotNull Settings settings, String branchOneRev, String branchTwoRev) throws VcsException { - if (branchOneRev.equals(branchTwoRev)) - return branchOneRev; - File workingDir = getWorkingDir(settings); - LogCommand lc = new LogCommand(settings, workingDir, myLogTemplate); - lc.setFromRevId(new ChangeSetRevision(branchOneRev).getId()); - lc.setToRevId(new ChangeSetRevision(branchTwoRev).getId()); - lc.setLimit(1); - List<ChangeSet> changeSets = lc.execute(); - ChangeSet cs = changeSets.get(0); - if (cs.isInitial()) { - return cs.getId(); - } else { - return cs.getParents().get(0).getId(); - } + @Nullable + private String getMergeBase(@NotNull Settings settings, @NotNull String revision1, @NotNull String revision2) throws VcsException { + String result = myCommandFactory.createMergeBase(settings, getWorkingDir(settings)).execute(revision1, revision2); + if (result == null) + result = getMinusNthCommit(settings, 10); + return result; } + + @Nullable + private String getMinusNthCommit(@NotNull Settings settings, int n) throws VcsException { + LogCommand log = myCommandFactory.createLog(settings, getWorkingDir(settings)); + log.setToRevId(settings.getBranchName()); + if (n > 0) + log.setLimit(n); + List<ChangeSet> changeSets = log.execute(); + if (changeSets.isEmpty()) + return null; + return changeSets.get(0).getId(); + } + + @NotNull public CollectChangesPolicy getCollectChangesPolicy() { - return new CollectChangesByCheckoutRules() { - @NotNull - public List<ModificationData> collectChanges(@NotNull VcsRoot root, @NotNull String fromVersion, @Nullable String currentVersion, @NotNull CheckoutRules checkoutRules) throws VcsException { - Settings settings = createSettings(root); - syncRepository(settings); - - // first obtain changes between specified versions - List<ModificationData> result = new ArrayList<ModificationData>(); - if (currentVersion == null) - return result; - - File workingDir = getWorkingDir(settings); - LogCommand lc = new LogCommand(settings, workingDir, myLogTemplate); - String fromId = new ChangeSetRevision(fromVersion).getId(); - lc.setFromRevId(fromId); - lc.setToRevId(new ChangeSetRevision(currentVersion).getId()); - List<ChangeSet> changeSets = lc.execute(); - if (changeSets.isEmpty()) { - return result; - } + return this; + } - ChangeSet prev = new ChangeSet(fromVersion); - for (ChangeSet cur : changeSets) { - if (cur.getId().equals(fromId)) - continue; // skip already reported changeset + public List<ModificationData> collectChanges(@NotNull VcsRoot root, @NotNull String fromVersion, @Nullable String currentVersion, @NotNull CheckoutRules checkoutRules) throws VcsException { + Settings settings = createSettings(root); + syncRepository(settings); + List<ModificationData> result = new ArrayList<ModificationData>(); + for (ChangeSet cset : getChangesets(settings, fromVersion, currentVersion)) { + result.add(createModificationData(cset, root, checkoutRules)); + } + return result; + } - boolean merge = cur.getParents().size() > 1; - List<ModifiedFile> modifiedFiles = cur.getModifiedFiles(); - List<VcsChange> files = toVcsChanges(modifiedFiles, prev.getFullVersion(), cur.getFullVersion(), checkoutRules); - if (files.isEmpty() && !merge) - continue; - ModificationData md = new ModificationData(cur.getTimestamp(), files, cur.getDescription(), cur.getUser(), root, cur.getFullVersion(), cur.getId()); - if (merge) - md.setCanBeIgnored(false); - result.add(md); - prev = cur; - } - 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 Settings settings, @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(); + File workingDir = getWorkingDir(settings); + CollectChangesCommand log = myCommandFactory.getCollectChangesCommand(settings, workingDir); + try { + List<ChangeSet> changesets = log.execute(fromCommit, toCommit); + 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 new BuildPatchByCheckoutRules() { @@ -666,11 +692,11 @@ } private void lockWorkDir(@NotNull File workDir) { - getWorkDirLock(workDir).lock(); + myMirrorManager.lockDir(workDir); } private void unlockWorkDir(@NotNull File workDir) { - getWorkDirLock(workDir).unlock(); + myMirrorManager.unlockDir(workDir); } @Override @@ -680,50 +706,11 @@ return false; } - private Lock getWorkDirLock(final File workDir) { - String path = workDir.getAbsolutePath(); - Lock lock = myWorkDirLocks.get(path); - if (lock == null) { - lock = new ReentrantLock(); - Lock curLock = myWorkDirLocks.putIfAbsent(path, lock); - if (curLock != null) { - lock = curLock; - } - } - return lock; - } - - private void removeOldWorkFolders() { - Set<File> workDirs = new HashSet<File>(myMirrorManager.getMirrors()); - - for (VcsRoot vcsRoot: getMercurialVcsRoots()) { - try { - Settings s = createSettings(vcsRoot); - File workingDir = getWorkingDir(s); - workDirs.remove(PathUtil.getCanonicalFile(workingDir)); - } catch (VcsException e) { - Loggers.VCS.error(e); - } - } - - deleteWithLocking(workDirs); - } - - private Collection<VcsRoot> getMercurialVcsRoots() { - List<VcsRoot> res = new ArrayList<VcsRoot>(myVcsManager.getAllRegisteredVcsRoots()); - FilterUtil.filterCollection(res, new Filter<VcsRoot>() { - public boolean accept(@NotNull final VcsRoot data) { - return getName().equals(data.getVcsName()); - } - }); - return res; - } - public String label(@NotNull String label, @NotNull String version, @NotNull VcsRoot root, @NotNull CheckoutRules checkoutRules) throws VcsException { File tmpDir = null; try { tmpDir = createLabelingTmpDir(); - final Settings settings = createSettings(root); + Settings settings = createSettings(root); settings.setCustomWorkingDir(tmpDir); syncRepository(settings); File workingDir = getWorkingDir(settings); @@ -733,6 +720,9 @@ TagCommand tc = new TagCommand(settings, workingDir); tc.setRevId(new ChangeSet(version).getId()); tc.setTag(fixedTagname); + String user = settings.getUserForTag(); + if (user != null) + tc.setUser(user); tc.execute(); PushCommand pc = new PushCommand(settings, workingDir); @@ -756,18 +746,18 @@ private File getWorkingDir(Settings s) { File customDir = s.getCustomWorkingDir(); - return customDir != null ? customDir : myMirrorManager.getMirrorDir(s.getRepositoryUrl()); + return customDir != null ? customDir : myMirrorManager.getMirrorDir(s.getRepository()); } private Settings createSettings(final VcsRoot root) throws VcsException { - Settings settings = new Settings(root); + Settings settings = new Settings(myHgPathProvider, root); String customClonePath = settings.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 = settings.getRepositoryUrl(); + String repPath = settings.getRepositoryUrlWithCredentials(); String[] splitted = repPath.split("[/\\\\]"); if (splitted.length > 0) { repPath = splitted[splitted.length-1]; @@ -805,6 +795,11 @@ } + @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())); @@ -816,4 +811,14 @@ return true; } } + + @NotNull + public String getBranchName(@NotNull final VcsRoot root) { + try { + Settings s = createSettings(root); + return s.getBranchName(); + } catch (VcsException e) { + return "default"; + } + } }
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mercurial-server/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/MergeBaseNoRevsets.java Wed Feb 15 13:23:53 2012 +0400 @@ -0,0 +1,64 @@ +package jetbrains.buildServer.buildTriggers.vcs.mercurial; + +import com.intellij.openapi.util.Pair; +import jetbrains.buildServer.buildTriggers.vcs.mercurial.command.*; +import jetbrains.buildServer.util.graph.DAG; +import jetbrains.buildServer.util.graph.DAGs; +import jetbrains.buildServer.vcs.VcsException; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.io.File; +import java.util.*; + +/** + * Implementation of merge-base for hg versions which don't have revsets + * @author dmitry.neverov + */ +public final class MergeBaseNoRevsets implements MergeBaseCommand { + + private final Settings mySettings; + private final File myWorkingDir; + private final CommandFactoryImpl myCommandFactory; + + public MergeBaseNoRevsets(@NotNull final Settings settings, @NotNull final File workingDir, @NotNull final CommandFactoryImpl commandFactory) { + mySettings = settings; + myWorkingDir = workingDir; + myCommandFactory = commandFactory; + } + + + @Nullable + public String execute(@NotNull final String revision1, @NotNull final String revision2) { + if (revision1.equals(revision2)) + return revision1; + try { + List<Pair<String, String>> edges = new ArrayList<Pair<String, String>>(); + fillEdges(edges, getRevisionsReachableFrom(revision1)); + fillEdges(edges, getRevisionsReachableFrom(revision2)); + DAG<String> dag = DAGs.createFromEdges(edges); + List<String> commonAncestors = dag.getCommonAncestors(new ChangeSetRevision(revision1).getId(), new ChangeSetRevision(revision2).getId()); + return commonAncestors.isEmpty() ? null : commonAncestors.get(0); + } catch (VcsException e) { + return null; + } + } + + + private List<ChangeSet> getRevisionsReachableFrom(@NotNull final String revision) throws VcsException { + LogCommand log = myCommandFactory.createLog(mySettings, myWorkingDir); + log.setFromRevId(new ChangeSetRevision(revision).getId()); + log.showCommitsFromAllBranches(); + log.setToRevId("0"); + return log.execute(); + } + + + private void fillEdges(List<Pair<String, String>> edges, List<ChangeSet> csets) { + for (ChangeSet cset : csets) { + for (ChangeSetRevision parent : cset.getParents()) { + edges.add(Pair.create(cset.getId(), parent.getId())); + } + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mercurial-server/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/MergeBaseWithRevsets.java Wed Feb 15 13:23:53 2012 +0400 @@ -0,0 +1,38 @@ +package jetbrains.buildServer.buildTriggers.vcs.mercurial; + +import jetbrains.buildServer.buildTriggers.vcs.mercurial.command.*; +import jetbrains.buildServer.vcs.VcsException; +import org.jetbrains.annotations.NotNull; + +import java.io.File; +import java.util.List; + +/** + * Implementation of merge-base using hg revsets + * @author dmitry.neverov + */ +public final class MergeBaseWithRevsets implements MergeBaseCommand { + + private final Settings mySettings; + private final File myWorkingDir; + private final CommandFactoryImpl myCommandFactory; + + public MergeBaseWithRevsets(@NotNull final Settings settings, @NotNull final File workingDir, @NotNull final CommandFactoryImpl commandFactory) { + mySettings = settings; + myWorkingDir = workingDir; + myCommandFactory = commandFactory; + } + + public String execute(@NotNull final String revision1, @NotNull final String revision2) throws VcsException { + try { + LogCommand log = myCommandFactory.createLog(mySettings, myWorkingDir); + log.setRevsets("ancestor(" + new ChangeSetRevision(revision1).getId() + ", " + new ChangeSetRevision(revision2).getId() + ")"); + log.showCommitsFromAllBranches(); + log.setCalculateParents(false); + List<ChangeSet> csets = log.execute(); + return csets.isEmpty() ? null : csets.get(0).getId(); + } catch (VcsException e) { + return null; + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mercurial-server/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/ServerHgPathProvider.java Wed Feb 15 13:23:53 2012 +0400 @@ -0,0 +1,39 @@ +package jetbrains.buildServer.buildTriggers.vcs.mercurial; + +import jetbrains.buildServer.buildTriggers.vcs.mercurial.command.Settings; +import org.jetbrains.annotations.NotNull; + +/** + * @author dmitry.neverov + */ +public class ServerHgPathProvider implements HgPathProvider { + + private final ServerPluginConfig myConfig; + + + public ServerHgPathProvider(@NotNull final ServerPluginConfig config) { + myConfig = config; + } + + + public String getHgPath(@NotNull final Settings settings) { + String serverWideHgPath = myConfig.getHgPath(); + if (serverWideHgPath != null) { + return serverWideHgPath; + } else { + String pathFromRoot = settings.getHgPath(); + if (pathFromRoot.equals(unresolvedAgentHgPath())) { + //try to use hg from the PATH: + return "hg"; + } else { + return pathFromRoot; + } + } + } + + + private String unresolvedAgentHgPath() { + //Use hard-coded value here in order to not add dependency on agent part of plugin: + return "%teamcity.hg.agent.path%"; + } +}
--- a/mercurial-server/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/ServerPluginConfig.java Wed Feb 15 12:07:12 2012 +0400 +++ b/mercurial-server/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/ServerPluginConfig.java Wed Feb 15 13:23:53 2012 +0400 @@ -1,10 +1,22 @@ package jetbrains.buildServer.buildTriggers.vcs.mercurial; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.io.File; + /** * @author dmitry.neverov */ public interface ServerPluginConfig extends PluginConfig { + @Nullable + String getHgPath(); + public boolean isUsePullProtocol(); + @NotNull + public File getPluginDataDir(); + + int getPullTimeout(); }
--- a/mercurial-server/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/ServerPluginConfigImpl.java Wed Feb 15 12:07:12 2012 +0400 +++ b/mercurial-server/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/ServerPluginConfigImpl.java Wed Feb 15 13:23:53 2012 +0400 @@ -1,6 +1,11 @@ package jetbrains.buildServer.buildTriggers.vcs.mercurial; +import jetbrains.buildServer.serverSide.ServerPaths; import jetbrains.buildServer.serverSide.TeamCityProperties; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.io.File; /** * @author dmitry.neverov @@ -8,12 +13,35 @@ public class ServerPluginConfigImpl implements ServerPluginConfig { private static final String PULL_TIMEOUT_SECONDS = "teamcity.hg.pull.timeout.seconds"; - private final int DEFAULT_PULL_TIMEOUT_SECONDS = 3600; + public static final int DEFAULT_PULL_TIMEOUT_SECONDS = 3600; + + private final File myCachesDir; + private final File myPluginDataDir; + + public ServerPluginConfigImpl(@NotNull final ServerPaths paths) { + myCachesDir = new File(paths.getCachesDir(), "mercurial"); + myPluginDataDir = paths.getPluginDataDirectory(); + } public boolean isUsePullProtocol() { return TeamCityProperties.getBooleanOrTrue("teamcity.hg.use.pull.protocol"); } + @Nullable + public String getHgPath() { + return TeamCityProperties.getPropertyOrNull("teamcity.hg.server.path"); + } + + @NotNull + public File getCachesDir() { + return myCachesDir; + } + + @NotNull + public File getPluginDataDir() { + return myPluginDataDir; + } + public int getPullTimeout() { int timeout = TeamCityProperties.getInteger(PULL_TIMEOUT_SECONDS, DEFAULT_PULL_TIMEOUT_SECONDS); return timeout > 0 ? timeout : DEFAULT_PULL_TIMEOUT_SECONDS;
--- a/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/AgentSideCheckoutTest.java Wed Feb 15 12:07:12 2012 +0400 +++ b/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/AgentSideCheckoutTest.java Wed Feb 15 13:23:53 2012 +0400 @@ -1,225 +1,285 @@ -/* - * 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.agent.AgentRunningBuild; -import jetbrains.buildServer.agent.BuildAgentConfiguration; -import jetbrains.buildServer.agent.BuildProgressLogger; -import jetbrains.buildServer.util.FileUtil; -import jetbrains.buildServer.vcs.CheckoutRules; -import jetbrains.buildServer.vcs.IncludeRule; -import jetbrains.buildServer.vcs.VcsException; -import jetbrains.buildServer.vcs.VcsRoot; -import org.jmock.Expectations; -import org.jmock.Mockery; -import org.testng.annotations.BeforeMethod; -import org.testng.annotations.Test; - -import java.io.File; -import java.io.IOException; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -/** - * @author Pavel.Sher - * Date: 30.07.2008 - */ -@Test -public class AgentSideCheckoutTest extends BaseMercurialTestCase { - private MercurialAgentSideVcsSupport myVcsSupport; - private File myWorkDir; - private File myMirrorsRootDir; - private Mockery myContext; - private BuildProgressLogger myLogger; - private int myBuildCounter = 0; - - @Override - @BeforeMethod - protected void setUp() throws Exception { - super.setUp(); - - myContext = new Mockery(); - - myMirrorsRootDir = myTempFiles.createTempDir(); - - final BuildAgentConfiguration agentConfig = myContext.mock(BuildAgentConfiguration.class); - myContext.checking(new Expectations() {{ - allowing(agentConfig).getCacheDirectory("mercurial"); will(returnValue(myMirrorsRootDir)); - }}); - - myVcsSupport = new MercurialAgentSideVcsSupport(agentConfig); - - myLogger = myContext.mock(BuildProgressLogger.class); - myContext.checking(new Expectations() {{ - allowing(myLogger).message(with(any(String.class))); - }}); - - myWorkDir = myTempFiles.createTempDir(); - - } - - public void checkout_on_agent() throws IOException, VcsException { - testUpdate(createVcsRoot(simpleRepo()), "4:b06a290a363b", "cleanPatch1/after", new IncludeRule(".", ".", null)); - } - - public void checkout_on_agent_include_rule_with_mapping() throws IOException, VcsException { - testUpdate(createVcsRoot(simpleRepo()), "4:b06a290a363b", "cleanPatch1/after", new IncludeRule("+:.", "subdir", null)); - } - - private void testUpdate(final VcsRoot vcsRoot, String version, String expected, final IncludeRule includeRule) throws VcsException, IOException { - File workDir = doUpdate(vcsRoot, version, includeRule); - - checkWorkingDir(expected, workDir); - } - - private void checkWorkingDir(final String expected, final File workDir) throws IOException { - FileUtil.delete(new File(workDir, ".hg")); - checkDirectoriesAreEqual(new File(getTestDataPath(), expected), workDir); - } - - private File doUpdate(final VcsRoot vcsRoot, final String version, final IncludeRule includeRule) throws VcsException { - return doUpdate(vcsRoot, version, includeRule, false); - } - - private File doUpdate(final VcsRoot vcsRoot, final String version, final IncludeRule includeRule, boolean useLocalMirrors) throws VcsException { - File actualWorkDir = new File(myWorkDir, includeRule.getTo()); - final Map<String, String> sharedConfigParameters = new HashMap<String, String>(); - sharedConfigParameters.put("teamcity.hg.use.local.mirrors", String.valueOf(useLocalMirrors)); - final AgentRunningBuild build = myContext.mock(AgentRunningBuild.class, "build" + myBuildCounter++); - myContext.checking(new Expectations() {{ - allowing(build).getBuildLogger(); will(returnValue(myLogger)); - allowing(build).getSharedConfigParameters(); will(returnValue(sharedConfigParameters)); - }}); - myVcsSupport.getUpdater(vcsRoot, new CheckoutRules(""), version, myWorkDir, build, false).process(includeRule, actualWorkDir); - - File hgDir = new File(actualWorkDir, ".hg"); - assertTrue(hgDir.isDirectory()); - return actualWorkDir; - } - - public void checkout_on_agent_from_branch() throws IOException, VcsException { - testUpdate(createVcsRoot(simpleRepo(), "test_branch"), "7:376dcf05cd2a", "patch3/after", new IncludeRule(".", ".", null)); - } - - public void update_on_agent() throws IOException, VcsException { - VcsRoot vcsRoot = createVcsRoot(simpleRepo()); - doUpdate(vcsRoot, "3:9522278aa38d", new IncludeRule(".", ".", null)); - File workDir = doUpdate(vcsRoot, "4:b06a290a363b", new IncludeRule(".", ".", null)); - - checkWorkingDir("patch1/after", workDir); - } - - public void update_on_agent_with_include_rule() throws IOException, VcsException { - VcsRoot vcsRoot = createVcsRoot(simpleRepo()); - doUpdate(vcsRoot, "3:9522278aa38d", new IncludeRule(".", "subdir", null)); - File workDir = doUpdate(vcsRoot, "4:b06a290a363b", new IncludeRule(".", "subdir", null)); - - checkWorkingDir("patch1/after", workDir); - } - - public void update_on_agent_from_branch() throws IOException, VcsException { - VcsRoot vcsRoot = createVcsRoot(simpleRepo(), "test_branch"); - doUpdate(vcsRoot, "7:376dcf05cd2a", new IncludeRule(".", ".", null)); - File workDir = doUpdate(vcsRoot, "8:04c3ae4c6312", new IncludeRule(".", ".", null)); - - checkWorkingDir("patch4/after", workDir); - } - - public void by_default_local_mirror_not_created() throws IOException, VcsException { - List<File> mirrors = FileUtil.getSubDirectories(myMirrorsRootDir); - assertTrue(mirrors.isEmpty()); - VcsRoot root = createVcsRoot(simpleRepo()); - doUpdate(root, "3:9522278aa38d", new IncludeRule(".", ".", null)); - mirrors = FileUtil.getSubDirectories(myMirrorsRootDir); - //though some dirs are created - they are empty => there were no clones into local mirrors - for (File mirror : mirrors) { - assertTrue(FileUtil.getSubDirectories(mirror).isEmpty()); - } - } - - public void local_mirror_is_created() throws IOException, VcsException { - List<File> mirrors = FileUtil.getSubDirectories(myMirrorsRootDir); - assertTrue(mirrors.isEmpty()); - VcsRoot root = createVcsRoot(simpleRepo()); - doUpdate(root, "3:9522278aa38d", new IncludeRule(".", ".", null), true); - mirrors = FileUtil.getSubDirectories(myMirrorsRootDir); - assertEquals(1, mirrors.size()); - File mirror = mirrors.get(0); - File dotHg = new File(mirror, ".hg"); - assertTrue(dotHg.exists()); - File hgrc = new File(dotHg, "hgrc"); - String hgrcContent = FileUtil.readText(hgrc); - assertTrue(hgrcContent.contains("default = " + root.getProperty(Constants.REPOSITORY_PROP))); - } - - public void new_repository_is_cloned_from_local_mirror() throws IOException, VcsException { - VcsRoot root = createVcsRoot(simpleRepo()); - File workingDir = doUpdate(root, "3:9522278aa38d", new IncludeRule(".", ".", null), true); - File mirrorDir = FileUtil.getSubDirectories(myMirrorsRootDir).get(0); - File hgrc = new File(workingDir, ".hg" + File.separator + "hgrc"); - String hgrcContent = FileUtil.readText(hgrc); - assertTrue(hgrcContent.contains("default = " + mirrorDir.getCanonicalPath())); - } - - public void repository_cloned_from_remote_start_cloning_from_local_mirror() throws IOException, VcsException { - VcsRoot root = createVcsRoot(simpleRepo()); - //clone from remote repository - File workingDir = doUpdate(root, "3:9522278aa38d", new IncludeRule(".", ".", null)); - String hgrcContent = FileUtil.readText(new File(workingDir, ".hg" + File.separator + "hgrc")); - - File workingDir2 = doUpdate(root, "3:9522278aa38d", new IncludeRule(".", ".", null), true); - File newMirrorDir = FileUtil.getSubDirectories(myMirrorsRootDir).get(0); - String hgrcContent2 = FileUtil.readText(new File(workingDir2, ".hg" + File.separator + "hgrc")); - assertFalse(hgrcContent2.equals(hgrcContent));//repository settings are changed - assertTrue(hgrcContent2.contains("default = " + newMirrorDir.getCanonicalPath()));//now it clones from local mirror - } - - public void repository_cloned_from_local_mirror_start_cloning_from_remote() throws IOException, VcsException { - VcsRoot root = createVcsRoot(simpleRepo()); - //clone from remote repository - File workingDir = doUpdate(root, "3:9522278aa38d", new IncludeRule(".", ".", null), true); - String hgrcContent = FileUtil.readText(new File(workingDir, ".hg" + File.separator + "hgrc")); - File newMirrorDir = FileUtil.getSubDirectories(myMirrorsRootDir).get(0); - assertTrue(hgrcContent.contains("default = " + newMirrorDir.getCanonicalPath()));//now it clones from local mirror - - File workingDir2 = doUpdate(root, "3:9522278aa38d", new IncludeRule(".", ".", null)); - String hgrcContent2 = FileUtil.readText(new File(workingDir2, ".hg" + File.separator + "hgrc")); - assertFalse(hgrcContent2.equals(hgrcContent));//repository settings are changed - assertTrue(hgrcContent2.contains("default = " + root.getProperty(Constants.REPOSITORY_PROP)));//now it clones from remote - } - - /** - * TW-15984 - */ - public void should_be_able_to_clone_into_non_empty_dir() throws IOException, VcsException { - VcsRoot vcsRoot = createVcsRoot(simpleRepo()); - doUpdate(vcsRoot, "3:9522278aa38d", new IncludeRule(".", "subdir", null)); - doUpdate(vcsRoot, "4:b06a290a363b", new IncludeRule(".", ".", null)); - } - - public void cloned_repo_should_contains_default_parameter_in_hgrc() throws VcsException, IOException { - VcsRoot root = createVcsRoot(simpleRepo()); - File workingDir = doUpdate(root, "4:b06a290a363b", new IncludeRule(".", ".", null)); - File hgrc = new File(workingDir, ".hg" + File.separator + "hgrc"); - String hgrcContent = FileUtil.readText(hgrc); - assertTrue(hgrcContent.contains("default = " + root.getProperty(Constants.REPOSITORY_PROP))); - } - - protected String getTestDataPath() { - return "mercurial-tests/testData"; - } -} +/* + * 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.agent.AgentRunningBuild; +import jetbrains.buildServer.agent.BuildAgentConfiguration; +import jetbrains.buildServer.agent.BuildProgressLogger; +import jetbrains.buildServer.util.FileUtil; +import jetbrains.buildServer.vcs.CheckoutRules; +import jetbrains.buildServer.vcs.IncludeRule; +import jetbrains.buildServer.vcs.VcsException; +import jetbrains.buildServer.vcs.VcsRoot; +import jetbrains.buildServer.vcs.impl.VcsRootImpl; +import org.jetbrains.annotations.NotNull; +import org.jmock.Expectations; +import org.jmock.Mockery; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +import java.io.File; +import java.io.IOException; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.*; + +/** + * @author Pavel.Sher + * Date: 30.07.2008 + */ +@Test +public class AgentSideCheckoutTest extends BaseMercurialTestCase { + + final static String HG_PATH_REFERENCE = "%" + HgDetector.AGENT_HG_PATH_PROPERTY + "%"; + private MercurialAgentSideVcsSupport myVcsSupport; + private File myWorkDir; + private File myMirrorsRootDir; + private Mockery myContext; + private BuildProgressLogger myLogger; + private int myBuildCounter = 0; + + @Override + @BeforeMethod + protected void setUp() throws Exception { + super.setUp(); + + myContext = new Mockery(); + + myMirrorsRootDir = myTempFiles.createTempDir(); + + final BuildAgentConfiguration agentConfig = myContext.mock(BuildAgentConfiguration.class); + myContext.checking(new Expectations() {{ + allowing(agentConfig).getCacheDirectory("mercurial"); will(returnValue(myMirrorsRootDir)); + allowing(agentConfig).getTempDirectory(); will(returnValue(myTempFiles.createTempDir())); + allowing(agentConfig).getParametersResolver(); will(returnValue(new HgPathResolver())); + }}); + + final AgentPluginConfigImpl pluginConfig = new AgentPluginConfigImpl(agentConfig); + myVcsSupport = new MercurialAgentSideVcsSupport(pluginConfig, new AgentHgPathProvider(agentConfig), new MirrorManagerImpl(pluginConfig)); + + myLogger = myContext.mock(BuildProgressLogger.class); + myContext.checking(new Expectations() {{ + allowing(myLogger).message(with(any(String.class))); + }}); + + myWorkDir = myTempFiles.createTempDir(); + + } + + public void should_work_when_path_to_hg_is_property() throws Exception { + VcsRootImpl root = new VcsRootBuilder() + .withUrl(LocalRepositoryUtil.prepareRepository(simpleRepo()).getAbsolutePath()) + .withHgPath(HG_PATH_REFERENCE).build(); + testUpdate(root, "4:b06a290a363b", "cleanPatch1/after", new IncludeRule(".", ".", null)); + } + + + public void checkout_on_agent() throws IOException, VcsException { + testUpdate(createVcsRoot(simpleRepo()), "4:b06a290a363b", "cleanPatch1/after", new IncludeRule(".", ".", null)); + } + + public void checkout_on_agent_include_rule_with_mapping() throws IOException, VcsException { + testUpdate(createVcsRoot(simpleRepo()), "4:b06a290a363b", "cleanPatch1/after", new IncludeRule("+:.", "subdir", null)); + } + + private void testUpdate(final VcsRoot vcsRoot, String version, String expected, final IncludeRule includeRule) throws VcsException, IOException { + File workDir = doUpdate(vcsRoot, version, includeRule); + + checkWorkingDir(expected, workDir); + } + + private void checkWorkingDir(final String expected, final File workDir) throws IOException { + FileUtil.delete(new File(workDir, ".hg")); + checkDirectoriesAreEqual(new File(getTestDataPath(), expected), workDir); + } + + private File doUpdate(@NotNull final VcsRoot root, @NotNull final String version, int timeoutSeconds) throws Exception { + ExecutorService executor = Executors.newSingleThreadExecutor(); + Future<File> future = executor.submit(new Callable<File>() { + public File call() throws Exception { + return doUpdate(root, version); + } + }); + executor.shutdown(); + executor.awaitTermination(timeoutSeconds, TimeUnit.SECONDS); + if (!future.isDone()) + fail("Update failed due to timeout"); + return future.get(); + } + + private File doUpdate(@NotNull VcsRoot root, @NotNull String version) throws VcsException { + return doUpdate(root, version, IncludeRule.createDefaultInstance()); + } + + private File doUpdate(final VcsRoot vcsRoot, final String version, final IncludeRule includeRule) throws VcsException { + return doUpdate(vcsRoot, version, includeRule, false); + } + + private File doUpdate(final VcsRoot vcsRoot, final String version, final IncludeRule includeRule, boolean useLocalMirrors) throws VcsException { + File actualWorkDir = new File(myWorkDir, includeRule.getTo()); + final Map<String, String> sharedConfigParameters = new HashMap<String, String>(); + sharedConfigParameters.put("teamcity.hg.use.local.mirrors", String.valueOf(useLocalMirrors)); + final AgentRunningBuild build = myContext.mock(AgentRunningBuild.class, "build" + myBuildCounter++); + myContext.checking(new Expectations() {{ + allowing(build).getBuildLogger(); will(returnValue(myLogger)); + allowing(build).getSharedConfigParameters(); will(returnValue(sharedConfigParameters)); + }}); + myVcsSupport.getUpdater(vcsRoot, new CheckoutRules(""), version, myWorkDir, build, false).process(includeRule, actualWorkDir); + + File hgDir = new File(actualWorkDir, ".hg"); + assertTrue(hgDir.isDirectory()); + return actualWorkDir; + } + + public void checkout_on_agent_from_branch() throws IOException, VcsException { + testUpdate(createVcsRoot(simpleRepo(), "test_branch"), "7:376dcf05cd2a", "patch3/after", new IncludeRule(".", ".", null)); + } + + public void update_on_agent() throws IOException, VcsException { + VcsRoot vcsRoot = createVcsRoot(simpleRepo()); + doUpdate(vcsRoot, "3:9522278aa38d", new IncludeRule(".", ".", null)); + File workDir = doUpdate(vcsRoot, "4:b06a290a363b", new IncludeRule(".", ".", null)); + + checkWorkingDir("patch1/after", workDir); + } + + public void update_on_agent_with_include_rule() throws IOException, VcsException { + VcsRoot vcsRoot = createVcsRoot(simpleRepo()); + doUpdate(vcsRoot, "3:9522278aa38d", new IncludeRule(".", "subdir", null)); + File workDir = doUpdate(vcsRoot, "4:b06a290a363b", new IncludeRule(".", "subdir", null)); + + checkWorkingDir("patch1/after", workDir); + } + + public void update_on_agent_from_branch() throws IOException, VcsException { + VcsRoot vcsRoot = createVcsRoot(simpleRepo(), "test_branch"); + doUpdate(vcsRoot, "7:376dcf05cd2a", new IncludeRule(".", ".", null)); + File workDir = doUpdate(vcsRoot, "8:04c3ae4c6312", new IncludeRule(".", ".", null)); + + checkWorkingDir("patch4/after", workDir); + } + + public void by_default_local_mirror_not_created() throws IOException, VcsException { + List<File> mirrors = FileUtil.getSubDirectories(myMirrorsRootDir); + assertTrue(mirrors.isEmpty()); + VcsRoot root = createVcsRoot(simpleRepo()); + doUpdate(root, "3:9522278aa38d", new IncludeRule(".", ".", null)); + mirrors = FileUtil.getSubDirectories(myMirrorsRootDir); + //though some dirs are created - they are empty => there were no clones into local mirrors + for (File mirror : mirrors) { + assertTrue(FileUtil.getSubDirectories(mirror).isEmpty()); + } + } + + public void local_mirror_is_created() throws IOException, VcsException { + List<File> mirrors = FileUtil.getSubDirectories(myMirrorsRootDir); + assertTrue(mirrors.isEmpty()); + VcsRoot root = createVcsRoot(simpleRepo()); + doUpdate(root, "3:9522278aa38d", new IncludeRule(".", ".", null), true); + mirrors = FileUtil.getSubDirectories(myMirrorsRootDir); + assertEquals(1, mirrors.size()); + File mirror = mirrors.get(0); + File dotHg = new File(mirror, ".hg"); + assertTrue(dotHg.exists()); + File hgrc = new File(dotHg, "hgrc"); + String hgrcContent = FileUtil.readText(hgrc); + assertTrue(hgrcContent.contains("default = " + root.getProperty(Constants.REPOSITORY_PROP))); + } + + public void new_repository_is_cloned_from_local_mirror() throws IOException, VcsException { + VcsRoot root = createVcsRoot(simpleRepo()); + File workingDir = doUpdate(root, "3:9522278aa38d", new IncludeRule(".", ".", null), true); + File mirrorDir = FileUtil.getSubDirectories(myMirrorsRootDir).get(0); + File hgrc = new File(workingDir, ".hg" + File.separator + "hgrc"); + String hgrcContent = FileUtil.readText(hgrc); + assertTrue(hgrcContent.contains("default = " + mirrorDir.getCanonicalPath())); + } + + public void repository_cloned_from_remote_start_cloning_from_local_mirror() throws IOException, VcsException { + VcsRoot root = createVcsRoot(simpleRepo()); + //clone from remote repository + File workingDir = doUpdate(root, "3:9522278aa38d", new IncludeRule(".", ".", null)); + String hgrcContent = FileUtil.readText(new File(workingDir, ".hg" + File.separator + "hgrc")); + + File workingDir2 = doUpdate(root, "3:9522278aa38d", new IncludeRule(".", ".", null), true); + File newMirrorDir = FileUtil.getSubDirectories(myMirrorsRootDir).get(0); + String hgrcContent2 = FileUtil.readText(new File(workingDir2, ".hg" + File.separator + "hgrc")); + assertFalse(hgrcContent2.equals(hgrcContent));//repository settings are changed + assertTrue(hgrcContent2.contains("default = " + newMirrorDir.getCanonicalPath()));//now it clones from local mirror + } + + public void repository_cloned_from_local_mirror_start_cloning_from_remote() throws IOException, VcsException { + VcsRoot root = createVcsRoot(simpleRepo()); + //clone from remote repository + File workingDir = doUpdate(root, "3:9522278aa38d", new IncludeRule(".", ".", null), true); + String hgrcContent = FileUtil.readText(new File(workingDir, ".hg" + File.separator + "hgrc")); + File newMirrorDir = FileUtil.getSubDirectories(myMirrorsRootDir).get(0); + assertTrue(hgrcContent.contains("default = " + newMirrorDir.getCanonicalPath()));//now it clones from local mirror + + File workingDir2 = doUpdate(root, "3:9522278aa38d", new IncludeRule(".", ".", null)); + String hgrcContent2 = FileUtil.readText(new File(workingDir2, ".hg" + File.separator + "hgrc")); + assertFalse(hgrcContent2.equals(hgrcContent));//repository settings are changed + assertTrue(hgrcContent2.contains("default = " + root.getProperty(Constants.REPOSITORY_PROP)));//now it clones from remote + } + + /** + * TW-15984 + */ + public void should_be_able_to_clone_into_non_empty_dir() throws IOException, VcsException { + VcsRoot vcsRoot = createVcsRoot(simpleRepo()); + doUpdate(vcsRoot, "3:9522278aa38d", new IncludeRule(".", "subdir", null)); + doUpdate(vcsRoot, "4:b06a290a363b", new IncludeRule(".", ".", null)); + } + + public void cloned_repo_should_contains_default_parameter_in_hgrc() throws VcsException, IOException { + VcsRoot root = createVcsRoot(simpleRepo()); + File workingDir = doUpdate(root, "4:b06a290a363b", new IncludeRule(".", ".", null)); + File hgrc = new File(workingDir, ".hg" + File.separator + "hgrc"); + String hgrcContent = FileUtil.readText(hgrc); + assertTrue(hgrcContent.contains("default = " + root.getProperty(Constants.REPOSITORY_PROP))); + } + + //TW-19703 + public void should_work_when_repository_is_locked() throws Exception{ + VcsRootImpl root = new VcsRootBuilder() + .withUrl(LocalRepositoryUtil.prepareRepository(simpleRepo()).getAbsolutePath()) + .withHgPath(HG_PATH_REFERENCE).build(); + File workingDir = doUpdate(root, "4:b06a290a363b"); + + lockRepository(workingDir); + doUpdate(root, "6:b9deb9a1c6f4", 10); + + lockWorkingDir(workingDir); + doUpdate(root, "10:9c6a6b4aede0", 10); + } + + private void lockRepository(@NotNull File workingDir) { + File lock = new File(workingDir, ".hg" + File.separator + "store" + File.separator + "lock"); + FileUtil.writeFile(lock, ""); + } + + private void lockWorkingDir(@NotNull File workingDir) { + File lock = new File(workingDir, ".hg" + File.separator + "wlock"); + FileUtil.writeFile(lock, ""); + } + + protected String getTestDataPath() { + return "mercurial-tests/testData"; + } + + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/AgentSideCheckoutWithSubreposTest.java Wed Feb 15 13:23:53 2012 +0400 @@ -0,0 +1,106 @@ +package jetbrains.buildServer.buildTriggers.vcs.mercurial; + +import com.intellij.openapi.diagnostic.Logger; +import jetbrains.buildServer.TempFiles; +import jetbrains.buildServer.agent.AgentRunningBuild; +import jetbrains.buildServer.agent.BuildAgentConfiguration; +import jetbrains.buildServer.agent.BuildProgressLogger; +import jetbrains.buildServer.agent.vcs.UpdateByIncludeRules2; +import jetbrains.buildServer.log.Log4jFactory; +import jetbrains.buildServer.util.FileUtil; +import jetbrains.buildServer.vcs.CheckoutRules; +import jetbrains.buildServer.vcs.IncludeRule; +import jetbrains.buildServer.vcs.VcsException; +import jetbrains.buildServer.vcs.VcsRoot; +import jetbrains.buildServer.vcs.impl.VcsRootImpl; +import org.jetbrains.annotations.NotNull; +import org.jmock.Expectations; +import org.jmock.Mockery; +import org.testng.annotations.AfterMethod; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +import java.io.File; +import java.io.IOException; +import java.util.Collections; + +/** + * @author dmitry.neverov + */ +@Test +public class AgentSideCheckoutWithSubreposTest { + + private TempFiles myTempFiles = new TempFiles(); + private File myOriginalRepositoriesParentDir; + private File myWorkDir; + private Mockery myContext; + private BuildProgressLogger myLogger; + private UpdateByIncludeRules2 myVcsSupport; + private int myBuildCounter = 0; + private File myR1Dir; + + static { + Logger.setFactory(new Log4jFactory()); + } + + @BeforeMethod + public void setUp() throws Exception { + myOriginalRepositoriesParentDir = myTempFiles.createTempDir(); + myWorkDir = new File(myOriginalRepositoriesParentDir, "agentWorkDir"); + myContext = new Mockery(); + + final BuildAgentConfiguration agentConfig = myContext.mock(BuildAgentConfiguration.class); + myContext.checking(new Expectations() {{ + allowing(agentConfig).getCacheDirectory("mercurial"); will(returnValue(myTempFiles.createTempDir())); + allowing(agentConfig).getParametersResolver(); will(returnValue(new HgPathResolver())); + }}); + + final AgentPluginConfigImpl pluginConfig = new AgentPluginConfigImpl(agentConfig); + myVcsSupport = new MercurialAgentSideVcsSupport(pluginConfig, new AgentHgPathProvider(agentConfig), new MirrorManagerImpl(pluginConfig)); + + myLogger = myContext.mock(BuildProgressLogger.class); + myContext.checking(new Expectations() {{ + allowing(myLogger).message(with(any(String.class))); + allowing(myLogger).warning(with(any(String.class))); + }}); + + myR1Dir = copy(new File("mercurial-tests/testData/subrepos/r1")); + copy(new File("mercurial-tests/testData/subrepos/r2")); + copy(new File("mercurial-tests/testData/subrepos/r3")); + } + + @AfterMethod + public void tearDown() { + myTempFiles.cleanup(); + } + + + public void subrepository_url_changed() throws Exception { + VcsRootImpl root = new VcsRootBuilder() + .withUrl(myR1Dir.getAbsolutePath()) + .build(); + doUpdate(root, "34017377d9c3"); + doUpdate(root, "d350e7209906"); + } + + + private void doUpdate(final VcsRoot vcsRoot, final String toVersion) throws VcsException { + final AgentRunningBuild build = myContext.mock(AgentRunningBuild.class, "build" + myBuildCounter++); + myContext.checking(new Expectations() {{ + allowing(build).getBuildLogger(); will(returnValue(myLogger)); + allowing(build).getSharedConfigParameters(); will(returnValue(Collections.emptyMap())); + }}); + myVcsSupport.getUpdater(vcsRoot, CheckoutRules.DEFAULT, toVersion, myWorkDir, build, false).process(IncludeRule.createDefaultInstance(), myWorkDir); + } + + + private File copy(@NotNull File originalRepositoryDir) throws IOException { + String dirName = originalRepositoryDir.getName(); + File copyDir = new File(myOriginalRepositoriesParentDir, dirName); + FileUtil.copyDir(originalRepositoryDir, copyDir); + if (new File(copyDir, "hg").isDirectory()) { + FileUtil.rename(new File(copyDir, "hg"), new File(copyDir, ".hg")); + } + return copyDir; + } +}
--- a/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/BaseMercurialTestCase.java Wed Feb 15 12:07:12 2012 +0400 +++ b/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/BaseMercurialTestCase.java Wed Feb 15 13:23:53 2012 +0400 @@ -1,74 +1,64 @@ -/* - * 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.MockSupport; -import jetbrains.buildServer.TempFiles; -import jetbrains.buildServer.vcs.impl.VcsRootImpl; -import jetbrains.buildServer.vcs.patches.PatchTestCase; -import org.jetbrains.annotations.NotNull; -import org.testng.annotations.AfterMethod; -import org.testng.annotations.BeforeMethod; - -import java.io.File; -import java.io.IOException; - -/** - * @author Pavel.Sher - * Date: 31.07.2008 - */ -public abstract class BaseMercurialTestCase extends PatchTestCase { - protected TempFiles myTempFiles; - protected MockSupport myMockSupport; - - @Override - @BeforeMethod - protected void setUp() throws Exception { - super.setUp(); - - myMockSupport = new MockSupport(); - myMockSupport.setUpMocks(); - myTempFiles = new TempFiles(); - } - - @AfterMethod - protected void tearDown() throws Exception { - myMockSupport.tearDownMocks(); - myTempFiles.cleanup(); - } - - protected VcsRootImpl createVcsRoot(@NotNull String repPath) throws IOException { - VcsRootImpl vcsRoot = new VcsRootImpl(1, Constants.VCS_NAME); - vcsRoot.addProperty(Constants.HG_COMMAND_PATH_PROP, new File(Util.getHgPath()).getAbsolutePath()); - File repository = LocalRepositoryUtil.prepareRepository(repPath); - vcsRoot.addProperty(Constants.REPOSITORY_PROP, repository.getAbsolutePath()); - return vcsRoot; - } - - protected VcsRootImpl createVcsRoot(@NotNull String repPath, @NotNull String branchName) throws IOException { - VcsRootImpl vcsRoot = createVcsRoot(repPath); - vcsRoot.addProperty(Constants.BRANCH_NAME_PROP, branchName); - return vcsRoot; - } - - protected void cleanRepositoryAfterTest(@NotNull String repPath) { - LocalRepositoryUtil.forgetRepository(repPath); - } - - protected String simpleRepo() { - return new File("mercurial-tests/testData/rep1").getAbsolutePath(); - } -} +/* + * 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.TempFiles; +import jetbrains.buildServer.vcs.impl.VcsRootImpl; +import jetbrains.buildServer.vcs.patches.PatchTestCase; +import org.jetbrains.annotations.NotNull; +import org.testng.annotations.AfterMethod; +import org.testng.annotations.BeforeMethod; + +import java.io.File; +import java.io.IOException; + +/** + * @author Pavel.Sher + * Date: 31.07.2008 + */ +public abstract class BaseMercurialTestCase extends PatchTestCase { + protected TempFiles myTempFiles; + + @Override + @BeforeMethod + protected void setUp() throws Exception { + super.setUp(); + myTempFiles = new TempFiles(); + } + + @AfterMethod + protected void tearDown() throws Exception { + myTempFiles.cleanup(); + } + + protected VcsRootImpl createVcsRoot(@NotNull String repPath) throws IOException { + File repository = LocalRepositoryUtil.prepareRepository(repPath); + return new VcsRootBuilder().withUrl(repository.getAbsolutePath()).build(); + } + + protected VcsRootImpl createVcsRoot(@NotNull String repPath, @NotNull String branchName) throws IOException { + File repository = LocalRepositoryUtil.prepareRepository(repPath); + return new VcsRootBuilder().withUrl(repository.getAbsolutePath()).withBranch(branchName).build(); + } + + protected void cleanRepositoryAfterTest(@NotNull String repPath) { + LocalRepositoryUtil.forgetRepository(repPath); + } + + protected String simpleRepo() { + return new File("mercurial-tests/testData/rep1").getAbsolutePath(); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/CleanupTest.java Wed Feb 15 13:23:53 2012 +0400 @@ -0,0 +1,163 @@ +package jetbrains.buildServer.buildTriggers.vcs.mercurial; + +import jetbrains.buildServer.TempFiles; +import jetbrains.buildServer.util.FileUtil; +import jetbrains.buildServer.vcs.SVcsRoot; +import jetbrains.buildServer.vcs.VcsManager; +import org.hamcrest.Description; +import org.hamcrest.TypeSafeMatcher; +import org.jetbrains.annotations.NotNull; +import org.jmock.Expectations; +import org.jmock.Mockery; +import org.testng.annotations.AfterMethod; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +import java.io.File; +import java.io.FileFilter; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +import static java.util.Arrays.asList; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.testng.AssertJUnit.assertEquals; + +/** + * @author dmitry.neverov + */ +@Test +public class CleanupTest { + + private Mockery myContext; + private TempFiles myTempFiles; + private File myCachesDir; + private Cleanup myCleanup; + private MirrorManager myMirrorManager; + private VcsManager myVcsManager; + private long myRootId = 0; + + @BeforeMethod + public void setUp() throws Exception { + myContext = new Mockery(); + myTempFiles = new TempFiles(); + myCachesDir = myTempFiles.createTempDir(); + + ServerPluginConfig config = new ServerPluginConfigBuilder().cachesDir(myCachesDir).build(); + myMirrorManager = new MirrorManagerImpl(config); + + myVcsManager = myContext.mock(VcsManager.class); + myCleanup = new Cleanup(myVcsManager, myMirrorManager, config, new ServerHgPathProvider(config)); + } + + @AfterMethod + public void tearDown() { + myTempFiles.cleanup(); + } + + + public void cleanup_should_remove_all_directories_that_are_not_mirrors() throws IOException { + final String url1 = "http://some.org/repository1"; + final String url2 = "http://some.org/repository2"; + final String url3 = "http://some.org/repository3"; + createDirFor(url1); + createDirFor(url2); + createDirFor(url3); + createUnusedDir(); + myContext.checking(new Expectations() {{ + atLeast(1).of(myVcsManager).getAllRegisteredVcsRoots(); + will(returnValue(asList(build(vcsRoot().withUrl(url1)), build(vcsRoot().withUrl(url2))))); + }}); + + myCleanup.run(); + + myContext.assertIsSatisfied(); + assertEquals(2, directoriesInside(myCachesDir).size()); + assertThat(myMirrorManager, knowsAboutAll(directoriesInside(myCachesDir))); + assertThat(mappingsFile(), containsOnly(directoriesInside(myCachesDir))); + } + + private void createUnusedDir() { + new File(myCachesDir, "some unused dir").mkdirs(); + } + + + private void createDirFor(@NotNull String url) { + myMirrorManager.getMirrorDir(url); + } + + private VcsRootBuilder vcsRoot() { + return new VcsRootBuilder().withId(myRootId++); + } + + private SVcsRoot build(@NotNull VcsRootBuilder builder) { + return builder.build(myContext); + } + + private List<File> directoriesInside(@NotNull File dir) { + return asList(dir.listFiles(new FileFilter() { + public boolean accept(File f) { + return f.isDirectory(); + } + })); + } + + private List<File> mappingsFile() throws IOException { + File mappingFile = new File(myCachesDir, "map"); + List<File> files = new ArrayList<File>(); + for (String line : FileUtil.readFile(mappingFile)) { + String[] fields = line.split("="); + files.add(new File(mappingFile.getParentFile(), fields[1].trim())); + } + return files; + } + + + static MirrorManagerKnowsAboutDirectories knowsAboutAll(@NotNull List<File> dirs) { + return new MirrorManagerKnowsAboutDirectories(dirs); + } + + static class MirrorManagerKnowsAboutDirectories extends TypeSafeMatcher<MirrorManager> { + private final List<File> myActualMirrorDirs; + + MirrorManagerKnowsAboutDirectories(@NotNull List<File> actualMirrorDirs) { + myActualMirrorDirs = actualMirrorDirs; + } + + @Override + public boolean matchesSafely(MirrorManager mirrorManager) { + List<File> knownMirrors = mirrorManager.getMirrors(); + return knownMirrors.size() == myActualMirrorDirs.size() && knownMirrors.containsAll(myActualMirrorDirs); + } + + public void describeTo(Description description) { + description.appendText("mirrorManager knows following directories: "); + for (File dir : myActualMirrorDirs) { + description.appendText(dir.getAbsolutePath()).appendText("\n"); + } + } + } + + + private static <T> ContainsOnly<T> containsOnly(Collection<T> expected) { + return new ContainsOnly<T>(expected); + } + + static class ContainsOnly<T> extends TypeSafeMatcher<Collection<T>> { + private final Collection<T> myExpected; + + ContainsOnly(@NotNull Collection<T> expected) { + myExpected = expected; + } + + @Override + public boolean matchesSafely(Collection<T> col) { + return col.size() == myExpected.size() && col.containsAll(myExpected); + } + + public void describeTo(Description description) { + description.appendText(myExpected.toString()); + } + } +}
--- a/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/CommandResultTest.java Wed Feb 15 12:07:12 2012 +0400 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,41 +0,0 @@ -package jetbrains.buildServer.buildTriggers.vcs.mercurial; - -import jetbrains.buildServer.ExecResult; -import jetbrains.buildServer.StreamGobbler; -import jetbrains.buildServer.buildTriggers.vcs.mercurial.command.CommandResult; -import org.jetbrains.annotations.NotNull; -import org.testng.annotations.Test; - -import java.io.ByteArrayInputStream; -import java.util.Collections; - -import static org.testng.AssertJUnit.assertFalse; - -/** - * @author dmitry.neverov - */ -@Test -public class CommandResultTest { - - public void output_should_not_contain_private_data() { - String password = "pass"; - ExecResult result = createExecResult(password, password); - CommandResult commandResult = new CommandResult(result, Collections.singleton(password)); - assertFalse(commandResult.getStdout().contains(password)); - assertFalse(commandResult.getStderr().contains(password)); - } - - private ExecResult createExecResult(@NotNull final String output, @NotNull final String error) { - ExecResult result = new ExecResult(); - result.setOutputGobbler(createStringGobbler(output)); - result.setErrorGobbler(createStringGobbler(error)); - return result; - } - - private StreamGobbler createStringGobbler(@NotNull final String str) { - StreamGobbler gobbler = new StreamGobbler(new ByteArrayInputStream(str.getBytes())); - gobbler.start(); - return gobbler; - } - -}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/DagFeaturesTest.java Wed Feb 15 13:23:53 2012 +0400 @@ -0,0 +1,92 @@ +package jetbrains.buildServer.buildTriggers.vcs.mercurial; + +import com.intellij.openapi.diagnostic.Logger; +import jetbrains.buildServer.TempFiles; +import jetbrains.buildServer.buildTriggers.vcs.mercurial.command.ChangeSetRevision; +import jetbrains.buildServer.log.Log4jFactory; +import jetbrains.buildServer.vcs.CheckoutRules; +import jetbrains.buildServer.vcs.ModificationData; +import jetbrains.buildServer.vcs.impl.VcsRootImpl; +import org.jetbrains.annotations.NotNull; +import org.jmock.Mockery; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +import java.io.File; +import java.util.List; + +import static org.testng.AssertJUnit.assertEquals; +import static org.testng.AssertJUnit.assertFalse; +import static org.testng.AssertJUnit.assertTrue; + +/** + * @author dmitry.neverov + */ +@Test +public class DagFeaturesTest { + + static { + Logger.setFactory(new Log4jFactory()); + } + + private TempFiles myTempFiles = new TempFiles(); + private MercurialVcsSupport myHg; + private String myRepository; + + @BeforeMethod + public void setUp() throws Exception { + ServerPluginConfig config = new ServerPluginConfigBuilder() + .cachesDir(myTempFiles.createTempDir()) + .pluginDataDir(myTempFiles.createTempDir()) + .build(); + myHg = Util.createMercurialServerSupport(new Mockery(), config); + + File original = new File("mercurial-tests/testData/rep2"); + File copy = new File(myTempFiles.createTempDir(), "rep2"); + LocalRepositoryUtil.copyRepository(original, copy); + myRepository = copy.getAbsolutePath(); + } + + public void tearDown() { + myTempFiles.cleanup(); + } + + + //TW-17882 + public void should_detect_changes_from_named_branches() throws Exception { + VcsRootImpl root = new VcsRootBuilder().withUrl(myRepository).build(); + + List<ModificationData> changes = myHg.collectChanges(root, "8:b6e2d176fe8e", "12:1e620196c4b6", CheckoutRules.DEFAULT); + assertEquals(4, changes.size()); + for (ModificationData change : changes) { + assertFalse(change.getParentRevisions().isEmpty()); + checkVersionsHaveNumbers(change.getParentRevisions()); + } + + changes = myHg.collectChanges(root, "12:1e620196c4b6", "18:df04faa7575a", CheckoutRules.DEFAULT); + assertEquals(6, changes.size()); + for (ModificationData change : changes) { + assertFalse(change.getParentRevisions().isEmpty()); + checkVersionsHaveNumbers(change.getParentRevisions()); + } + } + + + //TW-17882 + public void should_report_changes_only_from_merged_named_branches() throws Exception { + VcsRootImpl root = new VcsRootBuilder().withUrl(myRepository).build(); + List<ModificationData> changes = myHg.collectChanges(root, "1e620196c4b6", "505c5b9d01e6", CheckoutRules.DEFAULT); + assertEquals(2, changes.size()); + for (ModificationData change : changes) { + checkVersionsHaveNumbers(change.getParentRevisions()); + } + } + + + private void checkVersionsHaveNumbers(@NotNull List<String> versions) { + for (String version : versions) { + ChangeSetRevision rev = new ChangeSetRevision(version); + assertTrue(rev.getRevNumber() != -1); + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/HgPathResolver.java Wed Feb 15 13:23:53 2012 +0400 @@ -0,0 +1,64 @@ +package jetbrains.buildServer.buildTriggers.vcs.mercurial; + +import jetbrains.buildServer.parameters.NullValueResolver; +import jetbrains.buildServer.parameters.ProcessingResult; +import jetbrains.buildServer.parameters.ReferencesResolverUtil; +import org.jetbrains.annotations.NotNull; + +import java.io.IOException; + +/** +* @author dmitry.neverov +*/ +class HgPathResolver extends NullValueResolver { + @NotNull + public ProcessingResult resolve(@NotNull String value) { + if (ReferencesResolverUtil.containsReference(value)) { + if (value.equals(AgentSideCheckoutTest.HG_PATH_REFERENCE)) { + try { + return new ResolvedPath(Util.getHgPath()); + } catch (IOException e) { + return new Unresolved(value); + } + } else { + throw new IllegalArgumentException("Value resolver is asked to resolve " + value); + } + } else { + return new ResolvedPath(value); + } + } + + private static class ResolvedPath implements ProcessingResult { + private final String myPath; + ResolvedPath(final @NotNull String path) { + myPath = path; + } + public boolean isModified() { + return true; + } + @NotNull + public String getResult() { + return myPath; + } + public boolean isFullyResolved() { + return true; + } + } + + private static class Unresolved implements ProcessingResult { + private final String myUnresolvedValue; + Unresolved(@NotNull final String value) { + myUnresolvedValue = value; + } + public boolean isModified() { + return false; + } + @NotNull + public String getResult() { + return myUnresolvedValue; + } + public boolean isFullyResolved() { + return false; + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/HgVersionTest.java Wed Feb 15 13:23:53 2012 +0400 @@ -0,0 +1,24 @@ +package jetbrains.buildServer.buildTriggers.vcs.mercurial; + +import junit.framework.TestCase; +import org.jetbrains.annotations.NotNull; +import org.testng.annotations.Test; + +/** + * @author dmitry.neverov + */ +@Test +public class HgVersionTest extends TestCase { + + public void test_parse() { + checkParseCorrectly("Mercurial Distributed SCM (version 1.7.1+11-cc4e13c92dfa)", "1.7.1"); + checkParseCorrectly("Mercurial Distributed SCM (version 1.7)", "1.7.0"); + checkParseCorrectly("Mercurial Distributed SCM (version 1.5.2)", "1.5.2"); + } + + + private void checkParseCorrectly(@NotNull String versionToParse, @NotNull String expected) { + assertEquals(expected, HgVersion.parse(versionToParse).toString()); + } + +}
--- a/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/LocalRepositoryUtil.java Wed Feb 15 12:07:12 2012 +0400 +++ b/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/LocalRepositoryUtil.java Wed Feb 15 13:23:53 2012 +0400 @@ -1,57 +1,61 @@ -/* - * 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.TempFiles; -import jetbrains.buildServer.util.FileUtil; -import org.jetbrains.annotations.NotNull; - -import java.io.File; -import java.io.IOException; -import java.util.HashMap; -import java.util.Map; - -/** - * @author Pavel.Sher - * Date: 14.07.2008 - */ -public class LocalRepositoryUtil { - private final static TempFiles myTempFiles = new TempFiles(); - private final static Map<String, File> myRepositories = new HashMap<String, File>(); - static { - Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() { - public void run() { - myTempFiles.cleanup(); - } - })); - } - - public static File prepareRepository(@NotNull String repPath) throws IOException { - File repository = myRepositories.get(repPath); - if (repository != null) return repository; - final File tempDir = myTempFiles.createTempDir(); - FileUtil.copyDir(new File(repPath), tempDir); - if (new File(tempDir, "hg").isDirectory()) { - FileUtil.rename(new File(tempDir, "hg"), new File(tempDir, ".hg")); - } - myRepositories.put(repPath, tempDir); - return tempDir; - } - - public static void forgetRepository(@NotNull String repPath) { - myRepositories.remove(repPath); - } -} +/* + * 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.TempFiles; +import jetbrains.buildServer.util.FileUtil; +import org.jetbrains.annotations.NotNull; + +import java.io.File; +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; + +/** + * @author Pavel.Sher + * Date: 14.07.2008 + */ +public class LocalRepositoryUtil { + private final static TempFiles myTempFiles = new TempFiles(); + private final static Map<String, File> myRepositories = new HashMap<String, File>(); + static { + Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() { + public void run() { + myTempFiles.cleanup(); + } + })); + } + + public static File prepareRepository(@NotNull String repPath) throws IOException { + File repository = myRepositories.get(repPath); + if (repository != null) return repository; + final File tempDir = myTempFiles.createTempDir(); + copyRepository(new File(repPath), tempDir); + myRepositories.put(repPath, tempDir); + return tempDir; + } + + public static void forgetRepository(@NotNull String repPath) { + myRepositories.remove(repPath); + } + + + public static void copyRepository(File src, File dst) throws IOException { + FileUtil.copyDir(src, dst); + if (new File(dst, "hg").isDirectory()) + FileUtil.rename(new File(dst, "hg"), new File(dst, ".hg")); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/MercurialResetCacheHandlerTest.java Wed Feb 15 13:23:53 2012 +0400 @@ -0,0 +1,107 @@ +package jetbrains.buildServer.buildTriggers.vcs.mercurial; + +import jetbrains.buildServer.TempFiles; +import jetbrains.buildServer.util.cache.ResetCacheHandler; +import org.jmock.Expectations; +import org.jmock.Mockery; +import org.jmock.States; +import org.testng.annotations.AfterMethod; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +import java.io.File; +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; + +import static java.util.Arrays.asList; +import static java.util.Collections.emptyMap; +import static org.testng.AssertJUnit.*; + +/** + * @author dmitry.neverov + */ +@Test +public class MercurialResetCacheHandlerTest { + + private Mockery myContext; + private TempFiles myTempFiles; + private File myCachesDir; + private MirrorManager myMirrorManager; + private ResetCacheHandler myCacheHandler; + + @BeforeMethod + public void setUp() throws IOException { + myContext = new Mockery(); + myTempFiles = new TempFiles(); + myCachesDir = myTempFiles.createTempDir(); + myMirrorManager = myContext.mock(MirrorManager.class); + myCacheHandler = new MercurialResetCacheHandler(myMirrorManager); + } + + @AfterMethod + public void tearDown() { + myTempFiles.cleanup(); + } + + + public void cache_list_should_contains_entry_for_mercurial_in_general() { + assertEquals(asList("mercurial"), myCacheHandler.listCaches()); + } + + + public void mercurial_cache_is_empty_when_there_are_no_mirrors() { + myContext.checking(new Expectations() {{ + atLeast(1).of(myMirrorManager).getMappings(); will(returnValue(emptyMap())); + }}); + + assertTrue(myCacheHandler.isEmpty("mercurial")); + myContext.assertIsSatisfied(); + } + + + public void mercurial_cache_is_not_empty_when_mirrors_exist() { + final Map<String, File> mapping = new HashMap<String, File>() {{ + put("http://some.org/repository1", new File(myCachesDir, "a")); + put("http://some.org/repository2", new File(myCachesDir, "b")); + }}; + myContext.checking(new Expectations() {{ + atLeast(1).of(myMirrorManager).getMappings(); will(returnValue(mapping)); + }}); + + assertFalse(myCacheHandler.isEmpty("mercurial")); + myContext.assertIsSatisfied(); + } + + + public void reset_cache_should_reset_caches_for_all_mirrors() { + final String url1 = "http://some.org/repository1"; + final String url2 = "http://some.org/repository2"; + final File mirror1 = new File(myCachesDir, "a"); + final File mirror2 = new File(myCachesDir, "b"); + mirror1.mkdirs(); + mirror2.mkdirs(); + final Map<String, File> mapping = new HashMap<String, File>() {{ + put(url1, mirror1); + put(url2, mirror2); + }}; + final States stateOfMirror1 = myContext.states("mirror1").startsAs("initial"); + final States stateOfMirror2 = myContext.states("mirror2").startsAs("initial"); + myContext.checking(new Expectations() {{ + atLeast(1).of(myMirrorManager).getMappings(); will(returnValue(mapping)); + + one(myMirrorManager).lockDir(mirror1); when(stateOfMirror1.is("initial")); then(stateOfMirror1.is("locked")); + one(myMirrorManager).lockDir(mirror2); when(stateOfMirror2.is("initial")); then(stateOfMirror2.is("locked")); + + one(myMirrorManager).forgetDir(mirror1); when(stateOfMirror1.is("locked")); then(stateOfMirror1.is("reset")); + one(myMirrorManager).forgetDir(mirror2); when(stateOfMirror2.is("locked")); then(stateOfMirror2.is("reset")); + + one(myMirrorManager).unlockDir(mirror1); when(stateOfMirror1.is("reset")); + one(myMirrorManager).unlockDir(mirror2); when(stateOfMirror2.is("reset")); + }}); + + myCacheHandler.resetCache("mercurial"); + myContext.assertIsSatisfied(); + assertTrue(myCachesDir.listFiles().length == 0); + } +}
--- a/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/MercurialVcsSupportTest.java Wed Feb 15 12:07:12 2012 +0400 +++ b/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/MercurialVcsSupportTest.java Wed Feb 15 13:23:53 2012 +0400 @@ -19,16 +19,12 @@ import jetbrains.buildServer.buildTriggers.vcs.mercurial.command.CommandResult; import jetbrains.buildServer.buildTriggers.vcs.mercurial.command.CommandUtil; import jetbrains.buildServer.buildTriggers.vcs.mercurial.command.Settings; -import jetbrains.buildServer.serverSide.BuildServerListener; -import jetbrains.buildServer.serverSide.SBuildServer; -import jetbrains.buildServer.serverSide.ServerPaths; -import jetbrains.buildServer.util.EventDispatcher; import jetbrains.buildServer.vcs.*; import jetbrains.buildServer.vcs.impl.VcsRootImpl; import jetbrains.buildServer.vcs.patches.PatchBuilderImpl; import junit.framework.Assert; import org.jetbrains.annotations.NotNull; -import org.jmock.Mock; +import org.jmock.Mockery; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; @@ -37,33 +33,27 @@ import java.io.FilenameFilter; import java.io.IOException; import java.util.*; -import java.util.concurrent.Executors; -import java.util.concurrent.ScheduledExecutorService; import static com.intellij.openapi.util.io.FileUtil.copyDir; import static com.intellij.openapi.util.io.FileUtil.moveDirWithContent; +import static jetbrains.buildServer.buildTriggers.vcs.mercurial.VcsRootBuilder.vcsRoot; @Test public class MercurialVcsSupportTest extends BaseMercurialTestCase { + private MercurialVcsSupport myVcs; - private ServerPaths myServerPaths; + private String myRep2Path = new File("mercurial-tests/testData/rep2").getAbsolutePath(); + private ServerPluginConfig myPluginConfig; @BeforeMethod protected void setUp() throws Exception { super.setUp(); - - Mock vcsManagerMock = new Mock(VcsManager.class); - vcsManagerMock.stubs().method("registerVcsSupport"); - Mock serverMock = new Mock(SBuildServer.class); - ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor(); - serverMock.stubs().method("getExecutor").will(myMockSupport.returnValue(executor)); - - EventDispatcher<BuildServerListener> dispatcher = EventDispatcher.create(BuildServerListener.class); - - File systemDir = myTempFiles.createTempDir(); - myServerPaths = new ServerPaths(systemDir.getAbsolutePath(), systemDir.getAbsolutePath(), systemDir.getAbsolutePath()); - assertTrue(new File(myServerPaths.getCachesDir()).mkdirs()); - myVcs = new MercurialVcsSupport((VcsManager)vcsManagerMock.proxy(), myServerPaths, (SBuildServer)serverMock.proxy(), dispatcher, createPluginConfig()); + Mockery context = new Mockery(); + myPluginConfig = new ServerPluginConfigBuilder() + .cachesDir(myTempFiles.createTempDir()) + .pluginDataDir(myTempFiles.createTempDir()) + .build(); + myVcs = Util.createMercurialServerSupport(context, myPluginConfig); } protected String getTestDataPath() { @@ -82,7 +72,7 @@ } private List<ModificationData> collectChanges(@NotNull VcsRoot vcsRoot, @NotNull String from, @NotNull String to, @NotNull CheckoutRules rules) throws VcsException { - return ((CollectChangesByCheckoutRules) myVcs.getCollectChangesPolicy()).collectChanges(vcsRoot, from, to, rules); + return myVcs.collectChanges(vcsRoot, from, to, rules); } public void test_collect_changes_between_two_same_roots() throws Exception { @@ -92,6 +82,13 @@ do_check_for_collect_changes(changes); } + public void test_collect_changes_from_non_existing_revision() throws Exception { + VcsRootImpl vcsRoot = createVcsRoot(simpleRepo()); + VcsRootImpl sameVcsRoot = createVcsRoot(simpleRepo()); + List<ModificationData> changes = myVcs.collectChanges(vcsRoot, "0:9875b412a789", sameVcsRoot, "3:9522278aa38d", new CheckoutRules("")); + assertFalse(changes.isEmpty());//should return some changes from the toRoot + } + public void test_collect_changes() throws Exception { VcsRootImpl vcsRoot = createVcsRoot(simpleRepo()); List<ModificationData> changes = collectChanges(vcsRoot, "0:9875b412a788", "3:9522278aa38d", new CheckoutRules("")); @@ -143,7 +140,7 @@ ByteArrayOutputStream output = buildPatch(vcsRoot, null, "4:b06a290a363b", new CheckoutRules("")); checkPatchResult(output.toByteArray()); - File clonedReposParentDir = new File(myServerPaths.getCachesDir(), "mercurial"); + File clonedReposParentDir = myPluginConfig.getCachesDir(); assertTrue(clonedReposParentDir.isDirectory()); assertTrue(1 == clonedReposParentDir.list(new FilenameFilter() { public boolean accept(final File dir, final String name) { @@ -189,8 +186,8 @@ } public void test_clean_patch_with_subrepositories() throws Exception { - File r1 = new File(myServerPaths.getCachesDir() + File.separator + "mercurial", "r1"); - File r3 = new File(myServerPaths.getCachesDir() + File.separator + "mercurial", "r3"); + File r1 = new File(myPluginConfig.getCachesDir(), "r1"); + File r3 = new File(myPluginConfig.getCachesDir(), "r3"); copyDir(new File("mercurial-tests/testData/subrepos/r1"), r1); copyDir(new File("mercurial-tests/testData/subrepos/r3"), r3); moveDirWithContent(new File(r1, "hg"), new File(r1, ".hg")); @@ -263,6 +260,21 @@ } + public void test_tag_with_specified_username() throws IOException, VcsException { + final String customUserForTag = "John Doe <john@some.org>"; + File repository = LocalRepositoryUtil.prepareRepository(simpleRepo()); + VcsRoot root = vcsRoot().withUrl(repository.getAbsolutePath()).withUserForTag(customUserForTag).build(); + cleanRepositoryAfterTest(simpleRepo()); + + myVcs.label("tag_by_specified_user", "10:9c6a6b4aede0", root, CheckoutRules.DEFAULT); + + String currentVersion = myVcs.getCurrentVersion(root); + List<ModificationData> changes = myVcs.collectChanges(root, "10:9c6a6b4aede0", currentVersion, CheckoutRules.DEFAULT); + assertEquals(changes.size(), 1); + assertEquals(changes.get(0).getUserName(), customUserForTag); + } + + public void labeling_should_not_populate_files_in_local_mirror() throws Exception { VcsRootImpl root = createVcsRoot(simpleRepo()); cleanRepositoryAfterTest(simpleRepo()); @@ -344,15 +356,15 @@ VcsRootImpl vcsRoot = createVcsRoot(simpleRepo()); String repPath = vcsRoot.getProperty(Constants.REPOSITORY_PROP); vcsRoot.addProperty(Constants.REPOSITORY_PROP, repPath + "#test_branch"); - Settings settings = new Settings(vcsRoot); + Settings settings = new Settings(new ServerHgPathProvider(myPluginConfig), vcsRoot); assertEquals("test_branch", settings.getBranchName()); vcsRoot.addProperty(Constants.REPOSITORY_PROP, repPath + "#"); - settings = new Settings(vcsRoot); + settings = new Settings(new ServerHgPathProvider(myPluginConfig), vcsRoot); assertEquals("default", settings.getBranchName()); vcsRoot.addProperty(Constants.REPOSITORY_PROP, repPath); - settings = new Settings(vcsRoot); + settings = new Settings(new ServerHgPathProvider(myPluginConfig), vcsRoot); assertEquals("default", settings.getBranchName()); } @@ -383,6 +395,13 @@ assertEquals("10:fc524efc2bc4", changes.get(1).getVersion()); } + public void collectChanges_should_return_all_changes_from_branch() throws Exception { + VcsRootImpl defaultBranchRoot = createVcsRoot(myRep2Path, "default"); + VcsRootImpl personalBranchRoot = createVcsRoot(myRep2Path, "personal-branch"); + List<ModificationData> modifications = myVcs.collectChanges(defaultBranchRoot, "16:505c5b9d01e6", personalBranchRoot, "17:9ec402c74298", CheckoutRules.DEFAULT); + assertEquals(3, modifications.size()); + } + public void test_collect_changes_merge() throws Exception { VcsRootImpl vcsRoot = createVcsRoot(mergeCommittsRepo()); @@ -412,10 +431,10 @@ VcsRootImpl vcsRoot = createVcsRoot(mergeCommittsRepo()); List<ModificationData> changes = collectChanges(vcsRoot, "8:b6e2d176fe8e", "12:1e620196c4b6", CheckoutRules.DEFAULT); - assertEquals(changes.size(), 2); + assertEquals(changes.size(), 4); - assertFiles(Arrays.asList("A dir6/file6.txt"), changes.get(0)); - assertFiles(Arrays.asList("M dir6/file6.txt", "A dir5/file5.txt"), changes.get(1)); + assertFiles(Arrays.asList("A dir6/file6.txt"), changes.get(2)); + assertFiles(Arrays.asList("M dir6/file6.txt", "A dir5/file5.txt"), changes.get(3)); } //TW-17530 @@ -443,7 +462,7 @@ VcsRootImpl root = new VcsRootImpl(1, Constants.VCS_NAME); root.addAllProperties(myVcs.getDefaultVcsProperties()); root.addProperty(Constants.REPOSITORY_PROP, "http://host.com/path"); - Settings settings = new Settings(root); + Settings settings = new Settings(new ServerHgPathProvider(myPluginConfig), root); assertFalse(settings.isUncompressedTransfer()); } @@ -510,16 +529,5 @@ public void test_collect_changes_using_checkout_rules() { assertTrue(myVcs.getCollectChangesPolicy() instanceof CollectChangesByCheckoutRules); } - - private ServerPluginConfig createPluginConfig() { - return new ServerPluginConfig() { - public boolean isUsePullProtocol() { - return true; - } - public int getPullTimeout() { - return CommandUtil.DEFAULT_COMMAND_TIMEOUT_SEC; - } - }; - } }
--- a/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/MirrorManagerTest.java Wed Feb 15 12:07:12 2012 +0400 +++ b/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/MirrorManagerTest.java Wed Feb 15 13:23:53 2012 +0400 @@ -3,6 +3,7 @@ import jetbrains.buildServer.TempFiles; import jetbrains.buildServer.util.Hash; import junit.framework.TestCase; +import org.jetbrains.annotations.NotNull; import org.testng.annotations.AfterMethod; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; @@ -19,14 +20,19 @@ private TempFiles myTempFiles; private File myRootDir; - private MirrorManager myManager; + private MirrorManagerImpl myManager; @BeforeMethod public void setUp() throws Exception { myTempFiles = new TempFiles(); myRootDir = myTempFiles.createTempDir(); - myManager = new MirrorManager(myRootDir); + myManager = new MirrorManagerImpl(new PluginConfig() { + @NotNull + public File getCachesDir() { + return myRootDir; + } + }); } @AfterMethod @@ -67,7 +73,7 @@ final String url1 = "hg://some.com/repository.hg"; final String url2 = "hg://other.com/repository.hg"; - MirrorManager.HashCalculator hashWithCollision = new MirrorManager.HashCalculator() { + MirrorManagerImpl.HashCalculator hashWithCollision = new MirrorManagerImpl.HashCalculator() { public long calc(String value) { if (value.equals(url1) || value.equals(url2)) { return 0;//emulate collision @@ -78,11 +84,23 @@ }; //alone they get dir with the same name: - MirrorManager mm1 = new MirrorManager(myTempFiles.createTempDir()); + final File dir1 = myTempFiles.createTempDir(); + MirrorManagerImpl mm1 = new MirrorManagerImpl(new PluginConfig() { + @NotNull + public File getCachesDir() { + return dir1; + } + }); mm1.setHashCalculator(hashWithCollision); File separateMirrorDir1 = mm1.getMirrorDir(url1); - MirrorManager mm2 = new MirrorManager(myTempFiles.createTempDir()); + final File dir2 = myTempFiles.createTempDir(); + MirrorManagerImpl mm2 = new MirrorManagerImpl(new PluginConfig() { + @NotNull + public File getCachesDir() { + return dir2; + } + }); mm2.setHashCalculator(hashWithCollision); File separateMirrorDir2 = mm2.getMirrorDir(url2); @@ -102,10 +120,26 @@ File mirrorDir2 = myManager.getMirrorDir(url2); //emulate restart by creating a new manager for the same rootDir - MirrorManager manager = new MirrorManager(myRootDir); + MirrorManagerImpl manager = new MirrorManagerImpl(new PluginConfig() { + @NotNull + public File getCachesDir() { + return myRootDir; + } + }); assertEquals(2, manager.getMirrors().size()); assertTrue(manager.getMirrors().contains(mirrorDir1)); assertTrue(manager.getMirrors().contains(mirrorDir2)); } + + + public void should_be_able_to_forget_directory() throws Exception { + String url = "hg://some.org/repository"; + File mirror1 = myManager.getMirrorDir(url); + File dotHg = new File(mirror1, ".hg"); + dotHg.mkdirs(); + myManager.forgetDir(mirror1); + File mirror2 = myManager.getMirrorDir(url); + assertFalse(mirror2.equals(mirror1)); + } }
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/ServerHgPathProviderTest.java Wed Feb 15 13:23:53 2012 +0400 @@ -0,0 +1,54 @@ +package jetbrains.buildServer.buildTriggers.vcs.mercurial; + +import jetbrains.buildServer.buildTriggers.vcs.mercurial.command.Settings; +import jetbrains.buildServer.vcs.impl.VcsRootImpl; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +import static org.testng.AssertJUnit.assertEquals; + +/** + * @author dmitry.neverov + */ +@Test +public class ServerHgPathProviderTest { + + private String myServerWideHgPath; + private String myVcsRootHgPath; + + + @BeforeMethod + public void setUp() { + myServerWideHgPath = null; + myVcsRootHgPath = "/vcs/root/hg/path"; + } + + + public void server_should_use_settings_from_vcs_root_if_server_wide_path_is_not_set() throws Exception { + myServerWideHgPath = null; + HgPathProvider provider = createHgPathProvider(); + Settings settings = createSettings(provider); + assertEquals(myVcsRootHgPath, provider.getHgPath(settings)); + } + + + public void server_should_use_server_wide_path_if_it_is_set() throws Exception { + myServerWideHgPath = "/server-wide/hg/path"; + HgPathProvider provider = createHgPathProvider(); + Settings settings = createSettings(provider); + assertEquals(myServerWideHgPath, provider.getHgPath(settings)); + } + + + private ServerHgPathProvider createHgPathProvider() { + ServerPluginConfig config = new ServerPluginConfigBuilder().hgPath(myServerWideHgPath).build(); + return new ServerHgPathProvider(config); + } + + + private Settings createSettings(HgPathProvider hgPathProvider) throws Exception { + VcsRootImpl root = new VcsRootBuilder().withHgPath(myVcsRootHgPath).build(); + return new Settings(hgPathProvider, root); + } + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/ServerPluginConfigBuilder.java Wed Feb 15 13:23:53 2012 +0400 @@ -0,0 +1,70 @@ +package jetbrains.buildServer.buildTriggers.vcs.mercurial; + +import org.jetbrains.annotations.NotNull; + +import java.io.File; + +/** + * @author dmitry.neverov + */ +public class ServerPluginConfigBuilder { + + private boolean myUsePullProtocol = true; + private String myHgPath; + private File myCachesDir; + private File myPluginDataDir; + + + @NotNull + public ServerPluginConfig build() { + return new ServerPluginConfig() { + + public boolean isUsePullProtocol() { + return myUsePullProtocol; + } + + public String getHgPath() { + return myHgPath; + } + + @NotNull + public File getCachesDir() { + if (myCachesDir == null) + throw new IllegalStateException("Caches dir is not set"); + return myCachesDir; + } + + @NotNull + public File getPluginDataDir() { + if (myPluginDataDir == null) + throw new IllegalStateException("Plugin data dir is not set"); + return myPluginDataDir; + } + + public int getPullTimeout() { + return ServerPluginConfigImpl.DEFAULT_PULL_TIMEOUT_SECONDS; + } + }; + } + + + public ServerPluginConfigBuilder userPullProtocol(boolean doUse) { + myUsePullProtocol = doUse; + return this; + } + + public ServerPluginConfigBuilder hgPath(String hgPath) { + myHgPath = hgPath; + return this; + } + + public ServerPluginConfigBuilder cachesDir(File cachesDir) { + myCachesDir = cachesDir; + return this; + } + + public ServerPluginConfigBuilder pluginDataDir(File pluginDataDir) { + myPluginDataDir = pluginDataDir; + return this; + } +}
--- a/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/SettingsTest.java Wed Feb 15 12:07:12 2012 +0400 +++ b/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/SettingsTest.java Wed Feb 15 13:23:53 2012 +0400 @@ -1,136 +1,125 @@ -/* - * 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.TempFiles; -import jetbrains.buildServer.buildTriggers.vcs.mercurial.command.Settings; -import jetbrains.buildServer.vcs.impl.VcsRootImpl; -import junit.framework.TestCase; -import org.testng.annotations.AfterMethod; -import org.testng.annotations.BeforeMethod; -import org.testng.annotations.Test; - -/** - * @author Pavel.Sher - */ -@Test -public class SettingsTest extends TestCase { - - private TempFiles myTempFiles = new TempFiles(); - private MirrorManager myMirrorManager; - - @Override - @BeforeMethod - public void setUp() throws Exception { - myMirrorManager = new MirrorManager(myTempFiles.createTempDir()); - } - - @Override - @AfterMethod - public void tearDown() throws Exception { - myTempFiles.cleanup(); - } - - public void test_url_without_credentials() { - VcsRootImpl vcsRoot = createVcsRoot("http://host.com/path"); - Settings settings = new Settings(vcsRoot); - assertEquals("http://user:pwd@host.com/path", settings.getRepositoryUrl()); - } - - public void test_url_with_credentials() { - VcsRootImpl vcsRoot = createVcsRoot("http://user:pwd@host.com/path"); - Settings settings = new Settings(vcsRoot); - assertEquals("http://user:pwd@host.com/path", settings.getRepositoryUrl()); - } - - public void test_url_with_username() { - VcsRootImpl vcsRoot = createVcsRoot("http://user@host.com/path"); - Settings settings = new Settings(vcsRoot); - assertEquals("http://user:pwd@host.com/path", settings.getRepositoryUrl()); - } - - public void test_url_with_at_after_slash() { - VcsRootImpl vcsRoot = createVcsRoot("http://host.com/path@"); - Settings settings = new Settings(vcsRoot); - assertEquals("http://user:pwd@host.com/path@", settings.getRepositoryUrl()); - } - - public void test_url_with_at_in_username() { - VcsRootImpl vcsRoot = createVcsRoot("http://host.com/path", "my.name@gmail.com", "1234"); - Settings settings = new Settings(vcsRoot); - assertEquals("http://my.name%40gmail.com:1234@host.com/path", settings.getRepositoryUrl()); - } - - /** TW-13768 */ - public void test_underscore_in_host() { - VcsRootImpl vcsRoot = createVcsRoot("http://Klekovkin.SDK_GARANT:8000/", "my.name@gmail.com", "1234"); - Settings settings = new Settings(vcsRoot); - assertEquals("http://my.name%40gmail.com:1234@Klekovkin.SDK_GARANT:8000/", settings.getRepositoryUrl()); - } - - /** TW-13768 */ - public void test_underscore_in_host_with_credentials_in_url() { - VcsRootImpl vcsRoot = createVcsRoot("http://me:mypass@Klekovkin.SDK_GARANT:8000/"); - Settings settings = new Settings(vcsRoot); - assertEquals("http://me:mypass@Klekovkin.SDK_GARANT:8000/", settings.getRepositoryUrl()); - } - - public void test_windows_path() throws Exception { - VcsRootImpl vcsRoot = createVcsRoot("c:\\windows\\path"); - Settings settings = new Settings(vcsRoot); - assertEquals("c:\\windows\\path", settings.getRepositoryUrl()); - } - - public void test_file_scheme_has_no_credentials() { - VcsRootImpl vcsRoot = createVcsRoot("file:///path/to/repo", "my.name@gmail.com", "1234"); - Settings settings = new Settings(vcsRoot); - assertEquals("file:///path/to/repo", settings.getRepositoryUrl()); - } - - public void uncompressed_transfer() { - VcsRootImpl root = createVcsRoot("http://host.com/path"); - root.addProperty(Constants.UNCOMPRESSED_TRANSFER, "true"); - Settings settings = new Settings(root); - assertTrue(settings.isUncompressedTransfer()); - } - - //TW-18262 - public void ampersand_in_password() { - VcsRootImpl vcsRoot = createVcsRoot("http://some.org/path", "user", "m&n"); - Settings settings = new Settings(vcsRoot); - assertEquals("http://user:m%26n@some.org/path", settings.getRepositoryUrl()); - } - - //TW-18835 - public void test_ssh() { - VcsRootImpl vcsRoot = createVcsRoot("ssh://ourserver.com/mercurialrepo/", "user", "pwd"); - Settings settings = new Settings(vcsRoot); - assertEquals("ssh://user:pwd@ourserver.com/mercurialrepo/", settings.getRepositoryUrl()); - } - - private VcsRootImpl createVcsRoot(String url) { - return createVcsRoot(url, "user", "pwd"); - } - - private VcsRootImpl createVcsRoot(String url, String userName, String password) { - VcsRootImpl vcsRoot = new VcsRootImpl(1, Constants.VCS_NAME); - vcsRoot.addProperty(Constants.HG_COMMAND_PATH_PROP, "hg.exe"); - vcsRoot.addProperty(Constants.REPOSITORY_PROP, url); - vcsRoot.addProperty(Constants.USERNAME, userName); - vcsRoot.addProperty(Constants.PASSWORD, password); - return vcsRoot; - } -} +/* + * 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.buildTriggers.vcs.mercurial.command.Settings; +import jetbrains.buildServer.vcs.VcsRoot; +import jetbrains.buildServer.vcs.impl.VcsRootImpl; +import junit.framework.TestCase; +import org.jetbrains.annotations.NotNull; +import org.testng.annotations.Test; + +/** + * @author Pavel.Sher + */ +@Test +public class SettingsTest extends TestCase { + + public void test_url_without_credentials() { + VcsRootImpl vcsRoot = createVcsRoot("http://host.com/path"); + Settings settings = createSettings(vcsRoot); + assertEquals("http://user:pwd@host.com/path", settings.getRepositoryUrlWithCredentials()); + } + + public void test_url_with_credentials() { + VcsRootImpl vcsRoot = createVcsRoot("http://user:pwd@host.com/path"); + Settings settings = createSettings(vcsRoot); + assertEquals("http://user:pwd@host.com/path", settings.getRepositoryUrlWithCredentials()); + } + + public void test_url_with_username() { + VcsRootImpl vcsRoot = createVcsRoot("http://user@host.com/path"); + Settings settings = createSettings(vcsRoot); + assertEquals("http://user:pwd@host.com/path", settings.getRepositoryUrlWithCredentials()); + } + + public void test_url_with_at_after_slash() { + VcsRootImpl vcsRoot = createVcsRoot("http://host.com/path@"); + Settings settings = createSettings(vcsRoot); + assertEquals("http://user:pwd@host.com/path@", settings.getRepositoryUrlWithCredentials()); + } + + public void test_url_with_at_in_username() { + VcsRootImpl vcsRoot = createVcsRoot("http://host.com/path", "my.name@gmail.com", "1234"); + Settings settings = createSettings(vcsRoot); + assertEquals("http://my.name%40gmail.com:1234@host.com/path", settings.getRepositoryUrlWithCredentials()); + } + + /** TW-13768 */ + public void test_underscore_in_host() { + VcsRootImpl vcsRoot = createVcsRoot("http://Klekovkin.SDK_GARANT:8000/", "my.name@gmail.com", "1234"); + Settings settings = createSettings(vcsRoot); + assertEquals("http://my.name%40gmail.com:1234@Klekovkin.SDK_GARANT:8000/", settings.getRepositoryUrlWithCredentials()); + } + + /** TW-13768 */ + public void test_underscore_in_host_with_credentials_in_url() { + VcsRootImpl vcsRoot = createVcsRoot("http://me:mypass@Klekovkin.SDK_GARANT:8000/"); + Settings settings = createSettings(vcsRoot); + assertEquals("http://me:mypass@Klekovkin.SDK_GARANT:8000/", settings.getRepositoryUrlWithCredentials()); + } + + public void test_windows_path() throws Exception { + VcsRootImpl vcsRoot = createVcsRoot("c:\\windows\\path"); + Settings settings = createSettings(vcsRoot); + assertEquals("c:\\windows\\path", settings.getRepositoryUrlWithCredentials()); + } + + public void test_file_scheme_has_no_credentials() { + VcsRootImpl vcsRoot = createVcsRoot("file:///path/to/repo", "my.name@gmail.com", "1234"); + Settings settings = createSettings(vcsRoot); + assertEquals("file:///path/to/repo", settings.getRepositoryUrlWithCredentials()); + } + + public void uncompressed_transfer() { + VcsRootImpl root = createVcsRoot("http://host.com/path"); + root.addProperty(Constants.UNCOMPRESSED_TRANSFER, "true"); + Settings settings = createSettings(root); + assertTrue(settings.isUncompressedTransfer()); + } + + //TW-18262 + public void ampersand_in_password() { + VcsRootImpl vcsRoot = createVcsRoot("http://some.org/path", "user", "m&n"); + Settings settings = createSettings(vcsRoot); + assertEquals("http://user:m%26n@some.org/path", settings.getRepositoryUrlWithCredentials()); + } + + //TW-18835 + public void test_ssh() { + VcsRootImpl vcsRoot = createVcsRoot("ssh://ourserver.com/mercurialrepo/", "user", "pwd"); + Settings settings = createSettings(vcsRoot); + assertEquals("ssh://user:pwd@ourserver.com/mercurialrepo/", settings.getRepositoryUrlWithCredentials()); + } + + private VcsRootImpl createVcsRoot(String url) { + return createVcsRoot(url, "user", "pwd"); + } + + private VcsRootImpl createVcsRoot(String url, String userName, String password) { + VcsRootImpl vcsRoot = new VcsRootImpl(1, Constants.VCS_NAME); + vcsRoot.addProperty(Constants.HG_COMMAND_PATH_PROP, "hg.exe"); + vcsRoot.addProperty(Constants.REPOSITORY_PROP, url); + vcsRoot.addProperty(Constants.USERNAME, userName); + vcsRoot.addProperty(Constants.PASSWORD, password); + return vcsRoot; + } + + private Settings createSettings(@NotNull final VcsRoot root) { + ServerPluginConfig config = new ServerPluginConfigBuilder().build(); + return new Settings(new ServerHgPathProvider(config), root); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/UnrelatedResitoriesTest.java Wed Feb 15 13:23:53 2012 +0400 @@ -0,0 +1,104 @@ +package jetbrains.buildServer.buildTriggers.vcs.mercurial; + +import jetbrains.buildServer.TempFiles; +import jetbrains.buildServer.buildTriggers.vcs.mercurial.command.Settings; +import jetbrains.buildServer.buildTriggers.vcs.mercurial.command.UnknownRevisionException; +import jetbrains.buildServer.util.FileUtil; +import jetbrains.buildServer.vcs.CheckoutRules; +import jetbrains.buildServer.vcs.VcsException; +import jetbrains.buildServer.vcs.impl.VcsRootImpl; +import org.jetbrains.annotations.NotNull; +import org.jmock.Expectations; +import org.jmock.Mockery; +import org.testng.annotations.AfterMethod; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +import java.io.File; +import java.io.IOException; + +import static org.testng.AssertJUnit.assertEquals; +import static org.testng.AssertJUnit.assertTrue; + +/** + * @author dmitry.neverov + */ +@Test +public class UnrelatedResitoriesTest { + + private final static String CURRENT_VERSION_OF_NEW_REPO = "18:df04faa7575a"; + private MercurialVcsSupport myVcs; + private TempFiles myTempFiles; + private File myRepositoryLocation; + private VcsRootImpl myRoot; + private Mockery myContext; + private ServerPluginConfig myPluginConfig; + + @BeforeMethod + public void setUp() throws Exception { + myContext = new Mockery(); + myTempFiles = new TempFiles(); + myPluginConfig = new ServerPluginConfigBuilder() + .cachesDir(myTempFiles.createTempDir()) + .pluginDataDir(myTempFiles.createTempDir()) + .build(); + + myRepositoryLocation = myTempFiles.createTempDir(); + copyRepository(new File("mercurial-tests/testData/rep1")); + myRoot = new VcsRootBuilder().withUrl(myRepositoryLocation.getCanonicalPath()).build(); + } + + @AfterMethod + public void tearDown() { + myTempFiles.cleanup(); + } + + + public void should_be_able_to_sync_when_repository_became_unrelated() throws Exception { + myVcs = createVcs(); + syncRepository(); + repositoryBecamesUnrelated(); + String currentVersion = syncRepository(); + assertEquals(CURRENT_VERSION_OF_NEW_REPO, currentVersion); + } + + + public void should_return_no_changes_when_fromRevision_is_from_unrelated_repository() throws Exception { + final CommandFactory factory = myContext.mock(CommandFactory.class); + final CollectChangesCommand commandExecutedWithException = myContext.mock(CollectChangesCommand.class); + myVcs = createVcs(factory); + myContext.checking(new Expectations(){{ + allowing(factory).getCollectChangesCommand(with(any(Settings.class)), with(any(File.class))); + will(returnValue(commandExecutedWithException)); + allowing(commandExecutedWithException).execute(with(any(String.class)), with(any(String.class))); + will(throwException(new UnknownRevisionException("1234"))); + }}); + + String currentVersionOfOldRepo = syncRepository(); + repositoryBecamesUnrelated(); + String currentVersionOfNewRepo = syncRepository(); + assertTrue(myVcs.collectChanges(myRoot, currentVersionOfOldRepo, currentVersionOfNewRepo, CheckoutRules.DEFAULT).isEmpty()); + } + + + private String syncRepository() throws VcsException { + return myVcs.getCurrentVersion(myRoot); + } + + private void repositoryBecamesUnrelated() throws IOException { + FileUtil.delete(myRepositoryLocation); + copyRepository(new File("mercurial-tests/testData/rep2")); + } + + private void copyRepository(@NotNull final File src) throws IOException { + LocalRepositoryUtil.copyRepository(src, myRepositoryLocation); + } + + private MercurialVcsSupport createVcs() throws IOException { + return Util.createMercurialServerSupport(myContext, myPluginConfig); + } + + private MercurialVcsSupport createVcs(@NotNull final CommandFactory factory) throws IOException { + return Util.createMercurialServerSupport(myContext, myPluginConfig, factory); + } +}
--- a/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/Util.java Wed Feb 15 12:07:12 2012 +0400 +++ b/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/Util.java Wed Feb 15 13:23:53 2012 +0400 @@ -1,6 +1,18 @@ package jetbrains.buildServer.buildTriggers.vcs.mercurial; +import jetbrains.buildServer.serverSide.BuildServerListener; +import jetbrains.buildServer.serverSide.SBuildServer; +import jetbrains.buildServer.util.EventDispatcher; +import jetbrains.buildServer.util.cache.ResetCacheRegister; +import jetbrains.buildServer.vcs.VcsManager; +import org.jetbrains.annotations.NotNull; +import org.jmock.Expectations; +import org.jmock.Mockery; + +import java.io.File; import java.io.IOException; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; /** * @author dmitry.neverov @@ -14,8 +26,24 @@ if (providedHg != null) { return providedHg; } else { - return "mercurial-tests/testData/bin/hg.exe"; + return new File("mercurial-tests/testData/bin/hg.exe").getAbsolutePath(); } } + + public static MercurialVcsSupport createMercurialServerSupport(@NotNull Mockery context, ServerPluginConfig config) throws IOException { + return createMercurialServerSupport(context, config, new CommandFactoryImpl(config)); + } + + + public static MercurialVcsSupport createMercurialServerSupport(@NotNull Mockery context, @NotNull ServerPluginConfig config, @NotNull CommandFactory commandFactory) throws IOException { + VcsManager vcsManager = context.mock(VcsManager.class); + final SBuildServer server = context.mock(SBuildServer.class); + final ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor(); + context.checking(new Expectations() {{ + allowing(server).getExecutor(); will(returnValue(executor)); + }}); + EventDispatcher<BuildServerListener> dispatcher = EventDispatcher.create(BuildServerListener.class); + return new MercurialVcsSupport(vcsManager, server, dispatcher, new ResetCacheRegister(), config, new ServerHgPathProvider(config), commandFactory, new MirrorManagerImpl(config)); + } }
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/VcsRootBuilder.java Wed Feb 15 13:23:53 2012 +0400 @@ -0,0 +1,97 @@ +package jetbrains.buildServer.buildTriggers.vcs.mercurial; + +import jetbrains.buildServer.vcs.SVcsRoot; +import jetbrains.buildServer.vcs.impl.VcsRootImpl; +import org.jetbrains.annotations.NotNull; +import org.jmock.Expectations; +import org.jmock.Mockery; + +import java.io.IOException; + +/** + * @author dmitry.neverov + */ +public class VcsRootBuilder { + + private String myRepository; + private String myUsername; + private String myPassword; + private String myBranch; + private long myRootId = 1L; + private String myHgPath; + private String myUserForTag; + + public static VcsRootBuilder vcsRoot() { + return new VcsRootBuilder(); + } + + public VcsRootImpl build() throws IOException { + VcsRootImpl vcsRoot = new VcsRootImpl(myRootId, Constants.VCS_NAME); + vcsRoot.addProperty(Constants.REPOSITORY_PROP, myRepository); + vcsRoot.addProperty(Constants.HG_COMMAND_PATH_PROP, myHgPath != null ? myHgPath : Util.getHgPath()); + vcsRoot.addProperty(Constants.USERNAME, myUsername); + vcsRoot.addProperty(Constants.PASSWORD, myPassword); + vcsRoot.addProperty(Constants.BRANCH_NAME_PROP, myBranch); + vcsRoot.addProperty(Constants.USER_FOR_TAG, myUserForTag); + return vcsRoot; + } + + + public SVcsRoot build(Mockery context) { + final SVcsRoot root = context.mock(SVcsRoot.class, "SVcsRoot" + myRootId); + context.checking(new Expectations() {{ + allowing(root).getVcsName(); will(returnValue(Constants.VCS_NAME)); + allowing(root).getProperty(with(Constants.REPOSITORY_PROP)); will(returnValue(myRepository)); + allowing(root).getProperty(with(Constants.HG_COMMAND_PATH_PROP)); will(returnValue(myHgPath)); + allowing(root).getProperty(with(Constants.BRANCH_NAME_PROP)); will(returnValue(myBranch)); + allowing(root).getProperty(with(Constants.SERVER_CLONE_PATH_PROP)); will(returnValue(null)); + allowing(root).getProperty(with(Constants.USERNAME)); will(returnValue(myUsername)); + allowing(root).getProperty(with(Constants.PASSWORD)); will(returnValue(myPassword)); + allowing(root).getProperty(with(Constants.UNCOMPRESSED_TRANSFER)); will(returnValue(null)); + allowing(root).getProperty(with(Constants.USER_FOR_TAG)); will(returnValue(myUserForTag)); + }}); + return root; + } + + + public VcsRootBuilder withUrl(@NotNull String repository) { + myRepository = repository; + return this; + } + + + public VcsRootBuilder withUserName(@NotNull String username) { + myUsername = username; + return this; + } + + + public VcsRootBuilder withPassword(@NotNull String password) { + myPassword = password; + return this; + } + + + public VcsRootBuilder withBranch(@NotNull String branch) { + myBranch = branch; + return this; + } + + + public VcsRootBuilder withId(long rootId) { + myRootId = rootId; + return this; + } + + + public VcsRootBuilder withHgPath(String hgPath) { + myHgPath = hgPath; + return this; + } + + + public VcsRootBuilder withUserForTag(String username) { + myUserForTag = username; + return this; + } +}
--- a/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/BaseCommandTest.java Wed Feb 15 12:07:12 2012 +0400 +++ b/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/BaseCommandTest.java Wed Feb 15 13:23:53 2012 +0400 @@ -2,9 +2,6 @@ import com.intellij.execution.configurations.GeneralCommandLine; import com.intellij.openapi.util.SystemInfo; -import jetbrains.buildServer.buildTriggers.vcs.mercurial.Constants; -import jetbrains.buildServer.buildTriggers.vcs.mercurial.Util; -import jetbrains.buildServer.vcs.impl.VcsRootImpl; import junit.framework.TestCase; import org.testng.annotations.Test; @@ -18,13 +15,8 @@ public class BaseCommandTest extends TestCase { public void should_quote_command_line_arguments() throws IOException { - VcsRootImpl root = new VcsRootImpl(1, "rootForTest"); - root.addProperty(Constants.REPOSITORY_PROP, "http://some.org/repo.hg"); - root.addProperty(Constants.HG_COMMAND_PATH_PROP, Util.getHgPath()); File workingDir = new File("some dir"); - Settings settings = new Settings(root); - - BaseCommand command = new BaseCommand(settings, workingDir); + BaseCommand command = new BaseCommand("/path/to/hg", workingDir); GeneralCommandLine cl = command.createCommandLine(); cl.addParameter("param with spaces"); cl.addParameter("param with quote \" rm -rf /");
--- a/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/BaseCommandTestCase.java Wed Feb 15 12:07:12 2012 +0400 +++ b/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/BaseCommandTestCase.java Wed Feb 15 13:23:53 2012 +0400 @@ -1,96 +1,92 @@ -/* - * 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.command; - -import jetbrains.buildServer.BaseTestCase; -import jetbrains.buildServer.TempFiles; -import jetbrains.buildServer.buildTriggers.vcs.mercurial.Constants; -import jetbrains.buildServer.buildTriggers.vcs.mercurial.LocalRepositoryUtil; -import jetbrains.buildServer.buildTriggers.vcs.mercurial.MirrorManager; -import jetbrains.buildServer.buildTriggers.vcs.mercurial.Util; -import jetbrains.buildServer.vcs.VcsException; -import jetbrains.buildServer.vcs.VcsRoot; -import jetbrains.buildServer.vcs.impl.VcsRootImpl; -import org.jetbrains.annotations.NotNull; - -import java.io.File; -import java.io.IOException; -import java.util.HashMap; -import java.util.Map; - -public class BaseCommandTestCase extends BaseTestCase { - private String myRepository; - private String myUsername; - private String myPassword; - private boolean myCloneRequired; - - public BaseCommandTestCase() { - } - - protected void setRepository(final String repository, boolean cloneRequired) { - myRepository = repository; - myCloneRequired = cloneRequired; - } - - protected void setUsername(final String username) { - myUsername = username; - } - - protected void setPassword(final String password) { - myPassword = password; - } - - protected <T> T runCommand(CommandExecutor<T> executor) throws IOException, VcsException { - Map<String, String> vcsRootProps = new HashMap<String, String>(); - - vcsRootProps.put(Constants.REPOSITORY_PROP, myRepository); - - if (myCloneRequired) { - File repository = LocalRepositoryUtil.prepareRepository(new File(myRepository).getAbsolutePath()); - vcsRootProps.put(Constants.REPOSITORY_PROP, repository.getAbsolutePath()); - } - - vcsRootProps.put(Constants.HG_COMMAND_PATH_PROP, Util.getHgPath()); - if (myUsername != null) { - vcsRootProps.put(Constants.USERNAME, myUsername); - } - if (myPassword != null) { - vcsRootProps.put(Constants.PASSWORD, myPassword); - } - - TempFiles tf = new TempFiles(); - File parentDir = tf.createTempDir(); - - MirrorManager mirrorManager = new MirrorManager(parentDir); - VcsRoot vcsRoot = new VcsRootImpl(1, vcsRootProps); - Settings settings = new Settings(vcsRoot); - final File workingDir = mirrorManager.getMirrorDir(settings.getRepositoryUrl()); - settings.setCustomWorkingDir(workingDir); - try { - if (myCloneRequired) { - new CloneCommand(settings, workingDir).execute(); - } - - return executor.execute(settings, workingDir); - } finally { - tf.cleanup(); - } - } - - public interface CommandExecutor<T> { - T execute(@NotNull Settings settings, File workingDir) throws VcsException; - } -} +/* + * 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.command; + +import jetbrains.buildServer.BaseTestCase; +import jetbrains.buildServer.TempFiles; +import jetbrains.buildServer.buildTriggers.vcs.mercurial.*; +import jetbrains.buildServer.vcs.VcsException; +import jetbrains.buildServer.vcs.VcsRoot; +import jetbrains.buildServer.vcs.impl.VcsRootImpl; +import org.jetbrains.annotations.NotNull; + +import java.io.File; +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; + +public class BaseCommandTestCase extends BaseTestCase { + private String myRepository; + private String myUsername; + private String myPassword; + private boolean myCloneRequired; + + public BaseCommandTestCase() { + } + + protected void setRepository(final String repository, boolean cloneRequired) { + myRepository = repository; + myCloneRequired = cloneRequired; + } + + protected void setUsername(final String username) { + myUsername = username; + } + + protected void setPassword(final String password) { + myPassword = password; + } + + protected <T> T runCommand(CommandExecutor<T> executor) throws IOException, VcsException { + Map<String, String> vcsRootProps = new HashMap<String, String>(); + + vcsRootProps.put(Constants.REPOSITORY_PROP, myRepository); + + if (myCloneRequired) { + File repository = LocalRepositoryUtil.prepareRepository(new File(myRepository).getAbsolutePath()); + vcsRootProps.put(Constants.REPOSITORY_PROP, repository.getAbsolutePath()); + } + + vcsRootProps.put(Constants.HG_COMMAND_PATH_PROP, Util.getHgPath()); + if (myUsername != null) { + vcsRootProps.put(Constants.USERNAME, myUsername); + } + if (myPassword != null) { + vcsRootProps.put(Constants.PASSWORD, myPassword); + } + + TempFiles tf = new TempFiles(); + VcsRoot vcsRoot = new VcsRootImpl(1, vcsRootProps); + ServerPluginConfig config = new ServerPluginConfigBuilder().cachesDir(tf.createTempDir()).build(); + MirrorManager mirrorManager = new MirrorManagerImpl(config); + Settings settings = new Settings(new ServerHgPathProvider(config), vcsRoot); + final File workingDir = mirrorManager.getMirrorDir(settings.getRepository()); + settings.setCustomWorkingDir(workingDir); + try { + if (myCloneRequired) { + new CloneCommand(settings, workingDir).execute(); + } + + return executor.execute(settings, workingDir); + } finally { + tf.cleanup(); + } + } + + public interface CommandExecutor<T> { + T execute(@NotNull Settings settings, File workingDir) throws VcsException; + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/CatCommandTest.java Wed Feb 15 13:23:53 2012 +0400 @@ -0,0 +1,74 @@ +package jetbrains.buildServer.buildTriggers.vcs.mercurial.command; + +import jetbrains.buildServer.util.FileUtil; +import jetbrains.buildServer.vcs.VcsException; +import org.jetbrains.annotations.NotNull; +import org.testng.annotations.Test; + +import java.io.File; +import java.io.FilenameFilter; +import java.io.IOException; +import java.util.List; + +import static com.intellij.openapi.util.io.FileUtil.delete; +import static java.util.Arrays.asList; + +/** + * @author dmitry.neverov + */ +@Test +public class CatCommandTest extends BaseCommandTestCase { + + //TW-13178 + public void command_should_not_leave_garbage_in_temp_dir() throws IOException, VcsException { + cleanCatResultDirs(); + setRepository("mercurial-tests/testData/rep1", true); + final String nonExisting = "/non/existing/path"; + try { + runCat(asList(nonExisting)); + fail("exception should be thrown for non-existing path"); + } catch (UnknownFileException e) { + assertEquals(nonExisting, e.getPath()); + checkTempDirDoesNotContainCatResults(); + } + } + + public void should_not_throw_exception_if_not_asked_to() throws IOException, VcsException { + cleanCatResultDirs(); + setRepository("mercurial-tests/testData/rep1", true); + runCommand(new CommandExecutor<File>() { + public File execute(@NotNull final Settings settings, @NotNull final File workingDir) throws VcsException { + CatCommand cat = new CatCommand(settings, workingDir); + return cat.execute(asList("/non/existing/path"), false); + } + }); + } + + private void cleanCatResultDirs() { + for (File f : getCatResultDirs()) + delete(f); + } + + private void checkTempDirDoesNotContainCatResults() throws IOException { + File[] catresults = getCatResultDirs(); + assertTrue("cat result dirs are not cleaned: " + asList(catresults), catresults.length == 0); + } + + private File[] getCatResultDirs() { + String tempDirPath = FileUtil.getTempDirectory(); + return new File(tempDirPath).listFiles(new FilenameFilter() { + public boolean accept(File dir, String name) { + return name.startsWith("mercurial") && name.endsWith("catresult"); + } + }); + } + + private File runCat(@NotNull final List<String> paths) throws IOException, VcsException { + return runCommand(new CommandExecutor<File>() { + public File execute(@NotNull final Settings settings, @NotNull final File workingDir) throws VcsException { + CatCommand cat = new CatCommand(settings, workingDir); + return cat.execute(paths); + } + }); + } +}
--- a/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/CloneCommandTest.java Wed Feb 15 12:07:12 2012 +0400 +++ b/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/CloneCommandTest.java Wed Feb 15 13:23:53 2012 +0400 @@ -2,9 +2,7 @@ import jetbrains.buildServer.BaseTestCase; import jetbrains.buildServer.TempFiles; -import jetbrains.buildServer.buildTriggers.vcs.mercurial.Constants; -import jetbrains.buildServer.buildTriggers.vcs.mercurial.LocalRepositoryUtil; -import jetbrains.buildServer.buildTriggers.vcs.mercurial.Util; +import jetbrains.buildServer.buildTriggers.vcs.mercurial.*; import jetbrains.buildServer.vcs.VcsException; import jetbrains.buildServer.vcs.impl.VcsRootImpl; import org.testng.annotations.AfterMethod; @@ -44,7 +42,8 @@ root.addProperty(Constants.HG_COMMAND_PATH_PROP, Util.getHgPath()); File workingDir = myTempFiles.createTempDir(); - Settings settings = new Settings(root); + ServerPluginConfig config = new ServerPluginConfigBuilder().cachesDir(myTempFiles.createTempDir()).build(); + Settings settings = new Settings(new ServerHgPathProvider(config), root); settings.setCustomWorkingDir(workingDir); CloneCommand clone = new CloneCommand(settings, workingDir);
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/CommandResultTest.java Wed Feb 15 13:23:53 2012 +0400 @@ -0,0 +1,182 @@ +package jetbrains.buildServer.buildTriggers.vcs.mercurial.command; + +import com.intellij.openapi.diagnostic.Logger; +import jetbrains.buildServer.ExecResult; +import jetbrains.buildServer.StreamGobbler; +import jetbrains.buildServer.vcs.VcsException; +import org.apache.log4j.Level; +import org.jetbrains.annotations.NonNls; +import org.jetbrains.annotations.NotNull; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +import java.io.ByteArrayInputStream; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; + +import static org.testng.AssertJUnit.*; + +/** + * @author dmitry.neverov + */ +@Test +public class CommandResultTest { + + private RecordingLogger myLogger; + + @BeforeMethod + public void setUp() { + myLogger = new RecordingLogger(); + } + + public void output_should_not_contain_private_data() { + String password = "pass"; + CommandResult commandResult = commandResultFor(execResult().withStdout(password).withStderr(password), password); + assertFalse(commandResult.getStdout().contains(password)); + myLogger.assertLogMessagesDontContain(password); + } + + @Test(expectedExceptions = UnrelatedRepositoryException.class) + public void should_detect_unrelated_repository_error() throws VcsException { + String unrelatedRepositoryStderr = "abort: repository is unrelated\n"; + CommandResult commandResult = commandResultFor(execResult().withStderr(unrelatedRepositoryStderr).withExitCode(255)); + commandResult.checkCommandFailed(); + } + + @Test + public void should_detect_unknown_revision_error() throws VcsException { + String unknownRevision = "9c6a6b4aede0"; + String unknownRevisionError = "abort: unknown revision '" + unknownRevision + "'\n"; + CommandResult commandResult = commandResultFor(execResult().withStderr(unknownRevisionError).withExitCode(255)); + try { + commandResult.checkCommandFailed(); + fail("unknown exception should be thrown"); + } catch (UnknownRevisionException e) { + assertEquals(unknownRevision, e.getRevision()); + } + } + + @Test(expectedExceptions = VcsException.class) + public void should_detect_failure_when_delegate_has_exception() throws VcsException { + CommandResult commandResult = commandResultFor(execResult().withException(new RuntimeException())); + commandResult.checkCommandFailed(); + } + + public void should_detect_failure_with_non_zero_exit_code() throws VcsException { + CommandResult commandResult = commandResultFor(execResult().withExitCode(1)); + commandResult.checkCommandFailed(); + } + + + ExecResultBuilder execResult() { + return new ExecResultBuilder(); + } + + CommandResult commandResultFor(ExecResultBuilder builder, String... privateData) { + return new CommandResult(myLogger, "", builder.build(), new HashSet<String>(Arrays.asList(privateData))); + } + + private class ExecResultBuilder { + private String myStdout = ""; + private String myStderr = ""; + private Throwable myException = null; + private int myExitCode = 0; + + ExecResultBuilder withStdout(String stdout) { + myStdout = stdout; + return this; + } + ExecResultBuilder withStderr(String stderr) { + myStderr = stderr; + return this; + } + ExecResultBuilder withException(Throwable exception) { + myException = exception; + return this; + } + ExecResultBuilder withExitCode(int exitCode) { + myExitCode = exitCode; + return this; + } + + ExecResult build() { + ExecResult result = new ExecResult(); + result.setOutputGobbler(createStringGobbler(myStdout)); + result.setErrorGobbler(createStringGobbler(myStderr)); + if (myException != null) + result.setException(myException); + if (myExitCode != 0) + result.setExitCode(myExitCode); + return result; + } + + private StreamGobbler createStringGobbler(@NotNull final String str) { + StreamGobbler gobbler = new StreamGobbler(new ByteArrayInputStream(str.getBytes())); + gobbler.start(); + try { + Thread.sleep(10);//wait for gobbler to read string + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + return gobbler; + } + } + + private class RecordingLogger extends Logger { + + private List<String> myMessages = new ArrayList<String>(); + + public void assertLogMessagesDontContain(@NotNull String... strs) { + for (String s : strs) { + for (String message : myMessages) + assertFalse("'" + s + "' was logged", message.contains(s)); + } + } + + @Override + public boolean isDebugEnabled() { + return true; + } + + @Override + public void debug(@NonNls String s) { + myMessages.add(s); + } + + @Override + public void debug(Throwable throwable) { + myMessages.add(throwable.getMessage()); + } + + @Override + public void debug(@NonNls String s, Throwable throwable) { + myMessages.add(s); + } + + @Override + public void error(@NonNls String s, Throwable throwable, @NonNls String... strings) { + myMessages.add(s); + } + + @Override + public void info(@NonNls String s) { + myMessages.add(s); + } + + @Override + public void info(@NonNls String s, Throwable throwable) { + myMessages.add(s); + } + + @Override + public void warn(@NonNls String s, Throwable throwable) { + myMessages.add(s); + } + + @Override + public void setLevel(Level level) { + } + } +}
--- a/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/LogCommandTest.java Wed Feb 15 12:07:12 2012 +0400 +++ b/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/LogCommandTest.java Wed Feb 15 13:23:53 2012 +0400 @@ -1,140 +1,184 @@ -/* - * 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.command; - -import jetbrains.buildServer.TempFiles; -import jetbrains.buildServer.buildTriggers.vcs.mercurial.MercurialVcsSupport; -import jetbrains.buildServer.util.FileUtil; -import jetbrains.buildServer.vcs.VcsException; -import org.jetbrains.annotations.NotNull; -import org.testng.annotations.AfterMethod; -import org.testng.annotations.BeforeMethod; -import org.testng.annotations.Test; - -import java.io.File; -import java.io.IOException; -import java.util.List; - -@Test -public class LogCommandTest extends BaseCommandTestCase { - - private TempFiles myTempFiles = new TempFiles(); - private File myTemplateFile; - - - @BeforeMethod - @Override - protected void setUp() throws Exception { - super.setUp(); - setRepository("mercurial-tests/testData/rep1", true); - myTemplateFile = myTempFiles.createTempFile(); - FileUtil.copyResource(MercurialVcsSupport.class, "/buildServerResources/log.template", myTemplateFile); - } - - @AfterMethod - public void tearDown() { - myTempFiles.cleanup(); - } - - - public void testOneChangeSet() throws Exception { - final String toId = "9875b412a788"; - List<ChangeSet> changes = runLog(null, toId); - assertEquals(1, changes.size()); - final ChangeSet changeSet = changes.get(0); - assertEquals(0, changeSet.getRevNumber()); - assertEquals(toId, changeSet.getId()); - assertEquals("pavel@localhost", changeSet.getUser()); - assertEquals("dir1 created", changeSet.getDescription()); - assertTrue(changeSet.getParents().isEmpty()); - } - - public void testMoreThanOneChangeSet() throws Exception { - final String fromId = "9875b412a788"; - final String toId = "7209b1f1d793"; - List<ChangeSet> changes = runLog(fromId, toId); - assertEquals(3, changes.size()); - ChangeSet changeSet1 = changes.get(0); - final ChangeSet changeSet2 = changes.get(1); - final ChangeSet changeSet3 = changes.get(2); - assertEquals("dir1 created", changeSet1.getDescription()); - assertEquals("new file added", changeSet2.getDescription()); - assertEquals("file4.txt added", changeSet3.getDescription()); - - changes = runLog(null, toId); - assertEquals(3, changes.size()); - changeSet1 = changes.get(2); - assertEquals("file4.txt added", changeSet1.getDescription()); - } - - public void changeset_parents() throws VcsException, IOException { - setRepository("mercurial-tests/testData/rep2", true); - List<ChangeSet> changes = runLog("6eeb8974fe67", "6eeb8974fe67"); - assertEquals(1, changes.size()); - ChangeSet cs = changes.get(0); - assertNotNull(cs.getParents()); - assertEquals(2, cs.getParents().size()); - assertTrue(cs.getParents().contains(new ChangeSetRevision("1:a3d15477d297"))); - assertTrue(cs.getParents().contains(new ChangeSetRevision("3:2538c02bafeb"))); - } - - public void parse_multiline_description() throws VcsException, IOException { - List<ChangeSet> changes = runLog("9babcf2d5705", "9c6a6b4aede0"); - assertEquals(1, changes.size()); - assertEquals("Multiline description\n" + - "description with new\n" + - "lines\n" + - "aaaa\n" + - "bbb", changes.get(0).getDescription()); - } - - public void log_result_should_contain_changed_files() throws Exception { - final String fromId = "7209b1f1d793"; - final String toId = "b06a290a363b"; - List<ChangeSet> csets = runLog(fromId, toId); - assertEquals(3, csets.size()); - - List<ModifiedFile> files = csets.get(0).getModifiedFiles(); - assertEquals(1, files.size()); - ModifiedFile file = files.get(0); - assertEquals(ModifiedFile.Status.ADDED, file.getStatus()); - assertEquals("dir1/file4.txt", file.getPath()); - - files = csets.get(1).getModifiedFiles(); - assertEquals(1, files.size()); - file = files.get(0); - assertEquals(ModifiedFile.Status.REMOVED, file.getStatus()); - assertEquals("dir1/file4.txt", file.getPath()); - -