view mercurial-agent/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/MercurialAgentSideVcsSupport.java @ 280:8c1fd2e565ae

Implement mercurial detection on the agents When agent starts, hg-plugin detects installed hg (searches it in the $PATH). If plugin is able to run hg and hg has an approrpiate version (1.5.2+), then plugin reports path to hg in the 'teamcity.hg.agent.path' parameter. This parameter can be used in the "HG command path" field in a VCS root settings, configurations with such root will be run only on agents which report path to hg. Also user can set this parameter manually in the buildAgent.properties. A server side of plugin first checks value of internal property 'teamcity.hg.server.path' and if property is set, its value is used. Second, plugin tries to use path from the settings of VCS root: if path is equal to '%teamcity.hg.agent.path%' - use 'hg' as path, otherwise use a value from the root. With such order old setups, where path in the VCS root was used on both server and agent, will continue to work. New VCS roots with references in the path will also work if hg is in the $PATH on the server or internal property is set.
author Dmitry Neverov <dmitry.neverov@jetbrains.com>
date Fri, 19 Aug 2011 15:21:38 +0400
parents f80e17ac2da6
children e9cdb499350d
line wrap: on
line source
/*
 * Copyright 2000-2011 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.agent.AgentRunningBuild;
import jetbrains.buildServer.agent.BuildAgentConfiguration;
import jetbrains.buildServer.agent.BuildProgressLogger;
import jetbrains.buildServer.agent.vcs.AgentVcsSupport;
import jetbrains.buildServer.agent.vcs.IncludeRuleUpdater;
import jetbrains.buildServer.agent.vcs.UpdateByIncludeRules2;
import jetbrains.buildServer.agent.vcs.UpdatePolicy;
import jetbrains.buildServer.buildTriggers.vcs.mercurial.command.*;
import jetbrains.buildServer.util.FileUtil;
import jetbrains.buildServer.vcs.CheckoutRules;
import jetbrains.buildServer.vcs.IncludeRule;
import jetbrains.buildServer.vcs.VcsException;
import jetbrains.buildServer.vcs.VcsRoot;
import org.jetbrains.annotations.NotNull;

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

import static jetbrains.buildServer.buildTriggers.vcs.mercurial.command.CommandUtil.removePrivateData;

public class MercurialAgentSideVcsSupport extends AgentVcsSupport implements UpdateByIncludeRules2 {

  private final MirrorManager myMirrorManager;
  private final HgPathProvider myHgPathProvider;

  public MercurialAgentSideVcsSupport(@NotNull final BuildAgentConfiguration agentConfiguration,
                                      @NotNull final HgPathProvider hgPathProvider) {
    myMirrorManager = new MirrorManager(agentConfiguration.getCacheDirectory("mercurial"));
    myHgPathProvider = hgPathProvider;
  }

  public IncludeRuleUpdater getUpdater(@NotNull final VcsRoot vcsRoot, @NotNull final CheckoutRules checkoutRules, @NotNull final String toVersion, @NotNull final File checkoutDirectory, @NotNull final AgentRunningBuild build, boolean cleanCheckoutRequested) throws VcsException {
    final BuildProgressLogger logger = build.getBuildLogger();
    final boolean useLocalMirrors = isUseLocalMirrors(build);

    return new IncludeRuleUpdater() {
      public void process(@NotNull final IncludeRule includeRule, @NotNull final File workingDir) throws VcsException {
        try {
          checkRuleIsValid(includeRule);
          Settings settings = new Settings(myHgPathProvider, vcsRoot);
          if (useLocalMirrors) {
            updateLocalMirror(vcsRoot, logger);
          }
          updateRepository(workingDir, settings, logger, useLocalMirrors);
          updateWorkingDir(settings, workingDir, toVersion, logger);
        } catch (Exception e) {
          if (e instanceof VcsException)
            throw (VcsException) e;
          else
            throw new VcsException(e);
        }
      }

      public void dispose() throws VcsException {
      }
    };
  }

  @NotNull
  @Override
  public String getName() {
    return Constants.VCS_NAME;
  }

  @NotNull
  @Override
  public UpdatePolicy getUpdatePolicy() {
    return this;
  }

  private boolean isUseLocalMirrors(AgentRunningBuild build) {
    String value = build.getSharedConfigParameters().get("teamcity.hg.use.local.mirrors");
    return "true".equals(value);
  }

  private void initRepository(File workingDir, Settings settings, BuildProgressLogger logger, boolean useLocalMirrors) throws VcsException {
    try {
      String defaultPullUrl = getDefaultPullUrl(settings, useLocalMirrors);
      logger.message("Init repository at " + workingDir.getAbsolutePath() + ", remote repository is " +
              removePrivateData(defaultPullUrl, Collections.singleton(settings.getPassword())));
      new Init(settings, workingDir, defaultPullUrl).execute();
    } catch (IOException e) {
      throw new VcsException("Error while initializing repository at " + workingDir.getAbsolutePath(), e);
    }
  }

  private void updateRepository(File workingDir, Settings settings, BuildProgressLogger logger, boolean useLocalMirrors) throws VcsException, IOException {
    if (!Settings.isValidRepository(workingDir)) {
      initRepository(workingDir, settings, logger, useLocalMirrors);
    } else {
      ensureUseRightRepository(workingDir, settings, logger, useLocalMirrors);
    }
    String defaultPullUrl = getDefaultPullUrl(settings, useLocalMirrors);
    logger.message("Start pulling changes from " + removePrivateData(defaultPullUrl, Collections.singleton(settings.getPassword())));
    new PullCommand(settings, workingDir).execute();
    logger.message("Changes successfully pulled");
  }

  private void ensureUseRightRepository(File workingDir, Settings settings, BuildProgressLogger logger, boolean useLocalMirrors) throws VcsException {
    boolean clonedFromWrongRepository = useLocalMirrors && !isClonedFromLocalMirror(settings, workingDir)
                                     || !useLocalMirrors && isClonedFromLocalMirror(settings, workingDir);

    if (clonedFromWrongRepository) {
      String rightRepository = useLocalMirrors ? "local mirror" : "remote repository";
      String wrongRepository = useLocalMirrors ? "remote repository" : "local mirror";
      logger.message("Repository in working directory is cloned from " + wrongRepository + ", clone it from " + rightRepository);
      FileUtil.delete(workingDir);
      initRepository(workingDir, settings, logger, useLocalMirrors);
    }
  }

  private void updateLocalMirror(VcsRoot root, BuildProgressLogger logger) throws VcsException, IOException {
    Settings settings = new Settings(myHgPathProvider, root);
    File mirrorDir = myMirrorManager.getMirrorDir(settings.getRepositoryUrl());
    logger.message("Update local mirror at " + mirrorDir);
    if (!Settings.isValidRepository(mirrorDir)) {
      initRepository(mirrorDir, settings, logger, false);
    }
    final String defaultPullUrl = getDefaultPullUrl(settings, true);
    logger.message("Start pulling changes from " + removePrivateData(defaultPullUrl, Collections.singleton(settings.getPassword())));
    new PullCommand(settings, mirrorDir).execute();
    logger.message("Local mirror changes successfully pulled");
  }

  private void updateWorkingDir(final Settings settings, File workingDir, @NotNull final String version, final BuildProgressLogger logger) throws VcsException {
    logger.message("Updating folder " + workingDir.getAbsolutePath() + " to revision " + version);
    UpdateCommand uc = new UpdateCommand(settings, workingDir);
    ChangeSet cs = new ChangeSet(version);
    uc.setToId(cs.getId());
    uc.execute();
    logger.message("Folder successfully updated");
  }

  private String getDefaultPullUrl(Settings settings, boolean useLocalMirror) throws IOException {
    if (useLocalMirror) {
      File mirrorDir = myMirrorManager.getMirrorDir(settings.getRepositoryUrl());
      return mirrorDir.getCanonicalPath();
    } else {
      return settings.getRepositoryUrl();
    }
  }

  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.");
      }
    }
  }

  public boolean isClonedFromLocalMirror(Settings settings, File workingDir) {
    try {
      File mirrorDir = myMirrorManager.getMirrorDir(settings.getRepositoryUrl());
      File hgrc = new File(workingDir, ".hg" + File.separator + "hgrc");
      String config = FileUtil.readText(hgrc);
      return config.contains("default = " + mirrorDir.getCanonicalPath());
    } catch (Exception e) {
      return false;
    }
  }
}