view mercurial-server/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/OperationContext.java @ 564:d012388935fb Faradi-7.1.x

TW-26379 optimize changes collecting Take revisions of all branches into account
author Dmitry Neverov <dmitry.neverov@jetbrains.com>
date Wed, 06 Mar 2013 15:49:37 +0400
parents
children 844fc8f99c29
line wrap: on
line source
package jetbrains.buildServer.buildTriggers.vcs.mercurial;

import com.intellij.openapi.util.Pair;
import jetbrains.buildServer.buildTriggers.vcs.mercurial.command.AuthSettings;
import jetbrains.buildServer.buildTriggers.vcs.mercurial.command.HgVcsRoot;
import jetbrains.buildServer.util.graph.*;
import jetbrains.buildServer.vcs.*;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.io.File;
import java.util.*;

import static java.util.Collections.singleton;

public class OperationContext {

  private final MercurialVcsSupport myVcs;
  private final RepoFactory myRepoFactory;
  private final HgPathProvider myHgPathProvider;
  private final RepositoryState myFromState;
  private final RepositoryState myToState;
  private final Map<VcsRootKey, DAG<String>> myDags = new HashMap<VcsRootKey, DAG<String>>();
  private Set<File> mySyncedDirs = new HashSet<File>();
  private Map<File, ServerHgRepo> myRepos = new HashMap<File, ServerHgRepo>();

  public OperationContext(@NotNull MercurialVcsSupport vcs,
                          @NotNull RepoFactory repoFactory,
                          @NotNull HgPathProvider hgPathProvider,
                          @NotNull RepositoryState fromState,
                          @NotNull RepositoryState toState) {
    myVcs = vcs;
    myRepoFactory = repoFactory;
    myHgPathProvider = hgPathProvider;
    myFromState = fromState;
    myToState = toState;
  }

  public void syncRepository(@NotNull final HgVcsRoot root) throws VcsException {
    File dir = myVcs.getWorkingDir(root);
    if (mySyncedDirs.contains(dir))
      return;
    myVcs.syncRepository(root);
    mySyncedDirs.add(dir);
  }

  @NotNull
  public ServerHgRepo createRepo(@NotNull File workingDir, @NotNull String hgPath, @NotNull AuthSettings authSettings) throws VcsException {
    ServerHgRepo repo = myRepos.get(workingDir);
    if (repo != null)
      return repo;
    repo = myRepoFactory.create(workingDir, hgPath, authSettings);
    myRepos.put(workingDir, repo);
    return repo;
  }

  /**
   * 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) throws VcsException {
    syncRepository(root);
    ServerHgRepo repo = createRepo(myVcs.getWorkingDir(root), myHgPathProvider.getHgPath(root), root.getAuthSettings());

    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);
      }
      FindIntervalVisitor visitor = new FindIntervalVisitor(dag, myFromState.getBranchRevisions().values());
      dag.breadthFirstSearch(toRevision, visitor);
      fromRevisions.addAll(visitor.getEndpoints());
    } else {
      fromRevisions.add(fromRevision);
    }

    if (fromRevisions.isEmpty())
      fromRevisions.add(toRevision);

    return fromRevisions;
  }


  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 VcsRoot root) {
      return new VcsRootKey(root.getProperty(Constants.REPOSITORY_PROP), null);
    }
  }


  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) {
        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;
      }
    }
  }
}