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
;
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() {
45 public int compareTo(VcsRevisionNumber o
) {
46 if (o
== this) return 0;
50 public static final VcsRevisionNumber UNKNOWN
= new VcsRevisionNumber() {
51 public String
asString() {
55 public int compareTo(VcsRevisionNumber o
) {
56 if (o
== this) return 0;
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
) {
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
);
94 getQueue(storedVcsRoot
).forceRemove(key
);
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
) {
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
);
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
);
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
));
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
) {
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
));
173 queue
.forceRemove(key
);
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
);
189 private class MyUpdater
implements Consumer
<String
> {
190 private final VcsRoot myVcsRoot
;
192 public MyUpdater(final VcsRoot 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();
202 // doesnt matter if directory or not
203 state
= diffProvider
.getLastRevision(FilePathImpl
.createForDeletedFile(new File(s
), false));
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
) {
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
);
238 if (latestNew
== null) return true;
239 if ((latestKnown
== null) || (latestNew
.compareTo(latestKnown
) != 0)) {
240 myLatestRevisionsMap
.put(vcsName
, latestNew
);
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
)) {
269 return local
.compareTo(remote
) == 0;