changeset 138:b2d05e230e34 Darjeeling-5.1.x

Merge chagnes since September
author Dmitry Neverov <dmitry.neverov@jetbrains.com>
date Wed, 22 Dec 2010 12:15:44 +0300
parents ebca599e7e51 (current diff) ea7972ed3ab7 (diff)
children
files mercurial-server/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/MercurialVcsSupport.java
diffstat 18 files changed, 430 insertions(+), 287 deletions(-) [+]
line wrap: on
line diff
--- a/build.xml	Tue Aug 31 20:46:18 2010 +0400
+++ b/build.xml	Wed Dec 22 12:15:44 2010 +0300
@@ -6,6 +6,17 @@
 
   <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">
@@ -38,7 +49,7 @@
     <path refid="mercurial-tests.runtime.module.classpath"/>
   </path>
 
-  <target name="run-tests" depends="clean, init, compile.module.mercurial-tests.production">
+  <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"
@@ -54,4 +65,4 @@
       </xmlfileset>
     </testng>
   </target>
-</project>
\ No newline at end of file
+</project>
--- a/mercurial-agent/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/MercurialAgentSideVcsSupport.java	Tue Aug 31 20:46:18 2010 +0400
+++ b/mercurial-agent/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/MercurialAgentSideVcsSupport.java	Wed Dec 22 12:15:44 2010 +0300
@@ -16,9 +16,13 @@
 package jetbrains.buildServer.buildTriggers.vcs.mercurial;
 
 import jetbrains.buildServer.agent.BuildProgressLogger;
-import jetbrains.buildServer.agent.vcs.CheckoutOnAgentVcsSupport;
+import jetbrains.buildServer.agent.vcs.AgentVcsSupport;
+import jetbrains.buildServer.agent.vcs.IncludeRuleUpdater;
+import jetbrains.buildServer.agent.vcs.UpdateByIncludeRules;
+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;
@@ -27,46 +31,7 @@
 import java.io.File;
 import java.io.IOException;
 
-public class MercurialAgentSideVcsSupport implements CheckoutOnAgentVcsSupport {
-  public void updateSources(@NotNull final BuildProgressLogger logger, @NotNull final File workingDir, @NotNull final VcsRoot vcsRoot, @NotNull final String version, final 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.");
-      }
-    }
-
-    Settings settings = new Settings(workingDir, vcsRoot);
-    settings.setWorkingDir(workingDir);
-    if (settings.hasCopyOfRepository()) {
-      // execute pull command
-      logger.message("Repository in working directory found, start pulling changes");
-      PullCommand pc = new PullCommand(settings);
-      pc.execute();
-      logger.message("Changes successfully pulled");
-    } else {
-      // execute clone command
-      logger.message("No repository in working directory found, start cloning repository to temporary folder");
-      File parentDir = cloneRepository(settings);
-      logger.message("Repository successfully cloned to: " + parentDir.getAbsolutePath());
-      logger.message("Moving repository to working directory: " + workingDir.getAbsolutePath());
-      if (!moveDir(parentDir, workingDir)) {
-        File hgDir = new File(workingDir, ".hg");
-        if (hgDir.isDirectory()) {
-          FileUtil.delete(hgDir);
-        }
-        throw new VcsException("Failed to move directory content: " + parentDir.getAbsolutePath() + " to: " + workingDir.getAbsolutePath());
-      }
-
-      logger.message("Repository successfully moved to working directory: " + workingDir.getAbsolutePath());
-    }
-    updateWorkingDir(settings, version, logger);
-  }
-
-  private File getWorkingDir(final File workingDir, final IncludeRule includeRule) {
-    if (includeRule.getTo().length() == 0) return workingDir;
-    return new File(workingDir, includeRule.getTo()).getAbsoluteFile();
-  }
-
+public class MercurialAgentSideVcsSupport extends AgentVcsSupport implements UpdateByIncludeRules {
   private void updateWorkingDir(final Settings settings, final String version, final BuildProgressLogger logger) throws VcsException {
     logger.message("Updating working directory from the local repository copy");
     UpdateCommand uc = new UpdateCommand(settings);
@@ -94,11 +59,6 @@
     return tempDir;
   }
 
-  @NotNull
-  public String getName() {
-    return Constants.VCS_NAME;
-  }
-
   /**
    * Moves files from one directory to another with all subdirectories.
    * Removes old directory if it became empty.
@@ -127,4 +87,57 @@
 
     return moveSuccessful;
   }
+
+  @NotNull
+  @Override
+  public UpdatePolicy getUpdatePolicy() {
+    return this;
+  }
+
+  @NotNull
+  @Override
+  public String getName() {
+    return Constants.VCS_NAME;
+  }
+
+  public IncludeRuleUpdater getUpdater(@NotNull final VcsRoot vcsRoot, @NotNull final CheckoutRules checkoutRules, @NotNull final String toVersion, @NotNull final File checkoutDirectory, @NotNull final BuildProgressLogger logger) throws VcsException {
+    return new IncludeRuleUpdater() {
+      public void process(@NotNull final IncludeRule includeRule, @NotNull final File workingDir) 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.");
+          }
+        }
+
+        Settings settings = new Settings(workingDir, vcsRoot);
+        settings.setWorkingDir(workingDir);
+        if (settings.hasCopyOfRepository()) {
+          // execute pull command
+          logger.message("Repository in working directory found, start pulling changes");
+          PullCommand pc = new PullCommand(settings);
+          pc.execute();
+          logger.message("Changes successfully pulled");
+        } else {
+          // execute clone command
+          logger.message("No repository in working directory found, start cloning repository to temporary folder");
+          File parentDir = cloneRepository(settings);
+          logger.message("Repository successfully cloned to: " + parentDir.getAbsolutePath());
+          logger.message("Moving repository to working directory: " + workingDir.getAbsolutePath());
+          if (!moveDir(parentDir, workingDir)) {
+            File hgDir = new File(workingDir, ".hg");
+            if (hgDir.isDirectory()) {
+              FileUtil.delete(hgDir);
+            }
+            throw new VcsException("Failed to move directory content: " + parentDir.getAbsolutePath() + " to: " + workingDir.getAbsolutePath());
+          }
+
+          logger.message("Repository successfully moved to working directory: " + workingDir.getAbsolutePath());
+        }
+        updateWorkingDir(settings, toVersion, logger);
+      }
+
+      public void dispose() throws VcsException {
+      }
+    };
+  }
 }
--- a/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/Constants.java	Tue Aug 31 20:46:18 2010 +0400
+++ b/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/Constants.java	Wed Dec 22 12:15:44 2010 +0300
@@ -22,6 +22,7 @@
   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";
--- a/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/LogCommand.java	Tue Aug 31 20:46:18 2010 +0400
+++ b/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/LogCommand.java	Wed Dec 22 12:15:44 2010 +0300
@@ -61,6 +61,8 @@
     GeneralCommandLine cli = createCommandLine();
     cli.addParameter("log");
     cli.addParameter("-v");
+    cli.addParameter("--style");
+    cli.addParameter("default");
     cli.addParameter("-b");
     cli.addParameter(getSettings().getBranchName());
     cli.addParameter("-r");
--- a/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/Settings.java	Tue Aug 31 20:46:18 2010 +0400
+++ b/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/Settings.java	Wed Dec 22 12:15:44 2010 +0300
@@ -17,12 +17,17 @@
 
 import jetbrains.buildServer.buildTriggers.vcs.mercurial.Constants;
 import jetbrains.buildServer.buildTriggers.vcs.mercurial.PathUtil;
+import jetbrains.buildServer.log.Loggers;
 import jetbrains.buildServer.util.Hash;
 import jetbrains.buildServer.util.StringUtil;
 import jetbrains.buildServer.vcs.VcsRoot;
 import org.jetbrains.annotations.NotNull;
 
 import java.io.File;
+import java.net.MalformedURLException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.net.URL;
 import java.util.HashSet;
 import java.util.Set;
 
@@ -92,9 +97,9 @@
 
   private final static Set<String> AUTH_PROTOS = new HashSet<String>();
   static {
-    AUTH_PROTOS.add("http://");
-    AUTH_PROTOS.add("https://");
-    AUTH_PROTOS.add("ssh://");
+    AUTH_PROTOS.add("http");
+    AUTH_PROTOS.add("https");
+    AUTH_PROTOS.add("ssh");
   }
 
   /**
@@ -102,45 +107,77 @@
    * @return URL to use for push command
    */
   public String getRepositoryUrl() {
-    if (containsCredentials(myRepository)) return myRepository;
-
-    for (String proto: AUTH_PROTOS) {
-      if (myRepository.startsWith(proto)) {
-        String repoUrl = myRepository.substring(proto.length());
-        int endIdx = repoUrl.indexOf('@');
-        int slashIdx = repoUrl.indexOf('/');
-        if (endIdx != -1 && slashIdx > endIdx) {
-          repoUrl = repoUrl.substring(endIdx+1);
-        }
-
-        String cre = "";
-        if (!StringUtil.isEmpty(myUsername)) {
-          cre += myUsername;
-          if (!StringUtil.isEmpty(myPassword)) {
-            cre += ":" + myPassword;
-          }
-          cre += "@";
-        }
-
-        return proto + cre + repoUrl;
+    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;
     }
-
-    return myRepository;
   }
 
   private boolean containsCredentials(final String repository) {
-    for (String proto: AUTH_PROTOS) {
-      if (repository.startsWith(proto)) {
-        String withoutProto = repository.substring(proto.length());
-        int comma = withoutProto.indexOf(':');
-        int at = withoutProto.indexOf('@');
-        if (at != -1 && comma != -1 && at > comma) return true;
+    try {
+      URL url = new URL(repository);
+      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(originalUrl);
+      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;
+      return escapedURI.substring(from, to);
+    } catch (URISyntaxException e) {
+      assert false;
+    }
+    return userInfo;
+  }
+
   public void setHgCommandPath(@NotNull final String hgCommandPath) {
     myHgCommandPath = hgCommandPath;
   }
--- a/mercurial-server/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/MercurialVcsSupport.java	Tue Aug 31 20:46:18 2010 +0400
+++ b/mercurial-server/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/MercurialVcsSupport.java	Wed Dec 22 12:15:44 2010 +0300
@@ -15,9 +15,7 @@
  */
 package jetbrains.buildServer.buildTriggers.vcs.mercurial;
 
-import jetbrains.buildServer.AgentSideCheckoutAbility;
 import jetbrains.buildServer.BuildAgent;
-import jetbrains.buildServer.CollectChangesByIncludeRule;
 import jetbrains.buildServer.Used;
 import jetbrains.buildServer.buildTriggers.vcs.AbstractVcsPropertiesProcessor;
 import jetbrains.buildServer.buildTriggers.vcs.mercurial.command.*;
@@ -55,7 +53,7 @@
  * <p>Working copy of repository is created in the $TEAMCITY_DATA_PATH/system/caches/hg_&lt;hash code> folder.
  * <p>Personal builds (remote runs) are not yet supported, they require corresponding functionality from the IDE.
  */
-public class MercurialVcsSupport extends VcsSupport implements CollectChangesByIncludeRule, LabelingSupport, AgentSideCheckoutAbility {
+public class MercurialVcsSupport extends ServerVcsSupport implements LabelingSupport, VcsFileContentProvider {
   private ConcurrentMap<String, Lock> myWorkDirLocks= new ConcurrentHashMap<String, Lock>();
   private static final int OLD_WORK_DIRS_CLEANUP_PERIOD = 600;
   private VcsManager myVcsManager;
@@ -65,7 +63,6 @@
                              @NotNull ServerPaths paths,
                              @NotNull final SBuildServer server,
                              @NotNull EventDispatcher<BuildServerListener> dispatcher) {
-    vcsManager.registerVcsSupport(this);
     myVcsManager = vcsManager;
     server.getExecutor().scheduleAtFixedRate(new Runnable() {
       public void run() {
@@ -95,67 +92,6 @@
     });
   }
 
-  public List<ModificationData> collectBuildChanges(final VcsRoot root,
-                                                    @NotNull final String fromVersion,
-                                                    @NotNull final String currentVersion,
-                                                    final CheckoutRules checkoutRules) throws VcsException {
-    syncClonedRepository(root);
-    return VcsSupportUtil.collectBuildChanges(root, fromVersion, currentVersion, checkoutRules, this);
-  }
-
-  public List<ModificationData> collectBuildChanges(final VcsRoot root,
-                                                    final String fromVersion,
-                                                    final String currentVersion,
-                                                    final IncludeRule includeRule) throws VcsException {
-    // first obtain changes between specified versions
-    List<ModificationData> result = new ArrayList<ModificationData>();
-    Settings settings = createSettings(root);
-    LogCommand lc = new LogCommand(settings);
-    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;
-    }
-
-    // invoke status command for each changeset and determine what files were modified in these changesets
-    StatusCommand st = new StatusCommand(settings);
-    ChangeSet prev = new ChangeSet(fromVersion);
-    for (ChangeSet cur : changeSets) {
-      if (cur.getId().equals(fromId)) continue; // skip already reported changeset
-
-      String prevId = prev.getId();
-      List<ChangeSetRevision> curParents = cur.getParents();
-      boolean merge = curParents != null && curParents.size() > 1;
-      if (curParents != null && !merge) {
-        prevId = curParents.get(0).getId();
-      }
-
-      List<ModifiedFile> modifiedFiles = new ArrayList<ModifiedFile>();
-      if (merge) {
-        modifiedFiles.addAll(computeModifiedFilesForMergeCommit(settings, cur));
-      } else {
-        st.setFromRevId(prevId);
-        st.setToRevId(cur.getId());
-        modifiedFiles = st.execute();
-      }
-
-      // changeset full version will be set into VcsChange structure and
-      // stored in database (note that getContent method will be invoked with this version)
-      List<VcsChange> files = toVcsChanges(modifiedFiles, prev.getFullVersion(), cur.getFullVersion(), includeRule);
-      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 Collection<ModifiedFile> computeModifiedFilesForMergeCommit(final Settings settings, final ChangeSet cur) throws VcsException {
     if (!cur.containsFiles()) return Collections.emptyList();
 
@@ -169,13 +105,14 @@
     for (ModifiedFile mf: modifiedFiles) {
       String normalizedPath = PathUtil.normalizeSeparator(mf.getPath());
       if (!normalizedPath.startsWith(includeRule.getFrom())) continue; // skip files which do not match include rule
+      String relPath = StringUtil.removeLeadingSlash(normalizedPath.substring(includeRule.getFrom().length()));
 
       VcsChangeInfo.Type changeType = getChangeType(mf.getStatus());
       if (changeType == null) {
         Loggers.VCS.warn("Unable to convert status: " + mf.getStatus() + " to VCS change type");
         changeType = VcsChangeInfo.Type.NOT_CHANGED;
       }
-      files.add(new VcsChange(changeType, mf.getStatus().getName(), normalizedPath, normalizedPath, prevVer, curVer));
+      files.add(new VcsChange(changeType, mf.getStatus().getName(), normalizedPath, relPath, prevVer, curVer));
     }
     return files;
   }
@@ -272,26 +209,30 @@
     return result.get(settings.getBranchName()).getFullVersion();
   }
 
+  public boolean sourcesUpdatePossibleIfChangesNotFound(@NotNull final VcsRoot root) {
+    return false;
+  }
+
   @NotNull
   public String describeVcsRoot(final VcsRoot vcsRoot) {
     return "mercurial: " + vcsRoot.getProperty(Constants.REPOSITORY_PROP);
   }
 
-  public boolean isTestConnectionSupported() {
-    return true;
-  }
-
-  @Nullable
-  public String testConnection(@NotNull final VcsRoot vcsRoot) throws VcsException {
-    Settings settings = createSettings(vcsRoot);
-    IdentifyCommand id = new IdentifyCommand(settings);
-    StringBuilder res = new StringBuilder();
-    res.append(quoteIfNeeded(settings.getHgCommandPath()));
-    res.append(" identify ");
-    final String obfuscatedUrl = CommandUtil.removePrivateData(settings.getRepositoryUrl(), Collections.singleton(settings.getPassword()));
-    res.append(quoteIfNeeded(obfuscatedUrl));
-    res.append('\n').append(id.execute());
-    return res.toString();
+  @Override
+  public TestConnectionSupport getTestConnectionSupport() {
+    return new TestConnectionSupport() {
+      public String testConnection(@NotNull final VcsRoot vcsRoot) throws VcsException {
+        Settings settings = createSettings(vcsRoot);
+        IdentifyCommand id = new IdentifyCommand(settings);
+        StringBuilder res = new StringBuilder();
+        res.append(quoteIfNeeded(settings.getHgCommandPath()));
+        res.append(" identify ");
+        final String obfuscatedUrl = CommandUtil.removePrivateData(settings.getRepositoryUrl(), Collections.singleton(settings.getPassword()));
+        res.append(quoteIfNeeded(obfuscatedUrl));
+        res.append('\n').append(id.execute());
+        return res.toString();
+      }
+    };
   }
 
   private String quoteIfNeeded(@NotNull String str) {
@@ -330,20 +271,6 @@
     };
   }
 
-  public void buildPatch(@NotNull final VcsRoot root,
-                         @Nullable final String fromVersion,
-                         @NotNull final String toVersion,
-                         @NotNull final PatchBuilder builder,
-                         @NotNull final CheckoutRules checkoutRules) throws IOException, VcsException {
-    syncClonedRepository(root);
-    Settings settings = createSettings(root);
-    if (fromVersion == null) {
-      buildFullPatch(settings, new ChangeSet(toVersion), builder, checkoutRules);
-    } else {
-      buildIncrementalPatch(settings, new ChangeSet(fromVersion), new ChangeSet(toVersion), builder, checkoutRules);
-    }
-  }
-
   // builds patch from version to version
   private void buildIncrementalPatch(final Settings settings, @NotNull final ChangeSet fromVer, @NotNull final ChangeSet toVer, final PatchBuilder builder, final CheckoutRules checkoutRules)
     throws VcsException, IOException {
@@ -481,6 +408,98 @@
     return this;
   }
 
+  @NotNull
+  public VcsFileContentProvider getContentProvider() {
+    return this;
+  }
+
+  @NotNull
+  public CollectChangesPolicy getCollectChangesPolicy() {
+    return new CollectChangesByIncludeRules() {
+      @NotNull
+      public IncludeRuleChangeCollector getChangeCollector(@NotNull final VcsRoot root, @NotNull final String fromVersion, @Nullable final String currentVersion) throws VcsException {
+        return new IncludeRuleChangeCollector() {
+          @NotNull
+          public List<ModificationData> collectChanges(@NotNull final IncludeRule includeRule) throws VcsException {
+            syncClonedRepository(root);
+
+            // first obtain changes between specified versions
+            List<ModificationData> result = new ArrayList<ModificationData>();
+            if (currentVersion == null) return result;
+
+            Settings settings = createSettings(root);
+            LogCommand lc = new LogCommand(settings);
+            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;
+            }
+
+            // invoke status command for each changeset and determine what files were modified in these changesets
+            StatusCommand st = new StatusCommand(settings);
+            ChangeSet prev = new ChangeSet(fromVersion);
+            for (ChangeSet cur : changeSets) {
+              if (cur.getId().equals(fromId)) continue; // skip already reported changeset
+
+              String prevId = prev.getId();
+              List<ChangeSetRevision> curParents = cur.getParents();
+              boolean merge = curParents != null && curParents.size() > 1;
+              if (curParents != null && !merge) {
+                prevId = curParents.get(0).getId();
+              }
+
+              List<ModifiedFile> modifiedFiles = new ArrayList<ModifiedFile>();
+              if (merge) {
+                modifiedFiles.addAll(computeModifiedFilesForMergeCommit(settings, cur));
+              } else {
+                st.setFromRevId(prevId);
+                st.setToRevId(cur.getId());
+                modifiedFiles = st.execute();
+              }
+
+              // changeset full version will be set into VcsChange structure and
+              // stored in database (note that getContent method will be invoked with this version)
+              List<VcsChange> files = toVcsChanges(modifiedFiles, prev.getFullVersion(), cur.getFullVersion(), includeRule);
+              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;
+          }
+
+          public void dispose() throws VcsException {
+          }
+        };
+      }
+    };
+  }
+
+  @NotNull
+  public BuildPatchPolicy getBuildPatchPolicy() {
+    return new BuildPatchByCheckoutRules() {
+      public void buildPatch(@NotNull final VcsRoot root,
+                             @Nullable final String fromVersion,
+                             @NotNull final String toVersion,
+                             @NotNull final PatchBuilder builder,
+                             @NotNull final CheckoutRules checkoutRules) throws IOException, VcsException {
+        syncClonedRepository(root);
+        Settings settings = createSettings(root);
+        if (fromVersion == null) {
+          buildFullPatch(settings, new ChangeSet(toVersion), builder, checkoutRules);
+        } else {
+          buildIncrementalPatch(settings, new ChangeSet(fromVersion), new ChangeSet(toVersion), builder, checkoutRules);
+        }
+      }
+    };
+  }
+
   private void lockWorkDir(@NotNull File workDir) {
     getWorkDirLock(workDir).lock();
   }
@@ -490,10 +509,10 @@
   }
 
   @Override
-  public boolean ignoreServerCachesFor(@NotNull final VcsRoot root) {
+  public boolean allowSourceCaching() {
     // since a copy of repository for each VCS root is already stored on disk
-    // we do not need separate cache for our patches 
-    return true;
+    // we do not need separate cache for our patches
+    return false;
   }
 
   private Lock getWorkDirLock(final File workDir) {
--- a/mercurial-tests/mercurial-tests.iml	Tue Aug 31 20:46:18 2010 +0400
+++ b/mercurial-tests/mercurial-tests.iml	Wed Dec 22 12:15:44 2010 +0300
@@ -17,6 +17,7 @@
     <orderEntry type="module" module-name="mercurial-agent" />
     <orderEntry type="library" name="TeamCity-TestsAPI" level="project" />
     <orderEntry type="library" name="TeamCity-impl" level="project" />
+    <orderEntry type="library" name="TeamCityAPI-agent" level="project" />
   </component>
 </module>
 
--- a/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/AgentSideCheckoutTest.java	Tue Aug 31 20:46:18 2010 +0400
+++ b/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/AgentSideCheckoutTest.java	Wed Dec 22 12:15:44 2010 +0300
@@ -17,6 +17,7 @@
 
 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;
@@ -69,7 +70,7 @@
 
   private File doUpdate(final VcsRoot vcsRoot, final String version, final IncludeRule includeRule) throws VcsException {
     File actualWorkDir = new File(myWorkDir, includeRule.getTo());
-    myVcsSupport.updateSources((BuildProgressLogger) myProgressLoggerMock.proxy(), actualWorkDir, vcsRoot, version, includeRule);
+    myVcsSupport.getUpdater(vcsRoot, new CheckoutRules(""), version, myWorkDir, (BuildProgressLogger) myProgressLoggerMock.proxy()).process(includeRule, actualWorkDir);
 
     File hgDir = new File(actualWorkDir, ".hg");
     assertTrue(hgDir.isDirectory());
--- a/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/BaseMercurialTestCase.java	Tue Aug 31 20:46:18 2010 +0400
+++ b/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/BaseMercurialTestCase.java	Wed Dec 22 12:15:44 2010 +0300
@@ -52,7 +52,7 @@
 
   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("mercurial-tests/testData/bin/hg.exe").getAbsolutePath());
+    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;
--- a/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/MercurialVcsSupportTest.java	Tue Aug 31 20:46:18 2010 +0400
+++ b/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/MercurialVcsSupportTest.java	Wed Dec 22 12:15:44 2010 +0300
@@ -27,6 +27,7 @@
 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.testng.annotations.BeforeMethod;
 import org.testng.annotations.Test;
@@ -38,6 +39,7 @@
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
+import java.util.Locale;
 import java.util.concurrent.Executors;
 import java.util.concurrent.ScheduledExecutorService;
 
@@ -79,10 +81,14 @@
     assertEquals(myVcs.getCurrentVersion(createVcsRoot(simpleRepo(), "name with space")), "9:9babcf2d5705");
   }
 
+  private List<ModificationData> collectChanges(@NotNull VcsRoot vcsRoot, @NotNull String from, @NotNull String to, @NotNull IncludeRule rule) throws VcsException {
+    return ((CollectChangesByIncludeRules)myVcs.getCollectChangesPolicy()).getChangeCollector(vcsRoot, from, to).collectChanges(rule);
+  }
+
   public void test_collect_changes() throws Exception {
     VcsRootImpl vcsRoot = createVcsRoot(simpleRepo());
 
-    List<ModificationData> changes = myVcs.collectBuildChanges(vcsRoot, "0:9875b412a788", "3:9522278aa38d", new CheckoutRules(""));
+    List<ModificationData> changes = collectChanges(vcsRoot, "0:9875b412a788", "3:9522278aa38d", new CheckoutRules("").getIncludeRuleFor(""));
     assertEquals(3, changes.size());
 
     ModificationData md1 = changes.get(0);
@@ -110,29 +116,36 @@
     assertEquals(normalizePath(files3.get(0).getRelativeFileName()), "dir1/file4.txt");
   }
 
-  public void test_collect_changes_with_checkout_rules() throws Exception {
+  public void test_collect_changes_with_not_empty_include_rule() throws Exception {
     VcsRootImpl vcsRoot = createVcsRoot(simpleRepo());
 
-    List<ModificationData> changes = myVcs.collectBuildChanges(vcsRoot, "0:9875b412a788", "3:9522278aa38d", new CheckoutRules("-:.\n+:dir1/subdir"));
-    assertEquals(changes.size(), 0);
+    List<ModificationData> changes = collectChanges(vcsRoot, "0:9875b412a788", "5:1d2cc6f3bc29", new IncludeRule("dir1/subdir", "dir1/subdir", null));
+    assertEquals(changes.size(), 1, changes.toString());
 
-    changes = myVcs.collectBuildChanges(vcsRoot, "0:9875b412a788", "5:1d2cc6f3bc29", new CheckoutRules("-:.\n+:dir1/subdir"));
-    assertEquals(changes.size(), 1);
     ModificationData md = changes.get(0);
     assertEquals(md.getDescription(), "modified in subdir");
+
+    List<VcsChange> files = changes.get(0).getChanges();
+    assertEquals(files.size(), 1, files.toString());
+    assertEquals("dir1/subdir/file2.txt", files.get(0).getFileName());
+    assertEquals("file2.txt", files.get(0).getRelativeFileName());
   }
 
+  private ByteArrayOutputStream buildPatch(VcsRoot vcsRoot, String from, String to, CheckoutRules rules) throws IOException, VcsException {
+    final ByteArrayOutputStream output = new ByteArrayOutputStream();
+    final PatchBuilderImpl builder = new PatchBuilderImpl(output);
+
+    ((BuildPatchByCheckoutRules)myVcs.getBuildPatchPolicy()).buildPatch(vcsRoot, from, to, builder, rules);
+    builder.close();
+    return output;
+  }
+  
   @Test
   public void test_build_patch() throws IOException, VcsException {
     setName("cleanPatch1");
     VcsRootImpl vcsRoot = createVcsRoot(simpleRepo());
 
-    final ByteArrayOutputStream output = new ByteArrayOutputStream();
-    final PatchBuilderImpl builder = new PatchBuilderImpl(output);
-
-    myVcs.buildPatch(vcsRoot, null, "4:b06a290a363b", builder, new CheckoutRules(""));
-    builder.close();
-
+    ByteArrayOutputStream output = buildPatch(vcsRoot, null, "4:b06a290a363b", new CheckoutRules(""));
     checkPatchResult(output.toByteArray());
 
     File clonedReposParentDir = new File(myServerPaths.getCachesDir(), "mercurial");
@@ -148,11 +161,7 @@
     setName("patch1");
     VcsRootImpl vcsRoot = createVcsRoot(simpleRepo());
 
-    final ByteArrayOutputStream output = new ByteArrayOutputStream();
-    final PatchBuilderImpl builder = new PatchBuilderImpl(output);
-
-    myVcs.buildPatch(vcsRoot, "3:9522278aa38d", "4:b06a290a363b", builder, new CheckoutRules(""));
-    builder.close();
+    ByteArrayOutputStream output = buildPatch(vcsRoot, "3:9522278aa38d", "4:b06a290a363b", new CheckoutRules(""));
 
     checkPatchResult(output.toByteArray());
   }
@@ -161,11 +170,7 @@
     setName("patch1_checkout_rules");
     VcsRootImpl vcsRoot = createVcsRoot(simpleRepo());
 
-    final ByteArrayOutputStream output = new ByteArrayOutputStream();
-    final PatchBuilderImpl builder = new PatchBuilderImpl(output);
-
-    myVcs.buildPatch(vcsRoot, "3:9522278aa38d", "4:b06a290a363b", builder, new CheckoutRules("+:dir1=>path"));
-    builder.close();
+    ByteArrayOutputStream output = buildPatch(vcsRoot, "3:9522278aa38d", "4:b06a290a363b", new CheckoutRules("+:dir1=>path"));
 
     checkPatchResult(output.toByteArray());
   }
@@ -174,11 +179,7 @@
     setName("cleanPatch1_checkout_rules");
     VcsRootImpl vcsRoot = createVcsRoot(simpleRepo());
 
-    final ByteArrayOutputStream output = new ByteArrayOutputStream();
-    final PatchBuilderImpl builder = new PatchBuilderImpl(output);
-
-    myVcs.buildPatch(vcsRoot, null, "4:b06a290a363b", builder, new CheckoutRules("+:dir1/subdir=>."));
-    builder.close();
+    ByteArrayOutputStream output = buildPatch(vcsRoot, null, "4:b06a290a363b", new CheckoutRules("+:dir1/subdir=>."));
 
     checkPatchResult(output.toByteArray());
   }
@@ -187,11 +188,7 @@
     setName("patch2");
     VcsRootImpl vcsRoot = createVcsRoot(simpleRepo());
 
-    final ByteArrayOutputStream output = new ByteArrayOutputStream();
-    final PatchBuilderImpl builder = new PatchBuilderImpl(output);
-
-    myVcs.buildPatch(vcsRoot, "3:9522278aa38d", "6:b9deb9a1c6f4", builder, new CheckoutRules(""));
-    builder.close();
+    ByteArrayOutputStream output = buildPatch(vcsRoot, "3:9522278aa38d", "6:b9deb9a1c6f4", new CheckoutRules(""));
 
     checkPatchResult(output.toByteArray());
   }
@@ -215,11 +212,11 @@
   public void test_test_connection() throws IOException, VcsException {
     VcsRootImpl vcsRoot = createVcsRoot(simpleRepo());
 
-    System.out.println(myVcs.testConnection(vcsRoot));
+    System.out.println(myVcs.getTestConnectionSupport().testConnection(vcsRoot));
 
     vcsRoot.addProperty(Constants.REPOSITORY_PROP, "/some/non/existent/path");
     try {
-      myVcs.testConnection(vcsRoot);
+      myVcs.getTestConnectionSupport().testConnection(vcsRoot);
       fail("Exception expected");
     } catch (VcsException e) {
     }
@@ -263,7 +260,7 @@
     VcsRootImpl vcsRoot = createVcsRoot(simpleRepo(), "test_branch");
 
     // fromVersion(6:b9deb9a1c6f4) is not in the branch (it is in the default branch)
-    List<ModificationData> changes = myVcs.collectBuildChanges(vcsRoot, "6:b9deb9a1c6f4", "7:376dcf05cd2a", new CheckoutRules(""));
+    List<ModificationData> changes = collectChanges(vcsRoot, "6:b9deb9a1c6f4", "7:376dcf05cd2a", IncludeRule.createDefaultInstance());
     assertEquals(1, changes.size());
 
     ModificationData md1 = changes.get(0);
@@ -274,7 +271,7 @@
     assertEquals(VcsChangeInfo.Type.ADDED, files1.get(0).getType());
     assertEquals(normalizePath(files1.get(0).getRelativeFileName()), "file_in_branch.txt");
 
-    changes = myVcs.collectBuildChanges(vcsRoot, "7:376dcf05cd2a", "8:04c3ae4c6312", new CheckoutRules(""));
+    changes = collectChanges(vcsRoot, "7:376dcf05cd2a", "8:04c3ae4c6312", IncludeRule.createDefaultInstance());
     assertEquals(1, changes.size());
 
     md1 = changes.get(0);
@@ -282,24 +279,11 @@
     assertEquals(md1.getDescription(), "file modified");
   }
 
-  public void test_collect_changes_after_merge() throws IOException, VcsException {
-    VcsRootImpl vcsRoot = createVcsRoot(simpleRepo());
-
-    List<ModificationData> changes = myVcs.collectBuildChanges(vcsRoot, "6:b9deb9a1c6f4", "12:1870e1100fab", new CheckoutRules(""));
-    for (ModificationData md: changes) {
-      System.out.println(md);
-    }
-  }
-
   public void test_full_patch_from_branch() throws IOException, VcsException {
     setName("patch3");
     VcsRootImpl vcsRoot = createVcsRoot(simpleRepo(), "test_branch");
 
-    final ByteArrayOutputStream output = new ByteArrayOutputStream();
-    final PatchBuilderImpl builder = new PatchBuilderImpl(output);
-
-    myVcs.buildPatch(vcsRoot, null, "7:376dcf05cd2a", builder, new CheckoutRules(""));
-    builder.close();
+    ByteArrayOutputStream output = buildPatch(vcsRoot, null, "7:376dcf05cd2a", new CheckoutRules(""));
 
     checkPatchResult(output.toByteArray());
   }
@@ -308,11 +292,7 @@
     setName("patch3_checkout_rules1");
     VcsRootImpl vcsRoot = createVcsRoot(simpleRepo(), "test_branch");
 
-    final ByteArrayOutputStream output = new ByteArrayOutputStream();
-    final PatchBuilderImpl builder = new PatchBuilderImpl(output);
-
-    myVcs.buildPatch(vcsRoot, null, "7:376dcf05cd2a", builder, new CheckoutRules("+:.=>path"));
-    builder.close();
+    ByteArrayOutputStream output = buildPatch(vcsRoot, null, "7:376dcf05cd2a", new CheckoutRules("+:.=>path"));
 
     checkPatchResult(output.toByteArray());
   }
@@ -321,11 +301,7 @@
     setName("patch3_checkout_rules2");
     VcsRootImpl vcsRoot = createVcsRoot(simpleRepo(), "test_branch");
 
-    final ByteArrayOutputStream output = new ByteArrayOutputStream();
-    final PatchBuilderImpl builder = new PatchBuilderImpl(output);
-
-    myVcs.buildPatch(vcsRoot, null, "7:376dcf05cd2a", builder, new CheckoutRules("+:dir1=>path/dir1\n+:dir with space"));
-    builder.close();
+    ByteArrayOutputStream output = buildPatch(vcsRoot, null, "7:376dcf05cd2a", new CheckoutRules("+:dir1=>path/dir1\n+:dir with space"));
 
     checkPatchResult(output.toByteArray());
   }
@@ -334,11 +310,7 @@
     setName("patch4");
     VcsRootImpl vcsRoot = createVcsRoot(simpleRepo(), "test_branch");
 
-    final ByteArrayOutputStream output = new ByteArrayOutputStream();
-    final PatchBuilderImpl builder = new PatchBuilderImpl(output);
-
-    myVcs.buildPatch(vcsRoot, "7:376dcf05cd2a", "8:04c3ae4c6312", builder, new CheckoutRules(""));
-    builder.close();
+    ByteArrayOutputStream output = buildPatch(vcsRoot, "7:376dcf05cd2a", "8:04c3ae4c6312", new CheckoutRules(""));
 
     checkPatchResult(output.toByteArray());
   }
@@ -366,11 +338,7 @@
     File cloneDir = myTempFiles.createTempDir();
     vcsRoot.addProperty(Constants.SERVER_CLONE_PATH_PROP, cloneDir.getAbsolutePath());
 
-    final ByteArrayOutputStream output = new ByteArrayOutputStream();
-    final PatchBuilderImpl builder = new PatchBuilderImpl(output);
-
-    myVcs.buildPatch(vcsRoot, null, "4:b06a290a363b", builder, new CheckoutRules(""));
-    builder.close();
+    ByteArrayOutputStream output = buildPatch(vcsRoot, null, "4:b06a290a363b", new CheckoutRules(""));
 
     checkPatchResult(output.toByteArray());
 
@@ -384,7 +352,7 @@
   public void test_collect_changes_merge() throws Exception {
     VcsRootImpl vcsRoot = createVcsRoot(mergeCommittsRepo());
 
-    List<ModificationData> changes = myVcs.collectBuildChanges(vcsRoot, "1:a3d15477d297", "4:6eeb8974fe67", new CheckoutRules(""));
+    List<ModificationData> changes = collectChanges(vcsRoot, "1:a3d15477d297", "4:6eeb8974fe67", IncludeRule.createDefaultInstance());
     assertEquals(changes.size(), 3);
 
     assertEquals("2:db8a04d262f3", changes.get(0).getVersion());
@@ -399,7 +367,7 @@
   public void test_collect_changes_merge_conflict() throws Exception {
     VcsRootImpl vcsRoot = createVcsRoot(mergeCommittsRepo());
 
-    List<ModificationData> changes = myVcs.collectBuildChanges(vcsRoot, "6:6066b677d026", "8:b6e2d176fe8e", new CheckoutRules(""));
+    List<ModificationData> changes = collectChanges(vcsRoot, "6:6066b677d026", "8:b6e2d176fe8e", IncludeRule.createDefaultInstance());
     assertEquals(changes.size(), 2);
 
     assertFiles(Arrays.asList("A dir4/file41.txt"), changes.get(0));
@@ -409,7 +377,7 @@
   public void test_collect_changes_merge_conflict_named_branch() throws Exception {
     VcsRootImpl vcsRoot = createVcsRoot(mergeCommittsRepo());
 
-    List<ModificationData> changes = myVcs.collectBuildChanges(vcsRoot, "8:b6e2d176fe8e", "12:1e620196c4b6", new CheckoutRules(""));
+    List<ModificationData> changes = collectChanges(vcsRoot, "8:b6e2d176fe8e", "12:1e620196c4b6", IncludeRule.createDefaultInstance());
     assertEquals(changes.size(), 2);
 
     assertFiles(Arrays.asList("A dir6/file6.txt"), changes.get(0));
--- a/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/SettingsTest.java	Tue Aug 31 20:46:18 2010 +0400
+++ b/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/SettingsTest.java	Wed Dec 22 12:15:44 2010 +0300
@@ -51,12 +51,48 @@
     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(new File("."), 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(new File("."), 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(new File("."), 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(new File("."), 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(new File("."), vcsRoot);
+    assertEquals("file:///path/to/repo", 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, "user");
-    vcsRoot.addProperty(Constants.PASSWORD, "pwd");
+    vcsRoot.addProperty(Constants.USERNAME, userName);
+    vcsRoot.addProperty(Constants.PASSWORD, password);
     return vcsRoot;
   }
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/Util.java	Wed Dec 22 12:15:44 2010 +0300
@@ -0,0 +1,21 @@
+package jetbrains.buildServer.buildTriggers.vcs.mercurial;
+
+import java.io.IOException;
+
+/**
+ * @author dmitry.neverov
+ */
+public final class Util {
+
+  private Util() {}
+
+  public static String getHgPath() throws IOException {
+    String providedHg = System.getenv(Constants.HG_PATH_ENV);
+    if (providedHg != null) {
+      return providedHg;
+    } else {
+      return "mercurial-tests/testData/bin/hg.exe";
+    }
+  }
+
+}
--- a/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/BaseCommandTestCase.java	Tue Aug 31 20:46:18 2010 +0400
+++ b/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/BaseCommandTestCase.java	Wed Dec 22 12:15:44 2010 +0300
@@ -17,8 +17,9 @@
 
 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.Constants;
+import jetbrains.buildServer.buildTriggers.vcs.mercurial.Util;
 import jetbrains.buildServer.vcs.VcsException;
 import jetbrains.buildServer.vcs.VcsRoot;
 import jetbrains.buildServer.vcs.impl.VcsRootImpl;
@@ -26,8 +27,8 @@
 
 import java.io.File;
 import java.io.IOException;
+import java.util.HashMap;
 import java.util.Map;
-import java.util.HashMap;
 
 public class BaseCommandTestCase extends BaseTestCase {
   private String myRepository;
@@ -58,7 +59,7 @@
       vcsRootProps.put(Constants.REPOSITORY_PROP, repository.getAbsolutePath());
     }
 
-    vcsRootProps.put(Constants.HG_COMMAND_PATH_PROP, "mercurial-tests/testData/bin/hg.exe");
+    vcsRootProps.put(Constants.HG_COMMAND_PATH_PROP, Util.getHgPath());
     if (myUsername != null) {
       vcsRootProps.put(Constants.USERNAME, myUsername);
     }
--- a/mercurial-tests/src/testng.xml	Tue Aug 31 20:46:18 2010 +0400
+++ b/mercurial-tests/src/testng.xml	Wed Dec 22 12:15:44 2010 +0300
@@ -7,6 +7,7 @@
       <class name="jetbrains.buildServer.buildTriggers.vcs.mercurial.command.PushCommandTest"/>
       <class name="jetbrains.buildServer.buildTriggers.vcs.mercurial.MercurialVcsSupportTest"/>
       <class name="jetbrains.buildServer.buildTriggers.vcs.mercurial.AgentSideCheckoutTest"/>
+      <class name="jetbrains.buildServer.buildTriggers.vcs.mercurial.SettingsTest"/>
     </classes>
   </test>
 </suite>
--- a/mercurial.properties	Tue Aug 31 20:46:18 2010 +0400
+++ b/mercurial.properties	Wed Dec 22 12:15:44 2010 +0300
@@ -1,3 +1,5 @@
 path.variable.ant_home=C\:/Tools/apache-ant-1.7.1
 path.variable.maven_repository=C\:/Documents and Settings/pavel.sher/.m2/repository
-path.variable.teamcitydistribution=C\:/TeamCity
\ No newline at end of file
+path.variable.teamcitydistribution=C\:/TeamCity
+path.variable.user_home_grails=C\:/Documents and Settings/pavel.sher/.grails
+path.variable.user_home_griffon=C\:/Documents and Settings/pavel.sher/.griffon
\ No newline at end of file
--- a/mercurial.xml	Tue Aug 31 20:46:18 2010 +0400
+++ b/mercurial.xml	Wed Dec 22 12:15:44 2010 +0300
@@ -28,6 +28,7 @@
     <exclude name="**/.git/**"/>
     <exclude name="**/*.hprof/**"/>
     <exclude name="**/_svn/**"/>
+    <exclude name="**/.hg/**"/>
   </patternset>
   <patternset id="library.patterns">
     <include name="*.zip"/>
@@ -399,6 +400,7 @@
     <pathelement location="${mercurial-agent.output.dir}"/>
     <path refid="library.teamcity-testsapi.classpath"/>
     <path refid="library.teamcity-impl.classpath"/>
+    <path refid="library.teamcityapi-agent.classpath"/>
   </path>
   
   <path id="mercurial-tests.runtime.module.classpath">
@@ -427,7 +429,7 @@
     <patternset refid="excluded.from.module.mercurial-tests"/>
   </patternset>
   
-  <path id="mercurial-tests.module.sourcepath">
+  <path id="mercurial-tests.module.test.sourcepath">
     <dirset dir="${module.mercurial-tests.basedir}/mercurial-tests">
       <include name="src"/>
     </dirset>
@@ -436,17 +438,22 @@
   
   <target name="compile.module.mercurial-tests" depends="compile.module.mercurial-tests.production,compile.module.mercurial-tests.tests" description="Compile module mercurial-tests"/>
   
-  <target name="compile.module.mercurial-tests.production" depends="compile.module.mercurial-server,compile.module.mercurial-common,compile.module.mercurial-agent" description="Compile module mercurial-tests; production classes">
-    <mkdir dir="${mercurial-tests.output.dir}"/>
-    <javac destdir="${mercurial-tests.output.dir}" debug="${compiler.debug}" nowarn="${compiler.generate.no.warnings}" memorymaximumsize="${compiler.max.memory}" fork="true">
+  <target name="compile.module.mercurial-tests.production" depends="compile.module.mercurial-server,compile.module.mercurial-common,compile.module.mercurial-agent" description="Compile module mercurial-tests; production classes"/>
+  
+  <target name="compile.module.mercurial-tests.tests" depends="compile.module.mercurial-tests.production" description="compile module mercurial-tests; test classes" unless="skip.tests">
+    <mkdir dir="${mercurial-tests.testoutput.dir}"/>
+    <javac destdir="${mercurial-tests.testoutput.dir}" debug="${compiler.debug}" nowarn="${compiler.generate.no.warnings}" memorymaximumsize="${compiler.max.memory}" fork="true">
       <compilerarg line="${compiler.args.mercurial-tests}"/>
-      <bootclasspath refid="mercurial-tests.module.bootclasspath"/>
       <classpath refid="mercurial-tests.module.classpath"/>
-      <src refid="mercurial-tests.module.sourcepath"/>
+      <classpath>
+        <path refid="mercurial-tests.module.classpath"/>
+        <pathelement location="${mercurial-tests.output.dir}"/>
+      </classpath>
+      <src refid="mercurial-tests.module.test.sourcepath"/>
       <patternset refid="excluded.from.compilation.mercurial-tests"/>
     </javac>
     
-    <copy todir="${mercurial-tests.output.dir}">
+    <copy todir="${mercurial-tests.testoutput.dir}">
       <fileset dir="${module.mercurial-tests.basedir}/mercurial-tests/src">
         <patternset refid="compiler.resources"/>
         <type type="file"/>
@@ -454,8 +461,6 @@
     </copy>
   </target>
   
-  <target name="compile.module.mercurial-tests.tests" depends="compile.module.mercurial-tests.production" description="compile module mercurial-tests; test classes" unless="skip.tests"/>
-  
   <target name="clean.module.mercurial-tests" description="cleanup module">
     <delete dir="${mercurial-tests.output.dir}"/>
     <delete dir="${mercurial-tests.testoutput.dir}"/>
@@ -467,5 +472,7 @@
   
   <target name="clean" depends="clean.module.main, clean.module.mercurial-common, clean.module.mercurial-agent, clean.module.mercurial-server, clean.module.mercurial-tests" description="cleanup all"/>
   
-  <target name="all" depends="init, clean, compile.module.main, compile.module.mercurial-common, compile.module.mercurial-agent, compile.module.mercurial-server, compile.module.mercurial-tests" description="build all"/>
+  <target name="build.modules" depends="init, clean, compile.module.main, compile.module.mercurial-common, compile.module.mercurial-agent, compile.module.mercurial-server, compile.module.mercurial-tests" description="build all modules"/>
+  
+  <target name="all" depends="build.modules" description="build all"/>
 </project>
\ No newline at end of file
--- a/teamcity-common.xml	Tue Aug 31 20:46:18 2010 +0400
+++ b/teamcity-common.xml	Wed Dec 22 12:15:44 2010 +0300
@@ -31,9 +31,14 @@
     <attribute name="server.lib.dir" default="" description="directory to get libs for the server part"/>
     <attribute name="server.lib.includes" default="none should match" description="includes pattern of the files in the directory"/>
     <attribute name="plugin.version" default="SNAPSHOT"
-               description="the version of the plugin to put into teamcity-plugin.xml instead of '@version@'"/>
+               description="the version of the plugin to put into teamcity-plugin.xml instead of '@Plugin_Version@'"/>
+    <attribute name="plugin.vendor" default="Unknown vendor"
+               description="name of the vendor to put into teamcity-plugin.xml instead of '@Plugin_Vendor@'"/>
+    <attribute name="plugin.vendor.url" default=""
+               description="vendor URL to put into teamcity-plugin.xml instead of '@Plugin_Vendor_Url@'"/>
 
-    <element name="additional-files" optional="yes" description="fileset of the files to pack into root of the plugin"/>
+    <element name="server-additional-files" optional="yes" description="fileset of the files to pack into root of the server plugin part"/>
+    <element name="agent-additional-files" optional="yes" description="fileset of the files to pack into root of the agent plugin part"/>
 
     <sequential>
 
@@ -91,17 +96,31 @@
         <param name="common.jar.path" value="${teamcity.internal.distrib.prep.common}/@{common.jar.name}"/>
       </antcall>
 
+      <!-- copy additional agent files -->
+      <mkdir dir="${teamcity.internal.distrib.prep.agent.jars}/@{name}" />
+      <copy todir="${teamcity.internal.distrib.prep.agent.jars}/@{name}">
+        <fileset dir="${basedir}" excludes="**"/>
+        <agent-additional-files/>
+      </copy>
+
+      <antcall target="pack.agent.part">
+        <param name="out.unpacked" value="@{out.unpacked}"/>
+        <param name="name" value="@{name}"/>
+      </antcall>
+
       <!-- copy teamcity-plugin.xml -->
       <antcall target="prepare.plugin.descriptor.file">
         <param name="source" value="@{plugin.descriptor.file}"/>
         <param name="dest" value="@{out.unpacked}/teamcity-plugin.xml"/>
         <param name="plugin.version" value="@{plugin.version}"/>
+        <param name="plugin.vendor" value="@{plugin.vendor}"/>
+        <param name="plugin.vendor.url" value="@{plugin.vendor.url}"/>
       </antcall>
 
-      <!-- copy additional files -->
-      <copy todir="@{out.unpacked}" failonerror="false">
-        <fileset dir="${basedir}" excludes="**/*"/>
-        <additional-files/>
+      <!-- copy additional server plugin files -->
+      <copy todir="@{out.unpacked}">
+        <fileset dir="${basedir}" excludes="**"/>
+        <server-additional-files/>
       </copy>
 
       <!-- clean -->
@@ -121,7 +140,7 @@
 
   <target name="check.teamcitydistribution" description="checks whether TeamCity distribution ready to be used by IDEA-generated build">
     <check.property name="path.variable.teamcitydistribution"
-                    fail-message="Please define 'teamcity.distribution' or 'path.variable.teamcitydistribution' property in build.properties file (see build.properties.dist). The property should point to unpacked TeamCity .tar.gz or .exe distribution."
+                    fail-message="Please define 'teamcity.distribution' or 'path.variable.teamcitydistribution' property (e.g. in build.properties file). The property should point to unpacked TeamCity .tar.gz or .exe distribution."
         />
 
     <condition property="teamcity.distribution.not.present">
@@ -133,6 +152,7 @@
     </condition>
     <fail if="teamcity.distribution.not.present"
           message="Cannot find TeamCity distribution at path '${path.variable.teamcitydistribution}'. The property 'teamcity.distribution' or 'path.variable.teamcitydistribution' should point to unpacked TeamCity .tar.gz or .exe distribution."/>
+    <echo message="Using ${path.variable.teamcitydistribution} as TeamCity distribution" />
   </target>
 
   <macrodef name="deploy.teamcity.plugin">
@@ -223,6 +243,11 @@
     </copy>
   </target>
 
+  <target name="pack.agent.part" if="agent.needed">
+    <zip destfile="${out.unpacked}/agent/${name}.zip">
+      <fileset dir="${teamcity.internal.distrib.prep.agent.jars}"/>
+    </zip>
+  </target>
 
   <target name="prepare.agent.part" if="agent.needed">
     <!-- prepare.agent.part -->
@@ -237,21 +262,18 @@
     <copy todir="${teamcity.internal.distrib.prep.agent.jars}/${name}/lib" failonerror="false">
       <fileset file="${common.jar.path}"/>
     </copy>
-
-    <zip destfile="${out.unpacked}/agent/${name}.zip">
-      <fileset dir="${teamcity.internal.distrib.prep.agent.jars}"/>
-    </zip>
   </target>
 
   <target name="prepare.plugin.descriptor.file" if="plugin.descriptor.file.needed">
     <echo message="Copying plugin descriptor file '${source}' to '${dest}'."/>
     <copy file="${source}" tofile="${dest}" failonerror="false"/>
 
-    <echo message="Replacing '@Plugin_Version@' on '${plugin.version}' in the copied plugin descriptor file."/>
-    <replace file="${dest}" value="${plugin.version}">
+    <echo message="Replacing '@Plugin_Version@', '@Plugin_Vendor@' and '@Plugin_Vendor_Url@' in the copied plugin descriptor file."/>
+    <replace file="${dest}">
+      <replacefilter token="@version@" value="${plugin.version}"/>
       <replacefilter token="@Plugin_Version@" value="${plugin.version}"/>
-      <replacefilter token="@Plugin_Vendor@" value="JetBrains, s.r.o."/>
-      <replacefilter token="@Plugin_Vendor_Url@" value="http://www.jetbrains.com"/>
+      <replacefilter token="@Plugin_Vendor@" value="${plugin.vendor}"/>
+      <replacefilter token="@Plugin_Vendor_Url@" value="${plugin.vendor.url}"/>
     </replace>
   </target>
 
@@ -263,4 +285,4 @@
           message="It seems you are trying to use the script several times in the same context. This script does not support such use. Please call this script as a separate Ant process, via 'ant' task."/>
   </target>
 
-</project>
+</project>
\ No newline at end of file
--- a/teamcity-plugin.xml	Tue Aug 31 20:46:18 2010 +0400
+++ b/teamcity-plugin.xml	Wed Dec 22 12:15:44 2010 +0300
@@ -6,8 +6,8 @@
     <display-name>VCS Support: Mercurial</display-name>
     <version>@Plugin_Version@</version>
     <vendor>
-      <name>@Plugin_Vendor@</name>
-      <url>@Plugin_Vendor_Url@</url>
+      <name>JetBrains, s.r.o.</name>
+      <url>http://www.jetbrains.com</url>
     </vendor>
   </info>
   <deployment use-separate-classloader="false"/>