changeset 856:a11bcfb63f4f

Support sparse checkout
author Dmitry Neverov <dmitry.neverov@jetbrains.com>
date Tue, 08 Jul 2014 16:15:55 +0200
parents dbb5464363d9
children b11ace800435
files mercurial-agent/src/META-INF/build-agent-plugin-mercurial.xml mercurial-agent/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/AgentPluginConfig.java mercurial-agent/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/AgentPluginConfigImpl.java mercurial-agent/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/AgentRepoFactory.java mercurial-agent/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/MercurialAgentSideVcsSupport.java mercurial-agent/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/MercurialIncludeRuleUpdater.java mercurial-agent/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/ext/BeforeWorkingDirUpdateExtension.java mercurial-agent/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/ext/CheckoutInfo.java mercurial-agent/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/ext/MercurialExtension.java mercurial-agent/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/ext/MercurialExtensionManager.java mercurial-agent/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/ext/MercurialExtensionProvider.java mercurial-agent/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/ext/impl/SparseCheckoutProvider.java mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/HgRepoFactory.java mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/AgentMirrorCleanerTest.java mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/AgentSideCheckoutTest.java mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/AgentSideCheckoutWithSharedMirrorsTest.java mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/AgentSideCheckoutWithSubreposTest.java mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/BaseAgentSideCheckoutTestCase.java
diffstat 18 files changed, 385 insertions(+), 11 deletions(-) [+]
line wrap: on
line diff
--- a/mercurial-agent/src/META-INF/build-agent-plugin-mercurial.xml	Tue Jul 08 12:08:44 2014 +0200
+++ b/mercurial-agent/src/META-INF/build-agent-plugin-mercurial.xml	Tue Jul 08 16:15:55 2014 +0200
@@ -30,4 +30,7 @@
   <bean class="jetbrains.buildServer.buildTriggers.vcs.mercurial.command.CommandSettingsForRootImpl"/>
   <bean class="jetbrains.buildServer.buildTriggers.vcs.mercurial.command.ExtensionsWeaver"/>
   <bean class="jetbrains.buildServer.buildTriggers.vcs.mercurial.command.CommandlineViaFileWrapperWeaver"/>
+
+  <bean class="jetbrains.buildServer.buildTriggers.vcs.mercurial.ext.MercurialExtensionManager"/>
+  <bean class="jetbrains.buildServer.buildTriggers.vcs.mercurial.ext.impl.SparseCheckoutProvider"/>
 </beans>
--- a/mercurial-agent/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/AgentPluginConfig.java	Tue Jul 08 12:08:44 2014 +0200
+++ b/mercurial-agent/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/AgentPluginConfig.java	Tue Jul 08 16:15:55 2014 +0200
@@ -19,6 +19,8 @@
 import jetbrains.buildServer.agent.AgentRunningBuild;
 import org.jetbrains.annotations.NotNull;
 
+import java.io.File;
+
 /**
  * @author dmitry.neverov
  */
@@ -31,4 +33,7 @@
   int getPullTimeout(@NotNull AgentRunningBuild build);
 
   boolean runWithTraceback(@NotNull AgentRunningBuild build);
+
+  @NotNull
+  File getTempDir();
 }
--- a/mercurial-agent/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/AgentPluginConfigImpl.java	Tue Jul 08 12:08:44 2014 +0200
+++ b/mercurial-agent/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/AgentPluginConfigImpl.java	Tue Jul 08 16:15:55 2014 +0200
@@ -81,4 +81,9 @@
       return null;
     }
   }
+
+  @NotNull
+  public File getTempDir() {
+    return myAgentConfig.getTempDirectory();
+  }
 }
--- a/mercurial-agent/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/AgentRepoFactory.java	Tue Jul 08 12:08:44 2014 +0200
+++ b/mercurial-agent/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/AgentRepoFactory.java	Tue Jul 08 16:15:55 2014 +0200
@@ -25,16 +25,26 @@
 
 public class AgentRepoFactory implements HgRepoFactory {
 
+  private final AgentPluginConfig myPluginConfig;
   private final CommandSettingsForRoot myCommandSettingsFactory;
   private final HgPathProvider myHgPathProvider;
 
-  public AgentRepoFactory(@NotNull CommandSettingsForRoot commandSettingsFactory,
+  public AgentRepoFactory(@NotNull AgentPluginConfig pluginConfig,
+                          @NotNull CommandSettingsForRoot commandSettingsFactory,
                           @NotNull HgPathProvider hgPathProvider) {
+    myPluginConfig = pluginConfig;
     myCommandSettingsFactory = commandSettingsFactory;
     myHgPathProvider = hgPathProvider;
   }
 
+  @NotNull
   public HgRepo createRepo(@NotNull HgVcsRoot root, @NotNull File workingDir) throws VcsException {
     return new HgRepo(myCommandSettingsFactory.forRoot(root), workingDir, myHgPathProvider.getHgPath(root), root.getAuthSettings());
   }
+
+  @NotNull
+  public HgRepo createTempDirRepo(@NotNull HgVcsRoot root) {
+    return new HgRepo(myCommandSettingsFactory.forRoot(root), myPluginConfig.getTempDir(),
+            myHgPathProvider.getHgPath(root), root.getAuthSettings());
+  }
 }
--- a/mercurial-agent/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/MercurialAgentSideVcsSupport.java	Tue Jul 08 12:08:44 2014 +0200
+++ b/mercurial-agent/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/MercurialAgentSideVcsSupport.java	Tue Jul 08 16:15:55 2014 +0200
@@ -20,6 +20,10 @@
 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.HgVcsRoot;
+import jetbrains.buildServer.buildTriggers.vcs.mercurial.ext.CheckoutInfo;
+import jetbrains.buildServer.buildTriggers.vcs.mercurial.ext.MercurialExtension;
+import jetbrains.buildServer.buildTriggers.vcs.mercurial.ext.MercurialExtensionManager;
 import jetbrains.buildServer.vcs.CheckoutRules;
 import jetbrains.buildServer.vcs.VcsException;
 import jetbrains.buildServer.vcs.VcsRoot;
@@ -32,20 +36,34 @@
   private final AgentPluginConfig myConfig;
   private final MirrorManager myMirrorManager;
   private final AgentRepoFactory myRepoFactory;
+  private final MercurialExtensionManager myExtentionManager;
 
   public MercurialAgentSideVcsSupport(@NotNull AgentPluginConfig pluginConfig,
                                       @NotNull MirrorManager mirrorManager,
-                                      @NotNull AgentRepoFactory repoFactory) {
+                                      @NotNull AgentRepoFactory repoFactory,
+                                      @NotNull MercurialExtensionManager extentionManager) {
     myConfig = pluginConfig;
     myMirrorManager = mirrorManager;
     myRepoFactory = repoFactory;
+    myExtentionManager = extentionManager;
   }
 
   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 {
+    MercurialIncludeRuleUpdater updater;
     if (myConfig.isUseLocalMirrors(build) && myConfig.shareLocalMirrors(build)) {
-      return new SharingMercurialUpdater(myConfig, myMirrorManager, myRepoFactory, vcsRoot, toVersion, build);
+      updater = new SharingMercurialUpdater(myConfig, myMirrorManager, myRepoFactory, vcsRoot, toVersion, build);
     } else {
-      return new MercurialIncludeRuleUpdater(myConfig, myMirrorManager, myRepoFactory, vcsRoot, toVersion, build);
+      updater = new MercurialIncludeRuleUpdater(myConfig, myMirrorManager, myRepoFactory, vcsRoot, toVersion, build);
+    }
+    registerExtensions(vcsRoot, checkoutRules, updater);
+    return updater;
+  }
+
+
+  private void registerExtensions(@NotNull VcsRoot root, @NotNull CheckoutRules checkoutRules, @NotNull MercurialIncludeRuleUpdater updater) {
+    CheckoutInfo info = new CheckoutInfo(myRepoFactory, new HgVcsRoot(root), checkoutRules);
+    for (MercurialExtension ext :myExtentionManager.getExtensionsForCheckout(info)) {
+      updater.registerExtension(ext);
     }
   }
 
--- a/mercurial-agent/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/MercurialIncludeRuleUpdater.java	Tue Jul 08 12:08:44 2014 +0200
+++ b/mercurial-agent/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/MercurialIncludeRuleUpdater.java	Tue Jul 08 16:15:55 2014 +0200
@@ -23,6 +23,8 @@
 import jetbrains.buildServer.buildTriggers.vcs.mercurial.command.HgVcsRoot;
 import jetbrains.buildServer.buildTriggers.vcs.mercurial.command.exception.UnrelatedRepositoryException;
 import jetbrains.buildServer.buildTriggers.vcs.mercurial.command.exception.WrongSubrepoUrlException;
+import jetbrains.buildServer.buildTriggers.vcs.mercurial.ext.BeforeWorkingDirUpdateExtension;
+import jetbrains.buildServer.buildTriggers.vcs.mercurial.ext.MercurialExtension;
 import jetbrains.buildServer.log.Loggers;
 import jetbrains.buildServer.util.FileUtil;
 import jetbrains.buildServer.vcs.IncludeRule;
@@ -52,6 +54,7 @@
   private final boolean myUseLocalMirrors;
   private int myPullTimeout;
   private final boolean myUseTraceback;
+  private final List<MercurialExtension> myExtensions = new ArrayList<MercurialExtension>();
 
   public MercurialIncludeRuleUpdater(@NotNull AgentPluginConfig pluginConfig,
                                      @NotNull MirrorManager mirrorManager,
@@ -88,6 +91,22 @@
   }
 
 
+  public void registerExtension(@NotNull MercurialExtension extention) {
+    myExtensions.add(extention);
+  }
+
+
+  @NotNull
+  protected <T extends MercurialExtension> List<T> getExtensions(@NotNull Class<T> extensionClass) {
+    List<T> extentions = new ArrayList<T>();
+    for (MercurialExtension e : myExtensions) {
+      if (extensionClass.isInstance(e))
+        extentions.add(extensionClass.cast(e));
+    }
+    return extentions;
+  }
+
+
   protected void updateLocalMirror(@NotNull String repositoryUrl, @NotNull String revision) throws VcsException, IOException {
     File mirrorDir = myMirrorManager.getMirrorDir(repositoryUrl);
     HgRepo mirrorRepo = myRepoFactory.createRepo(myRoot, mirrorDir);
@@ -248,6 +267,9 @@
 
 
   private void doUpdateWorkingDir(@NotNull HgRepo repo, @NotNull String revision) throws VcsException {
+    for (BeforeWorkingDirUpdateExtension e : getExtensions(BeforeWorkingDirUpdateExtension.class)) {
+      e.call(repo, revision);
+    }
     myLogger.message("Updating working dir " + repo.path() + " to revision " + revision);
     repo.update().withTraceback(myUseTraceback).toRevision(revision).call();
     myLogger.message("Working dir updated");
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial-agent/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/ext/BeforeWorkingDirUpdateExtension.java	Tue Jul 08 16:15:55 2014 +0200
@@ -0,0 +1,25 @@
+/*
+ * 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.ext;
+
+import jetbrains.buildServer.buildTriggers.vcs.mercurial.HgRepo;
+import jetbrains.buildServer.vcs.VcsException;
+import org.jetbrains.annotations.NotNull;
+
+public interface BeforeWorkingDirUpdateExtension extends MercurialExtension {
+  public void call(@NotNull HgRepo repo, @NotNull String revision) throws VcsException;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial-agent/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/ext/CheckoutInfo.java	Tue Jul 08 16:15:55 2014 +0200
@@ -0,0 +1,52 @@
+/*
+ * 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.ext;
+
+import jetbrains.buildServer.buildTriggers.vcs.mercurial.AgentRepoFactory;
+import jetbrains.buildServer.buildTriggers.vcs.mercurial.HgRepo;
+import jetbrains.buildServer.buildTriggers.vcs.mercurial.command.HgVcsRoot;
+import jetbrains.buildServer.vcs.CheckoutRules;
+import org.jetbrains.annotations.NotNull;
+
+public class CheckoutInfo {
+  private final AgentRepoFactory myRepoFactory;
+  private final HgVcsRoot myRoot;
+  private final CheckoutRules myRules;
+
+  public CheckoutInfo(@NotNull AgentRepoFactory repoFactory,
+                      @NotNull HgVcsRoot root,
+                      @NotNull CheckoutRules rules) {
+    myRepoFactory = repoFactory;
+    myRoot = root;
+    myRules = rules;
+  }
+
+  @NotNull
+  public CheckoutRules getCheckoutRules() {
+    return myRules;
+  }
+
+  @NotNull
+  public HgVcsRoot getRoot() {
+    return myRoot;
+  }
+
+  @NotNull
+  public HgRepo getTempDirRepo() {
+    return myRepoFactory.createTempDirRepo(getRoot());
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial-agent/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/ext/MercurialExtension.java	Tue Jul 08 16:15:55 2014 +0200
@@ -0,0 +1,20 @@
+/*
+ * 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.ext;
+
+public interface MercurialExtension {
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial-agent/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/ext/MercurialExtensionManager.java	Tue Jul 08 16:15:55 2014 +0200
@@ -0,0 +1,42 @@
+/*
+ * 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.ext;
+
+import org.jetbrains.annotations.NotNull;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class MercurialExtensionManager {
+
+  private final List<MercurialExtensionProvider> myProviders = new ArrayList<MercurialExtensionProvider>();
+
+  public void registerExtentionFactory(@NotNull MercurialExtensionProvider provider) {
+    myProviders.add(provider);
+  }
+
+  @NotNull
+  public List<MercurialExtension> getExtensionsForCheckout(@NotNull CheckoutInfo info) {
+    List<MercurialExtension> extensions = new ArrayList<MercurialExtension>();
+    for (MercurialExtensionProvider provider : myProviders) {
+      MercurialExtension ext = provider.getExtentionForCheckout(info);
+      if (ext != null)
+        extensions.add(ext);
+    }
+    return extensions;
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial-agent/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/ext/MercurialExtensionProvider.java	Tue Jul 08 16:15:55 2014 +0200
@@ -0,0 +1,27 @@
+/*
+ * 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.ext;
+
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+public interface MercurialExtensionProvider {
+
+  @Nullable
+  MercurialExtension getExtentionForCheckout(@NotNull CheckoutInfo info);
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial-agent/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/ext/impl/SparseCheckoutProvider.java	Tue Jul 08 16:15:55 2014 +0200
@@ -0,0 +1,134 @@
+/*
+ * 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.ext.impl;
+
+import com.intellij.openapi.util.text.StringUtil;
+import jetbrains.buildServer.buildTriggers.vcs.mercurial.HgRepo;
+import jetbrains.buildServer.buildTriggers.vcs.mercurial.HgVersion;
+import jetbrains.buildServer.buildTriggers.vcs.mercurial.command.CommandResult;
+import jetbrains.buildServer.buildTriggers.vcs.mercurial.ext.*;
+import jetbrains.buildServer.log.Loggers;
+import jetbrains.buildServer.util.FileUtil;
+import jetbrains.buildServer.vcs.CheckoutRules;
+import jetbrains.buildServer.vcs.FileRule;
+import jetbrains.buildServer.vcs.VcsException;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.List;
+
+public class SparseCheckoutProvider implements MercurialExtensionProvider {
+
+  private static final HgVersion MIN_SPARSE_CHECKOUT_VERSION = new HgVersion(3, 0, 0);
+
+  public SparseCheckoutProvider(@NotNull MercurialExtensionManager extentionManager) {
+    extentionManager.registerExtentionFactory(this);
+  }
+
+  @Nullable
+  public MercurialExtension getExtentionForCheckout(@NotNull CheckoutInfo info) {
+    if (sparseIsAvailable(info)) {
+      return new SparseCheckout(info.getCheckoutRules());
+    } else {
+      return new UndoSparseCheckout();
+    }
+  }
+
+
+  private boolean sparseIsAvailable(@NotNull CheckoutInfo info) {
+    if (info.getCheckoutRules().getExcludeRules().isEmpty())
+      return false;
+    try {
+      HgVersion version = info.getTempDirRepo().version().call();
+      if (!version.isEqualsOrGreaterThan(MIN_SPARSE_CHECKOUT_VERSION)) {
+        Loggers.VCS.info("The sparse checkout is not supported in the mercurial version " + version.toString());
+        return false;
+      }
+      CommandResult result = info.getTempDirRepo().runCommand("help", "extensions");
+      boolean enabledExtensions = false;
+      for (String line : StringUtil.splitByLines(result.getStdout())) {
+        if (line.trim().equals("enabled extensions:")) {
+          enabledExtensions = true;
+          continue;
+        }
+        if (line.trim().equals("disabled extensions:")) {
+          return false;
+        }
+        if (enabledExtensions) {
+          List<String> words = StringUtil.getWordsIn(line.trim());
+          if (words.isEmpty())
+            continue;
+          if (words.get(0).equals("sparse")) {
+            Loggers.VCS.info("Sparse extension is enabled");
+            return true;
+          }
+        }
+      }
+      return true;
+    } catch (VcsException e) {
+      //log
+      return false;
+    }
+  }
+
+
+  private static class SparseCheckout implements BeforeWorkingDirUpdateExtension {
+    private final CheckoutRules myRules;
+    private SparseCheckout(@NotNull CheckoutRules rules) {
+      myRules = rules;
+    }
+
+    public void call(@NotNull HgRepo repo, @NotNull String revision) throws VcsException {
+      writeSparseConfig(repo.getWorkingDir());
+      repo.runCommand("sparse", "--refresh");
+    }
+
+    private void writeSparseConfig(@NotNull File workingDir) throws VcsException {
+      String sparse = getSparseContent();
+      try {
+        FileUtil.writeToFile(new File(new File(workingDir, ".hg"), "sparse"), sparse.getBytes("UTF-8"), false);
+      } catch (IOException e) {
+        Loggers.VCS.warn("Error while writing .hg/sparse, will not do a sparse checkout", e);
+      }
+    }
+
+    @NotNull
+    private String getSparseContent() {
+      StringBuilder sparse = new StringBuilder();
+      sparse.append("[exclude]\n");
+      for (FileRule rule : myRules.getExcludeRules()) {
+        sparse.append(rule.getFrom()).append("\n");
+      }
+      return sparse.toString();
+    }
+  }
+
+
+  private static class UndoSparseCheckout implements BeforeWorkingDirUpdateExtension {
+    public void call(@NotNull HgRepo repo, @NotNull String revision) throws VcsException {
+      File workingDir = repo.getWorkingDir();
+      File sparseConfig = new File(new File(workingDir, ".hg"), "sparse");
+      if (sparseConfig.exists()) {
+        Loggers.VCS.info("Remove sparse extension config and reset working directory state");
+        FileUtil.delete(sparseConfig);
+        FileUtil.delete(new File(new File(workingDir, ".hg"), "dirstate"));
+      }
+    }
+  }
+}
--- a/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/HgRepoFactory.java	Tue Jul 08 12:08:44 2014 +0200
+++ b/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/HgRepoFactory.java	Tue Jul 08 16:15:55 2014 +0200
@@ -24,6 +24,7 @@
 
 public interface HgRepoFactory {
 
+  @NotNull
   HgRepo createRepo(@NotNull HgVcsRoot root, @NotNull File workingDir) throws VcsException;
 
 }
--- a/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/AgentMirrorCleanerTest.java	Tue Jul 08 12:08:44 2014 +0200
+++ b/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/AgentMirrorCleanerTest.java	Tue Jul 08 16:15:55 2014 +0200
@@ -21,6 +21,7 @@
 import jetbrains.buildServer.buildTriggers.vcs.mercurial.command.CommandlineViaFileWrapperWeaver;
 import jetbrains.buildServer.buildTriggers.vcs.mercurial.command.ExtensionsWeaver;
 import jetbrains.buildServer.buildTriggers.vcs.mercurial.command.TestCommandSettingsFactory;
+import jetbrains.buildServer.buildTriggers.vcs.mercurial.ext.MercurialExtensionManager;
 import jetbrains.buildServer.vcs.*;
 import jetbrains.buildServer.vcs.impl.VcsRootImpl;
 import org.jetbrains.annotations.NotNull;
@@ -65,8 +66,8 @@
 
     AgentPluginConfigImpl pluginConfig = new AgentPluginConfigImpl(agentConfig);
     myMirrorManager = new MirrorManagerImpl(pluginConfig);
-    AgentRepoFactory repoFactory = new AgentRepoFactory(new CommandSettingsForRootImpl(new TestCommandSettingsFactory(), new ExtensionsWeaver(), new CommandlineViaFileWrapperWeaver()), new AgentHgPathProvider(agentConfig));
-    myVcsSupport = new MercurialAgentSideVcsSupport(pluginConfig, myMirrorManager, repoFactory);
+    AgentRepoFactory repoFactory = new AgentRepoFactory(pluginConfig, new CommandSettingsForRootImpl(new TestCommandSettingsFactory(), new ExtensionsWeaver(), new CommandlineViaFileWrapperWeaver()), new AgentHgPathProvider(agentConfig));
+    myVcsSupport = new MercurialAgentSideVcsSupport(pluginConfig, myMirrorManager, repoFactory, new MercurialExtensionManager());
     myCleaner = new AgentMirrorCleaner(myMirrorManager);
     myLogger = myContext.mock(BuildProgressLogger.class);
     myContext.checking(new Expectations() {{
--- a/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/AgentSideCheckoutTest.java	Tue Jul 08 12:08:44 2014 +0200
+++ b/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/AgentSideCheckoutTest.java	Tue Jul 08 16:15:55 2014 +0200
@@ -22,6 +22,7 @@
 import jetbrains.buildServer.buildTriggers.vcs.mercurial.command.CommandlineViaFileWrapperWeaver;
 import jetbrains.buildServer.buildTriggers.vcs.mercurial.command.ExtensionsWeaver;
 import jetbrains.buildServer.buildTriggers.vcs.mercurial.command.TestCommandSettingsFactory;
+import jetbrains.buildServer.buildTriggers.vcs.mercurial.ext.MercurialExtensionManager;
 import jetbrains.buildServer.util.FileUtil;
 import jetbrains.buildServer.util.TestFor;
 import jetbrains.buildServer.vcs.CheckoutRules;
@@ -78,7 +79,8 @@
     final AgentPluginConfigImpl pluginConfig = new AgentPluginConfigImpl(agentConfig);
     myVcsSupport = new MercurialAgentSideVcsSupport(pluginConfig,
             new MirrorManagerImpl(pluginConfig),
-            new AgentRepoFactory(new CommandSettingsForRootImpl(new TestCommandSettingsFactory(), new ExtensionsWeaver(), new CommandlineViaFileWrapperWeaver()), new AgentHgPathProvider(agentConfig)));
+            new AgentRepoFactory(pluginConfig, new CommandSettingsForRootImpl(new TestCommandSettingsFactory(), new ExtensionsWeaver(), new CommandlineViaFileWrapperWeaver()), new AgentHgPathProvider(agentConfig)),
+            new MercurialExtensionManager());
 
     myLogger = myContext.mock(BuildProgressLogger.class);
     myContext.checking(new Expectations() {{
--- a/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/AgentSideCheckoutWithSharedMirrorsTest.java	Tue Jul 08 12:08:44 2014 +0200
+++ b/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/AgentSideCheckoutWithSharedMirrorsTest.java	Tue Jul 08 16:15:55 2014 +0200
@@ -23,6 +23,7 @@
 import jetbrains.buildServer.buildTriggers.vcs.mercurial.command.CommandlineViaFileWrapperWeaver;
 import jetbrains.buildServer.buildTriggers.vcs.mercurial.command.ExtensionsWeaver;
 import jetbrains.buildServer.buildTriggers.vcs.mercurial.command.TestCommandSettingsFactory;
+import jetbrains.buildServer.buildTriggers.vcs.mercurial.ext.MercurialExtensionManager;
 import jetbrains.buildServer.util.FileUtil;
 import jetbrains.buildServer.vcs.CheckoutRules;
 import jetbrains.buildServer.vcs.IncludeRule;
@@ -73,8 +74,9 @@
     final AgentPluginConfigImpl pluginConfig = new AgentPluginConfigImpl(agentConfig);
     myVcsSupport = new MercurialAgentSideVcsSupport(pluginConfig,
             new MirrorManagerImpl(pluginConfig),
-            new AgentRepoFactory(new CommandSettingsForRootImpl(new TestCommandSettingsFactory(), new ExtensionsWeaver(),
-                    new CommandlineViaFileWrapperWeaver()), new AgentHgPathProvider(agentConfig)));
+            new AgentRepoFactory(pluginConfig, new CommandSettingsForRootImpl(new TestCommandSettingsFactory(), new ExtensionsWeaver(),
+                    new CommandlineViaFileWrapperWeaver()), new AgentHgPathProvider(agentConfig)),
+            new MercurialExtensionManager());
 
     myLogger = myContext.mock(BuildProgressLogger.class);
     myContext.checking(new Expectations() {{
--- a/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/AgentSideCheckoutWithSubreposTest.java	Tue Jul 08 12:08:44 2014 +0200
+++ b/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/AgentSideCheckoutWithSubreposTest.java	Tue Jul 08 16:15:55 2014 +0200
@@ -22,6 +22,7 @@
 import jetbrains.buildServer.agent.vcs.UpdateByIncludeRules2;
 import jetbrains.buildServer.buildTriggers.vcs.mercurial.command.*;
 import jetbrains.buildServer.buildTriggers.vcs.mercurial.command.exception.ConnectionRefusedException;
+import jetbrains.buildServer.buildTriggers.vcs.mercurial.ext.MercurialExtensionManager;
 import jetbrains.buildServer.util.FileUtil;
 import jetbrains.buildServer.vcs.CheckoutRules;
 import jetbrains.buildServer.vcs.IncludeRule;
@@ -82,7 +83,9 @@
     myMirrorManager = new MirrorManagerImpl(pluginConfig);
     myVcsSupport = new MercurialAgentSideVcsSupport(pluginConfig,
             myMirrorManager,
-            new AgentRepoFactory(new CommandSettingsForRootImpl(new TestCommandSettingsFactory(), new ExtensionsWeaver(), new CommandlineViaFileWrapperWeaver()), new AgentHgPathProvider(agentConfig)));
+            new AgentRepoFactory(pluginConfig, new CommandSettingsForRootImpl(new TestCommandSettingsFactory(), new ExtensionsWeaver(), new CommandlineViaFileWrapperWeaver()),
+                    new AgentHgPathProvider(agentConfig)),
+            new MercurialExtensionManager());
 
     myLogger = myContext.mock(BuildProgressLogger.class);
     myContext.checking(new Expectations() {{
--- a/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/BaseAgentSideCheckoutTestCase.java	Tue Jul 08 12:08:44 2014 +0200
+++ b/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/BaseAgentSideCheckoutTestCase.java	Tue Jul 08 16:15:55 2014 +0200
@@ -24,6 +24,7 @@
 import jetbrains.buildServer.buildTriggers.vcs.mercurial.command.CommandlineViaFileWrapperWeaver;
 import jetbrains.buildServer.buildTriggers.vcs.mercurial.command.ExtensionsWeaver;
 import jetbrains.buildServer.buildTriggers.vcs.mercurial.command.TestCommandSettingsFactory;
+import jetbrains.buildServer.buildTriggers.vcs.mercurial.ext.MercurialExtensionManager;
 import jetbrains.buildServer.vcs.CheckoutRules;
 import jetbrains.buildServer.vcs.IncludeRule;
 import jetbrains.buildServer.vcs.VcsException;
@@ -60,7 +61,8 @@
     final AgentPluginConfigImpl pluginConfig = new AgentPluginConfigImpl(agentConfig);
     MirrorManager mirrorManager = new MirrorManagerImpl(pluginConfig);
     CommandSettingsForRootImpl commandSettingsFactory = new CommandSettingsForRootImpl(new TestCommandSettingsFactory(), new ExtensionsWeaver(), new CommandlineViaFileWrapperWeaver());
-    myVcsSupport = new MercurialAgentSideVcsSupport(pluginConfig, mirrorManager, new AgentRepoFactory(commandSettingsFactory, new AgentHgPathProvider(agentConfig)));
+    myVcsSupport = new MercurialAgentSideVcsSupport(pluginConfig, mirrorManager, new AgentRepoFactory(pluginConfig, commandSettingsFactory, new AgentHgPathProvider(agentConfig)),
+            new MercurialExtensionManager());
 
     myLogger = myContext.mock(BuildProgressLogger.class);
     myContext.checking(new Expectations() {{