TW-48103 exclude username from http urls in git configs
authorDmitry Neverov <dmitry.neverov@gmail.com>
Fri, 21 Apr 2017 10:20:44 +0000 (12:20 +0200)
committerDmitry Neverov <dmitry.neverov@gmail.com>
Fri, 21 Apr 2017 10:22:53 +0000 (12:22 +0200)
The mirror repository location is still computed using URL with username
in order to avoid re-clones.

git-agent/src/jetbrains/buildServer/buildTriggers/vcs/git/agent/AgentPluginConfig.java
git-agent/src/jetbrains/buildServer/buildTriggers/vcs/git/agent/PluginConfigImpl.java
git-agent/src/jetbrains/buildServer/buildTriggers/vcs/git/agent/RemoteRepositoryConfigurator.java [new file with mode: 0644]
git-agent/src/jetbrains/buildServer/buildTriggers/vcs/git/agent/UpdaterImpl.java
git-agent/src/jetbrains/buildServer/buildTriggers/vcs/git/agent/UpdaterWithMirror.java
git-tests/src/jetbrains/buildServer/buildTriggers/vcs/git/tests/HttpUrlWithUsernameTest.java [new file with mode: 0644]
git-tests/src/jetbrains/buildServer/buildTriggers/vcs/git/tests/RemoteRepositoryConfiguratorTest.java [new file with mode: 0644]
git-tests/src/native-git-testng.xml

index 94ab07cd69047068f6b4d56adc4a7e3421bd0a89..0c766cc3c0de5a4054df8f10511ea3859a396462 100644 (file)
@@ -62,6 +62,8 @@ public interface AgentPluginConfig extends PluginConfig {
   @NotNull
   GitProgressMode getGitProgressMode();
 
+  boolean isExcludeUsernameFromHttpUrl();
+
   /**
    * Defines how progress output from git commands is written into build log
    */
index 362563f8877ee4dd36e2aa56613e1547a527ce37..2149bf7a50e403c5e1f52a25e834134f922cf26c 100644 (file)
@@ -48,6 +48,7 @@ public class PluginConfigImpl implements AgentPluginConfig {
   public static final String USE_BUILD_ENV = "teamcity.git.useBuildEnv";
   public static final String FETCH_ALL_HEADS = "teamcity.git.fetchAllHeads";
   public static final String FETCH_TAGS = "teamcity.git.fetchTags";
+  public static final String EXCLUDE_USERNAME_FROM_HTTP_URL = "teamcity.git.excludeUsernameFromHttpUrl";
 
   private final BuildAgentConfiguration myAgentConfig;
   private final AgentRunningBuild myBuild;
@@ -240,6 +241,12 @@ public class PluginConfigImpl implements AgentPluginConfig {
     }
   }
 
+  @Override
+  public boolean isExcludeUsernameFromHttpUrl() {
+    String value = myBuild.getSharedConfigParameters().get(EXCLUDE_USERNAME_FROM_HTTP_URL);
+    return !"false".equals(value);
+  }
+
   private int parseTimeout(String valueFromBuild) {
     return parseTimeout(valueFromBuild, DEFAULT_IDLE_TIMEOUT);
   }
diff --git a/git-agent/src/jetbrains/buildServer/buildTriggers/vcs/git/agent/RemoteRepositoryConfigurator.java b/git-agent/src/jetbrains/buildServer/buildTriggers/vcs/git/agent/RemoteRepositoryConfigurator.java
new file mode 100644 (file)
index 0000000..f28f4dd
--- /dev/null
@@ -0,0 +1,94 @@
+/*
+ * Copyright 2000-2017 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.git.agent;
+
+import jetbrains.buildServer.buildTriggers.vcs.git.GitVcsRoot;
+import jetbrains.buildServer.util.StringUtil;
+import jetbrains.buildServer.vcs.VcsException;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.lib.RepositoryBuilder;
+import org.eclipse.jgit.lib.StoredConfig;
+import org.eclipse.jgit.transport.URIish;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.io.File;
+
+/**
+ * Configures remote repository
+ */
+public class RemoteRepositoryConfigurator {
+
+  private boolean myExcludeUsernameFromHttpUrls;
+  private File myGitDir;
+
+
+  public void setExcludeUsernameFromHttpUrls(boolean excludeUsernameFromHttpUrls) {
+    myExcludeUsernameFromHttpUrls = excludeUsernameFromHttpUrls;
+  }
+
+
+  public void setGitDir(@NotNull File gitDir) {
+    myGitDir = gitDir;
+  }
+
+
+  /**
+   * Configures and save the remote repository for specified VCS root
+   * @param root VCS root of interest
+   * @throws VcsException in case of any error
+   */
+  public void configure(@NotNull GitVcsRoot root) throws VcsException {
+    File gitDir = getGitDir();
+    Repository repository = null;
+    try {
+      repository = new RepositoryBuilder().setGitDir(gitDir).build();
+      StoredConfig config = repository.getConfig();
+      URIish fetchUrl = root.getRepositoryFetchURL();
+      String scheme = fetchUrl.getScheme();
+      String user = fetchUrl.getUser();
+      if (myExcludeUsernameFromHttpUrls && isHttp(scheme) && !StringUtil.isEmpty(user)) {
+        URIish fetchUrlNoUser = fetchUrl.setUser(null);
+        config.setString("remote", "origin", "url", fetchUrlNoUser.toString());
+        config.setString("credential", null, "username", user);
+      } else {
+        config.setString("remote", "origin", "url", fetchUrl.toString());
+        config.unset("credential", null, "username");
+      }
+      config.setString("remote", "origin", "fetch", "+refs/heads/*:refs/remotes/origin/*");
+      config.save();
+    } catch (Exception e) {
+      throw new VcsException("Error while configuring remote repository at " + gitDir, e);
+    } finally {
+      if (repository != null)
+        repository.close();
+    }
+  }
+
+
+  private boolean isHttp(@Nullable String scheme) {
+    return "http".equals(scheme) || "https".equals(scheme);
+  }
+
+
+  @NotNull
+  private File getGitDir() {
+    if (myGitDir != null)
+      return myGitDir;
+    throw new IllegalStateException("Git directory is not specified");
+  }
+}
index 2ecbec3dda3116d7be100e32eb09498ed01c2e4f..6ee4e9da6107dd10695e5cde230edae2a803f8a4 100644 (file)
@@ -65,6 +65,9 @@ public class UpdaterImpl implements Updater {
    * Git version supporting an empty credential helper - the only way to disable system/global/local cred helper
    */
   public final static GitVersion EMPTY_CRED_HELPER = new GitVersion(2, 9, 0);
+  /** Git version supporting [credential] section in config (the first version including a6fc9fd3f4b42cd97b5262026e18bd451c28ee3c) */
+  public final static GitVersion CREDENTIALS_SECTION_VERSION = new GitVersion(1, 7, 10);
+
   private static final int SILENT_TIMEOUT = 24 * 60 * 60; //24 hours
 
   protected final FS myFS;
@@ -144,34 +147,20 @@ public class UpdaterImpl implements Updater {
   }
 
 
-  /**
-   * Init .git in the target dir
-   * @return true if there was no fetch in the target dir before
-   * @throws VcsException in teh case of any problems
-   */
-  private boolean initGitRepository() throws VcsException {
-    boolean firstFetch = false;
+  private void initGitRepository() throws VcsException {
     if (!new File(myTargetDirectory, ".git").exists()) {
       initDirectory();
-      firstFetch = true;
     } else {
-      String remoteUrl = getRemoteUrl();
-      if (!remoteUrl.equals(myRoot.getRepositoryFetchURL().toString())) {
+      try {
+        configureRemoteUrl(new File(myTargetDirectory, ".git"));
+        setupExistingRepository();
+        configureSparseCheckout();
+      } catch (Exception e) {
+        LOG.warn("Do clean checkout due to errors while configure use of local mirrors", e);
         initDirectory();
-        firstFetch = true;
-      } else {
-        try {
-          setupExistingRepository();
-          configureSparseCheckout();
-        } catch (Exception e) {
-          LOG.warn("Do clean checkout due to errors while configure use of local mirrors", e);
-          initDirectory();
-          firstFetch = true;
-        }
       }
     }
     removeOrphanedIdxFiles(new File(myTargetDirectory, ".git"));
-    return firstFetch;
   }
 
   protected void setupNewRepository() throws VcsException {
@@ -731,7 +720,7 @@ public class UpdaterImpl implements Updater {
    *
    * @throws VcsException if there are problems with initializing the directory
    */
-  void initDirectory() throws VcsException {
+  private void initDirectory() throws VcsException {
     BuildDirectoryCleanerCallback c = new BuildDirectoryCleanerCallback(myLogger, LOG);
     myDirectoryCleaner.cleanFolder(myTargetDirectory, c);
     //noinspection ResultOfMethodCallIgnored
@@ -742,20 +731,27 @@ public class UpdaterImpl implements Updater {
     myLogger.message("The .git directory is missing in '" + myTargetDirectory + "'. Running 'git init'...");
     myGitFactory.create(myTargetDirectory).init().call();
     validateUrls();
-    myGitFactory.create(myRoot.getLocalRepositoryDir())
-      .addRemote()
-      .setName("origin")
-      .setUrl(myRoot.getRepositoryFetchURL().toString())
-      .call();
+    configureRemoteUrl(new File(myTargetDirectory, ".git"));
+
+    URIish fetchUrl = myRoot.getRepositoryFetchURL();
     URIish url = myRoot.getRepositoryPushURL();
     String pushUrl = url == null ? null : url.toString();
-    if (pushUrl != null && !pushUrl.equals(myRoot.getRepositoryFetchURL().toString())) {
+    if (pushUrl != null && !pushUrl.equals(fetchUrl.toString())) {
       myGitFactory.create(myTargetDirectory).setConfig().setPropertyName("remote.origin.pushurl").setValue(pushUrl).call();
     }
     setupNewRepository();
     configureSparseCheckout();
   }
 
+
+  void configureRemoteUrl(@NotNull File gitDir) throws VcsException {
+    RemoteRepositoryConfigurator cfg = new RemoteRepositoryConfigurator();
+    cfg.setGitDir(gitDir);
+    cfg.setExcludeUsernameFromHttpUrls(myPluginConfig.isExcludeUsernameFromHttpUrl() && !myPluginConfig.getGitVersion().isLessThan(UpdaterImpl.CREDENTIALS_SECTION_VERSION));
+    cfg.configure(myRoot);
+  }
+
+
   private void configureSparseCheckout() throws VcsException {
     if (myCheckoutMode == CheckoutMode.SPARSE_CHECKOUT) {
       setupSparseCheckout();
index 936992e79f1267ce2ecbc947cd0bb21093af4947..137f5ec36d77b788239f20999fc57961e8c6c8f7 100644 (file)
@@ -27,6 +27,7 @@ import jetbrains.buildServer.vcs.VcsException;
 import jetbrains.buildServer.vcs.VcsRoot;
 import org.apache.log4j.Logger;
 import org.eclipse.jgit.lib.Ref;
+import org.eclipse.jgit.lib.Repository;
 import org.eclipse.jgit.lib.RepositoryBuilder;
 import org.eclipse.jgit.transport.URIish;
 import org.jetbrains.annotations.NotNull;
@@ -91,9 +92,10 @@ public class UpdaterWithMirror extends UpdaterImpl {
       bareRepositoryDir.mkdirs();
       GitFacade git = myGitFactory.create(bareRepositoryDir);
       git.init().setBare(true).call();
-      git.addRemote().setName("origin").setUrl(myRoot.getRepositoryFetchURL().toString()).call();
+      configureRemoteUrl(bareRepositoryDir);
       newMirror = true;
     } else {
+      configureRemoteUrl(bareRepositoryDir);
       boolean outdatedTagsFound = removeOutdatedRefs(bareRepositoryDir);
       if (!outdatedTagsFound) {
         LOG.debug("Try to find revision " + myRevision + " in " + mirrorDescription);
@@ -147,7 +149,7 @@ public class UpdaterWithMirror extends UpdaterImpl {
       if (cleanDir(repositoryDir)) {
         GitFacade git = myGitFactory.create(repositoryDir);
         git.init().setBare(true).call();
-        git.addRemote().setName("origin").setUrl(myRoot.getRepositoryFetchURL().toString()).call();
+        configureRemoteUrl(repositoryDir);
         fetch(repositoryDir, refspec, shallowClone);
       } else {
         LOG.info("Failed to delete repository " + repositoryDir + " after failed checkout, clone repository in another directory");
@@ -220,8 +222,26 @@ public class UpdaterWithMirror extends UpdaterImpl {
     }
   }
 
+
+  @NotNull
+  private String readRemoteUrl() throws VcsException {
+    Repository repository = null;
+    try {
+      repository = new RepositoryBuilder().setWorkTree(myTargetDirectory).build();
+      return repository.getConfig().getString("remote", "origin", "url");
+    } catch (IOException e) {
+      throw new VcsException("Error while reading remote repository url", e);
+    } finally {
+      if (repository != null)
+        repository.close();
+    }
+  }
+
+
   private void setUseLocalMirror() throws VcsException {
-    String remoteUrl = myRoot.getRepositoryFetchURL().toString();
+    //read remote url from config instead of VCS root, they can be different
+    //e.g. due to username exclusion from http(s) urls
+    String remoteUrl = readRemoteUrl();
     String localMirrorUrl = getLocalMirrorUrl();
     GitFacade git = myGitFactory.create(myTargetDirectory);
     git.setConfig()
diff --git a/git-tests/src/jetbrains/buildServer/buildTriggers/vcs/git/tests/HttpUrlWithUsernameTest.java b/git-tests/src/jetbrains/buildServer/buildTriggers/vcs/git/tests/HttpUrlWithUsernameTest.java
new file mode 100644 (file)
index 0000000..b3dc47e
--- /dev/null
@@ -0,0 +1,238 @@
+/*
+ * Copyright 2000-2017 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.git.tests;
+
+import jetbrains.buildServer.agent.AgentRunningBuild;
+import jetbrains.buildServer.buildTriggers.vcs.git.AuthenticationMethod;
+import jetbrains.buildServer.buildTriggers.vcs.git.Constants;
+import jetbrains.buildServer.buildTriggers.vcs.git.GitVcsRoot;
+import jetbrains.buildServer.buildTriggers.vcs.git.MirrorManagerImpl;
+import jetbrains.buildServer.buildTriggers.vcs.git.agent.GitAgentVcsSupport;
+import jetbrains.buildServer.buildTriggers.vcs.git.agent.PluginConfigImpl;
+import jetbrains.buildServer.util.FileUtil;
+import jetbrains.buildServer.util.TestFor;
+import jetbrains.buildServer.vcs.CheckoutRules;
+import jetbrains.buildServer.vcs.VcsException;
+import jetbrains.buildServer.vcs.impl.VcsRootImpl;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.lib.RepositoryBuilder;
+import org.eclipse.jgit.lib.StoredConfig;
+import org.jetbrains.annotations.NotNull;
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+
+import static jetbrains.buildServer.buildTriggers.vcs.git.tests.GitTestUtil.copyRepository;
+import static jetbrains.buildServer.buildTriggers.vcs.git.tests.GitTestUtil.dataFile;
+import static jetbrains.buildServer.buildTriggers.vcs.git.tests.VcsRootBuilder.vcsRoot;
+import static jetbrains.buildServer.buildTriggers.vcs.git.tests.builders.AgentRunningBuildBuilder.runningBuild;
+import static jetbrains.buildServer.util.Util.map;
+import static org.assertj.core.api.BDDAssertions.then;
+
+@SuppressWarnings("ALL")
+@Test
+@TestFor(issues = "TW-48103")
+public class HttpUrlWithUsernameTest extends BaseRemoteRepositoryTest {
+
+  private final static String USER = "user";
+  private final static String PASSWORD = "pwd";
+
+  private AgentSupportBuilder myBuilder;
+  private GitAgentVcsSupport myVcsSupport;
+  private MirrorManagerImpl myMirrorManager;
+  private GitHttpServer myServer;
+  private String myGitPath;
+  private File myBuildDir;
+
+  @Override
+  @BeforeMethod
+  public void setUp() throws Exception {
+    super.setUp();
+
+    myBuilder = new AgentSupportBuilder(myTempFiles);
+    myVcsSupport = myBuilder.build();
+    myMirrorManager = myBuilder.getMirrorManager();
+    myGitPath = GitVersionProvider.getGitPath();
+    myBuildDir = myTempFiles.createTempDir();
+  }
+
+
+  @Override
+  @AfterMethod
+  public void tearDown() {
+    super.tearDown();
+    if (myServer != null)
+      myServer.stop();
+  }
+
+
+  @Test(dataProvider = "mirrorModes")
+  public void should_include_username_into_url_when_asked(@NotNull MirrorMode mirrorMode) throws Exception {
+    //need that in order to be able to disable new logic in case of any problems
+
+    File repo = copyRepository(myTempFiles, dataFile("repo_for_fetch.1"), "repo.git");
+    startGitServer(repo);
+    VcsRootImpl root = createRoot();
+    AgentRunningBuild build = createBuild(configParams(mirrorMode, PluginConfigImpl.EXCLUDE_USERNAME_FROM_HTTP_URL, "false"));
+    checkout(root, build, "add81050184d3c818560bdd8839f50024c188586");
+
+    if (mirrorMode != MirrorMode.DISABLED) {
+      StoredConfig config = getMirrorConfig(root);
+      then(config.getString("remote", "origin", "url")).contains(USER + "@");
+    }
+
+    StoredConfig config = getWorkingDirConfig();
+    then(config.getString("remote", "origin", "url")).contains(USER + "@");
+  }
+
+
+  @Test(dataProvider = "mirrorModes")
+  public void no_username_in_http_urls(@NotNull MirrorMode mirrorMode) throws Exception {
+    File repo = copyRepository(myTempFiles, dataFile("repo_for_fetch.1"), "repo.git");
+    startGitServer(repo);
+    VcsRootImpl root = createRoot();
+    AgentRunningBuild build = createBuild(configParams(mirrorMode));
+    checkout(root, build, "add81050184d3c818560bdd8839f50024c188586");
+
+    if (mirrorMode != MirrorMode.DISABLED) {
+      StoredConfig config = getMirrorConfig(root);
+      then(config.getString("remote", "origin", "url")).doesNotContain(USER + "@");
+      then(config.getString("credential", null, "username")).isEqualTo(USER);
+    }
+
+    StoredConfig config = getWorkingDirConfig();
+    then(config.getString("remote", "origin", "url")).doesNotContain(USER + "@");
+    then(config.getString("credential", null, "username")).isEqualTo(USER);
+  }
+
+
+  @Test(dataProvider = "mirrorModes")
+  public void no_username_in_http_urls_upgrade(@NotNull MirrorMode mirrorMode) throws Exception {
+    //run first build to initialize fetch urls with usernames
+    File repo = copyRepository(myTempFiles, dataFile("repo_for_fetch.1"), "repo.git");
+    startGitServer(repo);
+    VcsRootImpl root = createRoot();
+    AgentRunningBuild build1 = createBuild(configParams(mirrorMode, PluginConfigImpl.EXCLUDE_USERNAME_FROM_HTTP_URL, "false"));
+    checkout(root, build1, "add81050184d3c818560bdd8839f50024c188586");
+
+    //update remote repo to cause fetch
+    FileUtil.delete(repo);
+    copyRepository(dataFile("repo_for_fetch.2"), repo);
+
+    AgentRunningBuild build = createBuild(configParams(mirrorMode));
+    checkout(root, build, "d47dda159b27b9a8c4cee4ce98e4435eb5b17168");
+
+    if (mirrorMode != MirrorMode.DISABLED) {
+      StoredConfig config = getMirrorConfig(root);
+      then(config.getString("remote", "origin", "url")).doesNotContain(USER + "@");
+      then(config.getString("credential", null, "username")).isEqualTo(USER);
+    }
+
+    StoredConfig config = getWorkingDirConfig();
+    then(config.getString("remote", "origin", "url")).doesNotContain(USER + "@");
+    then(config.getString("credential", null, "username")).isEqualTo(USER);
+  }
+
+
+  private void checkout(@NotNull VcsRootImpl root, @NotNull AgentRunningBuild build, @NotNull String revision) throws VcsException {
+    myVcsSupport.updateSources(root, CheckoutRules.DEFAULT, revision, myBuildDir, build, false);
+  }
+
+
+  @NotNull
+  private StoredConfig getWorkingDirConfig() throws IOException {
+    Repository r = new RepositoryBuilder().setWorkTree(myBuildDir).build();
+    return r.getConfig();
+  }
+
+
+  @NotNull
+  private StoredConfig getMirrorConfig(@NotNull VcsRootImpl root) throws IOException, VcsException {
+    GitVcsRoot gitRoot = new GitVcsRoot(myMirrorManager, root);
+    File mirrorDir = myMirrorManager.getMirrorDir(gitRoot.getRepositoryFetchURL().toString());
+    Repository r = new RepositoryBuilder().setGitDir(mirrorDir).build();
+    return r.getConfig();
+  }
+
+
+  @NotNull
+  private AgentRunningBuild createBuild(@NotNull Map<String, String> configParams) {
+    return runningBuild()
+      .sharedEnvVariable(Constants.TEAMCITY_AGENT_GIT_PATH, myGitPath)
+      .sharedConfigParams(configParams)
+      .build();
+  }
+
+
+  @NotNull
+  private VcsRootImpl createRoot() {
+    return vcsRoot()
+      .withFetchUrl(myServer.getRepoUrl())
+      .withAuthMethod(AuthenticationMethod.PASSWORD)
+      .withUsername(USER)
+      .withPassword(PASSWORD)
+      .withBranch("master")
+      .build();
+  }
+
+
+  private void startGitServer(@NotNull File repo) throws IOException {
+    myServer = new GitHttpServer(myGitPath, repo);
+    myServer.setCredentials(USER, PASSWORD);
+    myServer.start();
+  }
+
+
+  @NotNull
+  private Map<String, String> configParams(@NotNull MirrorMode mirrorMode, String... params) {
+    Map<String, String> result = new HashMap<>();
+    switch (mirrorMode) {
+      case MIRROR:
+        result.put(PluginConfigImpl.USE_MIRRORS, "true");
+        break;
+      case ALTERNATES:
+        result.put(PluginConfigImpl.USE_ALTERNATES, "true");
+    }
+    if (params.length != 0)
+      result.putAll(map(params));
+    return result;
+  }
+
+
+  @DataProvider
+  public static Object[][] mirrorModes() throws Exception {
+    Object[][] result = new Object[MirrorMode.values().length][];
+    int i = 0;
+    for (MirrorMode mode : MirrorMode.values()) {
+      result[i] = new Object[]{mode};
+      i++;
+    }
+    return result;
+  }
+
+  private enum MirrorMode {
+    DISABLED,
+    MIRROR,
+    ALTERNATES
+  }
+}
diff --git a/git-tests/src/jetbrains/buildServer/buildTriggers/vcs/git/tests/RemoteRepositoryConfiguratorTest.java b/git-tests/src/jetbrains/buildServer/buildTriggers/vcs/git/tests/RemoteRepositoryConfiguratorTest.java
new file mode 100644 (file)
index 0000000..6c12a02
--- /dev/null
@@ -0,0 +1,334 @@
+/*
+ * Copyright 2000-2017 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.git.tests;
+
+import jetbrains.buildServer.TempFiles;
+import jetbrains.buildServer.TestInternalProperties;
+import jetbrains.buildServer.buildTriggers.vcs.git.AuthenticationMethod;
+import jetbrains.buildServer.buildTriggers.vcs.git.Constants;
+import jetbrains.buildServer.buildTriggers.vcs.git.GitVcsRoot;
+import jetbrains.buildServer.buildTriggers.vcs.git.MirrorManagerImpl;
+import jetbrains.buildServer.buildTriggers.vcs.git.agent.GitAgentVcsSupport;
+import jetbrains.buildServer.buildTriggers.vcs.git.agent.RemoteRepositoryConfigurator;
+import jetbrains.buildServer.log.LogInitializer;
+import jetbrains.buildServer.util.TestFor;
+import jetbrains.buildServer.vcs.VcsException;
+import jetbrains.buildServer.vcs.impl.VcsRootImpl;
+import org.eclipse.jgit.lib.Config;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.lib.RepositoryBuilder;
+import org.eclipse.jgit.lib.StoredConfig;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import org.testng.annotations.*;
+
+import java.io.File;
+import java.io.IOException;
+
+import static jetbrains.buildServer.buildTriggers.vcs.git.tests.VcsRootBuilder.vcsRoot;
+import static org.assertj.core.api.BDDAssertions.then;
+
+@SuppressWarnings("ALL")
+@Test
+@TestFor(issues = "TW-48103")
+public class RemoteRepositoryConfiguratorTest {
+
+  private TempFiles myTempFiles;
+  private MirrorManagerImpl myMirrorManager;
+
+  @BeforeClass
+  public void setUpClass() {
+    LogInitializer.setUnitTest(true);
+  }
+
+  @BeforeMethod
+  public void setUp() throws Exception {
+    TestInternalProperties.init();
+    myTempFiles = new TempFiles();
+    AgentSupportBuilder builder = new AgentSupportBuilder(myTempFiles);
+    GitAgentVcsSupport vcs = builder.build();
+    myMirrorManager = builder.getMirrorManager();
+  }
+
+
+  @AfterMethod
+  public void tearDown() throws Exception {
+    myTempFiles.cleanup();
+  }
+
+
+  @DataProvider
+  public static Object[][] setups() throws Exception {
+    return new Object[][] {
+      //new repository
+      new Object[] {new Setup()
+        .setRoot(vcsRoot().withFetchUrl("git://some.org/repo.git"))
+        .setExpectedUrl("git://some.org/repo.git")
+        .setExpectedCredentialUser(null)
+      },
+      new Object[] {new Setup()
+        .setRoot(vcsRoot().withFetchUrl("git@some.org:repo.git"))
+        .setExpectedUrl("git@some.org:repo.git")
+        .setExpectedCredentialUser(null)
+      },
+      new Object[] {new Setup()
+        .setRoot(vcsRoot().withFetchUrl("ssh://git@some.org/repo.git"))
+        .setExpectedUrl("ssh://git@some.org/repo.git")
+        .setExpectedCredentialUser(null)
+      },
+      new Object[] {new Setup()
+        .setRoot(vcsRoot().withFetchUrl("http://some.org/repo.git"))
+        .setExpectedUrl("http://some.org/repo.git")
+        .setExpectedCredentialUser(null)
+      },
+      new Object[] {new Setup()
+        .setRoot(vcsRoot().withFetchUrl("https://some.org/repo.git"))
+        .setExpectedUrl("https://some.org/repo.git")
+        .setExpectedCredentialUser(null)
+      },
+      new Object[] {new Setup()
+        .setRoot(vcsRoot().withFetchUrl("http://some.org/repo.git").withAuthMethod(AuthenticationMethod.PASSWORD).withUsername("user").withPassword("pwd"))
+        .setExpectedUrl("http://some.org/repo.git")
+        .setExpectedCredentialUser("user")
+      },
+      new Object[] {new Setup()
+        .setRoot(vcsRoot().withFetchUrl("https://user@some.org/repo.git").withAuthMethod(AuthenticationMethod.PASSWORD).withPassword("pwd"))
+        .setExpectedUrl("https://some.org/repo.git")
+        .setExpectedCredentialUser("user")
+      },
+
+      //existing repository
+      new Object[] {new Setup()
+        .setRoot(vcsRoot().withFetchUrl("git://some.org/repo.git"))
+        .setExpectedUrl("git://some.org/repo.git")
+        .setExpectedCredentialUser(null)
+        .setConfigAction("prepare existing repository", config -> {
+          config.setString("remote", "origin", "url", "someUrl");
+          config.setString("credential", null, "username", "someUser");
+        })
+      },
+      new Object[] {new Setup()
+        .setRoot(vcsRoot().withFetchUrl("git@some.org:repo.git"))
+        .setExpectedUrl("git@some.org:repo.git")
+        .setExpectedCredentialUser(null)
+        .setConfigAction("prepare existing repository", config -> {
+          config.setString("remote", "origin", "url", "someUrl");
+          config.setString("credential", null, "username", "someUser");
+        })
+      },
+      new Object[] {new Setup()
+        .setRoot(vcsRoot().withFetchUrl("ssh://git@some.org/repo.git"))
+        .setExpectedUrl("ssh://git@some.org/repo.git")
+        .setExpectedCredentialUser(null)
+        .setConfigAction("prepare existing repository", config -> {
+          config.setString("remote", "origin", "url", "someUrl");
+          config.setString("credential", null, "username", "someUser");
+        })
+      },
+      new Object[] {new Setup()
+        .setRoot(vcsRoot().withFetchUrl("http://some.org/repo.git"))
+        .setExpectedUrl("http://some.org/repo.git")
+        .setExpectedCredentialUser(null)
+        .setConfigAction("prepare existing repository", config -> {
+          config.setString("remote", "origin", "url", "someUrl");
+          config.setString("credential", null, "username", "someUser");
+        })
+      },
+      new Object[] {new Setup()
+        .setRoot(vcsRoot().withFetchUrl("http://some.org/repo.git").withAuthMethod(AuthenticationMethod.PASSWORD).withUsername("user").withPassword("pwd"))
+        .setExpectedUrl("http://some.org/repo.git")
+        .setExpectedCredentialUser("user")
+        .setConfigAction("prepare existing repository", config -> {
+          config.setString("remote", "origin", "url", "someUrl");
+          config.setString("credential", null, "username", "someUser");
+        })
+      },
+      new Object[] {new Setup()
+        .setRoot(vcsRoot().withFetchUrl("https://user@some.org/repo.git").withAuthMethod(AuthenticationMethod.PASSWORD).withPassword("pwd"))
+        .setExpectedUrl("https://some.org/repo.git")
+        .setExpectedCredentialUser("user")
+        .setConfigAction("prepare existing repository", config -> {
+          config.setString("remote", "origin", "url", "someUrl");
+          config.setString("credential", null, "username", "someUser");
+        })
+      },
+      new Object[] {new Setup()
+        .setRoot(vcsRoot().withFetchUrl("https://some.org/repo.git").withAuthMethod(AuthenticationMethod.PASSWORD).withUsername("user").withPassword("pwd"))
+        .setExpectedUrl("https://some.org/repo.git")
+        .setExpectedCredentialUser("user")
+        .setConfigAction("prepare existing repository", config -> {
+          config.setString("remote", "origin", "url", "someUrl");
+          config.setString("credential", null, "username", "someUser");
+        })
+      },
+
+      //disable exclude
+      new Object[] {new Setup()
+        .setRoot(vcsRoot().withFetchUrl("https://some.org/repo.git").withAuthMethod(AuthenticationMethod.PASSWORD).withUsername("user").withPassword("pwd"))
+        .disableUsernameExclude()
+        .setExpectedUrl("https://user@some.org/repo.git")
+        .setExpectedCredentialUser(null)
+      },
+      new Object[] {new Setup()
+        .setRoot(vcsRoot().withFetchUrl("http://some.org/repo.git").withAuthMethod(AuthenticationMethod.PASSWORD).withUsername("user").withPassword("pwd"))
+        .disableUsernameExclude()
+        .setExpectedUrl("http://user@some.org/repo.git")
+        .setExpectedCredentialUser(null)
+        .setConfigAction("prepare existing repository", config -> {
+          config.setString("remote", "origin", "url", "http://some.org/repo.git");
+          config.setString("credential", null, "username", "user");
+        })
+      },
+    };
+  }
+
+
+  @Test(dataProvider = "setups")
+  public void test(@NotNull Setup setup) throws Exception {
+    Repository r = createBareRepository();
+
+    ConfigAction action = setup.getConfigAction();
+    if (action != null) {
+      StoredConfig cfg = r.getConfig();
+      try {
+        action.run(cfg);
+      } finally {
+        cfg.save();
+      }
+    }
+
+    RemoteRepositoryConfigurator configurator = new RemoteRepositoryConfigurator();
+    configurator.setGitDir(r.getDirectory());
+    configurator.setExcludeUsernameFromHttpUrls(setup.isExcludeUsernameFromHttpUrl());
+    configurator.configure(createRoot(setup.getRoot()));
+
+    StoredConfig config = r.getConfig();
+    then(config.getString("remote", "origin", "url")).isEqualTo(setup.getExpectedUrl());
+    then(config.getString("credential", null, "username")).isEqualTo(setup.getExpectedCredentialUser());
+  }
+
+
+  @NotNull
+  private Repository createBareRepository() throws IOException {
+    File mirrorDir = myTempFiles.createTempDir();
+    mirrorDir.mkdirs();
+    Repository result = new RepositoryBuilder().setBare().setGitDir(mirrorDir).build();
+    result.create(true);
+    return result;
+  }
+
+
+  @NotNull
+  private Repository createRepository() throws IOException {
+    File workingDir = myTempFiles.createTempDir();
+    workingDir.mkdirs();
+    Repository result = new RepositoryBuilder().setWorkTree(workingDir).build();
+    result.create();
+    return result;
+  }
+
+
+  @NotNull
+  private GitVcsRoot createRoot(@NotNull VcsRootBuilder root) throws VcsException {
+    return new GitVcsRoot(myMirrorManager, root.build());
+  }
+
+
+  private static class Setup {
+    private VcsRootBuilder myRoot;
+    private String myExpectedUrl;
+    private String myExpectedCredentialUser;
+    private String myConfigActionDescription;
+    private ConfigAction myConfigAction;
+    private boolean myExcludeUsernameFromHttpUrl = true;
+
+    public VcsRootBuilder getRoot() {
+      return myRoot;
+    }
+
+    @NotNull
+    public Setup setRoot(@NotNull VcsRootBuilder root) {
+      myRoot = root;
+      return this;
+    }
+
+    public String getExpectedUrl() {
+      return myExpectedUrl;
+    }
+
+    @NotNull
+    public Setup setExpectedUrl(final String expectedUrl) {
+      myExpectedUrl = expectedUrl;
+      return this;
+    }
+
+    public String getExpectedCredentialUser() {
+      return myExpectedCredentialUser;
+    }
+
+    @NotNull
+    public Setup setExpectedCredentialUser(@Nullable String expectedCredentialUser) {
+      myExpectedCredentialUser = expectedCredentialUser;
+      return this;
+    }
+
+    @NotNull
+    public Setup setConfigAction(@NotNull String description, @NotNull ConfigAction action) {
+      myConfigActionDescription = description;
+      myConfigAction = action;
+      return this;
+    }
+
+    public ConfigAction getConfigAction() {
+      return myConfigAction;
+    }
+
+    public boolean isExcludeUsernameFromHttpUrl() {
+      return myExcludeUsernameFromHttpUrl;
+    }
+
+    @NotNull
+    public Setup disableUsernameExclude() {
+      myExcludeUsernameFromHttpUrl = false;
+      return this;
+    }
+
+    @Override
+    public String toString() {
+      return "root: " + describeRoot() +
+             ", expectedUrl: '" + myExpectedUrl + '\'' +
+             ", expectedCredentialUser: " + (myExpectedCredentialUser != null ? "'" + myExpectedCredentialUser  + "'" : "null") +
+             (myConfigActionDescription != null ? ", configAction: " + myConfigActionDescription : "") +
+             (myExcludeUsernameFromHttpUrl ? "" : ", excludeUsernameFromUrl: false");
+    }
+
+    @NotNull
+    private String describeRoot() {
+      VcsRootImpl root = myRoot.build();
+      StringBuilder result = new StringBuilder();
+      result.append("{url=").append(root.getProperty(Constants.FETCH_URL));
+      result.append(", auth=").append(root.getProperty(Constants.AUTH_METHOD));
+      result.append(", user=").append(root.getProperty(Constants.USERNAME));
+      result.append("}");
+      return result.toString();
+    }
+  }
+
+  interface ConfigAction {
+    void run(@NotNull Config config) throws Exception;
+  }
+}
index a84030ade5dc95c51401bc534b16700a0ff1bf11..a86907c6ac15e6369ecc13cc1da60e81b9955bc3 100644 (file)
@@ -23,6 +23,8 @@
       <class name="jetbrains.buildServer.buildTriggers.vcs.git.tests.AgentSideSparseCheckoutTest"/>
       <class name="jetbrains.buildServer.buildTriggers.vcs.git.tests.CredentialsHelperTest"/>
       <class name="jetbrains.buildServer.buildTriggers.vcs.git.tests.HttpAuthTest"/>
+      <class name="jetbrains.buildServer.buildTriggers.vcs.git.tests.HttpUrlWithUsernameTest"/>
+      <class name="jetbrains.buildServer.buildTriggers.vcs.git.tests.RemoteRepositoryConfiguratorTest"/>
     </classes>
   </test>
 </suite>
\ No newline at end of file