changeset 879:c0c1a3f4b6f7

Extract OperationContext without details needed only for changes collecting
author Dmitry Neverov <dmitry.neverov@jetbrains.com>
date Sat, 04 Oct 2014 23:16:32 +0200
parents 593c37cb219d
children ea8b15b97960
files mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/MercurialProgress.java mercurial-server/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/CollectChangesContext.java mercurial-server/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/MercurialCollectChangesPolicy.java mercurial-server/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/MercurialModificationInfoBuilder.java mercurial-server/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/MercurialVcsSupport.java mercurial-server/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/ModificationDataFactory.java mercurial-server/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/OperationContext.java mercurial-server/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/ServerHgRepo.java
diffstat 8 files changed, 420 insertions(+), 343 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/MercurialProgress.java	Sat Oct 04 23:16:32 2014 +0200
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2000-2014 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package jetbrains.buildServer.buildTriggers.vcs.mercurial;
+
+import org.jetbrains.annotations.NotNull;
+
+public interface MercurialProgress {
+
+  void reportProgress(@NotNull String progressMessage);
+
+  void reportProgress(float percentage, @NotNull String stage);
+
+  static MercurialProgress NO_OP = new MercurialProgress() {
+    public void reportProgress(@NotNull String progressMessage) {
+    }
+    public void reportProgress(float percentage, @NotNull String stage) {
+    }
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial-server/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/CollectChangesContext.java	Sat Oct 04 23:16:32 2014 +0200
@@ -0,0 +1,313 @@
+/*
+ * Copyright 2000-2014 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package jetbrains.buildServer.buildTriggers.vcs.mercurial;
+
+import com.intellij.openapi.util.Pair;
+import gnu.trove.TLongObjectHashMap;
+import jetbrains.buildServer.buildTriggers.vcs.mercurial.command.HgVcsRoot;
+import jetbrains.buildServer.buildTriggers.vcs.mercurial.command.ProgressParser;
+import jetbrains.buildServer.util.Hash;
+import jetbrains.buildServer.util.graph.BFSVisitorAdapter;
+import jetbrains.buildServer.util.graph.DAG;
+import jetbrains.buildServer.vcs.ModificationData;
+import jetbrains.buildServer.vcs.RepositoryStateData;
+import jetbrains.buildServer.vcs.VcsException;
+import jetbrains.buildServer.vcs.VcsRoot;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.io.File;
+import java.util.*;
+
+import static java.util.Collections.singleton;
+import static jetbrains.buildServer.util.CollectionsUtil.setOf;
+
+public class CollectChangesContext extends OperationContext {
+
+  private final Set<String> myUninterestingRevisions;
+  private final RepositoryStateData myToState;
+  private final Map<VcsRootKey, DAG<String>> myDags = new HashMap<VcsRootKey, DAG<String>>();
+  private Set<File> mySyncedDirs = new HashSet<File>();
+  private Map<String, Set<SubrepoChangesInterval>> myProcessedSubrepoChanges = new HashMap<String, Set<SubrepoChangesInterval>>();//subrepo url -> processed changes intervals
+  private Map<VcsRootKey, Set<String>> myRevisionsPerRoot = new HashMap<VcsRootKey, Set<String>>();
+  private Map<File, ServerHgRepo> myRepos = new HashMap<File, ServerHgRepo>();
+  private boolean myIncludeFromRevisions = false;//by default don't include them, they should be included only for subrepos
+  private TLongObjectHashMap<String> myStringPool = new TLongObjectHashMap<String>();
+  private ProgressParser.ProgressConsumer myProgressConsumer;
+
+  public CollectChangesContext(@NotNull MercurialVcsSupport vcs,
+                               @NotNull RepoFactory repoFactory,
+                               @NotNull RepositoryStateData fromState,
+                               @NotNull RepositoryStateData toState) {
+    this(vcs, repoFactory, new HashSet<String>(fromState.getBranchRevisions().values()), toState);
+  }
+
+  public CollectChangesContext(@NotNull MercurialVcsSupport vcs,
+                               @NotNull RepoFactory repoFactory,
+                               @NotNull String fromVersion,
+                               @NotNull String toVersion) {
+    this(vcs, repoFactory, setOf(fromVersion), RepositoryStateData.createSingleVersionState(toVersion));
+  }
+
+  public CollectChangesContext(@NotNull MercurialVcsSupport vcs,
+                               @NotNull RepoFactory repoFactory,
+                               @NotNull Collection<String> fromVersions,
+                               @NotNull RepositoryStateData toState) {
+    super(vcs, repoFactory, MercurialProgress.NO_OP);
+    myToState = toState;
+    myUninterestingRevisions = new HashSet<String>(fromVersions);
+  }
+
+  public void syncRepository(@NotNull HgVcsRoot root, @Nullable ProgressParser.ProgressConsumer progressConsumer) throws VcsException {
+    File dir = myVcs.getWorkingDir(root);
+    if (mySyncedDirs.contains(dir))
+      return;
+    SyncSettings<Void> settings = new SyncSettings<Void>(VcsCallable.NO_OP);
+    settings.setProgressConsumer(progressConsumer);
+    myVcs.syncRepository(root, settings);
+    mySyncedDirs.add(dir);
+  }
+
+  public void markProcessedSubrepoChanges(@NotNull VcsRoot subrepo, @NotNull List<String> previousSubrepoRevisions, @NotNull String currentSubrepoRevision) {
+    String subrepoUrl = subrepo.getProperty(Constants.REPOSITORY_PROP);
+    Set<SubrepoChangesInterval> processedSubrepoChanges = myProcessedSubrepoChanges.get(subrepoUrl);
+    if (processedSubrepoChanges == null) {
+      processedSubrepoChanges = new HashSet<SubrepoChangesInterval>();
+      myProcessedSubrepoChanges.put(subrepoUrl, processedSubrepoChanges);
+    }
+    processedSubrepoChanges.add(new SubrepoChangesInterval(previousSubrepoRevisions, currentSubrepoRevision));
+  }
+
+  public boolean isProcessedSubrepoChanges(@NotNull VcsRoot subrepo, @NotNull List<String> previousSubrepoRevisions, @NotNull String currentSubrepoRevision) {
+    String subrepoUrl = subrepo.getProperty(Constants.REPOSITORY_PROP);
+    Set<SubrepoChangesInterval> processedSubrepoChanges = myProcessedSubrepoChanges.get(subrepoUrl);
+    if (processedSubrepoChanges == null)
+      return false;
+    return processedSubrepoChanges.contains(new SubrepoChangesInterval(previousSubrepoRevisions, currentSubrepoRevision));
+  }
+
+  @NotNull
+  public ServerHgRepo createRepo(@NotNull final HgVcsRoot root, @NotNull final File workingDir) throws VcsException {
+    ServerHgRepo repo = myRepos.get(workingDir);
+    if (repo != null) {
+      return repo;
+    }
+
+    repo = super.createRepo(root, workingDir);
+    repo.setOperationContext(this);
+    myRepos.put(workingDir, repo);
+    return repo;
+  }
+
+  public boolean isReportedModification(@NotNull ModificationData m) {
+    Set<String> revisions = getReportedRootRevisions(m);
+    return revisions.contains(m.getVersion());
+  }
+
+  public void markAsReported(@NotNull ModificationData m) {
+    Set<String> revisions = getReportedRootRevisions(m);
+    revisions.add(m.getVersion());
+  }
+
+  @NotNull
+  private Set<String> getReportedRootRevisions(@NotNull ModificationData m) {
+    VcsRootKey key = VcsRootKey.create(m.getVcsRoot());
+    Set<String> revisions = myRevisionsPerRoot.get(key);
+    if (revisions == null) {
+      revisions = new HashSet<String>();
+      myRevisionsPerRoot.put(key, revisions);
+    }
+    return revisions;
+  }
+
+
+  public boolean includeFromRevisions() {
+    return myIncludeFromRevisions;
+  }
+
+
+  public void setIncludeFromRevisions(boolean doInclude) {
+    myIncludeFromRevisions = doInclude;
+  }
+
+
+  public String getStringFromPool(@Nullable String s) {
+    if (s == null)
+      return null;
+    final long hash = Hash.calc(s);
+    String reused = myStringPool.get(hash);
+    if (reused != null) {
+      if (!reused.equals(s))  // hash collision
+        return s;
+      return reused;
+    }
+    //noinspection RedundantStringConstructorCall
+    reused = new String(s);
+    myStringPool.put(hash, reused);
+    return reused;
+  }
+
+
+  /**
+   * Collecting changes is per branch, but we should take revisions of other branches
+   * into account otherwise plugin can report redundant changes. Consider the following graph:
+   *
+   * default
+   * | topic
+   * |  |
+   * V  V
+   *103
+   * | 102
+   * | |
+   *101|
+   * |\|
+   * | 100
+   * | |
+   *99 |
+   *.....
+   * | |
+   * |/
+   * 1
+   *
+   * Let's say plugin collects changes between states {default:99, topic:100} and {default: 103, topic:102}.
+   * If we run hg log -r 'ancestors(103)-ancestors(99)' it will return changes [1..103]. But changes
+   * [1..100] were probably already reported and TeamCity will not persist them.
+   *
+   * This method detects such a cases and returns (99, 100) for 103.
+   */
+  @NotNull
+  public Collection<String> getFromRevisionsForBranch(@NotNull HgVcsRoot root,
+                                                      @NotNull String fromRevision,
+                                                      @NotNull String toRevision,
+                                                      @Nullable ProgressParser.ProgressConsumer progressConsumer) throws VcsException {
+    syncRepository(root, progressConsumer);
+    ServerHgRepo repo = createRepo(root, myVcs.getWorkingDir(root));
+
+    if (!repo.supportRevsets())
+      return singleton(fromRevision);
+
+    Set<String> fromRevisions = new HashSet<String>();
+    if (myToState.getBranchRevisions().size() > 1) {
+      VcsRootKey rootKey = VcsRootKey.create(root);
+      DAG<String> dag = myDags.get(rootKey);
+      if (dag == null) {
+        dag = repo.loadDag();
+        myDags.put(rootKey, dag);
+      }
+      if (dag.containsNode(toRevision)) {
+        FindIntervalVisitor visitor = new FindIntervalVisitor(dag, myUninterestingRevisions);
+        dag.breadthFirstSearch(toRevision, visitor);
+        fromRevisions.addAll(visitor.getEndpoints());
+      } else {
+        fromRevisions.add(fromRevision);
+      }
+    } else {
+      fromRevisions.add(fromRevision);
+    }
+
+    if (fromRevisions.isEmpty())
+      fromRevisions.add(toRevision);
+
+    return fromRevisions;
+  }
+
+  private final static class SubrepoChangesInterval extends Pair<List<String>, String> {
+    private SubrepoChangesInterval(@NotNull List<String> prevRevisions, @NotNull String currentRevision) {
+      super(prevRevisions, currentRevision);
+    }
+  }
+
+  private final static class VcsRootKey extends Pair<String, String> {
+    private VcsRootKey(@NotNull String repositoryUrl, @Nullable String subrepoPath) {
+      super(repositoryUrl, subrepoPath);
+    }
+
+    private static VcsRootKey create(@NotNull HgVcsRoot root) {
+      return new VcsRootKey(root.getProperty(Constants.REPOSITORY_PROP), root.getProperty("teamcity.internal.subrepo.path"));
+    }
+
+    private static VcsRootKey create(@NotNull VcsRoot root) {
+      return new VcsRootKey(root.getProperty(Constants.REPOSITORY_PROP), root.getProperty("teamcity.internal.subrepo.path"));
+    }
+  }
+
+
+
+  private final static class FindIntervalVisitor extends BFSVisitorAdapter<String> {
+
+    private final DAG<String> myDag;
+    private final Collection<String> myUninterestingRevisions;
+    private final Set<String> myVisitedUninterestingRevisions = new HashSet<String>();
+    private final Set<String> myEndpoints = new HashSet<String>();
+
+    private FindIntervalVisitor(@NotNull DAG<String> dag, @NotNull Collection<String> uninteresting) {
+      myDag = dag;
+      myUninterestingRevisions = uninteresting;
+    }
+
+    @Override
+    public boolean discover(@NotNull String revision) {
+      markUninteresting();
+      if (!isInteresting(revision)) {
+        addEndpoint(revision);
+        return false;
+      }
+      return true;
+    }
+
+    private void markUninteresting() {
+      if (!myVisitedUninterestingRevisions.isEmpty())
+        return;
+      for (String rev : myUninterestingRevisions) {
+        if (myDag.containsNode(rev))
+          myDag.breadthFirstSearch(rev, new MarkUninterestingRevisions());
+      }
+    }
+
+    private boolean isInteresting(@NotNull String revision) {
+      return !myVisitedUninterestingRevisions.contains(revision);
+    }
+
+    private void addEndpoint(@NotNull String revision) {
+      myEndpoints.add(revision);
+    }
+
+    @NotNull
+    public Set<String> getEndpoints() {
+      return myEndpoints;
+    }
+
+    private class MarkUninterestingRevisions extends BFSVisitorAdapter<String> {
+      @Override
+      public boolean discover(@NotNull String node) {
+        if (myVisitedUninterestingRevisions.contains(node))
+          return false;
+        myVisitedUninterestingRevisions.add(node);
+        return true;
+      }
+    }
+  }
+
+  @Nullable
+  public ProgressParser.ProgressConsumer getProgressConsumer() {
+    return myProgressConsumer;
+  }
+
+  public void setProgressConsumer(ProgressParser.ProgressConsumer progressConsumer) {
+    myProgressConsumer = progressConsumer;
+  }
+}
--- a/mercurial-server/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/MercurialCollectChangesPolicy.java	Fri Oct 03 22:48:45 2014 +0200
+++ b/mercurial-server/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/MercurialCollectChangesPolicy.java	Sat Oct 04 23:16:32 2014 +0200
@@ -96,7 +96,7 @@
                                                @NotNull CheckoutRules rules) throws VcsException {
     List<ModificationData> changes = new ArrayList<ModificationData>();
     HgVcsRoot hgRoot = myHgVcsRootFactory.createHgRoot(root);
-    OperationContext ctx = new OperationContext(myVcs, myRepoFactory, fromState, toState);
+    CollectChangesContext ctx = new CollectChangesContext(myVcs, myRepoFactory, fromState, toState);
     ctx.setProgressConsumer(createProgressConsumer());
     for (Map.Entry<String, String> entry : toState.getBranchRevisions().entrySet()) {
       String branch = entry.getKey();
@@ -149,7 +149,7 @@
                                                @NotNull CheckoutRules checkoutRules) throws VcsException {
     if (currentVersion == null)
       return emptyList();
-    OperationContext ctx = new OperationContext(myVcs, myRepoFactory, fromVersion, currentVersion);
+    CollectChangesContext ctx = new CollectChangesContext(myVcs, myRepoFactory, fromVersion, currentVersion);
     ctx.setProgressConsumer(createProgressConsumer());
     List<ModificationData> changes = collectChanges(ctx, root, asList(fromVersion), currentVersion, checkoutRules);
     changes.addAll(getSubrepoChanges(ctx, root, changes));
@@ -184,7 +184,7 @@
 
 
   @NotNull
-  private List<ModificationData> collectChanges(@NotNull OperationContext ctx,
+  private List<ModificationData> collectChanges(@NotNull CollectChangesContext ctx,
                                                 @NotNull VcsRoot root,
                                                 @NotNull Collection<String> fromVersion,
                                                 @Nullable String currentVersion,
@@ -205,7 +205,7 @@
 
 
   @NotNull
-  private List<ChangeSet> getChangesets(@NotNull OperationContext ctx,
+  private List<ChangeSet> getChangesets(@NotNull CollectChangesContext ctx,
                                         @NotNull final HgVcsRoot root,
                                         @NotNull final Collection<String> fromVersions,
                                         @Nullable final String toVersion) throws VcsException {
@@ -239,7 +239,7 @@
     }
   }
 
-  private ModificationData createModificationData(@NotNull final OperationContext ctx,
+  private ModificationData createModificationData(@NotNull final CollectChangesContext ctx,
                                                   @NotNull final ChangeSet cset,
                                                   @NotNull final VcsRoot root,
                                                   @NotNull final HgVcsRoot hgRoot,
@@ -250,7 +250,7 @@
   }
 
   @NotNull
-  private List<ModificationData> getSubrepoChanges(@NotNull OperationContext ctx, @NotNull VcsRoot root, @NotNull List<ModificationData> changes) throws VcsException {
+  private List<ModificationData> getSubrepoChanges(@NotNull CollectChangesContext ctx, @NotNull VcsRoot root, @NotNull List<ModificationData> changes) throws VcsException {
     if (changes.isEmpty())
       return emptyList();
 
@@ -312,7 +312,7 @@
   }
 
 
-  private List<HgSubrepoConfigChange> getSubrepoConfigChanges(@NotNull OperationContext ctx, @NotNull List<ModificationData> mainRootChanges) throws VcsException {
+  private List<HgSubrepoConfigChange> getSubrepoConfigChanges(@NotNull CollectChangesContext ctx, @NotNull List<ModificationData> mainRootChanges) throws VcsException {
     List<HgSubrepoConfigChange> subrepoConfigChanges = new ArrayList<HgSubrepoConfigChange>();
     for (ModificationData m : mainRootChanges) {
       subrepoConfigChanges.addAll(getSubrepoConfigChanges(ctx, m));
@@ -322,7 +322,7 @@
 
 
   @NotNull
-  private List<HgSubrepoConfigChange> getSubrepoConfigChanges(@NotNull OperationContext ctx, @NotNull ModificationData m) throws VcsException {
+  private List<HgSubrepoConfigChange> getSubrepoConfigChanges(@NotNull CollectChangesContext ctx, @NotNull ModificationData m) throws VcsException {
     List<HgSubrepoConfigChange> configChanges = new ArrayList<HgSubrepoConfigChange>();
 
     HgVcsRoot mainRoot = myHgVcsRootFactory.createHgRoot(m.getVcsRoot());
@@ -352,7 +352,7 @@
 
 
   @NotNull
-  private Map<String, String> getAttributes(@NotNull OperationContext ctx,
+  private Map<String, String> getAttributes(@NotNull CollectChangesContext ctx,
                                             @NotNull VcsRoot mainRoot,
                                             @NotNull HgVcsRoot mainHgRoot,
                                             @NotNull ModificationData m) throws VcsException {
--- a/mercurial-server/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/MercurialModificationInfoBuilder.java	Fri Oct 03 22:48:45 2014 +0200
+++ b/mercurial-server/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/MercurialModificationInfoBuilder.java	Sat Oct 04 23:16:32 2014 +0200
@@ -48,7 +48,7 @@
                                @NotNull final ChangesConsumer consumer) throws VcsException {
     final HgVcsRoot hgRoot = myHgVcsRootFactory.createHgRoot(root);
 
-    final OperationContext ctx = new OperationContext(myVcs,
+    final CollectChangesContext ctx = new CollectChangesContext(myVcs,
             myRepoFactory,
             Collections.<String>emptyList(),
             RepositoryStateData.createVersionState("", Collections.<String, String>emptyMap()));
--- a/mercurial-server/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/MercurialVcsSupport.java	Fri Oct 03 22:48:45 2014 +0200
+++ b/mercurial-server/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/MercurialVcsSupport.java	Sat Oct 04 23:16:32 2014 +0200
@@ -779,7 +779,7 @@
   }
 
   @NotNull
-  public ServerHgRepo createRepo(@NotNull OperationContext ctx, @NotNull HgVcsRoot root) throws VcsException {
+  public ServerHgRepo createRepo(@NotNull CollectChangesContext ctx, @NotNull HgVcsRoot root) throws VcsException {
     return ctx.createRepo(root, getWorkingDir(root));
   }
 
--- a/mercurial-server/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/ModificationDataFactory.java	Fri Oct 03 22:48:45 2014 +0200
+++ b/mercurial-server/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/ModificationDataFactory.java	Sat Oct 04 23:16:32 2014 +0200
@@ -36,7 +36,7 @@
  */
 public class ModificationDataFactory {
   @NotNull
-  public static ModificationData createModificationData(@NotNull final OperationContext ctx,
+  public static ModificationData createModificationData(@NotNull final CollectChangesContext ctx,
                                                         @NotNull final ChangeSet cset,
                                                         @NotNull final VcsRoot root,
                                                         @NotNull final CheckoutRules checkoutRules) throws VcsException {
@@ -67,7 +67,7 @@
 
 
   @NotNull
-  public static List<VcsChange> toVcsChanges(@NotNull OperationContext ctx,
+  public static List<VcsChange> toVcsChanges(@NotNull CollectChangesContext ctx,
                                              @NotNull List<FileStatus> modifiedFiles,
                                              @NotNull String prevVer,
                                              @NotNull String curVer,
--- a/mercurial-server/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/OperationContext.java	Fri Oct 03 22:48:45 2014 +0200
+++ b/mercurial-server/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/OperationContext.java	Sat Oct 04 23:16:32 2014 +0200
@@ -1,328 +1,59 @@
-/*
- * Copyright 2000-2014 JetBrains s.r.o.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package jetbrains.buildServer.buildTriggers.vcs.mercurial;
-
-import com.intellij.openapi.util.Pair;
-import gnu.trove.TLongObjectHashMap;
-import jetbrains.buildServer.buildTriggers.vcs.mercurial.command.HgVcsRoot;
-import jetbrains.buildServer.buildTriggers.vcs.mercurial.command.ProgressParser;
-import jetbrains.buildServer.util.Hash;
-import jetbrains.buildServer.util.graph.BFSVisitorAdapter;
-import jetbrains.buildServer.util.graph.DAG;
-import jetbrains.buildServer.vcs.ModificationData;
-import jetbrains.buildServer.vcs.RepositoryStateData;
-import jetbrains.buildServer.vcs.VcsException;
-import jetbrains.buildServer.vcs.VcsRoot;
-import org.jetbrains.annotations.NotNull;
-import org.jetbrains.annotations.Nullable;
-
-import java.io.File;
-import java.util.*;
-
-import static java.util.Collections.singleton;
-import static jetbrains.buildServer.util.CollectionsUtil.setOf;
-
-public class OperationContext {
-
-  private final MercurialVcsSupport myVcs;
-  private final RepoFactory myRepoFactory;
-  private final Set<String> myUninterestingRevisions;
-  private final RepositoryStateData myToState;
-  private final Map<VcsRootKey, DAG<String>> myDags = new HashMap<VcsRootKey, DAG<String>>();
-  private Set<File> mySyncedDirs = new HashSet<File>();
-  private Map<String, HgVersion> myHgVersions = new HashMap<String, HgVersion>();
-  private Map<String, Set<SubrepoChangesInterval>> myProcessedSubrepoChanges = new HashMap<String, Set<SubrepoChangesInterval>>();//subrepo url -> processed changes intervals
-  private Map<VcsRootKey, Set<String>> myRevisionsPerRoot = new HashMap<VcsRootKey, Set<String>>();
-  private Map<File, ServerHgRepo> myRepos = new HashMap<File, ServerHgRepo>();
-  private boolean myIncludeFromRevisions = false;//by default don't include them, they should be included only for subrepos
-  private TLongObjectHashMap<String> myStringPool = new TLongObjectHashMap<String>();
-  private ProgressParser.ProgressConsumer myProgressConsumer;
-
-  public OperationContext(@NotNull MercurialVcsSupport vcs,
-                          @NotNull RepoFactory repoFactory,
-                          @NotNull RepositoryStateData fromState,
-                          @NotNull RepositoryStateData toState) {
-    this(vcs, repoFactory, new HashSet<String>(fromState.getBranchRevisions().values()), toState);
-  }
-
-  public OperationContext(@NotNull MercurialVcsSupport vcs,
-                          @NotNull RepoFactory repoFactory,
-                          @NotNull String fromVersion,
-                          @NotNull String toVersion) {
-    this(vcs, repoFactory, setOf(fromVersion), RepositoryStateData.createSingleVersionState(toVersion));
-  }
-
-  public OperationContext(@NotNull MercurialVcsSupport vcs,
-                          @NotNull RepoFactory repoFactory,
-                          @NotNull Collection<String> fromVersions,
-                          @NotNull RepositoryStateData toState) {
-    myVcs = vcs;
-    myRepoFactory = repoFactory;
-    myToState = toState;
-    myUninterestingRevisions = new HashSet<String>(fromVersions);
-  }
-
-  public void syncRepository(@NotNull HgVcsRoot root, @Nullable ProgressParser.ProgressConsumer progressConsumer) throws VcsException {
-    File dir = myVcs.getWorkingDir(root);
-    if (mySyncedDirs.contains(dir))
-      return;
-    SyncSettings<Void> settings = new SyncSettings<Void>(VcsCallable.NO_OP);
-    settings.setProgressConsumer(progressConsumer);
-    myVcs.syncRepository(root, settings);
-    mySyncedDirs.add(dir);
-  }
-
-  @NotNull
-  public HgVersion getHgVersion(@NotNull ServerHgRepo repo) throws VcsException {
-    String hgPath = repo.getHgPath();
-    HgVersion cachedVersion = myHgVersions.get(hgPath);
-    if (cachedVersion != null)
-      return cachedVersion;
-    HgVersion version = repo.version().call();
-    myHgVersions.put(hgPath, version);
-    return version;
-  }
-
-  public void markProcessedSubrepoChanges(@NotNull VcsRoot subrepo, @NotNull List<String> previousSubrepoRevisions, @NotNull String currentSubrepoRevision) {
-    String subrepoUrl = subrepo.getProperty(Constants.REPOSITORY_PROP);
-    Set<SubrepoChangesInterval> processedSubrepoChanges = myProcessedSubrepoChanges.get(subrepoUrl);
-    if (processedSubrepoChanges == null) {
-      processedSubrepoChanges = new HashSet<SubrepoChangesInterval>();
-      myProcessedSubrepoChanges.put(subrepoUrl, processedSubrepoChanges);
-    }
-    processedSubrepoChanges.add(new SubrepoChangesInterval(previousSubrepoRevisions, currentSubrepoRevision));
-  }
-
-  public boolean isProcessedSubrepoChanges(@NotNull VcsRoot subrepo, @NotNull List<String> previousSubrepoRevisions, @NotNull String currentSubrepoRevision) {
-    String subrepoUrl = subrepo.getProperty(Constants.REPOSITORY_PROP);
-    Set<SubrepoChangesInterval> processedSubrepoChanges = myProcessedSubrepoChanges.get(subrepoUrl);
-    if (processedSubrepoChanges == null)
-      return false;
-    return processedSubrepoChanges.contains(new SubrepoChangesInterval(previousSubrepoRevisions, currentSubrepoRevision));
-  }
-
-  @NotNull
-  public ServerHgRepo createRepo(@NotNull final HgVcsRoot root, @NotNull final File workingDir) throws VcsException {
-    ServerHgRepo repo = myRepos.get(workingDir);
-    if (repo != null) {
-      return repo;
-    }
-
-    repo = myRepoFactory.createRepo(root, workingDir);
-    repo.setOperationContext(this);
-    myRepos.put(workingDir, repo);
-    return repo;
-  }
-
-  public boolean isReportedModification(@NotNull ModificationData m) {
-    Set<String> revisions = getReportedRootRevisions(m);
-    return revisions.contains(m.getVersion());
-  }
-
-  public void markAsReported(@NotNull ModificationData m) {
-    Set<String> revisions = getReportedRootRevisions(m);
-    revisions.add(m.getVersion());
-  }
-
-  @NotNull
-  private Set<String> getReportedRootRevisions(@NotNull ModificationData m) {
-    VcsRootKey key = VcsRootKey.create(m.getVcsRoot());
-    Set<String> revisions = myRevisionsPerRoot.get(key);
-    if (revisions == null) {
-      revisions = new HashSet<String>();
-      myRevisionsPerRoot.put(key, revisions);
-    }
-    return revisions;
-  }
-
-
-  public boolean includeFromRevisions() {
-    return myIncludeFromRevisions;
-  }
-
-
-  public void setIncludeFromRevisions(boolean doInclude) {
-    myIncludeFromRevisions = doInclude;
-  }
-
-
-  public String getStringFromPool(@Nullable String s) {
-    if (s == null)
-      return null;
-    final long hash = Hash.calc(s);
-    String reused = myStringPool.get(hash);
-    if (reused != null) {
-      if (!reused.equals(s))  // hash collision
-        return s;
-      return reused;
-    }
-    //noinspection RedundantStringConstructorCall
-    reused = new String(s);
-    myStringPool.put(hash, reused);
-    return reused;
-  }
-
-
-  /**
-   * Collecting changes is per branch, but we should take revisions of other branches
-   * into account otherwise plugin can report redundant changes. Consider the following graph:
-   *
-   * default
-   * | topic
-   * |  |
-   * V  V
-   *103
-   * | 102
-   * | |
-   *101|
-   * |\|
-   * | 100
-   * | |
-   *99 |
-   *.....
-   * | |
-   * |/
-   * 1
-   *
-   * Let's say plugin collects changes between states {default:99, topic:100} and {default: 103, topic:102}.
-   * If we run hg log -r 'ancestors(103)-ancestors(99)' it will return changes [1..103]. But changes
-   * [1..100] were probably already reported and TeamCity will not persist them.
-   *
-   * This method detects such a cases and returns (99, 100) for 103.
-   */
-  @NotNull
-  public Collection<String> getFromRevisionsForBranch(@NotNull HgVcsRoot root,
-                                                      @NotNull String fromRevision,
-                                                      @NotNull String toRevision,
-                                                      @Nullable ProgressParser.ProgressConsumer progressConsumer) throws VcsException {
-    syncRepository(root, progressConsumer);
-    ServerHgRepo repo = createRepo(root, myVcs.getWorkingDir(root));
-
-    if (!repo.supportRevsets())
-      return singleton(fromRevision);
-
-    Set<String> fromRevisions = new HashSet<String>();
-    if (myToState.getBranchRevisions().size() > 1) {
-      VcsRootKey rootKey = VcsRootKey.create(root);
-      DAG<String> dag = myDags.get(rootKey);
-      if (dag == null) {
-        dag = repo.loadDag();
-        myDags.put(rootKey, dag);
-      }
-      if (dag.containsNode(toRevision)) {
-        FindIntervalVisitor visitor = new FindIntervalVisitor(dag, myUninterestingRevisions);
-        dag.breadthFirstSearch(toRevision, visitor);
-        fromRevisions.addAll(visitor.getEndpoints());
-      } else {
-        fromRevisions.add(fromRevision);
-      }
-    } else {
-      fromRevisions.add(fromRevision);
-    }
-
-    if (fromRevisions.isEmpty())
-      fromRevisions.add(toRevision);
-
-    return fromRevisions;
-  }
-
-  private final static class SubrepoChangesInterval extends Pair<List<String>, String> {
-    private SubrepoChangesInterval(@NotNull List<String> prevRevisions, @NotNull String currentRevision) {
-      super(prevRevisions, currentRevision);
-    }
-  }
-
-  private final static class VcsRootKey extends Pair<String, String> {
-    private VcsRootKey(@NotNull String repositoryUrl, @Nullable String subrepoPath) {
-      super(repositoryUrl, subrepoPath);
-    }
-
-    private static VcsRootKey create(@NotNull HgVcsRoot root) {
-      return new VcsRootKey(root.getProperty(Constants.REPOSITORY_PROP), root.getProperty("teamcity.internal.subrepo.path"));
-    }
-
-    private static VcsRootKey create(@NotNull VcsRoot root) {
-      return new VcsRootKey(root.getProperty(Constants.REPOSITORY_PROP), root.getProperty("teamcity.internal.subrepo.path"));
-    }
-  }
-
-
-
-  private final static class FindIntervalVisitor extends BFSVisitorAdapter<String> {
-
-    private final DAG<String> myDag;
-    private final Collection<String> myUninterestingRevisions;
-    private final Set<String> myVisitedUninterestingRevisions = new HashSet<String>();
-    private final Set<String> myEndpoints = new HashSet<String>();
-
-    private FindIntervalVisitor(@NotNull DAG<String> dag, @NotNull Collection<String> uninteresting) {
-      myDag = dag;
-      myUninterestingRevisions = uninteresting;
-    }
-
-    @Override
-    public boolean discover(@NotNull String revision) {
-      markUninteresting();
-      if (!isInteresting(revision)) {
-        addEndpoint(revision);
-        return false;
-      }
-      return true;
-    }
-
-    private void markUninteresting() {
-      if (!myVisitedUninterestingRevisions.isEmpty())
-        return;
-      for (String rev : myUninterestingRevisions) {
-        if (myDag.containsNode(rev))
-          myDag.breadthFirstSearch(rev, new MarkUninterestingRevisions());
-      }
-    }
-
-    private boolean isInteresting(@NotNull String revision) {
-      return !myVisitedUninterestingRevisions.contains(revision);
-    }
-
-    private void addEndpoint(@NotNull String revision) {
-      myEndpoints.add(revision);
-    }
-
-    @NotNull
-    public Set<String> getEndpoints() {
-      return myEndpoints;
-    }
-
-    private class MarkUninterestingRevisions extends BFSVisitorAdapter<String> {
-      @Override
-      public boolean discover(@NotNull String node) {
-        if (myVisitedUninterestingRevisions.contains(node))
-          return false;
-        myVisitedUninterestingRevisions.add(node);
-        return true;
-      }
-    }
-  }
-
-  @Nullable
-  public ProgressParser.ProgressConsumer getProgressConsumer() {
-    return myProgressConsumer;
-  }
-
-  public void setProgressConsumer(ProgressParser.ProgressConsumer progressConsumer) {
-    myProgressConsumer = progressConsumer;
-  }
-}
+/*
+ * Copyright 2000-2014 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package jetbrains.buildServer.buildTriggers.vcs.mercurial;
+
+import jetbrains.buildServer.buildTriggers.vcs.mercurial.command.HgVcsRoot;
+import jetbrains.buildServer.vcs.VcsException;
+import org.jetbrains.annotations.NotNull;
+
+import java.io.File;
+import java.util.HashMap;
+import java.util.Map;
+
+public class OperationContext {
+
+  protected final MercurialVcsSupport myVcs;
+  protected final RepoFactory myRepoFactory;
+  protected final MercurialProgress myProgress;
+  private final Map<String, HgVersion> myHgVersions = new HashMap<String, HgVersion>();
+
+  public OperationContext(@NotNull MercurialVcsSupport vcs,
+                          @NotNull RepoFactory repoFactory,
+                          @NotNull MercurialProgress progress) {
+    myVcs = vcs;
+    myRepoFactory = repoFactory;
+    myProgress = progress;
+  }
+
+
+  @NotNull
+  public HgVersion getHgVersion(@NotNull ServerHgRepo repo) throws VcsException {
+    String hgPath = repo.getHgPath();
+    HgVersion cachedVersion = myHgVersions.get(hgPath);
+    if (cachedVersion != null)
+      return cachedVersion;
+    HgVersion version = repo.version().call();
+    myHgVersions.put(hgPath, version);
+    return version;
+  }
+
+
+  @NotNull
+  public ServerHgRepo createRepo(@NotNull HgVcsRoot root, @NotNull File workingDir) throws VcsException {
+    return myRepoFactory.createRepo(root, workingDir);
+  }
+}
--- a/mercurial-server/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/ServerHgRepo.java	Fri Oct 03 22:48:45 2014 +0200
+++ b/mercurial-server/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/ServerHgRepo.java	Sat Oct 04 23:16:32 2014 +0200
@@ -43,7 +43,7 @@
   protected final MercurialClasspathTemplate myLogNoFilesTemplate = new MercurialClasspathTemplate("/buildServerResources/log.no.files.template", "hg.short.log.template");
   protected final MercurialClasspathTemplate myDagTemplate = new MercurialClasspathTemplate("/buildServerResources/dag.template", "hg.dag.template");
   protected final MercurialClasspathTemplate myFastLogTemplate = new MercurialClasspathTemplate("/buildServerResources/fastlog.template", "hg.fastlog.template");
-  private OperationContext myContext;
+  private CollectChangesContext myContext;
 
   public ServerHgRepo(@NotNull CommandSettingsFactory commandSettingsFactory,
                       @NotNull ServerPluginConfig config,
@@ -55,7 +55,7 @@
     myConfig = config;
   }
 
-  public void setOperationContext(@NotNull OperationContext context) {
+  public void setOperationContext(@NotNull CollectChangesContext context) {
     myContext = context;
   }