TW-49786 respect root settings when checking constraints between roots
authorDmitry Neverov <dmitry.neverov@gmail.com>
Tue, 25 Apr 2017 08:38:40 +0000 (10:38 +0200)
committerDmitry Neverov <dmitry.neverov@gmail.com>
Tue, 25 Apr 2017 08:38:40 +0000 (10:38 +0200)
git-agent/src/jetbrains/buildServer/buildTriggers/vcs/git/agent/GitAgentVcsSupport.java
git-tests/src/jetbrains/buildServer/buildTriggers/vcs/git/tests/AutoCheckoutTest.java
git-tests/src/jetbrains/buildServer/buildTriggers/vcs/git/tests/builders/AgentRunningBuildBuilder.java

index 3736da3ac77d8ee901fac17c0d7fa0c74a8344da..972082eca00b1167516b1ff46fc97544a59b92a5 100644 (file)
@@ -32,6 +32,9 @@ import org.jetbrains.annotations.Nullable;
 
 import java.io.File;
 import java.util.*;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.atomic.AtomicLong;
 
 /**
  * The agent support for VCS.
@@ -45,6 +48,15 @@ public class GitAgentVcsSupport extends AgentVcsSupport implements UpdateByCheck
   private final MirrorManager myMirrorManager;
   private final GitMetaFactory myGitMetaFactory;
 
+  //The canCheckout() method should check that roots are not checked out in the same dir (TW-49786).
+  //To do that we need to create AgentPluginConfig for each VCS root which involves 'git version'
+  //command execution. Since we don't have a dedicated API for checking several roots, every root
+  //is checked with all other roots. In order to avoid running n^2 'git version' commands configs
+  //are cached for the build. Cache is reset when we get a new build.
+  private final AtomicLong myConfigsCacheBuildId = new AtomicLong(-1); //buildId for which configs are cached
+  private final ConcurrentMap<VcsRoot, AgentPluginConfig> myConfigsCache = new ConcurrentHashMap<VcsRoot, AgentPluginConfig>();//cached config per root
+  private final ConcurrentMap<VcsRoot, VcsException> myConfigErrorsCache = new ConcurrentHashMap<VcsRoot, VcsException>();//cached error thrown during config creation per root
+
   public GitAgentVcsSupport(@NotNull FS fs,
                             @NotNull SmartDirectoryCleaner directoryCleaner,
                             @NotNull GitAgentSSHService sshService,
@@ -113,7 +125,7 @@ public class GitAgentVcsSupport extends AgentVcsSupport implements UpdateByCheck
   public AgentCheckoutAbility canCheckout(@NotNull final VcsRoot vcsRoot, @NotNull CheckoutRules checkoutRules, @NotNull final AgentRunningBuild build) {
     AgentPluginConfig config;
     try {
-      config = myConfigFactory.createConfig(build, vcsRoot);
+      config = getAndCacheConfig(build, vcsRoot);
     } catch (VcsException e) {
       return AgentCheckoutAbility.noVcsClientOnAgent(e.getMessage());
     }
@@ -141,7 +153,18 @@ public class GitAgentVcsSupport extends AgentVcsSupport implements UpdateByCheck
         VcsRoot otherRoot = entry.getVcsRoot();
         if (vcsRoot.equals(otherRoot))
           continue;
-        String entryPath = getTargetPathAndMode(entry.getCheckoutRules()).second;
+
+        AgentPluginConfig otherConfig;
+        try {
+          otherConfig = getAndCacheConfig(build, otherRoot);
+        } catch (VcsException e) {
+          continue;//appropriate reason will be returned during otherRoot check
+        }
+        Pair<CheckoutMode, String> otherPathAndMode = getTargetPathAndMode(entry.getCheckoutRules());
+        if (otherPathAndMode.first == CheckoutMode.SPARSE_CHECKOUT && !canUseSparseCheckout(otherConfig)) {
+          continue;//appropriate reason will be returned during otherRoot check
+        }
+        String entryPath = otherPathAndMode.second;
         if (targetDir.equals(entryPath))
           return AgentCheckoutAbility.canNotCheckout("Cannot checkout VCS root '" + vcsRoot.getName() + "' into the same directory as VCS root '" + otherRoot.getName() + "'");
       }
@@ -211,6 +234,32 @@ public class GitAgentVcsSupport extends AgentVcsSupport implements UpdateByCheck
   }
 
 
+  @NotNull
+  private AgentPluginConfig getAndCacheConfig(@NotNull AgentRunningBuild build, @NotNull VcsRoot root) throws VcsException {
+    //reset cache if we get a new build
+    if (build.getBuildId() != myConfigsCacheBuildId.get()) {
+      myConfigsCacheBuildId.set(build.getBuildId());
+      myConfigsCache.clear();
+      myConfigErrorsCache.clear();
+    }
+
+    AgentPluginConfig result = myConfigsCache.get(root);
+    if (result == null) {
+      VcsException error = myConfigErrorsCache.get(root);
+      if (error != null)
+        throw error;
+      try {
+        result = myConfigFactory.createConfig(build, root);
+      } catch (VcsException e) {
+        myConfigErrorsCache.put(root, e);
+        throw e;
+      }
+      myConfigsCache.put(root, result);
+    }
+    return result;
+  }
+
+
   @NotNull
   private Pair<CheckoutMode, String> getTargetPathAndMode(@NotNull CheckoutRules rules) {
     if (isRequireSparseCheckout(rules)) {
index 3aebb40bfc2d4ecdc537928074f19f78579dc151..c89adec53438b43cd092b9ee5a13688f1ce2cc5f 100644 (file)
@@ -179,7 +179,7 @@ public class AutoCheckoutTest extends BaseRemoteRepositoryTest {
 
   @TestFor(issues = "TW-49786")
   @Test(dataProvider = "severalRootsSetups")
-  private void several_roots(@NotNull Setup setup) throws Exception {
+  public void several_roots(@NotNull Setup setup) throws Exception {
     myVcsSupport = vcsSupportWithRealGit();
 
     VcsRoot root1 = vcsRoot().withId(1).withFetchUrl("http://some.org/repo1.git").build();
@@ -202,6 +202,61 @@ public class AutoCheckoutTest extends BaseRemoteRepositoryTest {
   }
 
 
+  @TestFor(issues = "TW-49786")
+  public void should_respect_root_settings_when_checking_multi_root_constraints() throws Exception {
+    myVcsSupport = vcsSupportWithRealGit();
+
+    //second root has broken git path, we should not take it into account
+    //during canCheckout() for the first VCS root
+    VcsRoot root1 = vcsRoot().withId(1).withFetchUrl("http://some.org/repo1.git").build();
+    VcsRoot root2 = vcsRoot().withId(2).withAgentGitPath("wrongGitPath").withFetchUrl("http://some.org/repo2.git").build();
+    AgentRunningBuild build = runningBuild()
+      .addRootEntry(root1, "+:dir1")
+      .addRootEntry(root2, "+:dir2")
+      .build();
+
+    AgentCheckoutAbility canCheckout1 = myVcsSupport.canCheckout(root1, new CheckoutRules("+:dir1"), build);
+    AgentCheckoutAbility canCheckout2 = myVcsSupport.canCheckout(root2, new CheckoutRules("+:dir2"), build);
+    then(canCheckout1.getCanNotCheckoutReason()).isNull();
+    then(canCheckout2.getCanNotCheckoutReason().getType()).isEqualTo(AgentCanNotCheckoutReason.NO_VCS_CLIENT);
+    then(canCheckout2.getCanNotCheckoutReason().getDetails()).contains("Unable to run git at path wrongGitPath");
+  }
+
+
+  @TestFor(issues = "TW-49786")
+  public void should_respect_root_settings_when_checking_multi_root_constraints2() throws Exception {
+    VcsRoot root1 = vcsRoot().withId(1).withFetchUrl("http://some.org/repo1.git").build();
+    VcsRoot root2 = vcsRoot().withId(2).withFetchUrl("http://some.org/repo2.git").build();
+    AgentRunningBuild build = runningBuild()
+      .addRootEntry(root1, "+:dir1")
+      .addRootEntry(root2, "+:dir2")
+      .build();
+
+    //both roots require sparse checkout and mapped into the same directory, but the second
+    //root uses git version which doesn't support sparse checkout; we shouldn't take it into
+    //account during canCheckout() check for the first root
+    GitDetector detector = new GitDetector() {
+      @NotNull
+      public GitExec getGitPathAndVersion(@NotNull VcsRoot root, @NotNull BuildAgentConfiguration config, @NotNull AgentRunningBuild build) throws VcsException {
+        if (root.equals(root1)) {
+          return new GitExec("git1", GIT_WITH_SPARSE_CHECKOUT);
+        }
+        if (root.equals(root2)) {
+          return new GitExec("git2", GIT_WITH_SPARSE_CHECKOUT.previousVersion());
+        }
+        throw new VcsException("Unexpected VCS root");
+      }
+    };
+    myVcsSupport = createVcsSupport(detector);
+
+    AgentCheckoutAbility canCheckout1 = myVcsSupport.canCheckout(root1, new CheckoutRules("+:dir1"), build);
+    AgentCheckoutAbility canCheckout2 = myVcsSupport.canCheckout(root2, new CheckoutRules("+:dir2"), build);
+    then(canCheckout1.getCanNotCheckoutReason()).isNull();
+    then(canCheckout2.getCanNotCheckoutReason().getType()).isEqualTo(AgentCanNotCheckoutReason.NOT_SUPPORTED_CHECKOUT_RULES);
+    then(canCheckout2.getCanNotCheckoutReason().getDetails()).contains("Cannot perform sparse checkout using git " + GIT_WITH_SPARSE_CHECKOUT.previousVersion());
+  }
+
+
   private void verifyCanCheckout(final VcsRoot vcsRoot, CheckoutRules checkoutRules, final AgentRunningBuild build) throws VcsException {
     AgentCheckoutAbility canCheckout = myVcsSupport.canCheckout(vcsRoot, checkoutRules, build);
     then(canCheckout.getCanNotCheckoutReason()).isNull();
index c5a4adfbcbc1357a91175b380d977699277e1d39..1d0ddfd2e02b72dba40dbeb93cb90bc759378e79 100644 (file)
@@ -243,7 +243,7 @@ public class AgentRunningBuildBuilder {
       }
 
       public long getBuildId() {
-        throw new UnsupportedOperationException();
+        return 1;
       }
 
       public boolean isCleanBuild() {