changeset 666:557d41234a97

Merge branch Gaya-8.0.x
author Dmitry Neverov <dmitry.neverov@jetbrains.com>
date Tue, 01 Oct 2013 12:52:11 +0400
parents cdbda2058141 (diff) 55cf557a14de (current diff)
children bcee3a8b2c7b
files
diffstat 149 files changed, 1636 insertions(+), 386 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/.idea/codeStyleSettings.xml	Tue Oct 01 12:52:11 2013 +0400
@@ -0,0 +1,85 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="ProjectCodeStyleSettingsManager">
+    <option name="PER_PROJECT_SETTINGS">
+      <value>
+        <option name="USE_SAME_INDENTS" value="true" />
+        <option name="IGNORE_SAME_INDENTS_FOR_LANGUAGES" value="true" />
+        <option name="OTHER_INDENT_OPTIONS">
+          <value>
+            <option name="INDENT_SIZE" value="2" />
+            <option name="CONTINUATION_INDENT_SIZE" value="8" />
+            <option name="TAB_SIZE" value="2" />
+            <option name="USE_TAB_CHARACTER" value="false" />
+            <option name="SMART_TABS" value="false" />
+            <option name="LABEL_INDENT_SIZE" value="0" />
+            <option name="LABEL_INDENT_ABSOLUTE" value="false" />
+            <option name="USE_RELATIVE_INDENTS" value="false" />
+          </value>
+        </option>
+        <option name="LINE_SEPARATOR" value="&#13;&#10;" />
+        <option name="FIELD_NAME_PREFIX" value="my" />
+        <option name="STATIC_FIELD_NAME_PREFIX" value="our" />
+        <option name="RIGHT_MARGIN" value="140" />
+        <XML>
+          <option name="XML_LEGACY_SETTINGS_IMPORTED" value="true" />
+        </XML>
+        <codeStyleSettings language="CSS">
+          <indentOptions>
+            <option name="INDENT_SIZE" value="2" />
+            <option name="TAB_SIZE" value="2" />
+          </indentOptions>
+        </codeStyleSettings>
+        <codeStyleSettings language="CoffeeScript">
+          <indentOptions>
+            <option name="CONTINUATION_INDENT_SIZE" value="8" />
+          </indentOptions>
+        </codeStyleSettings>
+        <codeStyleSettings language="Groovy">
+          <indentOptions>
+            <option name="INDENT_SIZE" value="2" />
+            <option name="TAB_SIZE" value="2" />
+          </indentOptions>
+        </codeStyleSettings>
+        <codeStyleSettings language="JAVA">
+          <indentOptions>
+            <option name="INDENT_SIZE" value="2" />
+            <option name="TAB_SIZE" value="2" />
+          </indentOptions>
+        </codeStyleSettings>
+        <codeStyleSettings language="JSP">
+          <indentOptions>
+            <option name="INDENT_SIZE" value="2" />
+            <option name="TAB_SIZE" value="2" />
+          </indentOptions>
+        </codeStyleSettings>
+        <codeStyleSettings language="JavaScript">
+          <indentOptions>
+            <option name="INDENT_SIZE" value="2" />
+            <option name="CONTINUATION_INDENT_SIZE" value="8" />
+            <option name="TAB_SIZE" value="2" />
+          </indentOptions>
+        </codeStyleSettings>
+        <codeStyleSettings language="XML">
+          <indentOptions>
+            <option name="INDENT_SIZE" value="2" />
+            <option name="TAB_SIZE" value="2" />
+          </indentOptions>
+        </codeStyleSettings>
+        <codeStyleSettings language="jet">
+          <indentOptions>
+            <option name="INDENT_SIZE" value="2" />
+            <option name="TAB_SIZE" value="2" />
+          </indentOptions>
+        </codeStyleSettings>
+        <codeStyleSettings language="yaml">
+          <indentOptions>
+            <option name="TAB_SIZE" value="2" />
+          </indentOptions>
+        </codeStyleSettings>
+      </value>
+    </option>
+    <option name="USE_PER_PROJECT_SETTINGS" value="true" />
+  </component>
+</project>
+
--- a/mercurial-agent/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/AgentRepoFactory.java	Tue Oct 01 12:51:13 2013 +0400
+++ b/mercurial-agent/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/AgentRepoFactory.java	Tue Oct 01 12:52:11 2013 +0400
@@ -2,16 +2,25 @@
 
 import jetbrains.buildServer.buildTriggers.vcs.mercurial.command.AuthSettings;
 import jetbrains.buildServer.buildTriggers.vcs.mercurial.command.CommandSettingsFactory;
+import jetbrains.buildServer.buildTriggers.vcs.mercurial.command.HgVcsRoot;
+import jetbrains.buildServer.vcs.VcsException;
 import org.jetbrains.annotations.NotNull;
 
 import java.io.File;
 
-public class AgentRepoFactory {
+public class AgentRepoFactory implements HgRepoFactory {
 
   private final CommandSettingsFactory myCommandSettingsFactory;
+  private final HgPathProvider myHgPathProvider;
 
-  public AgentRepoFactory(@NotNull CommandSettingsFactory commandSettingsFactory) {
+  public AgentRepoFactory(@NotNull CommandSettingsFactory commandSettingsFactory,
+                          @NotNull HgPathProvider hgPathProvider) {
     myCommandSettingsFactory = commandSettingsFactory;
+    myHgPathProvider = hgPathProvider;
+  }
+
+  public HgRepo createRepo(@NotNull HgVcsRoot root, @NotNull File workingDir) throws VcsException {
+    return new HgRepo(myCommandSettingsFactory, workingDir, myHgPathProvider.getHgPath(root), root.getAuthSettings());
   }
 
   public HgRepo create(@NotNull File workingDir,
--- a/mercurial-agent/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/MercurialAgentSideVcsSupport.java	Tue Oct 01 12:51:13 2013 +0400
+++ b/mercurial-agent/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/MercurialAgentSideVcsSupport.java	Tue Oct 01 12:52:11 2013 +0400
@@ -30,22 +30,19 @@
 public class MercurialAgentSideVcsSupport extends AgentVcsSupport implements UpdateByIncludeRules2 {
 
   private final AgentPluginConfig myConfig;
-  private final HgPathProvider myHgPathProvider;
   private final MirrorManager myMirrorManager;
   private final AgentRepoFactory myRepoFactory;
 
   public MercurialAgentSideVcsSupport(@NotNull AgentPluginConfig pluginConfig,
-                                      @NotNull HgPathProvider hgPathProvider,
                                       @NotNull MirrorManager mirrorManager,
                                       @NotNull AgentRepoFactory repoFactory) {
     myConfig = pluginConfig;
-    myHgPathProvider = hgPathProvider;
     myMirrorManager = mirrorManager;
     myRepoFactory = repoFactory;
   }
 
   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 {
-    return new MercurialIncludeRuleUpdater(myConfig, myMirrorManager, myHgPathProvider, myRepoFactory, vcsRoot, toVersion, build);
+    return new MercurialIncludeRuleUpdater(myConfig, myMirrorManager, myRepoFactory, vcsRoot, toVersion, build);
   }
 
   @NotNull
--- a/mercurial-agent/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/MercurialIncludeRuleUpdater.java	Tue Oct 01 12:51:13 2013 +0400
+++ b/mercurial-agent/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/MercurialIncludeRuleUpdater.java	Tue Oct 01 12:52:11 2013 +0400
@@ -13,7 +13,6 @@
 
 import java.io.File;
 import java.io.IOException;
-import java.net.URI;
 import java.net.URISyntaxException;
 import java.util.Map;
 
@@ -24,12 +23,10 @@
  */
 public class MercurialIncludeRuleUpdater implements IncludeRuleUpdater {
 
-  private final AgentPluginConfig myConfig;
   private final MirrorManager myMirrorManager;
   private final AgentRepoFactory myRepoFactory;
   private final HgVcsRoot myRoot;
   private final AuthSettings myAuthSettings;
-  private final String myHgPath;
   private final String myToVersion;
   private final BuildProgressLogger myLogger;
   private final boolean myUseLocalMirrors;
@@ -38,22 +35,19 @@
 
   public MercurialIncludeRuleUpdater(@NotNull AgentPluginConfig pluginConfig,
                                      @NotNull MirrorManager mirrorManager,
-                                     @NotNull HgPathProvider hgPathProvider,
                                      @NotNull AgentRepoFactory repoFactory,
                                      @NotNull VcsRoot root,
                                      @NotNull String toVersion,
                                      @NotNull AgentRunningBuild build) {
-    myConfig = pluginConfig;
     myMirrorManager = mirrorManager;
     myRepoFactory = repoFactory;
     myRoot = new HgVcsRoot(root);
     myAuthSettings = myRoot.getAuthSettings();
-    myHgPath = hgPathProvider.getHgPath(myRoot);
     myToVersion = toVersion;
     myLogger = build.getBuildLogger();
-    myUseLocalMirrors = myConfig.isUseLocalMirrors(build);
-    myPullTimeout = myConfig.getPullTimeout(build);
-    myUseTraceback = myConfig.runWithTraceback(build);
+    myUseLocalMirrors = pluginConfig.isUseLocalMirrors(build);
+    myPullTimeout = pluginConfig.getPullTimeout(build);
+    myUseTraceback = pluginConfig.runWithTraceback(build);
   }
 
 
@@ -76,7 +70,7 @@
 
   private void updateLocalMirror(@NotNull String repositoryUrl, @NotNull String revision) throws VcsException, IOException {
     File mirrorDir = myMirrorManager.getMirrorDir(repositoryUrl);
-    HgRepo mirrorRepo = myRepoFactory.create(mirrorDir, myHgPath, myAuthSettings);
+    HgRepo mirrorRepo = myRepoFactory.createRepo(myRoot, mirrorDir);
     if (!mirrorRepo.isValidRepository()) {
       delete(mirrorDir);
       myLogger.message("Clone repository " + myAuthSettings.getRepositoryUrlWithHiddenPassword(repositoryUrl) + " into local mirror " + mirrorRepo.path());
@@ -105,7 +99,7 @@
 
   private void updateRepository(@NotNull File workingDir) throws VcsException, IOException {
     String repositoryUrl = getDefaultPullUrl(myRoot, myUseLocalMirrors);
-    HgRepo repo = myRepoFactory.create(workingDir, myHgPath, myAuthSettings);
+    HgRepo repo = myRepoFactory.createRepo(myRoot, workingDir);
     myLogger.message("Update repository " + workingDir.getAbsolutePath());
     if (repo.isEmpty()) {//can do clone only in empty dir
       myLogger.message("Start cloning from " + (myUseLocalMirrors ? "local mirror " : "") + myAuthSettings.getRepositoryUrlWithHiddenPassword(repositoryUrl));
@@ -140,7 +134,7 @@
 
 
   private void updateWorkingDir(@NotNull File workingDir, @NotNull String toVersion, @NotNull String repositoryUrl) throws VcsException, IOException {
-    HgRepo repo = myRepoFactory.create(workingDir, myHgPath, myAuthSettings);
+    HgRepo repo = myRepoFactory.createRepo(myRoot, workingDir);
     updateSubrepositories(repo, toVersion, repositoryUrl);
     doUpdateWorkingDir(repo, toVersion);
   }
@@ -161,7 +155,7 @@
         myLogger.message("The url of subrepoConfig was changed between revisions " + workingDirRevision + " and " + toVersion + " , delete the subrepoConfig");
         delete(subrepoConfigDir(repo, subrepoConfig));
       }
-      HgRepo subrepository = myRepoFactory.create(subrepoConfigDir(repo, subrepoConfig), myHgPath, myAuthSettings);
+      HgRepo subrepository = myRepoFactory.createRepo(myRoot, subrepoConfigDir(repo, subrepoConfig));
       String subrepoUrl;
       try {
         subrepoUrl = subrepoConfig.resolveUrl(parentRepositoryUrl);
--- a/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/Constants.java	Tue Oct 01 12:51:13 2013 +0400
+++ b/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/Constants.java	Tue Oct 01 12:52:11 2013 +0400
@@ -29,6 +29,7 @@
   String UNCOMPRESSED_TRANSFER = "uncompressedTransfer";
   String USER_FOR_TAG = "tagUsername";
   String DETECT_SUBREPO_CHANGES = "detectSubrepoChanges";
+  String USE_TAGS_AS_BRANCHES = "useTagsAsBranches";
 
   String GLOBAL_DETECT_SUBREPO_CHANGES = "teamcity.hg.detectSubrepoChanges";
 }
--- a/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/HgRepo.java	Tue Oct 01 12:51:13 2013 +0400
+++ b/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/HgRepo.java	Tue Oct 01 12:52:11 2013 +0400
@@ -73,6 +73,20 @@
     return new BookmarksCommand(myCommandSettingsFactory.create(), myHgPath, myWorkingDir, myAuthSettings);
   }
 
+  public TagsCommand tags() {
+    return new TagsCommand(myCommandSettingsFactory.create(), myHgPath, myWorkingDir, myAuthSettings);
+  }
+
+  public Map<String, String> getBranchRevisions(boolean includeBookmarks, boolean includeTags) throws VcsException {
+    Map<String, String> revisions = new HashMap<String, String>();
+    if (includeTags)
+      revisions.putAll(tags().call());
+    if (includeBookmarks && version().call().isEqualsOrGreaterThan(BookmarksCommand.REQUIRED_HG_VERSION))
+      revisions.putAll(bookmarks().call());
+    revisions.putAll(branches().call());
+    return revisions;
+  }
+
   public StatusCommand status() {
     return new StatusCommand(myCommandSettingsFactory.create(), myHgPath, myWorkingDir, myAuthSettings);
   }
@@ -97,6 +111,18 @@
     return new ParentsCommand(myCommandSettingsFactory.create(), myHgPath, myWorkingDir);
   }
 
+  public MergeCommand merge() {
+    return new MergeCommand(myCommandSettingsFactory.create(), myHgPath, myWorkingDir);
+  }
+
+  public ResolveCommand resolve() {
+    return new ResolveCommand(myCommandSettingsFactory.create(), myHgPath, myWorkingDir);
+  }
+
+  public CommitCommand commit() {
+    return new CommitCommand(myCommandSettingsFactory.create(), myHgPath, myWorkingDir);
+  }
+
   public String path() {
     return myWorkingDir.getAbsolutePath();
   }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/HgRepoFactory.java	Tue Oct 01 12:52:11 2013 +0400
@@ -0,0 +1,13 @@
+package jetbrains.buildServer.buildTriggers.vcs.mercurial;
+
+import jetbrains.buildServer.buildTriggers.vcs.mercurial.command.HgVcsRoot;
+import jetbrains.buildServer.vcs.VcsException;
+import org.jetbrains.annotations.NotNull;
+
+import java.io.File;
+
+public interface HgRepoFactory {
+
+  HgRepo createRepo(@NotNull HgVcsRoot root, @NotNull File workingDir) throws VcsException;
+
+}
--- a/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/ChangeSet.java	Tue Oct 01 12:51:13 2013 +0400
+++ b/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/ChangeSet.java	Tue Oct 01 12:52:11 2013 +0400
@@ -16,11 +16,14 @@
 package jetbrains.buildServer.buildTriggers.vcs.mercurial.command;
 
 import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
 
 import java.util.ArrayList;
 import java.util.Date;
 import java.util.List;
 
+import static jetbrains.buildServer.buildTriggers.vcs.mercurial.command.HgVcsRoot.DEFAULT_BRANCH_NAME;
+
 /**
  * Represents Mercurial change set
  */
@@ -28,6 +31,7 @@
   @NotNull private String myUser;
   @NotNull private Date myTimestamp;
   private String myDescription;
+  private String myBranch = DEFAULT_BRANCH_NAME;
   private List<ChangeSetRevision> myParents = new ArrayList<ChangeSetRevision>();
   private List<FileStatus> myModifiedFiles = new ArrayList<FileStatus>();
 
@@ -110,4 +114,14 @@
   public List<FileStatus> getModifiedFiles() {
     return myModifiedFiles;
   }
+
+  @NotNull
+  public String getBranch() {
+    return myBranch;
+  }
+
+  public void setBranch(@Nullable String branch) {
+    if (branch == null || branch.trim().isEmpty() || DEFAULT_BRANCH_NAME.equals(branch)) branch = DEFAULT_BRANCH_NAME;
+    myBranch = branch;
+  }
 }
--- a/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/CommandResult.java	Tue Oct 01 12:51:13 2013 +0400
+++ b/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/CommandResult.java	Tue Oct 01 12:52:11 2013 +0400
@@ -54,6 +54,10 @@
     mySettings = settings;
   }
 
+  public int getExitCode() {
+    return myDelegate.getExitCode();
+  }
+
   @NotNull
   public String getStdout() {
     return removePrivateData(myDelegate.getStdout(), myPrivateData);
@@ -178,6 +182,7 @@
     checkFileNotUnderTheRoot(stderr);
     checkConnectionRefused(stderr);
     checkAbandonedTransaction(stderr);
+    checkMergeWithWorkDirAncestor(stderr);
   }
 
   private void checkUnrelatedRepository(@NotNull final String stderr) throws UnrelatedRepositoryException {
@@ -219,6 +224,11 @@
       throw new AbandonedTransactionFound();
   }
 
+  private void checkMergeWithWorkDirAncestor(@NotNull final String stderr) throws MergeWithWorkingDirAncestor {
+    if (stderr.equals("abort: merging with a working directory ancestor has no effect"))
+      throw new MergeWithWorkingDirAncestor();
+  }
+
   private static Set<Integer> setOf(Integer... ints) {
     return new HashSet<Integer>(asList(ints));
   }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/CommitCommand.java	Tue Oct 01 12:52:11 2013 +0400
@@ -0,0 +1,31 @@
+package jetbrains.buildServer.buildTriggers.vcs.mercurial.command;
+
+import jetbrains.buildServer.vcs.VcsException;
+import org.jetbrains.annotations.NotNull;
+
+import java.io.File;
+
+public class CommitCommand extends BaseCommand {
+
+  private String myCommitMessage;
+
+  public CommitCommand(@NotNull CommandSettings commandSettings,
+                       @NotNull String hgPath,
+                       @NotNull File workingDir) {
+    super(commandSettings, hgPath, workingDir);
+  }
+
+  public CommitCommand message(@NotNull String commitMessage) {
+    myCommitMessage = commitMessage;
+    return this;
+  }
+
+  public void call() throws VcsException {
+    MercurialCommandLine cmd = createCommandLine();
+    cmd.addParameter("commit");
+    cmd.addParameter("-S");
+    if (myCommitMessage != null)
+      cmd.addParameters("-m", myCommitMessage);
+    runCommand(cmd);
+  }
+}
--- a/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/HgVcsRoot.java	Tue Oct 01 12:51:13 2013 +0400
+++ b/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/HgVcsRoot.java	Tue Oct 01 12:52:11 2013 +0400
@@ -15,24 +15,24 @@
  */
 package jetbrains.buildServer.buildTriggers.vcs.mercurial.command;
 
-import java.io.File;
-import java.util.Map;
 import jetbrains.buildServer.buildTriggers.vcs.mercurial.Constants;
 import jetbrains.buildServer.buildTriggers.vcs.mercurial.PathUtil;
 import jetbrains.buildServer.util.StringUtil;
 import jetbrains.buildServer.vcs.VcsRoot;
-import jetbrains.buildServer.vcs.impl.VcsRootImpl;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 
+import java.io.File;
+import java.util.HashMap;
+import java.util.Map;
+
 /**
  * Represents mercurial VCS root
  */
-public class HgVcsRoot implements VcsRoot {
+public class HgVcsRoot {
+  public static final String DEFAULT_BRANCH_NAME = "default";
 
-  private static final String DEFAULT_BRANCH_NAME = "default";
-
-  private final VcsRoot myRoot;
+  private final Map<String, String> myVcsRootProperties;
   private final String myRepository;
   private final String myHgCommandPath;
   private final String myBranchName;
@@ -42,14 +42,15 @@
   private final AuthSettings myAuthSettings;
   private File myCustomWorkingDir;
   private final boolean myDetectSubrepoChanges;
+  private final boolean myUseTagsAsBranches;
 
   public HgVcsRoot(@NotNull final VcsRoot vcsRoot) {
-    this(vcsRoot, vcsRoot.getProperty(Constants.REPOSITORY_PROP));
+    this(vcsRoot.getProperties());
   }
 
-  public HgVcsRoot(@NotNull VcsRoot vcsRoot, @NotNull String repository) {
-    myRoot = vcsRoot;
-    myRepository = repository;
+  public HgVcsRoot(@NotNull Map<String, String> vcsRootProperties) {
+    myVcsRootProperties = vcsRootProperties;
+    myRepository = getProperty(Constants.REPOSITORY_PROP);
     myHgCommandPath = getProperty(Constants.HG_COMMAND_PATH_PROP);
     myBranchName = getProperty(Constants.BRANCH_NAME_PROP);
     myCustomClonePath = getProperty(Constants.SERVER_CLONE_PATH_PROP);
@@ -57,6 +58,13 @@
     myUserForTag = getProperty(Constants.USER_FOR_TAG);
     myAuthSettings = new AuthSettings(getProperty(Constants.USERNAME), getProperty(Constants.PASSWORD));
     myDetectSubrepoChanges = Boolean.parseBoolean(getProperty(Constants.DETECT_SUBREPO_CHANGES));
+    myUseTagsAsBranches = Boolean.parseBoolean(getProperty(Constants.USE_TAGS_AS_BRANCHES));
+  }
+
+  public HgVcsRoot withUrl(@NotNull String repositoryUrl) {
+    Map<String, String> customUrlProperties = new HashMap<String, String>(getProperties());
+    customUrlProperties.put(Constants.REPOSITORY_PROP, repositoryUrl);
+    return new HgVcsRoot(customUrlProperties);
   }
 
   public String getCustomClonePath() {
@@ -67,10 +75,6 @@
     return myRepository;
   }
 
-  public HgVcsRoot withUrl(@NotNull String repositoryUrl) {
-    return new HgVcsRoot(this, repositoryUrl);
-  }
-
   /**
    * Returns name of the branch to use (returns 'default' if no branch specified)
    * @return see above
@@ -126,6 +130,10 @@
     return myDetectSubrepoChanges;
   }
 
+  public boolean useTagsAsBranches() {
+    return myUseTagsAsBranches;
+  }
+
   public boolean isSubrepo() {
     return Boolean.valueOf(getProperty("teamcity.internal.subrepo"));
   }
@@ -145,50 +153,12 @@
     return mySubrepoPath + subrepoPath;
   }
 
-  public String getVcsName() {
-    return myRoot.getVcsName();
-  }
-
-  public String getProperty(String propertyName) {
-    if (Constants.REPOSITORY_PROP.equals(propertyName) && myRepository != null)
-      return myRepository;
-    return myRoot.getProperty(propertyName);
-  }
-
-  public String getProperty(String propertyName, String defaultValue) {
-    return myRoot.getProperty(propertyName, defaultValue);
-  }
-
-  public Map<String, String> getProperties() {
-    return myRoot.getProperties();
-  }
-
-  public String convertToString() {
-    return myRoot.convertToString();
-  }
-
-  public String convertToPresentableString() {
-    return myRoot.convertToPresentableString();
-  }
-
-  public long getPropertiesHash() {
-    return myRoot.getPropertiesHash();
-  }
-
-  public String getName() {
-    return myRoot.getName();
-  }
-
-  public long getId() {
-    return myRoot.getId();
-  }
-
-  public Map<String, String> getPublicProperties() {
-    return myRoot.getPublicProperties();
+  public String getProperty(@NotNull String propertyName) {
+    return myVcsRootProperties.get(propertyName);
   }
 
   @NotNull
-  public String describe(final boolean verbose) {
-    return myRoot.describe(verbose);
+  public Map<String, String> getProperties() {
+    return myVcsRootProperties;
   }
 }
--- a/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/LogCommand.java	Tue Oct 01 12:51:13 2013 +0400
+++ b/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/LogCommand.java	Tue Oct 01 12:52:11 2013 +0400
@@ -36,7 +36,8 @@
 public class LogCommand extends VcsRootCommand {
 
   private final static Logger LOG = Logger.getInstance(LogCommand.class.getName());
-  private final static String ZERO_PARENT_ID = "0000000000000000000000000000000000000000";
+  public final static String ZERO_PARENT_ID = "0000000000000000000000000000000000000000";
+  public final static String ZERO_PARENT_SHORT_ID = "000000000000";
   private final static SAXParserFactory ourSAXFactory = SAXParserFactory.newInstance();
 
   private String myFromId;
--- a/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/MercurialXmlLogParser.java	Tue Oct 01 12:51:13 2013 +0400
+++ b/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/MercurialXmlLogParser.java	Tue Oct 01 12:52:11 2013 +0400
@@ -34,6 +34,7 @@
   public void startElement(String uri, String localName, String qName, Attributes attrs) throws SAXException {
     if ("logentry".equals(qName)) {
       myCset = new ChangeSet(getRevision(attrs), getId(attrs));
+      myCset.setBranch(attrs.getValue("branch"));
     } else if ("parent".equals(qName)) {
       ChangeSetRevision parentCset = new ChangeSetRevision(getRevision(attrs), getId(attrs));
       myCset.addParent(parentCset);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/MergeCommand.java	Tue Oct 01 12:52:11 2013 +0400
@@ -0,0 +1,33 @@
+package jetbrains.buildServer.buildTriggers.vcs.mercurial.command;
+
+import jetbrains.buildServer.buildTriggers.vcs.mercurial.command.exception.MergeConflictException;
+import jetbrains.buildServer.vcs.VcsException;
+import org.jetbrains.annotations.NotNull;
+
+import java.io.File;
+
+public class MergeCommand extends BaseCommand {
+
+  private String myRevision;
+
+  public MergeCommand(@NotNull CommandSettings commandSettings,
+                      @NotNull String hgPath,
+                      @NotNull File workingDir) {
+    super(commandSettings, hgPath, workingDir);
+  }
+
+  public MergeCommand revision(@NotNull String revision) {
+    myRevision = revision;
+    return this;
+  }
+
+  public void call() throws VcsException {
+    MercurialCommandLine cmd = createCommandLine();
+    cmd.addParameter("merge");
+    if (myRevision != null)
+      cmd.addParameters("-r", myRevision);
+    CommandResult result = runCommand(cmd);
+    if (result.getExitCode() == 1)
+      throw new MergeConflictException();
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/ResolveCommand.java	Tue Oct 01 12:52:11 2013 +0400
@@ -0,0 +1,33 @@
+package jetbrains.buildServer.buildTriggers.vcs.mercurial.command;
+
+import jetbrains.buildServer.vcs.VcsException;
+import org.jetbrains.annotations.NotNull;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.List;
+
+import static java.util.Collections.emptyList;
+
+public class ResolveCommand extends BaseCommand {
+
+  public ResolveCommand(@NotNull CommandSettings commandSettings,
+                        @NotNull String hgPath,
+                        @NotNull File workingDir) {
+    super(commandSettings, hgPath, workingDir);
+  }
+
+  public List<String> call() throws VcsException {
+    MercurialCommandLine cmd = createCommandLine();
+    cmd.addParameters("resolve", "--no-status", "--list");
+    CommandResult result = runCommand(cmd);
+    String stdout = result.getStdout();
+    if (stdout.length() == 0)
+      return emptyList();
+    List<String> unresolvedFiles = new ArrayList<String>();
+    for (String line: stdout.split("[\r\n]+")) {
+      unresolvedFiles.add(line);
+    }
+    return unresolvedFiles;
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/TagsCommand.java	Tue Oct 01 12:52:11 2013 +0400
@@ -0,0 +1,30 @@
+package jetbrains.buildServer.buildTriggers.vcs.mercurial.command;
+
+import jetbrains.buildServer.vcs.VcsException;
+import org.jetbrains.annotations.NotNull;
+
+import java.io.File;
+import java.util.Map;
+
+public class TagsCommand extends BranchesCommand  {
+
+  public TagsCommand(@NotNull CommandSettings commandSettings,
+                     @NotNull String hgPath,
+                     @NotNull File workingDir,
+                     @NotNull AuthSettings authSettings) {
+    super(commandSettings, hgPath, workingDir, authSettings);
+  }
+
+  @NotNull
+  @Override
+  protected String getBranchesCommand() {
+    return "tags";
+  }
+
+  @Override
+  public Map<String, String> call() throws VcsException {
+    Map<String, String> raw = super.call();
+    raw.remove("tip");
+    return raw;
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/exception/MergeConflictException.java	Tue Oct 01 12:52:11 2013 +0400
@@ -0,0 +1,9 @@
+package jetbrains.buildServer.buildTriggers.vcs.mercurial.command.exception;
+
+import jetbrains.buildServer.vcs.VcsException;
+
+public class MergeConflictException extends VcsException {
+  public MergeConflictException() {
+    super("Merge conflict");
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/exception/MergeWithWorkingDirAncestor.java	Tue Oct 01 12:52:11 2013 +0400
@@ -0,0 +1,11 @@
+package jetbrains.buildServer.buildTriggers.vcs.mercurial.command.exception;
+
+import jetbrains.buildServer.vcs.VcsException;
+
+public class MergeWithWorkingDirAncestor extends VcsException {
+
+  public MergeWithWorkingDirAncestor() {
+    super("Merging with a working directory ancestor");
+  }
+
+}
--- a/mercurial-server-tc/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/MercurialExtensionRegistry.java	Tue Oct 01 12:51:13 2013 +0400
+++ b/mercurial-server-tc/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/MercurialExtensionRegistry.java	Tue Oct 01 12:52:11 2013 +0400
@@ -8,6 +8,6 @@
 
   public MercurialExtensionRegistry(@NotNull MercurialVcsSupport vcs,
                                     @NotNull Collection<MercurialServerExtension> extensions) {
-    vcs.setExtensions(extensions);
+    vcs.addExtensions(extensions);
   }
 }
--- a/mercurial-server/resources/buildServerResources/fastlog.template	Tue Oct 01 12:51:13 2013 +0400
+++ b/mercurial-server/resources/buildServerResources/fastlog.template	Tue Oct 01 12:52:11 2013 +0400
@@ -1,8 +1,10 @@
 header = '<?xml version="1.0"?>\n<log>\n'
 footer = '</log>\n'
 
-changeset = '<logentry revision="{rev}" shortnode="{node|short}">\n{parents}<author original="{author|xmlescape}"/>\n<date>{date|date|xmlescape}</date>\n<msg xml:space="preserve">{desc|xmlescape}</msg>\n<paths>\n{files}</paths>\n</logentry>\n'
+changeset = '<logentry revision="{rev}" shortnode="{node|short}" branch="{branches}">\n{parents}<author original="{author|xmlescape}"/>\n<date>{date|date|xmlescape}</date>\n<msg xml:space="preserve">{desc|xmlescape}</msg>\n<paths>\n{files}</paths>\n</logentry>\n'
 
 file  = '<path action="M">{file|xmlescape}</path>\n'
 
 parent = '<parent revision="{rev}" shortnode="{node|short}"/>\n'
+
+branch = '{branch|xmlescape}'
--- a/mercurial-server/resources/buildServerResources/log.no.files.template	Tue Oct 01 12:51:13 2013 +0400
+++ b/mercurial-server/resources/buildServerResources/log.no.files.template	Tue Oct 01 12:52:11 2013 +0400
@@ -1,6 +1,6 @@
 header = '<?xml version="1.0"?>\n<log>\n'
 footer = '</log>\n'
 
-changeset = '<logentry revision="{rev}" shortnode="{node|short}">\n{parents}<author original="{author|xmlescape}"/>\n<date>{date|date|xmlescape}</date>\n<msg xml:space="preserve">{desc|xmlescape}</msg>\n</logentry>\n'
-
+changeset = '<logentry revision="{rev}" shortnode="{node|short}" branch="{branches}">\n{parents}<author original="{author|xmlescape}"/>\n<date>{date|date|xmlescape}</date>\n<msg xml:space="preserve">{desc|xmlescape}</msg>\n</logentry>\n'
 parent = '<parent revision="{rev}" node="{node}" shortnode="{node|short}"/>\n'
+branch = '{branch|xmlescape}'
--- a/mercurial-server/resources/buildServerResources/log.template	Tue Oct 01 12:51:13 2013 +0400
+++ b/mercurial-server/resources/buildServerResources/log.template	Tue Oct 01 12:52:11 2013 +0400
@@ -1,11 +1,12 @@
 header = '<?xml version="1.0"?>\n<log>\n'
 footer = '</log>\n'
 
-changeset = '<logentry revision="{rev}" shortnode="{node|short}">\n{parents}<author original="{author|xmlescape}"/>\n<date>{date|date|xmlescape}</date>\n<msg xml:space="preserve">{desc|xmlescape}</msg>\n<paths>\n{file_adds}{file_dels}{file_mods}</paths>\n</logentry>\n'
+changeset = '<logentry revision="{rev}" shortnode="{node|short}" branch="{branches}">\n{parents}<author original="{author|xmlescape}"/>\n<date>{date|date|xmlescape}</date>\n<msg xml:space="preserve">{desc|xmlescape}</msg>\n<paths>\n{file_adds}{file_dels}{file_mods}</paths>\n</logentry>\n'
 
 file_add  = '<path action="A">{file_add|xmlescape}</path>\n'
 file_mod  = '<path action="M">{file_mod|xmlescape}</path>\n'
 file_del  = '<path action="R">{file_del|xmlescape}</path>\n'
 
 parent = '<parent revision="{rev}" shortnode="{node|short}"/>\n'
+branch = '{branch|xmlescape}'
 
--- a/mercurial-server/resources/buildServerResources/mercurialSettings.jsp	Tue Oct 01 12:51:13 2013 +0400
+++ b/mercurial-server/resources/buildServerResources/mercurialSettings.jsp	Tue Oct 01 12:52:11 2013 +0400
@@ -27,13 +27,20 @@
     </td>
   </tr>
   <bs:branchSpecTableRow/>
-  <tr>
+  <tr class="advancedSetting">
+    <th><label for="reportTagRevisions">Use tags as branches:</label></th>
+    <td>
+      <props:checkboxProperty name="useTagsAsBranches"/>
+      <div class="smallNote" style="margin: 0">If enabled tags can be used in branch specification</div>
+    </td>
+  </tr>
+  <tr class="advancedSetting">
     <th><label for="serverClonePath">Clone repository to: </label></th>
     <td><props:textProperty name="serverClonePath" className="longField"/>
       <div class="smallNote" style="margin: 0;">Provide path to a parent directory on TeamCity server where a cloned repository should be created (applicable to "Automatically on server" checkout mode only). Leave blank to use default path.</div>
     </td>
   </tr>
-  <tr>
+  <tr class="advancedSetting">
     <th><label for="detectSubrepoChanges">Detect subrepo changes: </label></th>
       <td>
         <props:checkboxProperty name="detectSubrepoChanges"/>
@@ -42,13 +49,13 @@
         </c:if>
       </td>
     </tr>
-  <tr>
+  <tr class="advancedSetting">
     <th><label for="tagUsername">Username for tags: </label></th>
     <td><props:textProperty name="tagUsername"/>
       <div class="smallNote" style="margin: 0;">Format: User Name &lt;email&gt;</div>
     </td>
   </tr>
-  <tr>
+  <tr class="advancedSetting">
     <th><label for="uncompressedTransfer">Use uncompressed transfer: </label></th>
     <td><props:checkboxProperty name="uncompressedTransfer"/>
       <div class="smallNote" style="margin: 0;">Uncompressed transfer is faster for repositories in the LAN.</div>
--- a/mercurial-server/src/META-INF/build-server-plugin-mercurial.xml	Tue Oct 01 12:51:13 2013 +0400
+++ b/mercurial-server/src/META-INF/build-server-plugin-mercurial.xml	Tue Oct 01 12:52:11 2013 +0400
@@ -11,4 +11,8 @@
   <bean id="testConnection" class="jetbrains.buildServer.buildTriggers.vcs.mercurial.HgTestConnectionSupport" />
   <bean id="commandSettingsFactory" class="jetbrains.buildServer.buildTriggers.vcs.mercurial.ServerCommandSettingsFactory"/>
   <bean id="cleaner" class="jetbrains.buildServer.buildTriggers.vcs.mercurial.MercurialCleaner"/>
+  <bean id="mergeSupport" class="jetbrains.buildServer.buildTriggers.vcs.mercurial.MercurialMergeSupport"/>
+
+  <bean class="jetbrains.buildServer.buildTriggers.vcs.mercurial.MercurialCommitsInfoBuilderSupport"/>
+  <bean class="jetbrains.buildServer.buildTriggers.vcs.mercurial.MercurialModificationInfoBuilder"/>
 </beans>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial-server/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/CheckoutRepository.java	Tue Oct 01 12:52:11 2013 +0400
@@ -0,0 +1,178 @@
+package jetbrains.buildServer.buildTriggers.vcs.mercurial;
+
+import jetbrains.buildServer.buildTriggers.vcs.mercurial.command.HgVcsRoot;
+import jetbrains.buildServer.vcs.VcsException;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.URISyntaxException;
+import java.util.Map;
+
+import static jetbrains.buildServer.util.FileUtil.delete;
+
+/**
+ * Repository checkout which uses mirrors (updates them first)
+ * and supports subrepos.
+ */
+public class CheckoutRepository {
+
+  private final MirrorManager myMirrorManager;
+  private final HgRepoFactory myHgRepoFactory;
+  private final int myPullTimeout;
+  private boolean myGlobalTagsAsBranches;
+  private final HgVcsRoot myRoot;
+  private final File myWorkingDir;
+  private String myRevision;
+  private String myBranch;
+
+  CheckoutRepository(@NotNull MirrorManager mirrorManager,
+                     @NotNull HgRepoFactory hgRepoFactory,
+                     int pullTimeout,
+                     boolean globalTagsAsBranches,
+                     @NotNull HgVcsRoot root,
+                     @NotNull File workingDir) {
+    myMirrorManager = mirrorManager;
+    myHgRepoFactory = hgRepoFactory;
+    myPullTimeout = pullTimeout;
+    myRoot = root;
+    myWorkingDir = workingDir;
+  }
+
+  public CheckoutRepository setRevision(String revision) {
+    myRevision = revision;
+    return this;
+  }
+
+  public CheckoutRepository setBranch(String branch) {
+    myBranch = branch;
+    return this;
+  }
+
+  public void checkout() throws VcsException {
+    checkout(myRoot, myWorkingDir, myRevision);
+  }
+
+  private void checkout(@NotNull HgVcsRoot root,
+                        @NotNull File workingDir,
+                        @Nullable String revision) throws VcsException {
+    updateRepository(root, workingDir, revision);
+    if (revision == null && myBranch != null) {
+      HgRepo repo = myHgRepoFactory.createRepo(root, workingDir);
+      boolean includeTags = myGlobalTagsAsBranches && root.useTagsAsBranches();
+      Map<String, String> revisions = repo.getBranchRevisions(true, includeTags);
+      revision = revisions.get(myBranch);
+    }
+    if (revision == null)
+      return;
+    updateSubrepos(root, workingDir, revision);
+    updateWorkingDir(root, workingDir, revision);
+  }
+
+
+  private void updateRepository(@NotNull HgVcsRoot root,
+                                @NotNull File workingDir,
+                                @Nullable String revision) throws VcsException {
+    HgRepo repo = myHgRepoFactory.createRepo(root, workingDir);
+    if (revision != null && repo.containsRevision(revision))
+      return;
+
+    updateMirror(root, revision);
+    String mirrorUrl = getMirrorUrl(root);
+
+    if (repo.isValidRepository()) {
+      repo.pull().fromRepository(mirrorUrl)
+              .withTimeout(myPullTimeout)
+              .call();
+    } else {
+      repo.doClone().fromRepository(mirrorUrl)
+              .setUpdateWorkingDir(false)
+              .call();
+
+      repo.setDefaultPath(root.getRepository());
+    }
+  }
+
+
+  private void updateWorkingDir(@NotNull HgVcsRoot root,
+                                @NotNull File workingDir,
+                                @NotNull String revision) throws VcsException {
+    HgRepo repo = myHgRepoFactory.createRepo(root, workingDir);
+    repo.update().toRevision(revision).call();
+  }
+
+
+  private void updateSubrepos(@NotNull HgVcsRoot root,
+                              @NotNull File workingDir,
+                              @Nullable String revision) throws VcsException {
+    HgRepo repo = myHgRepoFactory.createRepo(root, workingDir);
+    if (!repo.hasSubreposAtRevision(revision))
+      return;
+
+    String workingDirRevision = repo.getWorkingDirRevision();
+    Map<String, SubRepo> workingDirSubrepos = repo.getSubrepositories(workingDirRevision);
+    Map<String, SubRepo> subrepos = repo.getSubrepositories(revision);
+    for (Map.Entry<String, SubRepo> entry : subrepos.entrySet()) {
+      String path = entry.getKey();
+      SubRepo subrepoConfig = entry.getValue();
+      SubRepo workingDirSubrepo = workingDirSubrepos.get(path);
+      if (workingDirSubrepo != null && subrepoConfig.hasDifferentUrlThan(workingDirSubrepo))
+        delete(subrepoConfigDir(repo, subrepoConfig));
+
+      File subrepoDir = new File(workingDir, subrepoConfig.path());
+      String subrepoUrl;
+      try {
+        subrepoUrl = subrepoConfig.resolveUrl(root.getRepository());
+        if (subrepoConfig.vcsType() == SubRepo.VcsType.hg && !isRelativeUrl(subrepoUrl)) {
+          HgVcsRoot subrepoRoot = root.withUrl(subrepoUrl);
+          updateRepository(subrepoRoot, subrepoDir, subrepoConfig.revision());
+        }
+      } catch (URISyntaxException e) {
+        subrepoUrl = subrepoConfig.url();
+      }
+
+      updateSubrepos(root.withUrl(subrepoUrl), subrepoDir, subrepoConfig.revision());
+    }
+  }
+
+
+  private boolean isRelativeUrl(@NotNull String url) {
+    return url.startsWith(".");
+  }
+
+
+  private File subrepoConfigDir(@NotNull HgRepo parentRepo, @NotNull SubRepo subrepo) {
+    return new File(parentRepo.getWorkingDir(), subrepo.path());
+  }
+
+
+  private void updateMirror(@NotNull HgVcsRoot root, @Nullable String revision) throws VcsException {
+    String url = root.getRepository();
+    File mirrorDir = myMirrorManager.getMirrorDir(url);
+    HgRepo repo = myHgRepoFactory.createRepo(root, mirrorDir);
+    if (revision != null && repo.containsRevision(revision)) {
+      return;
+    }
+
+    if (repo.isValidRepository()) {
+      repo.pull().fromRepository(root.getRepository())
+              .withTimeout(myPullTimeout)
+              .call();
+    } else {
+      repo.doClone().fromRepository(root.getRepository())
+              .setUpdateWorkingDir(false)
+              .useUncompressedTransfer(root.isUncompressedTransfer())
+              .call();
+      repo.setDefaultPath(root.getRepository());
+    }
+  }
+
+  private String getMirrorUrl(@NotNull HgVcsRoot root) throws VcsException {
+    try {
+      return myMirrorManager.getMirrorDir(root.getRepository()).getCanonicalPath();
+    } catch (IOException e) {
+      throw new VcsException(e);
+    }
+  }
+}
--- a/mercurial-server/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/HgVcsRootFactory.java	Tue Oct 01 12:51:13 2013 +0400
+++ b/mercurial-server/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/HgVcsRootFactory.java	Tue Oct 01 12:52:11 2013 +0400
@@ -21,6 +21,7 @@
   }
 
 
+  @NotNull
   public HgVcsRoot createHgRoot(@NotNull VcsRoot root) throws VcsException {
     HgVcsRoot hgRoot = new HgVcsRoot(root);
     String customClonePath = hgRoot.getCustomClonePath();
--- a/mercurial-server/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/MercurialCollectChangesPolicy.java	Tue Oct 01 12:51:13 2013 +0400
+++ b/mercurial-server/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/MercurialCollectChangesPolicy.java	Tue Oct 01 12:52:11 2013 +0400
@@ -43,9 +43,8 @@
   public RepositoryStateData getCurrentState(@NotNull VcsRoot root) throws VcsException {
     HgVcsRoot hgRoot = myHgVcsRootFactory.createHgRoot(root);
     myVcs.syncRepository(hgRoot);
-    Map<String, String> revisions = new HashMap<String, String>();
-    revisions.putAll(getBookmarkRevisions(hgRoot));
-    revisions.putAll(getBranchesRevisions(hgRoot));
+    boolean includeTags = myConfig.useTagsAsBranches() && hgRoot.useTagsAsBranches();
+    Map<String, String> revisions = myVcs.createRepo(hgRoot).getBranchRevisions(myConfig.bookmarksEnabled(), includeTags);
     String defaultBranchName = hgRoot.getBranchName();
     if (revisions.get(defaultBranchName) == null) {
       throw new VcsException("Cannot find revision of the default branch '" +
@@ -56,25 +55,6 @@
 
 
   @NotNull
-  private Map<String, String> getBranchesRevisions(@NotNull HgVcsRoot root) throws VcsException {
-    HgRepo repo = myVcs.createRepo(root);
-    return repo.branches().call();
-  }
-
-
-  @NotNull
-  private Map<String, String> getBookmarkRevisions(@NotNull HgVcsRoot root) throws VcsException {
-    ServerHgRepo repo = myVcs.createRepo(root);
-    if (!myConfig.bookmarksEnabled())
-      return emptyMap();
-    HgVersion version = repo.version().call();
-    if (!version.isEqualsOrGreaterThan(BookmarksCommand.REQUIRED_HG_VERSION))
-      return emptyMap();
-    return repo.bookmarks().call();
-  }
-
-
-  @NotNull
   public List<ModificationData> collectChanges(@NotNull VcsRoot fromRoot,
                                                @NotNull RepositoryStateData fromState,
                                                @NotNull VcsRoot toRoot,
@@ -170,11 +150,11 @@
   }
 
 
-  private List<ModificationData> collectChanges(@NotNull OperationContext ctx,
-                                                @NotNull VcsRoot root,
-                                                @NotNull Collection<String> fromVersion,
-                                                @Nullable String currentVersion,
-                                                @NotNull CheckoutRules checkoutRules) throws VcsException {
+  public List<ModificationData> collectChanges(@NotNull OperationContext ctx,
+                                               @NotNull VcsRoot root,
+                                               @NotNull Collection<String> fromVersion,
+                                               @Nullable String currentVersion,
+                                               @NotNull CheckoutRules checkoutRules) throws VcsException {
     HgVcsRoot hgRoot = myHgVcsRootFactory.createHgRoot(root);
     ctx.syncRepository(hgRoot);
     List<ModificationData> result = new ArrayList<ModificationData>();
@@ -392,7 +372,7 @@
         ServerHgRepo repo = myVcs.createRepo(ctx, root);
         SubrepoRevisionAttributesBuilder attrBuilder = new SubrepoRevisionAttributesBuilder();
         for (SubRepo s : repo.getSubrepositories(m).values()) {
-          attrBuilder.addSubrepo(new SubrepoConfig(root)
+          attrBuilder.addSubrepo(new SubrepoConfig(mainRoot)
                   .setSubrepoPath(ctx.getStringFromPool(root.expandSubrepoPath(s.path())))
                   .setSubrepoRootParamDiff(Constants.REPOSITORY_PROP, ctx.getStringFromPool(s.resolveUrl(root.getRepository())))
                   .setSubrepoRootParamDiff("teamcity.internal.subrepo", "true")
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial-server/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/MercurialCommitsInfoBuilderSupport.java	Tue Oct 01 12:52:11 2013 +0400
@@ -0,0 +1,75 @@
+package jetbrains.buildServer.buildTriggers.vcs.mercurial;
+
+import jetbrains.buildServer.buildTriggers.vcs.mercurial.command.ChangeSet;
+import jetbrains.buildServer.buildTriggers.vcs.mercurial.command.ChangeSetRevision;
+import jetbrains.buildServer.buildTriggers.vcs.mercurial.command.HgVcsRoot;
+import jetbrains.buildServer.vcs.*;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import static jetbrains.buildServer.buildTriggers.vcs.mercurial.command.LogCommand.ZERO_PARENT_ID;
+import static jetbrains.buildServer.buildTriggers.vcs.mercurial.command.LogCommand.ZERO_PARENT_SHORT_ID;
+
+/**
+ * Created 30.09.13 13:05
+ *
+ * @author Eugene Petrenko (eugene.petrenko@jetbrains.com)
+ */
+public class MercurialCommitsInfoBuilderSupport implements CommitsInfoBuilder, MercurialServerExtension {
+  private final MercurialVcsSupport mySupport;
+  private final HgVcsRootFactory myHgVcsRootFactory;
+
+  public MercurialCommitsInfoBuilderSupport(@NotNull MercurialVcsSupport vcs,
+                                            @NotNull HgVcsRootFactory vcsRootFactory) {
+    vcs.addExtension(this);
+    mySupport = vcs;
+    myHgVcsRootFactory = vcsRootFactory;
+  }
+
+
+  @NotNull
+  public List<CommitDataBean> collectCommits(@NotNull VcsRoot root,
+                                             @NotNull CheckoutRules rules) throws VcsException {
+    final HgVcsRoot hgRoot = myHgVcsRootFactory.createHgRoot(root);
+    final ServerHgRepo repo = mySupport.createRepo(hgRoot);
+    mySupport.syncRepository(hgRoot);
+
+    final Map<String, CommitDataBean> commitToBean = processChanges(repo);
+
+    //collect tags info
+    Map<String, String> tags = repo.tags().call();
+    for (Map.Entry<String, String> e : tags.entrySet()) {
+      CommitDataBean bean = commitToBean.get(e.getValue());
+      if (bean != null) bean.addTag(e.getKey());
+    }
+
+    return new ArrayList<CommitDataBean>(commitToBean.values());
+  }
+
+  @NotNull
+  private Map<String, CommitDataBean> processChanges(@NotNull ServerHgRepo repo) throws VcsException {
+    final List<ChangeSet> tip = repo.logNoFiles()
+            .showCommitsFromAllBranches()
+            .call();
+
+    final Map<String, CommitDataBean> result = new HashMap<String, CommitDataBean>();
+    for (ChangeSet set : tip) {
+      final CommitDataBean bean = new CommitDataBean(set.getId(), set.getFullVersion(), set.getTimestamp());
+      for (ChangeSetRevision p : set.getParents()) {
+        final String commitId = p.getId();
+
+        if (ZERO_PARENT_ID.equals(commitId)) continue;
+        if (ZERO_PARENT_SHORT_ID.equals(commitId)) continue;
+        bean.addParentRevision(commitId);
+      }
+      bean.addBranch(set.getBranch());
+      result.put(bean.getVersion(), bean);
+    }
+
+    return result;
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial-server/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/MercurialMergeSupport.java	Tue Oct 01 12:52:11 2013 +0400
@@ -0,0 +1,134 @@
+package jetbrains.buildServer.buildTriggers.vcs.mercurial;
+
+import com.intellij.openapi.diagnostic.Logger;
+import jetbrains.buildServer.buildTriggers.vcs.mercurial.command.HgVcsRoot;
+import jetbrains.buildServer.buildTriggers.vcs.mercurial.command.exception.MergeConflictException;
+import jetbrains.buildServer.buildTriggers.vcs.mercurial.command.exception.MergeWithWorkingDirAncestor;
+import jetbrains.buildServer.vcs.*;
+import org.jetbrains.annotations.NotNull;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import static jetbrains.buildServer.buildTriggers.vcs.mercurial.HgFileUtil.deleteDir;
+
+public class MercurialMergeSupport implements MergeSupport, MercurialServerExtension {
+
+  private final static Logger LOG = Logger.getInstance(MercurialMergeSupport.class.getName());
+
+  private final MirrorManager myMirrorManager;
+  private final ServerPluginConfig myConfig;
+  private final HgVcsRootFactory myHgVcsRootFactory;
+  private final HgRepoFactory myHgRepoFactory;
+
+  public MercurialMergeSupport(@NotNull MercurialVcsSupport vcs,
+                               @NotNull MirrorManager mirrorManager,
+                               @NotNull ServerPluginConfig config,
+                               @NotNull HgVcsRootFactory vcsRootFactory,
+                               @NotNull HgRepoFactory hgRepoFactory) {
+    vcs.addExtension(this);
+    myMirrorManager = mirrorManager;
+    myConfig = config;
+    myHgVcsRootFactory = vcsRootFactory;
+    myHgRepoFactory = hgRepoFactory;
+  }
+
+  @NotNull
+  public Map<MergeTask, MergeResult> tryMerge(@NotNull VcsRoot root, @NotNull List<MergeTask> tasks, @NotNull MergeOptions options) throws VcsException {
+    File tmpDir = null;
+    try {
+      tmpDir = HgFileUtil.createTempDir();
+      HgVcsRoot hgRoot = myHgVcsRootFactory.createHgRoot(root);
+      HgRepo repo = myHgRepoFactory.createRepo(hgRoot, tmpDir);
+      Map<MergeTask, MergeResult> results = new HashMap<MergeTask, MergeResult>();
+      for (MergeTask task : tasks) {
+        MergeResult result = new MergeResult();
+        try {
+          new CheckoutRepository(myMirrorManager, myHgRepoFactory, myConfig.getPullTimeout(), myConfig.useTagsAsBranches(), hgRoot, tmpDir)
+                  .setRevision(task.getDestinationRevision()).checkout();
+          repo.merge().revision(task.getSourceRevision()).call();
+        } catch (MergeConflictException e) {
+          result.setSuccess(false);
+          result.setConflicts(detectConflicts(hgRoot, "", repo));
+        } catch (MergeWithWorkingDirAncestor e) {
+          //ignore
+        } catch (VcsException e) {
+          result.setSuccess(false);
+        } finally {
+          repo.update().toRevision(task.getDestinationRevision()).call();
+        }
+        results.put(task, result);
+      }
+      return results;
+    } catch (Exception e) {
+      if (e instanceof VcsException)
+        throw (VcsException) e;
+      throw new VcsException(e);
+    } finally {
+      deleteDir(tmpDir, LOG);
+    }
+  }
+
+
+  @NotNull
+  public MergeResult merge(@NotNull VcsRoot root,
+                           @NotNull String srcRevision,
+                           @NotNull String dstBranch,
+                           @NotNull String message,
+                           @NotNull MergeOptions options) throws VcsException {
+    File tmpDir = null;
+    MergeResult mergeResult = new MergeResult();
+    try {
+      tmpDir = HgFileUtil.createTempDir();
+      HgVcsRoot hgRoot = myHgVcsRootFactory.createHgRoot(root);
+
+      new CheckoutRepository(myMirrorManager, myHgRepoFactory, myConfig.getPullTimeout(), myConfig.useTagsAsBranches(), hgRoot, tmpDir)
+              .setBranch(dstBranch).checkout();
+
+      HgRepo repo = myHgRepoFactory.createRepo(hgRoot, tmpDir);
+      try {
+        repo.merge().revision(srcRevision).call();
+      } catch (MergeConflictException e) {
+        mergeResult.setSuccess(false);
+        mergeResult.setConflicts(detectConflicts(hgRoot, "", repo));
+        return mergeResult;
+      } catch (MergeWithWorkingDirAncestor e) {
+        return mergeResult;
+      }
+
+      repo.commit().message(message).call();
+
+      repo.push().toRepository(hgRoot.getRepository()).call();
+      return mergeResult;
+    } catch (Exception e) {
+      if (e instanceof VcsException)
+        throw (VcsException) e;
+      throw new VcsException(e);
+    } finally {
+      deleteDir(tmpDir, LOG);
+    }
+  }
+
+
+  private List<String> detectConflicts(@NotNull HgVcsRoot root, @NotNull String prefix, @NotNull HgRepo repo) throws VcsException {
+    List<String> conflicts = new ArrayList<String>();
+    for (String conflict : repo.resolve().call()) {
+      conflicts.add(prefix + conflict);
+    }
+
+    List<String> parents = repo.parents().call();
+    if (parents.isEmpty())
+      return conflicts;
+
+    Map<String, SubRepo> subrepos = repo.getSubrepositories(parents.get(0));
+    for (SubRepo subrepo : subrepos.values()) {
+      HgVcsRoot subrepoRoot = root.withUrl(subrepo.url());
+      HgRepo hgSubrepo = myHgRepoFactory.createRepo(subrepoRoot, new File(repo.getWorkingDir(), subrepo.path()));
+      conflicts.addAll(detectConflicts(subrepoRoot, prefix + subrepo.path() + "/", hgSubrepo));
+    }
+    return conflicts;
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial-server/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/MercurialModificationInfoBuilder.java	Tue Oct 01 12:52:11 2013 +0400
@@ -0,0 +1,59 @@
+package jetbrains.buildServer.buildTriggers.vcs.mercurial;
+
+import jetbrains.buildServer.buildTriggers.vcs.mercurial.command.HgVcsRoot;
+import jetbrains.buildServer.vcs.*;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import static java.util.Collections.emptyList;
+
+public class MercurialModificationInfoBuilder implements ModificationInfoBuilder, MercurialServerExtension {
+
+  private final MercurialVcsSupport myVcs;
+  private final HgVcsRootFactory myHgVcsRootFactory;
+  private final RepoFactory myRepoFactory;
+  private final HgPathProvider myHgPathProvider;
+
+  public MercurialModificationInfoBuilder(@NotNull MercurialVcsSupport vcs,
+                                          @NotNull HgVcsRootFactory hgVcsRootFactory,
+                                          @NotNull RepoFactory repoFactory,
+                                          @NotNull HgPathProvider hgPathProvider) {
+    myVcs = vcs;
+    myHgVcsRootFactory = hgVcsRootFactory;
+    myRepoFactory = repoFactory;
+    myHgPathProvider = hgPathProvider;
+    myVcs.addExtension(this);
+  }
+
+  @NotNull
+  public List<ModificationData> fetchModificationInfo(@NotNull VcsRoot root,
+                                                      @NotNull RepositoryStateData state,
+                                                      @NotNull CheckoutRules checkoutRules) throws VcsException {
+    HgVcsRoot hgRoot = myHgVcsRootFactory.createHgRoot(root);
+    myVcs.syncRepository(hgRoot);
+    Set<String> prevRevisions = getPrevRevisions(hgRoot, state);
+    if (prevRevisions.isEmpty())
+      return emptyList();
+    OperationContext ctx = new OperationContext(myVcs, myRepoFactory, myHgPathProvider, prevRevisions, state);
+    List<ModificationData> changes = new ArrayList<ModificationData>();
+    for (String revision : state.getBranchRevisions().values()) {
+      changes.addAll(myVcs.getCollectChangesPolicy().collectChanges(ctx, root, prevRevisions, revision, checkoutRules));
+    }
+    return changes;
+  }
+
+
+  @NotNull
+  private Set<String> getPrevRevisions(@NotNull HgVcsRoot root, @NotNull RepositoryStateData state) throws VcsException {
+    Set<String> prevRevisions = new HashSet<String>();
+    HgRepo repo  = myVcs.createRepo(root);
+    for (String revision : state.getBranchRevisions().values()) {
+      prevRevisions.addAll(repo.parents().ofRevision(revision).call());
+    }
+    return prevRevisions;
+  }
+}
--- a/mercurial-server/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/MercurialVcsSupport.java	Tue Oct 01 12:51:13 2013 +0400
+++ b/mercurial-server/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/MercurialVcsSupport.java	Tue Oct 01 12:52:11 2013 +0400
@@ -21,7 +21,10 @@
 import jetbrains.buildServer.buildTriggers.vcs.mercurial.command.exception.AbandonedTransactionFound;
 import jetbrains.buildServer.buildTriggers.vcs.mercurial.command.exception.UnrelatedRepositoryException;
 import jetbrains.buildServer.log.Loggers;
-import jetbrains.buildServer.serverSide.*;
+import jetbrains.buildServer.serverSide.InvalidProperty;
+import jetbrains.buildServer.serverSide.PropertiesProcessor;
+import jetbrains.buildServer.serverSide.ServerListener;
+import jetbrains.buildServer.serverSide.ServerListenerAdapter;
 import jetbrains.buildServer.util.EventDispatcher;
 import jetbrains.buildServer.util.FileUtil;
 import jetbrains.buildServer.util.cache.ResetCacheRegister;
@@ -37,7 +40,6 @@
 import java.net.URISyntaxException;
 import java.util.*;
 
-import static java.util.Collections.emptyMap;
 import static jetbrains.buildServer.buildTriggers.vcs.mercurial.HgFileUtil.deleteDir;
 
 /**
@@ -61,7 +63,7 @@
   private final FileFilter myAcceptAllFilter = new AcceptAllFilter();
   private final HgTestConnectionSupport myTestConnection;
   private final SubrepoCheckoutRulesProvider mySubrepoCheckoutRulesProvider;
-  private Collection<MercurialServerExtension> myExtensions;
+  private final Collection<MercurialServerExtension> myExtensions = new ArrayList<MercurialServerExtension>();
 
   public MercurialVcsSupport(@NotNull final EventDispatcher<ServerListener> dispatcher,
                              @NotNull final ResetCacheRegister resetCacheHandlerManager,
@@ -89,8 +91,12 @@
     logUsedHg();
   }
 
-  public void setExtensions(@NotNull Collection<MercurialServerExtension> extensions) {
-    myExtensions = extensions;
+  public void addExtensions(@NotNull Collection<MercurialServerExtension> extensions) {
+    myExtensions.addAll(extensions);
+  }
+
+  public void addExtension(@NotNull MercurialServerExtension extension) {
+    myExtensions.add(extension);
   }
 
   private void logUsedHg() {
@@ -498,6 +504,14 @@
 
   public void buildPatch(@NotNull VcsRoot root, @Nullable String fromVersion, @NotNull String toVersion, @NotNull PatchBuilder builder, @NotNull CheckoutRules checkoutRules) throws IOException, VcsException {
     HgVcsRoot hgRoot = myHgVcsRootFactory.createHgRoot(root);
+    buildPatch(hgRoot, fromVersion, toVersion, builder, checkoutRules);
+  }
+
+  public void buildPatch(@NotNull HgVcsRoot hgRoot,
+                         @Nullable String fromVersion,
+                         @NotNull String toVersion,
+                         @NotNull PatchBuilder builder,
+                         @NotNull CheckoutRules checkoutRules) throws IOException, VcsException {
     syncRepository(hgRoot);
     ChangeSet to = new ChangeSet(toVersion);
     if (fromVersion == null) {
@@ -571,7 +585,7 @@
     return label.replace(':', '_').replace('\r', '_').replace('\n', '_');
   }
 
-  File getWorkingDir(HgVcsRoot root) {
+  public File getWorkingDir(HgVcsRoot root) {
     File customDir = root.getCustomWorkingDir();
     return customDir != null ? customDir : myMirrorManager.getMirrorDir(root.getRepository());
   }
@@ -623,7 +637,7 @@
   }
 
 
-  private HgRepo createRepo(@NotNull HgVcsRoot root, @NotNull File customDir) throws VcsException {
+  public HgRepo createRepo(@NotNull HgVcsRoot root, @NotNull File customDir) throws VcsException {
     return myRepoFactory.create(customDir, myHgPathProvider.getHgPath(root), root.getAuthSettings());
   }
 
@@ -646,12 +660,10 @@
   @Override
   @Nullable
   protected <T extends VcsExtension> T getVcsCustomExtension(@NotNull final Class<T> extensionClass) {
-    if (myExtensions == null)
-      return null;
     for (MercurialServerExtension e : myExtensions) {
       if (extensionClass.isInstance(e))
         return extensionClass.cast(e);
     }
-    return null;
+    return super.getVcsCustomExtension(extensionClass);
   }
 }
--- a/mercurial-server/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/OperationContext.java	Tue Oct 01 12:51:13 2013 +0400
+++ b/mercurial-server/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/OperationContext.java	Tue Oct 01 12:52:11 2013 +0400
@@ -17,13 +17,14 @@
 import java.util.*;
 
 import static java.util.Collections.singleton;
+import static jetbrains.buildServer.util.CollectionsUtil.setOf;
 
 public class OperationContext {
 
   private final MercurialVcsSupport myVcs;
   private final RepoFactory myRepoFactory;
   private final HgPathProvider myHgPathProvider;
-  private final RepositoryStateData myFromState;
+  private final Set<String> myUninterestingRevisions;
   private final RepositoryStateData myToState;
   private final Map<VcsRootKey, DAG<String>> myDags = new HashMap<VcsRootKey, DAG<String>>();
   private Set<File> mySyncedDirs = new HashSet<File>();
@@ -39,11 +40,7 @@
                           @NotNull HgPathProvider hgPathProvider,
                           @NotNull RepositoryStateData fromState,
                           @NotNull RepositoryStateData toState) {
-    myVcs = vcs;
-    myRepoFactory = repoFactory;
-    myHgPathProvider = hgPathProvider;
-    myFromState = fromState;
-    myToState = toState;
+    this(vcs, repoFactory, hgPathProvider, new HashSet<String>(fromState.getBranchRevisions().values()), toState);
   }
 
   public OperationContext(@NotNull MercurialVcsSupport vcs,
@@ -51,7 +48,19 @@
                           @NotNull HgPathProvider hgPathProvider,
                           @NotNull String fromVersion,
                           @NotNull String toVersion) {
-    this(vcs, repoFactory, hgPathProvider, RepositoryStateData.createSingleVersionState(fromVersion), RepositoryStateData.createSingleVersionState(toVersion));
+    this(vcs, repoFactory, hgPathProvider, setOf(fromVersion), RepositoryStateData.createSingleVersionState(toVersion));
+  }
+
+  public OperationContext(@NotNull MercurialVcsSupport vcs,
+                          @NotNull RepoFactory repoFactory,
+                          @NotNull HgPathProvider hgPathProvider,
+                          @NotNull Collection<String> fromVersions,
+                          @NotNull RepositoryStateData toState) {
+    myVcs = vcs;
+    myRepoFactory = repoFactory;
+    myHgPathProvider = hgPathProvider;
+    myToState = toState;
+    myUninterestingRevisions = new HashSet<String>(fromVersions);
   }
 
   public void syncRepository(@NotNull final HgVcsRoot root) throws VcsException {
@@ -197,7 +206,7 @@
         myDags.put(rootKey, dag);
       }
       if (dag.containsNode(toRevision)) {
-        FindIntervalVisitor visitor = new FindIntervalVisitor(dag, myFromState.getBranchRevisions().values());
+        FindIntervalVisitor visitor = new FindIntervalVisitor(dag, myUninterestingRevisions);
         dag.breadthFirstSearch(toRevision, visitor);
         fromRevisions.addAll(visitor.getEndpoints());
       } else {
@@ -224,6 +233,10 @@
       super(repositoryUrl, subrepoPath);
     }
 
+    private static VcsRootKey create(@NotNull HgVcsRoot root) {
+      return new VcsRootKey(root.getProperty(Constants.REPOSITORY_PROP), root.getProperty("teamcity.internal.subrepo.path"));
+    }
+
     private static VcsRootKey create(@NotNull VcsRoot root) {
       return new VcsRootKey(root.getProperty(Constants.REPOSITORY_PROP), root.getProperty("teamcity.internal.subrepo.path"));
     }
--- a/mercurial-server/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/RepoFactory.java	Tue Oct 01 12:51:13 2013 +0400
+++ b/mercurial-server/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/RepoFactory.java	Tue Oct 01 12:52:11 2013 +0400
@@ -2,6 +2,7 @@
 
 import jetbrains.buildServer.buildTriggers.vcs.mercurial.command.AuthSettings;
 import jetbrains.buildServer.buildTriggers.vcs.mercurial.command.CommandSettingsFactory;
+import jetbrains.buildServer.buildTriggers.vcs.mercurial.command.HgVcsRoot;
 import jetbrains.buildServer.util.FileUtil;
 import jetbrains.buildServer.vcs.VcsException;
 import org.jetbrains.annotations.NotNull;
@@ -15,19 +16,23 @@
 /**
  * @author dmitry.neverov
  */
-public class RepoFactory {
+public class RepoFactory implements HgRepoFactory {
 
   protected final ServerPluginConfig myConfig;
   protected final CommandSettingsFactory myCommandSettingsFactory;
+  protected final HgPathProvider myHgPathProvider;
 
   protected final MercurialLogTemplate myLogTemplate = new MercurialLogTemplate("/buildServerResources/log.template", "hg.log.template");
   protected final MercurialLogTemplate myLogNoFilesTemplate = new MercurialLogTemplate("/buildServerResources/log.no.files.template", "hg.short.log.template");
   protected final MercurialLogTemplate myDagTemplate = new MercurialLogTemplate("/buildServerResources/dag.template", "hg.dag.template");
   protected final MercurialLogTemplate myFastLogTemplate = new MercurialLogTemplate("/buildServerResources/fastlog.template", "hg.fastlog.template");
 
-  public RepoFactory(@NotNull ServerPluginConfig config, @NotNull CommandSettingsFactory commandSettingsFactory) throws IOException {
+  public RepoFactory(@NotNull ServerPluginConfig config,
+                     @NotNull CommandSettingsFactory commandSettingsFactory,
+                     @NotNull HgPathProvider hgPathProvider) throws IOException {
     myConfig = config;
     myCommandSettingsFactory = commandSettingsFactory;
+    myHgPathProvider = hgPathProvider;
   }
 
   @NotNull
@@ -41,6 +46,10 @@
                     myFastLogTemplate.getTemplate());
   }
 
+  public HgRepo createRepo(@NotNull HgVcsRoot root, @NotNull File workingDir) throws VcsException {
+    return create(workingDir, myHgPathProvider.getHgPath(root), root.getAuthSettings());
+  }
+
   public void dispose() {
     myLogTemplate.dispose();
     myLogNoFilesTemplate.dispose();
--- a/mercurial-server/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/ServerHgRepo.java	Tue Oct 01 12:51:13 2013 +0400
+++ b/mercurial-server/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/ServerHgRepo.java	Tue Oct 01 12:52:11 2013 +0400
@@ -58,6 +58,11 @@
     return new LogCommand(myCommandSettingsFactory.create(), myHgPath, myWorkingDir, myAuthSettings).withTemplate(myLogTemplate);
   }
 
+  @NotNull
+  public LogCommand logNoFiles() {
+    return log().withTemplate(myLogNoFilesTemplate);
+  }
+
   public LogCommand log(@NotNull HgVcsRoot root) {
     File template = root.isSubrepo() && !myConfig.reportSubrepoChangesFileStatus() ? myFastLogTemplate : myLogTemplate;
     return new LogCommand(myCommandSettingsFactory.create(), myHgPath, myWorkingDir, myAuthSettings).withTemplate(template);
--- a/mercurial-server/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/ServerPluginConfig.java	Tue Oct 01 12:51:13 2013 +0400
+++ b/mercurial-server/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/ServerPluginConfig.java	Tue Oct 01 12:52:11 2013 +0400
@@ -3,8 +3,6 @@
 import org.jetbrains.annotations.Nullable;
 import org.quartz.CronExpression;
 
-import java.util.Set;
-
 /**
  * @author dmitry.neverov
  */
@@ -23,6 +21,8 @@
 
   public boolean bookmarksEnabled();
 
+  public boolean useTagsAsBranches();
+
   public int getMaxDagNodesCount();
 
   public int getLogOutputLimit();
--- a/mercurial-server/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/ServerPluginConfigImpl.java	Tue Oct 01 12:51:13 2013 +0400
+++ b/mercurial-server/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/ServerPluginConfigImpl.java	Tue Oct 01 12:52:11 2013 +0400
@@ -67,6 +67,10 @@
     return TeamCityProperties.getBooleanOrTrue("teamcity.hg.enableBookmarks");
   }
 
+  public boolean useTagsAsBranches() {
+    return TeamCityProperties.getBooleanOrTrue("teamcity.hg.useTagsAsBranches");
+  }
+
   public long getMirrorExpirationTimeoutMillis() {
     int days = TeamCityProperties.getInteger("teamcity.hg.mirrorExpirationTimeoutDays", 7);
     return days * Dates.ONE_DAY;
--- a/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/AgentMirrorCleanerTest.java	Tue Oct 01 12:51:13 2013 +0400
+++ b/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/AgentMirrorCleanerTest.java	Tue Oct 01 12:52:11 2013 +0400
@@ -1,16 +1,12 @@
 package jetbrains.buildServer.buildTriggers.vcs.mercurial;
 
-import com.intellij.openapi.diagnostic.Logger;
-import jetbrains.buildServer.TempFiles;
 import jetbrains.buildServer.agent.*;
 import jetbrains.buildServer.buildTriggers.vcs.mercurial.command.TestCommandSettingsFactory;
-import jetbrains.buildServer.log.Log4jFactory;
 import jetbrains.buildServer.vcs.*;
 import jetbrains.buildServer.vcs.impl.VcsRootImpl;
 import org.jetbrains.annotations.NotNull;
 import org.jmock.Expectations;
 import org.jmock.Mockery;
-import org.testng.annotations.AfterMethod;
 import org.testng.annotations.BeforeMethod;
 import org.testng.annotations.Test;
 
@@ -26,13 +22,8 @@
  * @author dmitry.neverov
  */
 @Test
-public class AgentMirrorCleanerTest {
+public class AgentMirrorCleanerTest extends BaseMercurialTestCase {
 
-  static {
-    Logger.setFactory(new Log4jFactory());
-  }
-
-  private TempFiles myTempFiles = new TempFiles();
   private Mockery myContext;
   private MercurialAgentSideVcsSupport myVcsSupport;
   private AgentMirrorCleaner myCleaner;
@@ -43,6 +34,7 @@
 
   @BeforeMethod
   public void setUp() throws Exception {
+    super.setUp();
     myContext = new Mockery();
     myWorkDir = myTempFiles.createTempDir();
 
@@ -53,10 +45,9 @@
     }});
 
     AgentPluginConfigImpl pluginConfig = new AgentPluginConfigImpl(agentConfig);
-    AgentHgPathProvider hgPathProvider = new AgentHgPathProvider(agentConfig);
     myMirrorManager = new MirrorManagerImpl(pluginConfig);
-    AgentRepoFactory repoFactory = new AgentRepoFactory(new TestCommandSettingsFactory());
-    myVcsSupport = new MercurialAgentSideVcsSupport(pluginConfig, hgPathProvider, myMirrorManager, repoFactory);
+    AgentRepoFactory repoFactory = new AgentRepoFactory(new TestCommandSettingsFactory(), new AgentHgPathProvider(agentConfig));
+    myVcsSupport = new MercurialAgentSideVcsSupport(pluginConfig, myMirrorManager, repoFactory);
     myCleaner = new AgentMirrorCleaner(myMirrorManager);
     myLogger = myContext.mock(BuildProgressLogger.class);
     myContext.checking(new Expectations() {{
@@ -65,11 +56,6 @@
     }});
   }
 
-  @AfterMethod
-  public void tearDown() {
-    myTempFiles.cleanup();
-  }
-
 
   public void should_add_cleaners_only_for_roots_not_used_in_build() throws Exception {
     //setup mirrors for 2 roots on an agent:
--- a/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/AgentSideCheckoutTest.java	Tue Oct 01 12:51:13 2013 +0400
+++ b/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/AgentSideCheckoutTest.java	Tue Oct 01 12:52:11 2013 +0400
@@ -46,7 +46,7 @@
  *         Date: 30.07.2008
  */
 @Test
-public class AgentSideCheckoutTest extends BaseMercurialTestCase {
+public class AgentSideCheckoutTest extends BaseMercurialPatchTestCase {
 
   final static String HG_PATH_REFERENCE = "%" + HgDetector.AGENT_HG_PATH_PROPERTY + "%";
   private MercurialAgentSideVcsSupport myVcsSupport;
@@ -74,9 +74,8 @@
 
     final AgentPluginConfigImpl pluginConfig = new AgentPluginConfigImpl(agentConfig);
     myVcsSupport = new MercurialAgentSideVcsSupport(pluginConfig,
-            new AgentHgPathProvider(agentConfig),
             new MirrorManagerImpl(pluginConfig),
-            new AgentRepoFactory(new TestCommandSettingsFactory()));
+            new AgentRepoFactory(new TestCommandSettingsFactory(), new AgentHgPathProvider(agentConfig)));
 
     myLogger = myContext.mock(BuildProgressLogger.class);
     myContext.checking(new Expectations() {{
--- a/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/AgentSideCheckoutWithSubreposTest.java	Tue Oct 01 12:51:13 2013 +0400
+++ b/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/AgentSideCheckoutWithSubreposTest.java	Tue Oct 01 12:52:11 2013 +0400
@@ -1,7 +1,5 @@
 package jetbrains.buildServer.buildTriggers.vcs.mercurial;
 
-import com.intellij.openapi.diagnostic.Logger;
-import jetbrains.buildServer.TempFiles;
 import jetbrains.buildServer.agent.AgentRunningBuild;
 import jetbrains.buildServer.agent.BuildAgentConfiguration;
 import jetbrains.buildServer.agent.BuildProgressLogger;
@@ -9,7 +7,6 @@
 import jetbrains.buildServer.buildTriggers.vcs.mercurial.command.AuthSettings;
 import jetbrains.buildServer.buildTriggers.vcs.mercurial.command.TestCommandSettingsFactory;
 import jetbrains.buildServer.buildTriggers.vcs.mercurial.command.exception.ConnectionRefusedException;
-import jetbrains.buildServer.log.Log4jFactory;
 import jetbrains.buildServer.util.FileUtil;
 import jetbrains.buildServer.vcs.CheckoutRules;
 import jetbrains.buildServer.vcs.IncludeRule;
@@ -39,9 +36,8 @@
  * @author dmitry.neverov
  */
 @Test
-public class AgentSideCheckoutWithSubreposTest {
+public class AgentSideCheckoutWithSubreposTest extends BaseMercurialTestCase {
 
-  private TempFiles myTempFiles = new TempFiles();
   private File myOriginalRepositoriesParentDir;
   private File myWorkDir;
   private Mockery myContext;
@@ -52,12 +48,9 @@
   private File myDefaultWorkDir;
   private MirrorManagerImpl myMirrorManager;
 
-  static {
-    Logger.setFactory(new Log4jFactory());
-  }
-
   @BeforeMethod
   public void setUp() throws Exception {
+    super.setUp();
     myProcesses = new ArrayList<Process>();
     myOriginalRepositoriesParentDir = myTempFiles.createTempDir();
     myDefaultWorkDir = new File(myOriginalRepositoriesParentDir, "agentWorkDir");
@@ -73,9 +66,8 @@
     final AgentPluginConfigImpl pluginConfig = new AgentPluginConfigImpl(agentConfig);
     myMirrorManager = new MirrorManagerImpl(pluginConfig);
     myVcsSupport = new MercurialAgentSideVcsSupport(pluginConfig,
-            new AgentHgPathProvider(agentConfig),
             myMirrorManager,
-            new AgentRepoFactory(new TestCommandSettingsFactory()));
+            new AgentRepoFactory(new TestCommandSettingsFactory(), new AgentHgPathProvider(agentConfig)));
 
     myLogger = myContext.mock(BuildProgressLogger.class);
     myContext.checking(new Expectations() {{
@@ -86,7 +78,7 @@
 
   @AfterMethod
   public void tearDown() {
-    myTempFiles.cleanup();
+    super.tearDown();
     for (Process p : myProcesses) {
       p.destroy();
     }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/BaseMercurialPatchTestCase.java	Tue Oct 01 12:52:11 2013 +0400
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2000-2011 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package jetbrains.buildServer.buildTriggers.vcs.mercurial;
+
+import jetbrains.buildServer.vcs.impl.VcsRootImpl;
+import jetbrains.buildServer.vcs.patches.PatchTestCase;
+import org.jetbrains.annotations.NotNull;
+
+import java.io.File;
+import java.io.IOException;
+
+import static jetbrains.buildServer.buildTriggers.vcs.mercurial.Util.copyRepository;
+
+/**
+ * @author Pavel.Sher
+ *         Date: 31.07.2008
+ */
+public abstract class BaseMercurialPatchTestCase extends PatchTestCase {
+  protected VcsRootImpl createVcsRoot(@NotNull String repPath) throws IOException {
+    File repository = copyRepository(myTempFiles, repPath);
+    return new VcsRootBuilder().withUrl(repository.getAbsolutePath()).build();
+  }
+
+  protected VcsRootImpl createVcsRoot(@NotNull String repPath, @NotNull String branchName) throws IOException {
+    File repository = copyRepository(myTempFiles, repPath);
+    return new VcsRootBuilder().withUrl(repository.getAbsolutePath()).withBranch(branchName).build();
+  }
+
+  protected String simpleRepo() {
+    return new File("mercurial-tests/testData/rep1").getAbsolutePath();
+  }
+}
--- a/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/BaseMercurialTestCase.java	Tue Oct 01 12:51:13 2013 +0400
+++ b/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/BaseMercurialTestCase.java	Tue Oct 01 12:52:11 2013 +0400
@@ -1,62 +1,19 @@
-/*
- * Copyright 2000-2011 JetBrains s.r.o.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
 package jetbrains.buildServer.buildTriggers.vcs.mercurial;
 
 import jetbrains.buildServer.TempFiles;
-import jetbrains.buildServer.vcs.impl.VcsRootImpl;
-import jetbrains.buildServer.vcs.patches.PatchTestCase;
-import org.jetbrains.annotations.NotNull;
 import org.testng.annotations.AfterMethod;
 import org.testng.annotations.BeforeMethod;
 
-import java.io.File;
-import java.io.IOException;
-
-import static jetbrains.buildServer.buildTriggers.vcs.mercurial.Util.copyRepository;
-
-/**
- * @author Pavel.Sher
- *         Date: 31.07.2008
- */
-public abstract class BaseMercurialTestCase extends PatchTestCase {
+public class BaseMercurialTestCase {
   protected TempFiles myTempFiles;
 
-  @Override
   @BeforeMethod
-  protected void setUp() throws Exception {
-    super.setUp();
+  public void setUp() throws Exception {
     myTempFiles = new TempFiles();
   }
 
   @AfterMethod
-  protected void tearDown() throws Exception {
+  public void tearDown() {
     myTempFiles.cleanup();
   }
-
-  protected VcsRootImpl createVcsRoot(@NotNull String repPath) throws IOException {
-    File repository = copyRepository(myTempFiles, repPath);
-    return new VcsRootBuilder().withUrl(repository.getAbsolutePath()).build();
-  }
-
-  protected VcsRootImpl createVcsRoot(@NotNull String repPath, @NotNull String branchName) throws IOException {
-    File repository = copyRepository(myTempFiles, repPath);
-    return new VcsRootBuilder().withUrl(repository.getAbsolutePath()).withBranch(branchName).build();
-  }
-
-  protected String simpleRepo() {
-    return new File("mercurial-tests/testData/rep1").getAbsolutePath();
-  }
 }
--- a/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/BookmarksTest.java	Tue Oct 01 12:51:13 2013 +0400
+++ b/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/BookmarksTest.java	Tue Oct 01 12:52:11 2013 +0400
@@ -1,8 +1,5 @@
 package jetbrains.buildServer.buildTriggers.vcs.mercurial;
 
-import com.intellij.openapi.diagnostic.Logger;
-import jetbrains.buildServer.TempFiles;
-import jetbrains.buildServer.log.Log4jFactory;
 import jetbrains.buildServer.vcs.RepositoryStateData;
 
 import jetbrains.buildServer.vcs.VcsRoot;
@@ -25,13 +22,8 @@
 
 @RequiredHgVersion(min = "2.4")
 @Test(dataProviderClass = HgVersionConstraint.class, dataProvider = "installedHgVersion")
-public class BookmarksTest {
+public class BookmarksTest extends BaseMercurialTestCase {
 
-  static {
-    Logger.setFactory(new Log4jFactory());
-  }
-
-  private TempFiles myTempFiles;
   private File myRemoteRepository;
   private ServerPluginConfig myConfig;
   private MercurialVcsSupport myVcs;
@@ -39,7 +31,7 @@
 
   @BeforeMethod
   public void setUp() throws Exception {
-    myTempFiles = new TempFiles();
+    super.setUp();
     myRemoteRepository = myTempFiles.createTempDir();
     myConfig = serverPluginConfig()
             .cachesDir(myTempFiles.createTempDir())
@@ -50,11 +42,6 @@
     myRoot = vcsRoot().withUrl(myRemoteRepository.getAbsolutePath()).build();
   }
 
-  @AfterMethod
-  public void tearDown() {
-    myTempFiles.cleanup();
-  }
-
 
   public void current_state_should_include_bookmarks(@NotNull HgVersion _) throws Exception {
     setupRemoteRepositoryWithTwoBookmarks();
--- a/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/CleanupTest.java	Tue Oct 01 12:51:13 2013 +0400
+++ b/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/CleanupTest.java	Tue Oct 01 12:52:11 2013 +0400
@@ -26,9 +26,8 @@
  * @author dmitry.neverov
  */
 @Test
-public class CleanupTest {
+public class CleanupTest extends BaseMercurialTestCase {
 
-  private TempFiles myTempFiles;
   private File myCachesDir;
   private Cleanup myCleanup;
   private MirrorManager myMirrorManager;
@@ -36,7 +35,7 @@
 
   @BeforeMethod
   public void setUp() throws Exception {
-    myTempFiles = new TempFiles();
+    super.setUp();
     myCachesDir = myTempFiles.createTempDir();
 
     ServerPluginConfig config = new ServerPluginConfigBuilder().cachesDir(myCachesDir).withMirrorExpirationTimeout(myMirrorExpirationTimeout).build();
@@ -45,11 +44,6 @@
     myCleanup = new Cleanup(myMirrorManager, config);
   }
 
-  @AfterMethod
-  public void tearDown() {
-    myTempFiles.cleanup();
-  }
-
 
   public void cleanup_should_remove_directories_by_timestamp() throws Exception {
     final String url1 = "http://some.org/repository1";
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/CommitsInfoBuilderSupportTest.java	Tue Oct 01 12:52:11 2013 +0400
@@ -0,0 +1,100 @@
+package jetbrains.buildServer.buildTriggers.vcs.mercurial;
+
+import jetbrains.buildServer.TestLogger;
+import jetbrains.buildServer.vcs.CheckoutRules;
+import jetbrains.buildServer.vcs.CommitDataBean;
+import jetbrains.buildServer.vcs.VcsException;
+import jetbrains.buildServer.vcs.VcsRoot;
+import jetbrains.vcs.api.CommitInfo;
+import org.testng.Assert;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Set;
+import java.util.TreeSet;
+
+import static jetbrains.buildServer.buildTriggers.vcs.mercurial.MercurialSupportBuilder.mercurialSupport;
+import static jetbrains.buildServer.buildTriggers.vcs.mercurial.Util.copyRepository;
+import static jetbrains.buildServer.buildTriggers.vcs.mercurial.VcsRootBuilder.vcsRoot;
+
+/**
+ * Created 30.09.13 13:07
+ *
+ * @author Eugene Petrenko (eugene.petrenko@jetbrains.com)
+ */
+public class CommitsInfoBuilderSupportTest extends BaseMercurialTestCase {
+  private File myRemoteRepo;
+  private MercurialVcsSupport myVcs;
+  private MercurialCommitsInfoBuilderSupport mySupport;
+
+  @Override
+  @BeforeMethod
+  public void setUp() throws Exception {
+    super.setUp();
+    ServerPluginConfig pluginConfig = new ServerPluginConfigBuilder()
+//            .hgPath("hg")
+            .cachesDir(myTempFiles.createTempDir())
+            .build();
+    MercurialSupportBuilder mercurialBuilder = mercurialSupport().withConfig(pluginConfig);
+    myVcs = mercurialBuilder.build();
+    mySupport = new MercurialCommitsInfoBuilderSupport(myVcs, mercurialBuilder.getHgRootFactory());
+    myRemoteRepo = myTempFiles.createTempDir();
+    copyRepository(new File("mercurial-tests/testData/rep2"), myRemoteRepo);
+  }
+
+  @Test
+  public void simpleTest() throws IOException, VcsException {
+    TestLogger.enableMainAndVCSLoggerDebug();
+
+    VcsRoot root = vcsRoot().withUrl(myRemoteRepo).build();
+
+
+    final List<CommitDataBean> commitInfos = mySupport.collectCommits(root, CheckoutRules.DEFAULT);
+    Assert.assertFalse(commitInfos.isEmpty());
+
+    for (CommitInfo commitInfo : commitInfos) {
+      System.out.println(commitInfo);
+    }
+
+    final Set<String> branches = new TreeSet<String>();
+    final Set<String> tags = new TreeSet<String>();
+    final Set<String> commits = new TreeSet<String>();
+    final Set<String> parents = new TreeSet<String>();
+    for (CommitInfo commitInfo : commitInfos) {
+      branches.addAll(commitInfo.getBranches());
+      tags.addAll(commitInfo.getTags());
+
+      //commit should be returned once
+      Assert.assertTrue(commits.add(commitInfo.getVersion()));
+      parents.addAll(commitInfo.getParentRevisions());
+    }
+
+    Assert.assertEquals(branches, new TreeSet<String>(Arrays.asList("default", "personal-branch", "test", "topic", "<TEST> Branch with exitics)(*&^%$#@!", "NULL")));
+    Assert.assertEquals(tags, new TreeSet<String>(Arrays.asList("t1")));
+
+    //check all parent commits are included into the set
+    Assert.assertTrue(commits.containsAll(parents), "Unknown commits: " + new TreeSet<String>(parents){{removeAll(commits);}});
+
+    final String v = "4780519e01aa"; ///30
+    Assert.assertFalse(parents.contains(v));
+  }
+
+  @Test
+  public void should_not_have_parse_errors() throws IOException, VcsException {
+    TestLogger.enableMainAndVCSLoggerDebug();
+
+    VcsRoot root = vcsRoot().withUrl(myRemoteRepo).build();
+
+    final List<CommitDataBean> commitInfos = mySupport.collectCommits(root, CheckoutRules.DEFAULT);
+    Assert.assertFalse(commitInfos.isEmpty());
+
+    for (CommitInfo commitInfo : commitInfos) {
+      System.out.println(commitInfo);
+    }
+  }
+
+}
--- a/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/DagFeaturesTest.java	Tue Oct 01 12:51:13 2013 +0400
+++ b/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/DagFeaturesTest.java	Tue Oct 01 12:52:11 2013 +0400
@@ -1,8 +1,5 @@
 package jetbrains.buildServer.buildTriggers.vcs.mercurial;
 
-import com.intellij.openapi.diagnostic.Logger;
-import jetbrains.buildServer.TempFiles;
-import jetbrains.buildServer.log.Log4jFactory;
 import jetbrains.buildServer.util.TestFor;
 import jetbrains.buildServer.vcs.CheckoutRules;
 import jetbrains.buildServer.vcs.ModificationData;
@@ -22,18 +19,14 @@
  * @author dmitry.neverov
  */
 @Test
-public class DagFeaturesTest {
+public class DagFeaturesTest extends BaseMercurialTestCase {
 
-  static {
-    Logger.setFactory(new Log4jFactory());
-  }
-
-  private TempFiles myTempFiles = new TempFiles();
   private MercurialVcsSupport myHg;
   private String myRepository;
 
   @BeforeMethod
   public void setUp() throws Exception {
+    super.setUp();
     ServerPluginConfig config = new ServerPluginConfigBuilder()
             .cachesDir(myTempFiles.createTempDir())
             .build();
@@ -45,10 +38,6 @@
     myRepository = copy.getAbsolutePath();
   }
 
-  public void tearDown() {
-    myTempFiles.cleanup();
-  }
-
 
   @TestFor(issues = "TW-17882")
   public void should_detect_changes_from_named_branches() throws Exception {
--- a/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/HgFileUtilTest.java	Tue Oct 01 12:51:13 2013 +0400
+++ b/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/HgFileUtilTest.java	Tue Oct 01 12:52:11 2013 +0400
@@ -1,7 +1,6 @@
 package jetbrains.buildServer.buildTriggers.vcs.mercurial;
 
 
-import jetbrains.buildServer.util.FileUtil;
 import jetbrains.buildServer.util.TestFor;
 import org.testng.annotations.Test;
 
@@ -12,7 +11,6 @@
 import java.util.List;
 import java.util.Set;
 import java.util.concurrent.CountDownLatch;
-import com.intellij.openapi.util.SystemInfo;
 
 import static org.testng.AssertJUnit.assertEquals;
 import static org.testng.AssertJUnit.assertFalse;
--- a/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/HgRepoTest.java	Tue Oct 01 12:51:13 2013 +0400
+++ b/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/HgRepoTest.java	Tue Oct 01 12:52:11 2013 +0400
@@ -4,13 +4,9 @@
 import jetbrains.buildServer.TempFiles;
 import jetbrains.buildServer.buildTriggers.vcs.mercurial.command.AuthSettings;
 import jetbrains.buildServer.buildTriggers.vcs.mercurial.command.TestCommandSettingsFactory;
-import jetbrains.buildServer.log.Log4jFactory;
-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;
 
 import static jetbrains.buildServer.buildTriggers.vcs.mercurial.Util.copyRepository;
@@ -19,23 +15,7 @@
 import static org.testng.AssertJUnit.assertTrue;
 
 @Test
-public class HgRepoTest {
-
-  static {
-    Logger.setFactory(new Log4jFactory());
-  }
-
-  private TempFiles myTempFiles;
-
-  @BeforeMethod
-  public void setUp() throws IOException {
-    myTempFiles = new TempFiles();
-  }
-
-  @AfterMethod
-  public void tearDown() {
-    myTempFiles.cleanup();
-  }
+public class HgRepoTest extends BaseMercurialTestCase {
 
   public void subrepos() throws Exception {
     File repository = myTempFiles.createTempDir();
--- a/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/HgVcsRootFactoryTest.java	Tue Oct 01 12:51:13 2013 +0400
+++ b/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/HgVcsRootFactoryTest.java	Tue Oct 01 12:52:11 2013 +0400
@@ -4,8 +4,6 @@
 import jetbrains.buildServer.buildTriggers.vcs.mercurial.command.HgVcsRoot;
 import jetbrains.buildServer.util.TestFor;
 import jetbrains.buildServer.vcs.VcsRoot;
-import org.testng.annotations.AfterMethod;
-import org.testng.annotations.BeforeMethod;
 import org.testng.annotations.Test;
 
 import java.io.File;
@@ -16,19 +14,7 @@
 import static org.testng.AssertJUnit.assertNull;
 
 @Test
-public class HgVcsRootFactoryTest {
-
-  private TempFiles myTempFiles;
-
-  @BeforeMethod(alwaysRun = true)
-  public void setUp() {
-    myTempFiles = new TempFiles();
-  }
-
-  @AfterMethod(alwaysRun = true)
-  public void tearDown() {
-    myTempFiles.cleanup();
-  }
+public class HgVcsRootFactoryTest extends BaseMercurialTestCase {
 
   @TestFor(issues = "TW-25057")
   public void vcs_root_custom_clone_dir_should_not_contain_password() throws Exception {
--- a/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/ListFilesSupportTest.java	Tue Oct 01 12:51:13 2013 +0400
+++ b/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/ListFilesSupportTest.java	Tue Oct 01 12:52:11 2013 +0400
@@ -1,8 +1,5 @@
 package jetbrains.buildServer.buildTriggers.vcs.mercurial;
 
-import com.intellij.openapi.diagnostic.Logger;
-import jetbrains.buildServer.TempFiles;
-import jetbrains.buildServer.log.Log4jFactory;
 import jetbrains.buildServer.util.TestFor;
 import jetbrains.buildServer.vcs.ListDirectChildrenPolicy;
 import jetbrains.buildServer.vcs.VcsException;
@@ -27,25 +24,21 @@
  * @author dmitry.neverov
  */
 @Test
-public class ListFilesSupportTest {
-
-  static {
-    Logger.setFactory(new Log4jFactory());
-  }
+public class ListFilesSupportTest extends BaseMercurialTestCase {
 
   private MercurialVcsSupport myVcs;
   private VcsRoot myRoot;
   private File myRemoteRepoDir;
 
   @BeforeMethod
-  protected void setUp() throws Exception {
-    TempFiles tempFiles = new TempFiles();
+  public void setUp() throws Exception {
+    super.setUp();
     ServerPluginConfig myPluginConfig = new ServerPluginConfigBuilder()
-            .cachesDir(tempFiles.createTempDir())
+            .cachesDir(myTempFiles.createTempDir())
             .build();
     myVcs = mercurialSupport().withConfig(myPluginConfig).build();
 
-    myRemoteRepoDir = tempFiles.createTempDir();
+    myRemoteRepoDir = myTempFiles.createTempDir();
     copyRepository(new File("mercurial-tests/testData/rep1"), myRemoteRepoDir);
     myRoot = vcsRoot().withUrl(myRemoteRepoDir.getAbsolutePath()).build();
   }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/MercurialModificationInfoBuilderTest.java	Tue Oct 01 12:52:11 2013 +0400
@@ -0,0 +1,54 @@
+package jetbrains.buildServer.buildTriggers.vcs.mercurial;
+
+import jetbrains.buildServer.vcs.CheckoutRules;
+import jetbrains.buildServer.vcs.ModificationData;
+import jetbrains.buildServer.vcs.RepositoryStateData;
+import jetbrains.buildServer.vcs.VcsRoot;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import java.io.File;
+import java.util.List;
+
+import static jetbrains.buildServer.buildTriggers.vcs.mercurial.MercurialSupportBuilder.mercurialSupport;
+import static jetbrains.buildServer.buildTriggers.vcs.mercurial.ModificationDataMatcher.modificationData;
+import static jetbrains.buildServer.buildTriggers.vcs.mercurial.ServerPluginConfigBuilder.serverPluginConfig;
+import static jetbrains.buildServer.buildTriggers.vcs.mercurial.VcsRootBuilder.vcsRoot;
+import static jetbrains.buildServer.util.Util.map;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.hasItem;
+import static org.testng.AssertJUnit.assertEquals;
+
+@RequiredHgVersion(min = "1.7.0")
+@Test(dataProviderClass = HgVersionConstraint.class, dataProvider = "installedHgVersion")
+public class MercurialModificationInfoBuilderTest extends BaseMercurialTestCase {
+
+  private File myRemoteRepository;
+  private MercurialModificationInfoBuilder myModInfoBuilder;
+
+  @BeforeMethod
+  public void setUp() throws Exception {
+    super.setUp();
+    ServerPluginConfig config = serverPluginConfig()
+            .cachesDir(myTempFiles.createTempDir())
+            .hgPath(Util.getHgPath())
+            .build();
+
+    myRemoteRepository = myTempFiles.createTempDir();
+    Util.copyRepository(new File("mercurial-tests/testData/rep2"), myRemoteRepository);
+    MercurialSupportBuilder hgBuilder = mercurialSupport().withConfig(config);
+    MercurialVcsSupport vcs = hgBuilder.build();
+    myModInfoBuilder = new MercurialModificationInfoBuilder(vcs, hgBuilder.getHgRootFactory(), hgBuilder.getHgRepoFactory(), hgBuilder.getHgPathProvider());
+  }
+
+
+  public void should_return_commits_for_every_revision_in_state(HgVersion _) throws Exception {
+    VcsRoot root = vcsRoot().withUrl(myRemoteRepository.getAbsolutePath()).build();
+    RepositoryStateData state = RepositoryStateData.createVersionState("default",
+            map("default", "505c5b9d01e6", "personal-branch", "9ec402c74298"));
+    List<ModificationData> changes = myModInfoBuilder.fetchModificationInfo(root, state, CheckoutRules.DEFAULT);
+    assertThat(changes, hasItem(modificationData().withVersion("505c5b9d01e6")));
+    assertThat(changes, hasItem(modificationData().withVersion("9ec402c74298")));
+    assertEquals(2, changes.size());
+  }
+}
--- a/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/MercurialSupportBuilder.java	Tue Oct 01 12:51:13 2013 +0400
+++ b/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/MercurialSupportBuilder.java	Tue Oct 01 12:52:11 2013 +0400
@@ -21,6 +21,7 @@
   private List<MercurialServerExtension> myExtensions = new ArrayList<MercurialServerExtension>();
   private HgVcsRootFactory myHgRootFactory;
   private RepoFactory myRepoFactory;
+  private ServerHgPathProvider myHgPathProvider;
 
   public static MercurialSupportBuilder mercurialSupport() {
     return new MercurialSupportBuilder();
@@ -33,16 +34,17 @@
     EventDispatcher<ServerListener> dispatcher = EventDispatcher.create(ServerListener.class);
     myHgRootFactory = new HgVcsRootFactory(myConfig);
     MirrorManagerImpl mirrorManager = new MirrorManagerImpl(myConfig);
-    ServerHgPathProvider hgPathProvider = new ServerHgPathProvider(myConfig);
-    RepoFactory repoFactory = myRepoFactory != null ? myRepoFactory : new RepoFactory(myConfig, new TestCommandSettingsFactory());
-    HgTestConnectionSupport testConnection = new HgTestConnectionSupport(myHgRootFactory, repoFactory, mirrorManager, hgPathProvider);
+    myHgPathProvider = new ServerHgPathProvider(myConfig);
+    if (myRepoFactory == null)
+      myRepoFactory = new RepoFactory(myConfig, new TestCommandSettingsFactory(), myHgPathProvider);
+    HgTestConnectionSupport testConnection = new HgTestConnectionSupport(myHgRootFactory, myRepoFactory, mirrorManager, myHgPathProvider);
     final ResetCacheRegister resetCacheManager = myContext.mock(ResetCacheRegister.class);
     myContext.checking(new Expectations() {{
       allowing(resetCacheManager).registerHandler(with(any(ResetCacheHandler.class)));
     }});
-    MercurialVcsSupport vcs = new MercurialVcsSupport(dispatcher, resetCacheManager, myConfig, hgPathProvider,
-            repoFactory, mirrorManager, myHgRootFactory, testConnection, new SubrepoCheckoutRulesProviderImpl());
-    vcs.setExtensions(myExtensions);
+    MercurialVcsSupport vcs = new MercurialVcsSupport(dispatcher, resetCacheManager, myConfig, myHgPathProvider,
+            myRepoFactory, mirrorManager, myHgRootFactory, testConnection, new SubrepoCheckoutRulesProviderImpl());
+    vcs.addExtensions(myExtensions);
     return vcs;
   }
 
@@ -56,4 +58,16 @@
     myRepoFactory = repoFactory;
     return this;
   }
+
+  public HgVcsRootFactory getHgRootFactory() {
+    return myHgRootFactory;
+  }
+
+  public RepoFactory getHgRepoFactory() {
+    return myRepoFactory;
+  }
+
+  public ServerHgPathProvider getHgPathProvider() {
+    return myHgPathProvider;
+  }
 }
--- a/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/MercurialVcsSupportTest.java	Tue Oct 01 12:51:13 2013 +0400
+++ b/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/MercurialVcsSupportTest.java	Tue Oct 01 12:52:11 2013 +0400
@@ -45,11 +45,12 @@
 import static org.hamcrest.Matchers.not;
 
 @Test
-public class MercurialVcsSupportTest extends BaseMercurialTestCase {
+public class MercurialVcsSupportTest extends BaseMercurialPatchTestCase {
 
   private MercurialVcsSupport myVcs;
   private String myRep2Path = new File("mercurial-tests/testData/rep2").getAbsolutePath();
   private ServerPluginConfig myPluginConfig;
+  private HgPathProvider myHgPathProvider;
 
   @BeforeMethod
   protected void setUp() throws Exception {
@@ -57,7 +58,9 @@
     myPluginConfig = new ServerPluginConfigBuilder()
             .cachesDir(myTempFiles.createTempDir())
             .build();
-    myVcs = mercurialSupport().withConfig(myPluginConfig).build();
+    MercurialSupportBuilder mercurialBuilder = mercurialSupport().withConfig(myPluginConfig);
+    myVcs = mercurialBuilder.build();
+    myHgPathProvider = mercurialBuilder.getHgPathProvider();
   }
 
   protected String getTestDataPath() {
@@ -548,7 +551,7 @@
     if (!SystemInfo.isUnix)
       return;
 
-    RepoFactory repoFactory = new RepoFactory(myPluginConfig, new TestCommandSettingsFactory());
+    RepoFactory repoFactory = new RepoFactory(myPluginConfig, new TestCommandSettingsFactory(), myHgPathProvider);
 
     //create a file on the server
     File dirOnTheServer = myTempFiles.createTempDir();
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/MergeSupportSubreposTest.java	Tue Oct 01 12:52:11 2013 +0400
@@ -0,0 +1,68 @@
+package jetbrains.buildServer.buildTriggers.vcs.mercurial;
+
+import jetbrains.buildServer.vcs.*;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import java.io.File;
+import java.util.List;
+
+import static jetbrains.buildServer.buildTriggers.vcs.mercurial.MercurialSupportBuilder.mercurialSupport;
+import static jetbrains.buildServer.buildTriggers.vcs.mercurial.ModificationDataMatcher.modificationData;
+import static jetbrains.buildServer.buildTriggers.vcs.mercurial.Util.copyRepository;
+import static jetbrains.buildServer.buildTriggers.vcs.mercurial.VcsRootBuilder.vcsRoot;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.hasItem;
+import static org.testng.AssertJUnit.assertFalse;
+
+@Test
+public class MergeSupportSubreposTest extends BaseMercurialTestCase {
+
+  private File mySubrepo1;
+  private File mySubrepo2;
+  private MercurialVcsSupport myVcs;
+  private MercurialMergeSupport myMergeSupport;
+
+  @BeforeMethod
+  @Override
+  public void setUp() throws Exception {
+    super.setUp();
+    File parentDir = myTempFiles.createTempDir();
+    mySubrepo1 = new File(parentDir, "subrepo1");
+    mySubrepo2 = new File(parentDir, "subrepo2");
+    mySubrepo1.mkdirs();
+    mySubrepo2.mkdirs();
+    copyRepository(new File("mercurial-tests/testData/merge/subrepos/subrepo1"), mySubrepo1);
+    copyRepository(new File("mercurial-tests/testData/merge/subrepos/subrepo2"), mySubrepo2);
+
+    ServerPluginConfig pluginConfig = new ServerPluginConfigBuilder()
+            .cachesDir(myTempFiles.createTempDir())
+            .build();
+    MercurialSupportBuilder mercurialBuilder = mercurialSupport().withConfig(pluginConfig);
+    myVcs = mercurialBuilder.build();
+    myMergeSupport = new MercurialMergeSupport(myVcs, myVcs.getMirrorManager(), pluginConfig,
+            mercurialBuilder.getHgRootFactory(), mercurialBuilder.getHgRepoFactory());
+  }
+
+
+  public void should_do_merge_in_subrepo_if_it_has_appropriate_branch() throws Exception {
+    VcsRoot root = vcsRoot().withUrl(mySubrepo1).withSubrepoChanges(true).build();
+    RepositoryStateData beforeMerge = myVcs.getCollectChangesPolicy().getCurrentState(root);
+    myMergeSupport.merge(root, "0bd1ae88632d", "default", "merge into default", new MergeOptions());
+    RepositoryStateData afterMerge = myVcs.getCollectChangesPolicy().getCurrentState(root);
+    List<ModificationData> ms = myVcs.getCollectChangesPolicy().collectChanges(root, beforeMerge, afterMerge, CheckoutRules.DEFAULT);
+    assertThat("Cannot find main repo merge commit", ms, hasItem(modificationData().withDescription("merge into default")
+            .withParentRevisions("eb211547efbe", "0bd1ae88632d")));
+    assertThat("Cannot find subrepo merge commit", ms, hasItem(modificationData().withDescription("merge into default")
+            .withParentRevisions("675fa105b184", "1532dee5d922")));
+  }
+
+
+  public void should_report_conflicts_from_subrepos() throws Exception {
+    VcsRoot root = vcsRoot().withUrl(mySubrepo1).withSubrepoChanges(true).build();
+    MergeResult result = myMergeSupport.merge(root, "3e43f76179ed", "default", "merge into default", new MergeOptions());
+    assertFalse(result.isSuccess());
+    assertThat("conflict from main repo is not reported", result.getConflicts(), hasItem("b/c"));
+    assertThat("conflict from subrepo is not reported", result.getConflicts(), hasItem("subrepo2/b/c"));
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/MergeSupportTest.java	Tue Oct 01 12:52:11 2013 +0400
@@ -0,0 +1,115 @@
+package jetbrains.buildServer.buildTriggers.vcs.mercurial;
+
+import jetbrains.buildServer.vcs.*;
+import org.jetbrains.annotations.NotNull;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import java.io.File;
+import java.util.List;
+import java.util.Map;
+
+import static java.util.Arrays.asList;
+import static jetbrains.buildServer.buildTriggers.vcs.mercurial.MercurialSupportBuilder.mercurialSupport;
+import static jetbrains.buildServer.buildTriggers.vcs.mercurial.ModificationDataMatcher.modificationData;
+import static jetbrains.buildServer.buildTriggers.vcs.mercurial.Util.copyRepository;
+import static jetbrains.buildServer.buildTriggers.vcs.mercurial.VcsRootBuilder.vcsRoot;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.hasItem;
+import static org.testng.AssertJUnit.assertFalse;
+import static org.testng.AssertJUnit.assertTrue;
+
+
+@RequiredHgVersion(min = "1.7.0")
+@Test(dataProviderClass = HgVersionConstraint.class, dataProvider = "installedHgVersion")
+public class MergeSupportTest extends BaseMercurialTestCase {
+
+  private File myRemoteRepo;
+  private MercurialVcsSupport myVcs;
+  private MercurialMergeSupport myMergeSupport;
+
+  @Override
+  @BeforeMethod
+  public void setUp() throws Exception {
+    super.setUp();
+    ServerPluginConfig pluginConfig = new ServerPluginConfigBuilder()
+            .cachesDir(myTempFiles.createTempDir())
+            .build();
+    MercurialSupportBuilder mercurialBuilder = mercurialSupport().withConfig(pluginConfig);
+    myVcs = mercurialBuilder.build();
+    myMergeSupport = new MercurialMergeSupport(myVcs, myVcs.getMirrorManager(), pluginConfig, mercurialBuilder.getHgRootFactory(), mercurialBuilder.getHgRepoFactory());
+    myRemoteRepo = myTempFiles.createTempDir();
+    copyRepository(new File("mercurial-tests/testData/merge/no.subrepos"), myRemoteRepo);
+  }
+
+
+  public void should_return_succesful_result_when_merge_succeeds(@NotNull HgVersion _) throws Exception {
+    VcsRoot root = vcsRoot().withUrl(myRemoteRepo).build();
+    MergeResult mergeResult = myMergeSupport.merge(root, "2742914d19b2", "default", "merge topic1 into default", new MergeOptions());
+    assertTrue(mergeResult.isSuccess());
+  }
+
+
+  public void result_of_succesful_merge_should_appear_in_remote_repository(@NotNull HgVersion _) throws Exception {
+    final String mergeDestinationBranch = "default";
+    final String mergeCommitMessage = "merge topic1 into default";
+
+    VcsRoot root = vcsRoot().withUrl(myRemoteRepo).build();
+    RepositoryStateData stateBeforeMerge = myVcs.getCollectChangesPolicy().getCurrentState(root);
+    String dstBranchRevBefore = stateBeforeMerge.getBranchRevisions().get(mergeDestinationBranch);
+
+    myMergeSupport.merge(root, "2742914d19b2", mergeDestinationBranch, mergeCommitMessage, new MergeOptions());
+    RepositoryStateData stateAfterMerge = myVcs.getCollectChangesPolicy().getCurrentState(root);
+    String dstBranchRevAfter = stateAfterMerge.getBranchRevisions().get(mergeDestinationBranch);
+
+    assertFalse("Revision of the destination branch is not changed", dstBranchRevAfter.equals(dstBranchRevBefore));
+
+    List<ModificationData> changes = myVcs.getCollectChangesPolicy().collectChanges(root, stateBeforeMerge, stateAfterMerge, CheckoutRules.DEFAULT);
+    assertThat(changes, hasItem(modificationData().withDescription(mergeCommitMessage)));
+  }
+
+
+  public void should_return_unsuccesful_result_when_merge_fails(@NotNull HgVersion _) throws Exception {
+    VcsRoot root = vcsRoot().withUrl(myRemoteRepo).build();
+    MergeResult mergeResult = myMergeSupport.merge(root, "79d836707416", "default", "merge topic2 into default", new MergeOptions());
+    assertFalse(mergeResult.isSuccess());
+    assertThat(mergeResult.getConflicts(), hasItem("b/c"));
+  }
+
+
+  public void result_of_failed_merge_should_not_appear_in_remote_repository(@NotNull HgVersion _) throws Exception {
+    final String mergeDestinationBranch = "default";
+
+    VcsRoot root = vcsRoot().withUrl(myRemoteRepo).build();
+    RepositoryStateData stateBeforeMerge = myVcs.getCollectChangesPolicy().getCurrentState(root);
+    String dstBranchRevBefore = stateBeforeMerge.getBranchRevisions().get(mergeDestinationBranch);
+
+    myMergeSupport.merge(root, "79d836707416", mergeDestinationBranch, "merge topic2 into default", new MergeOptions());
+    RepositoryStateData stateAfterMerge = myVcs.getCollectChangesPolicy().getCurrentState(root);
+    String dstBranchRevAfter = stateAfterMerge.getBranchRevisions().get(mergeDestinationBranch);
+
+    assertTrue("Revision of the destination branch changed after failed merge", dstBranchRevAfter.equals(dstBranchRevBefore));
+  }
+
+
+  public void try_merge_returns_correct_result_for_every_task(@NotNull HgVersion _) throws Exception {
+    VcsRoot root = vcsRoot().withUrl(myRemoteRepo).build();
+
+    final MergeTask topic1ToDefault = new MergeTask("2742914d19b2", "09dd527b77ec");
+    final MergeTask topic2ToDefault = new MergeTask("79d836707416", "09dd527b77ec");
+    final MergeTask topic1ToTopic2 = new MergeTask("2742914d19b2", "79d836707416");
+    Map<MergeTask, MergeResult> results = myMergeSupport.tryMerge(root, asList(topic1ToDefault, topic2ToDefault, topic1ToTopic2), new MergeOptions());
+
+    assertTrue(results.get(topic1ToDefault).isSuccess());
+    assertFalse(results.get(topic2ToDefault).isSuccess());
+    assertThat(results.get(topic2ToDefault).getConflicts(), hasItem("b/c"));
+    assertTrue(results.get(topic1ToTopic2).isSuccess());
+  }
+
+
+  public void merge_into_same_branch(@NotNull HgVersion _) throws Exception {
+    VcsRoot root = vcsRoot().withUrl(myRemoteRepo).build();
+    MergeResult result = myMergeSupport.merge(root, "6370ce18689a", "default", "merge into same branch", new MergeOptions());
+    assertTrue(result.isSuccess());
+  }
+}
--- a/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/MirrorManagerTest.java	Tue Oct 01 12:51:13 2013 +0400
+++ b/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/MirrorManagerTest.java	Tue Oct 01 12:52:11 2013 +0400
@@ -1,10 +1,7 @@
 package jetbrains.buildServer.buildTriggers.vcs.mercurial;
 
-import jetbrains.buildServer.TempFiles;
 import jetbrains.buildServer.util.Hash;
-import junit.framework.TestCase;
 import org.jetbrains.annotations.NotNull;
-import org.testng.annotations.AfterMethod;
 import org.testng.annotations.BeforeMethod;
 import org.testng.annotations.Test;
 
@@ -12,20 +9,23 @@
 import java.io.IOException;
 import java.util.List;
 
+import static org.testng.AssertJUnit.assertEquals;
+import static org.testng.AssertJUnit.assertFalse;
+import static org.testng.AssertJUnit.assertTrue;
+
 /**
  * @author dmitry.neverov
  */
 @Test
-public class MirrorManagerTest extends TestCase {
+public class MirrorManagerTest extends BaseMercurialTestCase {
 
-  private TempFiles myTempFiles;
   private File myRootDir;
   private MirrorManagerImpl myManager;
 
 
   @BeforeMethod
   public void setUp() throws Exception {
-    myTempFiles = new TempFiles();
+    super.setUp();
     myRootDir = myTempFiles.createTempDir();
     myManager = new MirrorManagerImpl(new PluginConfig() {
       @NotNull
@@ -35,11 +35,6 @@
     });
   }
 
-  @AfterMethod
-  public void tearDown() {
-    myTempFiles.cleanup();
-  }
-
 
   public void manager_should_create_an_empty_map_file() {
     assertTrue(myRootDir.listFiles().length == 1);
--- a/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/ModificationDataMatcher.java	Tue Oct 01 12:51:13 2013 +0400
+++ b/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/ModificationDataMatcher.java	Tue Oct 01 12:52:11 2013 +0400
@@ -6,22 +6,31 @@
 import org.jetbrains.annotations.NotNull;
 
 import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
 
+import static java.util.Arrays.asList;
+
 public class ModificationDataMatcher extends TypeSafeMatcher<ModificationData> {
 
   private String myVersion;
+  private String myDescription;
   private Map<String, String> myVcsRootProperties = new HashMap<String, String>();
   private Map<String, String> myAttributes = new HashMap<String, String>();
+  private List<String> myParentRevisions;
 
   @Override
   public boolean matchesSafely(ModificationData m) {
     if (myVersion != null && !myVersion.equals(m.getDisplayVersion()))
       return false;
+    if (myDescription != null && !myDescription.equals(m.getDescription()))
+      return false;
     if (!myVcsRootProperties.isEmpty() && !myVcsRootProperties.equals(m.getVcsRootObject().getProperties()))
       return false;
     if (!myAttributes.isEmpty() && !containsAllAttributes(m.getAttributes()))
       return false;
+    if (myParentRevisions != null && !m.getParentRevisions().equals(myParentRevisions))
+      return false;
     return true;
   }
 
@@ -29,10 +38,14 @@
     description.appendText("modification");
     if (myVersion != null)
       description.appendText(" with version ").appendValue(myVersion);
+    if (myDescription != null)
+      description.appendText(" with description ").appendValue(myDescription);
     if (!myVcsRootProperties.isEmpty())
       description.appendText(" with vcs root properties ").appendValue(myVcsRootProperties);
     if (!myAttributes.isEmpty())
       description.appendText(" with attributes ").appendValue(myAttributes);
+    if (myParentRevisions != null)
+      description.appendText(" with parent revisions ").appendValue(myParentRevisions);
   }
 
   public static ModificationDataMatcher modificationData() {
@@ -44,6 +57,11 @@
     return this;
   }
 
+  public ModificationDataMatcher withDescription(@NotNull String description) {
+    myDescription = description;
+    return this;
+  }
+
   public ModificationDataMatcher withVcsRootProperties(@NotNull Map<String, String> properties) {
     myVcsRootProperties.putAll(properties);
     return this;
@@ -54,6 +72,11 @@
     return this;
   }
 
+  public ModificationDataMatcher withParentRevisions(String... parentRevisions) {
+    myParentRevisions = asList(parentRevisions);
+    return this;
+  }
+
   private boolean containsAllAttributes(@NotNull Map<String, String> attributes) {
     for (Map.Entry<String, String> e : myAttributes.entrySet()) {
       String expectedValue = e.getValue();
--- a/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/RevisionFormatTest.java	Tue Oct 01 12:51:13 2013 +0400
+++ b/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/RevisionFormatTest.java	Tue Oct 01 12:52:11 2013 +0400
@@ -1,14 +1,9 @@
 package jetbrains.buildServer.buildTriggers.vcs.mercurial;
 
-import com.intellij.openapi.diagnostic.Logger;
-import jetbrains.buildServer.TempFiles;
 import jetbrains.buildServer.buildTriggers.vcs.mercurial.command.AuthSettings;
 import jetbrains.buildServer.buildTriggers.vcs.mercurial.command.TestCommandSettingsFactory;
-import jetbrains.buildServer.log.Log4jFactory;
 import jetbrains.buildServer.vcs.*;
-import jetbrains.buildServer.vcs.patches.PatchTestCase;
 import org.jetbrains.annotations.NotNull;
-import org.testng.annotations.AfterMethod;
 import org.testng.annotations.BeforeMethod;
 import org.testng.annotations.Test;
 
@@ -24,20 +19,15 @@
 import static jetbrains.buildServer.buildTriggers.vcs.mercurial.VcsRootBuilder.vcsRoot;
 
 @Test
-public class RevisionFormatTest extends PatchTestCase {
+public class RevisionFormatTest extends BaseMercurialPatchTestCase {
 
-  static {
-    Logger.setFactory(new Log4jFactory());
-  }
-
-  private TempFiles myTempFiles;
   private MercurialVcsSupport myVcs;
   private VcsRoot myRoot;
   private File myRemoteRepoDir;
 
   @BeforeMethod
   public void setUp() throws Exception {
-    myTempFiles = new TempFiles();
+    super.setUp();
     ServerPluginConfig pluginConfig = new ServerPluginConfigBuilder()
             .cachesDir(myTempFiles.createTempDir())
             .build();
@@ -47,11 +37,6 @@
     myRoot = vcsRoot().withUrl(myRemoteRepoDir.getAbsolutePath()).build();
   }
 
-  @AfterMethod
-  public void tearDown() {
-    myTempFiles.cleanup();
-  }
-
 
   public void collect_changes_result_does_not_depend_on_revnums() throws VcsException {
     List<ModificationData> changesWithRevnums = myVcs.getCollectChangesPolicy().collectChanges(myRoot,
--- a/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/ServerPluginConfigBuilder.java	Tue Oct 01 12:51:13 2013 +0400
+++ b/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/ServerPluginConfigBuilder.java	Tue Oct 01 12:52:11 2013 +0400
@@ -15,8 +15,9 @@
   private String myHgPath;
   private File myCachesDir;
   private boolean myDontUseRevsets = false;
-  private boolean myDetectSubrepoChanges = false;
+  private boolean myDetectSubrepoChanges = true;
   private long myMirrorExpirationTimeout;
+  private boolean myTagsAsBranches = true;
 
   @NotNull
   public ServerPluginConfig build() {
@@ -53,6 +54,10 @@
         return true;
       }
 
+      public boolean useTagsAsBranches() {
+        return myTagsAsBranches;
+      }
+
       public int getMaxDagNodesCount() {
         return 0;
       }
@@ -114,4 +119,9 @@
     myMirrorExpirationTimeout = timeoutMillis;
     return this;
   }
+
+  public ServerPluginConfigBuilder withTagsAsBranches(boolean tagsAsBranches) {
+    myTagsAsBranches = tagsAsBranches;
+    return this;
+  }
 }
--- a/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/SubrepoChangesTest.java	Tue Oct 01 12:51:13 2013 +0400
+++ b/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/SubrepoChangesTest.java	Tue Oct 01 12:52:11 2013 +0400
@@ -1,20 +1,15 @@
 package jetbrains.buildServer.buildTriggers.vcs.mercurial;
 
-import com.intellij.openapi.diagnostic.Logger;
-import jetbrains.buildServer.TempFiles;
 import jetbrains.buildServer.buildTriggers.vcs.mercurial.command.AuthSettings;
 import jetbrains.buildServer.buildTriggers.vcs.mercurial.command.CatCommand;
 import jetbrains.buildServer.buildTriggers.vcs.mercurial.command.CommandSettingsFactory;
 import jetbrains.buildServer.buildTriggers.vcs.mercurial.command.TestCommandSettingsFactory;
-import jetbrains.buildServer.log.Log4jFactory;
 import jetbrains.buildServer.vcs.*;
 import org.jetbrains.annotations.NotNull;
-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.HashMap;
 import java.util.List;
 import java.util.Map;
@@ -30,22 +25,18 @@
 
 @RequiredHgVersion(min = "1.7.0") //support subrepos only for hg with revsets
 @Test(dataProviderClass = HgVersionConstraint.class, dataProvider = "installedHgVersion")
-public class SubrepoChangesTest {
+public class SubrepoChangesTest extends BaseMercurialTestCase {
 
-  static {
-    Logger.setFactory(new Log4jFactory());
-  }
-
-  private TempFiles myTempFiles;
   private MercurialVcsSupport myVcs;
   private File myRemoteRepo1;
   private File myRemoteRepo2;
   private File myRemoteRepo3;
   private File myRepoNoSubs;
+  private HgPathProvider myHgPathProvider;
 
   @BeforeMethod
-  public void setUp() throws IOException {
-    myTempFiles = new TempFiles();
+  public void setUp() throws Exception {
+    super.setUp();
     ServerPluginConfig pluginConfig = new ServerPluginConfigBuilder()
             .cachesDir(myTempFiles.createTempDir())
             .detectSubrepoChanges(true)
@@ -59,12 +50,9 @@
     copyRepository(new File("mercurial-tests/testData/subrepos/r2"), myRemoteRepo2);
     copyRepository(new File("mercurial-tests/testData/subrepos/r3"), myRemoteRepo3);
     copyRepository(new File("mercurial-tests/testData/rep1"), myRepoNoSubs);
-    myVcs = mercurialSupport().withConfig(pluginConfig).build();
-  }
-
-  @AfterMethod
-  public void tearDown() {
-    myTempFiles.cleanup();
+    MercurialSupportBuilder mercurialBuilder = mercurialSupport().withConfig(pluginConfig);
+    myVcs = mercurialBuilder.build();
+    myHgPathProvider = mercurialBuilder.getHgPathProvider();
   }
 
 
@@ -226,7 +214,7 @@
             .build();
 
     final AtomicInteger catCallCounter = new AtomicInteger(0);
-    RepoFactory repoFactory = new RepoFactory(pluginConfig, new TestCommandSettingsFactory()) {
+    RepoFactory repoFactory = new RepoFactory(pluginConfig, new TestCommandSettingsFactory(), myHgPathProvider) {
       @NotNull
       @Override
       public ServerHgRepo create(@NotNull File workingDir, @NotNull String hgPath, @NotNull AuthSettings authSettings) throws VcsException {
--- a/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/SubrepoPatchTest.java	Tue Oct 01 12:51:13 2013 +0400
+++ b/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/SubrepoPatchTest.java	Tue Oct 01 12:52:11 2013 +0400
@@ -1,10 +1,7 @@
 package jetbrains.buildServer.buildTriggers.vcs.mercurial;
 
-import com.intellij.openapi.diagnostic.Logger;
-import jetbrains.buildServer.log.Log4jFactory;
 import jetbrains.buildServer.vcs.CheckoutRules;
 import jetbrains.buildServer.vcs.VcsRoot;
-import jetbrains.buildServer.vcs.impl.VcsRootImpl;
 import org.testng.annotations.BeforeMethod;
 import org.testng.annotations.Test;
 
@@ -17,11 +14,7 @@
 import static jetbrains.buildServer.buildTriggers.vcs.mercurial.VcsRootBuilder.vcsRoot;
 
 @Test
-public class SubrepoPatchTest extends BaseMercurialTestCase {
-
-  static {
-    Logger.setFactory(new Log4jFactory());
-  }
+public class SubrepoPatchTest extends BaseMercurialPatchTestCase {
 
   private MercurialVcsSupport myVcs;
   private File myRemoteRepo1;
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/TagsTest.java	Tue Oct 01 12:52:11 2013 +0400
@@ -0,0 +1,74 @@
+package jetbrains.buildServer.buildTriggers.vcs.mercurial;
+
+import jetbrains.buildServer.vcs.RepositoryStateData;
+import jetbrains.buildServer.vcs.VcsRoot;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import java.io.File;
+import java.io.IOException;
+
+import static jetbrains.buildServer.buildTriggers.vcs.mercurial.BookmarksTest.RepositoryStateDataMatcher.hasBranch;
+import static jetbrains.buildServer.buildTriggers.vcs.mercurial.BookmarksTest.RepositoryStateDataMatcher.hasNoBranch;
+import static jetbrains.buildServer.buildTriggers.vcs.mercurial.MercurialSupportBuilder.mercurialSupport;
+import static jetbrains.buildServer.buildTriggers.vcs.mercurial.ServerPluginConfigBuilder.serverPluginConfig;
+import static jetbrains.buildServer.buildTriggers.vcs.mercurial.VcsRootBuilder.vcsRoot;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.not;
+
+@RequiredHgVersion(min = "1.7.0")
+@Test(dataProviderClass = HgVersionConstraint.class, dataProvider = "installedHgVersion")
+public class TagsTest extends BaseMercurialTestCase {
+
+  private File myRemoteRepository;
+  private ServerPluginConfigBuilder myConfig;
+
+  @BeforeMethod
+  public void setUp() throws Exception {
+    super.setUp();
+    myConfig = serverPluginConfig()
+            .cachesDir(myTempFiles.createTempDir())
+            .hgPath(Util.getHgPath());
+
+    myRemoteRepository = myTempFiles.createTempDir();
+    Util.copyRepository(new File("mercurial-tests/testData/tags"), myRemoteRepository);
+  }
+
+  public void no_tags_reported_by_default(HgVersion _) throws Exception {
+    VcsRoot root = vcsRoot().withUrl(myRemoteRepository.getAbsolutePath()).build();
+    RepositoryStateData state = getVcs().getCollectChangesPolicy().getCurrentState(root);
+    assertThat(state, not(hasBranch("v1").withRevision("fa7ad5b80a88")));
+    assertThat(state, not(hasBranch("v4").withRevision("f7fbcc489e40")));
+  }
+
+  public void should_report_tag_revisions(HgVersion _) throws Exception {
+    VcsRoot root = vcsRoot().withUrl(myRemoteRepository.getAbsolutePath()).withTagsEnabled(true).build();
+    RepositoryStateData state = getVcs().getCollectChangesPolicy().getCurrentState(root);
+    assertThat(state, hasBranch("v1").withRevision("fa7ad5b80a88"));
+    assertThat(state, hasBranch("v4").withRevision("f7fbcc489e40"));
+  }
+
+  public void branch_has_higher_precedence_over_tag(HgVersion _) throws Exception {
+    VcsRoot root = vcsRoot().withUrl(myRemoteRepository.getAbsolutePath()).withTagsEnabled(true).build();
+    RepositoryStateData state = getVcs().getCollectChangesPolicy().getCurrentState(root);
+    assertThat(state, hasBranch("topic").withRevision("efde33cd0b66"));
+  }
+
+  public void tags_can_be_turned_off_globally(HgVersion _) throws Exception {
+    myConfig.withTagsAsBranches(false);
+    VcsRoot root = vcsRoot().withUrl(myRemoteRepository.getAbsolutePath()).withTagsEnabled(true).build();
+    RepositoryStateData state = getVcs().getCollectChangesPolicy().getCurrentState(root);
+    assertThat(state, not(hasBranch("v1").withRevision("fa7ad5b80a88")));
+    assertThat(state, not(hasBranch("v4").withRevision("f7fbcc489e40")));
+  }
+
+  public void tags_should_not_include_tip(HgVersion _) throws Exception {
+    VcsRoot root = vcsRoot().withUrl(myRemoteRepository.getAbsolutePath()).withTagsEnabled(true).build();
+    RepositoryStateData state = getVcs().getCollectChangesPolicy().getCurrentState(root);
+    assertThat(state, hasNoBranch("tip"));
+  }
+
+  private MercurialVcsSupport getVcs() throws IOException {
+    return mercurialSupport().withConfig(myConfig.build()).build();
+  }
+}
--- a/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/UnrelatedResitoriesTest.java	Tue Oct 01 12:51:13 2013 +0400
+++ b/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/UnrelatedResitoriesTest.java	Tue Oct 01 12:52:11 2013 +0400
@@ -1,14 +1,10 @@
 package jetbrains.buildServer.buildTriggers.vcs.mercurial;
 
-import com.intellij.openapi.diagnostic.Logger;
-import jetbrains.buildServer.TempFiles;
-import jetbrains.buildServer.log.Log4jFactory;
 import jetbrains.buildServer.util.FileUtil;
 import jetbrains.buildServer.vcs.CheckoutRules;
 import jetbrains.buildServer.vcs.VcsException;
 import jetbrains.buildServer.vcs.impl.VcsRootImpl;
 import org.jetbrains.annotations.NotNull;
-import org.testng.annotations.AfterMethod;
 import org.testng.annotations.BeforeMethod;
 import org.testng.annotations.Test;
 
@@ -23,22 +19,17 @@
  * @author dmitry.neverov
  */
 @Test
-public class UnrelatedResitoriesTest {
+public class UnrelatedResitoriesTest extends BaseMercurialTestCase {
 
   private final static String CURRENT_VERSION_OF_NEW_REPO = "4780519e01aa";
   private MercurialVcsSupport myVcs;
-  private TempFiles myTempFiles;
   private File myRepositoryLocation;
   private VcsRootImpl myRoot;
   private ServerPluginConfig myPluginConfig;
 
-  static {
-    Logger.setFactory(new Log4jFactory());
-  }
-
   @BeforeMethod
   public void setUp() throws Exception {
-    myTempFiles = new TempFiles();
+    super.setUp();
     myPluginConfig = new ServerPluginConfigBuilder()
             .cachesDir(myTempFiles.createTempDir())
             .build();
@@ -48,11 +39,6 @@
     myRoot = new VcsRootBuilder().withUrl(myRepositoryLocation.getCanonicalPath()).build();
   }
 
-  @AfterMethod
-  public void tearDown() {
-    myTempFiles.cleanup();
-  }
-
 
   public void should_be_able_to_sync_when_repository_became_unrelated() throws Exception {
     myVcs = createVcs();
--- a/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/VcsRootBuilder.java	Tue Oct 01 12:51:13 2013 +0400
+++ b/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/VcsRootBuilder.java	Tue Oct 01 12:52:11 2013 +0400
@@ -1,13 +1,14 @@
 package jetbrains.buildServer.buildTriggers.vcs.mercurial;
 
-import java.io.File;
-import java.io.IOException;
 import jetbrains.buildServer.vcs.SVcsRoot;
 import jetbrains.buildServer.vcs.impl.VcsRootImpl;
 import org.jetbrains.annotations.NotNull;
 import org.jmock.Expectations;
 import org.jmock.Mockery;
 
+import java.io.File;
+import java.io.IOException;
+
 /**
  * @author dmitry.neverov
  */
@@ -23,6 +24,7 @@
   private boolean myUncompressed = true;
   private File myCloneRepositoryTo;
   private boolean myDetectSubrepoChanges = false;
+  private boolean myTagsAsBranches = false;
 
   public static VcsRootBuilder vcsRoot() {
     return new VcsRootBuilder();
@@ -40,6 +42,7 @@
     vcsRoot.addProperty(Constants.DETECT_SUBREPO_CHANGES, String.valueOf(myDetectSubrepoChanges));
     if (myCloneRepositoryTo != null)
       vcsRoot.addProperty(Constants.SERVER_CLONE_PATH_PROP, String.valueOf(myCloneRepositoryTo.getAbsolutePath()));
+    vcsRoot.addProperty(Constants.USE_TAGS_AS_BRANCHES, String.valueOf(myTagsAsBranches));
     return vcsRoot;
   }
 
@@ -58,6 +61,7 @@
       allowing(root).getProperty(with(Constants.UNCOMPRESSED_TRANSFER)); will(returnValue(null));
       allowing(root).getProperty(with(Constants.USER_FOR_TAG)); will(returnValue(myUserForTag));
       allowing(root).getProperty(with(Constants.DETECT_SUBREPO_CHANGES)); will(returnValue(String.valueOf(myDetectSubrepoChanges)));
+      allowing(root).getProperty(with(Constants.USE_TAGS_AS_BRANCHES)); will(returnValue(String.valueOf(myTagsAsBranches)));
     }});
     if (myCloneRepositoryTo != null) {
       context.checking(new Expectations() {{
@@ -74,6 +78,12 @@
   }
 
 
+  public VcsRootBuilder withUrl(@NotNull File repository) {
+    myRepository = repository.getAbsolutePath();
+    return this;
+  }
+
+
   public VcsRootBuilder withUserName(@NotNull String username) {
     myUsername = username;
     return this;
@@ -120,4 +130,10 @@
     myDetectSubrepoChanges = detectSubrepoChanges;
     return this;
   }
+
+
+  public VcsRootBuilder withTagsEnabled(boolean useTagsAsBranches) {
+    myTagsAsBranches = useTagsAsBranches;
+    return this;
+  }
 }
--- a/mercurial-tests/src/testng.xml	Tue Oct 01 12:51:13 2013 +0400
+++ b/mercurial-tests/src/testng.xml	Tue Oct 01 12:52:11 2013 +0400
@@ -32,6 +32,10 @@
       <class name="jetbrains.buildServer.buildTriggers.vcs.mercurial.MercurialXmlLogParserTest"/>
       <class name="jetbrains.buildServer.buildTriggers.vcs.mercurial.HgVcsRootFactoryTest"/>
       <class name="jetbrains.buildServer.buildTriggers.vcs.mercurial.SubRepoTest"/>
+      <class name="jetbrains.buildServer.buildTriggers.vcs.mercurial.MergeSupportTest"/>
+      <class name="jetbrains.buildServer.buildTriggers.vcs.mercurial.TagsTest"/>
+      <class name="jetbrains.buildServer.buildTriggers.vcs.mercurial.CommitsInfoBuilderSupportTest"/>
+      <class name="jetbrains.buildServer.buildTriggers.vcs.mercurial.MercurialModificationInfoBuilderTest"/>
     </classes>
   </test>
 </suite>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial-tests/testData/merge/README	Tue Oct 01 12:52:11 2013 +0400
@@ -0,0 +1,45 @@
+no.subrepos
+
+o    4:79d836707416 topic2
+|
+| o  3:2742914d19b2 topic1
+|/
+| o  2:09dd527b77ec
+|/
+o    1:6370ce18689a
+|
+o    0:7e3988f0f412
+
+
+
+subrepo1
+
+o      6:eb211547efbe         ~> subrepo2 675fa105b184
+|
+| o    5:a3db56ba38d3 topic3  ~> subrepo2 085a6376bbce
+| |
+| | o  4:3e43f76179ed topic2  ~> subrepo2 085a6376bbce
+| |/
+| | o  3:0bd1ae88632d topic1  ~> subrepo2 1532dee5d922
+| |/
+o |    2:111ee5cb00c8         ~> subrepo2 627ca21f914a
+|/
+o      1:a4bb39f3d42b         ~> subrepo2 627ca21f914a
+|
+o      0:f43f3725e922
+
+
+subrepo2
+
+o    4:085a6376bbce topic2
+|
+| o  3:1532dee5d922 topic1
+|/
+| o  2:675fa105b184
+|/
+o    1:627ca21f914a
+|
+o    0:5a88c102487e
+
+
+
Binary file mercurial-tests/testData/merge/no.subrepos/hg/00changelog.i has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial-tests/testData/merge/no.subrepos/hg/branch	Tue Oct 01 12:52:11 2013 +0400
@@ -0,0 +1,1 @@
+topic2
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial-tests/testData/merge/no.subrepos/hg/cache/branchheads-served	Tue Oct 01 12:52:11 2013 +0400
@@ -0,0 +1,4 @@
+79d8367074167bba5a1820ad757f4052b5777d20 4
+09dd527b77ec6e1ab9396bc16eb3b5cd09c55d11 default
+2742914d19b233227f753c51110959b407eeff6e topic1
+79d8367074167bba5a1820ad757f4052b5777d20 topic2
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial-tests/testData/merge/no.subrepos/hg/cache/tags	Tue Oct 01 12:52:11 2013 +0400
@@ -0,0 +1,3 @@
+3 2742914d19b233227f753c51110959b407eeff6e
+2 09dd527b77ec6e1ab9396bc16eb3b5cd09c55d11
+
Binary file mercurial-tests/testData/merge/no.subrepos/hg/dirstate has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial-tests/testData/merge/no.subrepos/hg/last-message.txt	Tue Oct 01 12:52:11 2013 +0400
@@ -0,0 +1,1 @@
+create b/c conflicting with default
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial-tests/testData/merge/no.subrepos/hg/requires	Tue Oct 01 12:52:11 2013 +0400
@@ -0,0 +1,4 @@
+dotencode
+fncache
+revlogv1
+store
Binary file mercurial-tests/testData/merge/no.subrepos/hg/store/00changelog.i has changed
Binary file mercurial-tests/testData/merge/no.subrepos/hg/store/00manifest.i has changed
Binary file mercurial-tests/testData/merge/no.subrepos/hg/store/data/a.i has changed
Binary file mercurial-tests/testData/merge/no.subrepos/hg/store/data/b/c.i has changed
Binary file mercurial-tests/testData/merge/no.subrepos/hg/store/data/b/d.i has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial-tests/testData/merge/no.subrepos/hg/store/fncache	Tue Oct 01 12:52:11 2013 +0400
@@ -0,0 +1,3 @@
+data/b/c.i
+data/b/d.i
+data/a.i
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial-tests/testData/merge/no.subrepos/hg/store/phaseroots	Tue Oct 01 12:52:11 2013 +0400
@@ -0,0 +1,1 @@
+1 7e3988f0f412b846df355cb77cf5b863dd42fb5f
Binary file mercurial-tests/testData/merge/no.subrepos/hg/store/undo has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial-tests/testData/merge/no.subrepos/hg/store/undo.phaseroots	Tue Oct 01 12:52:11 2013 +0400
@@ -0,0 +1,1 @@
+1 7e3988f0f412b846df355cb77cf5b863dd42fb5f
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial-tests/testData/merge/no.subrepos/hg/undo.branch	Tue Oct 01 12:52:11 2013 +0400
@@ -0,0 +1,1 @@
+topic2
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial-tests/testData/merge/no.subrepos/hg/undo.desc	Tue Oct 01 12:52:11 2013 +0400
@@ -0,0 +1,2 @@
+4
+commit
Binary file mercurial-tests/testData/merge/no.subrepos/hg/undo.dirstate has changed
Binary file mercurial-tests/testData/merge/subrepos/subrepo1/hg/00changelog.i has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial-tests/testData/merge/subrepos/subrepo1/hg/branch	Tue Oct 01 12:52:11 2013 +0400
@@ -0,0 +1,1 @@
+default
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial-tests/testData/merge/subrepos/subrepo1/hg/cache/branchheads-served	Tue Oct 01 12:52:11 2013 +0400
@@ -0,0 +1,5 @@
+eb211547efbe93ab842d97263d645fcf77c01b8e 6
+eb211547efbe93ab842d97263d645fcf77c01b8e default
+0bd1ae88632d2694bdb227b76c4e7f7346bd1718 topic1
+3e43f76179edacde228056f906d16bdbd8b3ba30 topic2
+a3db56ba38d345e814abca5a0555243b4db809fd topic3
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial-tests/testData/merge/subrepos/subrepo1/hg/cache/tags	Tue Oct 01 12:52:11 2013 +0400
@@ -0,0 +1,5 @@
+5 a3db56ba38d345e814abca5a0555243b4db809fd
+4 3e43f76179edacde228056f906d16bdbd8b3ba30
+3 0bd1ae88632d2694bdb227b76c4e7f7346bd1718
+2 111ee5cb00c83258c9fd13e402e20741bb575510
+
Binary file mercurial-tests/testData/merge/subrepos/subrepo1/hg/dirstate has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial-tests/testData/merge/subrepos/subrepo1/hg/last-message.txt	Tue Oct 01 12:52:11 2013 +0400
@@ -0,0 +1,1 @@
+track last subrepo1 commit in default
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial-tests/testData/merge/subrepos/subrepo1/hg/requires	Tue Oct 01 12:52:11 2013 +0400
@@ -0,0 +1,4 @@
+dotencode
+fncache
+revlogv1
+store
Binary file mercurial-tests/testData/merge/subrepos/subrepo1/hg/store/00changelog.i has changed
Binary file mercurial-tests/testData/merge/subrepos/subrepo1/hg/store/00manifest.i has changed
Binary file mercurial-tests/testData/merge/subrepos/subrepo1/hg/store/data/a.i has changed
Binary file mercurial-tests/testData/merge/subrepos/subrepo1/hg/store/data/b/c.i has changed
Binary file mercurial-tests/testData/merge/subrepos/subrepo1/hg/store/data/b/d.i has changed
Binary file mercurial-tests/testData/merge/subrepos/subrepo1/hg/store/data/~2ehgsub.i has changed
Binary file mercurial-tests/testData/merge/subrepos/subrepo1/hg/store/data/~2ehgsubstate.i has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial-tests/testData/merge/subrepos/subrepo1/hg/store/fncache	Tue Oct 01 12:52:11 2013 +0400
@@ -0,0 +1,5 @@
+data/b/c.i
+data/.hgsubstate.i
+data/b/d.i
+data/.hgsub.i
+data/a.i
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial-tests/testData/merge/subrepos/subrepo1/hg/store/phaseroots	Tue Oct 01 12:52:11 2013 +0400
@@ -0,0 +1,1 @@
+1 f43f3725e92209f5e82118524ac8075a62326151
Binary file mercurial-tests/testData/merge/subrepos/subrepo1/hg/store/undo has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial-tests/testData/merge/subrepos/subrepo1/hg/store/undo.phaseroots	Tue Oct 01 12:52:11 2013 +0400
@@ -0,0 +1,1 @@
+1 f43f3725e92209f5e82118524ac8075a62326151
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial-tests/testData/merge/subrepos/subrepo1/hg/undo.branch	Tue Oct 01 12:52:11 2013 +0400
@@ -0,0 +1,1 @@
+default
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial-tests/testData/merge/subrepos/subrepo1/hg/undo.desc	Tue Oct 01 12:52:11 2013 +0400
@@ -0,0 +1,2 @@
+6
+commit
Binary file mercurial-tests/testData/merge/subrepos/subrepo1/hg/undo.dirstate has changed
Binary file mercurial-tests/testData/merge/subrepos/subrepo2/hg/00changelog.i has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial-tests/testData/merge/subrepos/subrepo2/hg/branch	Tue Oct 01 12:52:11 2013 +0400
@@ -0,0 +1,1 @@
+topic2
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial-tests/testData/merge/subrepos/subrepo2/hg/cache/branchheads-served	Tue Oct 01 12:52:11 2013 +0400
@@ -0,0 +1,4 @@
+085a6376bbceaefcdaa2c9c7a8ae3ea9ec91d2c3 4
+675fa105b184f695b11cbf31d08cda09f1114cc2 default
+1532dee5d922539bb47ae13c3d1eadd126dad426 topic1
+085a6376bbceaefcdaa2c9c7a8ae3ea9ec91d2c3 topic2
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial-tests/testData/merge/subrepos/subrepo2/hg/cache/tags	Tue Oct 01 12:52:11 2013 +0400
@@ -0,0 +1,4 @@
+4 085a6376bbceaefcdaa2c9c7a8ae3ea9ec91d2c3
+3 1532dee5d922539bb47ae13c3d1eadd126dad426
+2 675fa105b184f695b11cbf31d08cda09f1114cc2
+
Binary file mercurial-tests/testData/merge/subrepos/subrepo2/hg/dirstate has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial-tests/testData/merge/subrepos/subrepo2/hg/last-message.txt	Tue Oct 01 12:52:11 2013 +0400
@@ -0,0 +1,1 @@
+create b/c conflicting with default
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial-tests/testData/merge/subrepos/subrepo2/hg/requires	Tue Oct 01 12:52:11 2013 +0400
@@ -0,0 +1,4 @@
+dotencode
+fncache
+revlogv1
+store
Binary file mercurial-tests/testData/merge/subrepos/subrepo2/hg/store/00changelog.i has changed
Binary file mercurial-tests/testData/merge/subrepos/subrepo2/hg/store/00manifest.i has changed
Binary file mercurial-tests/testData/merge/subrepos/subrepo2/hg/store/data/a.i has changed
Binary file mercurial-tests/testData/merge/subrepos/subrepo2/hg/store/data/b/c.i has changed
Binary file mercurial-tests/testData/merge/subrepos/subrepo2/hg/store/data/b/d.i has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial-tests/testData/merge/subrepos/subrepo2/hg/store/fncache	Tue Oct 01 12:52:11 2013 +0400
@@ -0,0 +1,3 @@
+data/b/c.i
+data/b/d.i
+data/a.i
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial-tests/testData/merge/subrepos/subrepo2/hg/store/phaseroots	Tue Oct 01 12:52:11 2013 +0400
@@ -0,0 +1,1 @@
+1 5a88c102487e07f4413e3574357303a38e483ac0
Binary file mercurial-tests/testData/merge/subrepos/subrepo2/hg/store/undo has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial-tests/testData/merge/subrepos/subrepo2/hg/store/undo.phaseroots	Tue Oct 01 12:52:11 2013 +0400
@@ -0,0 +1,1 @@
+1 5a88c102487e07f4413e3574357303a38e483ac0
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial-tests/testData/merge/subrepos/subrepo2/hg/undo.branch	Tue Oct 01 12:52:11 2013 +0400
@@ -0,0 +1,1 @@
+topic2
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial-tests/testData/merge/subrepos/subrepo2/hg/undo.desc	Tue Oct 01 12:52:11 2013 +0400
@@ -0,0 +1,2 @@
+4
+commit
Binary file mercurial-tests/testData/merge/subrepos/subrepo2/hg/undo.dirstate has changed
Binary file mercurial-tests/testData/rep2/hg/store/00changelog.i has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial-tests/testData/rep2/hg/store/phaseroots	Tue Oct 01 12:52:11 2013 +0400
@@ -0,0 +1,1 @@
+1 ef4a538a17cf3e72eeffcc59140805a69e8c9b03
Binary file mercurial-tests/testData/rep2/hg/store/undo has changed
--- a/mercurial-tests/testData/rep2/hg/undo.branch	Tue Oct 01 12:51:13 2013 +0400
+++ b/mercurial-tests/testData/rep2/hg/undo.branch	Tue Oct 01 12:52:11 2013 +0400
@@ -1,1 +1,1 @@
-default
\ No newline at end of file
+<TEST> Branch with exitics)(*&^%$#@!
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial-tests/testData/rep2/hg/undo.desc	Tue Oct 01 12:52:11 2013 +0400
@@ -0,0 +1,2 @@
+32
+commit
Binary file mercurial-tests/testData/rep2/hg/undo.dirstate has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial-tests/testData/tags/README	Tue Oct 01 12:52:11 2013 +0400
@@ -0,0 +1,19 @@
+    o  9:72902de1ba45 +tag topic -> 3256c19d708b
+    |
+o   |  8:efde33cd0b66 (topic) +tag v1 -> fa7ad5b80a88
+|   |
+| o |  7:210d711041da (topic2) +tag v1 -> b86ee8b83781
+| | |
+| o |  6:b86ee8b83781 (topic2)
+| | |
+| | o  5:3256c19d708b +tag v4 -> f7fbcc489e40
+| | |
+| | o  4:f7fbcc489e40
+| |/
+o |    3:fa7ad5b80a88 (topic) +tag v1 -> 6c3929c66c32
+| |
+o |    2:6c3929c66c32 (topic)
+|/
+o      1:b6e596caff9d +tag v1 -> 1ecfad722425
+|
+o      0:1ecfad722425
Binary file mercurial-tests/testData/tags/hg/00changelog.i has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial-tests/testData/tags/hg/branch	Tue Oct 01 12:52:11 2013 +0400
@@ -0,0 +1,1 @@
+default
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial-tests/testData/tags/hg/cache/branchheads-served	Tue Oct 01 12:52:11 2013 +0400
@@ -0,0 +1,4 @@
+72902de1ba4503a2acabdac782dd1baedc56c590 9
+72902de1ba4503a2acabdac782dd1baedc56c590 default
+efde33cd0b66ae0fb22031ae49890ed8291d54b0 topic
+210d711041da69982df2472bdbae12d3da50b7eb topic2
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial-tests/testData/tags/hg/cache/tags	Tue Oct 01 12:52:11 2013 +0400
@@ -0,0 +1,11 @@
+9 72902de1ba4503a2acabdac782dd1baedc56c590 e2013c4534f7af87eba501437ab756184ce06738
+8 efde33cd0b66ae0fb22031ae49890ed8291d54b0 16b0ff5750dd8451acd25496c85e8ee8827f02c8
+7 210d711041da69982df2472bdbae12d3da50b7eb 9b2a39cbdbc150021d3769e20b8f75affa52af84
+
+3256c19d708b4efae92b281b386876eab63d4b7e topic
+1ecfad72242555475feeefe00643a834cae04b15 v1
+1ecfad72242555475feeefe00643a834cae04b15 v1
+6c3929c66c328e85039f7588fc9344f93e0bd04b v1
+b86ee8b83781935bca1c5ee4ffe7f3b6f12923f2 v1
+fa7ad5b80a88c64f8f32d1b7a0cc96fb2a5092ae v1
+f7fbcc489e403a950ddba6e4a7b1674b60d66c65 v4
Binary file mercurial-tests/testData/tags/hg/dirstate has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial-tests/testData/tags/hg/last-message.txt	Tue Oct 01 12:52:11 2013 +0400
@@ -0,0 +1,1 @@
+Added tag topic for changeset 3256c19d708b
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial-tests/testData/tags/hg/requires	Tue Oct 01 12:52:11 2013 +0400
@@ -0,0 +1,4 @@
+dotencode
+fncache
+revlogv1
+store
Binary file mercurial-tests/testData/tags/hg/store/00changelog.i has changed
Binary file mercurial-tests/testData/tags/hg/store/00manifest.i has changed
Binary file mercurial-tests/testData/tags/hg/store/data/a.i has changed
Binary file mercurial-tests/testData/tags/hg/store/data/~2ehgtags.i has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial-tests/testData/tags/hg/store/fncache	Tue Oct 01 12:52:11 2013 +0400
@@ -0,0 +1,2 @@
+data/.hgtags.i
+data/a.i
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial-tests/testData/tags/hg/store/phaseroots	Tue Oct 01 12:52:11 2013 +0400
@@ -0,0 +1,1 @@
+1 1ecfad72242555475feeefe00643a834cae04b15
Binary file mercurial-tests/testData/tags/hg/store/undo has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial-tests/testData/tags/hg/store/undo.phaseroots	Tue Oct 01 12:52:11 2013 +0400
@@ -0,0 +1,1 @@
+1 1ecfad72242555475feeefe00643a834cae04b15
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial-tests/testData/tags/hg/undo.branch	Tue Oct 01 12:52:11 2013 +0400
@@ -0,0 +1,1 @@
+default
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial-tests/testData/tags/hg/undo.desc	Tue Oct 01 12:52:11 2013 +0400
@@ -0,0 +1,2 @@
+9
+commit
Binary file mercurial-tests/testData/tags/hg/undo.dirstate has changed