| /* |
| * 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 com.intellij.openapi.vcs.changes; |
| |
| 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.ContainerUtil; |
| 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.NotNull; |
| import org.jetbrains.annotations.Nullable; |
| |
| import java.util.*; |
| |
| /** |
| * @author max |
| * @author yole |
| */ |
| public class VcsDirtyScopeImpl extends VcsModifiableDirtyScope { |
| 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; |
| private final ProjectLevelVcsManager myVcsManager; |
| private final AbstractVcs myVcs; |
| private VcsDirtyScopeModifier myVcsDirtyScopeModifier; |
| private boolean myWasEverythingDirty; |
| |
| public VcsDirtyScopeImpl(final AbstractVcs vcs, final Project project) { |
| myProject = project; |
| myVcs = vcs; |
| myVcsManager = ProjectLevelVcsManager.getInstance(project); |
| myWasEverythingDirty = false; |
| myVcsDirtyScopeModifier = new VcsDirtyScopeModifier() { |
| @Override |
| public Collection<VirtualFile> getAffectedVcsRoots() { |
| return Collections.unmodifiableCollection(myDirtyDirectoriesRecursively.keySet()); |
| } |
| |
| @Override |
| public Iterator<FilePath> getDirtyFilesIterator() { |
| 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 ContainerUtil.concatIterators(iteratorList); |
| } |
| |
| @NotNull |
| @Override |
| public Iterator<FilePath> getDirtyDirectoriesIterator(final VirtualFile root) { |
| final THashSet<FilePath> filePaths = myDirtyDirectoriesRecursively.get(root); |
| if (filePaths != null) { |
| return filePaths.iterator(); |
| } |
| return ContainerUtil.emptyIterator(); |
| } |
| |
| @Override |
| 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(); |
| } |
| } |
| } |
| }; |
| } |
| |
| @Override |
| public Collection<VirtualFile> getAffectedContentRoots() { |
| return myAffectedContentRoots; |
| } |
| |
| @Override |
| public Collection<VirtualFile> getAffectedContentRootsWithCheck() { |
| if (myVcs.allowsNestedRoots()) { |
| final ProjectLevelVcsManager vcsManager = ProjectLevelVcsManager.getInstance(myVcs.getProject()); |
| final VirtualFile[] roots = vcsManager.getRootsUnderVcs(myVcs); |
| |
| final Set<VirtualFile> result = new HashSet<VirtualFile>(myAffectedContentRoots); |
| for (VirtualFile root : roots) { |
| for (VirtualFile dir : myDirtyDirectoriesRecursively.keySet()) { |
| if (VfsUtilCore.isAncestor(dir, root, true)) { |
| result.add(root); |
| } |
| } |
| } |
| return new SmartList<VirtualFile>(result); |
| } |
| return myAffectedContentRoots; |
| } |
| |
| @Override |
| public Project getProject() { |
| return myProject; |
| } |
| |
| @Override |
| public AbstractVcs getVcs() { |
| return myVcs; |
| } |
| |
| @Override |
| public Set<FilePath> getDirtyFiles() { |
| 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)); |
| } |
| } |
| } |
| } |
| return result; |
| } |
| |
| @Override |
| public Set<FilePath> getDirtyFilesNoExpand() { |
| final THashSet<FilePath> paths = new THashSet<FilePath>(); |
| for (THashSet<FilePath> filePaths : myDirtyFiles.values()) { |
| paths.addAll(filePaths); |
| } |
| return paths; |
| } |
| |
| @Override |
| public Set<FilePath> getRecursivelyDirtyDirectories() { |
| THashSet<FilePath> result = new THashSet<FilePath>(); |
| for(THashSet<FilePath> dirsByRoot: myDirtyDirectoriesRecursively.values()) { |
| result.addAll(dirsByRoot); |
| } |
| return result; |
| } |
| |
| @Override |
| public boolean isRecursivelyDirty(final VirtualFile vf) { |
| for(THashSet<FilePath> dirsByRoot: myDirtyDirectoriesRecursively.values()) { |
| for (FilePath dir : dirsByRoot) { |
| final VirtualFile dirVf = dir.getVirtualFile(); |
| if (dirVf != null) { |
| if (VfsUtilCore.isAncestor(dirVf, vf, false)) { |
| return true; |
| } |
| } |
| } |
| } |
| 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() { |
| @Override |
| 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 = MultiMap.createSet(); |
| 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(); |
| } |
| }, new PairProcessor<FileOrDir, FileOrDir>() { |
| @Override |
| public boolean process(FileOrDir parent, FileOrDir child) { |
| if (parent.myRecursive) { |
| return true; |
| } |
| // if under non-recursive dirty dir, generally do not remove child with one exception... |
| if (child.myRecursive || child.myPath.isDirectory()) { |
| return false; |
| } |
| // only if dir non-recursively + non-recursive file child -> can be truncated to dir only |
| return Comparing.equal(child.myPath.getParentPath(), parent.myPath); |
| } |
| }); |
| 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 |
| * entries are dropped from scope. |
| * |
| * @param newcomer a new directory to add |
| */ |
| @Override |
| public void addDirtyDirRecursively(final FilePath newcomer) { |
| ApplicationManager.getApplication().runReadAction(new Runnable() { |
| @Override |
| public void run() { |
| final VirtualFile vcsRoot = myVcsManager.getVcsRootFor(newcomer); |
| if (vcsRoot == null) return; |
| myAffectedContentRoots.add(vcsRoot); |
| |
| for (Map.Entry<VirtualFile, THashSet<FilePath>> entry : myDirtyFiles.entrySet()) { |
| final VirtualFile groupRoot = entry.getKey(); |
| if (groupRoot != null && 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(); |
| } |
| } |
| } |
| } |
| } |
| |
| THashSet<FilePath> dirsByRoot = myDirtyDirectoriesRecursively.get(vcsRoot); |
| if (dirsByRoot == null) { |
| dirsByRoot = new THashSet<FilePath>(); |
| myDirtyDirectoriesRecursively.put(vcsRoot, dirsByRoot); |
| } |
| else { |
| for (Iterator<FilePath> it = dirsByRoot.iterator(); it.hasNext();) { |
| FilePath oldBoy = it.next(); |
| if (newcomer.isUnder(oldBoy, false)) { |
| return; |
| } |
| |
| if (oldBoy.isUnder(newcomer, false)) { |
| it.remove(); |
| } |
| } |
| } |
| |
| dirsByRoot.add(newcomer); |
| } |
| }); |
| } |
| |
| /** |
| * Add dirty file to the scope. Note that file is not added if its ancestor was added as dirty recursively or if its parent is in already |
| * in the dirty scope. Also immediate non-directory children are removed from the set of dirty files. |
| * |
| * @param newcomer a file or directory added to the dirty scope. |
| */ |
| @Override |
| public void addDirtyFile(final FilePath newcomer) { |
| ApplicationManager.getApplication().runReadAction(new Runnable() { |
| @Override |
| public void run() { |
| final VirtualFile vcsRoot = myVcsManager.getVcsRootFor(newcomer); |
| myAffectedContentRoots.add(vcsRoot); |
| |
| THashSet<FilePath> dirsByRoot = myDirtyDirectoriesRecursively.get(vcsRoot); |
| if (dirsByRoot != null) { |
| for (FilePath oldBoy : dirsByRoot) { |
| if (newcomer.isUnder(oldBoy, false)) { |
| return; |
| } |
| } |
| } |
| |
| 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.isEmpty()) { |
| VirtualFile parent = newcomer.getVirtualFileParent(); |
| if (parent != null && dirtyFiles.contains(new FilePathImpl(parent))) { |
| return; |
| } |
| dirtyFiles.add(newcomer); |
| } |
| } |
| } |
| }); |
| } |
| |
| @Override |
| public void iterate(final Processor<FilePath> iterator) { |
| if (myProject.isDisposed()) return; |
| |
| for (VirtualFile root : myAffectedContentRoots) { |
| THashSet<FilePath> dirsByRoot = myDirtyDirectoriesRecursively.get(root); |
| if (dirsByRoot != null) { |
| for (FilePath dir : dirsByRoot) { |
| final VirtualFile vFile = dir.getVirtualFile(); |
| if (vFile != null && vFile.isValid()) { |
| myVcsManager.iterateVcsRoot(vFile, iterator); |
| } |
| } |
| } |
| } |
| |
| 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)); |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| @Override |
| public void iterateExistingInsideScope(Processor<VirtualFile> processor) { |
| if (myProject.isDisposed()) return; |
| |
| for (VirtualFile root : myAffectedContentRoots) { |
| THashSet<FilePath> dirsByRoot = myDirtyDirectoriesRecursively.get(root); |
| if (dirsByRoot != null) { |
| for (FilePath dir : dirsByRoot) { |
| final VirtualFile vFile = obtainVirtualFile(dir); |
| if (vFile != null && vFile.isValid()) { |
| myVcsManager.iterateVfUnderVcsRoot(vFile, processor); |
| } |
| } |
| } |
| } |
| |
| for (VirtualFile root : myAffectedContentRoots) { |
| final THashSet<FilePath> files = myDirtyFiles.get(root); |
| if (files != null) { |
| for (FilePath file : files) { |
| VirtualFile vFile = obtainVirtualFile(file); |
| if (vFile != null && vFile.isValid()) { |
| processor.process(vFile); |
| if (vFile.isDirectory()) { |
| for (VirtualFile child : vFile.getChildren()) { |
| processor.process(child); |
| } |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| @Nullable |
| private static VirtualFile obtainVirtualFile(FilePath file) { |
| VirtualFile vFile = file.getVirtualFile(); |
| return vFile == null ? VfsUtil.findFileByIoFile(file.getIOFile(), false) : vFile; |
| } |
| |
| @Override |
| public boolean isEmpty() { |
| return myDirtyDirectoriesRecursively.isEmpty() && myDirtyFiles.isEmpty(); |
| } |
| |
| @Override |
| public boolean belongsTo(final FilePath path, final Consumer<AbstractVcs> vcsConsumer) { |
| return ApplicationManager.getApplication().runReadAction(new Computable<Boolean>() { |
| @Override |
| public Boolean compute() { |
| if (myProject.isDisposed()) return Boolean.FALSE; |
| final VcsRoot rootObject = myVcsManager.getVcsRootObjectFor(path); |
| if (vcsConsumer != null && rootObject != null) { |
| vcsConsumer.consume(rootObject.getVcs()); |
| } |
| if (rootObject == null || rootObject.getVcs() != myVcs) { |
| return Boolean.FALSE; |
| } |
| |
| 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) { |
| for (FilePath filePath : dirsByRoot) { |
| if (path.isUnder(filePath, false)) return Boolean.TRUE; |
| } |
| } |
| } |
| } |
| } |
| |
| if (!myDirtyFiles.isEmpty()) { |
| FilePath parent; |
| VirtualFile vParent = path.getVirtualFileParent(); |
| if (vParent != null && vParent.isValid()) { |
| parent = new FilePathImpl(vParent); |
| } |
| else { |
| parent = FilePathImpl.create(path.getIOFile().getParentFile()); |
| } |
| return isInDirtyFiles(path) || isInDirtyFiles(parent); |
| } |
| |
| return Boolean.FALSE; |
| } |
| }).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); |
| } |
| |
| @Override @NonNls |
| public String toString() { |
| @NonNls StringBuilder result = new StringBuilder("VcsDirtyScope["); |
| if (!myDirtyFiles.isEmpty()) { |
| result.append(" files="); |
| for (THashSet<FilePath> paths : myDirtyFiles.values()) { |
| for (FilePath file : paths) { |
| result.append(file).append(" "); |
| } |
| } |
| } |
| if (!myDirtyDirectoriesRecursively.isEmpty()) { |
| result.append(" dirs="); |
| for(THashSet<FilePath> dirsByRoot: myDirtyDirectoriesRecursively.values()) { |
| for(FilePath file: dirsByRoot) { |
| result.append(file).append(" "); |
| } |
| } |
| } |
| result.append("affected roots="); |
| for (VirtualFile contentRoot : myAffectedContentRoots) { |
| result.append(contentRoot.getPath()).append(" "); |
| } |
| result.append("affected roots DISCLOSED="); |
| for (VirtualFile contentRoot : getAffectedContentRootsWithCheck()) { |
| result.append(contentRoot.getPath()).append(" "); |
| } |
| result.append("]"); |
| return result.toString(); |
| } |
| |
| @Override |
| public VcsDirtyScopeModifier getModifier() { |
| return myVcsDirtyScopeModifier; |
| } |
| |
| @Override |
| public boolean wasEveryThingDirty() { |
| return myWasEverythingDirty; |
| } |
| |
| @Override |
| public void setWasEverythingDirty(boolean wasEverythingDirty) { |
| myWasEverythingDirty = wasEverythingDirty; |
| } |
| } |