diff --git a/platform/vcs-api/src/com/intellij/openapi/vcs/update/FileGroup.java b/platform/vcs-api/src/com/intellij/openapi/vcs/update/FileGroup.java index 12e483269b1e..c4e809d1cbba 100644 --- a/platform/vcs-api/src/com/intellij/openapi/vcs/update/FileGroup.java +++ b/platform/vcs-api/src/com/intellij/openapi/vcs/update/FileGroup.java @@ -19,6 +19,7 @@ import com.intellij.openapi.util.*; import com.intellij.openapi.vcs.AbstractVcs; import com.intellij.openapi.vcs.FileStatus; import com.intellij.openapi.vcs.ProjectLevelVcsManager; +import com.intellij.openapi.vcs.VcsKey; import com.intellij.openapi.vcs.history.VcsRevisionNumber; import com.intellij.ui.SimpleTextAttributes; import com.intellij.vcsUtil.VcsUtil; @@ -91,10 +92,6 @@ public class FileGroup implements JDOMExternalizable { return mySupportsDeletion; } - public void add(@NotNull final String path) { - myFiles.add(new UpdatedFile(path)); - } - public void addError(@NotNull final String path, @NotNull final String error) { myErrorsMap.put(path, error); } @@ -103,8 +100,12 @@ public class FileGroup implements JDOMExternalizable { return myErrorsMap; } - public void add(@NotNull String path, @NotNull AbstractVcs vcs, @NotNull VcsRevisionNumber revision) { - myFiles.add(new UpdatedFile(path, vcs.getName(), revision.asString())); + public void add(@NotNull String path, @NotNull String vcsName, @Nullable VcsRevisionNumber revision) { + myFiles.add(new UpdatedFile(path, vcsName, revision == null ? "" : revision.asString())); + } + + public void add(@NotNull String path, @NotNull VcsKey vcsKey, @Nullable VcsRevisionNumber revision) { + myFiles.add(new UpdatedFile(path, vcsKey, revision == null ? "" : revision.asString())); } public void remove(String path) { @@ -128,6 +129,10 @@ public class FileGroup implements JDOMExternalizable { return files; } + public Collection getUpdatedFiles() { + return new ArrayList(myFiles); + } + public List> getFilesAndRevisions(ProjectLevelVcsManager vcsManager) { ArrayList> files = new ArrayList>(); for (UpdatedFile file : myFiles) { @@ -248,7 +253,7 @@ public class FileGroup implements JDOMExternalizable { public void setRevisions(final String path, final AbstractVcs vcs, final VcsRevisionNumber revision) { for (UpdatedFile file : myFiles) { if (file.getPath().startsWith(path)) { - file.setVcsName(vcs.getName()); + file.setVcsKey(vcs.getKeyInstanceMethod()); file.setRevision(revision.asString()); } } @@ -257,7 +262,7 @@ public class FileGroup implements JDOMExternalizable { } } - private static class UpdatedFile { + static class UpdatedFile { private final String myPath; private String myVcsName; private String myRevision; @@ -266,7 +271,13 @@ public class FileGroup implements JDOMExternalizable { myPath = path; } - public UpdatedFile(final String path, final String vcsName, final String revision) { + public UpdatedFile(final String path, @NotNull final VcsKey vcsKey, final String revision) { + myPath = path; + myVcsName = vcsKey.getName(); + myRevision = revision; + } + + private UpdatedFile(final String path, @NotNull String vcsName, final String revision) { myPath = path; myVcsName = vcsName; myRevision = revision; @@ -280,8 +291,8 @@ public class FileGroup implements JDOMExternalizable { return myVcsName; } - public void setVcsName(final String vcsName) { - myVcsName = vcsName; + public void setVcsKey(final VcsKey vcsKey) { + myVcsName = vcsKey.getName(); } public String getRevision() { diff --git a/platform/vcs-api/src/com/intellij/openapi/vcs/update/UpdatedFilesReverseSide.java b/platform/vcs-api/src/com/intellij/openapi/vcs/update/UpdatedFilesReverseSide.java index 47c5a05375a6..2ec80935436d 100644 --- a/platform/vcs-api/src/com/intellij/openapi/vcs/update/UpdatedFilesReverseSide.java +++ b/platform/vcs-api/src/com/intellij/openapi/vcs/update/UpdatedFilesReverseSide.java @@ -26,13 +26,6 @@ public class UpdatedFilesReverseSide { myFileIdx = new HashMap(); } - /** - * removes file from group that previously contained it - */ - public void addFileToGroup(final String groupId, final VirtualFile file, final DuplicateLevel duplicateLevel) { - addFileToGroup(groupId, file.getPresentableUrl(), duplicateLevel); - } - public boolean isEmpty() { return myFileIdx.isEmpty(); } @@ -41,12 +34,12 @@ public class UpdatedFilesReverseSide { return myGroupHolder.get(id); } - public void addFileToGroup(final String groupId, final String file, final DuplicateLevel duplicateLevel) { + public void addFileToGroup(final String groupId, final String file, final DuplicateLevel duplicateLevel, final String vcsName) { final FileGroup newGroup = myGroupHolder.get(groupId); - addFileToGroup(newGroup, file, duplicateLevel); + addFileToGroup(newGroup, file, duplicateLevel, vcsName); } - public void addFileToGroup(final FileGroup group, final String file, final DuplicateLevel duplicateLevel) { + public void addFileToGroup(final FileGroup group, final String file, final DuplicateLevel duplicateLevel, final String vcsName) { if (duplicateLevel.searchPreviousContainment(group.getId())) { final FileGroup oldGroup = myFileIdx.get(file); if (oldGroup != null) { @@ -57,7 +50,7 @@ public class UpdatedFilesReverseSide { } } - group.add(file); + group.add(file, vcsName, null); myFileIdx.put(file, group); } @@ -89,8 +82,8 @@ public class UpdatedFilesReverseSide { private void copyGroup(final Parent parent, final FileGroup from, final DuplicateLevel duplicateLevel) { final FileGroup to = createOrGet(parent, from); - for (String file : from.getFiles()) { - addFileToGroup(to, file, duplicateLevel); + for (FileGroup.UpdatedFile updatedFile : from.getUpdatedFiles()) { + addFileToGroup(to, updatedFile.getPath(), duplicateLevel, updatedFile.getVcsName()); } for (FileGroup fromChild : from.getChildren()) { copyGroup(new GroupParent(to), fromChild, duplicateLevel); diff --git a/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/ChangesOnServerTracker.java b/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/ChangesOnServerTracker.java new file mode 100644 index 000000000000..8bbec821f09a --- /dev/null +++ b/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/ChangesOnServerTracker.java @@ -0,0 +1,14 @@ +package com.intellij.openapi.vcs.changes; + +import com.intellij.openapi.util.Pair; +import com.intellij.openapi.vcs.AbstractVcs; +import com.intellij.openapi.vcs.VcsListener; + +import java.util.Collection; + +public interface ChangesOnServerTracker extends PlusMinus>, VcsListener { + // todo add vcs parameter??? + void invalidate(final Collection paths); + boolean isUpToDate(final Change change); + boolean updateStep(); +} diff --git a/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/RemoteRevisionsCache.java b/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/RemoteRevisionsCache.java index 9fa9aa1cbe4b..d2bd7ffae108 100644 --- a/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/RemoteRevisionsCache.java +++ b/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/RemoteRevisionsCache.java @@ -7,97 +7,85 @@ import com.intellij.openapi.components.ServiceManager; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.progress.ProcessCanceledException; import com.intellij.openapi.project.Project; -import com.intellij.openapi.util.Computable; import com.intellij.openapi.util.Disposer; import com.intellij.openapi.util.Pair; import com.intellij.openapi.vcs.*; import com.intellij.openapi.vcs.changes.ui.RemoteStatusChangeNodeDecorator; -import com.intellij.openapi.vcs.diff.ItemLatestState; -import com.intellij.openapi.vcs.diff.DiffProvider; -import com.intellij.openapi.vcs.history.VcsRevisionNumber; +import com.intellij.openapi.vcs.update.UpdateFilesHelper; +import com.intellij.openapi.vcs.update.UpdatedFiles; import com.intellij.openapi.vfs.LocalFileSystem; -import com.intellij.openapi.vfs.VirtualFile; import com.intellij.util.Alarm; import com.intellij.util.Consumer; import com.intellij.util.messages.Topic; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; -import java.io.File; -import java.util.*; +import java.util.Collection; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.Map; public class RemoteRevisionsCache implements PlusMinus>, VcsListener { private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.vcs.changes.RemoteRevisionsCache"); public static Topic REMOTE_VERSION_CHANGED = new Topic("REMOTE_VERSION_CHANGED", Runnable.class); - // every 5 minutes.. (time unit to check for server commits) - private static final long ourRottenPeriod = 300 * 1000; - private final Map> myData; - private final Map> myRefreshingQueues; - private final Map myLatestRevisionsMap; + private final RemoteRevisionsNumbersCache myRemoteRevisionsNumbersCache; + private final RemoteRevisionsStateCache myRemoteRevisionsStateCache; + private final ProjectLevelVcsManager myVcsManager; private final LocalFileSystem myLfs; - private final Object myLock; - private final RemoteStatusChangeNodeDecorator myChangeDecorator; - - public static final VcsRevisionNumber NOT_LOADED = new VcsRevisionNumber() { - public String asString() { - return "NOT_LOADED"; - } - - public int compareTo(VcsRevisionNumber o) { - if (o == this) return 0; - return -1; - } - }; - public static final VcsRevisionNumber UNKNOWN = new VcsRevisionNumber() { - public String asString() { - return "UNKNOWN"; - } - - public int compareTo(VcsRevisionNumber o) { - if (o == this) return 0; - return -1; - } - }; + private final Project myProject; + private final Object myLock; + private final Map myKinds; public static RemoteRevisionsCache getInstance(final Project project) { return ServiceManager.getService(project, RemoteRevisionsCache.class); } private RemoteRevisionsCache(final Project project) { - myLock = new Object(); - myData = new HashMap>(); - myRefreshingQueues = Collections.synchronizedMap(new HashMap>()); - myLatestRevisionsMap = new HashMap(); + myProject = project; myLfs = LocalFileSystem.getInstance(); + myLock = new Object(); + + myRemoteRevisionsNumbersCache = new RemoteRevisionsNumbersCache(myProject); + myRemoteRevisionsStateCache = new RemoteRevisionsStateCache(myProject); + myChangeDecorator = new RemoteStatusChangeNodeDecorator(this); myVcsManager = ProjectLevelVcsManager.getInstance(project); myVcsManager.addVcsListener(this); + myKinds = new HashMap(); Disposer.register(project, new Disposable() { public void dispose() { myVcsManager.removeVcsListener(RemoteRevisionsCache.this); } }); + updateKinds(); final MyRecursiveUpdateRequest request = new MyRecursiveUpdateRequest(project, new Runnable() { public void run() { - final List> list = new ArrayList>(); - synchronized (myLock) { - list.addAll(myRefreshingQueues.values()); - } - LOG.debug("queues refresh started, queues: " + list.size()); - for (LazyRefreshingSelfQueue queue : list) { - queue.updateStep(); + boolean somethingChanged = myRemoteRevisionsNumbersCache.updateStep(); + somethingChanged |= myRemoteRevisionsStateCache.updateStep(); + if (somethingChanged) { + myProject.getMessageBus().syncPublisher(REMOTE_VERSION_CHANGED).run(); } } }); request.start(); } + private void updateKinds() { + final VcsRoot[] roots = myVcsManager.getAllVcsRoots(); + synchronized (myLock) { + for (VcsRoot root : roots) { + final AbstractVcs vcs = root.vcs; + if (! myKinds.containsKey(vcs.getName())) { + myKinds.put(vcs.getName(), vcs.getRemoteDifferenceStrategy()); + } + } + } + } + private static class MyRecursiveUpdateRequest implements Runnable { private final Alarm mySimpleAlarm; private final SlowlyClosingAlarm myControlledAlarm; @@ -137,198 +125,68 @@ public class RemoteRevisionsCache implements PlusMinus } public void directoryMappingChanged() { - synchronized (myLock) { - final HashSet keys = new HashSet(myData.keySet()); - for (String key : keys) { - final Pair value = myData.get(key); - final VcsRoot storedVcsRoot = value.getFirst(); - final VirtualFile vf = myLfs.refreshAndFindFileByIoFile(new File(key)); - final AbstractVcs newVcs = (vf == null) ? null : myVcsManager.getVcsFor(vf); - - if (newVcs == null) { - myData.remove(key); - getQueue(storedVcsRoot).forceRemove(key); - } else { - final VirtualFile newRoot = myVcsManager.getVcsRootFor(vf); - final VcsRoot newVcsRoot = new VcsRoot(newVcs, newRoot); - if (! storedVcsRoot.equals(newVcsRoot)) { - switchVcs(storedVcsRoot, newVcsRoot, key); - } - } - } - } - } - - private void switchVcs(final VcsRoot oldVcsRoot, final VcsRoot newVcsRoot, final String key) { - synchronized (myLock) { - final LazyRefreshingSelfQueue oldQueue = getQueue(oldVcsRoot); - final LazyRefreshingSelfQueue newQueue = getQueue(newVcsRoot); - myData.put(key, new Pair(newVcsRoot, NOT_LOADED)); - oldQueue.forceRemove(key); - newQueue.addRequest(key); - } + updateKinds(); + myRemoteRevisionsNumbersCache.directoryMappingChanged(); + myRemoteRevisionsStateCache.directoryMappingChanged(); } public void plus(final Pair pair) { - LOG.debug("add " + pair.getFirst()); - // does not support - if (pair.getSecond().getDiffProvider() == null) return; - - final String key = pair.getFirst(); - final AbstractVcs newVcs = pair.getSecond(); - - final VirtualFile root = getRootForPath(key); - if (root == null) return; - - final VcsRoot vcsRoot = new VcsRoot(newVcs, root); - - synchronized (myLock) { - final Pair value = myData.get(key); - if (value == null) { - LOG.debug("adding new " + key); - final LazyRefreshingSelfQueue queue = getQueue(vcsRoot); - myData.put(key, new Pair(vcsRoot, NOT_LOADED)); - queue.addRequest(key); - } else if (! value.getFirst().equals(vcsRoot)) { - LOG.debug("switch vcs " + key); - switchVcs(value.getFirst(), vcsRoot, key); - } + final AbstractVcs vcs = pair.getSecond(); + if (RemoteDifferenceStrategy.ASK_TREE_PROVIDER.equals(vcs.getRemoteDifferenceStrategy())) { + myRemoteRevisionsStateCache.plus(pair); + } else { + myRemoteRevisionsNumbersCache.plus(pair); } } - public void invalidate(final String path) { + public void invalidate(final UpdatedFiles updatedFiles) { + final Map strategyMap; synchronized (myLock) { - final Pair pair = myData.remove(path); - if (pair != null) { - // vcs [root] seems to not change - final VcsRoot vcsRoot = pair.getFirst(); - final LazyRefreshingSelfQueue queue = getQueue(vcsRoot); - queue.forceRemove(path); - queue.addRequest(path); - myData.put(path, new Pair(vcsRoot, NOT_LOADED)); - } + strategyMap = new HashMap(myKinds); } - } + final Collection newForTree = new LinkedList(); + final Collection newForUsual = new LinkedList(); + UpdateFilesHelper.iterateAffectedFiles(updatedFiles, new Consumer>() { + public void consume(final Pair pair) { + final String vcsName = pair.getSecond(); + RemoteDifferenceStrategy strategy = strategyMap.get(vcsName); + if (strategy == null) { + final AbstractVcs vcs = myVcsManager.findVcsByName(vcsName); + if (vcs == null) return; + strategy = vcs.getRemoteDifferenceStrategy(); + } + if (RemoteDifferenceStrategy.ASK_TREE_PROVIDER.equals(strategy)) { + newForTree.add(pair.getFirst()); + } else { + newForUsual.add(pair.getFirst()); + } + } + }); - @Nullable - private VirtualFile getRootForPath(final String s) { - return myVcsManager.getVcsRootFor(new FilePathImpl(new File(s), false)); + myRemoteRevisionsStateCache.invalidate(newForTree); + myRemoteRevisionsNumbersCache.invalidate(newForUsual); } public void minus(Pair pair) { - LOG.debug("minus " + pair.getFirst()); - // does not support - if (pair.getSecond().getDiffProvider() == null) return; - final VirtualFile root = getRootForPath(pair.getFirst()); - if (root == null) return; - - final LazyRefreshingSelfQueue queue; - final String key = pair.getFirst(); - synchronized (myLock) { - LOG.debug("removing " + key); - queue = getQueue(new VcsRoot(pair.getSecond(), root)); - myData.remove(key); - } - queue.forceRemove(key); - } - - // +- - @NotNull - private LazyRefreshingSelfQueue getQueue(final VcsRoot vcsRoot) { - synchronized (myLock) { - LazyRefreshingSelfQueue queue = myRefreshingQueues.get(vcsRoot); - if (queue != null) return queue; - - queue = new LazyRefreshingSelfQueue(ourRottenPeriod, new MyShouldUpdateChecker(vcsRoot), new MyUpdater(vcsRoot)); - myRefreshingQueues.put(vcsRoot, queue); - return queue; + final AbstractVcs vcs = pair.getSecond(); + if (RemoteDifferenceStrategy.ASK_TREE_PROVIDER.equals(vcs.getRemoteDifferenceStrategy())) { + myRemoteRevisionsStateCache.minus(pair); + } else { + myRemoteRevisionsNumbersCache.minus(pair); } } - private class MyUpdater implements Consumer { - private final VcsRoot myVcsRoot; - - public MyUpdater(final VcsRoot vcsRoot) { - myVcsRoot = vcsRoot; + /** + * @return false if not up to date + */ + public boolean isUpToDate(final Change change) { + final AbstractVcs vcs = ChangesUtil.getVcsForChange(change, myProject); + final RemoteDifferenceStrategy strategy = vcs.getRemoteDifferenceStrategy(); + if (RemoteDifferenceStrategy.ASK_TREE_PROVIDER.equals(strategy)) { + return myRemoteRevisionsStateCache.isUpToDate(change); + } else { + return myRemoteRevisionsNumbersCache.isUpToDate(change); } - - public void consume(String s) { - LOG.debug("update for: " + s); - final VirtualFile vf = myLfs.refreshAndFindFileByIoFile(new File(s)); - final ItemLatestState state; - final DiffProvider diffProvider = myVcsRoot.vcs.getDiffProvider(); - if (vf == null) { - // doesnt matter if directory or not - state = diffProvider.getLastRevision(FilePathImpl.createForDeletedFile(new File(s), false)); - } else { - state = diffProvider.getLastRevision(vf); - } - final VcsRevisionNumber newNumber = state == null ? UNKNOWN : state.getNumber(); - - final Pair oldPair; - synchronized (myLock) { - oldPair = myData.get(s); - myData.put(s, new Pair(myVcsRoot, newNumber)); - } - - if ((oldPair == null) || (oldPair != null) && (oldPair.getSecond().compareTo(newNumber) != 0)) { - LOG.debug("refresh triggered by " + s); - myVcsRoot.vcs.getProject().getMessageBus().syncPublisher(REMOTE_VERSION_CHANGED).run(); - } - } - } - - private class MyShouldUpdateChecker implements Computable { - private final VcsRoot myVcsRoot; - - public MyShouldUpdateChecker(final VcsRoot vcsRoot) { - myVcsRoot = vcsRoot; - } - - public Boolean compute() { - final AbstractVcs vcs = myVcsRoot.vcs; - // won't be called in parallel for same vcs -> just synchronized map is ok - final String vcsName = vcs.getName(); - LOG.debug("should update for: " + vcsName + " root: " + myVcsRoot.path.getPath()); - final VcsRevisionNumber latestNew = vcs.getDiffProvider().getLatestCommittedRevision(myVcsRoot.path); - - final VcsRevisionNumber latestKnown = myLatestRevisionsMap.get(vcsName); - // not known - if (latestNew == null) return true; - if ((latestKnown == null) || (latestNew.compareTo(latestKnown) != 0)) { - myLatestRevisionsMap.put(vcsName, latestNew); - return true; - } - return false; - } - } - - public VcsRevisionNumber getNumber(final String path) { - synchronized (myLock) { - final Pair pair = myData.get(path); - return pair == null ? NOT_LOADED : pair.getSecond(); - } - } - - public boolean getState(final Change change) { - if (change.getBeforeRevision() != null && change.getAfterRevision() != null && (! change.isMoved()) && (! change.isRenamed())) { - // just edit - return getRevisionState(change.getBeforeRevision()); - } - return getRevisionState(change.getBeforeRevision()) & getRevisionState(change.getAfterRevision()); - } - - private boolean getRevisionState(final ContentRevision revision) { - if (revision != null) { - final VcsRevisionNumber local = revision.getRevisionNumber(); - final String path = revision.getFile().getIOFile().getAbsolutePath(); - final VcsRevisionNumber remote = getNumber(path); - if ((NOT_LOADED == remote) || (UNKNOWN == remote)) { - return true; - } - return local.compareTo(remote) == 0; - } - return true; } public RemoteStatusChangeNodeDecorator getChangesNodeDecorator() { diff --git a/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/RemoteRevisionsNumbersCache.java b/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/RemoteRevisionsNumbersCache.java new file mode 100644 index 000000000000..e912ce591bbb --- /dev/null +++ b/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/RemoteRevisionsNumbersCache.java @@ -0,0 +1,273 @@ +package com.intellij.openapi.vcs.changes; + +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.Computable; +import com.intellij.openapi.util.Pair; +import com.intellij.openapi.vcs.AbstractVcs; +import com.intellij.openapi.vcs.FilePathImpl; +import com.intellij.openapi.vcs.ProjectLevelVcsManager; +import com.intellij.openapi.vcs.VcsRoot; +import com.intellij.openapi.vcs.diff.DiffProvider; +import com.intellij.openapi.vcs.diff.ItemLatestState; +import com.intellij.openapi.vcs.history.VcsRevisionNumber; +import com.intellij.openapi.vfs.LocalFileSystem; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.util.Consumer; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.io.File; +import java.util.*; + +/** + * for vcses where it is reasonable to ask revision of each item separately + */ +public class RemoteRevisionsNumbersCache implements ChangesOnServerTracker { + public static final Logger LOG = Logger.getInstance("#com.intellij.openapi.vcs.changes.RemoteRevisionsNumbersCache"); + + // every 5 minutes.. (time unit to check for server commits) + private static final long ourRottenPeriod = 300 * 1000; + private final Map> myData; + private final Map> myRefreshingQueues; + private final Map myLatestRevisionsMap; + private final ProjectLevelVcsManager myVcsManager; + private final LocalFileSystem myLfs; + private boolean mySomethingChanged; + + private final Object myLock; + + public static final VcsRevisionNumber NOT_LOADED = new VcsRevisionNumber() { + public String asString() { + return "NOT_LOADED"; + } + + public int compareTo(VcsRevisionNumber o) { + if (o == this) return 0; + return -1; + } + }; + public static final VcsRevisionNumber UNKNOWN = new VcsRevisionNumber() { + public String asString() { + return "UNKNOWN"; + } + + public int compareTo(VcsRevisionNumber o) { + if (o == this) return 0; + return -1; + } + }; + + RemoteRevisionsNumbersCache(final Project project) { + myLock = new Object(); + myData = new HashMap>(); + myRefreshingQueues = Collections.synchronizedMap(new HashMap>()); + myLatestRevisionsMap = new HashMap(); + myLfs = LocalFileSystem.getInstance(); + myVcsManager = ProjectLevelVcsManager.getInstance(project); + } + + public boolean updateStep() { + final List> list = new ArrayList>(); + mySomethingChanged = false; + synchronized (myLock) { + list.addAll(myRefreshingQueues.values()); + } + LOG.debug("queues refresh started, queues: " + list.size()); + for (LazyRefreshingSelfQueue queue : list) { + queue.updateStep(); + } + return mySomethingChanged; + } + + public void directoryMappingChanged() { + synchronized (myLock) { + final HashSet keys = new HashSet(myData.keySet()); + for (String key : keys) { + final Pair value = myData.get(key); + final VcsRoot storedVcsRoot = value.getFirst(); + final VirtualFile vf = myLfs.refreshAndFindFileByIoFile(new File(key)); + final AbstractVcs newVcs = (vf == null) ? null : myVcsManager.getVcsFor(vf); + + if (newVcs == null) { + myData.remove(key); + getQueue(storedVcsRoot).forceRemove(key); + } else { + final VirtualFile newRoot = myVcsManager.getVcsRootFor(vf); + final VcsRoot newVcsRoot = new VcsRoot(newVcs, newRoot); + if (! storedVcsRoot.equals(newVcsRoot)) { + switchVcs(storedVcsRoot, newVcsRoot, key); + } + } + } + } + } + + private void switchVcs(final VcsRoot oldVcsRoot, final VcsRoot newVcsRoot, final String key) { + synchronized (myLock) { + final LazyRefreshingSelfQueue oldQueue = getQueue(oldVcsRoot); + final LazyRefreshingSelfQueue newQueue = getQueue(newVcsRoot); + myData.put(key, new Pair(newVcsRoot, NOT_LOADED)); + oldQueue.forceRemove(key); + newQueue.addRequest(key); + } + } + + public void plus(final Pair pair) { + // does not support + if (pair.getSecond().getDiffProvider() == null) return; + + final String key = pair.getFirst(); + final AbstractVcs newVcs = pair.getSecond(); + + final VirtualFile root = getRootForPath(key); + if (root == null) return; + + final VcsRoot vcsRoot = new VcsRoot(newVcs, root); + + synchronized (myLock) { + final Pair value = myData.get(key); + if (value == null) { + final LazyRefreshingSelfQueue queue = getQueue(vcsRoot); + myData.put(key, new Pair(vcsRoot, NOT_LOADED)); + queue.addRequest(key); + } else if (! value.getFirst().equals(vcsRoot)) { + switchVcs(value.getFirst(), vcsRoot, key); + } + } + } + + public void invalidate(final Collection paths) { + synchronized (myLock) { + for (String path : paths) { + final Pair pair = myData.remove(path); + if (pair != null) { + // vcs [root] seems to not change + final VcsRoot vcsRoot = pair.getFirst(); + final LazyRefreshingSelfQueue queue = getQueue(vcsRoot); + queue.forceRemove(path); + queue.addRequest(path); + myData.put(path, new Pair(vcsRoot, NOT_LOADED)); + } + } + } + } + + @Nullable + private VirtualFile getRootForPath(final String s) { + return myVcsManager.getVcsRootFor(new FilePathImpl(new File(s), false)); + } + + public void minus(Pair pair) { + // does not support + if (pair.getSecond().getDiffProvider() == null) return; + final VirtualFile root = getRootForPath(pair.getFirst()); + if (root == null) return; + + final LazyRefreshingSelfQueue queue; + final String key = pair.getFirst(); + synchronized (myLock) { + queue = getQueue(new VcsRoot(pair.getSecond(), root)); + myData.remove(key); + } + queue.forceRemove(key); + } + + // +- + @NotNull + private LazyRefreshingSelfQueue getQueue(final VcsRoot vcsRoot) { + synchronized (myLock) { + LazyRefreshingSelfQueue queue = myRefreshingQueues.get(vcsRoot); + if (queue != null) return queue; + + queue = new LazyRefreshingSelfQueue(ourRottenPeriod, new MyShouldUpdateChecker(vcsRoot), new MyUpdater(vcsRoot)); + myRefreshingQueues.put(vcsRoot, queue); + return queue; + } + } + + private class MyUpdater implements Consumer { + private final VcsRoot myVcsRoot; + + public MyUpdater(final VcsRoot vcsRoot) { + myVcsRoot = vcsRoot; + } + + public void consume(String s) { + LOG.debug("update for: " + s); + final VirtualFile vf = myLfs.refreshAndFindFileByIoFile(new File(s)); + final ItemLatestState state; + final DiffProvider diffProvider = myVcsRoot.vcs.getDiffProvider(); + if (vf == null) { + // doesnt matter if directory or not + state = diffProvider.getLastRevision(FilePathImpl.createForDeletedFile(new File(s), false)); + } else { + state = diffProvider.getLastRevision(vf); + } + final VcsRevisionNumber newNumber = state == null ? UNKNOWN : state.getNumber(); + + final Pair oldPair; + synchronized (myLock) { + oldPair = myData.get(s); + myData.put(s, new Pair(myVcsRoot, newNumber)); + } + + if ((oldPair == null) || (oldPair != null) && (oldPair.getSecond().compareTo(newNumber) != 0)) { + LOG.debug("refresh triggered by " + s); + mySomethingChanged = true; + } + } + } + + private class MyShouldUpdateChecker implements Computable { + private final VcsRoot myVcsRoot; + + public MyShouldUpdateChecker(final VcsRoot vcsRoot) { + myVcsRoot = vcsRoot; + } + + public Boolean compute() { + final AbstractVcs vcs = myVcsRoot.vcs; + // won't be called in parallel for same vcs -> just synchronized map is ok + final String vcsName = vcs.getName(); + LOG.debug("should update for: " + vcsName + " root: " + myVcsRoot.path.getPath()); + final VcsRevisionNumber latestNew = vcs.getDiffProvider().getLatestCommittedRevision(myVcsRoot.path); + + final VcsRevisionNumber latestKnown = myLatestRevisionsMap.get(vcsName); + // not known + if (latestNew == null) return true; + if ((latestKnown == null) || (latestNew.compareTo(latestKnown) != 0)) { + myLatestRevisionsMap.put(vcsName, latestNew); + return true; + } + return false; + } + } + + private VcsRevisionNumber getNumber(final String path) { + synchronized (myLock) { + final Pair pair = myData.get(path); + return pair == null ? NOT_LOADED : pair.getSecond(); + } + } + + public boolean isUpToDate(final Change change) { + if (change.getBeforeRevision() != null && change.getAfterRevision() != null && (! change.isMoved()) && (! change.isRenamed())) { + return getRevisionState(change.getBeforeRevision()); + } + return getRevisionState(change.getBeforeRevision()) && getRevisionState(change.getAfterRevision()); + } + + private boolean getRevisionState(final ContentRevision revision) { + if (revision != null) { + final VcsRevisionNumber local = revision.getRevisionNumber(); + final String path = revision.getFile().getIOFile().getAbsolutePath(); + final VcsRevisionNumber remote = getNumber(path); + if ((NOT_LOADED == remote) || (UNKNOWN == remote)) { + return true; + } + return local.compareTo(remote) == 0; + } + return true; + } +} diff --git a/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/RemoteRevisionsStateCache.java b/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/RemoteRevisionsStateCache.java new file mode 100644 index 000000000000..3625ea5266a5 --- /dev/null +++ b/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/RemoteRevisionsStateCache.java @@ -0,0 +1,135 @@ +package com.intellij.openapi.vcs.changes; + +import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.Pair; +import com.intellij.openapi.vcs.*; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.util.containers.MultiMap; +import org.jetbrains.annotations.Nullable; + +import java.io.File; +import java.util.*; + +public class RemoteRevisionsStateCache implements ChangesOnServerTracker { + private final static long DISCRETE = 600000; + // true -> changed + private final Map> myChanged; + + private final MultiMap myQueries; + private final Map myTs; + private final Object myLock; + private ProjectLevelVcsManager myVcsManager; + + RemoteRevisionsStateCache(final Project project) { + myVcsManager = ProjectLevelVcsManager.getInstance(project); + myChanged = new HashMap>(); + myQueries = new MultiMap(); + myTs = new HashMap(); + myLock = new Object(); + } + + public void invalidate(final Collection paths) { + synchronized (myLock) { + for (String path : paths) { + myChanged.remove(path); + } + } + } + + @Nullable + private VirtualFile getRootForPath(final String s) { + return myVcsManager.getVcsRootFor(new FilePathImpl(new File(s), false)); + } + + public boolean isUpToDate(final Change change) { + final List files = ChangesUtil.getIoFilesFromChanges(Collections.singletonList(change)); + synchronized (myLock) { + for (File file : files) { + final String path = file.getAbsolutePath(); + final Pair data = myChanged.get(path); + if (data != null && Boolean.TRUE.equals(data.getFirst())) return false; + } + } + return true; + } + + public void plus(final Pair pair) { + final VirtualFile root = getRootForPath(pair.getFirst()); + if (root == null) return; + synchronized (myLock) { + myQueries.putValue(new VcsRoot(pair.getSecond(), root), pair.getFirst()); + } + } + + public void minus(Pair pair) { + final VirtualFile root = getRootForPath(pair.getFirst()); + if (root == null) return; + synchronized (myLock) { + myQueries.removeValue(new VcsRoot(pair.getSecond(), root), pair.getFirst()); + myChanged.remove(pair.getFirst()); + } + } + + public void directoryMappingChanged() { + // todo will work? + synchronized (myLock) { + myChanged.clear(); + myTs.clear(); + } + } + + public boolean updateStep() { + final MultiMap dirty = new MultiMap(); + final long oldPoint = System.currentTimeMillis() - DISCRETE; + + synchronized (myLock) { + for (VcsRoot root : myQueries.keySet()) { + final Collection collection = myQueries.get(root); + for (String s : collection) { + dirty.putValue(root, s); + } + } + myQueries.clear(); + + final Set roots = new HashSet(); + for (Map.Entry entry : myTs.entrySet()) { + if (! dirty.get(entry.getKey()).isEmpty()) continue; + + final Long ts = entry.getValue(); + if ((ts == null) || (oldPoint > ts)) { + roots.add(entry.getKey()); + } + } + for (Map.Entry> entry : myChanged.entrySet()) { + final VcsRoot vcsRoot = entry.getValue().getSecond(); + if ((! dirty.get(vcsRoot).isEmpty()) || roots.contains(vcsRoot)) { + dirty.putValue(vcsRoot, entry.getKey()); + } + } + } + + if (dirty.isEmpty()) return false; + + final Map> results = new HashMap>(); + for (VcsRoot vcsRoot : dirty.keySet()) { + final TreeDiffProvider provider = vcsRoot.vcs.getTreeDiffProvider(); + if (provider == null) continue; + + final Collection paths = dirty.get(vcsRoot); + final Collection remotelyChanged = provider.getRemotelyChanged(vcsRoot.path, paths); + for (String path : paths) { + results.put(path, new Pair(remotelyChanged.contains(path), vcsRoot)); + } + } + + final long curTime = System.currentTimeMillis(); + synchronized (myLock) { + myChanged.putAll(results); + for (VcsRoot vcsRoot : dirty.keySet()) { + myTs.put(vcsRoot, curTime); + } + } + + return true; + } +} diff --git a/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/ui/RemoteStatusChangeNodeDecorator.java b/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/ui/RemoteStatusChangeNodeDecorator.java index 284071e9802c..33c1d60b414a 100644 --- a/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/ui/RemoteStatusChangeNodeDecorator.java +++ b/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/ui/RemoteStatusChangeNodeDecorator.java @@ -17,7 +17,7 @@ public class RemoteStatusChangeNodeDecorator implements ChangeNodeDecorator { } public void decorate(final Change change, final SimpleColoredComponent component) { - final boolean state = myRemoteRevisionsCache.getState(change); + final boolean state = myRemoteRevisionsCache.isUpToDate(change); reportState(state); if (! state) { component.append(" "); diff --git a/platform/vcs-impl/src/com/intellij/openapi/vcs/update/AbstractCommonUpdateAction.java b/platform/vcs-impl/src/com/intellij/openapi/vcs/update/AbstractCommonUpdateAction.java index 86e856742603..b0ceb5b1a4d8 100644 --- a/platform/vcs-impl/src/com/intellij/openapi/vcs/update/AbstractCommonUpdateAction.java +++ b/platform/vcs-impl/src/com/intellij/openapi/vcs/update/AbstractCommonUpdateAction.java @@ -501,9 +501,9 @@ public abstract class AbstractCommonUpdateAction extends AbstractVcsAction { if (myActionInfo.canChangeFileStatus()) { final List files = new ArrayList(); final RemoteRevisionsCache revisionsCache = RemoteRevisionsCache.getInstance(myProject); + revisionsCache.invalidate(myUpdatedFiles); UpdateFilesHelper.iterateFileGroupFiles(myUpdatedFiles, new UpdateFilesHelper.Callback() { public void onFile(final String filePath, final String groupId) { - revisionsCache.invalidate(filePath); @NonNls final String path = VfsUtil.pathToUrl(filePath.replace(File.separatorChar, '/')); final VirtualFile file = VirtualFileManager.getInstance().findFileByUrl(path); if (file != null) { diff --git a/platform/vcs-impl/src/com/intellij/openapi/vcs/update/UpdateFilesHelper.java b/platform/vcs-impl/src/com/intellij/openapi/vcs/update/UpdateFilesHelper.java index 9ac948577545..27f6a27660dd 100644 --- a/platform/vcs-impl/src/com/intellij/openapi/vcs/update/UpdateFilesHelper.java +++ b/platform/vcs-impl/src/com/intellij/openapi/vcs/update/UpdateFilesHelper.java @@ -1,5 +1,8 @@ package com.intellij.openapi.vcs.update; +import com.intellij.openapi.util.Pair; +import com.intellij.util.Consumer; + import java.util.List; public class UpdateFilesHelper { @@ -47,6 +50,24 @@ public class UpdateFilesHelper { } } + private static void iterateGroup(final FileGroup group, final Consumer> callback) { + for (FileGroup.UpdatedFile updatedFile : group.getUpdatedFiles()) { + callback.consume(new Pair(updatedFile.getPath(), updatedFile.getVcsName())); + } + } + + public static void iterateAffectedFiles(final UpdatedFiles updatedFiles, final Consumer> callback) { + final List groups = updatedFiles.getTopLevelGroups(); + for (FileGroup group : groups) { + iterateGroup(group, callback); + + // for changed on server + for (FileGroup childGroup : group.getChildren()) { + iterateGroup(childGroup, callback); + } + } + } + public interface Callback { void onFile(final String filePath, final String groupId); } diff --git a/plugins/cvs/cvs-plugin/src/com/intellij/cvsSupport2/CvsUpdateEnvironment.java b/plugins/cvs/cvs-plugin/src/com/intellij/cvsSupport2/CvsUpdateEnvironment.java index d098097f7a3e..be378f0e123f 100644 --- a/plugins/cvs/cvs-plugin/src/com/intellij/cvsSupport2/CvsUpdateEnvironment.java +++ b/plugins/cvs/cvs-plugin/src/com/intellij/cvsSupport2/CvsUpdateEnvironment.java @@ -53,6 +53,7 @@ import com.intellij.openapi.util.Ref; import com.intellij.openapi.util.io.FileUtil; import com.intellij.openapi.vcs.AbstractVcsHelper; import com.intellij.openapi.vcs.FilePath; +import com.intellij.openapi.vcs.VcsKey; import com.intellij.openapi.vcs.update.*; import com.intellij.openapi.vfs.VirtualFile; import org.jetbrains.annotations.NotNull; @@ -165,11 +166,12 @@ public class CvsUpdateEnvironment implements UpdateEnvironment { final List list = invokeManualMerging(paths, myProject); FileGroup mergedGroup = updatedFiles.getGroupById(FileGroup.MERGED_ID); + final VcsKey vcsKey = CvsVcs2.getKey(); for(VirtualFile mergedFile: list) { String path = FileUtil.toSystemDependentName(mergedFile.getPresentableUrl()); mergedWithConflictsGroup.remove(path); binaryMergedGroup.remove(path); - mergedGroup.add(path); + mergedGroup.add(path, vcsKey, null); } } } diff --git a/plugins/cvs/cvs-plugin/src/com/intellij/cvsSupport2/cvshandlers/UpdateHandler.java b/plugins/cvs/cvs-plugin/src/com/intellij/cvsSupport2/cvshandlers/UpdateHandler.java index 0c41b2671a6b..a518eee73793 100644 --- a/plugins/cvs/cvs-plugin/src/com/intellij/cvsSupport2/cvshandlers/UpdateHandler.java +++ b/plugins/cvs/cvs-plugin/src/com/intellij/cvsSupport2/cvshandlers/UpdateHandler.java @@ -1,6 +1,7 @@ package com.intellij.cvsSupport2.cvshandlers; import com.intellij.CvsBundle; +import com.intellij.cvsSupport2.CvsVcs2; import com.intellij.cvsSupport2.actions.update.UpdateSettings; import com.intellij.cvsSupport2.config.CvsConfiguration; import com.intellij.cvsSupport2.connections.CvsRootProvider; @@ -16,6 +17,7 @@ import com.intellij.openapi.progress.ProgressIndicator; import com.intellij.openapi.progress.ProgressManager; import com.intellij.openapi.project.Project; import com.intellij.openapi.vcs.FilePath; +import com.intellij.openapi.vcs.VcsKey; import com.intellij.openapi.vcs.update.FileGroup; import com.intellij.openapi.vcs.update.UpdatedFiles; import com.intellij.openapi.vfs.VirtualFile; @@ -126,12 +128,13 @@ public class UpdateHandler extends CommandCvsHandler implements PostCvsActivity } + final VcsKey vcsKey = CvsVcs2.getKey(); for (final MergedWithConflictProjectOrModuleFile myCorruptedFile : myCorruptedFiles) { if (myCorruptedFile.shouldBeCheckedOut()) { addFileToCheckout(myCorruptedFile.getOriginal()); } else { - myUpdatedFiles.getGroupById(FileGroup.MODIFIED_ID).add(myCorruptedFile.getOriginal().getPath()); + myUpdatedFiles.getGroupById(FileGroup.MODIFIED_ID).add(myCorruptedFile.getOriginal().getPath(), vcsKey, null); } } diff --git a/plugins/cvs/cvs-plugin/src/com/intellij/cvsSupport2/updateinfo/UpdatedFilesProcessor.java b/plugins/cvs/cvs-plugin/src/com/intellij/cvsSupport2/updateinfo/UpdatedFilesProcessor.java index 0589593dcf48..ffd61c77f856 100644 --- a/plugins/cvs/cvs-plugin/src/com/intellij/cvsSupport2/updateinfo/UpdatedFilesProcessor.java +++ b/plugins/cvs/cvs-plugin/src/com/intellij/cvsSupport2/updateinfo/UpdatedFilesProcessor.java @@ -65,12 +65,7 @@ public class UpdatedFilesProcessor extends CvsMessagesAdapter { FileGroup collection = getCollectionFor(message.getType(), virtualFile); LOG.assertTrue(collection != null, String.valueOf(message.getType())); final CvsRevisionNumber revision = message.getRevision(); - if (revision != null) { - collection.add(path, CvsVcs2.getInstance(myProject), revision); - } - else { - collection.add(path); - } + collection.add(path, CvsVcs2.getKey(), revision); } private FileGroup getCollectionFor(int messageType, @Nullable VirtualFile vFile) {