Mercurial > hg > mercurial
changeset 179:2b0eafc9bcf5 remote-run/dmitry.neverov/final_agent_caches
Mercurial mirrors on agent
line wrap: on
line diff
--- a/mercurial-agent/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/MercurialAgentSideVcsSupport.java Wed Feb 16 13:35:57 2011 +0300 +++ b/mercurial-agent/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/MercurialAgentSideVcsSupport.java Tue Mar 01 16:47:00 2011 +0300 @@ -15,10 +15,12 @@ */ package jetbrains.buildServer.buildTriggers.vcs.mercurial; +import jetbrains.buildServer.agent.AgentRunningBuild; +import jetbrains.buildServer.agent.BuildAgentConfiguration; import jetbrains.buildServer.agent.BuildProgressLogger; import jetbrains.buildServer.agent.vcs.AgentVcsSupport; import jetbrains.buildServer.agent.vcs.IncludeRuleUpdater; -import jetbrains.buildServer.agent.vcs.UpdateByIncludeRules; +import jetbrains.buildServer.agent.vcs.UpdateByIncludeRules2; import jetbrains.buildServer.agent.vcs.UpdatePolicy; import jetbrains.buildServer.buildTriggers.vcs.mercurial.command.*; import jetbrains.buildServer.util.FileUtil; @@ -29,63 +31,40 @@ import org.jetbrains.annotations.NotNull; import java.io.File; -import java.io.IOException; + +public class MercurialAgentSideVcsSupport extends AgentVcsSupport implements UpdateByIncludeRules2 { -public class MercurialAgentSideVcsSupport extends AgentVcsSupport implements UpdateByIncludeRules { - private void updateWorkingDir(final Settings settings, final String version, final BuildProgressLogger logger) throws VcsException { - logger.message("Updating working directory from the local repository copy"); - UpdateCommand uc = new UpdateCommand(settings); - ChangeSet cs = new ChangeSet(version); - uc.setToId(cs.getId()); - uc.execute(); - logger.message("Working directory updated successfully"); + private final MirrorManager myMirrorManager; + + public MercurialAgentSideVcsSupport(BuildAgentConfiguration agentConfiguration) { + myMirrorManager = new MirrorManager(agentConfiguration.getCacheDirectory("mercurial")); } - private File cloneRepository(final Settings settings) throws VcsException { - File tempDir; - try { - File parent = FileUtil.createTempDirectory("hg", "clone"); - parent.deleteOnExit(); - tempDir = new File(parent, "hg"); - } catch (IOException e) { - throw new VcsException("Failed to create temp directory: " + e.getLocalizedMessage()); - } + public IncludeRuleUpdater getUpdater(@NotNull final VcsRoot vcsRoot, @NotNull final CheckoutRules checkoutRules, @NotNull final String toVersion, @NotNull final File checkoutDirectory, @NotNull final AgentRunningBuild build, boolean cleanCheckoutRequested) throws VcsException { + final BuildProgressLogger logger = build.getBuildLogger(); + final boolean useLocalMirrors = isUseLocalMirrors(build); + return new IncludeRuleUpdater() { + public void process(@NotNull final IncludeRule includeRule, @NotNull final File workingDir) throws VcsException { + checkRuleIsValid(includeRule); + Settings settings = new Settings(vcsRoot); + if (useLocalMirrors) updateLocalMirror(vcsRoot, logger); + if (settings.isValidRepository(workingDir)) { + updateRepository(workingDir, settings, logger, useLocalMirrors); + } else { + cloneRepository(workingDir, settings, logger, useLocalMirrors); + } + updateWorkingDir(settings, workingDir, toVersion, logger); + } - CloneCommand cc = new CloneCommand(settings); - cc.setDestDir(tempDir.getAbsolutePath()); - - cc.setUpdateWorkingDir(false); - cc.execute(); - return tempDir; + public void dispose() throws VcsException { + } + }; } - /** - * Moves files from one directory to another with all subdirectories. - * Removes old directory if it became empty. - */ - private static boolean moveDir(File oldDir, File newDir) { - // both old and new directories exist - boolean moveSuccessful = true; - final File[] files = oldDir.listFiles(); - if (files != null) { - for (File file: files) { - if (file.isFile()) { - File destFile = new File(newDir, file.getName()); - destFile.getParentFile().mkdirs(); - if (!file.renameTo(destFile)) { - moveSuccessful = false; - } - } else if (!moveDir(file, new File(newDir, file.getName()))) { - moveSuccessful = false; - } - } - } - - if (moveSuccessful) { - FileUtil.deleteIfEmpty(oldDir); - } - - return moveSuccessful; + @NotNull + @Override + public String getName() { + return Constants.VCS_NAME; } @NotNull @@ -94,50 +73,110 @@ return this; } - @NotNull - @Override - public String getName() { - return Constants.VCS_NAME; + private boolean isUseLocalMirrors(AgentRunningBuild build) { + String value = build.getSharedConfigParameters().get("teamcity.hg.use.local.mirrors"); + return "true".equals(value); + } + + private void cloneRepository(File workingDir, Settings settings, BuildProgressLogger logger, boolean useLocalMirrors) throws VcsException { + String message = "No repository in working directory found, start cloning from " + (useLocalMirrors ? "local mirror" : "remote repository"); + logger.message(message); + if (useLocalMirrors) { + cloneFromLocalMirror(settings, workingDir); + } else { + cloneFromRemote(settings, workingDir); + } + logger.message("Repository successfully cloned to working directory: " + workingDir.getAbsolutePath()); + } + + private void updateRepository(File workingDir, Settings settings, BuildProgressLogger logger, boolean useLocalMirrors) throws VcsException { + if (useLocalMirrors) { + if (isClonedFromLocalMirror(settings, workingDir)) { + logger.message("Repository in working directory found, start pulling changes"); + new PullCommand(settings, workingDir).execute(); + logger.message("Changes successfully pulled"); + } else { + logger.message("Repository in working directory is cloned from remote repository, clone it from local mirror"); + FileUtil.delete(workingDir); + cloneFromLocalMirror(settings, workingDir); + } + } else { + if (isClonedFromLocalMirror(settings, workingDir)) { + logger.message("Repository in working directory is cloned from local mirror, clone it from remote repository"); + FileUtil.delete(workingDir); + cloneFromRemote(settings, workingDir); + } else { + logger.message("Repository in working directory found, start pulling changes"); + new PullCommand(settings, workingDir).execute(); + logger.message("Changes successfully pulled"); + } + } } - public IncludeRuleUpdater getUpdater(@NotNull final VcsRoot vcsRoot, @NotNull final CheckoutRules checkoutRules, @NotNull final String toVersion, @NotNull final File checkoutDirectory, @NotNull final BuildProgressLogger logger) throws VcsException { - return new IncludeRuleUpdater() { - public void process(@NotNull final IncludeRule includeRule, @NotNull final File workingDir) throws VcsException { - if (includeRule.getTo() != null && includeRule.getTo().length() > 0) { - if (!".".equals(includeRule.getFrom()) && includeRule.getFrom().length() != 0) { - throw new VcsException("Invalid include rule: " + includeRule.toString() + ", Mercurial plugin supports mapping of the form: +:.=>dir only."); - } - } + private void updateLocalMirror(VcsRoot root, BuildProgressLogger logger) throws VcsException { + Settings settings = new Settings(root); + File mirrorDir = myMirrorManager.getMirrorDir(settings.getRepositoryUrl()); + if (Settings.isValidRepository(mirrorDir)) { + logger.message("Start pulling changes to local mirror at " + mirrorDir); + new PullCommand(settings, mirrorDir).execute(); + logger.message("Local mirror changes successfully pulled"); + } else { + logger.message("No local mirror found for " + settings.getRepositoryUrl() + ", create mirror at " + mirrorDir.getAbsolutePath()); + logger.message("Clone local mirror at " + mirrorDir); + cloneRepository(settings, mirrorDir, settings.getRepositoryUrl()); + logger.message("Local mirror successfully cloned to " + mirrorDir); + } + } + + private void updateWorkingDir(final Settings settings, File workingDir, final String version, final BuildProgressLogger logger) throws VcsException { + logger.message("Updating working directory from the local repository copy"); + UpdateCommand uc = new UpdateCommand(settings, workingDir); + ChangeSet cs = new ChangeSet(version); + uc.setToId(cs.getId()); + uc.execute(); + logger.message("Working directory updated successfully"); + } + + private void cloneFromLocalMirror(final Settings settings, File workingDir) throws VcsException { + File mirrorDir = myMirrorManager.getMirrorDir(settings.getRepositoryUrl()); + try { + cloneRepository(settings, workingDir, mirrorDir.getCanonicalPath()); + } catch (Exception e) { + throw new VcsException("Failed to clone from local mirror at " + mirrorDir.getAbsolutePath(), e); + } + } - Settings settings = new Settings(workingDir, vcsRoot); - settings.setWorkingDir(workingDir); - if (settings.hasCopyOfRepository()) { - // execute pull command - logger.message("Repository in working directory found, start pulling changes"); - PullCommand pc = new PullCommand(settings); - pc.execute(); - logger.message("Changes successfully pulled"); - } else { - // execute clone command - logger.message("No repository in working directory found, start cloning repository to temporary folder"); - File parentDir = cloneRepository(settings); - logger.message("Repository successfully cloned to: " + parentDir.getAbsolutePath()); - logger.message("Moving repository to working directory: " + workingDir.getAbsolutePath()); - if (!moveDir(parentDir, workingDir)) { - File hgDir = new File(workingDir, ".hg"); - if (hgDir.isDirectory()) { - FileUtil.delete(hgDir); - } - throw new VcsException("Failed to move directory content: " + parentDir.getAbsolutePath() + " to: " + workingDir.getAbsolutePath()); - } + private void cloneFromRemote(final Settings settings, File workingDir) throws VcsException { + try { + cloneRepository(settings, workingDir, settings.getRepositoryUrl()); + } catch (Exception e) { + throw new VcsException("Failed to clone from remote repository " + settings.getRepositoryUrl(), e); + } + } + + private void cloneRepository(final Settings settings, File workingDir, String url) throws VcsException { + CloneCommand cc = new CloneCommand(settings, workingDir); + cc.setRepository(url); + cc.setUpdateWorkingDir(false); + cc.execute(); + } - logger.message("Repository successfully moved to working directory: " + workingDir.getAbsolutePath()); - } - updateWorkingDir(settings, toVersion, logger); + private void checkRuleIsValid(IncludeRule includeRule) throws VcsException { + if (includeRule.getTo() != null && includeRule.getTo().length() > 0) { + if (!".".equals(includeRule.getFrom()) && includeRule.getFrom().length() != 0) { + throw new VcsException("Invalid include rule: " + includeRule.toString() + ", Mercurial plugin supports mapping of the form: +:.=>dir only."); } + } + } - public void dispose() throws VcsException { - } - }; + public boolean isClonedFromLocalMirror(Settings settings, File workingDir) { + try { + File mirrorDir = myMirrorManager.getMirrorDir(settings.getRepositoryUrl()); + File hgrc = new File(workingDir, ".hg" + File.separator + "hgrc"); + String config = FileUtil.readText(hgrc); + return config.contains("default = " + mirrorDir.getCanonicalPath()); + } catch (Exception e) { + return false; + } } }
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/MirrorManager.java Tue Mar 01 16:47:00 2011 +0300 @@ -0,0 +1,237 @@ +package jetbrains.buildServer.buildTriggers.vcs.mercurial; + +import com.intellij.openapi.diagnostic.Logger; +import jetbrains.buildServer.util.FileUtil; +import jetbrains.buildServer.util.Hash; +import org.jetbrains.annotations.NotNull; + +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.locks.ReadWriteLock; +import java.util.concurrent.locks.ReentrantReadWriteLock; + +/** + * Manages local mirrors of remote repositories. + * Each unique url get unique local mirror. Each mirror is used for one url only. + * @author dmitry.neverov + */ +public final class MirrorManager { + + private static Logger LOG = Logger.getInstance(MirrorManager.class.getName()); + private static final String MIRROR_DIR_PREFIX = "hg_"; + private static final String MAPPING_FILE_NAME = "map"; + + private final ReadWriteLock myLock = new ReentrantReadWriteLock(); + private final File myRootDir; + /*Only one thread read or write to this file, it is protected by myLock.writeLock()*/ + private final File myMappingFile; + /*Protected by myLock*/ + private final Map<String, File> myMirrors = new HashMap<String, File>(); + private HashCalculator myHash = new StandartHash(); + + /** + * @param rootDir root directory where all mirrors are stored + */ + public MirrorManager(File rootDir) { + myRootDir = rootDir; + myMappingFile = new File(myRootDir, MAPPING_FILE_NAME); + readMappingFromFile(); + } + + + /** + * Get directory of local mirror repository for specified url, if directory is not exists it is created + * @param url url of interest + * @return see above + */ + @NotNull + public File getMirrorDir(@NotNull final String url) { + File result = getMirrorDirWithLock(url); + if (result == null) { + result = createDirFor(url); + } + return result; + } + + + /** + * Get all local mirror repository dirs + * @return see above + */ + @NotNull + public List<File> getMirrors() { + myLock.readLock().lock(); + try { + return new ArrayList<File>(myMirrors.values()); + } finally { + myLock.readLock().unlock(); + } + } + + + //for tests only + void setHashCalculator(HashCalculator hash) { + myHash = hash; + } + + + private File createDirFor(String url) { + File result; + myLock.writeLock().lock(); + try { + File mirrorDir = getUniqueDir(url); + result = saveMappingIfAbsent(url, mirrorDir); + } finally { + myLock.writeLock().unlock(); + } + if (!result.exists()) { + result.mkdirs(); + } + return result; + } + + + private File getMirrorDirWithLock(String url) { + myLock.readLock().lock(); + try { + return myMirrors.get(url); + } finally { + myLock.readLock().unlock(); + } + } + + + //should be called with myLock.writeLock() held + private File saveMappingIfAbsent(String url, File mirrorDir) { + File existing = myMirrors.get(url); + if (existing != null) { + return existing; + } else { + myMirrors.put(url, mirrorDir); + saveMappingToFile(); + return mirrorDir; + } + } + + + private File getUniqueDir(String url) { + myLock.readLock().lock(); + try { + String dirName = MIRROR_DIR_PREFIX + hash(normalize(url)); + File result = PathUtil.getCanonicalFile(new File(myRootDir, dirName)); + while (isUsedForOtherUrl(result, url)) { + dirName = MIRROR_DIR_PREFIX + hash(result.getName()); + result = PathUtil.getCanonicalFile(new File(myRootDir, dirName)); + } + return result; + } finally { + myLock.readLock().unlock(); + } + } + + + private boolean isUsedForOtherUrl(File repositoryDir, String url) { + myLock.readLock().lock(); + try { + for (Map.Entry<String, File> mirror : myMirrors.entrySet()) { + String mirrorUrl = mirror.getKey(); + File mirrorDir = mirror.getValue(); + if (mirrorDir.equals(repositoryDir) && !mirrorUrl.equals(url)) { + return true; + } + } + return false; + } finally { + myLock.readLock().unlock(); + } + } + + + private String hash(String value) { + return String.valueOf(myHash.calc(value)); + } + + + private static String normalize(final String path) { + String normalized = PathUtil.normalizeSeparator(path); + if (path.endsWith("/")) { + return normalized.substring(0, normalized.length()-1); + } + return normalized; + } + + + private void readMappingFromFile() { + myLock.writeLock().lock(); + try { + LOG.debug("Parse mapping file " + myMappingFile.getAbsolutePath()); + for (String line : readLines()) { + int separatorIndex = line.lastIndexOf(" = "); + if (separatorIndex == -1) { + LOG.error("Cannot parse mapping '" + line + "', skip it."); + } else { + String url = line.substring(0, separatorIndex); + String dirName = line.substring(separatorIndex + 3); + File repositoryDir = PathUtil.getCanonicalFile(new File(myRootDir, dirName)); + if (isUsedForOtherUrl(repositoryDir, url)) { + LOG.error("Skip mapping " + line + ": " + dirName + " is used for url other than " + url); + } else { + myMirrors.put(url, PathUtil.getCanonicalFile(new File(myRootDir, dirName))); + } + } + } + } finally { + myLock.writeLock().unlock(); + } + } + + /*Should be called with myLock.writeLock() held*/ + private List<String> readLines() { + if (myMappingFile.exists()) { + try { + return FileUtil.readFile(myMappingFile); + } catch (IOException e) { + LOG.error("Error while reading a mapping file at " + myMappingFile.getAbsolutePath() + " starting with empty mapping", e); + return new ArrayList<String>(); + } + } else { + LOG.info("No mapping file found at " + myMappingFile.getAbsolutePath() + " starting with empty mapping"); + File parentDir = myMappingFile.getParentFile(); + if (!parentDir.exists() && !parentDir.mkdirs()) { + LOG.error("Cannot create local mirrors dir at " + parentDir.getAbsolutePath()); + } + return new ArrayList<String>(); + } + } + + + private void saveMappingToFile() { + myLock.writeLock().lock(); + try { + StringBuilder sb = new StringBuilder(); + for (Map.Entry<String, File> mirror : myMirrors.entrySet()) { + String url = mirror.getKey(); + String dir = mirror.getValue().getName(); + sb.append(url).append(" = ").append(dir).append("\n"); + } + FileUtil.writeFile(myMappingFile, sb.toString()); + } finally { + myLock.writeLock().unlock(); + } + } + + + final static class StandartHash implements HashCalculator { + public long calc(String value) { + return Hash.calc(value); + } + } + + public static interface HashCalculator { + long calc(String value); + } +}
--- a/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/BaseCommand.java Wed Feb 16 13:35:57 2011 +0300 +++ b/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/BaseCommand.java Tue Mar 01 16:47:00 2011 +0300 @@ -22,6 +22,7 @@ import jetbrains.buildServer.vcs.VcsException; import org.jetbrains.annotations.NotNull; +import java.io.File; import java.util.Collections; import java.util.Set; @@ -32,11 +33,12 @@ private Settings mySettings; private String myWorkDirectory; - public BaseCommand(@NotNull final Settings settings) { + public BaseCommand(@NotNull final Settings settings, @NotNull File workingDir) { mySettings = settings; - myWorkDirectory = settings.getLocalRepositoryDir().getAbsolutePath(); + myWorkDirectory = workingDir.getAbsolutePath(); } + public Settings getSettings() { return mySettings; }
--- a/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/BranchesCommand.java Wed Feb 16 13:35:57 2011 +0300 +++ b/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/BranchesCommand.java Tue Mar 01 16:47:00 2011 +0300 @@ -20,18 +20,20 @@ import jetbrains.buildServer.vcs.VcsException; import org.jetbrains.annotations.NotNull; +import java.io.File; import java.util.HashMap; import java.util.Map; +import java.util.regex.Matcher; import java.util.regex.Pattern; -import java.util.regex.Matcher; /** * @author Pavel.Sher * Date: 26.10.2008 */ public class BranchesCommand extends BaseCommand { - public BranchesCommand(@NotNull final Settings settings) { - super(settings); + + public BranchesCommand(@NotNull Settings settings, @NotNull File workingDir) { + super(settings, workingDir); } /**
--- a/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/CatCommand.java Wed Feb 16 13:35:57 2011 +0300 +++ b/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/CatCommand.java Tue Mar 01 16:47:00 2011 +0300 @@ -30,8 +30,8 @@ private String myRevId; private final static int MAX_CMD_LEN = 900; - public CatCommand(@NotNull final Settings settings) { - super(settings); + public CatCommand(@NotNull Settings settings, @NotNull File workingDir) { + super(settings, workingDir); } public void setRevId(final String revId) {
--- a/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/ChangedFilesCommand.java Wed Feb 16 13:35:57 2011 +0300 +++ b/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/ChangedFilesCommand.java Tue Mar 01 16:47:00 2011 +0300 @@ -31,8 +31,8 @@ public class ChangedFilesCommand extends BaseCommand { private String myRevId; - public ChangedFilesCommand(@NotNull final Settings settings) { - super(settings); + public ChangedFilesCommand(@NotNull Settings settings, @NotNull File workingDir) { + super(settings, workingDir); } public void setRevId(@NotNull final String revId) {
--- a/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/CloneCommand.java Wed Feb 16 13:35:57 2011 +0300 +++ b/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/CloneCommand.java Tue Mar 01 16:47:00 2011 +0300 @@ -23,13 +23,14 @@ import java.io.File; public class CloneCommand extends BaseCommand{ - private String myDestDir; private String myToId; private boolean myUpdateWorkingDir = true; private String myRepository; + private File myWorkingDir; - public CloneCommand(@NotNull final Settings settings) { - super(settings); + public CloneCommand(@NotNull Settings settings, @NotNull File workingDir) { + super(settings, workingDir); + myWorkingDir = workingDir; myRepository = getSettings().getRepositoryUrl(); } @@ -41,10 +42,6 @@ myRepository = repo; } - public void setDestDir(@NotNull String destDir) { - myDestDir = destDir; - } - public void setToId(final String toId) { myToId = toId; } @@ -54,10 +51,8 @@ } public void execute() throws VcsException { - if (myDestDir == null) throw new IllegalStateException("Destination dir must be specified"); GeneralCommandLine cli = createCommandLine(); - File dir = new File(myDestDir); - File parent = dir.getParentFile(); + File parent = myWorkingDir.getParentFile(); cli.setWorkDirectory(parent.getAbsolutePath()); cli.addParameter("clone"); if (myToId != null) { @@ -72,7 +67,7 @@ cli.addParameter("--uncompressed"); } cli.addParameter(myRepository); - cli.addParameter(dir.getName()); + cli.addParameter(myWorkingDir.getName()); ExecResult res = runCommand(cli, 24*3600); // some repositories are quite large, we set timeout to 24 hours failIfNonZeroExitCode(cli, res);
--- a/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/IdentifyCommand.java Wed Feb 16 13:35:57 2011 +0300 +++ b/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/IdentifyCommand.java Tue Mar 01 16:47:00 2011 +0300 @@ -20,13 +20,16 @@ import jetbrains.buildServer.vcs.VcsException; import org.jetbrains.annotations.NotNull; +import java.io.File; + /** * @author Pavel.Sher * Date: 16.07.2008 */ public class IdentifyCommand extends BaseCommand { - public IdentifyCommand(@NotNull final Settings settings) { - super(settings); + + public IdentifyCommand(@NotNull Settings settings, @NotNull File workingDir) { + super(settings, workingDir); } public String execute() throws VcsException {
--- a/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/LogCommand.java Wed Feb 16 13:35:57 2011 +0300 +++ b/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/LogCommand.java Tue Mar 01 16:47:00 2011 +0300 @@ -21,6 +21,7 @@ import jetbrains.buildServer.vcs.VcsException; import org.jetbrains.annotations.NotNull; +import java.io.File; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.ArrayList; @@ -42,8 +43,8 @@ private static final String DESCRIPTION_PREFIX = "description:"; private static final String FILES_PREFIX = "files:"; - public LogCommand(@NotNull Settings settings) { - super(settings); + public LogCommand(@NotNull Settings settings, @NotNull File workingDir) { + super(settings, workingDir); } public void setFromRevId(String id) {
--- a/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/PullCommand.java Wed Feb 16 13:35:57 2011 +0300 +++ b/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/PullCommand.java Tue Mar 01 16:47:00 2011 +0300 @@ -19,13 +19,16 @@ import jetbrains.buildServer.vcs.VcsException; import org.jetbrains.annotations.NotNull; +import java.io.File; + /** * @author Pavel.Sher * Date: 14.07.2008 */ public class PullCommand extends BaseCommand { - public PullCommand(@NotNull final Settings settings) { - super(settings); + + public PullCommand(@NotNull Settings settings, @NotNull File workingDir) { + super(settings, workingDir); } public void execute() throws VcsException {
--- a/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/PushCommand.java Wed Feb 16 13:35:57 2011 +0300 +++ b/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/PushCommand.java Tue Mar 01 16:47:00 2011 +0300 @@ -20,14 +20,16 @@ import jetbrains.buildServer.vcs.VcsException; import org.jetbrains.annotations.NotNull; +import java.io.File; + /** * @author pavel */ public class PushCommand extends BaseCommand { private boolean myForced; - public PushCommand(@NotNull final Settings settings) { - super(settings); + public PushCommand(@NotNull Settings settings, @NotNull File workingDir) { + super(settings, workingDir); } public void setForce(boolean force) {
--- a/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/Settings.java Wed Feb 16 13:35:57 2011 +0300 +++ b/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/Settings.java Tue Mar 01 16:47:00 2011 +0300 @@ -18,10 +18,10 @@ import jetbrains.buildServer.buildTriggers.vcs.mercurial.Constants; import jetbrains.buildServer.buildTriggers.vcs.mercurial.PathUtil; import jetbrains.buildServer.log.Loggers; -import jetbrains.buildServer.util.Hash; import jetbrains.buildServer.util.StringUtil; import jetbrains.buildServer.vcs.VcsRoot; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import java.io.File; import java.net.MalformedURLException; @@ -37,30 +37,26 @@ public class Settings { private String myRepository; private String myHgCommandPath; - private File myWorkingDir; - private File myWorkFolderParentDir; + private File myCustomWorkingDir; private String myUsername; private String myPassword; private String myBranchName; private boolean myUncompressedTransfer = false; private static final String DEFAULT_BRANCH_NAME = "default"; + private String myCustomClonePath; - public Settings(@NotNull File workFolderParentDir, @NotNull VcsRoot vcsRoot) { - myWorkFolderParentDir = workFolderParentDir; - setRepository(vcsRoot.getProperty(Constants.REPOSITORY_PROP)); - setHgCommandPath(vcsRoot.getProperty(Constants.HG_COMMAND_PATH_PROP)); + public Settings(@NotNull VcsRoot vcsRoot) { + myRepository = vcsRoot.getProperty(Constants.REPOSITORY_PROP); + myHgCommandPath = vcsRoot.getProperty(Constants.HG_COMMAND_PATH_PROP); myBranchName = vcsRoot.getProperty(Constants.BRANCH_NAME_PROP); - + myCustomClonePath = vcsRoot.getProperty(Constants.SERVER_CLONE_PATH_PROP); myUsername = vcsRoot.getProperty(Constants.USERNAME); myPassword = vcsRoot.getProperty(Constants.PASSWORD); myUncompressedTransfer = "true".equals(vcsRoot.getProperty(Constants.UNCOMPRESSED_TRANSFER)); } - public Settings() { - } - - public void setRepository(@NotNull final String repository) { - myRepository = repository; + public String getCustomClonePath() { + return myCustomClonePath; } /** @@ -184,48 +180,26 @@ return userInfo; } - public void setHgCommandPath(@NotNull final String hgCommandPath) { - myHgCommandPath = hgCommandPath; - } - - public void setWorkingDir(@NotNull final File workingDir) { - myWorkingDir = PathUtil.getCanonicalFile(workingDir); - } - /** - * Returns directory where repository is supposed to be cloned, i.e. working directory of cloned repository - * @return repository working directory + * Set custom working dir for vcs root. This option make sence only for server-side checkout + * @param customWorkingDir custom working dir */ - @NotNull - public File getLocalRepositoryDir() { - if (myWorkingDir != null) { - return myWorkingDir; - } - - return getDefaultWorkDir(myWorkFolderParentDir, myRepository); + public void setCustomWorkingDir(@NotNull final File customWorkingDir) { + myCustomWorkingDir = PathUtil.getCanonicalFile(customWorkingDir); } /** - * Returns true if current working directory contains copy of repository (contains .hg directory) + * Returns custom working dir for root or null if default working dir should be used. + * This options make sence only with server-side checkout. * @return see above */ - public boolean hasCopyOfRepository() { - // need better way to check that repository copy is ok - return getLocalRepositoryDir().isDirectory() && new File(getLocalRepositoryDir(), ".hg").isDirectory(); + @Nullable + public File getCustomWorkingDir() { + return myCustomWorkingDir; } - public static String DEFAULT_WORK_DIR_PREFIX = "hg_"; - - private static File getDefaultWorkDir(@NotNull File workFolderParentDir, @NotNull String repPath) { - String workingDirname = DEFAULT_WORK_DIR_PREFIX + String.valueOf(Hash.calc(normalize(repPath))); - return PathUtil.getCanonicalFile(new File(workFolderParentDir, workingDirname)); - } - - private static String normalize(final String path) { - String normalized = PathUtil.normalizeSeparator(path); - if (path.endsWith("/")) { - return normalized.substring(0, normalized.length()-1); - } - return normalized; + public static boolean isValidRepository(File dir) { + // need better way to check that repository copy is ok + return dir.isDirectory() && new File(dir, ".hg").isDirectory(); } }
--- a/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/StatusCommand.java Wed Feb 16 13:35:57 2011 +0300 +++ b/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/StatusCommand.java Tue Mar 01 16:47:00 2011 +0300 @@ -20,6 +20,7 @@ import jetbrains.buildServer.vcs.VcsException; import org.jetbrains.annotations.NotNull; +import java.io.File; import java.util.ArrayList; import java.util.List; @@ -27,8 +28,8 @@ private String myFromId; private String myToId; - public StatusCommand(@NotNull final Settings settings) { - super(settings); + public StatusCommand(@NotNull Settings settings, @NotNull File workingDir) { + super(settings, workingDir); } public void setFromRevId(final String fromId) {
--- a/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/TagCommand.java Wed Feb 16 13:35:57 2011 +0300 +++ b/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/TagCommand.java Tue Mar 01 16:47:00 2011 +0300 @@ -15,16 +15,18 @@ */ package jetbrains.buildServer.buildTriggers.vcs.mercurial.command; -import org.jetbrains.annotations.NotNull; import com.intellij.execution.configurations.GeneralCommandLine; import jetbrains.buildServer.vcs.VcsException; +import org.jetbrains.annotations.NotNull; + +import java.io.File; public class TagCommand extends BaseCommand { private String myTag; private String myRevId; - public TagCommand(@NotNull Settings settings) { - super(settings); + public TagCommand(@NotNull Settings settings, @NotNull File workingDir) { + super(settings, workingDir); } public void setTag(@NotNull final String tag) {
--- a/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/UpdateCommand.java Wed Feb 16 13:35:57 2011 +0300 +++ b/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/UpdateCommand.java Tue Mar 01 16:47:00 2011 +0300 @@ -19,14 +19,16 @@ import jetbrains.buildServer.vcs.VcsException; import org.jetbrains.annotations.NotNull; +import java.io.File; + public class UpdateCommand extends BaseCommand { private static final int UPDATE_TIMEOUT_SECONDS = 8 * 3600;//8 hours private String myToId; - public UpdateCommand(@NotNull final Settings settings) { - super(settings); + public UpdateCommand(@NotNull Settings settings, @NotNull File workingDir) { + super(settings, workingDir); } public void setToId(final String toId) {
--- a/mercurial-server/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/MercurialVcsSupport.java Wed Feb 16 13:35:57 2011 +0300 +++ b/mercurial-server/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/MercurialVcsSupport.java Tue Mar 01 16:47:00 2011 +0300 @@ -56,6 +56,7 @@ private ConcurrentMap<String, Lock> myWorkDirLocks= new ConcurrentHashMap<String, Lock>(); private VcsManager myVcsManager; private File myDefaultWorkFolderParent; + private MirrorManager myMirrorManager; public MercurialVcsSupport(@NotNull final VcsManager vcsManager, @NotNull ServerPaths paths, @@ -63,6 +64,7 @@ @NotNull EventDispatcher<BuildServerListener> dispatcher) { myVcsManager = vcsManager; myDefaultWorkFolderParent = new File(paths.getCachesDir(), "mercurial"); + myMirrorManager = new MirrorManager(myDefaultWorkFolderParent); dispatcher.addListener(new BuildServerAdapter() { @Override public void cleanupFinished() { @@ -79,24 +81,31 @@ super.sourcesVersionReleased(agent); server.getExecutor().submit(new Runnable() { public void run() { - Set<File> clonedRepos = getAllClonedRepos(); - if (clonedRepos == null) return; - for (File f: clonedRepos) { - lockWorkDir(f); - try { - FileUtil.delete(f); - } finally { - unlockWorkDir(f); - } - } + deleteWithLocking(myMirrorManager.getMirrors()); } }); } }); } + public MirrorManager getMirrorManager() { + return myMirrorManager; + } + + private void deleteWithLocking(Collection<File> filesForDelete) { + for (File f : filesForDelete) { + lockWorkDir(f); + try { + FileUtil.delete(f); + } finally { + unlockWorkDir(f); + } + } + } + private Collection<ModifiedFile> computeModifiedFilesForMergeCommit(final Settings settings, final ChangeSet cur) throws VcsException { - ChangedFilesCommand cfc = new ChangedFilesCommand(settings); + File workingDir = myMirrorManager.getMirrorDir(settings.getRepositoryUrl()); + ChangedFilesCommand cfc = new ChangedFilesCommand(settings, workingDir); cfc.setRevId(cur.getId()); return cfc.execute(); } @@ -138,7 +147,8 @@ public byte[] getContent(@NotNull final String filePath, @NotNull final VcsRoot vcsRoot, @NotNull final String version) throws VcsException { syncClonedRepository(vcsRoot); Settings settings = createSettings(vcsRoot); - CatCommand cc = new CatCommand(settings); + File workingDir = myMirrorManager.getMirrorDir(settings.getRepositoryUrl()); + CatCommand cc = new CatCommand(settings, workingDir); cc.setRevId(new ChangeSet(version).getId()); File parentDir = cc.execute(Collections.singletonList(filePath)); try { @@ -195,7 +205,8 @@ // we will return full version of the most recent change as current version syncClonedRepository(root); Settings settings = createSettings(root); - BranchesCommand branches = new BranchesCommand(settings); + File workingDir = myMirrorManager.getMirrorDir(settings.getRepositoryUrl()); + BranchesCommand branches = new BranchesCommand(settings, workingDir); Map<String, ChangeSet> result = branches.execute(); if (!result.containsKey(settings.getBranchName())) { throw new VcsException("Unable to find current version for the branch: " + settings.getBranchName()); @@ -218,7 +229,8 @@ return new TestConnectionSupport() { public String testConnection(@NotNull final VcsRoot vcsRoot) throws VcsException { Settings settings = createSettings(vcsRoot); - IdentifyCommand id = new IdentifyCommand(settings); + File workingDir = myMirrorManager.getMirrorDir(settings.getRepositoryUrl()); + IdentifyCommand id = new IdentifyCommand(settings, workingDir); StringBuilder res = new StringBuilder(); res.append(quoteIfNeeded(settings.getHgCommandPath())); res.append(" identify "); @@ -269,7 +281,8 @@ // builds patch from version to version private void buildIncrementalPatch(final Settings settings, @NotNull final ChangeSet fromVer, @NotNull final ChangeSet toVer, final PatchBuilder builder, final CheckoutRules checkoutRules) throws VcsException, IOException { - StatusCommand st = new StatusCommand(settings); + File workingDir = myMirrorManager.getMirrorDir(settings.getRepositoryUrl()); + StatusCommand st = new StatusCommand(settings, workingDir); st.setFromRevId(fromVer.getId()); st.setToRevId(toVer.getId()); List<ModifiedFile> modifiedFiles = st.execute(); @@ -282,7 +295,7 @@ if (notDeletedFiles.isEmpty()) return; - CatCommand cc = new CatCommand(settings); + CatCommand cc = new CatCommand(settings, workingDir); cc.setRevId(toVer.getId()); File parentDir = cc.execute(notDeletedFiles); @@ -318,19 +331,19 @@ // builds patch by exporting files using specified version private void buildFullPatch(final Settings settings, @NotNull final ChangeSet toVer, final PatchBuilder builder, final CheckoutRules checkoutRules) throws IOException, VcsException { - CloneCommand cl = new CloneCommand(settings); - // clone from the local repository - cl.setRepository(settings.getLocalRepositoryDir().getAbsolutePath()); - cl.setToId(toVer.getId()); - cl.setUpdateWorkingDir(false); File tempDir = FileUtil.createTempDirectory("mercurial", toVer.getId()); try { + File customRepositoryDir = settings.getCustomWorkingDir(); + File mirrorDir = customRepositoryDir != null ? customRepositoryDir : myMirrorManager.getMirrorDir(settings.getRepositoryUrl()); final File repRoot = new File(tempDir, "rep"); - cl.setDestDir(repRoot.getAbsolutePath()); + CloneCommand cl = new CloneCommand(settings, repRoot); + // clone from the local repository + cl.setRepository(mirrorDir.getAbsolutePath()); + cl.setToId(toVer.getId()); + cl.setUpdateWorkingDir(false); cl.execute(); - UpdateCommand up = new UpdateCommand(settings); - up.setWorkDirectory(repRoot.getAbsolutePath()); + UpdateCommand up = new UpdateCommand(settings, repRoot); up.setToId(toVer.getId()); up.execute(); @@ -379,22 +392,20 @@ // updates current working copy of repository by pulling changes from the repository specified in VCS root private void syncClonedRepository(final VcsRoot root) throws VcsException { Settings settings = createSettings(root); - File workDir = settings.getLocalRepositoryDir(); - lockWorkDir(workDir); + File customRepositoryDir = settings.getCustomWorkingDir(); + File workingDir = customRepositoryDir != null ? customRepositoryDir : myMirrorManager.getMirrorDir(settings.getRepositoryUrl()); + lockWorkDir(workingDir); try { - if (settings.hasCopyOfRepository()) { - // update - PullCommand pull = new PullCommand(settings); + if (Settings.isValidRepository(workingDir)) { + PullCommand pull = new PullCommand(settings, workingDir); pull.execute(); } else { - // clone - CloneCommand cl = new CloneCommand(settings); - cl.setDestDir(workDir.getAbsolutePath()); + CloneCommand cl = new CloneCommand(settings, workingDir); cl.setUpdateWorkingDir(false); cl.execute(); } } finally { - unlockWorkDir(workDir); + unlockWorkDir(workingDir); } } @@ -417,7 +428,8 @@ public Map<String, String> getBranchesRevisions(@NotNull VcsRoot root) throws VcsException { syncClonedRepository(root); Settings settings = createSettings(root); - BranchesCommand branches = new BranchesCommand(settings); + File workingDir = myMirrorManager.getMirrorDir(settings.getRepositoryUrl()); + BranchesCommand branches = new BranchesCommand(settings, workingDir); Map<String, String> result = new HashMap<String, String>(); for (Map.Entry<String, ChangeSet> entry : branches.execute().entrySet()) { result.put(entry.getKey(), entry.getValue().getId()); @@ -443,7 +455,8 @@ private String getBranchPoint(@NotNull VcsRoot root, String branchOneRev, String branchTwoRev) throws VcsException { Settings settings = createSettings(root); - LogCommand lc = new LogCommand(settings); + File workingDir = myMirrorManager.getMirrorDir(settings.getRepositoryUrl()); + LogCommand lc = new LogCommand(settings, workingDir); lc.setFromRevId(new ChangeSetRevision(branchOneRev).getId()); lc.setToRevId(new ChangeSetRevision(branchTwoRev).getId()); lc.setLimit(1); @@ -467,7 +480,8 @@ if (currentVersion == null) return result; Settings settings = createSettings(root); - LogCommand lc = new LogCommand(settings); + File workingDir = myMirrorManager.getMirrorDir(settings.getRepositoryUrl()); + LogCommand lc = new LogCommand(settings, workingDir); String fromId = new ChangeSetRevision(fromVersion).getId(); lc.setFromRevId(fromId); lc.setToRevId(new ChangeSetRevision(currentVersion).getId()); @@ -477,7 +491,7 @@ } // invoke status command for each changeset and determine what files were modified in these changesets - StatusCommand st = new StatusCommand(settings); + StatusCommand st = new StatusCommand(settings, workingDir); ChangeSet prev = new ChangeSet(fromVersion); for (ChangeSet cur : changeSets) { if (cur.getId().equals(fromId)) continue; // skip already reported changeset @@ -563,26 +577,20 @@ } private void removeOldWorkFolders() { - Set<File> workDirs = getAllClonedRepos(); - if (workDirs == null) return; + Set<File> workDirs = new HashSet<File>(myMirrorManager.getMirrors()); for (VcsRoot vcsRoot: getMercurialVcsRoots()) { try { Settings s = createSettings(vcsRoot); - workDirs.remove(PathUtil.getCanonicalFile(s.getLocalRepositoryDir())); + File customWorkingDir = s.getCustomWorkingDir(); + File workingDir = customWorkingDir != null ? customWorkingDir : myMirrorManager.getMirrorDir(s.getRepositoryUrl()); + workDirs.remove(PathUtil.getCanonicalFile(workingDir)); } catch (VcsException e) { Loggers.VCS.error(e); } } - for (File f: workDirs) { - lockWorkDir(f); - try { - FileUtil.delete(f); - } finally { - unlockWorkDir(f); - } - } + deleteWithLocking(workDirs); } private Collection<VcsRoot> getMercurialVcsRoots() { @@ -595,44 +603,25 @@ return res; } - @Nullable - private Set<File> getAllClonedRepos() { - File workFoldersParent = myDefaultWorkFolderParent; - if (!workFoldersParent.isDirectory()) return null; - - Set<File> workDirs = new HashSet<File>(); - File[] files = workFoldersParent.listFiles(new FileFilter() { - public boolean accept(final File file) { - return file.isDirectory() && file.getName().startsWith(Settings.DEFAULT_WORK_DIR_PREFIX); - } - }); - if (files != null) { - for (File f: files) { - workDirs.add(PathUtil.getCanonicalFile(f)); - } - } - return workDirs; - } - public String label(@NotNull String label, @NotNull String version, @NotNull VcsRoot root, @NotNull CheckoutRules checkoutRules) throws VcsException { syncClonedRepository(root); Settings settings = createSettings(root); - + File workingDir = myMirrorManager.getMirrorDir(settings.getRepositoryUrl()); // I do not know why but hg tag does not work correctly if // update command was not invoked for the current repo // in such case if there were no tags before Mercurial attempts to // create new head when tag is pushed to the parent repository - UpdateCommand uc = new UpdateCommand(settings); + UpdateCommand uc = new UpdateCommand(settings, workingDir); uc.execute(); String fixedTagname = fixTagName(label); - TagCommand tc = new TagCommand(settings); + TagCommand tc = new TagCommand(settings, workingDir); tc.setRevId(new ChangeSet(version).getId()); tc.setTag(fixedTagname); tc.execute(); - PushCommand pc = new PushCommand(settings); + PushCommand pc = new PushCommand(settings, workingDir); // pc.setForce(true); pc.execute(); return fixedTagname; @@ -649,8 +638,8 @@ } private Settings createSettings(final VcsRoot root) throws VcsException { - Settings settings = new Settings(myDefaultWorkFolderParent, root); - String customClonePath = root.getProperty(Constants.SERVER_CLONE_PATH_PROP); + Settings settings = new Settings(root); + String customClonePath = settings.getCustomClonePath(); if (!StringUtil.isEmptyOrSpaces(customClonePath) && !myDefaultWorkFolderParent.equals(new File(customClonePath).getAbsoluteFile())) { File parentDir = new File(customClonePath); createClonedRepositoryParentDir(parentDir); @@ -663,9 +652,7 @@ } File customWorkingDir = new File(parentDir, repPath); - settings.setWorkingDir(customWorkingDir); - } else { - createClonedRepositoryParentDir(myDefaultWorkFolderParent); + settings.setCustomWorkingDir(customWorkingDir); } return settings; }
--- a/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/AgentSideCheckoutTest.java Wed Feb 16 13:35:57 2011 +0300 +++ b/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/AgentSideCheckoutTest.java Tue Mar 01 16:47:00 2011 +0300 @@ -15,18 +15,24 @@ */ package jetbrains.buildServer.buildTriggers.vcs.mercurial; +import jetbrains.buildServer.agent.AgentRunningBuild; +import jetbrains.buildServer.agent.BuildAgentConfiguration; import jetbrains.buildServer.agent.BuildProgressLogger; import jetbrains.buildServer.util.FileUtil; import jetbrains.buildServer.vcs.CheckoutRules; import jetbrains.buildServer.vcs.IncludeRule; import jetbrains.buildServer.vcs.VcsException; import jetbrains.buildServer.vcs.VcsRoot; -import org.jmock.Mock; +import org.jmock.Expectations; +import org.jmock.Mockery; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; import java.io.File; import java.io.IOException; +import java.util.HashMap; +import java.util.List; +import java.util.Map; /** * @author Pavel.Sher @@ -35,18 +41,35 @@ @Test public class AgentSideCheckoutTest extends BaseMercurialTestCase { private MercurialAgentSideVcsSupport myVcsSupport; - private Mock myProgressLoggerMock; private File myWorkDir; + private File myMirrorsRootDir; + private Mockery myContext; + private BuildProgressLogger myLogger; + private int myBuildCounter = 0; @Override @BeforeMethod protected void setUp() throws Exception { super.setUp(); - myVcsSupport = new MercurialAgentSideVcsSupport(); - myProgressLoggerMock = new Mock(BuildProgressLogger.class); + myContext = new Mockery(); + + myMirrorsRootDir = myTempFiles.createTempDir(); + + final BuildAgentConfiguration agentConfig = myContext.mock(BuildAgentConfiguration.class); + myContext.checking(new Expectations() {{ + allowing(agentConfig).getCacheDirectory("mercurial"); will(returnValue(myMirrorsRootDir)); + }}); + + myVcsSupport = new MercurialAgentSideVcsSupport(agentConfig); + + myLogger = myContext.mock(BuildProgressLogger.class); + myContext.checking(new Expectations() {{ + allowing(myLogger).message(with(any(String.class))); + }}); + myWorkDir = myTempFiles.createTempDir(); - myProgressLoggerMock.stubs().method("message"); + } public void checkout_on_agent() throws IOException, VcsException { @@ -69,8 +92,19 @@ } private File doUpdate(final VcsRoot vcsRoot, final String version, final IncludeRule includeRule) throws VcsException { + return doUpdate(vcsRoot, version, includeRule, false); + } + + private File doUpdate(final VcsRoot vcsRoot, final String version, final IncludeRule includeRule, boolean useLocalMirrors) throws VcsException { File actualWorkDir = new File(myWorkDir, includeRule.getTo()); - myVcsSupport.getUpdater(vcsRoot, new CheckoutRules(""), version, myWorkDir, (BuildProgressLogger) myProgressLoggerMock.proxy()).process(includeRule, actualWorkDir); + final Map<String, String> sharedConfigParameters = new HashMap<String, String>(); + sharedConfigParameters.put("teamcity.hg.use.local.mirrors", String.valueOf(useLocalMirrors)); + final AgentRunningBuild build = myContext.mock(AgentRunningBuild.class, "build" + myBuildCounter++); + myContext.checking(new Expectations() {{ + allowing(build).getBuildLogger(); will(returnValue(myLogger)); + allowing(build).getSharedConfigParameters(); will(returnValue(sharedConfigParameters)); + }}); + myVcsSupport.getUpdater(vcsRoot, new CheckoutRules(""), version, myWorkDir, build, false).process(includeRule, actualWorkDir); File hgDir = new File(actualWorkDir, ".hg"); assertTrue(hgDir.isDirectory()); @@ -105,6 +139,66 @@ checkWorkingDir("patch4/after", workDir); } + public void by_default_local_mirror_not_created() throws IOException, VcsException { + List<File> mirrors = FileUtil.getSubDirectories(myMirrorsRootDir); + assertTrue(mirrors.isEmpty()); + VcsRoot root = createVcsRoot(simpleRepo()); + doUpdate(root, "3:9522278aa38d", new IncludeRule(".", ".", null)); + mirrors = FileUtil.getSubDirectories(myMirrorsRootDir); + assertTrue(mirrors.isEmpty()); + } + + public void local_mirror_is_created() throws IOException, VcsException { + List<File> mirrors = FileUtil.getSubDirectories(myMirrorsRootDir); + assertTrue(mirrors.isEmpty()); + VcsRoot root = createVcsRoot(simpleRepo()); + doUpdate(root, "3:9522278aa38d", new IncludeRule(".", ".", null), true); + mirrors = FileUtil.getSubDirectories(myMirrorsRootDir); + assertEquals(1, mirrors.size()); + File mirror = mirrors.get(0); + File dotHg = new File(mirror, ".hg"); + assertTrue(dotHg.exists()); + File hgrc = new File(dotHg, "hgrc"); + String hgrcContent = FileUtil.readText(hgrc); + assertTrue(hgrcContent.contains("default = " + root.getProperty(Constants.REPOSITORY_PROP))); + } + + public void new_repository_is_cloned_from_local_mirror() throws IOException, VcsException { + VcsRoot root = createVcsRoot(simpleRepo()); + File workingDir = doUpdate(root, "3:9522278aa38d", new IncludeRule(".", ".", null), true); + File mirrorDir = FileUtil.getSubDirectories(myMirrorsRootDir).get(0); + File hgrc = new File(workingDir, ".hg" + File.separator + "hgrc"); + String hgrcContent = FileUtil.readText(hgrc); + assertTrue(hgrcContent.contains("default = " + mirrorDir.getCanonicalPath())); + } + + public void repository_cloned_from_remote_start_cloning_from_local_mirror() throws IOException, VcsException { + VcsRoot root = createVcsRoot(simpleRepo()); + //clone from remote repository + File workingDir = doUpdate(root, "3:9522278aa38d", new IncludeRule(".", ".", null)); + String hgrcContent = FileUtil.readText(new File(workingDir, ".hg" + File.separator + "hgrc")); + + File workingDir2 = doUpdate(root, "3:9522278aa38d", new IncludeRule(".", ".", null), true); + File newMirrorDir = FileUtil.getSubDirectories(myMirrorsRootDir).get(0); + String hgrcContent2 = FileUtil.readText(new File(workingDir2, ".hg" + File.separator + "hgrc")); + assertFalse(hgrcContent2.equals(hgrcContent));//repository settings are changed + assertTrue(hgrcContent2.contains("default = " + newMirrorDir.getCanonicalPath()));//now it clones from local mirror + } + + public void repository_cloned_from_local_mirror_start_cloning_from_remote() throws IOException, VcsException { + VcsRoot root = createVcsRoot(simpleRepo()); + //clone from remote repository + File workingDir = doUpdate(root, "3:9522278aa38d", new IncludeRule(".", ".", null), true); + String hgrcContent = FileUtil.readText(new File(workingDir, ".hg" + File.separator + "hgrc")); + File newMirrorDir = FileUtil.getSubDirectories(myMirrorsRootDir).get(0); + assertTrue(hgrcContent.contains("default = " + newMirrorDir.getCanonicalPath()));//now it clones from local mirror + + File workingDir2 = doUpdate(root, "3:9522278aa38d", new IncludeRule(".", ".", null)); + String hgrcContent2 = FileUtil.readText(new File(workingDir2, ".hg" + File.separator + "hgrc")); + assertFalse(hgrcContent2.equals(hgrcContent));//repository settings are changed + assertTrue(hgrcContent2.contains("default = " + root.getProperty(Constants.REPOSITORY_PROP)));//now it clones from remote + } + protected String getTestDataPath() { return "mercurial-tests/testData"; }
--- a/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/MercurialVcsSupportTest.java Wed Feb 16 13:35:57 2011 +0300 +++ b/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/MercurialVcsSupportTest.java Tue Mar 01 16:47:00 2011 +0300 @@ -312,15 +312,15 @@ VcsRootImpl vcsRoot = createVcsRoot(simpleRepo()); String repPath = vcsRoot.getProperty(Constants.REPOSITORY_PROP); vcsRoot.addProperty(Constants.REPOSITORY_PROP, repPath + "#test_branch"); - Settings settings = new Settings(new File(myServerPaths.getCachesDir()), vcsRoot); + Settings settings = new Settings(vcsRoot); assertEquals("test_branch", settings.getBranchName()); vcsRoot.addProperty(Constants.REPOSITORY_PROP, repPath + "#"); - settings = new Settings(new File(myServerPaths.getCachesDir()), vcsRoot); + settings = new Settings(vcsRoot); assertEquals("default", settings.getBranchName()); vcsRoot.addProperty(Constants.REPOSITORY_PROP, repPath); - settings = new Settings(new File(myServerPaths.getCachesDir()), vcsRoot); + settings = new Settings(vcsRoot); assertEquals("default", settings.getBranchName()); } @@ -404,7 +404,7 @@ VcsRootImpl root = new VcsRootImpl(1, Constants.VCS_NAME); root.addAllProperties(myVcs.getDefaultVcsProperties()); root.addProperty(Constants.REPOSITORY_PROP, "http://host.com/path"); - Settings settings = new Settings(new File("."), root); + Settings settings = new Settings(root); assertFalse(settings.isUncompressedTransfer()); }
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/MirrorManagerTest.java Tue Mar 01 16:47:00 2011 +0300 @@ -0,0 +1,106 @@ +package jetbrains.buildServer.buildTriggers.vcs.mercurial; + +import jetbrains.buildServer.TempFiles; +import jetbrains.buildServer.util.Hash; +import junit.framework.TestCase; +import org.testng.annotations.AfterMethod; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +import java.io.File; +import java.io.IOException; +import java.util.List; + +/** + * @author dmitry.neverov + */ +@Test +public class MirrorManagerTest extends TestCase { + + private TempFiles myTempFiles; + private File myRootDir; + private MirrorManager myManager; + + + @BeforeMethod + public void setUp() throws Exception { + myTempFiles = new TempFiles(); + myRootDir = myTempFiles.createTempDir(); + myManager = new MirrorManager(myRootDir); + } + + @AfterMethod + public void tearDown() { + myTempFiles.cleanup(); + } + + + public void getMirrorDir_returns_dir_under_root() { + File mirrorDir = myManager.getMirrorDir("hg://some.com/repository.hg"); + assertEquals(myRootDir, mirrorDir.getParentFile()); + assertTrue(mirrorDir.exists()); + } + + + public void getMirrorDir_returns_same_result_for_same_url() { + String url = "hg://some.com/repository.hg"; + assertEquals(myManager.getMirrorDir(url), myManager.getMirrorDir(url)); + } + + + public void getMirrors_remember_created_repositories() { + File mirrorDir1 = myManager.getMirrorDir("hg://some.com/repository.hg"); + File mirrorDir2 = myManager.getMirrorDir("hg://other.com/repository.hg"); + List<File> mirrors = myManager.getMirrors(); + assertEquals(2, mirrors.size()); + assertTrue(mirrors.contains(mirrorDir1)); + assertTrue(mirrors.contains(mirrorDir2)); + } + + + public void should_handle_url_collisions() throws IOException { + final String url1 = "hg://some.com/repository.hg"; + final String url2 = "hg://other.com/repository.hg"; + + MirrorManager.HashCalculator hashWithCollision = new MirrorManager.HashCalculator() { + public long calc(String value) { + if (value.equals(url1) || value.equals(url2)) { + return 0;//emulate collision + } else { + return Hash.calc(value); + } + } + }; + + //alone they get dir with the same name: + MirrorManager mm1 = new MirrorManager(myTempFiles.createTempDir()); + mm1.setHashCalculator(hashWithCollision); + File separateMirrorDir1 = mm1.getMirrorDir(url1); + + MirrorManager mm2 = new MirrorManager(myTempFiles.createTempDir()); + mm2.setHashCalculator(hashWithCollision); + File separateMirrorDir2 = mm2.getMirrorDir(url2); + + assertEquals(separateMirrorDir1.getName(), separateMirrorDir2.getName()); + + myManager.setHashCalculator(hashWithCollision); + File mirrorDir1 = myManager.getMirrorDir(url1); + File mirrorDir2 = myManager.getMirrorDir(url2); + assertFalse(mirrorDir1.equals(mirrorDir2)); + } + + + public void should_survive_restart() throws IOException { + String url1 = "hg://some.com/repository.hg"; + String url2 = "hg://other.com/repository.hg?param = 1"; + File mirrorDir1 = myManager.getMirrorDir(url1); + File mirrorDir2 = myManager.getMirrorDir(url2); + + //emulate restart by creating a new manager for the same rootDir + MirrorManager manager = new MirrorManager(myRootDir); + + assertEquals(2, manager.getMirrors().size()); + assertTrue(manager.getMirrors().contains(mirrorDir1)); + assertTrue(manager.getMirrors().contains(mirrorDir2)); + } +}
--- a/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/SettingsTest.java Wed Feb 16 13:35:57 2011 +0300 +++ b/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/SettingsTest.java Tue Mar 01 16:47:00 2011 +0300 @@ -15,78 +15,95 @@ */ package jetbrains.buildServer.buildTriggers.vcs.mercurial; +import jetbrains.buildServer.TempFiles; import jetbrains.buildServer.buildTriggers.vcs.mercurial.command.Settings; import jetbrains.buildServer.vcs.impl.VcsRootImpl; -import junit.framework.Assert; +import junit.framework.TestCase; +import org.testng.annotations.AfterMethod; +import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; -import java.io.File; - /** * @author Pavel.Sher */ @Test -public class SettingsTest extends Assert { +public class SettingsTest extends TestCase { + + private TempFiles myTempFiles = new TempFiles(); + private MirrorManager myMirrorManager; + + @Override + @BeforeMethod + public void setUp() throws Exception { + myMirrorManager = new MirrorManager(myTempFiles.createTempDir()); + } + + @Override + @AfterMethod + public void tearDown() throws Exception { + myTempFiles.cleanup(); + } + public void test_url_without_credentials() { VcsRootImpl vcsRoot = createVcsRoot("http://host.com/path"); - Settings settings = new Settings(new File("."), vcsRoot); + Settings settings = new Settings(vcsRoot); assertEquals("http://user:pwd@host.com/path", settings.getRepositoryUrl()); } public void test_url_with_credentials() { VcsRootImpl vcsRoot = createVcsRoot("http://user:pwd@host.com/path"); - Settings settings = new Settings(new File("."), vcsRoot); + Settings settings = new Settings(vcsRoot); assertEquals("http://user:pwd@host.com/path", settings.getRepositoryUrl()); } public void test_url_with_username() { VcsRootImpl vcsRoot = createVcsRoot("http://user@host.com/path"); - Settings settings = new Settings(new File("."), vcsRoot); + Settings settings = new Settings(vcsRoot); assertEquals("http://user:pwd@host.com/path", settings.getRepositoryUrl()); } public void test_url_with_at_after_slash() { VcsRootImpl vcsRoot = createVcsRoot("http://host.com/path@"); - Settings settings = new Settings(new File("."), vcsRoot); + Settings settings = new Settings(vcsRoot); assertEquals("http://user:pwd@host.com/path@", settings.getRepositoryUrl()); } public void test_url_with_at_in_username() { VcsRootImpl vcsRoot = createVcsRoot("http://host.com/path", "my.name@gmail.com", "1234"); - Settings settings = new Settings(new File("."), vcsRoot); + Settings settings = new Settings(vcsRoot); assertEquals("http://my.name%40gmail.com:1234@host.com/path", settings.getRepositoryUrl()); } /** TW-13768 */ public void test_underscore_in_host() { VcsRootImpl vcsRoot = createVcsRoot("http://Klekovkin.SDK_GARANT:8000/", "my.name@gmail.com", "1234"); - Settings settings = new Settings(new File("."), vcsRoot); + Settings settings = new Settings(vcsRoot); assertEquals("http://my.name%40gmail.com:1234@Klekovkin.SDK_GARANT:8000/", settings.getRepositoryUrl()); } /** TW-13768 */ public void test_underscore_in_host_with_credentials_in_url() { VcsRootImpl vcsRoot = createVcsRoot("http://me:mypass@Klekovkin.SDK_GARANT:8000/"); - Settings settings = new Settings(new File("."), vcsRoot); + Settings settings = new Settings(vcsRoot); assertEquals("http://me:mypass@Klekovkin.SDK_GARANT:8000/", settings.getRepositoryUrl()); } public void test_windows_path() throws Exception { VcsRootImpl vcsRoot = createVcsRoot("c:\\windows\\path"); - Settings settings = new Settings(new File("."), vcsRoot); + Settings settings = new Settings(vcsRoot); assertEquals("c:\\windows\\path", settings.getRepositoryUrl()); } public void test_file_scheme_has_no_credentials() { VcsRootImpl vcsRoot = createVcsRoot("file:///path/to/repo", "my.name@gmail.com", "1234"); - Settings settings = new Settings(new File("."), vcsRoot); + Settings settings = new Settings(vcsRoot); assertEquals("file:///path/to/repo", settings.getRepositoryUrl()); } public void uncompressed_transfer() { VcsRootImpl root = createVcsRoot("http://host.com/path"); root.addProperty(Constants.UNCOMPRESSED_TRANSFER, "true"); - Settings settings = new Settings(new File("."), root); + Settings settings = new Settings(root); assertTrue(settings.isUncompressedTransfer()); }
--- a/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/BaseCommandTestCase.java Wed Feb 16 13:35:57 2011 +0300 +++ b/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/BaseCommandTestCase.java Tue Mar 01 16:47:00 2011 +0300 @@ -19,6 +19,7 @@ import jetbrains.buildServer.TempFiles; import jetbrains.buildServer.buildTriggers.vcs.mercurial.Constants; import jetbrains.buildServer.buildTriggers.vcs.mercurial.LocalRepositoryUtil; +import jetbrains.buildServer.buildTriggers.vcs.mercurial.MirrorManager; import jetbrains.buildServer.buildTriggers.vcs.mercurial.Util; import jetbrains.buildServer.vcs.VcsException; import jetbrains.buildServer.vcs.VcsRoot; @@ -36,6 +37,9 @@ private String myPassword; private boolean myCloneRequired; + public BaseCommandTestCase() { + } + protected void setRepository(final String repository, boolean cloneRequired) { myRepository = repository; myCloneRequired = cloneRequired; @@ -69,25 +73,24 @@ TempFiles tf = new TempFiles(); File parentDir = tf.createTempDir(); - final File workingDir = new File(parentDir, "rep").getAbsoluteFile(); + MirrorManager mirrorManager = new MirrorManager(parentDir); VcsRoot vcsRoot = new VcsRootImpl(1, vcsRootProps); - Settings settings = new Settings(workingDir.getParentFile(), vcsRoot); - settings.setWorkingDir(workingDir); + Settings settings = new Settings(vcsRoot); + final File workingDir = mirrorManager.getMirrorDir(settings.getRepositoryUrl()); + settings.setCustomWorkingDir(workingDir); try { if (myCloneRequired) { - CloneCommand cl = new CloneCommand(settings); - cl.setDestDir(settings.getLocalRepositoryDir().getAbsolutePath()); - cl.execute(); + new CloneCommand(settings, workingDir).execute(); } - return executor.execute(settings); + return executor.execute(settings, workingDir); } finally { tf.cleanup(); } } public interface CommandExecutor<T> { - T execute(@NotNull Settings settings) throws VcsException; + T execute(@NotNull Settings settings, File workingDir) throws VcsException; } }
--- a/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/LogCommandTest.java Wed Feb 16 13:35:57 2011 +0300 +++ b/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/LogCommandTest.java Tue Mar 01 16:47:00 2011 +0300 @@ -20,6 +20,7 @@ import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; +import java.io.File; import java.io.IOException; import java.util.List; @@ -85,8 +86,8 @@ private List<ChangeSet> runLog(final String fromId, final String toId) throws IOException, VcsException { return runCommand(new CommandExecutor<List<ChangeSet>>() { - public List<ChangeSet> execute(@NotNull final Settings settings) throws VcsException { - LogCommand lc = new LogCommand(settings); + public List<ChangeSet> execute(@NotNull final Settings settings, @NotNull File workingDir) throws VcsException { + LogCommand lc = new LogCommand(settings, workingDir); lc.setFromRevId(fromId); lc.setToRevId(toId); return lc.execute();
--- a/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/PushCommandTest.java Wed Feb 16 13:35:57 2011 +0300 +++ b/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/PushCommandTest.java Tue Mar 01 16:47:00 2011 +0300 @@ -19,6 +19,7 @@ import org.jetbrains.annotations.NotNull; import org.testng.annotations.Test; +import java.io.File; import java.io.IOException; /** @@ -34,8 +35,8 @@ try { runCommand(new CommandExecutor<Boolean>() { - public Boolean execute(@NotNull final Settings settings) throws VcsException { - PushCommand cmd = new PushCommand(settings); + public Boolean execute(@NotNull final Settings settings, @NotNull File workingDir) throws VcsException { + PushCommand cmd = new PushCommand(settings, workingDir); cmd.execute(); return null; }
--- a/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/StatusCommandTest.java Wed Feb 16 13:35:57 2011 +0300 +++ b/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/StatusCommandTest.java Tue Mar 01 16:47:00 2011 +0300 @@ -54,8 +54,8 @@ private List<ModifiedFile> runStatus(final String fromId, final String toId) throws IOException, VcsException { return runCommand(new CommandExecutor<List<ModifiedFile>>() { - public List<ModifiedFile> execute(@NotNull final Settings settings) throws VcsException { - StatusCommand st = new StatusCommand(settings); + public List<ModifiedFile> execute(@NotNull final Settings settings, @NotNull final File workingDir) throws VcsException { + StatusCommand st = new StatusCommand(settings, workingDir); st.setFromRevId(fromId); st.setToRevId(toId); return st.execute();
--- a/mercurial-tests/src/testng.xml Wed Feb 16 13:35:57 2011 +0300 +++ b/mercurial-tests/src/testng.xml Tue Mar 01 16:47:00 2011 +0300 @@ -8,6 +8,7 @@ <class name="jetbrains.buildServer.buildTriggers.vcs.mercurial.MercurialVcsSupportTest"/> <class name="jetbrains.buildServer.buildTriggers.vcs.mercurial.AgentSideCheckoutTest"/> <class name="jetbrains.buildServer.buildTriggers.vcs.mercurial.SettingsTest"/> + <class name="jetbrains.buildServer.buildTriggers.vcs.mercurial.MirrorManagerTest"/> </classes> </test> </suite>
--- a/mercurial.ipr Wed Feb 16 13:35:57 2011 +0300 +++ b/mercurial.ipr Tue Mar 01 16:47:00 2011 +0300 @@ -388,6 +388,9 @@ </library> <library name="JMock"> <CLASSES> + <root url="jar://$PROJECT_DIR$/mercurial-tests/lib/hamcrest-library-1.1.jar!/" /> + <root url="jar://$PROJECT_DIR$/mercurial-tests/lib/hamcrest-core-1.1.jar!/" /> + <root url="jar://$PROJECT_DIR$/mercurial-tests/lib/jmock-2.5.1.jar!/" /> <root url="jar://$PROJECT_DIR$/mercurial-tests/lib/jmock-SNAPSHOT.jar!/" /> </CLASSES> <JAVADOC />