view mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/MercurialVcsSupportTest.java @ 980:1168c4c64d49

Merge branch Indore-2017.2.x
author Dmitry Neverov <dmitry.neverov@gmail.com>
date Wed, 24 Jan 2018 17:38:56 +0100
parents 7bf4d943d5bb 38e96101a6ef
children e78734c8fa95
line wrap: on
line source
/*
 * Copyright 2000-2018 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 com.intellij.openapi.util.SystemInfo;
import jetbrains.buildServer.buildTriggers.vcs.mercurial.command.*;
import jetbrains.buildServer.util.FileUtil;
import jetbrains.buildServer.util.TestFor;
import jetbrains.buildServer.vcs.*;
import jetbrains.buildServer.vcs.impl.VcsRootImpl;
import junit.framework.Assert;
import org.jetbrains.annotations.NotNull;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FilenameFilter;
import java.io.IOException;
import java.util.*;

import static com.intellij.openapi.util.io.FileUtil.delete;
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.*;
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.hamcrest.Matchers.not;

@Test
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 {
    super.setUp();
    myPluginConfig = new ServerPluginConfigBuilder()
            .cachesDir(myTempFiles.createTempDir())
            .build();
    MercurialSupportBuilder mercurialBuilder = mercurialSupport().withConfig(myPluginConfig);
    myVcs = mercurialBuilder.build();
    myHgPathProvider = mercurialBuilder.getHgPathProvider();
  }

  protected String getTestDataPath() {
    return "mercurial-tests/testData";
  }

  public void test_get_current_version() throws Exception {
    VcsRootImpl vcsRoot = createVcsRoot(simpleRepo());

    RepositoryStateData state = myVcs.getCollectChangesPolicy().getCurrentState(vcsRoot);
    assertEquals(state.getBranchRevisions().get(state.getDefaultBranchName()), "9c6a6b4aede0");
    assertEquals("9c6a6b4aede0", myVcs.getVersionDisplayName("10:9c6a6b4aede0", vcsRoot));

    state = myVcs.getCollectChangesPolicy().getCurrentState(createVcsRoot(simpleRepo(), "test_branch"));
    assertEquals(state.getBranchRevisions().get(state.getDefaultBranchName()), "04c3ae4c6312");

    state = myVcs.getCollectChangesPolicy().getCurrentState(createVcsRoot(simpleRepo(), "name with space"));
    assertEquals(state.getBranchRevisions().get(state.getDefaultBranchName()), "9babcf2d5705");
  }

  private List<ModificationData> collectChanges(@NotNull VcsRoot vcsRoot, @NotNull String from, @NotNull String to, @NotNull CheckoutRules rules) throws VcsException {
    return myVcs.getCollectChangesPolicy().collectChanges(vcsRoot, from, to, rules);
  }

  public void test_collect_changes_between_two_same_roots() throws Exception {
    VcsRootImpl vcsRoot = createVcsRoot(simpleRepo());
    VcsRootImpl sameVcsRoot = createVcsRoot(simpleRepo());
    List<ModificationData> changes = myVcs.getCollectChangesPolicy().collectChanges(vcsRoot, "0:9875b412a788", sameVcsRoot, "3:9522278aa38d", new CheckoutRules(""));
    do_check_for_collect_changes(changes);
  }

  public void test_collect_changes_from_non_existing_revision() throws Exception {
    VcsRootImpl vcsRoot = createVcsRoot(simpleRepo());
    VcsRootImpl sameVcsRoot = createVcsRoot(simpleRepo());
    List<ModificationData> changes = myVcs.getCollectChangesPolicy().collectChanges(vcsRoot, "0:9875b412a789", sameVcsRoot, "3:9522278aa38d", new CheckoutRules(""));
    assertFalse(changes.isEmpty());//should return some changes from the toRoot
  }

  public void test_collect_changes() throws Exception {
    VcsRootImpl vcsRoot = createVcsRoot(simpleRepo());
    List<ModificationData> changes = collectChanges(vcsRoot, "0:9875b412a788", "3:9522278aa38d", new CheckoutRules(""));
    do_check_for_collect_changes(changes);
  }

  private void do_check_for_collect_changes(List<ModificationData> changes) throws Exception {
    assertEquals(3, changes.size());

    ModificationData md1 = changes.get(0);
    ModificationData md2 = changes.get(1);
    ModificationData md3 = changes.get(2);
    assertEquals(md1.getVersion(), "1d446e82d356");
    assertEquals(md1.getDescription(), "new file added");
    List<VcsChange> files1 = md1.getChanges();
    assertEquals(1, files1.size());
    assertEquals(VcsChangeInfo.Type.ADDED, files1.get(0).getType());
    assertEquals(normalizePath(files1.get(0).getRelativeFileName()), "dir1/file3.txt");

    assertEquals(md2.getVersion(), "7209b1f1d793");
    assertEquals(md2.getDescription(), "file4.txt added");
    List<VcsChange> files2 = md2.getChanges();
    assertEquals(1, files2.size());
    assertEquals(files2.get(0).getType(), VcsChangeInfo.Type.ADDED);
    assertEquals(normalizePath(files2.get(0).getRelativeFileName()), "dir1/file4.txt");

    assertEquals(md3.getVersion(), "9522278aa38d");
    assertEquals(md3.getDescription(), "file removed");
    List<VcsChange> files3 = md3.getChanges();
    assertEquals(1, files3.size());
    assertEquals(files3.get(0).getType(), VcsChangeInfo.Type.REMOVED);
    assertEquals(normalizePath(files3.get(0).getRelativeFileName()), "dir1/file4.txt");
  }

  @Test
  public void test_build_patch() throws IOException, VcsException {
    setName("cleanPatch1");
    VcsRootImpl vcsRoot = createVcsRoot(simpleRepo());

    ByteArrayOutputStream output = buildPatch(myVcs, vcsRoot, null, "4:b06a290a363b", new CheckoutRules(""));
    checkPatchResult(output.toByteArray());

    File clonedReposParentDir = myPluginConfig.getCachesDir();
    assertTrue(clonedReposParentDir.isDirectory());
    assertTrue(1 == clonedReposParentDir.list(new FilenameFilter() {
      public boolean accept(final File dir, final String name) {
        return name.startsWith("hg_");
      }
    }).length);
  }

  public void test_build_incremental_patch() throws IOException, VcsException {
    setName("patch1");
    VcsRootImpl vcsRoot = createVcsRoot(simpleRepo());

    ByteArrayOutputStream output = buildPatch(myVcs, vcsRoot, "3:9522278aa38d", "4:b06a290a363b", new CheckoutRules(""));

    checkPatchResult(output.toByteArray());
  }

  public void test_build_incremental_patch_checkout_rules() throws IOException, VcsException {
    setName("patch1_checkout_rules");
    VcsRootImpl vcsRoot = createVcsRoot(simpleRepo());

    ByteArrayOutputStream output = buildPatch(myVcs, vcsRoot, "3:9522278aa38d", "4:b06a290a363b", new CheckoutRules("+:dir1=>path"));

    checkPatchResult(output.toByteArray());
  }

  public void test_build_clean_patch_checkout_rules() throws IOException, VcsException {
    setName("cleanPatch1_checkout_rules");
    VcsRootImpl vcsRoot = createVcsRoot(simpleRepo());

    ByteArrayOutputStream output = buildPatch(myVcs, vcsRoot, null, "4:b06a290a363b", new CheckoutRules("+:dir1/subdir=>."));

    checkPatchResult(output.toByteArray());
  }

  public void test_build_incremental_patch_file_with_space() throws IOException, VcsException {
    setName("patch2");
    VcsRootImpl vcsRoot = createVcsRoot(simpleRepo());

    ByteArrayOutputStream output = buildPatch(myVcs, vcsRoot, "3:9522278aa38d", "6:b9deb9a1c6f4", new CheckoutRules(""));

    checkPatchResult(output.toByteArray());
  }

  public void test_build_incremental_patch_file_non_ascii_does_not_fail() throws IOException, VcsException {
    final String path = Util.getHgPath();
    final VersionCommand versionCommand = new VersionCommand(new TestCommandSettingsFactory().create(), path, new File(".."));
    final HgVersion version = versionCommand.call();

    if (!version.isEqualsOrGreaterThan(new HgVersion(2,7,0))) {
      return;
    }

    setName("patch-non-ascii");
    VcsRootImpl vcsRoot = createVcsRoot(new File("mercurial-tests/testData/repo-non-ascii").getAbsolutePath());

    buildPatch(myVcs, vcsRoot, null, "0:7bd814e7540e", new CheckoutRules(""));
    buildPatch(myVcs, vcsRoot, "0:7bd814e7540e", "1:03c93a16132d", new CheckoutRules(""));
  }

  public void test_get_content() throws IOException, VcsException {
    VcsRootImpl vcsRoot = createVcsRoot(simpleRepo());

    byte[] content = myVcs.getContent("dir1/subdir/file2.txt", vcsRoot, "4:b06a290a363b");
    assertEquals(new String(content), "bbb");
    content = myVcs.getContent("dir1/subdir/file2.txt", vcsRoot, "5:1d2cc6f3bc29");
    assertEquals(new String(content), "modified\r\nbbb");
  }

  public void test_get_content_in_branch() throws IOException, VcsException {
    VcsRootImpl vcsRoot = createVcsRoot(simpleRepo(), "test_branch");

    byte[] content = myVcs.getContent("file_in_branch.txt", vcsRoot, "8:04c3ae4c6312");
    assertEquals(new String(content), "file from the test_branch\r\nfile modified");
  }

  public void get_content_of_non_existing_file_should_throw_exception() throws Exception {
    VcsRootImpl vcsRoot = createVcsRoot(simpleRepo());

    try {
      myVcs.getContent("non/existing/file", vcsRoot, "8:04c3ae4c6312");
      fail("getContent() should throw exception for non-existing file");
    } catch (VcsException e) {
      assertTrue(true);
    }
  }

  public void test_test_connection() throws IOException, VcsException {
    VcsRootImpl vcsRoot = createVcsRoot(simpleRepo());
    vcsRoot.addProperty(Constants.REPOSITORY_PROP, "/some/non/existent/path");
    try {
      myVcs.getTestConnectionSupport().testConnection(vcsRoot);
      fail("Exception expected");
    } catch (VcsException e) {
    }
  }


  public void should_throw_friendly_exception_when_cannot_run_hg() throws Exception {
    VcsRootImpl root = createVcsRoot(simpleRepo());
    File nonExistingHg = myTempFiles.createTempFile();
    delete(nonExistingHg);
    String nonExistingHgPath = nonExistingHg.getAbsolutePath();
    root.addProperty(Constants.HG_COMMAND_PATH_PROP, nonExistingHgPath);
    try {
      myVcs.getTestConnectionSupport().testConnection(root);
      fail("Exception expected");
    } catch (VcsException e) {
      assertEquals(e.getMessage(), "Cannot find mercurial executable at path '" + nonExistingHgPath + "'");
    }
  }


  @TestFor(issues = "TW-36251")
  public void exception_should_contain_no_password_when_password_is_escaped() throws Exception {
    String pwd = "pa{{word";//'{' requires escaping
    String escapedPwd = "pa%7B%7Bword";
    VcsRootImpl root = vcsRoot().withUrl("http://acme.com").withUserName("user").withPassword(pwd).build();
    File nonExistingHg = myTempFiles.createTempFile();
    delete(nonExistingHg);
    String nonExistingHgPath = nonExistingHg.getAbsolutePath();
    root.addProperty(Constants.HG_COMMAND_PATH_PROP, nonExistingHgPath);
    try {
      myVcs.getTestConnectionSupport().testConnection(root);
      fail("Exception expected");
    } catch (VcsException e) {
      String msg = e.getCause().getMessage();
      assertFalse(msg.contains(pwd));
      assertFalse(msg.contains(escapedPwd));
    }
  }


  public void test_tag() throws IOException, VcsException {
    VcsRootImpl vcsRoot = createVcsRoot(simpleRepo());

    String actualTag = myVcs.label("new:tag", "1:1d446e82d356", vcsRoot, new CheckoutRules(""));
    assertEquals(actualTag, "new_tag");

    // check the tag is pushed to the parent repository
    MercurialCommandLine cli = new MercurialCommandLine(Collections.<String>emptySet());
    cli.setExePath(vcsRoot.getProperty(Constants.HG_COMMAND_PATH_PROP));
    cli.setWorkDirectory(vcsRoot.getProperty(Constants.REPOSITORY_PROP));
    cli.setEnvParams(map("HGRCPATH", ""));
    cli.addParameter("tags");
    CommandResult res = CommandUtil.runCommand(cli, new CommandSettings());
    assertTrue(res.getRawStdout().contains("new_tag"));
    assertTrue(res.getRawStdout().contains("1:1d446e82d356"));
  }

  public void test_tag_in_branch() throws IOException, VcsException {
    VcsRootImpl vcsRoot = createVcsRoot(simpleRepo(), "test_branch");

    String actualTag = myVcs.label("branch_tag", "7:376dcf05cd2a", vcsRoot, new CheckoutRules(""));
    assertEquals(actualTag, "branch_tag");

    // check the tag is pushed to the parent repository
    MercurialCommandLine cli = new MercurialCommandLine(Collections.<String>emptySet());
    cli.setExePath(vcsRoot.getProperty(Constants.HG_COMMAND_PATH_PROP));
    cli.setWorkDirectory(vcsRoot.getProperty(Constants.REPOSITORY_PROP));
    cli.addParameter("tags");
    cli.setEnvParams(map("HGRCPATH", ""));
    CommandResult res = CommandUtil.runCommand(cli, new CommandSettings());
    assertTrue(res.getRawStdout().contains("branch_tag"));
    assertTrue(res.getRawStdout().contains("7:376dcf05cd2a"));
  }


  @TestFor(issues = "TW-50033")
  public void labeling_with_branch_tag_clash() throws Exception {
    VcsRootImpl vcsRoot = createVcsRoot(new File("mercurial-tests/testData/rep2").getAbsolutePath(), "default");
    RepositoryStateData s1 = myVcs.getCollectChangesPolicy().getCurrentState(vcsRoot);
    //repository contains the 'topic' branch, create tag on non-last revision in this branch
    myVcs.label("topic", "26:27184c50d7ef", vcsRoot, CheckoutRules.DEFAULT);
    RepositoryStateData s2 = myVcs.getCollectChangesPolicy().getCurrentState(vcsRoot);
    //retrieve tag into local clone
    myVcs.getCollectChangesPolicy().collectChanges(vcsRoot, s1, s2, CheckoutRules.DEFAULT);

    //tag another commit from the 'topic' branch
    myVcs.label("v1", "27:2a368008e4d9", vcsRoot, CheckoutRules.DEFAULT);
  }


  public void tag_should_be_created_in_branch_to_which_tagged_revision_belongs() throws Exception {
    File remoteRepo = copyRepository(myTempFiles, myRep2Path);
    VcsRoot root = vcsRoot().withUrl(remoteRepo.getCanonicalPath()).withBranch("default").build();
    RepositoryStateData beforeLabelState = myVcs.getCollectChangesPolicy().getCurrentState(root);
    String topicCurrentVersion = beforeLabelState.getBranchRevisions().get("topic");
    myVcs.label("label_in_branch", topicCurrentVersion, root, CheckoutRules.DEFAULT);
    RepositoryStateData afterLabelState = myVcs.getCollectChangesPolicy().getCurrentState(root);
    assertEquals(beforeLabelState.getBranchRevisions().get("default"), afterLabelState.getBranchRevisions().get("default"));
    assertNotEquals(beforeLabelState.getBranchRevisions().get("topic"), afterLabelState.getBranchRevisions().get("topic"));//topic changed => tag was created there
  }


  @TestFor(issues = "TW-10076")
  public void should_allow_to_ignore_changes_where_all_files_excluded() throws Exception {
    VcsRootImpl root = createVcsRoot(myRep2Path);
    List<ModificationData> changes = myVcs.getCollectChangesPolicy().collectChanges(root, "43023ea3f13b", "a2672ee13212", new CheckoutRules("-:.hgtags"));
    assertEquals(changes.size(), 1);
    assertTrue(changes.get(0).isCanBeIgnored());
  }


  @TestFor(issues = "TW-10076")
  public void should_not_allow_to_ignore_close_branch_commits() throws Exception {
    try {
      System.setProperty("teamcity.hg.allowIgnoringEmptyCommits", "false");
      VcsRootImpl root = createVcsRoot(myRep2Path);
      List<ModificationData> changes = myVcs.getCollectChangesPolicy().collectChanges(root, "df04faa7575a", "43023ea3f13b", CheckoutRules.DEFAULT);
      assertEquals(changes.size(), 2);
      ModificationData closeBranchCommit = changes.get(0);
      assertEquals(closeBranchCommit.getVersion(), "ca64c523f54c");
      assertEquals(closeBranchCommit.getChangeCount(), 0);
      assertFalse(closeBranchCommit.isCanBeIgnored());
    } finally {
      System.getProperties().remove("teamcity.hg.allowIgnoringEmptyCommits");
    }
  }


  @TestFor(issues = "TW-44122")
  public void by_default_all_commits_should_be_ignorable() throws Exception {
    VcsRootImpl root = createVcsRoot(myRep2Path);
    List<ModificationData> changes = myVcs.getCollectChangesPolicy().collectChanges(root, "df04faa7575a", "43023ea3f13b", CheckoutRules.DEFAULT);
    for (ModificationData change : changes) {
      assertTrue(change.isCanBeIgnored());
    }
  }


  public void test_tag_with_specified_username() throws IOException, VcsException {
    final String customUserForTag = "John Doe <john@some.org>";
    File repository = copyRepository(myTempFiles, simpleRepo());
    VcsRoot root = vcsRoot().withUrl(repository.getAbsolutePath()).withUserForTag(customUserForTag).build();

    myVcs.label("tag_by_specified_user", "10:9c6a6b4aede0", root, CheckoutRules.DEFAULT);

    RepositoryStateData state = myVcs.getCollectChangesPolicy().getCurrentState(root);
    String currentVersion = state.getBranchRevisions().get(state.getDefaultBranchName());
    List<ModificationData> changes = myVcs.getCollectChangesPolicy().collectChanges(root, "10:9c6a6b4aede0", currentVersion, CheckoutRules.DEFAULT);
    assertEquals(changes.size(), 1);
    assertEquals(changes.get(0).getUserName(), customUserForTag);
  }


  @RequiredHgVersion(min = "2.7")
  @Test(dataProviderClass = HgVersionConstraint.class, dataProvider = "installedHgVersion")
  public void unicode_in_filename(@NotNull HgVersion _) throws Exception {
    File repo = copyRepository(myTempFiles, new File("mercurial-tests/testData/unicodeFileName").getAbsolutePath());
    VcsRoot root = vcsRoot().withUrl(repo.getAbsolutePath())
            .withSubrepoChanges(false)
            .withSubreposInPatch(false)
            .withArchiveForPatch(true)
            .build();

    buildPatch(myVcs, root, null, "51677e03dc19", CheckoutRules.DEFAULT);

    if (SystemInfo.isUnix)
      return;//incremental patch on unix fails, because it cannot create a file in unicode
    buildPatch(myVcs, root, "54044489d391", "51677e03dc19", CheckoutRules.DEFAULT);
  }


  @TestFor(issues = "UP-1866")
  public void incremental_patch_with_spec_symbols_in_paths() throws Exception {
    setName("fileNameSymbolsIncrementalPatch");
    File remoteRepo = copyRepository(myTempFiles, new File("mercurial-tests/testData/fileNameSymbols").getAbsolutePath());
    VcsRoot root = vcsRoot().withUrl(remoteRepo.getAbsolutePath()).withArchiveForPatch(true).build();
    ByteArrayOutputStream output = buildPatch(myVcs, root, "cc8c19c8f8ee", "120549be17e9", CheckoutRules.DEFAULT);
    checkPatchResult(output.toByteArray());
  }


  public void clean_patch_from_archive() throws Exception {
    setName("patch3");
    File repository = copyRepository(myTempFiles, simpleRepo());
    VcsRoot root = vcsRoot().withUrl(repository.getAbsolutePath())
            .withBranch("test_branch")
            .withArchiveForPatch(true)
            .build();
    ByteArrayOutputStream output = buildPatch(myVcs, root, null, "7:376dcf05cd2a", CheckoutRules.DEFAULT);
    checkPatchResult(output.toByteArray());
  }


  @TestFor(issues = "TW-39663")
  public void do_not_escape_command_output_for_parsing() throws Exception {
    File repository = copyRepository(myTempFiles, simpleRepo());
    VcsRoot root = vcsRoot().withUrl(repository.getAbsolutePath())
            .withBranch("default")
            .withPassword("default") //to check we don't escape branch names
            .withArchiveForPatch(true)
            .build();

    assertTrue(myVcs.getCollectChangesPolicy().getCurrentState(root).getBranchRevisions().containsKey("default"));

    root = vcsRoot().withUrl(repository.getAbsolutePath())
            .withBranch("default")
            .withPassword("xml") //to ensure we don't escape xml from 'hg log'
            .withArchiveForPatch(true)
            .build();

    assertEquals(7, myVcs.getCollectChangesPolicy().collectChanges(root,
            RepositoryStateData.createVersionState("default", map("default", "1:1d446e82d356")),
            RepositoryStateData.createVersionState("default", map("default", "11:e6935c9c80bf")),
            CheckoutRules.DEFAULT).size());
  }


  public void labeling_should_not_populate_files_in_local_mirror() throws Exception {
    VcsRootImpl root = createVcsRoot(simpleRepo());
    myVcs.getCollectChangesPolicy().getCurrentState(root);
    File mirror = myVcs.getMirrorManager().getMirrorDir(root.getProperty(Constants.REPOSITORY_PROP));
    File[] files = mirror.listFiles();
    assertEquals(files.length, 1);
    assertEquals(files[0].getName(), ".hg");

    myVcs.label("v1.0", "7:376dcf05cd2a", root, new CheckoutRules(""));

    files = mirror.listFiles();
    assertEquals(files.length, 1);
    assertEquals(files[0].getName(), ".hg");
  }


  public void test_collect_changes_in_branch() throws Exception {
    VcsRootImpl vcsRoot = createVcsRoot(simpleRepo(), "test_branch");

    // fromVersion(6:b9deb9a1c6f4) is not in the branch (it is in the default branch)
    List<ModificationData> changes = collectChanges(vcsRoot, "6:b9deb9a1c6f4", "7:376dcf05cd2a", CheckoutRules.DEFAULT);
    assertEquals(1, changes.size());

    ModificationData md1 = changes.get(0);
    assertEquals(md1.getVersion(), "376dcf05cd2a");
    assertEquals(md1.getDescription(), "new file added in the test_branch");
    List<VcsChange> files1 = md1.getChanges();
    assertEquals(1, files1.size());
    assertEquals(VcsChangeInfo.Type.ADDED, files1.get(0).getType());
    assertEquals(normalizePath(files1.get(0).getRelativeFileName()), "file_in_branch.txt");

    changes = collectChanges(vcsRoot, "7:376dcf05cd2a", "8:04c3ae4c6312", CheckoutRules.DEFAULT);
    assertEquals(1, changes.size());

    md1 = changes.get(0);
    assertEquals(md1.getVersion(), "04c3ae4c6312");
    assertEquals(md1.getDescription(), "file modified");
  }

  public void incremental_patch_for_commit_with_deletes_only() throws Exception {
    setName("inc_patch_deletes_only");
    VcsRoot root = createVcsRoot(simpleRepo(), "test_remove");
    checkPatchResult(buildPatch(myVcs, root, "9c6a6b4aede0", "e6935c9c80bf", CheckoutRules.DEFAULT).toByteArray());
  }

  public void test_full_patch_from_branch() throws IOException, VcsException {
    setName("patch3");
    VcsRootImpl vcsRoot = createVcsRoot(simpleRepo(), "test_branch");

    ByteArrayOutputStream output = buildPatch(myVcs, vcsRoot, null, "7:376dcf05cd2a", new CheckoutRules(""));

    checkPatchResult(output.toByteArray());
  }

  public void test_full_patch_from_branch_with_checkout_rules() throws IOException, VcsException {
    setName("patch3_checkout_rules1");
    VcsRootImpl vcsRoot = createVcsRoot(simpleRepo(), "test_branch");

    ByteArrayOutputStream output = buildPatch(myVcs, vcsRoot, null, "7:376dcf05cd2a", new CheckoutRules("+:.=>path"));

    checkPatchResult(output.toByteArray());
  }

  public void test_full_patch_from_branch_with_checkout_rules_mapped_and_skipped() throws IOException, VcsException {
    setName("patch3_checkout_rules2");
    VcsRootImpl vcsRoot = createVcsRoot(simpleRepo(), "test_branch");

    ByteArrayOutputStream output = buildPatch(myVcs, vcsRoot, null, "7:376dcf05cd2a", new CheckoutRules("+:dir1=>path/dir1\n+:dir with space"));

    checkPatchResult(output.toByteArray());
  }

  public void test_incremental_patch_from_branch() throws IOException, VcsException {
    setName("patch4");
    VcsRootImpl vcsRoot = createVcsRoot(simpleRepo(), "test_branch");

    ByteArrayOutputStream output = buildPatch(myVcs, vcsRoot, "7:376dcf05cd2a", "8:04c3ae4c6312", new CheckoutRules(""));

    checkPatchResult(output.toByteArray());
  }

  @Test(enabled = false)
  public void support_anchor_branch_notation() throws IOException {
    VcsRootImpl vcsRoot = createVcsRoot(simpleRepo());
    String repPath = vcsRoot.getProperty(Constants.REPOSITORY_PROP);
    vcsRoot.addProperty(Constants.REPOSITORY_PROP, repPath + "#test_branch");
    HgVcsRoot hgRoot = new HgVcsRoot(vcsRoot);
    assertEquals("test_branch", hgRoot.getBranchName());

    vcsRoot.addProperty(Constants.REPOSITORY_PROP, repPath + "#");
    hgRoot = new HgVcsRoot(vcsRoot);
    assertEquals("default", hgRoot.getBranchName());

    vcsRoot.addProperty(Constants.REPOSITORY_PROP, repPath);
    hgRoot = new HgVcsRoot(vcsRoot);
    assertEquals("default", hgRoot.getBranchName());
  }

  public void build_patch_using_custom_clone_path() throws IOException, VcsException {
    setInternalProperty(Constants.CUSTOM_CLONE_PATH_ENABLED, "true");
    setName("cleanPatch1");
    VcsRootImpl vcsRoot = createVcsRoot(simpleRepo());
    File cloneDir = myTempFiles.createTempDir();
    vcsRoot.addProperty(Constants.SERVER_CLONE_PATH_PROP, cloneDir.getAbsolutePath());

    ByteArrayOutputStream output = buildPatch(myVcs, vcsRoot, null, "4:b06a290a363b", new CheckoutRules(""));

    checkPatchResult(output.toByteArray());

    assertTrue(new File(cloneDir, new File(vcsRoot.getProperty(Constants.REPOSITORY_PROP)).getName()).isDirectory());
  }

  public void build_patch_from_newer_revision_to_earlier() throws Exception {
    setName("patch5");
    VcsRootImpl vcsRoot = createVcsRoot(simpleRepo());
    ByteArrayOutputStream output = buildPatch(myVcs, vcsRoot, "6:b9deb9a1c6f4", "3:9522278aa38d", CheckoutRules.DEFAULT);
    checkPatchResult(output.toByteArray());
  }

  public void build_patch_from_unknown_revision() throws Exception {
    setName("patch6");
    VcsRootImpl vcsRoot = createVcsRoot(simpleRepo());
    ByteArrayOutputStream output = buildPatch(myVcs, vcsRoot, "6:hahahahahaha", "3:9522278aa38d", new CheckoutRules("+:.=>path"));
    checkPatchResult(output.toByteArray());
  }

  private String mergeCommittsRepo() {
    return new File("mercurial-tests/testData/rep2").getAbsolutePath();
  }

  public void test_collect_changes_between_two_different_roots() throws Exception {
    VcsRootImpl defaultRoot = createVcsRoot(mergeCommittsRepo());
    VcsRootImpl branchRoot = createVcsRoot(mergeCommittsRepo(), "test");
    List<ModificationData> changes = myVcs.getCollectChangesPolicy().collectChanges(defaultRoot, "11:48177654181c", branchRoot, "10:fc524efc2bc4", CheckoutRules.DEFAULT);
    assertEquals(changes.size(), 2);

    assertEquals(changes.get(0).getVersion(), "8c44244d6645");
    assertEquals(changes.get(1).getVersion(), "fc524efc2bc4");
  }

  public void collectChanges_should_return_all_changes_from_branch() throws Exception {
    VcsRootImpl defaultBranchRoot = createVcsRoot(myRep2Path, "default");
    VcsRootImpl personalBranchRoot = createVcsRoot(myRep2Path, "personal-branch");
    List<ModificationData> modifications = myVcs.getCollectChangesPolicy().collectChanges(defaultBranchRoot, "16:505c5b9d01e6", personalBranchRoot, "17:9ec402c74298", CheckoutRules.DEFAULT);
    assertEquals(3, modifications.size());
  }

  public void test_collect_changes_merge() throws Exception {
    VcsRootImpl vcsRoot = createVcsRoot(mergeCommittsRepo());

    List<ModificationData> changes = collectChanges(vcsRoot, "1:a3d15477d297", "4:6eeb8974fe67", CheckoutRules.DEFAULT);
    assertEquals(changes.size(), 3);

    assertEquals(changes.get(0).getVersion(), "db8a04d262f3");
    assertEquals(changes.get(1).getVersion(), "2538c02bafeb");
    assertEquals(changes.get(2).getVersion(), "6eeb8974fe67");

    assertFiles(Arrays.asList("A dir1/file1.txt"), changes.get(0));
    assertFiles(Arrays.asList("A dir2/file2.txt"), changes.get(1));
    assertFiles(Arrays.asList("A dir1/file1.txt", "A dir2/file2.txt"), changes.get(2));
  }

  public void test_collect_changes_merge_conflict() throws Exception {
    VcsRootImpl vcsRoot = createVcsRoot(mergeCommittsRepo());

    List<ModificationData> changes = collectChanges(vcsRoot, "6:6066b677d026", "8:b6e2d176fe8e", CheckoutRules.DEFAULT);
    assertEquals(changes.size(), 2);

    assertFiles(Arrays.asList("A dir4/file41.txt"), changes.get(0));
    assertFiles(Arrays.asList("M dir4/file41.txt", "A dir4/file42.txt", "A dir4/file43.txt", "R dir3/file3.txt"), changes.get(1));
  }

  public void test_collect_changes_merge_conflict_named_branch() throws Exception {
    VcsRootImpl vcsRoot = createVcsRoot(mergeCommittsRepo());

    List<ModificationData> changes = collectChanges(vcsRoot, "8:b6e2d176fe8e", "12:1e620196c4b6", CheckoutRules.DEFAULT);
    assertEquals(changes.size(), 4);

    assertFiles(Arrays.asList("A dir6/file6.txt"), changes.get(2));
    assertFiles(Arrays.asList("M dir6/file6.txt", "A dir5/file5.txt"), changes.get(3));
  }

  //TW-17530
  public void test_collect_changes_with_exclude_checkout_rules() throws Exception {
    VcsRootImpl root = createVcsRoot(simpleRepo());
    collectChanges(root, "0:9875b412a788", "10:9c6a6b4aede0", new CheckoutRules("-:dir1\n" +
            "-:dir with space"));
  }

  //TW-10172
  public void should_not_fill_server_clone_path() {
    assertFalse(myVcs.getDefaultVcsProperties().containsKey(Constants.SERVER_CLONE_PATH_PROP));

    Map<String, String> rootProperties = new HashMap<String, String>() {{
      put(Constants.HG_COMMAND_PATH_PROP, "hg");
      put(Constants.REPOSITORY_PROP, "http://somewhere.com/path");
    }};

    assertFalse(rootProperties.containsKey(Constants.SERVER_CLONE_PATH_PROP));
    myVcs.getVcsPropertiesProcessor().process(rootProperties);
    assertFalse(rootProperties.containsKey(Constants.SERVER_CLONE_PATH_PROP));
  }

  public void use_compressed_transfer_by_default() {
    VcsRootImpl root = new VcsRootImpl(1, Constants.VCS_NAME);
    root.setProperties(myVcs.getDefaultVcsProperties());
    root.addProperty(Constants.REPOSITORY_PROP, "http://host.com/path");
    HgVcsRoot hgRoot = new HgVcsRoot(root);
    assertTrue(hgRoot.isUncompressedTransfer());
  }


  //TW-15762
  public void should_use_clone_to_root_parameter() throws IOException, VcsException {
    VcsRootImpl vcsRoot = createVcsRoot(simpleRepo());
    File cloneDir = myTempFiles.createTempDir();
    vcsRoot.addProperty(Constants.SERVER_CLONE_PATH_PROP, cloneDir.getAbsolutePath());
    myVcs.getCollectChangesPolicy().getCurrentState(vcsRoot);
  }


  public void labeling_should_not_change_vcs_root_settings() throws Exception {
    VcsRootImpl root = createVcsRoot(simpleRepo(), "test_branch");
    assertNull(root.getProperty(Constants.SERVER_CLONE_PATH_PROP));
    myVcs.label("branch_tag", "7:376dcf05cd2a", root, CheckoutRules.DEFAULT);
    assertNull(root.getProperty(Constants.SERVER_CLONE_PATH_PROP));
  }


  @TestFor(issues = "TW-24810")
  public void do_not_delete_symlinked_dir() throws Exception {
    if (!SystemInfo.isUnix)
      return;

    //create a file on the server
    File dirOnTheServer = myTempFiles.createTempDir();
    File fileOnTheServer = new File(dirOnTheServer, "file.on.server");
    FileUtil.writeFile(fileOnTheServer, "some text");

    //create a remote repository with symlink pointing to the file on the server
    File repository = copyRepository(myTempFiles, simpleRepo());
    ServerHgRepo repo = new ServerHgRepo(new TestCommandSettingsFactory(), myPluginConfig, repository, getHgPath(), new AuthSettings());
    repo.update().toRevision("9c6a6b4aede0").call();
    new ProcessBuilder("ln", "-s", dirOnTheServer.getCanonicalPath()).directory(repository).start().waitFor();
    new ProcessBuilder(getHgPath(), "add", dirOnTheServer.getName()).directory(repository).start().waitFor();
    new ProcessBuilder(getHgPath(), "commit", "-m", "add a symlink").directory(repository).start().waitFor();

    String revisionWithSymlink = repo.branches().call().get("default");

    //build a clean patch on revision wich contain a symlink
    VcsRoot root = vcsRoot().withUrl(repository.getCanonicalPath()).build();
    buildPatch(myVcs, root, null, revisionWithSymlink, CheckoutRules.DEFAULT);

    //check file & dir still exist
    assertTrue(dirOnTheServer.exists());
    assertTrue(fileOnTheServer.exists());
  }


  public void hg_version_should_not_depend_on_locale() throws IOException, VcsException {
    HgRepo repo = new HgRepo(new TestCommandSettingsFactory(), new File(simpleRepo()), Util.getHgPath(), new AuthSettings(null, null));
    HgVersion defaultLocaleVersion = repo.version().call();

    VersionCommand russianLocalVersion = new VersionCommand(new TestCommandSettingsFactory().create(), Util.getHgPath(), new File(simpleRepo())) {
      @Override
      @NotNull
      protected MercurialCommandLine createCommandLine() {
        final MercurialCommandLine env = super.createCommandLine();
        env.addEnvParam("LANG", "ru_RU");
        env.addEnvParam("LANGUAGE", "ru_RU");
        env.addEnvParam("LC_MESSAGE", "ru_RU");
        env.addEnvParam("HGRCPATH", "");
        return env;
      }
    };

    HgVersion russianLocaleVersion = russianLocalVersion.call();
    assertEquals(russianLocaleVersion, defaultLocaleVersion);
  }

  public void collect_changes_between_states() throws Exception {
    VcsRootImpl root = createVcsRoot(myRep2Path);
    List<ModificationData> changes = myVcs.getCollectChangesPolicy().collectChanges(root,
            RepositoryStateData.createVersionState("default", map("default", "1e620196c4b6")),
            RepositoryStateData.createVersionState("default", map("default", "505c5b9d01e6", "personal-branch", "96b78d73081d")),
            CheckoutRules.DEFAULT);
    assertEquals(changes.size(), 4);
    assertThat(changes, hasItem(modificationData().withVersion("dec47d2d49bf")));
    assertThat(changes, hasItem(modificationData().withVersion("78e67807f916")));
    assertThat(changes, hasItem(modificationData().withVersion("96b78d73081d")));
    assertThat(changes, hasItem(modificationData().withVersion("505c5b9d01e6")));
  }


  public void collect_changes_between_states_does_not_report_duplicate_changes() throws Exception {
    VcsRootImpl root = createVcsRoot(myRep2Path);
    List<ModificationData> changes = myVcs.getCollectChangesPolicy().collectChanges(root,
            RepositoryStateData.createVersionState("default", map("default", "8c44244d6645")),
            RepositoryStateData.createVersionState("default", map("default", "505c5b9d01e6", "personal-branch", "9ec402c74298")),
            CheckoutRules.DEFAULT);
    assertEquals(changes.size(), 8);
    assertThat(changes, hasItem(modificationData().withVersion("9ec402c74298")));
    assertThat(changes, hasItem(modificationData().withVersion("505c5b9d01e6")));
    assertThat(changes, hasItem(modificationData().withVersion("96b78d73081d")));
    assertThat(changes, hasItem(modificationData().withVersion("78e67807f916")));
    assertThat(changes, hasItem(modificationData().withVersion("dec47d2d49bf")));
    assertThat(changes, hasItem(modificationData().withVersion("1e620196c4b6")));
    assertThat(changes, hasItem(modificationData().withVersion("48177654181c")));
    assertThat(changes, hasItem(modificationData().withVersion("fc524efc2bc4")));
  }


  @RequiredHgVersion(min = "1.7.0")
  @Test(dataProviderClass = HgVersionConstraint.class, dataProvider = "installedHgVersion")
  public void should_not_report_redundant_changes_after_merge(@NotNull HgVersion _) throws Exception {
    VcsRootImpl root = createVcsRoot(myRep2Path);
    List<ModificationData> changes = myVcs.getCollectChangesPolicy().collectChanges(root,
            RepositoryStateData.createVersionState("default", map("default", "505c5b9d01e6", "personal-branch", "9ec402c74298")),
            RepositoryStateData.createVersionState("default", map("default", "df04faa7575a", "personal-branch", "9ec402c74298")),
            CheckoutRules.DEFAULT);
    assertEquals(changes.size(), 1);
  }


  @RequiredHgVersion(min = "1.7.0")
  @Test(dataProviderClass = HgVersionConstraint.class, dataProvider = "installedHgVersion")
  public void should_not_report_duplicate_changes(@NotNull HgVersion _) throws Exception {
    VcsRootImpl root = createVcsRoot(myRep2Path);
    List<ModificationData> changes = myVcs.getCollectChangesPolicy().collectChanges(root,
            RepositoryStateData.createVersionState("default", map("default", "505c5b9d01e6", "personal-branch", "96b78d73081d")),
            RepositoryStateData.createVersionState("default", map("default", "df04faa7575a", "personal-branch", "9ec402c74298")),
            CheckoutRules.DEFAULT);
    assertThat(changes, not(hasItem(modificationData().withVersion("dec47d2d49bf"))));
  }


  @RequiredHgVersion(min = "1.7.0")
  @Test(dataProviderClass = HgVersionConstraint.class, dataProvider = "installedHgVersion")
  public void should_not_report_duplicate_changes2(@NotNull HgVersion _) throws Exception {
    VcsRootImpl root = createVcsRoot(myRep2Path);
    List<ModificationData> changes = myVcs.getCollectChangesPolicy().collectChanges(root,
            RepositoryStateData.createVersionState("default", map("default", "528572bbf77b", "personal-branch", "27184c50d7ef")),
            RepositoryStateData.createVersionState("default", map("default", "4780519e01aa", "personal-branch", "fd50e4842211")),
            CheckoutRules.DEFAULT);
    assertThat(changes, not(hasItem(modificationData().withVersion("4dbb87c381be"))));
  }


  @RequiredHgVersion(min = "1.7.0")
  @Test(dataProviderClass = HgVersionConstraint.class, dataProvider = "installedHgVersion")
  public void should_not_report_all_changes_in_repository_if_default_branch_is_unrelated(@NotNull HgVersion _) throws Exception {
    VcsRootImpl root = createVcsRoot(myRep2Path);
    List<ModificationData> changes = myVcs.getCollectChangesPolicy().collectChanges(root,
            RepositoryStateData.createVersionState("NULL", map("NULL", "1f355761350e")),
            RepositoryStateData.createVersionState("NULL", map("NULL", "1f355761350e", "personal-branch", "fd50e4842211")),
            CheckoutRules.DEFAULT);
    assertEquals(0, changes.size());
  }


  @TestFor(issues = "TW-29998")
  public void test_rev_not_found() throws Exception {
    VcsRoot root = createVcsRoot(myRep2Path);
    //there should be no exception, even though 010101010101 is not in the repository
    myVcs.getCollectChangesPolicy().collectChanges(root,
            RepositoryStateData.createVersionState("default", map("default", "737c6f57ef84", "unknown.branch", "010101010101")),
            RepositoryStateData.createVersionState("default", map("default", "4780519e01aa", "topic", "fd50e4842211")),
            CheckoutRules.DEFAULT);
  }


  private void assertFiles(final List<String> expectedFiles, final ModificationData modificationData) {
    Set<String> actualFiles = new HashSet<String>();
    for (VcsChange vc: modificationData.getChanges()) {
      actualFiles.add(toFileStatus(vc.getType()) + " " + vc.getRelativeFileName());
    }
    Assert.assertEquals("Actual files: " + actualFiles.toString(), new HashSet<String>(expectedFiles), actualFiles);
  }

  private String toFileStatus(VcsChange.Type type) {
    switch (type) {
      case ADDED:
        return "A";
      case REMOVED:
        return "R";
      case CHANGED:
        return "M";
    }
    return "?";
  }

  private Object normalizePath(final String path) {
    return path.replace(File.separatorChar, '/');
  }


  public void test_collect_changes_using_checkout_rules() {
    assertTrue(myVcs.getCollectChangesPolicy() instanceof CollectChangesByCheckoutRules);
  }
}