view mercurial-agent/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/MercurialIncludeRuleUpdater.java @ 429:04eab204ba39

Remove HgVcsRoot's dependency on HgPathProvider
author Dmitry Neverov <dmitry.neverov@jetbrains.com>
date Fri, 11 May 2012 16:37:00 +0400
parents c91c4f1ebd53
children 2af623e989f6
line wrap: on
line source
package jetbrains.buildServer.buildTriggers.vcs.mercurial;

import jetbrains.buildServer.agent.AgentRunningBuild;
import jetbrains.buildServer.agent.BuildProgressLogger;
import jetbrains.buildServer.agent.vcs.IncludeRuleUpdater;
import jetbrains.buildServer.buildTriggers.vcs.mercurial.command.AuthSettings;
import jetbrains.buildServer.buildTriggers.vcs.mercurial.command.HgVcsRoot;
import jetbrains.buildServer.buildTriggers.vcs.mercurial.command.exception.UnrelatedRepositoryException;
import jetbrains.buildServer.vcs.IncludeRule;
import jetbrains.buildServer.vcs.VcsException;
import jetbrains.buildServer.vcs.VcsRoot;
import org.jetbrains.annotations.NotNull;

import java.io.File;
import java.io.IOException;
import java.util.Map;

import static com.intellij.openapi.util.io.FileUtil.delete;

/**
 * @author dmitry.neverov
 */
public class MercurialIncludeRuleUpdater implements IncludeRuleUpdater {

  private final AgentPluginConfig myConfig;
  private final MirrorManager myMirrorManager;
  private final HgVcsRoot myRoot;
  private final AuthSettings myAuthSettings;
  private final String myHgPath;
  private final String myToVersion;
  private final BuildProgressLogger myLogger;
  private final boolean myUseLocalMirrors;
  private int myPullTimeout;

  public MercurialIncludeRuleUpdater(@NotNull final AgentPluginConfig pluginConfig,
                                     @NotNull final MirrorManager mirrorManager,
                                     @NotNull final HgPathProvider hgPathProvider,
                                     @NotNull final VcsRoot root,
                                     @NotNull final String toVersion,
                                     @NotNull final AgentRunningBuild build) {
    myConfig = pluginConfig;
    myMirrorManager = mirrorManager;
    myRoot = new HgVcsRoot(root);
    myAuthSettings = myRoot.getAuthSettings();
    myHgPath = hgPathProvider.getHgPath(myRoot);
    myToVersion = toVersion;
    myLogger = build.getBuildLogger();
    myUseLocalMirrors = myConfig.isUseLocalMirrors(build);
    myPullTimeout = myConfig.getPullTimeout(build);
  }


  public void process(@NotNull IncludeRule rule, @NotNull File workingDir) throws VcsException {
    try {
      checkRuleIsValid(rule);
      if (myUseLocalMirrors)
        updateLocalMirror(myRoot.getRepository(), myToVersion);
      updateRepository(workingDir);
      updateWorkingDir(workingDir, myToVersion, myRoot.getRepository());
    } catch (Exception e) {
      throwVcsException(e);
    }
  }


  public void dispose() throws VcsException {
  }


  private void updateLocalMirror(@NotNull String repositoryUrl, @NotNull String revision) throws VcsException, IOException {
    File mirrorDir = myMirrorManager.getMirrorDir(repositoryUrl);
    HgRepo mirrorRepo = new HgRepo(mirrorDir, myHgPath, myAuthSettings);
    if (!mirrorRepo.isValidRepository()) {
      delete(mirrorDir);
      myLogger.message("Clone repository " + myAuthSettings.getRepositoryUrlWithHiddenPassword(repositoryUrl) + " into local mirror " + mirrorRepo.path());
      mirrorRepo.doClone().fromRepository(repositoryUrl)
              .setUpdateWorkingDir(false)
              .setUsePullProtocol(false)
              .useUncompressedTransfer(myRoot.isUncompressedTransfer())
              .call();
      myLogger.message("Clone successfully finished");
    } else {
      myLogger.message("Update local mirror of " + myAuthSettings.getRepositoryUrlWithHiddenPassword(repositoryUrl) + " at " + mirrorDir);
      if (mirrorRepo.containsRevision(revision)) {
        myLogger.message("Local mirror is already up-to-date");
      } else {
        myLogger.message("Start pulling changes from " + myAuthSettings.getRepositoryUrlWithHiddenPassword(repositoryUrl));
        mirrorRepo.pull().fromRepository(repositoryUrl)
                .withTimeout(myPullTimeout)
                .call();
        myLogger.message("Local mirror changes successfully pulled");
      }
    }
  }


  private void updateRepository(@NotNull File workingDir) throws VcsException, IOException {
    String repositoryUrl = getDefaultPullUrl(myRoot, myUseLocalMirrors);
    HgRepo repo = new HgRepo(workingDir, myHgPath, myAuthSettings);
    myLogger.message("Update repository " + workingDir.getAbsolutePath());
    if (repo.isEmpty()) {//can do clone only in empty dir
      myLogger.message("Start cloning from " + (myUseLocalMirrors ? "local mirror " : "") + myAuthSettings.getRepositoryUrlWithHiddenPassword(repositoryUrl));
      repo.doClone().fromRepository(repositoryUrl)
              .setUsePullProtocol(false)
              .setUpdateWorkingDir(false)
              .useUncompressedTransfer(!myUseLocalMirrors && myRoot.isUncompressedTransfer())
              .call();
      repo.setDefaultPath(myRoot.getRepository());
      myLogger.message("Repository successfully cloned");
    } else {
      if (!repo.isValidRepository())
        repo.init().call();
      repo.setDefaultPath(myRoot.getRepository());
      if (repo.containsRevision(myToVersion)) {
        myLogger.message("Repository already contains revision " + myToVersion);
      } else {
        myLogger.message("Start pulling changes from " + (myUseLocalMirrors ? "local mirror " : "") + myAuthSettings.getRepositoryUrlWithHiddenPassword(repositoryUrl));
        try {
          repo.pull().fromRepository(repositoryUrl)
                  .withTimeout(myPullTimeout)
                  .call();
        } catch (UnrelatedRepositoryException e) {
          throw new UnrelatedRepositoryException(myAuthSettings.getRepositoryUrlWithHiddenPassword(repositoryUrl), workingDir);
        }
        myLogger.message("Changes successfully pulled");
      }
    }
  }


  private void updateWorkingDir(@NotNull File workingDir, @NotNull String toVersion, @NotNull String repositoryUrl) throws VcsException, IOException {
    HgRepo repo = new HgRepo(workingDir, myHgPath, myAuthSettings);
    updateSubrepositories(repo, toVersion, repositoryUrl);
    doUpdateWorkingDir(repo, toVersion);
  }

  private void updateSubrepositories(@NotNull HgRepo repo, @NotNull String toVersion, @NotNull String parentRepositoryUrl) throws VcsException, IOException {
    if (!repo.hasSubreposAtRevision(toVersion))
      return;
    myLogger.message("Process subrepos of " + parentRepositoryUrl);
    String workingDirRevision = repo.getWorkingDirRevision();
    Map<String, SubRepo> workingDirSubrepos = repo.getSubrepositories(workingDirRevision);
    Map<String, SubRepo> subrepos = repo.getSubrepositories(toVersion);
    for (Map.Entry<String, SubRepo> entry : subrepos.entrySet()) {
      String path = entry.getKey();
      myLogger.message("Process subrepo " + path);
      SubRepo subrepo = entry.getValue();
      SubRepo workingDirSubrepo = workingDirSubrepos.get(path);
      if (workingDirSubrepo != null && subrepo.hasDifferentUrlThan(workingDirSubrepo)) {
        myLogger.message("The url of subrepo was changed between revisions " + workingDirRevision + " and " + toVersion + " , delete the subrepo");
        delete(subrepo.dir());
      }
      HgRepo subrepository = new HgRepo(subrepo.dir(), myHgPath, myAuthSettings);
      if (myUseLocalMirrors && subrepo.vcsType() == SubRepo.VcsType.hg) {
        if (subrepo.isRelative()) {
          myLogger.message("Subrepo url " + subrepo.url() + " is relative, subrepo will be updated during parent repository update");
        } else {
          if (!subrepository.isValidRepository() || !subrepository.containsRevision(subrepo.revision())) {
            updateLocalMirror(subrepo.url(), subrepo.revision());
            File mirrorDir = myMirrorManager.getMirrorDir(subrepo.url());
            if (subrepository.isValidRepository()) {
              myLogger.message("Pull from local mirror");
              subrepository.pull().fromRepository(mirrorDir)
                      .withTimeout(myPullTimeout)
                      .call();
              myLogger.message("done");
            } else {
              myLogger.message("Clone subrepo from local mirror");
              subrepository.doClone().fromRepository(mirrorDir)
                      .setUpdateWorkingDir(false)
                      .setUsePullProtocol(false)
                      .call();
              subrepository.setDefaultPath(subrepo.url());
              myLogger.message("done");
            }
          }
        }
      } else {
        myLogger.message("Local mirrors aren't used, subrepo will be updated during the parent repo update");
      }
      updateSubrepositories(subrepository, subrepo.revision(), subrepo.url());
    }
  }


  private void doUpdateWorkingDir(@NotNull HgRepo repo, @NotNull String revision) throws VcsException {
    myLogger.message("Updating working dir " + repo.path() + " to revision " + revision);
    repo.update().toRevision(revision).call();
    myLogger.message("Working dir updated");
  }


  private String getDefaultPullUrl(HgVcsRoot root, boolean useLocalMirror) throws IOException {
    if (useLocalMirror) {
      File mirrorDir = myMirrorManager.getMirrorDir(root.getRepository());
      return mirrorDir.getCanonicalPath();
    } else {
      return root.getRepository();
    }
  }


  private void checkRuleIsValid(IncludeRule includeRule) throws VcsException {
    if (includeRule.getTo() != null && includeRule.getTo().length() > 0) {
      if (!".".equals(includeRule.getFrom()) && includeRule.getFrom().length() != 0)
        throw new VcsException("Invalid include rule: " + includeRule.toString() + ", Mercurial plugin supports mapping of the form: +:.=>dir only.");
    }
  }


  private void throwVcsException(Exception e) throws VcsException {
    if (e instanceof VcsException)
      throw (VcsException) e;
    else
      throw new VcsException(e);
  }
}