changeset 567:27cd2503cea3

Extract MercurialCollectChangesPolicy
author Dmitry Neverov <dmitry.neverov@jetbrains.com>
date Wed, 06 Mar 2013 21:26:28 +0400
parents a7719626703d
children 333a1ddcbf94
files mercurial-server/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/MercurialCollectChangesPolicy.java mercurial-server/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/MercurialVcsSupport.java mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/DagFeaturesTest.java mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/MercurialVcsSupportTest.java mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/RevisionFormatTest.java mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/SubrepoChangesTest.java mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/UnrelatedResitoriesTest.java
diffstat 7 files changed, 415 insertions(+), 336 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial-server/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/MercurialCollectChangesPolicy.java	Wed Mar 06 21:26:28 2013 +0400
@@ -0,0 +1,384 @@
+package jetbrains.buildServer.buildTriggers.vcs.mercurial;
+
+import jetbrains.buildServer.buildTriggers.vcs.mercurial.command.*;
+import jetbrains.buildServer.buildTriggers.vcs.mercurial.command.exception.UnknownRevisionException;
+import jetbrains.buildServer.log.Loggers;
+import jetbrains.buildServer.serverSide.impl.LogUtil;
+import jetbrains.buildServer.vcs.*;
+import jetbrains.buildServer.vcs.impl.VcsRootImpl;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.net.URISyntaxException;
+import java.util.*;
+
+import static java.util.Arrays.asList;
+import static java.util.Collections.emptyList;
+import static java.util.Collections.emptyMap;
+
+public class MercurialCollectChangesPolicy implements CollectChangesBetweenRoots, CollectChangesBetweenRepositories {
+
+  private final MercurialVcsSupport myVcs;
+  private final ServerPluginConfig myConfig;
+  private final HgVcsRootFactory myHgVcsRootFactory;
+  private final RepoFactory myRepoFactory;
+  private final HgPathProvider myHgPathProvider;
+
+
+  public MercurialCollectChangesPolicy(@NotNull MercurialVcsSupport vcs,
+                                       @NotNull ServerPluginConfig config,
+                                       @NotNull HgVcsRootFactory hgVcsRootFactory,
+                                       @NotNull RepoFactory repoFactory,
+                                       @NotNull HgPathProvider hgPathProvider) {
+    myVcs = vcs;
+    myConfig = config;
+    myHgVcsRootFactory = hgVcsRootFactory;
+    myRepoFactory = repoFactory;
+    myHgPathProvider = hgPathProvider;
+  }
+
+
+  @NotNull
+  public RepositoryStateData getCurrentState(@NotNull VcsRoot root) throws VcsException {
+    HgVcsRoot hgRoot = myHgVcsRootFactory.createHgRoot(root);
+    myVcs.syncRepository(hgRoot);
+    Map<String, String> revisions = new HashMap<String, String>();
+    revisions.putAll(getBookmarkRevisions(hgRoot));
+    revisions.putAll(getBranchesRevisions(hgRoot));
+    String defaultBranchName = hgRoot.getBranchName();
+    if (revisions.get(defaultBranchName) == null) {
+      throw new VcsException("Cannot find revision of the default branch '" +
+              defaultBranchName + "' of vcs root " + LogUtil.describe(root));
+    }
+    return RepositoryStateData.createVersionState(defaultBranchName, revisions);
+  }
+
+
+  @NotNull
+  private Map<String, String> getBranchesRevisions(@NotNull HgVcsRoot root) throws VcsException {
+    HgRepo repo = myVcs.createRepo(root);
+    return repo.branches().call();
+  }
+
+
+  @NotNull
+  private Map<String, String> getBookmarkRevisions(@NotNull HgVcsRoot root) throws VcsException {
+    ServerHgRepo repo = myVcs.createRepo(root);
+    if (!myConfig.bookmarksEnabled())
+      return emptyMap();
+    HgVersion version = repo.version().call();
+    if (!version.isEqualsOrGreaterThan(BookmarksCommand.REQUIRED_HG_VERSION))
+      return emptyMap();
+    return repo.bookmarks().call();
+  }
+
+
+  @NotNull
+  public List<ModificationData> collectChanges(@NotNull VcsRoot fromRoot,
+                                               @NotNull RepositoryStateData fromState,
+                                               @NotNull VcsRoot toRoot,
+                                               @NotNull RepositoryStateData toState,
+                                               @NotNull CheckoutRules rules) throws VcsException {
+    return collectChanges(toRoot, fromState, toState, rules);
+  }
+
+
+  @NotNull
+  public List<ModificationData> collectChanges(@NotNull VcsRoot root,
+                                               @NotNull RepositoryStateData fromState,
+                                               @NotNull RepositoryStateData toState,
+                                               @NotNull CheckoutRules rules) throws VcsException {
+    List<ModificationData> changes = new ArrayList<ModificationData>();
+    HgVcsRoot hgRoot = myHgVcsRootFactory.createHgRoot(root);
+    OperationContext ctx = new OperationContext(myVcs, myRepoFactory, myHgPathProvider, fromState, toState);
+    for (Map.Entry<String, String> entry : toState.getBranchRevisions().entrySet()) {
+      String branch = entry.getKey();
+      String toRevision = entry.getValue();
+      String fromRevision = fromState.getBranchRevisions().get(branch);
+      if (fromRevision == null)
+        fromRevision = fromState.getBranchRevisions().get(fromState.getDefaultBranchName());
+      if (toRevision.equals(fromRevision) || fromRevision == null)
+        continue;
+
+      Collection<String> fromRevisions = ctx.getFromRevisionsForBranch(hgRoot, fromRevision, toRevision);
+      List<ModificationData> branchChanges = collectChanges(ctx, root, fromRevisions, toRevision, rules);
+      for (ModificationData change : branchChanges) {
+        if (!ctx.isReportedModification(change)) {
+          changes.add(change);
+          ctx.markAsReported(change);
+        }
+      }
+    }
+    changes.addAll(getSubrepoChanges(ctx, root, changes));
+    return changes;
+  }
+
+
+  @NotNull
+  public List<ModificationData> collectChanges(@NotNull VcsRoot fromRoot,
+                                               @NotNull String fromRootRevision,
+                                               @NotNull VcsRoot toRoot,
+                                               @Nullable String toRootRevision,
+                                               @NotNull CheckoutRules checkoutRules) throws VcsException {
+    HgVcsRoot hgRoot = myHgVcsRootFactory.createHgRoot(toRoot);
+    myVcs.syncRepository(hgRoot);
+    String toRevision = toRootRevision != null ? toRootRevision : myVcs.getCurrentVersion(toRoot);
+    String mergeBase = getMergeBase(hgRoot, fromRootRevision, toRevision);
+    if (mergeBase == null)
+      return Collections.emptyList();
+    return collectChanges(toRoot, mergeBase, toRootRevision, checkoutRules);
+  }
+
+
+  public List<ModificationData> collectChanges(@NotNull VcsRoot root,
+                                               @NotNull String fromVersion,
+                                               @Nullable String currentVersion,
+                                               @NotNull CheckoutRules checkoutRules) throws VcsException {
+    if (currentVersion == null)
+      return emptyList();
+    OperationContext ctx = new OperationContext(myVcs, myRepoFactory, myHgPathProvider, fromVersion, currentVersion);
+    List<ModificationData> changes = collectChanges(ctx, root, asList(fromVersion), currentVersion, checkoutRules);
+    changes.addAll(getSubrepoChanges(ctx, root, changes));
+    return changes;
+  }
+
+
+  @Nullable
+  private String getMergeBase(@NotNull HgVcsRoot root, @NotNull String revision1, @NotNull String revision2) throws VcsException {
+    String result = myVcs.createRepo(root).mergeBase()
+            .revision1(revision1)
+            .revision2(revision2)
+            .call();
+    if (result == null)
+      result = getMinusNthCommit(root, 10);
+    return result;
+  }
+
+
+  @Nullable
+  private String getMinusNthCommit(@NotNull HgVcsRoot root, int n) throws VcsException {
+    LogCommand log = myVcs.createRepo(root).log()
+            .inBranch(root.getBranchName())
+            .toNamedRevision(root.getBranchName());
+    if (n > 0)
+      log.setLimit(n);
+    List<ChangeSet> changeSets = log.call();
+    if (changeSets.isEmpty())
+      return null;
+    return changeSets.get(0).getId();
+  }
+
+
+  private List<ModificationData> collectChanges(@NotNull OperationContext ctx,
+                                                @NotNull VcsRoot root,
+                                                @NotNull Collection<String> fromVersion,
+                                                @Nullable String currentVersion,
+                                                @NotNull CheckoutRules checkoutRules) throws VcsException {
+    HgVcsRoot hgRoot = myHgVcsRootFactory.createHgRoot(root);
+    ctx.syncRepository(hgRoot);
+    List<ModificationData> result = new ArrayList<ModificationData>();
+    for (ChangeSet cset : getChangesets(ctx, hgRoot, fromVersion, currentVersion)) {
+      result.add(createModificationData(ctx, cset, root, checkoutRules));
+    }
+    return result;
+  }
+
+
+  @NotNull
+  private List<ChangeSet> getChangesets(@NotNull OperationContext ctx,
+                                        @NotNull final HgVcsRoot root,
+                                        @NotNull final Collection<String> fromVersions,
+                                        @Nullable final String toVersion) throws VcsException {
+    if (toVersion == null)
+      return emptyList();
+    List<String> fromCommits = new ArrayList<String>();
+    for (String fromVersion : fromVersions) {
+      fromCommits.add(new ChangeSetRevision(fromVersion).getId());
+    }
+    String toCommit = new ChangeSetRevision(toVersion).getId();
+    try {
+      ServerHgRepo repo = myVcs.createRepo(ctx, root);
+      List<ChangeSet> csets = repo.collectChanges(root)
+              .fromRevision(fromCommits)
+              .toRevision(toCommit)
+              .call();
+      Iterator<ChangeSet> iter = csets.iterator();
+      while (iter.hasNext()) {
+        ChangeSet cset = iter.next();
+        if (fromVersions.contains(cset.getId()))
+          iter.remove();
+      }
+      return csets;
+    } catch (UnknownRevisionException e) {
+      Loggers.VCS.warn("Revision '" + e.getRevision() + "' is unknown, will return no changes");
+      return emptyList();
+    }
+  }
+
+
+  private ModificationData createModificationData(@NotNull OperationContext ctx,
+                                                  @NotNull final ChangeSet cset,
+                                                  @NotNull final VcsRoot root,
+                                                  @NotNull final CheckoutRules checkoutRules) throws VcsException {
+    List<ChangeSetRevision> parents = cset.getParents();
+    if (parents.isEmpty())
+      throw new IllegalStateException("Commit " + cset.getId() + " has no parents");
+    List<VcsChange> files = toVcsChanges(cset.getModifiedFiles(), parents.get(0).getId(), cset.getId(), checkoutRules);
+    final ModificationData result = new ModificationData(cset.getTimestamp(), files, cset.getDescription(), cset.getUser(), root, cset.getId(), cset.getId());
+    for (ChangeSetRevision parent : parents) {
+      result.addParentRevision(parent.getId());
+    }
+    setCanBeIgnored(result, cset);
+    result.setAttributes(getAttributes(ctx, root, cset));
+    return result;
+  }
+
+
+  private void setCanBeIgnored(@NotNull ModificationData md, @NotNull ChangeSet cset) {
+    if (md.getParentRevisions().size() > 1) {
+      //don't ignore merge commits
+      md.setCanBeIgnored(false);
+    } else if (cset.getModifiedFiles().isEmpty()) {
+      //don't ignore empty commits
+      md.setCanBeIgnored(false);
+    }
+  }
+
+
+  private List<VcsChange> toVcsChanges(final List<FileStatus> modifiedFiles, String prevVer, String curVer, CheckoutRules rules) {
+    List<VcsChange> files = new ArrayList<VcsChange>();
+    for (FileStatus mf: modifiedFiles) {
+      final String path = rules.map(mf.getPath());
+      if (shouldInclude(path))
+        files.add(toVcsChange(mf, prevVer, curVer, path));
+    }
+    return files;
+  }
+
+
+  private boolean shouldInclude(String path) {
+    return path != null;
+  }
+
+
+  private VcsChange toVcsChange(FileStatus mf, String prevVer, String curVer, String mappedPath) {
+    String normalizedPath = PathUtil.normalizeSeparator(mf.getPath());
+    VcsChangeInfo.Type changeType = getChangeType(mf.getStatus());
+    if (changeType == null) {
+      Loggers.VCS.warn("Unable to convert status: " + mf.getStatus() + " to VCS change type");
+      changeType = VcsChangeInfo.Type.NOT_CHANGED;
+    }
+    return new VcsChange(changeType, mf.getStatus().getName(), normalizedPath, mappedPath, prevVer, curVer);
+  }
+
+
+  private VcsChangeInfo.Type getChangeType(final Status status) {
+    switch (status) {
+      case ADDED:return VcsChangeInfo.Type.ADDED;
+      case MODIFIED:return VcsChangeInfo.Type.CHANGED;
+      case REMOVED:return VcsChangeInfo.Type.REMOVED;
+    }
+    return null;
+  }
+
+
+  @NotNull
+  private List<ModificationData> getSubrepoChanges(@NotNull OperationContext ctx, @NotNull VcsRoot root, @NotNull List<ModificationData> changes) throws VcsException {
+    if (changes.isEmpty())
+      return emptyList();
+
+    HgVcsRoot hgRoot = myHgVcsRootFactory.createHgRoot(root);
+    if (!detectSubrepoChanges(hgRoot))
+      return emptyList();
+
+    List<SubrepoConfigChange> subrepoConfigChanges = getSubrepoConfigChanges(changes);
+    List<ModificationData> subrepoChanges = new ArrayList<ModificationData>();
+
+    for (SubrepoConfigChange configChange : subrepoConfigChanges) {
+      VcsRootImpl subrepo = new VcsRootImpl(root.getId(), configChange.getSubrepoRootParams());
+      if (ctx.isProcessedSubrepoChanges(subrepo, configChange.getPreviousSubrepoRevisions(), configChange.getCurrentSubrepoRevision()))
+        continue;
+      List<ModificationData> subChanges = collectChanges(ctx, subrepo, configChange.getPreviousSubrepoRevisions(), configChange.getCurrentSubrepoRevision(), CheckoutRules.DEFAULT);
+      for (ModificationData m : subChanges) {
+        if (!ctx.isReportedModification(m)) {
+          subrepoChanges.add(m);
+          ctx.markAsReported(m);
+        }
+      }
+      ctx.markProcessedSubrepoChanges(subrepo, configChange.getPreviousSubrepoRevisions(), configChange.getCurrentSubrepoRevision());
+    }
+
+    List<ModificationData> subSubrepoChanges = getSubrepoChanges(ctx, root, subrepoChanges);
+    subrepoChanges.addAll(subSubrepoChanges);
+    return subrepoChanges;
+  }
+
+
+  private boolean detectSubrepoChanges(@NotNull HgVcsRoot root) {
+    return myConfig.detectSubrepoChanges() && root.detectSubrepoChanges();
+  }
+
+
+  private List<SubrepoConfigChange> getSubrepoConfigChanges(@NotNull List<ModificationData> mainRootChanges) {
+    List<SubrepoConfigChange> subrepoConfigChanges = new ArrayList<SubrepoConfigChange>();
+    for (ModificationData m : mainRootChanges) {
+      subrepoConfigChanges.addAll(getSubrepoConfigChanges(m));
+    }
+    return subrepoConfigChanges;
+  }
+
+
+  private List<SubrepoConfigChange> getSubrepoConfigChanges(@NotNull ModificationData m) {
+    List<SubrepoConfigChange> configChanges = new ArrayList<SubrepoConfigChange>();
+    for (SubrepoConfigChange configChange : SubrepoConfigChangesAttributes.readSubrepoConfigChanges(m.getAttributes())) {
+      if (configChange.getSubrepoRootParams().isEmpty())
+        continue;
+      if (configChange.getPreviousSubrepoRevisions().isEmpty())
+        continue;
+      configChanges.add(configChange);
+    }
+    return configChanges;
+  }
+
+
+  @NotNull
+  private Map<String, String> getAttributes(@NotNull OperationContext ctx, @NotNull VcsRoot mainRoot, @NotNull ChangeSet cset) throws VcsException {
+    Map<String, String> attributes = new HashMap<String, String>();
+    HgVcsRoot root = myHgVcsRootFactory.createHgRoot(mainRoot);
+    if (detectSubrepoChanges(root)) {
+      try {
+        ServerHgRepo repo = myVcs.createRepo(ctx, root);
+        SubrepoConfigChangesAttributes builder = new SubrepoConfigChangesAttributes();
+        for (HgSubrepoConfigChange c : repo.getSubrepoConfigChanges(cset)) {
+          fillSubrepoConfigChanges(builder, root, c);
+        }
+        attributes.putAll(builder.buildAttributes());
+      } catch (Exception e) {
+        Loggers.VCS.warn("Error while reporting subrepo config changes", e);
+        if (e instanceof VcsExtension)
+          throw (VcsException) e;
+        throw new VcsException(e);
+      }
+    }
+    return attributes;
+  }
+
+
+  private void fillSubrepoConfigChanges(@NotNull SubrepoConfigChangesAttributes builder,
+                                        @NotNull HgVcsRoot mainRoot,
+                                        @NotNull HgSubrepoConfigChange c) throws URISyntaxException, VcsException {
+    List<String> prevRevisions = new ArrayList<String>();
+    if (!(c.subrepoUrlChanged() || c.subrepoAdded() || c.subrepoRemoved())) {
+      String subrepoUrl = c.getCurrent().resolveUrl(mainRoot.getRepository());
+      String curRevision = c.getCurrent().revision();
+      for (SubRepo prevSubrepo : c.getPrevious()) {
+        prevRevisions.add(prevSubrepo.revision());
+      }
+      builder.addSubrepoConfigChange(new SubrepoConfigChange(mainRoot)
+              .setSubrepoPath(c.getPath())
+              .setSubrepoRootParamDiff(Constants.REPOSITORY_PROP, subrepoUrl)
+              .setCurrentSubrepoRevision(curRevision)
+              .setPreviousSubrepoRevisions(prevRevisions));
+    }
+  }
+}
--- a/mercurial-server/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/MercurialVcsSupport.java	Wed Mar 06 17:44:40 2013 +0400
+++ b/mercurial-server/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/MercurialVcsSupport.java	Wed Mar 06 21:26:28 2013 +0400
@@ -18,7 +18,6 @@
 import jetbrains.buildServer.Used;
 import jetbrains.buildServer.buildTriggers.vcs.AbstractVcsPropertiesProcessor;
 import jetbrains.buildServer.buildTriggers.vcs.mercurial.command.*;
-import jetbrains.buildServer.buildTriggers.vcs.mercurial.command.exception.UnknownRevisionException;
 import jetbrains.buildServer.buildTriggers.vcs.mercurial.command.exception.UnrelatedRepositoryException;
 import jetbrains.buildServer.log.Loggers;
 import jetbrains.buildServer.serverSide.*;
@@ -27,7 +26,6 @@
 import jetbrains.buildServer.util.FileUtil;
 import jetbrains.buildServer.util.cache.ResetCacheRegister;
 import jetbrains.buildServer.vcs.*;
-import jetbrains.buildServer.vcs.impl.VcsRootImpl;
 import jetbrains.buildServer.vcs.patches.PatchBuilder;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
@@ -36,11 +34,8 @@
 import java.io.FileFilter;
 import java.io.FileInputStream;
 import java.io.IOException;
-import java.net.URISyntaxException;
 import java.util.*;
 
-import static java.util.Arrays.asList;
-import static java.util.Collections.emptyList;
 import static java.util.Collections.emptyMap;
 import static jetbrains.buildServer.buildTriggers.vcs.mercurial.HgFileUtil.deleteDir;
 
@@ -55,8 +50,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 ServerVcsSupport implements LabelingSupport, VcsFileContentProvider,
-        CollectChangesBetweenRoots, CollectChangesBetweenRepositories, BuildPatchByCheckoutRules {
+public class MercurialVcsSupport extends ServerVcsSupport implements LabelingSupport, VcsFileContentProvider, BuildPatchByCheckoutRules {
   private final MirrorManager myMirrorManager;
   private final ServerPluginConfig myConfig;
   private final HgPathProvider myHgPathProvider;
@@ -103,39 +97,6 @@
       Loggers.VCS.info("Server-wide hg path is not set, will use path from the VCS root settings");
   }
 
-  private List<VcsChange> toVcsChanges(final List<FileStatus> modifiedFiles, String prevVer, String curVer, CheckoutRules rules) {
-    List<VcsChange> files = new ArrayList<VcsChange>();
-    for (FileStatus mf: modifiedFiles) {
-      final String path = rules.map(mf.getPath());
-      if (shouldInclude(path))
-        files.add(toVcsChange(mf, prevVer, curVer, path));
-    }
-    return files;
-  }
-
-  private boolean shouldInclude(String path) {
-    return path != null;
-  }
-
-  private VcsChange toVcsChange(FileStatus mf, String prevVer, String curVer, String mappedPath) {
-    String normalizedPath = PathUtil.normalizeSeparator(mf.getPath());
-    VcsChangeInfo.Type changeType = getChangeType(mf.getStatus());
-    if (changeType == null) {
-      Loggers.VCS.warn("Unable to convert status: " + mf.getStatus() + " to VCS change type");
-      changeType = VcsChangeInfo.Type.NOT_CHANGED;
-    }
-    return new VcsChange(changeType, mf.getStatus().getName(), normalizedPath, mappedPath, prevVer, curVer);
-  }
-
-  private VcsChangeInfo.Type getChangeType(final Status status) {
-    switch (status) {
-      case ADDED:return VcsChangeInfo.Type.ADDED;
-      case MODIFIED:return VcsChangeInfo.Type.CHANGED;
-      case REMOVED:return VcsChangeInfo.Type.REMOVED;
-    }
-    return null;
-  }
-
   @NotNull
   public byte[] getContent(@NotNull final VcsModification vcsModification,
                            @NotNull final VcsChangeInfo change,
@@ -462,268 +423,13 @@
     return repo.bookmarks().call();
   }
 
-  @NotNull
-  public List<ModificationData> collectChanges(@NotNull VcsRoot fromRoot,
-                                               @NotNull RepositoryStateData fromState,
-                                               @NotNull VcsRoot toRoot,
-                                               @NotNull RepositoryStateData toState,
-                                               @NotNull CheckoutRules rules) throws VcsException {
-    return collectChanges(toRoot, fromState, toState, rules);
-  }
-
-  @NotNull
-  public List<ModificationData> collectChanges(@NotNull VcsRoot root,
-                                               @NotNull RepositoryStateData fromState,
-                                               @NotNull RepositoryStateData toState,
-                                               @NotNull CheckoutRules rules) throws VcsException {
-    List<ModificationData> changes = new ArrayList<ModificationData>();
-    HgVcsRoot hgRoot = myHgVcsRootFactory.createHgRoot(root);
-    OperationContext ctx = new OperationContext(this, myRepoFactory, myHgPathProvider, fromState, toState);
-    for (Map.Entry<String, String> entry : toState.getBranchRevisions().entrySet()) {
-      String branch = entry.getKey();
-      String toRevision = entry.getValue();
-      String fromRevision = fromState.getBranchRevisions().get(branch);
-      if (fromRevision == null)
-        fromRevision = fromState.getBranchRevisions().get(fromState.getDefaultBranchName());
-      if (toRevision.equals(fromRevision) || fromRevision == null)
-        continue;
-
-      Collection<String> fromRevisions = ctx.getFromRevisionsForBranch(hgRoot, fromRevision, toRevision);
-      List<ModificationData> branchChanges = collectChanges(ctx, root, fromRevisions, toRevision, rules);
-      for (ModificationData change : branchChanges) {
-        if (!ctx.isReportedModification(change)) {
-          changes.add(change);
-          ctx.markAsReported(change);
-        }
-      }
-    }
-    changes.addAll(getSubrepoChanges(ctx, root, changes));
-    return changes;
-  }
-
-
-  @NotNull
-  public List<ModificationData> collectChanges(@NotNull VcsRoot fromRoot, @NotNull String fromRootRevision,
-                                               @NotNull VcsRoot toRoot, @Nullable String toRootRevision,
-                                               @NotNull CheckoutRules checkoutRules) throws VcsException {
-    HgVcsRoot hgRoot = myHgVcsRootFactory.createHgRoot(toRoot);
-    syncRepository(hgRoot);
-    String toRevision = toRootRevision != null ? toRootRevision : getCurrentVersion(toRoot);
-    String mergeBase = getMergeBase(hgRoot, fromRootRevision, toRevision);
-    if (mergeBase == null)
-      return Collections.emptyList();
-    return collectChanges(toRoot, mergeBase, toRootRevision, checkoutRules);
-  }
-
-
-  @Nullable
-  public String getMergeBase(@NotNull HgVcsRoot root, @NotNull String revision1, @NotNull String revision2) throws VcsException {
-    String result = createRepo(root).mergeBase()
-            .revision1(revision1)
-            .revision2(revision2)
-            .call();
-    if (result == null)
-      result = getMinusNthCommit(root, 10);
-    return result;
-  }
-
-
-  @Nullable
-  private String getMinusNthCommit(@NotNull HgVcsRoot root, int n) throws VcsException {
-    LogCommand log = createRepo(root).log()
-            .inBranch(root.getBranchName())
-            .toNamedRevision(root.getBranchName());
-    if (n > 0)
-      log.setLimit(n);
-    List<ChangeSet> changeSets = log.call();
-    if (changeSets.isEmpty())
-      return null;
-    return changeSets.get(0).getId();
-  }
-
-
-  @NotNull
-  public CollectChangesPolicy getCollectChangesPolicy() {
-    return this;
-  }
-
-  public List<ModificationData> collectChanges(@NotNull VcsRoot root, @NotNull String fromVersion, @Nullable String currentVersion, @NotNull CheckoutRules checkoutRules) throws VcsException {
-    if (currentVersion == null)
-      return emptyList();
-    OperationContext ctx = new OperationContext(this, myRepoFactory, myHgPathProvider, fromVersion, currentVersion);
-    List<ModificationData> changes = collectChanges(ctx, root, asList(fromVersion), currentVersion, checkoutRules);
-    changes.addAll(getSubrepoChanges(ctx, root, changes));
-    return changes;
-  }
-
-  private List<ModificationData> collectChanges(@NotNull OperationContext ctx,
-                                                @NotNull VcsRoot root,
-                                                @NotNull Collection<String> fromVersion,
-                                                @Nullable String currentVersion,
-                                                @NotNull CheckoutRules checkoutRules) throws VcsException {
-    HgVcsRoot hgRoot = myHgVcsRootFactory.createHgRoot(root);
-    ctx.syncRepository(hgRoot);
-    List<ModificationData> result = new ArrayList<ModificationData>();
-    for (ChangeSet cset : getChangesets(ctx, hgRoot, fromVersion, currentVersion)) {
-      result.add(createModificationData(ctx, cset, root, checkoutRules));
-    }
-    return result;
-  }
-
 
   @NotNull
-  private List<ModificationData> getSubrepoChanges(@NotNull OperationContext ctx, @NotNull VcsRoot root, @NotNull List<ModificationData> changes) throws VcsException {
-    if (changes.isEmpty())
-      return emptyList();
-
-    HgVcsRoot hgRoot = myHgVcsRootFactory.createHgRoot(root);
-    if (!detectSubrepoChanges(hgRoot))
-      return emptyList();
-
-    List<SubrepoConfigChange> subrepoConfigChanges = getSubrepoConfigChanges(changes);
-    List<ModificationData> subrepoChanges = new ArrayList<ModificationData>();
-
-    for (SubrepoConfigChange configChange : subrepoConfigChanges) {
-      VcsRootImpl subrepo = new VcsRootImpl(root.getId(), configChange.getSubrepoRootParams());
-      if (ctx.isProcessedSubrepoChanges(subrepo, configChange.getPreviousSubrepoRevisions(), configChange.getCurrentSubrepoRevision()))
-        continue;
-      List<ModificationData> subChanges = collectChanges(ctx, subrepo, configChange.getPreviousSubrepoRevisions(), configChange.getCurrentSubrepoRevision(), CheckoutRules.DEFAULT);
-      for (ModificationData m : subChanges) {
-        if (!ctx.isReportedModification(m)) {
-          subrepoChanges.add(m);
-          ctx.markAsReported(m);
-        }
-      }
-      ctx.markProcessedSubrepoChanges(subrepo, configChange.getPreviousSubrepoRevisions(), configChange.getCurrentSubrepoRevision());
-    }
-
-    List<ModificationData> subSubrepoChanges = getSubrepoChanges(ctx, root, subrepoChanges);
-    subrepoChanges.addAll(subSubrepoChanges);
-    return subrepoChanges;
-  }
-
-
-  private boolean detectSubrepoChanges(@NotNull HgVcsRoot root) {
-    return myConfig.detectSubrepoChanges() && root.detectSubrepoChanges();
-  }
-
-
-  private List<SubrepoConfigChange> getSubrepoConfigChanges(@NotNull List<ModificationData> mainRootChanges) {
-    List<SubrepoConfigChange> subrepoConfigChanges = new ArrayList<SubrepoConfigChange>();
-    for (ModificationData m : mainRootChanges) {
-      subrepoConfigChanges.addAll(getSubrepoConfigChanges(m));
-    }
-    return subrepoConfigChanges;
-  }
-
-
-  private List<SubrepoConfigChange> getSubrepoConfigChanges(@NotNull ModificationData m) {
-    List<SubrepoConfigChange> configChanges = new ArrayList<SubrepoConfigChange>();
-    for (SubrepoConfigChange configChange : SubrepoConfigChangesAttributes.readSubrepoConfigChanges(m.getAttributes())) {
-      if (configChange.getSubrepoRootParams().isEmpty())
-        continue;
-      if (configChange.getPreviousSubrepoRevisions().isEmpty())
-        continue;
-      configChanges.add(configChange);
-    }
-    return configChanges;
-  }
-
-
-  private ModificationData createModificationData(@NotNull OperationContext ctx, @NotNull final ChangeSet cset, @NotNull final VcsRoot root, @NotNull final CheckoutRules checkoutRules) throws VcsException {
-    List<ChangeSetRevision> parents = cset.getParents();
-    if (parents.isEmpty())
-      throw new IllegalStateException("Commit " + cset.getId() + " has no parents");
-    List<VcsChange> files = toVcsChanges(cset.getModifiedFiles(), parents.get(0).getId(), cset.getId(), checkoutRules);
-    final ModificationData result = new ModificationData(cset.getTimestamp(), files, cset.getDescription(), cset.getUser(), root, cset.getId(), cset.getId());
-    for (ChangeSetRevision parent : parents) {
-      result.addParentRevision(parent.getId());
-    }
-    setCanBeIgnored(result, cset);
-    result.setAttributes(getAttributes(ctx, root, cset));
-    return result;
+  public MercurialCollectChangesPolicy getCollectChangesPolicy() {
+    return new MercurialCollectChangesPolicy(this, myConfig, myHgVcsRootFactory, myRepoFactory, myHgPathProvider);
   }
 
 
-  private void setCanBeIgnored(@NotNull ModificationData md, @NotNull ChangeSet cset) {
-    if (md.getParentRevisions().size() > 1) {
-      //don't ignore merge commits
-      md.setCanBeIgnored(false);
-    } else if (cset.getModifiedFiles().isEmpty()) {
-      //don't ignore empty commits
-      md.setCanBeIgnored(false);
-    }
-  }
-
-
-  @NotNull
-  private List<ChangeSet> getChangesets(@NotNull OperationContext ctx, @NotNull final HgVcsRoot root, @NotNull final Collection<String> fromVersions, @Nullable final String toVersion) throws VcsException {
-    if (toVersion == null)
-      return Collections.emptyList();
-    List<String> fromCommits = new ArrayList<String>();
-    for (String fromVersion : fromVersions) {
-      fromCommits.add(new ChangeSetRevision(fromVersion).getId());
-    }
-    String toCommit = new ChangeSetRevision(toVersion).getId();
-    try {
-      ServerHgRepo repo = createRepo(ctx, root);
-      List<ChangeSet> csets = repo.collectChanges(root)
-              .fromRevision(fromCommits)
-              .toRevision(toCommit)
-              .call();
-      Iterator<ChangeSet> iter = csets.iterator();
-      while (iter.hasNext()) {
-        ChangeSet cset = iter.next();
-        if (fromVersions.contains(cset.getId()))
-          iter.remove();
-      }
-      return csets;
-    } catch (UnknownRevisionException e) {
-      Loggers.VCS.warn("Revision '" + e.getRevision() + "' is unknown, will return no changes");
-      return Collections.emptyList();
-    }
-  }
-
-  @NotNull
-  private Map<String, String> getAttributes(@NotNull OperationContext ctx, @NotNull VcsRoot mainRoot, @NotNull ChangeSet cset) throws VcsException {
-    Map<String, String> attributes = new HashMap<String, String>();
-    HgVcsRoot root = myHgVcsRootFactory.createHgRoot(mainRoot);
-    if (detectSubrepoChanges(root)) {
-      try {
-        ServerHgRepo repo = createRepo(ctx, root);
-        SubrepoConfigChangesAttributes builder = new SubrepoConfigChangesAttributes();
-        for (HgSubrepoConfigChange c : repo.getSubrepoConfigChanges(cset)) {
-          fillSubrepoConfigChanges(builder, root, c);
-        }
-        attributes.putAll(builder.buildAttributes());
-      } catch (Exception e) {
-        Loggers.VCS.warn("Error while reporting subrepo config changes", e);
-        if (e instanceof VcsExtension)
-          throw (VcsException) e;
-        throw new VcsException(e);
-      }
-    }
-    return attributes;
-  }
-
-  private void fillSubrepoConfigChanges(@NotNull SubrepoConfigChangesAttributes builder,
-                                        @NotNull HgVcsRoot mainRoot,
-                                        @NotNull HgSubrepoConfigChange c) throws URISyntaxException, VcsException {
-    List<String> prevRevisions = new ArrayList<String>();
-    if (!(c.subrepoUrlChanged() || c.subrepoAdded() || c.subrepoRemoved())) {
-      String subrepoUrl = c.getCurrent().resolveUrl(mainRoot.getRepository());
-      String curRevision = c.getCurrent().revision();
-      for (SubRepo prevSubrepo : c.getPrevious()) {
-        prevRevisions.add(prevSubrepo.revision());
-      }
-      builder.addSubrepoConfigChange(new SubrepoConfigChange(mainRoot)
-              .setSubrepoPath(c.getPath())
-              .setSubrepoRootParamDiff(Constants.REPOSITORY_PROP, subrepoUrl)
-              .setCurrentSubrepoRevision(curRevision)
-              .setPreviousSubrepoRevisions(prevRevisions));
-    }
-  }
-
   @NotNull
   public BuildPatchPolicy getBuildPatchPolicy() {
     return this;
@@ -857,16 +563,6 @@
   }
 
   @NotNull
-  public String getBranchName(@NotNull final VcsRoot root) {
-    try {
-      HgVcsRoot hgRoot = myHgVcsRootFactory.createHgRoot(root);
-      return hgRoot.getBranchName();
-    } catch (VcsException e) {
-      return "default";
-    }
-  }
-
-  @NotNull
   @Override
   public Map<String, String> getCheckoutProperties(@NotNull VcsRoot root) {
     Map<String, String> rootProperties = root.getProperties();
--- a/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/DagFeaturesTest.java	Wed Mar 06 17:44:40 2013 +0400
+++ b/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/DagFeaturesTest.java	Wed Mar 06 21:26:28 2013 +0400
@@ -54,13 +54,13 @@
   public void should_detect_changes_from_named_branches() throws Exception {
     VcsRootImpl root = new VcsRootBuilder().withUrl(myRepository).build();
 
-    List<ModificationData> changes = myHg.collectChanges(root, "8:b6e2d176fe8e", "12:1e620196c4b6", CheckoutRules.DEFAULT);
+    List<ModificationData> changes = myHg.getCollectChangesPolicy().collectChanges(root, "8:b6e2d176fe8e", "12:1e620196c4b6", CheckoutRules.DEFAULT);
     assertEquals(4, changes.size());
     for (ModificationData change : changes) {
       assertFalse(change.getParentRevisions().isEmpty());
     }
 
-    changes = myHg.collectChanges(root, "12:1e620196c4b6", "18:df04faa7575a", CheckoutRules.DEFAULT);
+    changes = myHg.getCollectChangesPolicy().collectChanges(root, "12:1e620196c4b6", "18:df04faa7575a", CheckoutRules.DEFAULT);
     assertEquals(6, changes.size());
     for (ModificationData change : changes) {
       assertFalse(change.getParentRevisions().isEmpty());
@@ -71,7 +71,7 @@
   @TestFor(issues = "TW-17882")
   public void should_report_changes_only_from_merged_named_branches() throws Exception {
     VcsRootImpl root = new VcsRootBuilder().withUrl(myRepository).build();
-    List<ModificationData> changes = myHg.collectChanges(root, "1e620196c4b6", "505c5b9d01e6", CheckoutRules.DEFAULT);
+    List<ModificationData> changes = myHg.getCollectChangesPolicy().collectChanges(root, "1e620196c4b6", "505c5b9d01e6", CheckoutRules.DEFAULT);
     assertEquals(2, changes.size());
   }
 }
--- a/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/MercurialVcsSupportTest.java	Wed Mar 06 17:44:40 2013 +0400
+++ b/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/MercurialVcsSupportTest.java	Wed Mar 06 21:26:28 2013 +0400
@@ -73,20 +73,20 @@
   }
 
   private List<ModificationData> collectChanges(@NotNull VcsRoot vcsRoot, @NotNull String from, @NotNull String to, @NotNull CheckoutRules rules) throws VcsException {
-    return myVcs.collectChanges(vcsRoot, from, to, rules);
+    return myVcs.getCollectChangesPolicy().collectChanges(vcsRoot, from, to, rules);
   }
 
   public void test_collect_changes_between_two_same_roots() throws Exception {
     VcsRootImpl vcsRoot = createVcsRoot(simpleRepo());
     VcsRootImpl sameVcsRoot = createVcsRoot(simpleRepo());
-    List<ModificationData> changes = myVcs.collectChanges(vcsRoot, "0:9875b412a788", sameVcsRoot, "3:9522278aa38d", new CheckoutRules(""));
+    List<ModificationData> changes = myVcs.getCollectChangesPolicy().collectChanges(vcsRoot, "0:9875b412a788", sameVcsRoot, "3:9522278aa38d", new CheckoutRules(""));
     do_check_for_collect_changes(changes);
   }
 
   public void test_collect_changes_from_non_existing_revision() throws Exception {
     VcsRootImpl vcsRoot = createVcsRoot(simpleRepo());
     VcsRootImpl sameVcsRoot = createVcsRoot(simpleRepo());
-    List<ModificationData> changes = myVcs.collectChanges(vcsRoot, "0:9875b412a789", sameVcsRoot, "3:9522278aa38d", new CheckoutRules(""));
+    List<ModificationData> changes = myVcs.getCollectChangesPolicy().collectChanges(vcsRoot, "0:9875b412a789", sameVcsRoot, "3:9522278aa38d", new CheckoutRules(""));
     assertFalse(changes.isEmpty());//should return some changes from the toRoot
   }
 
@@ -284,7 +284,7 @@
   @TestFor(issues = "TW-10076")
   public void should_allow_to_ignore_changes_where_all_files_excluded() throws Exception {
     VcsRootImpl root = createVcsRoot(myRep2Path);
-    List<ModificationData> changes = myVcs.collectChanges(root, "43023ea3f13b", "a2672ee13212", new CheckoutRules("-:.hgtags"));
+    List<ModificationData> changes = myVcs.getCollectChangesPolicy().collectChanges(root, "43023ea3f13b", "a2672ee13212", new CheckoutRules("-:.hgtags"));
     assertEquals(changes.size(), 1);
     assertTrue(changes.get(0).isCanBeIgnored());
   }
@@ -293,7 +293,7 @@
   @TestFor(issues = "TW-10076")
   public void should_not_allow_to_ignore_close_branch_commits() throws Exception {
     VcsRootImpl root = createVcsRoot(myRep2Path);
-    List<ModificationData> changes = myVcs.collectChanges(root, "df04faa7575a", "43023ea3f13b", CheckoutRules.DEFAULT);
+    List<ModificationData> changes = myVcs.getCollectChangesPolicy().collectChanges(root, "df04faa7575a", "43023ea3f13b", CheckoutRules.DEFAULT);
     assertEquals(changes.size(), 2);
     assertEquals(changes.get(1).getChangeCount(), 0);
     assertFalse(changes.get(1).isCanBeIgnored());
@@ -308,7 +308,7 @@
     myVcs.label("tag_by_specified_user", "10:9c6a6b4aede0", root, CheckoutRules.DEFAULT);
 
     String currentVersion = myVcs.getCurrentVersion(root);
-    List<ModificationData> changes = myVcs.collectChanges(root, "10:9c6a6b4aede0", currentVersion, CheckoutRules.DEFAULT);
+    List<ModificationData> changes = myVcs.getCollectChangesPolicy().collectChanges(root, "10:9c6a6b4aede0", currentVersion, CheckoutRules.DEFAULT);
     assertEquals(changes.size(), 1);
     assertEquals(changes.get(0).getUserName(), customUserForTag);
   }
@@ -440,7 +440,7 @@
   public void test_collect_changes_between_two_different_roots() throws Exception {
     VcsRootImpl defaultRoot = createVcsRoot(mergeCommittsRepo());
     VcsRootImpl branchRoot = createVcsRoot(mergeCommittsRepo(), "test");
-    List<ModificationData> changes = myVcs.collectChanges(defaultRoot, "11:48177654181c", branchRoot, "10:fc524efc2bc4", CheckoutRules.DEFAULT);
+    List<ModificationData> changes = myVcs.getCollectChangesPolicy().collectChanges(defaultRoot, "11:48177654181c", branchRoot, "10:fc524efc2bc4", CheckoutRules.DEFAULT);
     assertEquals(changes.size(), 2);
 
     assertEquals(changes.get(0).getVersion(), "8c44244d6645");
@@ -450,7 +450,7 @@
   public void collectChanges_should_return_all_changes_from_branch() throws Exception {
     VcsRootImpl defaultBranchRoot = createVcsRoot(myRep2Path, "default");
     VcsRootImpl personalBranchRoot = createVcsRoot(myRep2Path, "personal-branch");
-    List<ModificationData> modifications = myVcs.collectChanges(defaultBranchRoot, "16:505c5b9d01e6", personalBranchRoot, "17:9ec402c74298", CheckoutRules.DEFAULT);
+    List<ModificationData> modifications = myVcs.getCollectChangesPolicy().collectChanges(defaultBranchRoot, "16:505c5b9d01e6", personalBranchRoot, "17:9ec402c74298", CheckoutRules.DEFAULT);
     assertEquals(3, modifications.size());
   }
 
@@ -562,7 +562,7 @@
 
   public void collect_changes_between_states() throws Exception {
     VcsRootImpl root = createVcsRoot(myRep2Path);
-    List<ModificationData> changes = myVcs.collectChanges(root,
+    List<ModificationData> changes = myVcs.getCollectChangesPolicy().collectChanges(root,
             RepositoryStateData.createVersionState("default", map("default", "1e620196c4b6")),
             RepositoryStateData.createVersionState("default", map("default", "505c5b9d01e6", "personal-branch", "96b78d73081d")),
             CheckoutRules.DEFAULT);
@@ -576,7 +576,7 @@
 
   public void collect_changes_between_states_does_not_report_duplicate_changes() throws Exception {
     VcsRootImpl root = createVcsRoot(myRep2Path);
-    List<ModificationData> changes = myVcs.collectChanges(root,
+    List<ModificationData> changes = myVcs.getCollectChangesPolicy().collectChanges(root,
             RepositoryStateData.createVersionState("default", map("default", "8c44244d6645")),
             RepositoryStateData.createVersionState("default", map("default", "505c5b9d01e6", "personal-branch", "9ec402c74298")),
             CheckoutRules.DEFAULT);
@@ -596,7 +596,7 @@
   @Test(dataProviderClass = HgVersionConstraint.class, dataProvider = "installedHgVersion")
   public void should_not_report_redundant_changes_after_merge(@NotNull HgVersion _) throws Exception {
     VcsRootImpl root = createVcsRoot(myRep2Path);
-    List<ModificationData> changes = myVcs.collectChanges(root,
+    List<ModificationData> changes = myVcs.getCollectChangesPolicy().collectChanges(root,
             RepositoryStateData.createVersionState("default", map("default", "505c5b9d01e6", "personal-branch", "9ec402c74298")),
             RepositoryStateData.createVersionState("default", map("default", "df04faa7575a", "personal-branch", "9ec402c74298")),
             CheckoutRules.DEFAULT);
@@ -608,7 +608,7 @@
   @Test(dataProviderClass = HgVersionConstraint.class, dataProvider = "installedHgVersion")
   public void should_not_report_duplicate_changes(@NotNull HgVersion _) throws Exception {
     VcsRootImpl root = createVcsRoot(myRep2Path);
-    List<ModificationData> changes = myVcs.collectChanges(root,
+    List<ModificationData> changes = myVcs.getCollectChangesPolicy().collectChanges(root,
             RepositoryStateData.createVersionState("default", map("default", "505c5b9d01e6", "personal-branch", "96b78d73081d")),
             RepositoryStateData.createVersionState("default", map("default", "df04faa7575a", "personal-branch", "9ec402c74298")),
             CheckoutRules.DEFAULT);
@@ -620,7 +620,7 @@
   @Test(dataProviderClass = HgVersionConstraint.class, dataProvider = "installedHgVersion")
   public void should_not_report_duplicate_changes2(@NotNull HgVersion _) throws Exception {
     VcsRootImpl root = createVcsRoot(myRep2Path);
-    List<ModificationData> changes = myVcs.collectChanges(root,
+    List<ModificationData> changes = myVcs.getCollectChangesPolicy().collectChanges(root,
             RepositoryStateData.createVersionState("default", map("default", "528572bbf77b", "personal-branch", "27184c50d7ef")),
             RepositoryStateData.createVersionState("default", map("default", "4780519e01aa", "personal-branch", "fd50e4842211")),
             CheckoutRules.DEFAULT);
@@ -632,7 +632,7 @@
   @Test(dataProviderClass = HgVersionConstraint.class, dataProvider = "installedHgVersion")
   public void should_not_report_all_changes_in_repository_if_default_branch_is_unrelated(@NotNull HgVersion _) throws Exception {
     VcsRootImpl root = createVcsRoot(myRep2Path);
-    List<ModificationData> changes = myVcs.collectChanges(root,
+    List<ModificationData> changes = myVcs.getCollectChangesPolicy().collectChanges(root,
             RepositoryStateData.createVersionState("NULL", map("NULL", "1f355761350e")),
             RepositoryStateData.createVersionState("NULL", map("NULL", "1f355761350e", "personal-branch", "fd50e4842211")),
             CheckoutRules.DEFAULT);
--- a/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/RevisionFormatTest.java	Wed Mar 06 17:44:40 2013 +0400
+++ b/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/RevisionFormatTest.java	Wed Mar 06 21:26:28 2013 +0400
@@ -54,9 +54,9 @@
 
 
   public void collect_changes_result_does_not_depend_on_revnums() throws VcsException {
-    List<ModificationData> changesWithRevnums = myVcs.collectChanges(myRoot,
+    List<ModificationData> changesWithRevnums = myVcs.getCollectChangesPolicy().collectChanges(myRoot,
             "1:1d446e82d356", "3:9522278aa38d", CheckoutRules.DEFAULT);
-    List<ModificationData> changesWithoutRevnums = myVcs.collectChanges(myRoot,
+    List<ModificationData> changesWithoutRevnums = myVcs.getCollectChangesPolicy().collectChanges(myRoot,
             "1d446e82d356", "9522278aa38d", CheckoutRules.DEFAULT);
     assertEquals(changesWithoutRevnums, changesWithRevnums);
   }
@@ -117,7 +117,7 @@
 
 
   public void should_not_include_revnum_in_collected_changes() throws VcsException {
-    List<ModificationData> changes = myVcs.collectChanges(myRoot, "1d446e82d356", "9522278aa38d", CheckoutRules.DEFAULT);
+    List<ModificationData> changes = myVcs.getCollectChangesPolicy().collectChanges(myRoot, "1d446e82d356", "9522278aa38d", CheckoutRules.DEFAULT);
     for (ModificationData c : changes) {
       assertFalse(c.getVersion().contains(":"), "Change version contains revnum: " + c.toString());
       for (String parentVersion : c.getParentRevisions()) {
--- a/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/SubrepoChangesTest.java	Wed Mar 06 17:44:40 2013 +0400
+++ b/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/SubrepoChangesTest.java	Wed Mar 06 21:26:28 2013 +0400
@@ -20,7 +20,6 @@
 import static jetbrains.buildServer.buildTriggers.vcs.mercurial.ModificationDataMatcher.modificationData;
 import static jetbrains.buildServer.buildTriggers.vcs.mercurial.Util.copyRepository;
 import static jetbrains.buildServer.buildTriggers.vcs.mercurial.VcsRootBuilder.vcsRoot;
-import static jetbrains.buildServer.util.Util.map;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.hamcrest.Matchers.*;
 import static org.testng.AssertJUnit.assertEquals;
@@ -64,28 +63,28 @@
 
   public void should_report_changes_from_subrepos(@NotNull HgVersion _) throws Exception {
     VcsRoot root = vcsRoot().withUrl(myRemoteRepo1.getAbsolutePath()).withSubrepoChanges(true).build();
-    List<ModificationData> changes = myVcs.collectChanges(root, "d350e7209906", "09c256b6163e", CheckoutRules.DEFAULT);
+    List<ModificationData> changes = myVcs.getCollectChangesPolicy().collectChanges(root, "d350e7209906", "09c256b6163e", CheckoutRules.DEFAULT);
     assertEquals(3, changes.size());
   }
 
 
   public void should_not_report_any_changes_when_subrepo_removed(@NotNull HgVersion _) throws Exception {
     VcsRoot root = vcsRoot().withUrl(myRemoteRepo1.getAbsolutePath()).withSubrepoChanges(true).build();
-    List<ModificationData> changes = myVcs.collectChanges(root, "34017377d9c3", "4d7b3db8779f", CheckoutRules.DEFAULT);
+    List<ModificationData> changes = myVcs.getCollectChangesPolicy().collectChanges(root, "34017377d9c3", "4d7b3db8779f", CheckoutRules.DEFAULT);
     assertEquals(1, changes.size());
   }
 
 
   public void should_not_report_any_changes_when_subrepo_added(@NotNull HgVersion _) throws Exception {
     VcsRoot root = vcsRoot().withUrl(myRemoteRepo1.getAbsolutePath()).withSubrepoChanges(true).build();
-    List<ModificationData> changes = myVcs.collectChanges(root, "4d7b3db8779f", "d350e7209906", CheckoutRules.DEFAULT);
+    List<ModificationData> changes = myVcs.getCollectChangesPolicy().collectChanges(root, "4d7b3db8779f", "d350e7209906", CheckoutRules.DEFAULT);
     assertEquals(1, changes.size());
   }
 
 
   public void should_report_subrepo_changes_recursevly(@NotNull HgVersion _) throws Exception {
     VcsRoot root = vcsRoot().withUrl(myRemoteRepo1.getAbsolutePath()).withSubrepoChanges(true).build();
-    List<ModificationData> changes = myVcs.collectChanges(root, "09c256b6163e", "d64d9799c143", CheckoutRules.DEFAULT);
+    List<ModificationData> changes = myVcs.getCollectChangesPolicy().collectChanges(root, "09c256b6163e", "d64d9799c143", CheckoutRules.DEFAULT);
     assertEquals(5, changes.size());
 
     assertThat(changes, hasItem(modificationData().withVersion("d64d9799c143").withVcsRootProperties(root.getProperties())));
@@ -114,7 +113,7 @@
             .build();
     myVcs = mercurialSupport().withConfig(pluginConfig).build();
     VcsRoot root = vcsRoot().withUrl(myRemoteRepo1.getAbsolutePath()).withSubrepoChanges(true).build();
-    List<ModificationData> changes = myVcs.collectChanges(root, "d350e7209906", "09c256b6163e", CheckoutRules.DEFAULT);
+    List<ModificationData> changes = myVcs.getCollectChangesPolicy().collectChanges(root, "d350e7209906", "09c256b6163e", CheckoutRules.DEFAULT);
     assertEquals(3, changes.size());
   }
 
@@ -126,7 +125,7 @@
             .build();
     myVcs = mercurialSupport().withConfig(pluginConfig).build();
     VcsRoot root = vcsRoot().withUrl(myRemoteRepo1.getAbsolutePath()).withSubrepoChanges(true).build();
-    List<ModificationData> changes = myVcs.collectChanges(root, "09c256b6163e", "d64d9799c143", CheckoutRules.DEFAULT);
+    List<ModificationData> changes = myVcs.getCollectChangesPolicy().collectChanges(root, "09c256b6163e", "d64d9799c143", CheckoutRules.DEFAULT);
     ModificationData m = changes.get(0);
     Map<String, String> attrs = m.getAttributes();
     List<SubrepoConfigChange> subrepoConfigChanges = SubrepoConfigChangesAttributes.readSubrepoConfigChanges(attrs);
@@ -146,7 +145,7 @@
             .build();
     myVcs = mercurialSupport().withConfig(pluginConfig).build();
     VcsRoot root = vcsRoot().withUrl(myRemoteRepo1.getAbsolutePath()).withSubrepoChanges(true).build();
-    List<ModificationData> changes = myVcs.collectChanges(root, "09c256b6163e", "d64d9799c143", CheckoutRules.DEFAULT);
+    List<ModificationData> changes = myVcs.getCollectChangesPolicy().collectChanges(root, "09c256b6163e", "d64d9799c143", CheckoutRules.DEFAULT);
     assertEquals(5, changes.size());
 
     assertThat(changes, hasItem(modificationData().withVersion("d64d9799c143").withVcsRootProperties(root.getProperties())));
--- a/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/UnrelatedResitoriesTest.java	Wed Mar 06 17:44:40 2013 +0400
+++ b/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/UnrelatedResitoriesTest.java	Wed Mar 06 21:26:28 2013 +0400
@@ -68,7 +68,7 @@
     String currentVersionOfOldRepo = syncRepository();
     repositoryBecamesUnrelated();
     String currentVersionOfNewRepo = syncRepository();
-    assertTrue(myVcs.collectChanges(myRoot, currentVersionOfOldRepo, currentVersionOfNewRepo, CheckoutRules.DEFAULT).isEmpty());
+    assertTrue(myVcs.getCollectChangesPolicy().collectChanges(myRoot, currentVersionOfOldRepo, currentVersionOfNewRepo, CheckoutRules.DEFAULT).isEmpty());
   }