TW-49786 disable agent-side checkout when 2 roots are in the same dir
authorDmitry Neverov <dmitry.neverov@gmail.com>
Mon, 24 Apr 2017 12:32:49 +0000 (14:32 +0200)
committerDmitry Neverov <dmitry.neverov@gmail.com>
Mon, 24 Apr 2017 12:32:49 +0000 (14:32 +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 4e6862cf79a1cb74f5e2cdb06e18e466f37cf5b7..adef8025d17c2e53f5f6f6a8a19f216fb909b0eb 100644 (file)
@@ -26,18 +26,12 @@ import jetbrains.buildServer.agent.vcs.UpdatePolicy;
 import jetbrains.buildServer.buildTriggers.vcs.git.Constants;
 import jetbrains.buildServer.buildTriggers.vcs.git.GitVcsRoot;
 import jetbrains.buildServer.buildTriggers.vcs.git.MirrorManager;
-import jetbrains.buildServer.vcs.CheckoutRules;
-import jetbrains.buildServer.vcs.IncludeRule;
-import jetbrains.buildServer.vcs.VcsException;
-import jetbrains.buildServer.vcs.VcsRoot;
+import jetbrains.buildServer.vcs.*;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 
 import java.io.File;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Map;
-import java.util.Set;
+import java.util.*;
 
 /**
  * The agent support for VCS.
@@ -139,9 +133,54 @@ public class GitAgentVcsSupport extends AgentVcsSupport implements UpdateByCheck
       return AgentCheckoutAbility.canNotCheckout(e.getMessage());
     }
 
+    List<VcsRootEntry> gitEntries = getGitRootEntries(build);
+    if (gitEntries.size() > 1) {
+      String targetDir = getTargetDir(config, checkoutRules);
+      for (VcsRootEntry entry : gitEntries) {
+        VcsRoot otherRoot = entry.getVcsRoot();
+        if (vcsRoot.equals(otherRoot))
+          continue;
+        if (targetDir != null && targetDir.equals(getTargetDir(config, entry.getCheckoutRules())))
+          return AgentCheckoutAbility.canNotCheckout("Cannot checkout VCS root '" + vcsRoot.getName() + "' into the same directory as VCS root '" + otherRoot.getName() + "'");
+      }
+    }
+
     return AgentCheckoutAbility.canCheckout();
   }
 
+
+  @Nullable
+  private String getTargetDir(@NotNull AgentPluginConfig config, @NotNull CheckoutRules rules) {
+    if (isRequireSparseCheckout(rules)) {
+      return canUseSparseCheckout(config) ? getSingleTargetDirForSparseCheckout(rules) : null;
+    } else {
+      return rules.map("");
+    }
+  }
+
+
+  private boolean isRequireSparseCheckout(@NotNull CheckoutRules rules) {
+    if (!rules.getExcludeRules().isEmpty())
+      return true;
+    List<IncludeRule> includeRules = rules.getRootIncludeRules();
+    if (includeRules.isEmpty() || includeRules.size() > 1)
+      return true;
+    IncludeRule rule = includeRules.get(0);
+    return !"".equals(rule.getFrom()); //rule of form +:.=>dir doesn't require sparse checkout ('.' is transformed into empty string)
+  }
+
+
+  @NotNull
+  private List<VcsRootEntry> getGitRootEntries(@NotNull AgentRunningBuild build) {
+    List<VcsRootEntry> result = new ArrayList<VcsRootEntry>();
+    for (VcsRootEntry entry : build.getVcsRootEntries()) {
+      if (Constants.VCS_NAME.equals(entry.getVcsRoot().getVcsName()))
+        result.add(entry);
+    }
+    return result;
+  }
+
+
   @NotNull
   private GitBuildProgressLogger getLogger(@NotNull AgentRunningBuild build, @NotNull AgentPluginConfig config) {
     return new GitBuildProgressLogger(build.getBuildLogger().getFlowLogger("-1"), config.getGitProgressMode());
index 0905f562039b1732a87ebf772ab50336cb601595..30c7d9687d2ffaff71a4ecaff0b97a7ca9d7cebb 100644 (file)
@@ -24,11 +24,13 @@ import jetbrains.buildServer.buildTriggers.vcs.git.AuthenticationMethod;
 import jetbrains.buildServer.buildTriggers.vcs.git.Constants;
 import jetbrains.buildServer.buildTriggers.vcs.git.agent.*;
 import jetbrains.buildServer.util.FileUtil;
+import jetbrains.buildServer.util.TestFor;
 import jetbrains.buildServer.vcs.CheckoutRules;
 import jetbrains.buildServer.vcs.VcsException;
 import jetbrains.buildServer.vcs.VcsRoot;
 import org.jetbrains.annotations.NotNull;
 import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.DataProvider;
 import org.testng.annotations.Test;
 
 import java.io.File;
@@ -62,14 +64,14 @@ public class AutoCheckoutTest extends BaseRemoteRepositoryTest {
 
     VcsRoot vcsRoot = vcsRootWithAgentGitPath("git");
 
-    verifyCanCheckout(vcsRoot, CheckoutRules.DEFAULT, runningBuild().build());
+    verifyCanCheckout(vcsRoot, CheckoutRules.DEFAULT, runningBuild().addRoot(vcsRoot).build());
   }
 
   public void client_found_by_path_from_environment() throws IOException, VcsException {
     myVcsSupport = vcsSupportWithRealGit();
 
     VcsRoot vcsRoot = vcsRootWithAgentGitPath(null);
-    AgentRunningBuild build = runningBuild().sharedEnvVariable(Constants.TEAMCITY_AGENT_GIT_PATH, "git").build();
+    AgentRunningBuild build = runningBuild().sharedEnvVariable(Constants.TEAMCITY_AGENT_GIT_PATH, "git").addRoot(vcsRoot).build();
 
     verifyCanCheckout(vcsRoot, CheckoutRules.DEFAULT, build);
   }
@@ -79,7 +81,7 @@ public class AutoCheckoutTest extends BaseRemoteRepositoryTest {
 
     VcsRoot vcsRoot =  vcsRootWithAgentGitPath("gitt");
 
-    AgentCheckoutAbility canCheckout = myVcsSupport.canCheckout(vcsRoot, CheckoutRules.DEFAULT, runningBuild().build());
+    AgentCheckoutAbility canCheckout = myVcsSupport.canCheckout(vcsRoot, CheckoutRules.DEFAULT, runningBuild().addRoot(vcsRoot).build());
     then(canCheckout.getCanNotCheckoutReason().getType()).isEqualTo(AgentCanNotCheckoutReason.NO_VCS_CLIENT);
     then(canCheckout.getCanNotCheckoutReason().getDetails()).contains("Unable to run git at path gitt");
   }
@@ -88,7 +90,8 @@ public class AutoCheckoutTest extends BaseRemoteRepositoryTest {
     myVcsSupport = vcsSupportWithFakeGitOfVersion(GIT_WITH_SPARSE_CHECKOUT);
 
     VcsRoot vcsRoot = vcsRootWithAgentGitPath("git");
-    AgentRunningBuild build = runningBuild().sharedConfigParams(PluginConfigImpl.USE_SPARSE_CHECKOUT, "false").build();
+    AgentRunningBuild build = runningBuild().sharedConfigParams(PluginConfigImpl.USE_SPARSE_CHECKOUT, "false")
+      .addRootEntry(vcsRoot, "-:dir/q.txt").build();
 
     AgentCheckoutAbility canCheckout = myVcsSupport.canCheckout(vcsRoot, new CheckoutRules("-:dir/q.txt"), build);
     then(canCheckout.getCanNotCheckoutReason().getType()).isEqualTo(AgentCanNotCheckoutReason.NOT_SUPPORTED_CHECKOUT_RULES);
@@ -99,7 +102,8 @@ public class AutoCheckoutTest extends BaseRemoteRepositoryTest {
     myVcsSupport =  vcsSupportWithFakeGitOfVersion(GIT_WITH_SPARSE_CHECKOUT);
 
     VcsRoot vcsRoot = vcsRootWithAgentGitPath("git");
-    AgentRunningBuild build = runningBuild().sharedConfigParams(PluginConfigImpl.USE_SPARSE_CHECKOUT, "false").build();
+    AgentRunningBuild build = runningBuild().sharedConfigParams(PluginConfigImpl.USE_SPARSE_CHECKOUT, "false")
+      .addRootEntry(vcsRoot, "+:a/b/c => d").build();
 
     AgentCheckoutAbility canCheckout = myVcsSupport.canCheckout(vcsRoot, new CheckoutRules("+:a/b/c => d"), build);
     then(canCheckout.getCanNotCheckoutReason().getType()).isEqualTo(AgentCanNotCheckoutReason.NOT_SUPPORTED_CHECKOUT_RULES);
@@ -110,7 +114,8 @@ public class AutoCheckoutTest extends BaseRemoteRepositoryTest {
     myVcsSupport =  vcsSupportWithFakeGitOfVersion(GIT_WITH_SPARSE_CHECKOUT.previousVersion());
 
     VcsRoot vcsRoot = vcsRootWithAgentGitPath("git");
-    AgentRunningBuild build = runningBuild().sharedConfigParams(PluginConfigImpl.USE_SPARSE_CHECKOUT, "true").build();
+    AgentRunningBuild build = runningBuild().sharedConfigParams(PluginConfigImpl.USE_SPARSE_CHECKOUT, "true")
+      .addRootEntry(vcsRoot, "-:dir/q.txt").build();
 
     AgentCheckoutAbility canCheckout = myVcsSupport.canCheckout(vcsRoot, new CheckoutRules("-:dir/q.txt"), build);
     then(canCheckout.getCanNotCheckoutReason().getType()).isEqualTo(AgentCanNotCheckoutReason.NOT_SUPPORTED_CHECKOUT_RULES);
@@ -125,12 +130,66 @@ public class AutoCheckoutTest extends BaseRemoteRepositoryTest {
       .withAuthMethod(AuthenticationMethod.PRIVATE_KEY_FILE)
       .build();
 
-    AgentCheckoutAbility canCheckout = myVcsSupport.canCheckout(vcsRoot, CheckoutRules.DEFAULT, runningBuild().build());
+    AgentCheckoutAbility canCheckout = myVcsSupport.canCheckout(vcsRoot, CheckoutRules.DEFAULT, runningBuild().addRoot(vcsRoot).build());
     then(canCheckout.getCanNotCheckoutReason().getType()).isEqualTo(AgentCanNotCheckoutReason.UNKNOWN_REASON_TYPE);
     then(canCheckout.getCanNotCheckoutReason().getDetails()).contains(
       "TeamCity doesn't support authentication method 'Private Key' with agent checkout. Please use different authentication method.");
   }
 
+  @DataProvider
+  public static Object[][] severalRootsSetups() throws Exception {
+    return new Object[][]{
+      new Object[]{new Setup()
+        .setShouldFail(true)
+      },
+      new Object[]{new Setup()
+        .setCheckoutRules1("+:dir")
+        .setCheckoutRules2("+:dir")
+        .setShouldFail(true)
+      },
+      new Object[]{new Setup()
+        .setCheckoutRules2("+:dir2")
+        .setShouldFail(true)
+      },
+      new Object[]{new Setup()
+        .setCheckoutRules2("+:dir1") //even though we checkout different dirs, .git of both repositories is located in the same dir
+        .setCheckoutRules2("+:dir2")
+        .setShouldFail(true)
+      },
+      new Object[]{new Setup()
+        .setCheckoutRules1("+:.=>dir1")
+        .setCheckoutRules2("+:.=>dir2")
+        .setShouldFail(false)
+      }
+    };
+  }
+
+
+  @TestFor(issues = "TW-49786")
+  @Test(dataProvider = "severalRootsSetups")
+  private void several_roots(@NotNull Setup setup) throws Exception {
+    myVcsSupport = vcsSupportWithRealGit();
+
+    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, setup.getCheckoutRules1())
+      .addRootEntry(root2, setup.getCheckoutRules2())
+      .build();
+    AgentCheckoutAbility canCheckout1 = myVcsSupport.canCheckout(root1, new CheckoutRules(setup.getCheckoutRules1()), build);
+    AgentCheckoutAbility canCheckout2 = myVcsSupport.canCheckout(root2, new CheckoutRules(setup.getCheckoutRules2()), build);
+    if (setup.isShouldFail()) {
+      then(canCheckout1.getCanNotCheckoutReason().getDetails()).contains(
+        "Cannot checkout VCS root '" + root1.getName() + "' into the same directory as VCS root '" + root2.getName() + "'");
+      then(canCheckout2.getCanNotCheckoutReason().getDetails()).contains(
+        "Cannot checkout VCS root '" + root2.getName() + "' into the same directory as VCS root '" + root1.getName() + "'");
+    } else {
+      then(canCheckout1.getCanNotCheckoutReason()).isNull();
+      then(canCheckout2.getCanNotCheckoutReason()).isNull();
+    }
+  }
+
+
   private void verifyCanCheckout(final VcsRoot vcsRoot, CheckoutRules checkoutRules, final AgentRunningBuild build) throws VcsException {
     AgentCheckoutAbility canCheckout = myVcsSupport.canCheckout(vcsRoot, checkoutRules, build);
     then(canCheckout.getCanNotCheckoutReason()).isNull();
@@ -167,4 +226,48 @@ public class AutoCheckoutTest extends BaseRemoteRepositoryTest {
   private GitAgentVcsSupport createVcsSupport(final GitDetector detector) throws IOException {
     return new AgentSupportBuilder(myTempFiles).setGitDetector(detector).build();
   }
+
+
+  private static class Setup {
+    private String myCheckoutRules1 = CheckoutRules.DEFAULT.getAsString();
+    private String myCheckoutRules2 = CheckoutRules.DEFAULT.getAsString();
+    private boolean myShouldFail;
+
+    @NotNull
+    public String getCheckoutRules1() {
+      return myCheckoutRules1;
+    }
+
+    @NotNull
+    public Setup setCheckoutRules1(@NotNull String checkoutRules1) {
+      myCheckoutRules1 = checkoutRules1;
+      return this;
+    }
+
+    @NotNull
+    public String getCheckoutRules2() {
+      return myCheckoutRules2;
+    }
+
+    @NotNull
+    public Setup setCheckoutRules2(@NotNull String checkoutRules2) {
+      myCheckoutRules2 = checkoutRules2;
+      return this;
+    }
+
+    public boolean isShouldFail() {
+      return myShouldFail;
+    }
+
+    @NotNull
+    public Setup setShouldFail(boolean shouldFail) {
+      myShouldFail = shouldFail;
+      return this;
+    }
+
+    @Override
+    public String toString() {
+      return "rules1: '" + myCheckoutRules1 + "', rules2: '" + myCheckoutRules2 + "'";
+    }
+  }
 }
index cf24a2eaee35cbdac869d7c60283c547338ef0e6..c5a4adfbcbc1357a91175b380d977699277e1d39 100644 (file)
@@ -25,6 +25,7 @@ import jetbrains.buildServer.parameters.ValueResolver;
 import jetbrains.buildServer.util.FileUtil;
 import jetbrains.buildServer.util.Option;
 import jetbrains.buildServer.util.PasswordReplacer;
+import jetbrains.buildServer.vcs.CheckoutRules;
 import jetbrains.buildServer.vcs.VcsChangeInfo;
 import jetbrains.buildServer.vcs.VcsRoot;
 import jetbrains.buildServer.vcs.VcsRootEntry;
@@ -32,10 +33,7 @@ import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 
 import java.io.File;
-import java.util.Collection;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
+import java.util.*;
 
 import static jetbrains.buildServer.util.Util.map;
 
@@ -43,6 +41,7 @@ public class AgentRunningBuildBuilder {
 
   private Map<String, String> mySharedConfigParameters = new HashMap<String, String>();
   private Map<String, String> mySharedBuildParameters = new HashMap<String, String>();
+  private List<VcsRootEntry> myRootEntries = null;
 
   public static AgentRunningBuildBuilder runningBuild() {
     return new AgentRunningBuildBuilder();
@@ -73,6 +72,23 @@ public class AgentRunningBuildBuilder {
   }
 
 
+  public AgentRunningBuildBuilder addRootEntry(@NotNull VcsRoot root, @NotNull String rules) {
+    if (myRootEntries == null) {
+      myRootEntries = new ArrayList<>();
+    }
+    myRootEntries.add(new VcsRootEntry(root, new CheckoutRules(rules)));
+    return this;
+  }
+
+  public AgentRunningBuildBuilder addRoot(@NotNull VcsRoot root) {
+    if (myRootEntries == null) {
+      myRootEntries = new ArrayList<>();
+    }
+    myRootEntries.add(new VcsRootEntry(root, CheckoutRules.DEFAULT));
+    return this;
+  }
+
+
   public AgentRunningBuild build() {
     return new AgentRunningBuild() {
       @NotNull
@@ -271,7 +287,9 @@ public class AgentRunningBuildBuilder {
 
       @NotNull
       public List<VcsRootEntry> getVcsRootEntries() {
-        throw new UnsupportedOperationException();
+        if (myRootEntries == null)
+          throw new UnsupportedOperationException();
+        return myRootEntries;
       }
 
       public String getBuildCurrentVersion(final VcsRoot vcsRoot) {