git4idea: Fixed performance problem with checking for changes in big projects
authorConstantine Plotnikov <Constantine.Plotnikov@jetbrains.com>
Thu, 20 Aug 2009 12:06:59 +0000 (16:06 +0400)
committerConstantine Plotnikov <Constantine.Plotnikov@jetbrains.com>
Thu, 20 Aug 2009 12:06:59 +0000 (16:06 +0400)
plugins/git4idea/src/git4idea/changes/ChangeCollector.java
plugins/git4idea/src/git4idea/changes/GitChangeProvider.java

index 7d48b09d76b7e0d11c1cfd36c5d431711aa40911..78f03e8a7e4dc50692016f197eeb0bd1128503f2 100644 (file)
 package git4idea.changes;
 
 import com.intellij.openapi.project.Project;
+import com.intellij.openapi.vcs.FilePath;
 import com.intellij.openapi.vcs.FileStatus;
 import com.intellij.openapi.vcs.VcsException;
 import com.intellij.openapi.vcs.changes.Change;
+import com.intellij.openapi.vcs.changes.ChangeListManager;
 import com.intellij.openapi.vcs.changes.ContentRevision;
+import com.intellij.openapi.vcs.changes.VcsDirtyScope;
 import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.vcsUtil.VcsUtil;
 import git4idea.GitContentRevision;
 import git4idea.GitRevisionNumber;
 import git4idea.GitUtil;
@@ -35,6 +39,10 @@ import java.util.*;
  * cannot be got as a sum of stateless operations.
  */
 class ChangeCollector {
+  /**
+   * The dirty scope used in the collector
+   */
+  private VcsDirtyScope myDirtyScope;
   /**
    * a vcs root for changes
    */
@@ -67,10 +75,12 @@ class ChangeCollector {
   /**
    * A constructor
    *
-   * @param project a project
-   * @param vcsRoot a vcs root
+   * @param project    a project
+   * @param dirtyScope the dirty scope to check
+   * @param vcsRoot    a vcs root
    */
-  public ChangeCollector(final Project project, final VirtualFile vcsRoot) {
+  public ChangeCollector(final Project project, VcsDirtyScope dirtyScope, final VirtualFile vcsRoot) {
+    myDirtyScope = dirtyScope;
     myVcsRoot = vcsRoot;
     myProject = project;
   }
@@ -118,6 +128,76 @@ class ChangeCollector {
     myIsFailed = false;
   }
 
+  /**
+   * Collect dirty file paths
+   *
+   * @param includeChanges if true, previous changes are included in collection
+   * @return the set of dirty paths to check, the paths are automatically collapsed if the summary length more than limit
+   */
+  private Collection<FilePath> dirtyPaths(boolean includeChanges) {
+    // TODO collapse paths with common prefix
+    ArrayList<FilePath> paths = new ArrayList<FilePath>();
+    FilePath rootPath = VcsUtil.getFilePath(myVcsRoot.getPath(), true);
+    for (FilePath p : myDirtyScope.getRecursivelyDirtyDirectories()) {
+      addToPaths(rootPath, paths, p);
+    }
+    ArrayList<FilePath> candidatePaths = new ArrayList<FilePath>();
+    candidatePaths.addAll(myDirtyScope.getDirtyFilesNoExpand());
+    if (includeChanges) {
+      try {
+        ChangeListManager cm = ChangeListManager.getInstance(myProject);
+        for (Change c : cm.getChangesIn(myVcsRoot)) {
+          switch (c.getType()) {
+            case NEW:
+            case DELETED:
+            case MOVED:
+              if (c.getAfterRevision() != null) {
+                addToPaths(rootPath, paths, c.getAfterRevision().getFile());
+              }
+              if (c.getBeforeRevision() != null) {
+                addToPaths(rootPath, paths, c.getBeforeRevision().getFile());
+              }
+            default:
+              // do nothing
+          }
+        }
+      }
+      catch (Exception t) {
+        // ignore exceptions
+      }
+    }
+    for (FilePath p : candidatePaths) {
+      addToPaths(rootPath, paths, p);
+    }
+    return paths;
+  }
+
+  /**
+   * Add path to the collection of the paths to check for this vcs root
+   *
+   * @param root  the root path
+   * @param paths the existing paths
+   * @param toAdd the path to add
+   */
+  void addToPaths(FilePath root, Collection<FilePath> paths, FilePath toAdd) {
+    if (GitUtil.getGitRootOrNull(toAdd) != myVcsRoot) {
+      return;
+    }
+    if (root.isUnder(toAdd, true)) {
+      toAdd = root;
+    }
+    for (Iterator<FilePath> i = paths.iterator(); i.hasNext();) {
+      FilePath p = i.next();
+      if (p.isUnder(toAdd, true)) {
+        i.remove();
+      }
+      if (toAdd.isUnder(p, false)) {
+        return;
+      }
+    }
+    paths.add(toAdd);
+  }
+
   /**
    * Collect diff with head
    *
@@ -130,6 +210,7 @@ class ChangeCollector {
     handler.setSilent(true);
     handler.setStdoutSuppressed(true);
     handler.endOptions();
+    handler.addRelativePaths(dirtyPaths(true));
     try {
       String output = handler.run();
       GitChangeUtils.parseChanges(myProject, myVcsRoot, null, GitChangeUtils.loadRevision(myProject, myVcsRoot, "HEAD"), output, myChanges,
@@ -171,6 +252,7 @@ class ChangeCollector {
     handler.setSilent(true);
     handler.setNoSSH(true);
     handler.setStdoutSuppressed(true);
+    handler.addRelativePaths(dirtyPaths(false));
     // run handler and collect changes
     String list = handler.run();
     for (StringScanner sc = new StringScanner(list); sc.hasMoreData();) {
@@ -181,12 +263,19 @@ class ChangeCollector {
       char status = sc.peek();
       sc.skipChars(2);
       if ('?' == status) {
-        myUnversioned.add(myVcsRoot.findFileByRelativePath(GitUtil.unescapePath(sc.line())));
+        VirtualFile file = myVcsRoot.findFileByRelativePath(GitUtil.unescapePath(sc.line()));
+        if (GitUtil.gitRootOrNull(file) == myVcsRoot) {
+          myUnversioned.add(file);
+        }
       }
       else { //noinspection HardCodedStringLiteral
         if ('M' == status) {
           sc.boundedToken('\t');
           String file = GitUtil.unescapePath(sc.line());
+          VirtualFile vFile = myVcsRoot.findFileByRelativePath(GitUtil.unescapePath(sc.line()));
+          if (GitUtil.gitRootOrNull(vFile) != myVcsRoot) {
+            continue;
+          }
           if (!myUnmergedNames.add(file)) {
             continue;
           }
@@ -205,5 +294,4 @@ class ChangeCollector {
       }
     }
   }
-
 }
index d61e6420e89f4e77f0c8b7dc6d840fca5a9e0dc4..9ef77863a96d24d7072aa7c09a58ea2d9651fb99 100644 (file)
@@ -57,7 +57,7 @@ public class GitChangeProvider implements ChangeProvider {
                          final ChangeListManagerGate addGate) throws VcsException {
     Collection<VirtualFile> roots = GitUtil.gitRootsForPaths(dirtyScope.getAffectedContentRoots());
     for (VirtualFile root : roots) {
-      ChangeCollector c = new ChangeCollector(myProject, root);
+      ChangeCollector c = new ChangeCollector(myProject, dirtyScope, root);
       for (Change file : c.changes()) {
         builder.processChange(file, GitVcs.getKey());
       }