changeset 425:e33c3e4918f5

Add list files support
author Dmitry Neverov <dmitry.neverov@jetbrains.com>
date Fri, 11 May 2012 15:21:35 +0400
parents 3239780e4e8f
children c91c4f1ebd53
files mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/HgRepo.java mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/ChangeSet.java mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/FileStatus.java mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/LogCommand.java mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/ModifiedFile.java mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/Status.java mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/StatusCommand.java mercurial-server/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/ListFilesSupport.java mercurial-server/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/MercurialVcsSupport.java mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/ListFilesTest.java mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/LogCommandTest.java mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/StatusCommandTest.java mercurial-tests/src/testng.xml
diffstat 13 files changed, 381 insertions(+), 156 deletions(-) [+]
line wrap: on
line diff
--- a/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/HgRepo.java	Fri May 11 12:10:16 2012 +0400
+++ b/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/HgRepo.java	Fri May 11 15:21:35 2012 +0400
@@ -7,9 +7,7 @@
 
 import java.io.File;
 import java.io.IOException;
-import java.util.Arrays;
-import java.util.HashMap;
-import java.util.Map;
+import java.util.*;
 
 import static com.intellij.openapi.util.io.FileUtil.delete;
 import static java.util.Collections.emptyMap;
@@ -91,6 +89,20 @@
     return isEmptyDir(myWorkingDir);
   }
 
+  @NotNull
+  public List<String> listFiles() throws VcsException {
+    List<FileStatus> fileStatuses = status()
+            .fromRevision("tip")
+            .toRevision("tip")
+            .hideStatus()
+            .showAllFiles()
+            .call();
+    List<String> files = new ArrayList<String>(fileStatuses.size());
+    for (FileStatus fileStatus : fileStatuses)
+      files.add(fileStatus.getPath());
+    return files;
+  }
+
   public String getWorkingDirRevision() throws VcsException {
     return id().inLocalRepository().call();
   }
--- a/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/ChangeSet.java	Fri May 11 12:10:16 2012 +0400
+++ b/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/ChangeSet.java	Fri May 11 15:21:35 2012 +0400
@@ -29,7 +29,7 @@
   @NotNull private Date myTimestamp;
   private String myDescription;
   private List<ChangeSetRevision> myParents = new ArrayList<ChangeSetRevision>();
-  private List<ModifiedFile> myModifiedFiles = new ArrayList<ModifiedFile>();
+  private List<FileStatus> myModifiedFiles = new ArrayList<FileStatus>();
 
   public ChangeSet(final int revNumber, @NotNull final String id) {
     super(revNumber, id);
@@ -102,12 +102,12 @@
     return getParents().isEmpty();
   }
 
-  public void setModifiedFiles(@NotNull final List<ModifiedFile> files) {
+  public void setModifiedFiles(@NotNull final List<FileStatus> files) {
     myModifiedFiles = files;
   }
 
   @NotNull
-  public List<ModifiedFile> getModifiedFiles() {
+  public List<FileStatus> getModifiedFiles() {
     return myModifiedFiles;
   }
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/FileStatus.java	Fri May 11 15:21:35 2012 +0400
@@ -0,0 +1,65 @@
+/*
+ * 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.command;
+
+import org.jetbrains.annotations.NotNull;
+
+/**
+ * Status of a file in a repository
+ */
+public class FileStatus {
+
+  @NotNull private final Status myStatus;
+  @NotNull private final String myPath;
+
+  public FileStatus(@NotNull final Status status, @NotNull final String path) {
+    myStatus = status;
+    myPath = path;
+  }
+
+  /**
+   * @return status of a file
+   */
+  @NotNull
+  public Status getStatus() {
+    return myStatus;
+  }
+
+  /**
+   * @return file path
+   */
+  @NotNull
+  public String getPath() {
+    return myPath;
+  }
+
+  @Override
+  public boolean equals(final Object o) {
+    if (this == o) return true;
+    if (o == null || getClass() != o.getClass()) return false;
+
+    final FileStatus that = (FileStatus) o;
+
+    return myPath.equals(that.myPath) && myStatus == that.myStatus;
+  }
+
+  @Override
+  public int hashCode() {
+    int result = myStatus.hashCode();
+    result = 31 * result + myPath.hashCode();
+    return result;
+  }
+}
--- a/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/LogCommand.java	Fri May 11 12:10:16 2012 +0400
+++ b/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/LogCommand.java	Fri May 11 15:21:35 2012 +0400
@@ -31,6 +31,8 @@
 import java.text.SimpleDateFormat;
 import java.util.*;
 
+import static jetbrains.buildServer.buildTriggers.vcs.mercurial.command.Status.makeStatus;
+
 public class LogCommand extends VcsRootCommand {
 
   private final static String ZERO_PARENT_ID = "0000000000000000000000000000000000000000";
@@ -216,8 +218,8 @@
   }
 
 
-  private List<ModifiedFile> getModifiedFiles(@NotNull final Element logEntry) {
-    List<ModifiedFile> result = new ArrayList<ModifiedFile>();
+  private List<FileStatus> getModifiedFiles(@NotNull final Element logEntry) {
+    List<FileStatus> result = new ArrayList<FileStatus>();
     Element paths = logEntry.getChild("paths");
     if (paths == null)
       return result;
@@ -229,24 +231,10 @@
   }
 
 
-  private ModifiedFile getModifiedFile(@NotNull final Element path) {
+  private FileStatus getModifiedFile(@NotNull final Element path) {
     String filePath = path.getText();
-    ModifiedFile.Status status = getStatus(path);
-    return new ModifiedFile(status, filePath);
-  }
-
-
-  private ModifiedFile.Status getStatus(@NotNull final Element path) {
     String action = path.getAttribute("action").getValue();
-    if (action.equals("A")) {
-      return ModifiedFile.Status.ADDED;
-    } else if (action.equals("M")) {
-      return ModifiedFile.Status.MODIFIED;
-    } else if (action.equals("R")) {
-      return ModifiedFile.Status.REMOVED;
-    } else {
-      return ModifiedFile.Status.UNKNOWN;
-    }
+    return new FileStatus(makeStatus(action), filePath);
   }
 
   private void assignTrivialParents(final @NotNull List<ChangeSet> csets) throws VcsException {
--- a/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/ModifiedFile.java	Fri May 11 12:10:16 2012 +0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,86 +0,0 @@
-/*
- * 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.command;
-
-import org.jetbrains.annotations.NotNull;
-
-/**
- * Represents repository modified file
- */
-public class ModifiedFile {
-  /**
-   * Type of modification
-   */
-  public static enum Status {
-    ADDED("added"),
-    MODIFIED("modified"),
-    REMOVED("removed"),
-    UNKNOWN("unknown");
-    private String myName;
-
-    Status(@NotNull final String name) {
-      myName = name;
-    }
-
-    @NotNull
-    public String getName() {
-      return myName;
-    }
-  }
-
-  @NotNull private Status myStatus;
-  @NotNull private String myPath;
-
-  public ModifiedFile(@NotNull final Status status, @NotNull final String path) {
-    myStatus = status;
-    myPath = path;
-  }
-
-  /**
-   * Returns type of modification
-   * @return type of modification
-   */
-  @NotNull
-  public Status getStatus() {
-    return myStatus;
-  }
-
-  /**
-   * Returns file path
-   * @return file path
-   */
-  @NotNull
-  public String getPath() {
-    return myPath;
-  }
-
-  @Override
-  public boolean equals(final Object o) {
-    if (this == o) return true;
-    if (o == null || getClass() != o.getClass()) return false;
-
-    final ModifiedFile that = (ModifiedFile) o;
-
-    return myPath.equals(that.myPath) && myStatus == that.myStatus;
-  }
-
-  @Override
-  public int hashCode() {
-    int result = myStatus.hashCode();
-    result = 31 * result + myPath.hashCode();
-    return result;
-  }
-}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/Status.java	Fri May 11 15:21:35 2012 +0400
@@ -0,0 +1,51 @@
+package jetbrains.buildServer.buildTriggers.vcs.mercurial.command;
+
+import org.jetbrains.annotations.NotNull;
+
+import static com.intellij.openapi.util.text.StringUtil.isEmpty;
+
+/**
+ * File status, see 'hg help status'.
+ */
+public enum Status {
+
+  ADDED("added"),
+  MODIFIED("modified"),
+  REMOVED("removed"),
+  CLEAN("clean"),
+  MISSING("missing"),
+  NOT_TRACKED("not tracked"),
+  IGNORED("ignored"),
+  UNKNOWN("unknown");
+
+  private final String myName;
+
+  Status(@NotNull String name) {
+    myName = name;
+  }
+
+  @NotNull
+  public String getName() {
+    return myName;
+  }
+
+  public static Status makeStatus(@NotNull final String s) {
+    if (isEmpty(s))
+      return UNKNOWN;
+    return makeStatus(s.charAt(0));
+  }
+
+  public static Status makeStatus(final char c) {
+    switch (c) {
+      case 'A': return ADDED;
+      case 'M': return MODIFIED;
+      case 'R': return REMOVED;
+      case 'C': return CLEAN;
+      case '!': return MISSING;
+      case '?': return NOT_TRACKED;
+      case 'I': return IGNORED;
+      case ' ': return ADDED;
+      default : return UNKNOWN;
+    }
+  }
+}
--- a/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/StatusCommand.java	Fri May 11 12:10:16 2012 +0400
+++ b/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/StatusCommand.java	Fri May 11 15:21:35 2012 +0400
@@ -23,9 +23,13 @@
 import java.util.ArrayList;
 import java.util.List;
 
+import static com.intellij.openapi.util.text.StringUtil.isEmpty;
+
 public class StatusCommand extends VcsRootCommand {
   private String myFromId;
   private String myToId;
+  private boolean myShowAllFiles = false;
+  private boolean myHideStatus = false;
 
   public StatusCommand(@NotNull String hgPath, @NotNull File workingDir, @NotNull AuthSettings authSettings) {
     super(hgPath, workingDir, authSettings);
@@ -51,39 +55,64 @@
     return this;
   }
 
-  public List<ModifiedFile> call() throws VcsException {
+  /**
+   * Adds option -A (--all)
+   * @return self
+   */
+  public StatusCommand showAllFiles() {
+    myShowAllFiles = true;
+    return this;
+  }
+
+  /**
+   * Adds option -n (--no-status)
+   * @return self
+   */
+  public StatusCommand hideStatus() {
+    myHideStatus = true;
+    return this;
+  }
+
+  public List<FileStatus> call() throws VcsException {
     GeneralCommandLine cli = createCommandLine();
     cli.addParameter("status");
+    if (myShowAllFiles)
+      cli.addParameter("-A");
+    if (myHideStatus)
+      cli.addParameter("-n");
     cli.addParameter("--rev");
     String from = myFromId;
-    if (from == null) from = "0";
+    if (from == null)
+      from = "0";
     String to = myToId;
-    if (to == null) to = "0";
+    if (to == null)
+      to = "0";
     cli.addParameter(from + ":" + to);
     CommandResult res = runCommand(cli);
     return parseFiles(res.getStdout());
   }
 
-  public static List<ModifiedFile> parseFiles(final String stdout) {
-    List<ModifiedFile> result = new ArrayList<ModifiedFile>();
+  private List<FileStatus> parseFiles(@NotNull String stdout) {
+    List<FileStatus> result = new ArrayList<FileStatus>();
     String[] lines = stdout.split("\n");
-    for (String line: lines) {
-      if (line.length() == 0) continue;
-      char modifier = line.charAt(0);
-      String path = line.substring(2);
-      ModifiedFile.Status status = toStatus(modifier);
-      if (status == ModifiedFile.Status.UNKNOWN) continue;
-      result.add(new ModifiedFile(status, path));
+    for (String line : lines) {
+      if (isEmpty(line))
+        continue;
+      FileStatus fileStatus = parseLine(line);
+      if (!myHideStatus && fileStatus.getStatus() == Status.UNKNOWN)
+        continue;
+      result.add(fileStatus);
     }
     return result;
   }
 
-  public static ModifiedFile.Status toStatus(final char modifier) {
-    switch (modifier) {
-      case 'A': return ModifiedFile.Status.ADDED;
-      case 'M': return ModifiedFile.Status.MODIFIED;
-      case 'R': return ModifiedFile.Status.REMOVED;
-      default: return ModifiedFile.Status.UNKNOWN;
-    }
+  @NotNull
+  private FileStatus parseLine(@NotNull String line) {
+    if (myHideStatus)
+      return new FileStatus(Status.UNKNOWN, line);
+    char modifier = line.charAt(0);
+    String path = line.substring(2);
+    Status status = Status.makeStatus(modifier);
+    return new FileStatus(status, path);
   }
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial-server/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/ListFilesSupport.java	Fri May 11 15:21:35 2012 +0400
@@ -0,0 +1,63 @@
+package jetbrains.buildServer.buildTriggers.vcs.mercurial;
+
+import jetbrains.buildServer.buildTriggers.vcs.mercurial.command.Settings;
+import jetbrains.buildServer.vcs.ListDirectChildrenPolicy;
+import jetbrains.buildServer.vcs.VcsException;
+import jetbrains.buildServer.vcs.VcsFileData;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import static com.intellij.openapi.util.text.StringUtil.isEmpty;
+
+/**
+ * @author dmitry.neverov
+ */
+public class ListFilesSupport implements ListDirectChildrenPolicy {
+
+  private final MercurialVcsSupport myVcs;
+  private final Settings mySettings;
+  private final HgRepo myRepo;
+
+  public ListFilesSupport(@NotNull MercurialVcsSupport vcs,
+                          @NotNull Settings settings,
+                          @NotNull HgRepo repo) {
+    myVcs = vcs;
+    mySettings = settings;
+    myRepo = repo;
+  }
+
+  @NotNull
+  public List<VcsFileData> listFiles(@NotNull String dir) throws VcsException {
+    myVcs.syncRepository(mySettings);
+    String dirPath = isEmpty(dir) || dir.endsWith("/") ? dir : dir + "/";
+    return listFilesIn(dirPath);
+  }
+
+
+  @NotNull
+  private List<VcsFileData> listFilesIn(@NotNull String dir) throws VcsException {
+    List<VcsFileData> result = new ArrayList<VcsFileData>();
+    for (String file : myRepo.listFiles()) {
+      String canonicalFile = makeCanonical(file);
+      if (!canonicalFile.startsWith(dir))
+        continue;
+      String relativePath = canonicalFile.substring(dir.length());
+      int idx = relativePath.indexOf("/");
+      if (idx >= 0) {
+        result.add(new VcsFileData(relativePath.substring(0, idx), true));
+      } else {
+        result.add(new VcsFileData(relativePath, false));
+      }
+    }
+    return result;
+  }
+
+
+  @NotNull
+  private String makeCanonical(@NotNull String file) {
+    return file;
+  }
+
+}
--- a/mercurial-server/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/MercurialVcsSupport.java	Fri May 11 12:10:16 2012 +0400
+++ b/mercurial-server/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/MercurialVcsSupport.java	Fri May 11 15:21:35 2012 +0400
@@ -119,9 +119,9 @@
     }
   }
 
-  private List<VcsChange> toVcsChanges(final List<ModifiedFile> modifiedFiles, String prevVer, String curVer, CheckoutRules rules) {
+  private List<VcsChange> toVcsChanges(final List<FileStatus> modifiedFiles, String prevVer, String curVer, CheckoutRules rules) {
     List<VcsChange> files = new ArrayList<VcsChange>();
-    for (ModifiedFile mf: modifiedFiles) {
+    for (FileStatus mf: modifiedFiles) {
       final String path = rules.map(mf.getPath());
       if (shouldInclude(path))
         files.add(toVcsChange(mf, prevVer, curVer, path));
@@ -133,7 +133,7 @@
     return path != null;
   }
 
-  private VcsChange toVcsChange(ModifiedFile mf, String prevVer, String curVer, String mappedPath) {
+  private VcsChange toVcsChange(FileStatus mf, String prevVer, String curVer, String mappedPath) {
     String normalizedPath = PathUtil.normalizeSeparator(mf.getPath());
     VcsChangeInfo.Type changeType = getChangeType(mf.getStatus());
     if (changeType == null) {
@@ -143,7 +143,7 @@
     return new VcsChange(changeType, mf.getStatus().getName(), normalizedPath, mappedPath, prevVer, curVer);
   }
 
-  private VcsChangeInfo.Type getChangeType(final ModifiedFile.Status status) {
+  private VcsChangeInfo.Type getChangeType(final Status status) {
     switch (status) {
       case ADDED:return VcsChangeInfo.Type.ADDED;
       case MODIFIED:return VcsChangeInfo.Type.CHANGED;
@@ -289,10 +289,10 @@
   private void buildIncrementalPatch(final Settings settings, @NotNull final ChangeSet fromVer, @NotNull final ChangeSet toVer, final PatchBuilder builder, final CheckoutRules checkoutRules)
     throws VcsException, IOException {
     HgRepo repo = createRepo(settings);
-    List<ModifiedFile> modifiedFiles = repo.status().fromRevision(fromVer).toRevision(toVer).call();
+    List<FileStatus> modifiedFiles = repo.status().fromRevision(fromVer).toRevision(toVer).call();
     List<String> notDeletedFiles = new ArrayList<String>();
-    for (ModifiedFile f: modifiedFiles) {
-      if (f.getStatus() != ModifiedFile.Status.REMOVED) {
+    for (FileStatus f: modifiedFiles) {
+      if (f.getStatus() != Status.REMOVED) {
         notDeletedFiles.add(f.getPath());
       }
     }
@@ -302,11 +302,11 @@
 
     File parentDir = repo.cat().files(notDeletedFiles).atRevision(toVer).call();
     try {
-      for (ModifiedFile f: modifiedFiles) {
+      for (FileStatus f: modifiedFiles) {
         String mappedPath = checkoutRules.map(f.getPath());
         if (mappedPath == null) continue; // skip
         final File virtualFile = new File(mappedPath);
-        if (f.getStatus() == ModifiedFile.Status.REMOVED) {
+        if (f.getStatus() == Status.REMOVED) {
           builder.deleteFile(virtualFile, true);
         } else {
           File realFile = new File(parentDir, f.getPath());
@@ -420,7 +420,7 @@
     }
   }
 
-  private void syncRepository(final Settings settings) throws VcsException {
+  public void syncRepository(final Settings settings) throws VcsException {
     File workingDir = getWorkingDir(settings);
     lockWorkDir(workingDir);
     HgRepo repo = createRepo(settings);
@@ -786,4 +786,16 @@
     repositoryProperties.put(Constants.REPOSITORY_PROP, rootProperties.get(Constants.REPOSITORY_PROP));
     return repositoryProperties;
   }
+
+
+  @Override
+  public ListFilesPolicy getListFilesPolicy(@NotNull VcsRoot root) {
+    try {
+      Settings settings = createSettings(root);
+      HgRepo repo = createRepo(settings);
+      return new ListFilesSupport(this, settings, repo);
+    } catch (VcsException e) {
+      return null;
+    }
+  }
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/ListFilesTest.java	Fri May 11 15:21:35 2012 +0400
@@ -0,0 +1,90 @@
+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.ListDirectChildrenPolicy;
+import jetbrains.buildServer.vcs.VcsFileData;
+import jetbrains.buildServer.vcs.VcsRoot;
+import org.jetbrains.annotations.NotNull;
+import org.jmock.Mockery;
+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.LocalRepositoryUtil.copyRepository;
+import static jetbrains.buildServer.buildTriggers.vcs.mercurial.VcsRootBuilder.vcsRoot;
+import static org.testng.AssertJUnit.assertNotNull;
+import static org.testng.AssertJUnit.assertTrue;
+
+/**
+ * @author dmitry.neverov
+ */
+@Test
+public class ListFilesTest {
+
+  static {
+    Logger.setFactory(new Log4jFactory());
+  }
+
+  private MercurialVcsSupport myVcs;
+  private VcsRoot myRoot;
+
+  @BeforeMethod
+  protected void setUp() throws Exception {
+    TempFiles tempFiles = new TempFiles();
+    Mockery context = new Mockery();
+    ServerPluginConfig myPluginConfig = new ServerPluginConfigBuilder()
+            .cachesDir(tempFiles.createTempDir())
+            .build();
+    myVcs = Util.createMercurialServerSupport(context, myPluginConfig);
+
+    File repo = tempFiles.createTempDir();
+    copyRepository(new File("mercurial-tests/testData/rep1"), repo);
+    myRoot = vcsRoot().withUrl(repo.getAbsolutePath()).build();
+  }
+
+
+  public void should_support_list_files() throws Exception {
+    assertNotNull(myVcs.getListFilesPolicy(myRoot));
+  }
+
+
+  public void list_root_dir() throws Exception {
+    ListDirectChildrenPolicy policy = getListFilesPolicy();
+    List<VcsFileData> files = policy.listFiles("");
+    assertTrue(files.contains(dir("dir1")));
+    assertTrue(files.contains(dir("dir with space")));
+    assertTrue(files.contains(file("file.txt")));
+  }
+
+
+  public void list_subdir() throws Exception {
+    ListDirectChildrenPolicy policy = getListFilesPolicy();
+    List<VcsFileData> files = policy.listFiles("dir1");
+    assertTrue(files.contains(file("file1.txt")));
+    assertTrue(files.contains(file("file3.txt")));
+    assertTrue(files.contains(dir("subdir")));
+
+    files = policy.listFiles("dir1/subdir/");
+    assertTrue(files.contains(file("file2.txt")));
+  }
+
+  @NotNull
+  private ListDirectChildrenPolicy getListFilesPolicy() {
+    ListDirectChildrenPolicy listFilesPolicy = (ListDirectChildrenPolicy) myVcs.getListFilesPolicy(myRoot);
+    assert listFilesPolicy != null;
+    return listFilesPolicy;
+  }
+
+  private VcsFileData dir(@NotNull String path) {
+    return new VcsFileData(path, true);
+  }
+
+  private VcsFileData file(@NotNull String path) {
+    return new VcsFileData(path, false);
+  }
+
+}
--- a/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/LogCommandTest.java	Fri May 11 12:10:16 2012 +0400
+++ b/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/LogCommandTest.java	Fri May 11 15:21:35 2012 +0400
@@ -110,22 +110,22 @@
     List<ChangeSet> csets = runLog(fromId, toId);
     assertEquals(3, csets.size());
 
-    List<ModifiedFile> files = csets.get(0).getModifiedFiles();
+    List<FileStatus> files = csets.get(0).getModifiedFiles();
     assertEquals(1, files.size());
-    ModifiedFile file = files.get(0);
-    assertEquals(ModifiedFile.Status.ADDED, file.getStatus());
+    FileStatus file = files.get(0);
+    assertEquals(Status.ADDED, file.getStatus());
     assertEquals("dir1/file4.txt", file.getPath());
 
     files = csets.get(1).getModifiedFiles();
     assertEquals(1, files.size());
     file = files.get(0);
-    assertEquals(ModifiedFile.Status.REMOVED, file.getStatus());
+    assertEquals(Status.REMOVED, file.getStatus());
     assertEquals("dir1/file4.txt", file.getPath());
 
     files = csets.get(2).getModifiedFiles();
     assertEquals(1, files.size());
     file = files.get(0);
-    assertEquals(ModifiedFile.Status.MODIFIED, file.getStatus());
+    assertEquals(Status.MODIFIED, file.getStatus());
     assertEquals("dir1/file3.txt", file.getPath());
   }
 
--- a/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/StatusCommandTest.java	Fri May 11 12:10:16 2012 +0400
+++ b/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/StatusCommandTest.java	Fri May 11 15:21:35 2012 +0400
@@ -27,34 +27,34 @@
 public class StatusCommandTest extends BaseCommandTestCase {
   public void testAddedFile() throws IOException, VcsException {
     setRepository("mercurial-tests/testData/rep1", true);
-    List<ModifiedFile> files = runStatus("9875b412a788", "1d446e82d356");
+    List<FileStatus> files = runStatus("9875b412a788", "1d446e82d356");
     assertEquals(1, files.size());
-    ModifiedFile md = files.get(0);
-    assertEquals(ModifiedFile.Status.ADDED, md.getStatus());
+    FileStatus md = files.get(0);
+    assertEquals(Status.ADDED, md.getStatus());
     assertEquals("dir1/file3.txt", md.getPath().replace(File.separatorChar, '/'));
   }
 
   public void testRemovedFile() throws IOException, VcsException {
     setRepository("mercurial-tests/testData/rep1", true);
-    List<ModifiedFile> files = runStatus("7209b1f1d793", "9522278aa38d");
+    List<FileStatus> files = runStatus("7209b1f1d793", "9522278aa38d");
     assertEquals(1, files.size());
-    ModifiedFile md = files.get(0);
-    assertEquals(ModifiedFile.Status.REMOVED, md.getStatus());
+    FileStatus md = files.get(0);
+    assertEquals(Status.REMOVED, md.getStatus());
     assertEquals("dir1/file4.txt", md.getPath().replace(File.separatorChar, '/'));
   }
 
   public void testModifiedFile() throws IOException, VcsException {
     setRepository("mercurial-tests/testData/rep1", true);
-    List<ModifiedFile> files = runStatus("9522278aa38d", "b06a290a363b");
+    List<FileStatus> files = runStatus("9522278aa38d", "b06a290a363b");
     assertEquals(1, files.size());
-    ModifiedFile md = files.get(0);
-    assertEquals(ModifiedFile.Status.MODIFIED, md.getStatus());
+    FileStatus md = files.get(0);
+    assertEquals(Status.MODIFIED, md.getStatus());
     assertEquals("dir1/file3.txt", md.getPath().replace(File.separatorChar, '/'));
   }
 
-  private List<ModifiedFile> runStatus(final String fromId, final String toId) throws IOException, VcsException {
-    return runCommand(new CommandExecutor<List<ModifiedFile>>() {
-      public List<ModifiedFile> execute(@NotNull final Settings settings, @NotNull final File workingDir) throws VcsException {
+  private List<FileStatus> runStatus(final String fromId, final String toId) throws IOException, VcsException {
+    return runCommand(new CommandExecutor<List<FileStatus>>() {
+      public List<FileStatus> execute(@NotNull final Settings settings, @NotNull final File workingDir) throws VcsException {
         return new StatusCommand(settings.getHgCommandPath(), workingDir, settings.getAuthSettings())
                 .fromRevision(fromId)
                 .toRevision(toId)
--- a/mercurial-tests/src/testng.xml	Fri May 11 12:10:16 2012 +0400
+++ b/mercurial-tests/src/testng.xml	Fri May 11 15:21:35 2012 +0400
@@ -23,6 +23,7 @@
       <class name="jetbrains.buildServer.buildTriggers.vcs.mercurial.CleanupTest"/>
       <class name="jetbrains.buildServer.buildTriggers.vcs.mercurial.MercurialResetCacheHandlerTest"/>
       <class name="jetbrains.buildServer.buildTriggers.vcs.mercurial.AgentMirrorCleanerTest"/>
+      <class name="jetbrains.buildServer.buildTriggers.vcs.mercurial.ListFilesTest"/>
     </classes>
   </test>
 </suite>