2 * Copyright 2000-2009 JetBrains s.r.o.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
16 package com
.intellij
.openapi
.vcs
.changes
;
18 import com
.intellij
.lifecycle
.AtomicSectionsAware
;
19 import com
.intellij
.lifecycle
.ControlledAlarmFactory
;
20 import com
.intellij
.openapi
.diagnostic
.Logger
;
21 import com
.intellij
.openapi
.progress
.ProgressIndicator
;
22 import com
.intellij
.openapi
.project
.Project
;
23 import com
.intellij
.openapi
.util
.Computable
;
24 import com
.intellij
.openapi
.util
.Getter
;
25 import com
.intellij
.openapi
.util
.Pair
;
26 import com
.intellij
.openapi
.vcs
.*;
27 import com
.intellij
.openapi
.vcs
.diff
.DiffProvider
;
28 import com
.intellij
.openapi
.vcs
.diff
.ItemLatestState
;
29 import com
.intellij
.openapi
.vcs
.history
.VcsRevisionNumber
;
30 import com
.intellij
.openapi
.vfs
.LocalFileSystem
;
31 import com
.intellij
.openapi
.vfs
.VirtualFile
;
32 import com
.intellij
.util
.Consumer
;
33 import org
.jetbrains
.annotations
.NotNull
;
34 import org
.jetbrains
.annotations
.Nullable
;
40 * for vcses where it is reasonable to ask revision of each item separately
42 public class RemoteRevisionsNumbersCache
implements ChangesOnServerTracker
{
43 public static final Logger LOG
= Logger
.getInstance("#com.intellij.openapi.vcs.changes.RemoteRevisionsNumbersCache");
45 // every hour (time unit to check for server commits)
46 // default, actual in settings
47 private static final long ourRottenPeriod
= 3600 * 1000;
48 private final Map
<String
, Pair
<VcsRoot
, VcsRevisionNumber
>> myData
;
49 private final Map
<VcsRoot
, LazyRefreshingSelfQueue
<String
>> myRefreshingQueues
;
50 private final Map
<String
, VcsRevisionNumber
> myLatestRevisionsMap
;
51 private final ProjectLevelVcsManager myVcsManager
;
52 private final LocalFileSystem myLfs
;
53 private boolean mySomethingChanged
;
55 private final Object myLock
;
57 public static final VcsRevisionNumber NOT_LOADED
= new VcsRevisionNumber() {
58 public String
asString() {
62 public int compareTo(VcsRevisionNumber o
) {
63 if (o
== this) return 0;
67 public static final VcsRevisionNumber UNKNOWN
= new VcsRevisionNumber() {
68 public String
asString() {
72 public int compareTo(VcsRevisionNumber o
) {
73 if (o
== this) return 0;
77 private final VcsConfiguration myVcsConfiguration
;
79 RemoteRevisionsNumbersCache(final Project project
) {
80 myLock
= new Object();
81 myData
= new HashMap
<String
, Pair
<VcsRoot
, VcsRevisionNumber
>>();
82 myRefreshingQueues
= Collections
.synchronizedMap(new HashMap
<VcsRoot
, LazyRefreshingSelfQueue
<String
>>());
83 myLatestRevisionsMap
= new HashMap
<String
, VcsRevisionNumber
>();
84 myLfs
= LocalFileSystem
.getInstance();
85 myVcsManager
= ProjectLevelVcsManager
.getInstance(project
);
86 myVcsConfiguration
= VcsConfiguration
.getInstance(project
);
89 public boolean updateStep(final AtomicSectionsAware atomicSectionsAware
) {
90 final List
<LazyRefreshingSelfQueue
<String
>> list
= new ArrayList
<LazyRefreshingSelfQueue
<String
>>();
91 mySomethingChanged
= false;
92 synchronized (myLock
) {
93 final Set
<VcsRoot
> keys
= myRefreshingQueues
.keySet();
94 for (VcsRoot key
: keys
) {
95 final boolean backgroundOperationsAllowed
= key
.vcs
.isVcsBackgroundOperationsAllowed(key
.path
);
96 LOG
.debug("backgroundOperationsAllowed: " + backgroundOperationsAllowed
+ " for " + key
.vcs
.getName() + ", " + key
.path
.getPath());
97 if (backgroundOperationsAllowed
) {
98 list
.add(myRefreshingQueues
.get(key
));
102 LOG
.debug("queues refresh started, queues: " + list
.size());
103 final ProgressIndicator pi
= ControlledAlarmFactory
.createProgressIndicator(atomicSectionsAware
);
104 for (LazyRefreshingSelfQueue
<String
> queue
: list
) {
105 atomicSectionsAware
.checkShouldExit();
106 queue
.updateStep(pi
);
108 return mySomethingChanged
;
111 public void directoryMappingChanged() {
112 synchronized (myLock
) {
113 final HashSet
<String
> keys
= new HashSet
<String
>(myData
.keySet());
114 for (String key
: keys
) {
115 final Pair
<VcsRoot
, VcsRevisionNumber
> value
= myData
.get(key
);
116 final VcsRoot storedVcsRoot
= value
.getFirst();
117 final VirtualFile vf
= myLfs
.refreshAndFindFileByIoFile(new File(key
));
118 final AbstractVcs newVcs
= (vf
== null) ?
null : myVcsManager
.getVcsFor(vf
);
120 if (newVcs
== null) {
122 getQueue(storedVcsRoot
).forceRemove(key
);
124 final VirtualFile newRoot
= myVcsManager
.getVcsRootFor(vf
);
125 final VcsRoot newVcsRoot
= new VcsRoot(newVcs
, newRoot
);
126 if (! storedVcsRoot
.equals(newVcsRoot
)) {
127 switchVcs(storedVcsRoot
, newVcsRoot
, key
);
134 private void switchVcs(final VcsRoot oldVcsRoot
, final VcsRoot newVcsRoot
, final String key
) {
135 synchronized (myLock
) {
136 final LazyRefreshingSelfQueue
<String
> oldQueue
= getQueue(oldVcsRoot
);
137 final LazyRefreshingSelfQueue
<String
> newQueue
= getQueue(newVcsRoot
);
138 myData
.put(key
, new Pair
<VcsRoot
, VcsRevisionNumber
>(newVcsRoot
, NOT_LOADED
));
139 oldQueue
.forceRemove(key
);
140 newQueue
.addRequest(key
);
144 public void plus(final Pair
<String
, AbstractVcs
> pair
) {
146 if (pair
.getSecond().getDiffProvider() == null) return;
148 final String key
= pair
.getFirst();
149 final AbstractVcs newVcs
= pair
.getSecond();
151 final VirtualFile root
= getRootForPath(key
);
152 if (root
== null) return;
154 final VcsRoot vcsRoot
= new VcsRoot(newVcs
, root
);
156 synchronized (myLock
) {
157 final Pair
<VcsRoot
, VcsRevisionNumber
> value
= myData
.get(key
);
159 final LazyRefreshingSelfQueue
<String
> queue
= getQueue(vcsRoot
);
160 myData
.put(key
, new Pair
<VcsRoot
, VcsRevisionNumber
>(vcsRoot
, NOT_LOADED
));
161 queue
.addRequest(key
);
162 } else if (! value
.getFirst().equals(vcsRoot
)) {
163 switchVcs(value
.getFirst(), vcsRoot
, key
);
168 public void invalidate(final Collection
<String
> paths
) {
169 synchronized (myLock
) {
170 for (String path
: paths
) {
171 final Pair
<VcsRoot
, VcsRevisionNumber
> pair
= myData
.remove(path
);
173 // vcs [root] seems to not change
174 final VcsRoot vcsRoot
= pair
.getFirst();
175 final LazyRefreshingSelfQueue
<String
> queue
= getQueue(vcsRoot
);
176 queue
.forceRemove(path
);
177 queue
.addRequest(path
);
178 myData
.put(path
, new Pair
<VcsRoot
, VcsRevisionNumber
>(vcsRoot
, NOT_LOADED
));
185 private VirtualFile
getRootForPath(final String s
) {
186 return myVcsManager
.getVcsRootFor(new FilePathImpl(new File(s
), false));
189 public void minus(Pair
<String
, AbstractVcs
> pair
) {
191 if (pair
.getSecond().getDiffProvider() == null) return;
192 final VirtualFile root
= getRootForPath(pair
.getFirst());
193 if (root
== null) return;
195 final LazyRefreshingSelfQueue
<String
> queue
;
196 final String key
= pair
.getFirst();
197 synchronized (myLock
) {
198 queue
= getQueue(new VcsRoot(pair
.getSecond(), root
));
201 queue
.forceRemove(key
);
206 private LazyRefreshingSelfQueue
<String
> getQueue(final VcsRoot vcsRoot
) {
207 synchronized (myLock
) {
208 LazyRefreshingSelfQueue
<String
> queue
= myRefreshingQueues
.get(vcsRoot
);
209 if (queue
!= null) return queue
;
211 queue
= new LazyRefreshingSelfQueue
<String
>(new Getter
<Long
>() {
213 return myVcsConfiguration
.CHANGED_ON_SERVER_INTERVAL
> 0 ? myVcsConfiguration
.CHANGED_ON_SERVER_INTERVAL
* 60000 : ourRottenPeriod
;
215 }, new MyShouldUpdateChecker(vcsRoot
), new MyUpdater(vcsRoot
));
216 myRefreshingQueues
.put(vcsRoot
, queue
);
221 private class MyUpdater
implements Consumer
<String
> {
222 private final VcsRoot myVcsRoot
;
224 public MyUpdater(final VcsRoot vcsRoot
) {
228 public void consume(String s
) {
229 LOG
.debug("update for: " + s
);
230 //todo check canceled - check VCS's ready for asynchronous queries
231 final VirtualFile vf
= myLfs
.refreshAndFindFileByIoFile(new File(s
));
232 final ItemLatestState state
;
233 final DiffProvider diffProvider
= myVcsRoot
.vcs
.getDiffProvider();
235 // doesnt matter if directory or not
236 state
= diffProvider
.getLastRevision(FilePathImpl
.createForDeletedFile(new File(s
), false));
238 state
= diffProvider
.getLastRevision(vf
);
240 final VcsRevisionNumber newNumber
= (state
== null) || state
.isDefaultHead() ? UNKNOWN
: state
.getNumber();
242 final Pair
<VcsRoot
, VcsRevisionNumber
> oldPair
;
243 synchronized (myLock
) {
244 oldPair
= myData
.get(s
);
245 myData
.put(s
, new Pair
<VcsRoot
, VcsRevisionNumber
>(myVcsRoot
, newNumber
));
248 if ((oldPair
== null) || (oldPair
!= null) && (oldPair
.getSecond().compareTo(newNumber
) != 0)) {
249 LOG
.debug("refresh triggered by " + s
);
250 mySomethingChanged
= true;
255 private class MyShouldUpdateChecker
implements Computable
<Boolean
> {
256 private final VcsRoot myVcsRoot
;
258 public MyShouldUpdateChecker(final VcsRoot vcsRoot
) {
262 public Boolean
compute() {
263 final AbstractVcs vcs
= myVcsRoot
.vcs
;
264 // won't be called in parallel for same vcs -> just synchronized map is ok
265 final String vcsName
= vcs
.getName();
266 LOG
.debug("should update for: " + vcsName
+ " root: " + myVcsRoot
.path
.getPath());
267 final VcsRevisionNumber latestNew
= vcs
.getDiffProvider().getLatestCommittedRevision(myVcsRoot
.path
);
269 final VcsRevisionNumber latestKnown
= myLatestRevisionsMap
.get(vcsName
);
271 if (latestNew
== null) return true;
272 if ((latestKnown
== null) || (latestNew
.compareTo(latestKnown
) != 0)) {
273 myLatestRevisionsMap
.put(vcsName
, latestNew
);
280 private VcsRevisionNumber
getNumber(final String path
) {
281 synchronized (myLock
) {
282 final Pair
<VcsRoot
, VcsRevisionNumber
> pair
= myData
.get(path
);
283 return pair
== null ? NOT_LOADED
: pair
.getSecond();
287 public boolean isUpToDate(final Change change
) {
288 if (change
.getBeforeRevision() != null && change
.getAfterRevision() != null && (! change
.isMoved()) && (! change
.isRenamed())) {
289 return getRevisionState(change
.getBeforeRevision());
291 return getRevisionState(change
.getBeforeRevision()) && getRevisionState(change
.getAfterRevision());
294 private boolean getRevisionState(final ContentRevision revision
) {
295 if (revision
!= null) {
296 final VcsRevisionNumber local
= revision
.getRevisionNumber();
297 final String path
= revision
.getFile().getIOFile().getAbsolutePath();
298 final VcsRevisionNumber remote
= getNumber(path
);
299 if ((NOT_LOADED
== remote
) || (UNKNOWN
== remote
)) {
302 return local
.compareTo(remote
) == 0;