diff mercurial/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/MercurialVcsSupport.java @ 0:a530ea876f55

mercurial support sources added
author Pavel.Sher
date Mon, 14 Jul 2008 18:22:05 +0400
parents
children 56e7f34ca887
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/MercurialVcsSupport.java	Mon Jul 14 18:22:05 2008 +0400
@@ -0,0 +1,307 @@
+package jetbrains.buildServer.buildTriggers.vcs.mercurial;
+
+import java.io.File;
+import java.io.FileFilter;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.util.*;
+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;
+
+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);
+    LogCommand lc = new LogCommand(settings);
+    lc.setFromRevId("tip");
+    lc.setToRevId("tip");
+    List<ChangeSet> changes = lc.execute();
+    if (changes.isEmpty()) {
+      throw new VcsException("Unable to obtain current version of repository");
+    }
+    final ChangeSet changeSet = changes.get(0);
+    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
+        UpdateCommand up = new UpdateCommand(settings);
+        up.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();
+  }
+
+}