view mercurial/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/MercurialVcsSupport.java @ 9:7dadebd03515

use tip command instead of log in getCurrentVersion method
author Pavel.Sher
date Mon, 14 Jul 2008 21:17:17 +0400
parents 2cb2df5a0dcd
children 568707240741
line wrap: on
line source
package jetbrains.buildServer.buildTriggers.vcs.mercurial;

import jetbrains.buildServer.CollectChangesByIncludeRule;
import jetbrains.buildServer.Used;
import jetbrains.buildServer.buildTriggers.vcs.AbstractVcsPropertiesProcessor;
import jetbrains.buildServer.buildTriggers.vcs.mercurial.command.*;
import jetbrains.buildServer.log.Loggers;
import jetbrains.buildServer.serverSide.InvalidProperty;
import jetbrains.buildServer.serverSide.PropertiesProcessor;
import jetbrains.buildServer.serverSide.ServerPaths;
import jetbrains.buildServer.util.FileUtil;
import jetbrains.buildServer.vcs.*;
import jetbrains.buildServer.vcs.patches.PatchBuilder;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.io.File;
import java.io.FileFilter;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.*;

public class MercurialVcsSupport extends VcsSupport implements CollectChangesByIncludeRule {
  private ServerPaths myServerPaths;

  public MercurialVcsSupport(@NotNull VcsManager vcsManager, @NotNull ServerPaths paths) {
    vcsManager.registerVcsSupport(this);
    myServerPaths = paths;
  }

  public List<ModificationData> collectBuildChanges(final VcsRoot root,
                                                    @NotNull final String fromVersion,
                                                    @NotNull final String currentVersion,
                                                    final CheckoutRules checkoutRules) throws VcsException {
    updateWorkingDirectory(root);
    return VcsSupportUtil.collectBuildChanges(root, fromVersion, currentVersion, checkoutRules, this);
  }

  public List<ModificationData> collectBuildChanges(final VcsRoot root,
                                                    final String fromVersion,
                                                    final String currentVersion,
                                                    final IncludeRule includeRule) throws VcsException {
    List<ModificationData> result = new ArrayList<ModificationData>();
    Settings settings = new Settings(myServerPaths, root);
    LogCommand lc = new LogCommand(settings);
    lc.setFromRevId(new ChangeSet(fromVersion).getId());
    lc.setToRevId(new ChangeSet(currentVersion).getId());
    List<ChangeSet> changeSets = lc.execute();
    if (changeSets.isEmpty()) {
      return result;
    }

    Iterator<ChangeSet> it = changeSets.iterator();
    ChangeSet prev = it.next(); // skip first changeset (cause it was already reported)
    StatusCommand st = new StatusCommand(settings);
    while (it.hasNext()) {
      ChangeSet cur = it.next();
      st.setFromRevId(prev.getId());
      st.setToRevId(cur.getId());
      List<ModifiedFile> modifiedFiles = st.execute();
      List<VcsChange> files = toVcsChanges(modifiedFiles, prev.getFullVersion(), cur.getFullVersion(), includeRule);
      if (files.isEmpty()) continue;
      ModificationData md = new ModificationData(cur.getTimestamp(), files, cur.getSummary(), cur.getUser(), root, cur.getFullVersion(), cur.getFullVersion());
      result.add(md);
      prev = cur;
    }

    return result;
  }

  private List<VcsChange> toVcsChanges(final List<ModifiedFile> modifiedFiles, String prevVer, String curVer, final IncludeRule includeRule) {
    List<VcsChange> files = new ArrayList<VcsChange>();
    for (ModifiedFile mf: modifiedFiles) {
      if (!normalizePath(mf.getPath()).startsWith(includeRule.getFrom())) continue; // skip files which do not match include rule

      VcsChangeInfo.Type changeType = getChangeType(mf.getStatus());
      if (changeType == null) {
        Loggers.VCS.warn("Unable to convert status: " + mf.getStatus() + " to VCS change type");
        changeType = VcsChangeInfo.Type.NOT_CHANGED;
      }
      files.add(new VcsChange(changeType, mf.getStatus().getName(), mf.getPath(), mf.getPath(), prevVer, curVer));
    }
    return files;
  }

  private @NotNull String normalizePath(@NotNull String repPath) {
    return repPath.replace('\\', '/');
  }

  private VcsChangeInfo.Type getChangeType(final ModifiedFile.Status status) {
    switch (status) {
      case ADDED:return VcsChangeInfo.Type.ADDED;
      case MODIFIED:return VcsChangeInfo.Type.CHANGED;
      case REMOVED:return VcsChangeInfo.Type.REMOVED;
    }
    return null;
  }

  @NotNull
  public byte[] getContent(final VcsModification vcsModification,
                           final VcsChangeInfo change,
                           final VcsChangeInfo.ContentType contentType,
                           final VcsRoot vcsRoot) throws VcsException {
    return new byte[0];
  }

  @NotNull
  public byte[] getContent(final String filePath, final VcsRoot versionedRoot, final String version) throws VcsException {
    return new byte[0];
  }

  public String getName() {
    return "mercurial";
  }

  @Used("jsp")
  public String getDisplayName() {
    return "Mercurial";
  }

  @Nullable
  public PropertiesProcessor getVcsPropertiesProcessor() {
    return new AbstractVcsPropertiesProcessor() {
      public Collection<InvalidProperty> process(final Map<String, String> properties) {
        List<InvalidProperty> result = new ArrayList<InvalidProperty>();
        if (isEmpty(properties.get(Constants.HG_COMMAND_PATH_PROP))) {
          result.add(new InvalidProperty(Constants.HG_COMMAND_PATH_PROP, "Path to 'hg' command must be specified"));
        } 
        if (isEmpty(properties.get(Constants.REPOSITORY_PROP))) {
          result.add(new InvalidProperty(Constants.REPOSITORY_PROP, "Repository must be specified"));
        }
        return result;
      }
    };
  }

  public String getVcsSettingsJspFilePath() {
    return "mercurialSettings.jsp";
  }

  @NotNull
  public String getCurrentVersion(final VcsRoot root) throws VcsException {
    updateWorkingDirectory(root);
    Settings settings = new Settings(myServerPaths, root);
    TipCommand lc = new TipCommand(settings);
    ChangeSet changeSet = lc.execute();
    return changeSet.getFullVersion();
  }

  public String describeVcsRoot(final VcsRoot vcsRoot) {
    return "mercurial: " + vcsRoot.getProperty(Constants.REPOSITORY_PROP);
  }

  public boolean isTestConnectionSupported() {
    return true;
  }

  @Nullable
  public String testConnection(final VcsRoot vcsRoot) throws VcsException {
    getCurrentVersion(vcsRoot);
    return null;
  }

  @Nullable
  public Map<String, String> getDefaultVcsProperties() {
    return null;
  }

  public String getVersionDisplayName(final String version, final VcsRoot root) throws VcsException {
    return version;
  }

  @NotNull
  public Comparator<String> getVersionComparator() {
    return new Comparator<String>() {
      public int compare(final String o1, final String o2) {
        try {
          return new ChangeSet(o1).getRevNumber() - new ChangeSet(o2).getRevNumber();
        } catch (Exception e) {
          return 1;
        }
      }
    };
  }

  public void buildPatch(final VcsRoot root,
                         @Nullable final String fromVersion,
                         @NotNull final String toVersion,
                         final PatchBuilder builder,
                         final CheckoutRules checkoutRules) throws IOException, VcsException {
    updateWorkingDirectory(root);
    Settings settings = new Settings(myServerPaths, root);
    if (fromVersion == null) {
      buildFullPatch(settings, new ChangeSet(toVersion), builder);
    } else {
      buildIncrementalPatch(settings, new ChangeSet(fromVersion), new ChangeSet(toVersion), builder);
    }
  }

  private void buildIncrementalPatch(final Settings settings, @NotNull final ChangeSet fromVer, @NotNull final ChangeSet toVer, final PatchBuilder builder)
    throws VcsException, IOException {
    StatusCommand st = new StatusCommand(settings);
    st.setFromRevId(fromVer.getId());
    st.setToRevId(toVer.getId());
    List<ModifiedFile> modifiedFiles = st.execute();
    List<String> notDeletedFiles = new ArrayList<String>();
    for (ModifiedFile f: modifiedFiles) {
      if (f.getStatus() != ModifiedFile.Status.REMOVED) {
        notDeletedFiles.add(f.getPath());
      }
    }

    CatCommand cc = new CatCommand(settings);
    cc.setRevId(toVer.getId());
    File parentDir = cc.execute(notDeletedFiles);

    try {
      for (ModifiedFile f: modifiedFiles) {
        final File virtualFile = new File(f.getPath());
        if (f.getStatus() == ModifiedFile.Status.REMOVED) {
          builder.deleteFile(virtualFile, true);
        } else {
          File realFile = new File(parentDir, f.getPath());
          FileInputStream is = new FileInputStream(realFile);
          try {
            builder.createBinaryFile(virtualFile, null, is, realFile.length());
          } finally {
            is.close();
          }
        }
      }
    } finally {
      FileUtil.delete(parentDir);
    }
  }

  private void buildFullPatch(final Settings settings, @NotNull final ChangeSet toVer, final PatchBuilder builder)
    throws IOException, VcsException {
    CloneCommand cl = new CloneCommand(settings);
    cl.setToId(toVer.getId());
    File tempDir = FileUtil.createTempDirectory("mercurial", toVer.getId());
    try {
      final File repRoot = new File(tempDir, "rep");
      cl.setDestDir(repRoot.getAbsolutePath());
      cl.execute();
      buildPatchFromDirectory(builder, repRoot, new FileFilter() {
        public boolean accept(final File file) {
          return !(file.isDirectory() && ".hg".equals(file.getName()));
        }
      });
    } finally {
      FileUtil.delete(tempDir);
    }
  }

  private void buildPatchFromDirectory(final PatchBuilder builder, final File repRoot, final FileFilter filter) throws IOException {
    buildPatchFromDirectory(repRoot, builder, repRoot, filter);
  }

  private void buildPatchFromDirectory(File curDir, final PatchBuilder builder, final File repRoot, final FileFilter filter) throws IOException {
    File[] files = curDir.listFiles(filter);
    if (files != null) {
      for (File realFile: files) {
        String relPath = realFile.getAbsolutePath().substring(repRoot.getAbsolutePath().length());
        final File virtualFile = new File(relPath);
        if (realFile.isDirectory()) {
          builder.createDirectory(virtualFile);
          buildPatchFromDirectory(realFile, builder, repRoot, filter);
        } else {
          final FileInputStream is = new FileInputStream(realFile);
          try {
            builder.createBinaryFile(virtualFile, null, is, realFile.length());
          } finally {
            is.close();
          }
        }
      }
    }
  }

  private void updateWorkingDirectory(final VcsRoot root) throws VcsException {
    Settings settings = new Settings(myServerPaths, root);
    String workDir = settings.getWorkingDir();
    synchronized (root) {
      if (hasRepositoryCopy(new File(workDir))) {
        // update
        PullCommand pull = new PullCommand(settings);
        pull.execute();
      } else {
        // clone
        CloneCommand cl = new CloneCommand(settings);
        cl.setDestDir(workDir);
        cl.execute();
      }
    }
  }

  private boolean hasRepositoryCopy(final File workDir) {
    return workDir.isDirectory() && new File(workDir, ".hg").isDirectory();
  }

}