VCS: allow Git to report changed on server files comparing the whole tree. !! require...
[fedora-idea.git] / platform / vcs-impl / src / com / intellij / openapi / vcs / changes / RemoteRevisionsNumbersCache.java
blobe912ce591bbb02c65b2a2793a2a53dab2f0629e1
1 package com.intellij.openapi.vcs.changes;
3 import com.intellij.openapi.diagnostic.Logger;
4 import com.intellij.openapi.project.Project;
5 import com.intellij.openapi.util.Computable;
6 import com.intellij.openapi.util.Pair;
7 import com.intellij.openapi.vcs.AbstractVcs;
8 import com.intellij.openapi.vcs.FilePathImpl;
9 import com.intellij.openapi.vcs.ProjectLevelVcsManager;
10 import com.intellij.openapi.vcs.VcsRoot;
11 import com.intellij.openapi.vcs.diff.DiffProvider;
12 import com.intellij.openapi.vcs.diff.ItemLatestState;
13 import com.intellij.openapi.vcs.history.VcsRevisionNumber;
14 import com.intellij.openapi.vfs.LocalFileSystem;
15 import com.intellij.openapi.vfs.VirtualFile;
16 import com.intellij.util.Consumer;
17 import org.jetbrains.annotations.NotNull;
18 import org.jetbrains.annotations.Nullable;
20 import java.io.File;
21 import java.util.*;
23 /**
24 * for vcses where it is reasonable to ask revision of each item separately
26 public class RemoteRevisionsNumbersCache implements ChangesOnServerTracker {
27 public static final Logger LOG = Logger.getInstance("#com.intellij.openapi.vcs.changes.RemoteRevisionsNumbersCache");
29 // every 5 minutes.. (time unit to check for server commits)
30 private static final long ourRottenPeriod = 300 * 1000;
31 private final Map<String, Pair<VcsRoot, VcsRevisionNumber>> myData;
32 private final Map<VcsRoot, LazyRefreshingSelfQueue<String>> myRefreshingQueues;
33 private final Map<String, VcsRevisionNumber> myLatestRevisionsMap;
34 private final ProjectLevelVcsManager myVcsManager;
35 private final LocalFileSystem myLfs;
36 private boolean mySomethingChanged;
38 private final Object myLock;
40 public static final VcsRevisionNumber NOT_LOADED = new VcsRevisionNumber() {
41 public String asString() {
42 return "NOT_LOADED";
45 public int compareTo(VcsRevisionNumber o) {
46 if (o == this) return 0;
47 return -1;
50 public static final VcsRevisionNumber UNKNOWN = new VcsRevisionNumber() {
51 public String asString() {
52 return "UNKNOWN";
55 public int compareTo(VcsRevisionNumber o) {
56 if (o == this) return 0;
57 return -1;
61 RemoteRevisionsNumbersCache(final Project project) {
62 myLock = new Object();
63 myData = new HashMap<String, Pair<VcsRoot, VcsRevisionNumber>>();
64 myRefreshingQueues = Collections.synchronizedMap(new HashMap<VcsRoot, LazyRefreshingSelfQueue<String>>());
65 myLatestRevisionsMap = new HashMap<String, VcsRevisionNumber>();
66 myLfs = LocalFileSystem.getInstance();
67 myVcsManager = ProjectLevelVcsManager.getInstance(project);
70 public boolean updateStep() {
71 final List<LazyRefreshingSelfQueue<String>> list = new ArrayList<LazyRefreshingSelfQueue<String>>();
72 mySomethingChanged = false;
73 synchronized (myLock) {
74 list.addAll(myRefreshingQueues.values());
76 LOG.debug("queues refresh started, queues: " + list.size());
77 for (LazyRefreshingSelfQueue<String> queue : list) {
78 queue.updateStep();
80 return mySomethingChanged;
83 public void directoryMappingChanged() {
84 synchronized (myLock) {
85 final HashSet<String> keys = new HashSet<String>(myData.keySet());
86 for (String key : keys) {
87 final Pair<VcsRoot, VcsRevisionNumber> value = myData.get(key);
88 final VcsRoot storedVcsRoot = value.getFirst();
89 final VirtualFile vf = myLfs.refreshAndFindFileByIoFile(new File(key));
90 final AbstractVcs newVcs = (vf == null) ? null : myVcsManager.getVcsFor(vf);
92 if (newVcs == null) {
93 myData.remove(key);
94 getQueue(storedVcsRoot).forceRemove(key);
95 } else {
96 final VirtualFile newRoot = myVcsManager.getVcsRootFor(vf);
97 final VcsRoot newVcsRoot = new VcsRoot(newVcs, newRoot);
98 if (! storedVcsRoot.equals(newVcsRoot)) {
99 switchVcs(storedVcsRoot, newVcsRoot, key);
106 private void switchVcs(final VcsRoot oldVcsRoot, final VcsRoot newVcsRoot, final String key) {
107 synchronized (myLock) {
108 final LazyRefreshingSelfQueue<String> oldQueue = getQueue(oldVcsRoot);
109 final LazyRefreshingSelfQueue<String> newQueue = getQueue(newVcsRoot);
110 myData.put(key, new Pair<VcsRoot, VcsRevisionNumber>(newVcsRoot, NOT_LOADED));
111 oldQueue.forceRemove(key);
112 newQueue.addRequest(key);
116 public void plus(final Pair<String, AbstractVcs> pair) {
117 // does not support
118 if (pair.getSecond().getDiffProvider() == null) return;
120 final String key = pair.getFirst();
121 final AbstractVcs newVcs = pair.getSecond();
123 final VirtualFile root = getRootForPath(key);
124 if (root == null) return;
126 final VcsRoot vcsRoot = new VcsRoot(newVcs, root);
128 synchronized (myLock) {
129 final Pair<VcsRoot, VcsRevisionNumber> value = myData.get(key);
130 if (value == null) {
131 final LazyRefreshingSelfQueue<String> queue = getQueue(vcsRoot);
132 myData.put(key, new Pair<VcsRoot, VcsRevisionNumber>(vcsRoot, NOT_LOADED));
133 queue.addRequest(key);
134 } else if (! value.getFirst().equals(vcsRoot)) {
135 switchVcs(value.getFirst(), vcsRoot, key);
140 public void invalidate(final Collection<String> paths) {
141 synchronized (myLock) {
142 for (String path : paths) {
143 final Pair<VcsRoot, VcsRevisionNumber> pair = myData.remove(path);
144 if (pair != null) {
145 // vcs [root] seems to not change
146 final VcsRoot vcsRoot = pair.getFirst();
147 final LazyRefreshingSelfQueue<String> queue = getQueue(vcsRoot);
148 queue.forceRemove(path);
149 queue.addRequest(path);
150 myData.put(path, new Pair<VcsRoot, VcsRevisionNumber>(vcsRoot, NOT_LOADED));
156 @Nullable
157 private VirtualFile getRootForPath(final String s) {
158 return myVcsManager.getVcsRootFor(new FilePathImpl(new File(s), false));
161 public void minus(Pair<String, AbstractVcs> pair) {
162 // does not support
163 if (pair.getSecond().getDiffProvider() == null) return;
164 final VirtualFile root = getRootForPath(pair.getFirst());
165 if (root == null) return;
167 final LazyRefreshingSelfQueue<String> queue;
168 final String key = pair.getFirst();
169 synchronized (myLock) {
170 queue = getQueue(new VcsRoot(pair.getSecond(), root));
171 myData.remove(key);
173 queue.forceRemove(key);
176 // +-
177 @NotNull
178 private LazyRefreshingSelfQueue<String> getQueue(final VcsRoot vcsRoot) {
179 synchronized (myLock) {
180 LazyRefreshingSelfQueue<String> queue = myRefreshingQueues.get(vcsRoot);
181 if (queue != null) return queue;
183 queue = new LazyRefreshingSelfQueue<String>(ourRottenPeriod, new MyShouldUpdateChecker(vcsRoot), new MyUpdater(vcsRoot));
184 myRefreshingQueues.put(vcsRoot, queue);
185 return queue;
189 private class MyUpdater implements Consumer<String> {
190 private final VcsRoot myVcsRoot;
192 public MyUpdater(final VcsRoot vcsRoot) {
193 myVcsRoot = vcsRoot;
196 public void consume(String s) {
197 LOG.debug("update for: " + s);
198 final VirtualFile vf = myLfs.refreshAndFindFileByIoFile(new File(s));
199 final ItemLatestState state;
200 final DiffProvider diffProvider = myVcsRoot.vcs.getDiffProvider();
201 if (vf == null) {
202 // doesnt matter if directory or not
203 state = diffProvider.getLastRevision(FilePathImpl.createForDeletedFile(new File(s), false));
204 } else {
205 state = diffProvider.getLastRevision(vf);
207 final VcsRevisionNumber newNumber = state == null ? UNKNOWN : state.getNumber();
209 final Pair<VcsRoot, VcsRevisionNumber> oldPair;
210 synchronized (myLock) {
211 oldPair = myData.get(s);
212 myData.put(s, new Pair<VcsRoot, VcsRevisionNumber>(myVcsRoot, newNumber));
215 if ((oldPair == null) || (oldPair != null) && (oldPair.getSecond().compareTo(newNumber) != 0)) {
216 LOG.debug("refresh triggered by " + s);
217 mySomethingChanged = true;
222 private class MyShouldUpdateChecker implements Computable<Boolean> {
223 private final VcsRoot myVcsRoot;
225 public MyShouldUpdateChecker(final VcsRoot vcsRoot) {
226 myVcsRoot = vcsRoot;
229 public Boolean compute() {
230 final AbstractVcs vcs = myVcsRoot.vcs;
231 // won't be called in parallel for same vcs -> just synchronized map is ok
232 final String vcsName = vcs.getName();
233 LOG.debug("should update for: " + vcsName + " root: " + myVcsRoot.path.getPath());
234 final VcsRevisionNumber latestNew = vcs.getDiffProvider().getLatestCommittedRevision(myVcsRoot.path);
236 final VcsRevisionNumber latestKnown = myLatestRevisionsMap.get(vcsName);
237 // not known
238 if (latestNew == null) return true;
239 if ((latestKnown == null) || (latestNew.compareTo(latestKnown) != 0)) {
240 myLatestRevisionsMap.put(vcsName, latestNew);
241 return true;
243 return false;
247 private VcsRevisionNumber getNumber(final String path) {
248 synchronized (myLock) {
249 final Pair<VcsRoot, VcsRevisionNumber> pair = myData.get(path);
250 return pair == null ? NOT_LOADED : pair.getSecond();
254 public boolean isUpToDate(final Change change) {
255 if (change.getBeforeRevision() != null && change.getAfterRevision() != null && (! change.isMoved()) && (! change.isRenamed())) {
256 return getRevisionState(change.getBeforeRevision());
258 return getRevisionState(change.getBeforeRevision()) && getRevisionState(change.getAfterRevision());
261 private boolean getRevisionState(final ContentRevision revision) {
262 if (revision != null) {
263 final VcsRevisionNumber local = revision.getRevisionNumber();
264 final String path = revision.getFile().getIOFile().getAbsolutePath();
265 final VcsRevisionNumber remote = getNumber(path);
266 if ((NOT_LOADED == remote) || (UNKNOWN == remote)) {
267 return true;
269 return local.compareTo(remote) == 0;
271 return true;