Merge remote branch 'origin/master'
authorirengrig <Irina.Chernushina@jetbrains.com>
Wed, 5 Sep 2012 14:19:51 +0000 (18:19 +0400)
committerirengrig <Irina.Chernushina@jetbrains.com>
Wed, 5 Sep 2012 14:19:51 +0000 (18:19 +0400)
16 files changed:
platform/util/src/com/intellij/openapi/util/io/FileUtil.java
platform/util/src/com/intellij/util/PairProcessor.java
platform/util/src/com/intellij/util/Processor.java
platform/util/testSrc/com/intellij/openapi/util/io/FileUtilTest.java
platform/vcs-api/src/com/intellij/openapi/vcs/changes/ChangesUtil.java
platform/vcs-api/src/com/intellij/openapi/vcs/changes/VcsDirtyScopeModifier.java
platform/vcs-impl/src/com/intellij/openapi/vcs/changes/ChangeListManagerImpl.java
platform/vcs-impl/src/com/intellij/openapi/vcs/changes/Scopes.java
platform/vcs-impl/src/com/intellij/openapi/vcs/changes/VcsDirtyScopeImpl.java
platform/vcs-impl/src/com/intellij/openapi/vcs/changes/VcsGuess.java
platform/vcs-impl/src/com/intellij/openapi/vcs/impl/ProjectLevelVcsManagerImpl.java
plugins/cvs/cvs-plugin/src/com/intellij/cvsSupport2/changeBrowser/CvsCommittedChangesProvider.java
plugins/git4idea/src/git4idea/status/GitChangeProvider.java
plugins/git4idea/tests/git4idea/tests/GitChangeProviderNestedRepositoriesTest.java [new file with mode: 0644]
plugins/svn4idea/src/org/jetbrains/idea/svn/treeConflict/MergeFromTheirsResolver.java
plugins/svn4idea/src/org/jetbrains/idea/svn/treeConflict/SvnTreeConflictResolver.java

index 580f2442419b262b03cd3ade98fe6dbb06d272b0..cc0ddb7cde360953e323ab21e517591008db9aeb 100644 (file)
@@ -19,11 +19,9 @@ import com.intellij.CommonBundle;
 import com.intellij.openapi.diagnostic.Logger;
 import com.intellij.openapi.util.SystemInfo;
 import com.intellij.openapi.util.text.StringUtil;
-import com.intellij.util.ArrayUtil;
-import com.intellij.util.ObjectUtils;
-import com.intellij.util.Processor;
-import com.intellij.util.SystemProperties;
+import com.intellij.util.*;
 import com.intellij.util.containers.ContainerUtil;
+import com.intellij.util.containers.Convertor;
 import com.intellij.util.io.URLUtil;
 import com.intellij.util.text.CaseInsensitiveStringHashingStrategy;
 import gnu.trove.TObjectHashingStrategy;
@@ -119,6 +117,10 @@ public class FileUtil extends FileUtilRt {
       return false;
     }
 
+    return isCanonicalAncestor(strict, ancestorPath, filePath);
+  }
+
+  private static boolean isCanonicalAncestor(boolean strict, String ancestorPath, String filePath) {
     boolean startsWith = SystemInfo.isFileSystemCaseSensitive ? StringUtil.startsWith(filePath, ancestorPath)
                                                               : StringUtil.startsWithIgnoreCase(filePath, ancestorPath);
     if (!startsWith) {
@@ -129,6 +131,39 @@ public class FileUtil extends FileUtilRt {
            !strict && filePath.length() == ancestorPath.length();
   }
 
+  public static<T> Collection<T> removeAncestors(final Collection<T> files, final Convertor<T, String> convertor,
+                                                 final PairProcessor<String, T> removeProcessor) {
+    if (files.isEmpty()) return files;
+    final TreeMap<String, T> paths = new TreeMap<String, T>();
+    for (T file : files) {
+      final String path = convertor.convert(file);
+      assert path != null;
+      final String canonicalPath = toCanonicalPath(path);
+      paths.put(canonicalPath, file);
+    }
+    final List<Map.Entry<String, T>> ordered = new ArrayList<Map.Entry<String, T>>(paths.entrySet());
+    final List<T> result = new ArrayList<T>(ordered.size());
+    result.add(ordered.get(0).getValue());
+    for (int i = 1; i < ordered.size(); i++) {
+      final Map.Entry<String, T> entry = ordered.get(i);
+      final String child = entry.getKey();
+      boolean parentNotFound = true;
+      for (int j = i - 1; j >= 0; j--) {
+        // possible parents
+        final String parent = ordered.get(j).getKey();
+        if (parent == null) continue;
+        if (isCanonicalAncestor(false, parent, child) && removeProcessor.process(child, entry.getValue())) {
+          parentNotFound = false;
+          break;
+        }
+      }
+      if (parentNotFound) {
+        result.add(entry.getValue());
+      }
+    }
+    return result;
+  }
+
   /**
    * Get parent for the file. The method correctly
    * processes "." and ".." in file names. The name
index dac50a18b0282c6d70122bbf84601893fede482b..4b744aec292220fe61612c4739b6880a1007369c 100644 (file)
@@ -20,5 +20,11 @@ package com.intellij.util;
  * @author Gregory.Shrago
  */
 public interface PairProcessor<S, T> {
+  PairProcessor TRUE = new PairProcessor() {
+    @Override
+    public boolean process(Object o, Object o1) {
+      return true;
+    }
+  };
   boolean process(S s, T t);
 }
index fa610d63bba89b72a65967646612ae4a85830e22..0ede9dd5d4b726e7c39f53aeef1a5e38d104aa8d 100644 (file)
@@ -20,6 +20,12 @@ package com.intellij.util;
  * @see com.intellij.util.CommonProcessors
  */
 public interface Processor<T> {
+  Processor TRUE = new Processor() {
+    @Override
+    public boolean process(Object o) {
+      return true;
+    }
+  };
   /**
    * @param t consequently takes value of each element of the set this processor is passed to for processing.
    * @return {@code true} to continue processing or {@code false} to stop.
index 6560b6eff73a4ae1aba5dc6e0b18ed7f5e96dd6a..014285e6a30cd9a0cbd69df5ace1b7019cc274d4 100644 (file)
 package com.intellij.openapi.util.io;
 
 import com.intellij.openapi.util.SystemInfo;
+import com.intellij.util.PairProcessor;
+import com.intellij.util.containers.Convertor;
+import org.junit.Assert;
 import org.junit.Test;
 
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
+import java.util.Arrays;
+import java.util.Collection;
+
+import static org.junit.Assert.*;
 
 /**
  * @author Roman Shevchenko
@@ -76,4 +80,12 @@ public class FileUtilTest {
 
     assertEquals(!SystemInfo.isFileSystemCaseSensitive, FileUtil.isAncestor("/a/b/c", "/a/B/c/d", true));
   }
+
+  @Test
+  public void testRemoveAncestors() throws Exception {
+    final String[] arr = {"/a/b/c", "/a", "/a/b", "/d/e", "/b/c", "/a/d", "/b/c/ttt", "/a/ewqeuq"};
+    final String[] expectedResult = {"/a","/b/c","/d/e"};
+    final Collection<String> result = FileUtil.removeAncestors(Arrays.asList(arr), Convertor.SELF, PairProcessor.TRUE);
+    Assert.assertArrayEquals(expectedResult, result.toArray(new String[result.size()]));
+  }
 }
index e779e65c99df248cacb7f76ec2ab10640cf2fe82..ee31feab97c64089cb03c12e6b326c93ca49217f 100644 (file)
@@ -235,7 +235,31 @@ public class ChangesUtil {
     }
   }
 
+  public static VirtualFile findValidParentAccurately(final FilePath filePath) {
+    if (filePath.getVirtualFile() != null) return filePath.getVirtualFile();
+    final LocalFileSystem lfs = LocalFileSystem.getInstance();
+    VirtualFile result = lfs.findFileByIoFile(filePath.getIOFile());
+    if (result != null) return result;
+    if (! ApplicationManager.getApplication().isReadAccessAllowed()) {
+      result = lfs.refreshAndFindFileByIoFile(filePath.getIOFile());
+      if (result != null) return result;
+    }
+    return getValidParentUnderReadAction(filePath);
+  }
+
+  private static VirtualFile getValidParentUnderReadAction(final FilePath filePath) {
+    return ApplicationManager.getApplication().runReadAction(new Computable<VirtualFile>() {
+      public VirtualFile compute() {
+        return findValidParent(filePath);
+      }
+    });
+  }
+
+  /**
+   * @deprecated use {@link #findValidParentAccurately(com.intellij.openapi.vcs.FilePath)}
+   */
   @Nullable
+  @Deprecated
   public static VirtualFile findValidParent(FilePath file) {
     ApplicationManager.getApplication().assertReadAccessAllowed();
     VirtualFile parent = file.getVirtualFile();
@@ -244,8 +268,9 @@ public class ChangesUtil {
     }
     if (parent == null) {
       File ioFile = file.getIOFile();
+      final LocalFileSystem lfs = LocalFileSystem.getInstance();
       do {
-        parent = LocalFileSystem.getInstance().findFileByIoFile(ioFile);
+        parent = lfs.findFileByIoFile(ioFile);
         if (parent != null) break;
         ioFile = ioFile.getParentFile();
         if (ioFile == null) return null;
index aded6a2ac216dd04f5febd05f4e8be68dc4c8dba..b97305c1a0a3e2ed376b65a5ce278c496d4689a3 100644 (file)
@@ -30,5 +30,5 @@ public interface VcsDirtyScopeModifier {
   Collection<VirtualFile> getAffectedVcsRoots();
   @Nullable
   Iterator<FilePath> getDirtyDirectoriesIterator(VirtualFile root);
-  void recheckDirtyDirKeys();
+  void recheckDirtyKeys();
 }
index 34a705fa6e66c5058a9e3700ab8ef36e813b95e4..36d03cd5f7d43db4b8f688b1b0683eb0ed930325 100644 (file)
@@ -356,7 +356,7 @@ public class ChangeListManagerImpl extends ChangeListManagerEx implements Projec
                 }
               }
             }
-            modifier.recheckDirtyDirKeys();
+            modifier.recheckDirtyKeys();
             if (scope.isEmpty()) {
               iterator.remove();
             }
index 8cfbb9a0a86a5404f8a0cfdae1d2154eb07861dc..46c81132f6c902487da1e8d13de450879d6d344d 100644 (file)
@@ -20,6 +20,7 @@ import com.intellij.openapi.vcs.*;
 import com.intellij.openapi.vcs.impl.DefaultVcsRootPolicy;
 import com.intellij.openapi.vfs.LocalFileSystem;
 import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.util.containers.MultiMap;
 import org.jetbrains.annotations.NotNull;
 
 import java.util.*;
@@ -70,12 +71,26 @@ public class Scopes {
       return;
     }
     final Collection<FilePathUnderVcs> dirs = dirt.getDirsForVcs();
+    final Collection<FilePathUnderVcs> files = dirt.getFilesForVcs();
+
+    final MultiMap<AbstractVcs, FilePath> filesMap = new MultiMap<AbstractVcs, FilePath>();
+    final MultiMap<AbstractVcs, FilePath> dirsMap = new MultiMap<AbstractVcs, FilePath>();
+
     for (FilePathUnderVcs dir : dirs) {
-      getScope(dir.getVcs()).addDirtyDirRecursively(dir.getPath());
+      dirsMap.putValue(dir.getVcs(), dir.getPath());
     }
-    final Collection<FilePathUnderVcs> files = dirt.getFilesForVcs();
     for (FilePathUnderVcs file : files) {
-      getScope(file.getVcs()).addDirtyFile(file.getPath());
+      filesMap.putValue(file.getVcs(), file.getPath());
+    }
+    final Set<AbstractVcs> keys = new HashSet<AbstractVcs>(filesMap.keySet());
+    keys.addAll(dirsMap.keySet());
+    for (AbstractVcs key : keys) {
+      Collection<FilePath> dirPaths = dirsMap.get(key);
+      dirPaths = dirPaths == null ? Collections.<FilePath>emptyList() : dirPaths;
+      Collection<FilePath> filePaths = filesMap.get(key);
+      filePaths = filePaths == null ? Collections.<FilePath>emptyList() : filePaths;
+
+      getScope(key).addDirtyData(dirPaths, filePaths);
     }
   }
 
index 0fc2d79bc21b7b9d26c06ce1ae186e70ae908e2b..9a69037a262a0c88e0167f7340c0b757c4638e33 100644 (file)
@@ -20,13 +20,18 @@ import com.intellij.openapi.application.ApplicationManager;
 import com.intellij.openapi.project.Project;
 import com.intellij.openapi.util.Comparing;
 import com.intellij.openapi.util.Computable;
+import com.intellij.openapi.util.io.FileUtil;
 import com.intellij.openapi.vcs.*;
 import com.intellij.openapi.vfs.VfsUtil;
 import com.intellij.openapi.vfs.VfsUtilCore;
 import com.intellij.openapi.vfs.VirtualFile;
 import com.intellij.util.Consumer;
+import com.intellij.util.PairProcessor;
 import com.intellij.util.Processor;
 import com.intellij.util.SmartList;
+import com.intellij.util.containers.CompositeIterator;
+import com.intellij.util.containers.Convertor;
+import com.intellij.util.containers.MultiMap;
 import gnu.trove.THashSet;
 import org.jetbrains.annotations.NonNls;
 import org.jetbrains.annotations.Nullable;
@@ -38,7 +43,7 @@ import java.util.*;
  * @author yole
  */
 public class VcsDirtyScopeImpl extends VcsModifiableDirtyScope {
-  private final Set<FilePath> myDirtyFiles = new THashSet<FilePath>();
+  private final Map<VirtualFile, THashSet<FilePath>> myDirtyFiles = new HashMap<VirtualFile, THashSet<FilePath>>();
   private final Map<VirtualFile, THashSet<FilePath>> myDirtyDirectoriesRecursively = new HashMap<VirtualFile, THashSet<FilePath>>();
   private final Set<VirtualFile> myAffectedContentRoots = new THashSet<VirtualFile>();
   private final Project myProject;
@@ -60,7 +65,14 @@ public class VcsDirtyScopeImpl extends VcsModifiableDirtyScope {
 
       @Override
       public Iterator<FilePath> getDirtyFilesIterator() {
-        return myDirtyFiles.iterator();
+        if (myDirtyFiles.isEmpty()) {
+          return Collections.<FilePath>emptyList().iterator();
+        }
+        final ArrayList<Iterator<FilePath>> iteratorList = new ArrayList<Iterator<FilePath>>(myDirtyFiles.size());
+        for (THashSet<FilePath> paths : myDirtyFiles.values()) {
+          iteratorList.add(paths.iterator());
+        }
+        return new CompositeIterator<FilePath>(iteratorList);
       }
 
       @Nullable
@@ -74,8 +86,13 @@ public class VcsDirtyScopeImpl extends VcsModifiableDirtyScope {
       }
 
       @Override
-      public void recheckDirtyDirKeys() {
-        for (Iterator<THashSet<FilePath>> iterator = myDirtyDirectoriesRecursively.values().iterator(); iterator.hasNext();) {
+      public void recheckDirtyKeys() {
+        recheckMap(myDirtyDirectoriesRecursively);
+        recheckMap(myDirtyFiles);
+      }
+
+      private void recheckMap(Map<VirtualFile, THashSet<FilePath>> map) {
+        for (Iterator<THashSet<FilePath>> iterator = map.values().iterator(); iterator.hasNext();) {
           final THashSet<FilePath> next = iterator.next();
           if (next.isEmpty()) {
             iterator.remove();
@@ -116,12 +133,17 @@ public class VcsDirtyScopeImpl extends VcsModifiableDirtyScope {
   }
 
   public Set<FilePath> getDirtyFiles() {
-    final THashSet<FilePath> result = new THashSet<FilePath>(myDirtyFiles);
-    for(FilePath filePath: myDirtyFiles) {
-      VirtualFile vFile = filePath.getVirtualFile();
-      if (vFile != null && vFile.isValid() && vFile.isDirectory()) {
-        for(VirtualFile child: vFile.getChildren()) {
-          result.add(new FilePathImpl(child));
+    final THashSet<FilePath> result = new THashSet<FilePath>();
+    for (THashSet<FilePath> paths : myDirtyFiles.values()) {
+      result.addAll(paths);
+    }
+    for (THashSet<FilePath> paths : myDirtyFiles.values()) {
+      for (FilePath filePath : paths) {
+        VirtualFile vFile = filePath.getVirtualFile();
+        if (vFile != null && vFile.isValid() && vFile.isDirectory()) {
+          for(VirtualFile child: vFile.getChildren()) {
+            result.add(new FilePathImpl(child));
+          }
         }
       }
     }
@@ -129,7 +151,11 @@ public class VcsDirtyScopeImpl extends VcsModifiableDirtyScope {
   }
 
   public Set<FilePath> getDirtyFilesNoExpand() {
-    return new THashSet<FilePath>(myDirtyFiles);
+    final THashSet<FilePath> paths = new THashSet<FilePath>();
+    for (THashSet<FilePath> filePaths : myDirtyFiles.values()) {
+      paths.addAll(filePaths);
+    }
+    return paths;
   }
 
   public Set<FilePath> getRecursivelyDirtyDirectories() {
@@ -155,6 +181,92 @@ public class VcsDirtyScopeImpl extends VcsModifiableDirtyScope {
     return false;
   }
 
+  private static class FileOrDir {
+    private final FilePath myPath;
+    private final boolean myRecursive;
+
+    private FileOrDir(FilePath path, boolean recursive) {
+      myPath = path;
+      myRecursive = recursive;
+    }
+  }
+
+  public void addDirtyData(final Collection<FilePath> dirs, final Collection<FilePath> files) {
+    ApplicationManager.getApplication().runReadAction(new Runnable() {
+      public void run() {
+        final HashSet<FilePath> newFiles = new HashSet<FilePath>(files);
+        newFiles.removeAll(dirs); // if the same dir is added recursively and not recursively, prefer recursive mark
+
+        final MultiMap<VirtualFile, FileOrDir> perRoot = new MultiMap<VirtualFile, FileOrDir>() {
+          @Override
+          protected Collection<FileOrDir> createCollection() {
+            return new THashSet<FileOrDir>();
+          }
+        };
+        for (Map.Entry<VirtualFile, THashSet<FilePath>> entry : myDirtyDirectoriesRecursively.entrySet()) {
+          newFiles.removeAll(entry.getValue()); // if the same dir is added recursively and not recursively, prefer recursive mark
+          for (FilePath path : entry.getValue()) {
+            perRoot.putValue(entry.getKey(), new FileOrDir(path, true));
+          }
+        }
+
+        for (Map.Entry<VirtualFile, THashSet<FilePath>> entry : myDirtyFiles.entrySet()) {
+          for (FilePath path : entry.getValue()) {
+            perRoot.putValue(entry.getKey(), new FileOrDir(path, false));
+          }
+        }
+
+        for (FilePath dir : dirs) {
+          addFilePathToMap(perRoot, dir, true);
+        }
+        for (FilePath file : newFiles) {
+          addFilePathToMap(perRoot, file, false);
+        }
+
+        for (Map.Entry<VirtualFile, Collection<FileOrDir>> entry : perRoot.entrySet()) {
+          final Collection<FileOrDir> set = entry.getValue();
+          final Collection<FileOrDir> newCollection = FileUtil.removeAncestors(set, new Convertor<FileOrDir, String>() {
+            @Override
+            public String convert(FileOrDir o) {
+              return o.myPath.getPath();
+            }
+          }, PairProcessor.TRUE);
+          set.retainAll(newCollection);
+        }
+
+        myAffectedContentRoots.addAll(perRoot.keySet());
+        for (Map.Entry<VirtualFile, Collection<FileOrDir>> entry : perRoot.entrySet()) {
+          final VirtualFile root = entry.getKey();
+          final THashSet<FilePath> curFiles = new THashSet<FilePath>();
+          final THashSet<FilePath> curDirs = new THashSet<FilePath>();
+          final Collection<FileOrDir> value = entry.getValue();
+          for (FileOrDir fileOrDir : value) {
+            if (fileOrDir.myRecursive) {
+              curDirs.add(fileOrDir.myPath);
+            } else {
+              curFiles.add(fileOrDir.myPath);
+            }
+          }
+          // no clear is necessary since no root can disappear
+          // also, we replace contents, so here's no merging
+          if (! curDirs.isEmpty()) {
+            myDirtyDirectoriesRecursively.put(root, curDirs);
+          }
+          if (! curFiles.isEmpty()) {
+            myDirtyFiles.put(root, curFiles);
+          }
+        }
+      }
+    });
+  }
+
+  private void addFilePathToMap(MultiMap<VirtualFile, FileOrDir> perRoot, FilePath dir, final boolean recursively) {
+    final VirtualFile vcsRoot = myVcsManager.getVcsRootFor(dir);
+    if (vcsRoot != null) {
+      perRoot.putValue(vcsRoot, new FileOrDir(dir, recursively));
+    }
+  }
+
   /**
    * Add dirty directory recursively. If there are already dirty entries
    * that are descendants or ancestors for the added directory, the contained
@@ -169,10 +281,18 @@ public class VcsDirtyScopeImpl extends VcsModifiableDirtyScope {
         if (vcsRoot == null) return;
         myAffectedContentRoots.add(vcsRoot);
 
-        for (Iterator<FilePath> it = myDirtyFiles.iterator(); it.hasNext();) {
-          FilePath oldBoy = it.next();
-          if (oldBoy.isUnder(newcomer, false)) {
-            it.remove();
+        for (Map.Entry<VirtualFile, THashSet<FilePath>> entry : myDirtyFiles.entrySet()) {
+          final VirtualFile groupRoot = entry.getKey();
+          if (VfsUtilCore.isAncestor(vcsRoot, groupRoot, false)) {
+            final THashSet<FilePath> files = entry.getValue();
+            if (files != null) {
+              for (Iterator<FilePath> it = files.iterator(); it.hasNext();) {
+                FilePath oldBoy = it.next();
+                if (oldBoy.isUnder(newcomer, false)) {
+                  it.remove();
+                }
+              }
+            }
           }
         }
 
@@ -222,22 +342,27 @@ public class VcsDirtyScopeImpl extends VcsModifiableDirtyScope {
           }
         }
 
-        if (newcomer.isDirectory()) {
-          final List<FilePath> files = new ArrayList<FilePath>(myDirtyFiles);
-          for (FilePath oldBoy : files) {
-            if (!oldBoy.isDirectory() && Comparing.equal(oldBoy.getVirtualFileParent(), newcomer.getVirtualFile())) {
-              myDirtyFiles.remove(oldBoy);
+        final THashSet<FilePath> dirtyFiles = myDirtyFiles.get(vcsRoot);
+        if (dirtyFiles == null) {
+          final THashSet<FilePath> set = new THashSet<FilePath>();
+          set.add(newcomer);
+          myDirtyFiles.put(vcsRoot, set);
+        } else {
+          if (newcomer.isDirectory()) {
+            for (Iterator<FilePath> iterator = dirtyFiles.iterator(); iterator.hasNext(); ) {
+              final FilePath oldBoy = iterator.next();
+              if (!oldBoy.isDirectory() && Comparing.equal(oldBoy.getVirtualFileParent(), newcomer.getVirtualFile())) {
+                iterator.remove();
+              }
             }
+          } else if (dirtyFiles.size() > 0) {
+            VirtualFile parent = newcomer.getVirtualFileParent();
+            if (parent != null && dirtyFiles.contains(new FilePathImpl(parent))) {
+              return;
+            }
+            dirtyFiles.add(newcomer);
           }
         }
-        else if (myDirtyFiles.size() > 0) {
-          VirtualFile parent = newcomer.getVirtualFileParent();
-          if (parent != null && myDirtyFiles.contains(new FilePathImpl(parent))) {
-            return;
-          }
-        }
-
-        myDirtyFiles.add(newcomer);
       }
     });
   }
@@ -257,12 +382,17 @@ public class VcsDirtyScopeImpl extends VcsModifiableDirtyScope {
       }
     }
 
-    for (FilePath file : myDirtyFiles) {
-      iterator.process(file);
-      final VirtualFile vFile = file.getVirtualFile();
-      if (vFile != null && vFile.isValid() && vFile.isDirectory()) {
-        for (VirtualFile child : vFile.getChildren()) {
-          iterator.process(new FilePathImpl(child));
+    for (VirtualFile root : myAffectedContentRoots) {
+      final THashSet<FilePath> files = myDirtyFiles.get(root);
+      if (files != null) {
+        for (FilePath file : files) {
+          iterator.process(file);
+          final VirtualFile vFile = file.getVirtualFile();
+          if (vFile != null && vFile.isValid() && vFile.isDirectory()) {
+            for (VirtualFile child : vFile.getChildren()) {
+              iterator.process(new FilePathImpl(child));
+            }
+          }
         }
       }
     }
@@ -284,14 +414,19 @@ public class VcsDirtyScopeImpl extends VcsModifiableDirtyScope {
       }
     }
 
-    for (FilePath file : myDirtyFiles) {
-      if (file.getVirtualFile() != null) {
-        processor.process(file.getVirtualFile());
-      }
-      final VirtualFile vFile = file.getVirtualFile();
-      if (vFile != null && vFile.isValid() && vFile.isDirectory()) {
-        for (VirtualFile child : vFile.getChildren()) {
-          processor.process(child);
+    for (VirtualFile root : myAffectedContentRoots) {
+      final THashSet<FilePath> files = myDirtyFiles.get(root);
+      if (files != null) {
+        for (FilePath file : files) {
+          if (file.getVirtualFile() != null) {
+            processor.process(file.getVirtualFile());
+          }
+          final VirtualFile vFile = file.getVirtualFile();
+          if (vFile != null && vFile.isValid() && vFile.isDirectory()) {
+            for (VirtualFile child : vFile.getChildren()) {
+              processor.process(child);
+            }
+          }
         }
       }
     }
@@ -318,6 +453,10 @@ public class VcsDirtyScopeImpl extends VcsModifiableDirtyScope {
         final VirtualFile vcsRoot = rootObject.getPath();
         if (vcsRoot != null) {
           for (VirtualFile contentRoot : myAffectedContentRoots) {
+            // since we don't know exact dirty mechanics, maybe we have 3 nested mappings like:
+            // /root -> vcs1, /root/child -> vcs2, /root/child/inner -> vcs1, and we have file /root/child/inner/file,
+            // mapping is detected as vcs1 with root /root/child/inner, but we could possibly have in scope
+            // "affected root" -> /root with scope = /root recursively
             if (VfsUtilCore.isAncestor(contentRoot, vcsRoot, false)) {
               THashSet<FilePath> dirsByRoot = myDirtyDirectoriesRecursively.get(contentRoot);
               if (dirsByRoot != null) {
@@ -338,7 +477,7 @@ public class VcsDirtyScopeImpl extends VcsModifiableDirtyScope {
           else {
             parent = FilePathImpl.create(path.getIOFile().getParentFile());
           }
-          if (myDirtyFiles.contains(parent) || myDirtyFiles.contains(path)) return Boolean.TRUE;
+          return isInDirtyFiles(path) || isInDirtyFiles(parent);
         }
 
         return Boolean.FALSE;
@@ -346,6 +485,15 @@ public class VcsDirtyScopeImpl extends VcsModifiableDirtyScope {
     }).booleanValue();
   }
 
+  private boolean isInDirtyFiles(final FilePath path) {
+    final VcsRoot rootObject = myVcsManager.getVcsRootObjectFor(path);
+    if (rootObject != null && myVcs.equals(rootObject.getVcs())) {
+      final THashSet<FilePath> files = myDirtyFiles.get(rootObject.getPath());
+      if (files != null && files.contains(path)) return true;
+    }
+    return false;
+  }
+
   @Override
   public boolean belongsTo(final FilePath path) {
     return belongsTo(path, null);
@@ -356,8 +504,10 @@ public class VcsDirtyScopeImpl extends VcsModifiableDirtyScope {
     @NonNls StringBuilder result = new StringBuilder("VcsDirtyScope[");
     if (myDirtyFiles.size() > 0) {
       result.append(" files=");
-      for(FilePath file: myDirtyFiles) {
-        result.append(file).append(" ");
+      for (THashSet<FilePath> paths : myDirtyFiles.values()) {
+        for (FilePath file : paths) {
+          result.append(file).append(" ");
+        }
       }
     }
     if (myDirtyDirectoriesRecursively.size() > 0) {
index 22ceca6ae259251cdd17569e8a8562e42d205fc0..f5abcf039ece2992582c92101c0543737a363843 100644 (file)
@@ -54,11 +54,7 @@ public class VcsGuess {
     if (filePath.isNonLocal()) {
       return null;
     }
-    final VirtualFile validParent = ApplicationManager.getApplication().runReadAction(new Computable<VirtualFile>() {
-      public VirtualFile compute() {
-        return ChangesUtil.findValidParent(filePath);
-      }
-    });
+    final VirtualFile validParent = ChangesUtil.findValidParentAccurately(filePath);
     if (validParent == null) {
       return null;
     }
index bad83f3c41811556e991862cb7e6cc72ed033a35..e9c27cc3fac4118e2a2c37252c40954249e2df9f 100644 (file)
@@ -257,12 +257,12 @@ public class ProjectLevelVcsManagerImpl extends ProjectLevelVcsManagerEx impleme
 
   @Nullable
   public AbstractVcs getVcsFor(final FilePath file) {
+    final VirtualFile vFile = ChangesUtil.findValidParentAccurately(file);
     return ApplicationManager.getApplication().runReadAction(new Computable<AbstractVcs>() {
       @Nullable
       public AbstractVcs compute() {
         if (!ApplicationManager.getApplication().isUnitTestMode() && !myProject.isInitialized()) return null;
         if (myProject.isDisposed()) throw new ProcessCanceledException();
-        VirtualFile vFile = ChangesUtil.findValidParent(file);
         if (vFile != null) {
           return getVcsFor(vFile);
         }
@@ -300,34 +300,24 @@ public class ProjectLevelVcsManagerImpl extends ProjectLevelVcsManagerEx impleme
 
   @Nullable
   public VirtualFile getVcsRootFor(final FilePath file) {
-    return ApplicationManager.getApplication().runReadAction(new Computable<VirtualFile>() {
-      @Nullable
-      public VirtualFile compute() {
-        if (myProject.isDisposed()) return null;
-        VirtualFile vFile = ChangesUtil.findValidParent(file);
-        if (vFile != null) {
-          return getVcsRootFor(vFile);
-        }
-        return null;
-      }
-    });
+    if (myProject.isDisposed()) return null;
+    VirtualFile vFile = ChangesUtil.findValidParentAccurately(file);
+    if (vFile != null) {
+      return getVcsRootFor(vFile);
+    }
+    return null;
   }
 
   @Override
   public VcsRoot getVcsRootObjectFor(final FilePath file) {
-    return ApplicationManager.getApplication().runReadAction(new Computable<VcsRoot>() {
-      @Nullable
-      public VcsRoot compute() {
-        if (myProject.isDisposed()) {
-          return null;
-        }
-        VirtualFile vFile = ChangesUtil.findValidParent(file);
-        if (vFile != null) {
-          return getVcsRootObjectFor(vFile);
-        }
-        return null;
-      }
-    });
+    if (myProject.isDisposed()) {
+      return null;
+    }
+    VirtualFile vFile = ChangesUtil.findValidParentAccurately(file);
+    if (vFile != null) {
+      return getVcsRootObjectFor(vFile);
+    }
+    return null;
   }
 
   public void unregisterVcs(AbstractVcs vcs) {
@@ -477,20 +467,15 @@ public class ProjectLevelVcsManagerImpl extends ProjectLevelVcsManagerEx impleme
 
   @Nullable
   public VcsDirectoryMapping getDirectoryMappingFor(final FilePath path) {
-    return ApplicationManager.getApplication().runReadAction(new Computable<VcsDirectoryMapping>() {
-      @Nullable
-      public VcsDirectoryMapping compute() {
-        VirtualFile vFile = ChangesUtil.findValidParent(path);
-        if (vFile != null) {
-          return myMappings.getMappingFor(vFile);
-        }
-        return null;
-      }
-    });
+    VirtualFile vFile = ChangesUtil.findValidParentAccurately(path);
+    if (vFile != null) {
+      return myMappings.getMappingFor(vFile);
+    }
+    return null;
   }
 
   public boolean hasExplicitMapping(final FilePath f) {
-    VirtualFile vFile = ChangesUtil.findValidParent(f);
+    VirtualFile vFile = ChangesUtil.findValidParentAccurately(f);
     if (vFile == null) return false;
     return hasExplicitMapping(vFile);
   }
index 35b5da869a113581fb8a93f25edaa66df4926b14..0455e8f63a44e73a6879f208355d42c57291080c 100644 (file)
@@ -20,12 +20,14 @@ import com.intellij.cvsSupport2.CvsUtil;
 import com.intellij.cvsSupport2.application.CvsEntriesManager;
 import com.intellij.cvsSupport2.connections.CvsEnvironment;
 import com.intellij.cvsSupport2.history.CvsRevisionNumber;
-import com.intellij.openapi.application.ApplicationManager;
 import com.intellij.openapi.cvsIntegration.CvsResult;
 import com.intellij.openapi.diagnostic.Logger;
 import com.intellij.openapi.progress.ProcessCanceledException;
 import com.intellij.openapi.project.Project;
-import com.intellij.openapi.util.*;
+import com.intellij.openapi.util.Comparing;
+import com.intellij.openapi.util.Pair;
+import com.intellij.openapi.util.Ref;
+import com.intellij.openapi.util.Trinity;
 import com.intellij.openapi.vcs.*;
 import com.intellij.openapi.vcs.actions.VcsContextFactory;
 import com.intellij.openapi.vcs.changes.ChangesUtil;
@@ -379,12 +381,7 @@ public class CvsCommittedChangesProvider implements CachingCommittedChangesProvi
       }
     }
     else {
-      final VirtualFile validParent = ApplicationManager.getApplication().runReadAction(new Computable<VirtualFile>() {
-        @Nullable
-        public VirtualFile compute() {
-          return ChangesUtil.findValidParent(filePath);
-        }
-      });
+      final VirtualFile validParent = ChangesUtil.findValidParentAccurately(filePath);
       if (validParent == null) return false;
       localTag = getDirectoryTag(validParent);
     }
index 4a5194e96896302a0df2fd2d9836043635c26b60..d750cc0fd415067252c1869ed6cedabac116a987 100644 (file)
@@ -19,9 +19,13 @@ import com.intellij.openapi.diagnostic.Logger;
 import com.intellij.openapi.fileEditor.FileDocumentManager;
 import com.intellij.openapi.progress.ProgressIndicator;
 import com.intellij.openapi.project.Project;
+import com.intellij.openapi.util.io.FileUtil;
 import com.intellij.openapi.vcs.*;
 import com.intellij.openapi.vcs.changes.*;
+import com.intellij.openapi.vfs.LocalFileSystem;
 import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.util.PairProcessor;
+import com.intellij.util.containers.Convertor;
 import git4idea.GitContentRevision;
 import git4idea.GitRevisionNumber;
 import git4idea.GitUtil;
@@ -32,10 +36,7 @@ import git4idea.config.GitVersion;
 import git4idea.config.GitVersionSpecialty;
 import org.jetbrains.annotations.NotNull;
 
-import java.util.Collection;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Set;
+import java.util.*;
 
 /**
  * Git repository change provider
@@ -70,16 +71,42 @@ public class GitChangeProvider implements ChangeProvider {
       return;
     }
 
-    final Collection<VirtualFile> affected = dirtyScope.getAffectedContentRootsWithCheck();
-    final Collection<VirtualFile> affectedContentRoots = dirtyScope.getAffectedContentRoots();
-    if (affectedContentRoots.size() != affected.size()) {
-      final Set<VirtualFile> set = new HashSet<VirtualFile>(affected);
-      set.removeAll(affectedContentRoots);
-      for (VirtualFile file : set) {
-        debug("adding git root for check: " + file.getPath());
-        ((VcsModifiableDirtyScope) dirtyScope).addDirtyDirRecursively(new FilePathImpl(file));
+    final LocalFileSystem lfs = LocalFileSystem.getInstance();
+    final Set<VirtualFile> rootsUnderGit = new HashSet<VirtualFile>(Arrays.asList(myVcsManager.getRootsUnderVcs(vcs)));
+    final Set<VirtualFile> inputColl = new HashSet<VirtualFile>(rootsUnderGit);
+    final Set<VirtualFile> existingInScope = new HashSet<VirtualFile>();
+    for (FilePath dir : dirtyScope.getRecursivelyDirtyDirectories()) {
+      VirtualFile vf = dir.getVirtualFile();
+      if (vf == null) {
+        vf = lfs.findFileByIoFile(dir.getIOFile());
+      }
+      if (vf == null) {
+        vf = lfs.refreshAndFindFileByIoFile(dir.getIOFile());
+      }
+      if (vf != null) {
+        existingInScope.add(vf);
+      } else {
+        PROFILE_LOG.error("Can not find virtual file for recursively dirty dir: " + dir.getIOFile().getPath());
       }
     }
+    inputColl.addAll(existingInScope);
+    FileUtil.removeAncestors(inputColl, new Convertor<VirtualFile, String>() {
+      @Override
+      public String convert(VirtualFile o) {
+        return o.getPath();
+      }
+    }, new PairProcessor<String, VirtualFile>() {
+                               @Override
+                               public boolean process(String s, VirtualFile file) {
+                                 if (! existingInScope.contains(file)) {
+                                   debug("adding git root for check: " + file.getPath());
+                                   ((VcsModifiableDirtyScope) dirtyScope).addDirtyDirRecursively(new FilePathImpl(file));
+                                 }
+                                 return true;
+                               }
+                             });
+
+    final Collection<VirtualFile> affected = dirtyScope.getAffectedContentRoots();
     Collection<VirtualFile> roots = GitUtil.gitRootsForPaths(affected);
 
     try {
diff --git a/plugins/git4idea/tests/git4idea/tests/GitChangeProviderNestedRepositoriesTest.java b/plugins/git4idea/tests/git4idea/tests/GitChangeProviderNestedRepositoriesTest.java
new file mode 100644 (file)
index 0000000..0f2ae6a
--- /dev/null
@@ -0,0 +1,105 @@
+/*
+ * Copyright 2000-2012 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 git4idea.tests;
+
+import com.intellij.openapi.vcs.ProjectLevelVcsManager;
+import com.intellij.openapi.vcs.VcsDirectoryMapping;
+import com.intellij.openapi.vcs.changes.Change;
+import com.intellij.openapi.vcs.changes.ChangeListManager;
+import com.intellij.openapi.vcs.changes.LocalChangeList;
+import com.intellij.openapi.vcs.changes.VcsDirtyScopeManager;
+import com.intellij.openapi.vfs.LocalFileSystem;
+import com.intellij.openapi.vfs.VirtualFile;
+import git4idea.GitVcs;
+import git4idea.test.GitTestUtil;
+import junit.framework.Assert;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import java.io.File;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Map;
+
+/**
+ * Created with IntelliJ IDEA.
+ * User: Irina.Chernushina
+ * Date: 9/5/12
+ * Time: 10:32 AM
+ */
+public class GitChangeProviderNestedRepositoriesTest extends GitChangeProviderTest {
+  private File myChildRepoDir;
+  private GitTestRepository myChildRepo;
+  private ProjectLevelVcsManager myVcsManager;
+  private ChangeListManager myChangeListManager;
+  private Map<String,VirtualFile> myChildFiles;
+  private VcsDirtyScopeManager myDirtyScopeManager;
+
+  @BeforeMethod
+  public void setUp() throws Exception {
+    final File repoRoot = myRepo.getRootDir();
+    myChildRepoDir = new File(repoRoot, "child");
+    myChildRepoDir.mkdir();
+    myChildRepo = GitTestRepository.init(myChildRepoDir);
+    myChildRepo.setName(MAIN_USER_NAME, MAIN_USER_EMAIL);
+    LocalFileSystem.getInstance().refreshAndFindFileByIoFile(myChildRepo.getRootDir());
+    myChildRepo.refresh();
+
+    myChildFiles = GitTestUtil.createFileStructure(myProject, myChildRepo, "in1.txt", "in2.txt", "dirr/inin1.txt", "dirr/inin2.txt");
+    myChildRepo.addCommit();
+    myChildRepo.refresh();
+
+    myVcsManager = ProjectLevelVcsManager.getInstance(myProject);
+    myVcsManager.setDirectoryMappings(Arrays.asList(new VcsDirectoryMapping(myRepo.getRootDir().getPath(), GitVcs.getKey().getName()),
+                                                    new VcsDirectoryMapping(myChildRepo.getRootDir().getPath(), GitVcs.getKey().getName())));
+    myChangeListManager = ChangeListManager.getInstance(myProject);
+    myDirtyScopeManager = VcsDirtyScopeManager.getInstance(myProject);
+  }
+
+  @Test
+  public void testReproduceChangeListsRotten() throws Exception {
+    editFileInCommand(myProject, myFiles.get("a.txt"), "123");
+    VirtualFile in1 = myChildFiles.get("in1.txt");
+    editFileInCommand(myProject, in1, "321");
+    VirtualFile in2 = myChildFiles.get("in2.txt");
+    editFileInCommand(myProject, in2, "321*");
+
+    myDirtyScopeManager.markEverythingDirty();
+    myChangeListManager.ensureUpToDate(false);
+
+    final LocalChangeList localChangeList = myChangeListManager.addChangeList("new", "new");
+    Change change1 = myChangeListManager.getChange(in1);
+    Change change2 = myChangeListManager.getChange(in2);
+    myChangeListManager.moveChangesTo(localChangeList, change1, change2);
+
+    myDirtyScopeManager.filesDirty(Collections.singletonList(in1), Collections.singletonList(myRepo.getVFRootDir()));
+    myChangeListManager.ensureUpToDate(false);
+    LocalChangeList list = myChangeListManager.getChangeList(in1);
+    Assert.assertNotNull(list);
+    Assert.assertEquals("new", list.getName());
+    LocalChangeList list2 = myChangeListManager.getChangeList(in2);
+    Assert.assertNotNull(list2);
+    Assert.assertEquals("new", list2.getName());
+    myDirtyScopeManager.markEverythingDirty();
+    myChangeListManager.ensureUpToDate(false);
+    list = myChangeListManager.getChangeList(in1);
+    Assert.assertNotNull(list);
+    Assert.assertEquals("new", list.getName());
+    list2 = myChangeListManager.getChangeList(in2);
+    Assert.assertNotNull(list2);
+    Assert.assertEquals("new", list2.getName());
+  }
+}
index 2fd33334bf7529339bda345f3f0043b9a7c17c50..a6ec49c793d796601512e8282e7676180d784615 100644 (file)
@@ -26,7 +26,6 @@ import com.intellij.openapi.ui.DialogWrapper;
 import com.intellij.openapi.ui.MessageType;
 import com.intellij.openapi.ui.Messages;
 import com.intellij.openapi.util.Comparing;
-import com.intellij.openapi.util.Computable;
 import com.intellij.openapi.util.io.FileUtil;
 import com.intellij.openapi.vcs.*;
 import com.intellij.openapi.vcs.changes.*;
@@ -92,12 +91,7 @@ public class MergeFromTheirsResolver {
     myCommittedRevision = revision;
     myOldFilePath = myChange.getBeforeRevision().getFile();
     myNewFilePath = myChange.getAfterRevision().getFile();
-    myBaseForPatch = ApplicationManager.getApplication().runReadAction(new Computable<VirtualFile>() {
-      @Override
-      public VirtualFile compute() {
-        return ChangesUtil.findValidParent(myNewFilePath);
-      }
-    });
+    myBaseForPatch = ChangesUtil.findValidParentAccurately(myNewFilePath);
     myOldPresentation = TreeConflictRefreshablePanel.filePath(myOldFilePath);
     myNewPresentation = TreeConflictRefreshablePanel.filePath(myNewFilePath);
 
index b36f7e532eea83cf7ebc2052b7a973dbfaca7323..7ba2fc5a7c5bbc64e111e30bd23c766fec52a4c0 100644 (file)
 package org.jetbrains.idea.svn.treeConflict;
 
 import com.intellij.history.LocalHistory;
-import com.intellij.openapi.application.ApplicationManager;
-import com.intellij.openapi.util.Computable;
 import com.intellij.openapi.util.io.FileUtil;
 import com.intellij.openapi.vcs.FilePath;
 import com.intellij.openapi.vcs.VcsException;
 import com.intellij.openapi.vcs.changes.ChangesUtil;
 import com.intellij.openapi.vcs.changes.VcsDirtyScopeManager;
 import com.intellij.openapi.vcs.history.VcsRevisionNumber;
-import com.intellij.openapi.vfs.LocalFileSystem;
 import com.intellij.openapi.vfs.VirtualFile;
 import org.jetbrains.annotations.Nullable;
 import org.jetbrains.idea.svn.SvnRevisionNumber;
@@ -71,12 +68,7 @@ public class SvnTreeConflictResolver {
   }
 
   private void pathDirty(final FilePath path) {
-    final VirtualFile validParent = ApplicationManager.getApplication().runReadAction(new Computable<VirtualFile>() {
-      @Override
-      public VirtualFile compute() {
-        return ChangesUtil.findValidParent(path);
-      }
-    });
+    final VirtualFile validParent = ChangesUtil.findValidParentAccurately(path);
     if (validParent == null) return;
     validParent.refresh(false, true);
     if (path.isDirectory()) {