# HG changeset patch # User Dmitry Neverov # Date 1298648924 -10800 # Node ID d314f06e6ee369f0602d8c248b6bb4e7c0c80685 # Parent d94b260c4808f5c45eef5ce1f8c9302b212d5dd0 Agent caches. Settings contains only root settings and nothing more. Commands use settings and a directory where they should be run. diff -r d94b260c4808 -r d314f06e6ee3 mercurial-agent/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/MercurialAgentSideVcsSupport.java --- 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 Fri Feb 25 18:48:44 2011 +0300 @@ -15,6 +15,7 @@ */ package jetbrains.buildServer.buildTriggers.vcs.mercurial; +import jetbrains.buildServer.agent.BuildAgentConfiguration; import jetbrains.buildServer.agent.BuildProgressLogger; import jetbrains.buildServer.agent.vcs.AgentVcsSupport; import jetbrains.buildServer.agent.vcs.IncludeRuleUpdater; @@ -29,63 +30,34 @@ import org.jetbrains.annotations.NotNull; import java.io.File; -import java.io.IOException; public class MercurialAgentSideVcsSupport extends AgentVcsSupport implements UpdateByIncludeRules { - private void updateWorkingDir(final Settings settings, final String version, final BuildProgressLogger logger) throws VcsException { + + private final MirrorManager myMirrorManager; + + public MercurialAgentSideVcsSupport(BuildAgentConfiguration agentConfiguration) { + myMirrorManager = new MirrorManager(agentConfiguration.getCacheDirectory("mercurial")); + } + + 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); + UpdateCommand uc = new UpdateCommand(settings, workingDir); ChangeSet cs = new ChangeSet(version); uc.setToId(cs.getId()); uc.execute(); logger.message("Working directory updated successfully"); } - private File cloneRepository(final Settings settings) throws VcsException { - File tempDir; + private void cloneFromLocalMirror(final Settings settings, File workingDir) throws VcsException { + File mirrorDir = myMirrorManager.getMirrorDir(settings.getRepositoryUrl()); 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()); + CloneCommand cc = new CloneCommand(settings, workingDir); + cc.setRepository(mirrorDir.getCanonicalPath()); + cc.setUpdateWorkingDir(false); + cc.execute(); + } catch (Exception e) { + throw new VcsException("Failed to clone from local mirror at " + mirrorDir.getAbsolutePath(), e); } - - CloneCommand cc = new CloneCommand(settings); - cc.setDestDir(tempDir.getAbsolutePath()); - - cc.setUpdateWorkingDir(false); - cc.execute(); - return tempDir; - } - - /** - * 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 @@ -103,41 +75,66 @@ 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."); - } - } + checkRuleIsValid(includeRule); - 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"); + Settings settings = new Settings(vcsRoot); + updateLocalMirror(vcsRoot, logger); + if (settings.isValidRepository(workingDir)) { + 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 { - // 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()); - } - - logger.message("Repository successfully moved to working directory: " + workingDir.getAbsolutePath()); + logger.message("No repository in working directory found, start cloning from local mirror"); + cloneFromLocalMirror(settings, workingDir); + logger.message("Repository successfully cloned to working directory: " + workingDir.getAbsolutePath()); } - updateWorkingDir(settings, toVersion, logger); + updateWorkingDir(settings, workingDir, toVersion, logger); } public void dispose() throws VcsException { } }; } + + 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); + CloneCommand cc = new CloneCommand(settings, mirrorDir); + cc.setUpdateWorkingDir(false); + cc.execute(); + logger.message("Local mirror successfully cloned to " + mirrorDir); + } + } + + 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 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; + } + } } diff -r d94b260c4808 -r d314f06e6ee3 mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/MirrorManager.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/MirrorManager.java Fri Feb 25 18:48:44 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 myMirrors = new HashMap(); + 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 getMirrors() { + myLock.readLock().lock(); + try { + return new ArrayList(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 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 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(); + } + } 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(); + } + } + + + private void saveMappingToFile() { + myLock.writeLock().lock(); + try { + StringBuilder sb = new StringBuilder(); + for (Map.Entry 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); + } +} diff -r d94b260c4808 -r d314f06e6ee3 mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/BaseCommand.java --- 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 Fri Feb 25 18:48:44 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; } diff -r d94b260c4808 -r d314f06e6ee3 mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/BranchesCommand.java --- 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 Fri Feb 25 18:48:44 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); } /** diff -r d94b260c4808 -r d314f06e6ee3 mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/CatCommand.java --- 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 Fri Feb 25 18:48:44 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) { diff -r d94b260c4808 -r d314f06e6ee3 mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/ChangedFilesCommand.java --- 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 Fri Feb 25 18:48:44 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) { diff -r d94b260c4808 -r d314f06e6ee3 mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/CloneCommand.java --- 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 Fri Feb 25 18:48:44 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); diff -r d94b260c4808 -r d314f06e6ee3 mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/IdentifyCommand.java --- 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 Fri Feb 25 18:48:44 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 { diff -r d94b260c4808 -r d314f06e6ee3 mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/LogCommand.java --- 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 Fri Feb 25 18:48:44 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) { diff -r d94b260c4808 -r d314f06e6ee3 mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/PullCommand.java --- 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 Fri Feb 25 18:48:44 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 { diff -r d94b260c4808 -r d314f06e6ee3 mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/PushCommand.java --- 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 Fri Feb 25 18:48:44 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) { diff -r d94b260c4808 -r d314f06e6ee3 mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/Settings.java --- 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 Fri Feb 25 18:48:44 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(); } } diff -r d94b260c4808 -r d314f06e6ee3 mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/StatusCommand.java --- 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 Fri Feb 25 18:48:44 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) { diff -r d94b260c4808 -r d314f06e6ee3 mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/TagCommand.java --- 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 Fri Feb 25 18:48:44 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) { diff -r d94b260c4808 -r d314f06e6ee3 mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/UpdateCommand.java --- 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 Fri Feb 25 18:48:44 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) { diff -r d94b260c4808 -r d314f06e6ee3 mercurial-server/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/MercurialVcsSupport.java --- 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 Fri Feb 25 18:48:44 2011 +0300 @@ -56,6 +56,7 @@ private ConcurrentMap myWorkDirLocks= new ConcurrentHashMap(); private VcsManager myVcsManager; private File myDefaultWorkFolderParent; + private MirrorManager myMirrorManager; public MercurialVcsSupport(@NotNull final VcsManager vcsManager, @NotNull ServerPaths paths, @@ -63,6 +64,7 @@ @NotNull EventDispatcher 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 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 filesForDelete) { + for (File f : filesForDelete) { + lockWorkDir(f); + try { + FileUtil.delete(f); + } finally { + unlockWorkDir(f); + } + } + } + private Collection 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 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 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 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 result = new HashMap(); for (Map.Entry 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 workDirs = getAllClonedRepos(); - if (workDirs == null) return; + Set workDirs = new HashSet(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 getMercurialVcsRoots() { @@ -595,44 +603,25 @@ return res; } - @Nullable - private Set getAllClonedRepos() { - File workFoldersParent = myDefaultWorkFolderParent; - if (!workFoldersParent.isDirectory()) return null; - - Set workDirs = new HashSet(); - 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; } diff -r d94b260c4808 -r d314f06e6ee3 mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/AgentSideCheckoutTest.java --- 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 Fri Feb 25 18:48:44 2011 +0300 @@ -15,6 +15,7 @@ */ package jetbrains.buildServer.buildTriggers.vcs.mercurial; +import jetbrains.buildServer.agent.BuildAgentConfiguration; import jetbrains.buildServer.agent.BuildProgressLogger; import jetbrains.buildServer.util.FileUtil; import jetbrains.buildServer.vcs.CheckoutRules; @@ -22,11 +23,13 @@ import jetbrains.buildServer.vcs.VcsException; import jetbrains.buildServer.vcs.VcsRoot; import org.jmock.Mock; +import org.jmock.core.stub.ReturnStub; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; import java.io.File; import java.io.IOException; +import java.util.List; /** * @author Pavel.Sher @@ -37,13 +40,18 @@ private MercurialAgentSideVcsSupport myVcsSupport; private Mock myProgressLoggerMock; private File myWorkDir; + private File myMirrorsRootDir; @Override @BeforeMethod protected void setUp() throws Exception { super.setUp(); - myVcsSupport = new MercurialAgentSideVcsSupport(); + Mock agentConfigMock = new Mock(BuildAgentConfiguration.class); + myMirrorsRootDir = myTempFiles.createTempDir(); + agentConfigMock.stubs().method("getCacheDirectory").will(new ReturnStub(myMirrorsRootDir)); + + myVcsSupport = new MercurialAgentSideVcsSupport((BuildAgentConfiguration) agentConfigMock.proxy()); myProgressLoggerMock = new Mock(BuildProgressLogger.class); myWorkDir = myTempFiles.createTempDir(); myProgressLoggerMock.stubs().method("message"); @@ -105,6 +113,51 @@ checkWorkingDir("patch4/after", workDir); } + public void local_mirror_is_created() throws IOException, VcsException { + List mirrors = FileUtil.getSubDirectories(myMirrorsRootDir); + assertTrue(mirrors.isEmpty()); + VcsRoot root = createVcsRoot(simpleRepo()); + doUpdate(root, "3:9522278aa38d", new IncludeRule(".", ".", null)); + 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)); + 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()); + File workingDir = doUpdate(root, "3:9522278aa38d", new IncludeRule(".", ".", null)); + String hgrcContent = FileUtil.readText(new File(workingDir, ".hg" + File.separator + "hgrc")); + + //at this moment repository is cloned from local mirror, if we change mirrorsRootDir and do update + //this repository should start cloning from new local mirror + + //create vcsSupport that use mirrorManager with changed mirrorsRootDir + Mock agentConfigMock = new Mock(BuildAgentConfiguration.class); + myMirrorsRootDir = myTempFiles.createTempDir(); + agentConfigMock.stubs().method("getCacheDirectory").will(new ReturnStub(myMirrorsRootDir)); + myVcsSupport = new MercurialAgentSideVcsSupport((BuildAgentConfiguration) agentConfigMock.proxy()); + + File workingDir2 = doUpdate(root, "3:9522278aa38d", new IncludeRule(".", ".", null)); + 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 different local mirror + } + protected String getTestDataPath() { return "mercurial-tests/testData"; } diff -r d94b260c4808 -r d314f06e6ee3 mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/MercurialVcsSupportTest.java --- 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 Fri Feb 25 18:48:44 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()); } diff -r d94b260c4808 -r d314f06e6ee3 mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/MirrorManagerTest.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/MirrorManagerTest.java Fri Feb 25 18:48:44 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 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)); + } +} diff -r d94b260c4808 -r d314f06e6ee3 mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/SettingsTest.java --- 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 Fri Feb 25 18:48:44 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()); } diff -r d94b260c4808 -r d314f06e6ee3 mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/BaseCommandTestCase.java --- 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 Fri Feb 25 18:48:44 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 execute(@NotNull Settings settings) throws VcsException; + T execute(@NotNull Settings settings, File workingDir) throws VcsException; } } diff -r d94b260c4808 -r d314f06e6ee3 mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/LogCommandTest.java --- 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 Fri Feb 25 18:48:44 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 runLog(final String fromId, final String toId) throws IOException, VcsException { return runCommand(new CommandExecutor>() { - public List execute(@NotNull final Settings settings) throws VcsException { - LogCommand lc = new LogCommand(settings); + public List 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(); diff -r d94b260c4808 -r d314f06e6ee3 mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/PushCommandTest.java --- 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 Fri Feb 25 18:48:44 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() { - 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; } diff -r d94b260c4808 -r d314f06e6ee3 mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/StatusCommandTest.java --- 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 Fri Feb 25 18:48:44 2011 +0300 @@ -54,8 +54,8 @@ private List runStatus(final String fromId, final String toId) throws IOException, VcsException { return runCommand(new CommandExecutor>() { - public List execute(@NotNull final Settings settings) throws VcsException { - StatusCommand st = new StatusCommand(settings); + public List 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(); diff -r d94b260c4808 -r d314f06e6ee3 mercurial-tests/src/testng.xml --- a/mercurial-tests/src/testng.xml Wed Feb 16 13:35:57 2011 +0300 +++ b/mercurial-tests/src/testng.xml Fri Feb 25 18:48:44 2011 +0300 @@ -8,6 +8,7 @@ +