changeset 106:8587a9c22d55

switch to new API
author Pavel.Sher
date Tue, 11 May 2010 00:05:11 +0400
parents 8cc11b0cbbd4
children d7330bc105aa
files mercurial-agent/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/MercurialAgentSideVcsSupport.java mercurial-server/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/MercurialVcsSupport.java mercurial-tests/mercurial-tests.iml mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/AgentSideCheckoutTest.java mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/MercurialVcsSupportTest.java mercurial.properties mercurial.xml
diffstat 7 files changed, 240 insertions(+), 221 deletions(-) [+]
line wrap: on
line diff
--- a/mercurial-agent/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/MercurialAgentSideVcsSupport.java	Wed Apr 14 19:18:53 2010 +0400
+++ b/mercurial-agent/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/MercurialAgentSideVcsSupport.java	Tue May 11 00:05:11 2010 +0400
@@ -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-server/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/MercurialVcsSupport.java	Wed Apr 14 19:18:53 2010 +0400
+++ b/mercurial-server/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/MercurialVcsSupport.java	Tue May 11 00:05:11 2010 +0400
@@ -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 {
@@ -474,6 +401,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();
   }
@@ -483,10 +502,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	Wed Apr 14 19:18:53 2010 +0400
+++ b/mercurial-tests/mercurial-tests.iml	Tue May 11 00:05:11 2010 +0400
@@ -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	Wed Apr 14 19:18:53 2010 +0400
+++ b/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/AgentSideCheckoutTest.java	Tue May 11 00:05:11 2010 +0400
@@ -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/MercurialVcsSupportTest.java	Wed Apr 14 19:18:53 2010 +0400
+++ b/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/MercurialVcsSupportTest.java	Tue May 11 00:05:11 2010 +0400
@@ -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;
@@ -79,10 +80,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 +115,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 +160,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 +169,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 +178,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 +187,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 +211,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 +259,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 +270,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);
@@ -285,7 +281,7 @@
   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(""));
+    List<ModificationData> changes = collectChanges(vcsRoot, "6:b9deb9a1c6f4", "12:1870e1100fab", IncludeRule.createDefaultInstance());
     for (ModificationData md: changes) {
       System.out.println(md);
     }
@@ -295,11 +291,7 @@
     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 +300,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 +309,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 +318,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 +346,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 +360,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 +375,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 +385,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.properties	Wed Apr 14 19:18:53 2010 +0400
+++ b/mercurial.properties	Tue May 11 00:05:11 2010 +0400
@@ -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\:/TeamCity51
+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	Wed Apr 14 19:18:53 2010 +0400
+++ b/mercurial.xml	Tue May 11 00:05:11 2010 +0400
@@ -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