Merge branch 'Indore-10.0.x' into Indore-2017.1.x
[teamcity/git-plugin.git] / git-agent / src / jetbrains / buildServer / buildTriggers / vcs / git / agent / UpdaterImpl.java
index 4e57add35d6b7c9b70dbad2da62fb26d8516f8e4..03402ae0e2b459b3a1cd74b19d3af3c621c090d3 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;
@@ -80,6 +83,8 @@ public class UpdaterImpl implements Updater {
   private final CheckoutRules myRules;
   private final CheckoutMode myCheckoutMode;
   protected final MirrorManager myMirrorManager;
+  //remote repository refs, stored in field in order to not run 'git ls-remote' command twice
+  private Refs myRemoteRefs;
 
   public UpdaterImpl(@NotNull FS fs,
                      @NotNull AgentPluginConfig pluginConfig,
@@ -117,17 +122,25 @@ public class UpdaterImpl implements Updater {
 
 
   public void update() throws VcsException {
-    myLogger.message("Git version: " + myPluginConfig.getGitVersion());
+    String msg = "Git version: " + myPluginConfig.getGitVersion();
+    myLogger.message(msg);
+    LOG.info(msg);
     checkAuthMethodIsSupported();
     doUpdate();
   }
 
   protected void doUpdate() throws VcsException {
-    logStartUpdating();
-    initGitRepository();
-    removeRefLocks(new File(myTargetDirectory, ".git"));
-    doFetch();
-    updateSources();
+    String message = "Update checkout directory (" + myTargetDirectory.getAbsolutePath() + ")";
+    myLogger.activityStarted(message, GitBuildProgressLogger.GIT_PROGRESS_ACTIVITY);
+    try {
+      logStartUpdating();
+      initGitRepository();
+      removeRefLocks(new File(myTargetDirectory, ".git"));
+      doFetch();
+      updateSources();
+    } finally {
+      myLogger.activityFinished(message, GitBuildProgressLogger.GIT_PROGRESS_ACTIVITY);
+    }
   }
 
   private void logStartUpdating() {
@@ -136,33 +149,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(false);
-      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(true);
-        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(true);
-          firstFetch = true;
-        }
       }
     }
-    return firstFetch;
+    removeOrphanedIdxFiles(new File(myTargetDirectory, ".git"));
   }
 
   protected void setupNewRepository() throws VcsException {
@@ -356,40 +356,51 @@ public class UpdaterImpl implements Updater {
     }
 
     Repository r = new RepositoryBuilder().setBare().setGitDir(getGitDir(repositoryDir)).build();
-    StoredConfig gitConfig = r.getConfig();
+    try {
+      StoredConfig gitConfig = r.getConfig();
 
-    Set<String> submodules = gitModules.getSubsections("submodule");
-    if (submodules.isEmpty()) {
-      Loggers.VCS.info("No submodule sections found in " + new File(repositoryDir, ".gitmodules").getCanonicalPath()
-                       + ", skip updating credentials");
-      return;
-    }
-    File modulesDir = new File(r.getDirectory(), Constants.MODULES);
-    for (String submoduleName : submodules) {
-      String url = gitModules.getString("submodule", submoduleName, "url");
-      Loggers.VCS.info("Update credentials for submodule with url " + url);
-      if (url == null || !isRequireAuth(url)) {
-        Loggers.VCS.info("Url " + url + " does not require authentication, skip updating credentials");
-        continue;
+      Set<String> submodules = gitModules.getSubsections("submodule");
+      if (submodules.isEmpty()) {
+        Loggers.VCS.info("No submodule sections found in " + new File(repositoryDir, ".gitmodules").getCanonicalPath()
+                         + ", skip updating credentials");
+        return;
       }
-      try {
-        URIish uri = new URIish(url);
-        String updatedUrl = uri.setUser(userName).toASCIIString();
-        gitConfig.setString("submodule", submoduleName, "url", updatedUrl);
-        String submodulePath = gitModules.getString("submodule", submoduleName, "path");
-        if (submodulePath != null && myPluginConfig.isUpdateSubmoduleOriginUrl()) {
-          File submoduleDir = new File(modulesDir, submodulePath);
-          if (submoduleDir.isDirectory() && new File(submoduleDir, Constants.CONFIG).isFile())
-            updateOriginUrl(submoduleDir, updatedUrl);
+      File modulesDir = new File(r.getDirectory(), Constants.MODULES);
+      for (String submoduleName : submodules) {
+        //The 'git submodule sync' command executed before resolves relative submodule urls
+        //from .gitmodules and writes them into .git/config. We should use resolved urls in
+        //order to add parent repository username to submodules with relative urls.
+        String url = gitConfig.getString("submodule", submoduleName, "url");
+        if (url == null) {
+          Loggers.VCS.info(".git/config doesn't contain an url for submodule '" + submoduleName + "', use url from .gitmodules");
+          url = gitModules.getString("submodule", submoduleName, "url");
+        }
+        Loggers.VCS.info("Update credentials for submodule with url " + url);
+        if (url == null || !isRequireAuth(url)) {
+          Loggers.VCS.info("Url " + url + " does not require authentication, skip updating credentials");
+          continue;
+        }
+        try {
+          URIish uri = new URIish(url);
+          String updatedUrl = uri.setUser(userName).toASCIIString();
+          gitConfig.setString("submodule", submoduleName, "url", updatedUrl);
+          String submodulePath = gitModules.getString("submodule", submoduleName, "path");
+          if (submodulePath != null && myPluginConfig.isUpdateSubmoduleOriginUrl()) {
+            File submoduleDir = new File(modulesDir, submodulePath);
+            if (submoduleDir.isDirectory() && new File(submoduleDir, Constants.CONFIG).isFile())
+              updateOriginUrl(submoduleDir, updatedUrl);
+          }
+          Loggers.VCS.debug("Submodule url " + url + " changed to " + updatedUrl);
+        } catch (URISyntaxException e) {
+          Loggers.VCS.warn("Error while parsing an url " + url + ", skip updating submodule credentials", e);
+        } catch (Exception e) {
+          Loggers.VCS.warn("Error while updating the '" + submoduleName + "' submodule url", e);
         }
-        Loggers.VCS.debug("Submodule url " + url + " changed to " + updatedUrl);
-      } catch (URISyntaxException e) {
-        Loggers.VCS.warn("Error while parsing an url " + url + ", skip updating submodule credentials", e);
-      } catch (Exception e) {
-        Loggers.VCS.warn("Error while updating the '" + submoduleName + "' submodule url", e);
       }
+      gitConfig.save();
+    } finally {
+      r.close();
     }
-    gitConfig.save();
   }
 
   private void updateOriginUrl(@NotNull File repoDir, @NotNull String url) throws IOException {
@@ -558,8 +569,8 @@ public class UpdaterImpl implements Updater {
       fetchAllBranches();
       if (!myFullBranchName.startsWith("refs/heads/")) {
         Ref remoteRef = getRef(myTargetDirectory, GitUtils.createRemoteRef(myFullBranchName));
-        if (!fetchRequired && remoteRef != null && myRevision.equals(remoteRef.getObjectId().name()) && hasRevision(myTargetDirectory, myRevision))
-          return;
+        if (fetchRequired || remoteRef == null || !myRevision.equals(remoteRef.getObjectId().name()) || !hasRevision(myTargetDirectory, myRevision))
+          fetchDefaultBranch();
       }
     } else {
       Ref remoteRef = getRef(myTargetDirectory, GitUtils.createRemoteRef(myFullBranchName));
@@ -634,7 +645,8 @@ public class UpdaterImpl implements Updater {
       .setAuthSettings(myRoot.getAuthSettings())
       .setUseNativeSsh(myPluginConfig.isUseNativeSSH())
       .setTimeout(timeout)
-      .setRefspec(refspec);
+      .setRefspec(refspec)
+      .setFetchTags(myPluginConfig.isFetchTags());
 
     if (silent)
       result.setQuite(true);
@@ -735,20 +747,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();
@@ -808,9 +827,7 @@ public class UpdaterImpl implements Updater {
     }
     final Refs remoteRefs;
     try {
-      remoteRefs = new Refs(git.lsRemote().setAuthSettings(myRoot.getAuthSettings())
-                              .setUseNativeSsh(myPluginConfig.isUseNativeSSH())
-                              .call());
+      remoteRefs = getRemoteRefs(workingDir);
     } catch (VcsException e) {
       if (CommandUtil.isCanceledError(e))
         throw e;
@@ -833,6 +850,19 @@ public class UpdaterImpl implements Updater {
     return outdatedRefsRemoved;
   }
 
+
+  @NotNull
+  private Refs getRemoteRefs(@NotNull File workingDir) throws VcsException {
+    if (myRemoteRefs != null)
+      return myRemoteRefs;
+    GitFacade git = myGitFactory.create(workingDir);
+    myRemoteRefs = new Refs(git.lsRemote().setAuthSettings(myRoot.getAuthSettings())
+      .setUseNativeSsh(myPluginConfig.isUseNativeSSH())
+      .call());
+    return myRemoteRefs;
+  }
+
+
   private boolean isRemoteTrackingBranch(@NotNull Ref localRef) {
     return localRef.getName().startsWith("refs/remotes/origin");
   }
@@ -847,6 +877,8 @@ public class UpdaterImpl implements Updater {
 
 
   private void configureLFS(@NotNull BaseCommand command) {
+    if (!myPluginConfig.isProvideCredHelper())
+      return;
     Trinity<String, String, String> lfsAuth = getLfsAuth();
     if (lfsAuth == null)
       return;
@@ -870,12 +902,14 @@ public class UpdaterImpl implements Updater {
       for (Map.Entry<String, String> e : config.getEnv().entrySet()) {
         command.setEnv(e.getKey(), e.getValue());
       }
-      command.addPostAction(new Runnable() {
-        @Override
-        public void run() {
-          FileUtil.delete(credHelper);
-        }
-      });
+      if (myPluginConfig.isCleanCredHelperScript()) {
+        command.addPostAction(new Runnable() {
+          @Override
+          public void run() {
+            FileUtil.delete(credHelper);
+          }
+        });
+      }
     } catch (Exception e) {
       if (credentialsHelper != null)
         FileUtil.delete(credentialsHelper);
@@ -910,4 +944,36 @@ public class UpdaterImpl implements Updater {
   private interface VcsCommand {
     void call() throws VcsException;
   }
+
+
+  /**
+   * Removes .idx files which don't have a corresponding .pack file
+   * @param ditGitDir git dir
+   */
+  void removeOrphanedIdxFiles(@NotNull File ditGitDir) {
+    if ("false".equals(myBuild.getSharedConfigParameters().get("teamcity.git.removeOrphanedIdxFiles"))) {
+      //looks like this logic is always needed, if no problems will be reported we can drop the option
+      return;
+    }
+    File packDir = new File(new File(ditGitDir, "objects"), "pack");
+    File[] files = packDir.listFiles();
+    if (files == null || files.length == 0)
+      return;
+
+    Set<String> packs = new HashSet<String>();
+    for (File f : files) {
+      String name = f.getName();
+      if (name.endsWith(".pack")) {
+        packs.add(name.substring(0, name.length() - 5));
+      }
+    }
+
+    for (File f : files) {
+      String name = f.getName();
+      if (name.endsWith(".idx")) {
+        if (!packs.contains(name.substring(0, name.length() - 4)))
+          FileUtil.delete(f);
+      }
+    }
+  }
 }