Merge branch 'Indore-10.0.x' into Indore-2017.1.x
authorDmitry Neverov <dmitry.neverov@gmail.com>
Tue, 24 Oct 2017 11:41:58 +0000 (13:41 +0200)
committerDmitry Neverov <dmitry.neverov@gmail.com>
Tue, 24 Oct 2017 11:41:58 +0000 (13:41 +0200)
1  2 
git-agent/src/jetbrains/buildServer/buildTriggers/vcs/git/agent/UpdaterImpl.java

index 38eb17bc34b8449726945598ad75345909db467f,4e57add35d6b7c9b70dbad2da62fb26d8516f8e4..03402ae0e2b459b3a1cd74b19d3af3c621c090d3
@@@ -65,9 -65,6 +65,9 @@@ public class UpdaterImpl implements Upd
     * 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;
@@@ -83,8 -80,6 +83,8 @@@
    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,
  
  
    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() {
    }
  
  
 -  /**
 -   * 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();
+       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();
+         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 {
      }
  
      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 {
        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));
        .setAuthSettings(myRoot.getAuthSettings())
        .setUseNativeSsh(myPluginConfig.isUseNativeSSH())
        .setTimeout(timeout)
 -      .setRefspec(refspec);
 +      .setRefspec(refspec)
 +      .setFetchTags(myPluginConfig.isFetchTags());
  
      if (silent)
        result.setQuite(true);
     *
     * @throws VcsException if there are problems with initializing the directory
     */
-   private void initDirectory() throws VcsException {
-     BuildDirectoryCleanerCallback c = new BuildDirectoryCleanerCallback(myLogger, LOG);
-     myDirectoryCleaner.cleanFolder(myTargetDirectory, c);
-     //noinspection ResultOfMethodCallIgnored
-     myTargetDirectory.mkdirs();
-     if (c.isHasErrors()) {
-       throw new VcsException("Unable to clean directory " + myTargetDirectory + " for VCS root " + myRoot.getName());
+   private void initDirectory(boolean removeTargetDir) throws VcsException {
+     if (removeTargetDir) {
+       BuildDirectoryCleanerCallback c = new BuildDirectoryCleanerCallback(myLogger, LOG);
+       myDirectoryCleaner.cleanFolder(myTargetDirectory, c);
+       //noinspection ResultOfMethodCallIgnored
+       if (c.isHasErrors()) {
+         throw new VcsException("Unable to clean directory " + myTargetDirectory + " for VCS root " + myRoot.getName());
+       }
      }
+     myTargetDirectory.mkdirs();
      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();
      }
      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;
      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");
    }
  
  
    private void configureLFS(@NotNull BaseCommand command) {
 +    if (!myPluginConfig.isProvideCredHelper())
 +      return;
      Trinity<String, String, String> lfsAuth = getLfsAuth();
      if (lfsAuth == null)
        return;
        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);
    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);
 +      }
 +    }
 +  }
  }