VCS: allow Git to report changed on server files comparing the whole tree. !! requires fetch now. to be continued.

This commit is contained in:
Irina Chernushina
2009-09-10 12:37:03 +04:00
parent 395408e6b3
commit ef09358768
12 changed files with 563 additions and 258 deletions
@@ -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<UpdatedFile> getUpdatedFiles() {
return new ArrayList<UpdatedFile>(myFiles);
}
public List<Pair<String, VcsRevisionNumber>> getFilesAndRevisions(ProjectLevelVcsManager vcsManager) {
ArrayList<Pair<String, VcsRevisionNumber>> files = new ArrayList<Pair<String, VcsRevisionNumber>>();
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() {
@@ -26,13 +26,6 @@ public class UpdatedFilesReverseSide {
myFileIdx = new HashMap<String, FileGroup>();
}
/**
* 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);
@@ -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<Pair<String, AbstractVcs>>, VcsListener {
// todo add vcs parameter???
void invalidate(final Collection<String> paths);
boolean isUpToDate(final Change change);
boolean updateStep();
}
@@ -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<Pair<String, AbstractVcs>>, VcsListener {
private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.vcs.changes.RemoteRevisionsCache");
public static Topic<Runnable> REMOTE_VERSION_CHANGED = new Topic<Runnable>("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<String, Pair<VcsRoot, VcsRevisionNumber>> myData;
private final Map<VcsRoot, LazyRefreshingSelfQueue<String>> myRefreshingQueues;
private final Map<String, VcsRevisionNumber> 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<String, RemoteDifferenceStrategy> 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<String, Pair<VcsRoot, VcsRevisionNumber>>();
myRefreshingQueues = Collections.synchronizedMap(new HashMap<VcsRoot, LazyRefreshingSelfQueue<String>>());
myLatestRevisionsMap = new HashMap<String, VcsRevisionNumber>();
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<String, RemoteDifferenceStrategy>();
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<LazyRefreshingSelfQueue<String>> list = new ArrayList<LazyRefreshingSelfQueue<String>>();
synchronized (myLock) {
list.addAll(myRefreshingQueues.values());
}
LOG.debug("queues refresh started, queues: " + list.size());
for (LazyRefreshingSelfQueue<String> 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<Pair<String, AbstractVcs>
}
public void directoryMappingChanged() {
synchronized (myLock) {
final HashSet<String> keys = new HashSet<String>(myData.keySet());
for (String key : keys) {
final Pair<VcsRoot, VcsRevisionNumber> 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<String> oldQueue = getQueue(oldVcsRoot);
final LazyRefreshingSelfQueue<String> newQueue = getQueue(newVcsRoot);
myData.put(key, new Pair<VcsRoot, VcsRevisionNumber>(newVcsRoot, NOT_LOADED));
oldQueue.forceRemove(key);
newQueue.addRequest(key);
}
updateKinds();
myRemoteRevisionsNumbersCache.directoryMappingChanged();
myRemoteRevisionsStateCache.directoryMappingChanged();
}
public void plus(final Pair<String, AbstractVcs> 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<VcsRoot, VcsRevisionNumber> value = myData.get(key);
if (value == null) {
LOG.debug("adding new " + key);
final LazyRefreshingSelfQueue<String> queue = getQueue(vcsRoot);
myData.put(key, new Pair<VcsRoot, VcsRevisionNumber>(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<String, RemoteDifferenceStrategy> strategyMap;
synchronized (myLock) {
final Pair<VcsRoot, VcsRevisionNumber> pair = myData.remove(path);
if (pair != null) {
// vcs [root] seems to not change
final VcsRoot vcsRoot = pair.getFirst();
final LazyRefreshingSelfQueue<String> queue = getQueue(vcsRoot);
queue.forceRemove(path);
queue.addRequest(path);
myData.put(path, new Pair<VcsRoot, VcsRevisionNumber>(vcsRoot, NOT_LOADED));
}
strategyMap = new HashMap<String, RemoteDifferenceStrategy>(myKinds);
}
}
final Collection<String> newForTree = new LinkedList<String>();
final Collection<String> newForUsual = new LinkedList<String>();
UpdateFilesHelper.iterateAffectedFiles(updatedFiles, new Consumer<Pair<String, String>>() {
public void consume(final Pair<String, String> 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<String, AbstractVcs> 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<String> 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<String> getQueue(final VcsRoot vcsRoot) {
synchronized (myLock) {
LazyRefreshingSelfQueue<String> queue = myRefreshingQueues.get(vcsRoot);
if (queue != null) return queue;
queue = new LazyRefreshingSelfQueue<String>(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<String> {
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<VcsRoot, VcsRevisionNumber> oldPair;
synchronized (myLock) {
oldPair = myData.get(s);
myData.put(s, new Pair<VcsRoot, VcsRevisionNumber>(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<Boolean> {
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<VcsRoot, VcsRevisionNumber> 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() {
@@ -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<String, Pair<VcsRoot, VcsRevisionNumber>> myData;
private final Map<VcsRoot, LazyRefreshingSelfQueue<String>> myRefreshingQueues;
private final Map<String, VcsRevisionNumber> 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<String, Pair<VcsRoot, VcsRevisionNumber>>();
myRefreshingQueues = Collections.synchronizedMap(new HashMap<VcsRoot, LazyRefreshingSelfQueue<String>>());
myLatestRevisionsMap = new HashMap<String, VcsRevisionNumber>();
myLfs = LocalFileSystem.getInstance();
myVcsManager = ProjectLevelVcsManager.getInstance(project);
}
public boolean updateStep() {
final List<LazyRefreshingSelfQueue<String>> list = new ArrayList<LazyRefreshingSelfQueue<String>>();
mySomethingChanged = false;
synchronized (myLock) {
list.addAll(myRefreshingQueues.values());
}
LOG.debug("queues refresh started, queues: " + list.size());
for (LazyRefreshingSelfQueue<String> queue : list) {
queue.updateStep();
}
return mySomethingChanged;
}
public void directoryMappingChanged() {
synchronized (myLock) {
final HashSet<String> keys = new HashSet<String>(myData.keySet());
for (String key : keys) {
final Pair<VcsRoot, VcsRevisionNumber> 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<String> oldQueue = getQueue(oldVcsRoot);
final LazyRefreshingSelfQueue<String> newQueue = getQueue(newVcsRoot);
myData.put(key, new Pair<VcsRoot, VcsRevisionNumber>(newVcsRoot, NOT_LOADED));
oldQueue.forceRemove(key);
newQueue.addRequest(key);
}
}
public void plus(final Pair<String, AbstractVcs> 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<VcsRoot, VcsRevisionNumber> value = myData.get(key);
if (value == null) {
final LazyRefreshingSelfQueue<String> queue = getQueue(vcsRoot);
myData.put(key, new Pair<VcsRoot, VcsRevisionNumber>(vcsRoot, NOT_LOADED));
queue.addRequest(key);
} else if (! value.getFirst().equals(vcsRoot)) {
switchVcs(value.getFirst(), vcsRoot, key);
}
}
}
public void invalidate(final Collection<String> paths) {
synchronized (myLock) {
for (String path : paths) {
final Pair<VcsRoot, VcsRevisionNumber> pair = myData.remove(path);
if (pair != null) {
// vcs [root] seems to not change
final VcsRoot vcsRoot = pair.getFirst();
final LazyRefreshingSelfQueue<String> queue = getQueue(vcsRoot);
queue.forceRemove(path);
queue.addRequest(path);
myData.put(path, new Pair<VcsRoot, VcsRevisionNumber>(vcsRoot, NOT_LOADED));
}
}
}
}
@Nullable
private VirtualFile getRootForPath(final String s) {
return myVcsManager.getVcsRootFor(new FilePathImpl(new File(s), false));
}
public void minus(Pair<String, AbstractVcs> pair) {
// does not support
if (pair.getSecond().getDiffProvider() == null) return;
final VirtualFile root = getRootForPath(pair.getFirst());
if (root == null) return;
final LazyRefreshingSelfQueue<String> queue;
final String key = pair.getFirst();
synchronized (myLock) {
queue = getQueue(new VcsRoot(pair.getSecond(), root));
myData.remove(key);
}
queue.forceRemove(key);
}
// +-
@NotNull
private LazyRefreshingSelfQueue<String> getQueue(final VcsRoot vcsRoot) {
synchronized (myLock) {
LazyRefreshingSelfQueue<String> queue = myRefreshingQueues.get(vcsRoot);
if (queue != null) return queue;
queue = new LazyRefreshingSelfQueue<String>(ourRottenPeriod, new MyShouldUpdateChecker(vcsRoot), new MyUpdater(vcsRoot));
myRefreshingQueues.put(vcsRoot, queue);
return queue;
}
}
private class MyUpdater implements Consumer<String> {
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<VcsRoot, VcsRevisionNumber> oldPair;
synchronized (myLock) {
oldPair = myData.get(s);
myData.put(s, new Pair<VcsRoot, VcsRevisionNumber>(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<Boolean> {
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<VcsRoot, VcsRevisionNumber> 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;
}
}
@@ -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<String, Pair<Boolean, VcsRoot>> myChanged;
private final MultiMap<VcsRoot, String> myQueries;
private final Map<VcsRoot, Long> myTs;
private final Object myLock;
private ProjectLevelVcsManager myVcsManager;
RemoteRevisionsStateCache(final Project project) {
myVcsManager = ProjectLevelVcsManager.getInstance(project);
myChanged = new HashMap<String, Pair<Boolean, VcsRoot>>();
myQueries = new MultiMap<VcsRoot, String>();
myTs = new HashMap<VcsRoot, Long>();
myLock = new Object();
}
public void invalidate(final Collection<String> 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<File> files = ChangesUtil.getIoFilesFromChanges(Collections.singletonList(change));
synchronized (myLock) {
for (File file : files) {
final String path = file.getAbsolutePath();
final Pair<Boolean, VcsRoot> data = myChanged.get(path);
if (data != null && Boolean.TRUE.equals(data.getFirst())) return false;
}
}
return true;
}
public void plus(final Pair<String, AbstractVcs> 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<String, AbstractVcs> 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<VcsRoot, String> dirty = new MultiMap<VcsRoot, String>();
final long oldPoint = System.currentTimeMillis() - DISCRETE;
synchronized (myLock) {
for (VcsRoot root : myQueries.keySet()) {
final Collection<String> collection = myQueries.get(root);
for (String s : collection) {
dirty.putValue(root, s);
}
}
myQueries.clear();
final Set<VcsRoot> roots = new HashSet<VcsRoot>();
for (Map.Entry<VcsRoot, Long> 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<String, Pair<Boolean, VcsRoot>> 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<String, Pair<Boolean, VcsRoot>> results = new HashMap<String, Pair<Boolean, VcsRoot>>();
for (VcsRoot vcsRoot : dirty.keySet()) {
final TreeDiffProvider provider = vcsRoot.vcs.getTreeDiffProvider();
if (provider == null) continue;
final Collection<String> paths = dirty.get(vcsRoot);
final Collection<String> remotelyChanged = provider.getRemotelyChanged(vcsRoot.path, paths);
for (String path : paths) {
results.put(path, new Pair<Boolean, VcsRoot>(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;
}
}
@@ -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(" ");
@@ -501,9 +501,9 @@ public abstract class AbstractCommonUpdateAction extends AbstractVcsAction {
if (myActionInfo.canChangeFileStatus()) {
final List<VirtualFile> files = new ArrayList<VirtualFile>();
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) {
@@ -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<Pair<String, String>> callback) {
for (FileGroup.UpdatedFile updatedFile : group.getUpdatedFiles()) {
callback.consume(new Pair<String, String>(updatedFile.getPath(), updatedFile.getVcsName()));
}
}
public static void iterateAffectedFiles(final UpdatedFiles updatedFiles, final Consumer<Pair<String, String>> callback) {
final List<FileGroup> 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);
}
@@ -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<VirtualFile> 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);
}
}
}
@@ -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);
}
}
@@ -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) {