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
.committed
;
18 import com
.intellij
.concurrency
.JobScheduler
;
19 import com
.intellij
.openapi
.Disposable
;
20 import com
.intellij
.openapi
.application
.ApplicationManager
;
21 import com
.intellij
.openapi
.application
.ModalityState
;
22 import com
.intellij
.openapi
.components
.PersistentStateComponent
;
23 import com
.intellij
.openapi
.components
.RoamingType
;
24 import com
.intellij
.openapi
.components
.State
;
25 import com
.intellij
.openapi
.components
.Storage
;
26 import com
.intellij
.openapi
.diagnostic
.Logger
;
27 import com
.intellij
.openapi
.progress
.ProcessCanceledException
;
28 import com
.intellij
.openapi
.progress
.ProgressManagerQueue
;
29 import com
.intellij
.openapi
.project
.Project
;
30 import com
.intellij
.openapi
.util
.Computable
;
31 import com
.intellij
.openapi
.util
.Disposer
;
32 import com
.intellij
.openapi
.util
.Pair
;
33 import com
.intellij
.openapi
.util
.Ref
;
34 import com
.intellij
.openapi
.vcs
.*;
35 import com
.intellij
.openapi
.vcs
.changes
.Change
;
36 import com
.intellij
.openapi
.vcs
.update
.UpdatedFiles
;
37 import com
.intellij
.openapi
.vcs
.versionBrowser
.ChangeBrowserSettings
;
38 import com
.intellij
.openapi
.vcs
.versionBrowser
.CommittedChangeList
;
39 import com
.intellij
.openapi
.vfs
.VirtualFile
;
40 import com
.intellij
.util
.Consumer
;
41 import com
.intellij
.util
.NotNullFunction
;
42 import com
.intellij
.util
.containers
.ConcurrentHashMap
;
43 import com
.intellij
.util
.containers
.MultiMap
;
44 import com
.intellij
.util
.messages
.MessageBus
;
45 import com
.intellij
.util
.messages
.Topic
;
46 import org
.jetbrains
.annotations
.NonNls
;
47 import org
.jetbrains
.annotations
.NotNull
;
48 import org
.jetbrains
.annotations
.Nullable
;
49 import org
.jetbrains
.annotations
.TestOnly
;
52 import java
.io
.IOException
;
54 import java
.util
.concurrent
.ScheduledFuture
;
55 import java
.util
.concurrent
.TimeUnit
;
61 name
="CommittedChangesCache",
62 roamingType
= RoamingType
.DISABLED
,
66 file
= "$WORKSPACE_FILE$"
69 public class CommittedChangesCache
implements PersistentStateComponent
<CommittedChangesCache
.State
> {
70 private static final Logger LOG
= Logger
.getInstance("#com.intellij.openapi.vcs.changes.committed.CommittedChangesCache");
72 private final Project myProject
;
73 private final MessageBus myBus
;
74 private final ProgressManagerQueue myTaskQueue
;
75 private boolean myRefreshingIncomingChanges
= false;
76 private int myPendingUpdateCount
= 0;
77 private State myState
= new State();
78 private ScheduledFuture myFuture
;
79 private List
<CommittedChangeList
> myCachedIncomingChangeLists
;
80 private final Set
<CommittedChangeList
> myNewIncomingChanges
= new LinkedHashSet
<CommittedChangeList
>();
81 private final ProjectLevelVcsManager myVcsManager
;
83 public static final Change
[] ALL_CHANGES
= new Change
[0];
84 private MyRefreshRunnable myRefresnRunnable
;
86 private final Map
<String
, Pair
<Long
, List
<CommittedChangeList
>>> myExternallyLoadedChangeLists
;
87 private final CachesHolder myCachesHolder
;
88 private final RepositoryLocationCache myLocationCache
;
90 public static class State
{
91 private int myInitialCount
= 500;
92 private int myInitialDays
= 90;
93 private int myRefreshInterval
= 30;
94 private boolean myRefreshEnabled
= true;
96 public int getInitialCount() {
97 return myInitialCount
;
100 public void setInitialCount(final int initialCount
) {
101 myInitialCount
= initialCount
;
104 public int getInitialDays() {
105 return myInitialDays
;
108 public void setInitialDays(final int initialDays
) {
109 myInitialDays
= initialDays
;
112 public int getRefreshInterval() {
113 return myRefreshInterval
;
116 public void setRefreshInterval(final int refreshInterval
) {
117 myRefreshInterval
= refreshInterval
;
120 public boolean isRefreshEnabled() {
121 return myRefreshEnabled
;
124 public void setRefreshEnabled(final boolean refreshEnabled
) {
125 myRefreshEnabled
= refreshEnabled
;
129 public static final Topic
<CommittedChangesListener
> COMMITTED_TOPIC
= new Topic
<CommittedChangesListener
>("committed changes updates",
130 CommittedChangesListener
.class);
132 public static CommittedChangesCache
getInstance(Project project
) {
133 return project
.getComponent(CommittedChangesCache
.class);
136 public CommittedChangesCache(final Project project
, final MessageBus bus
, final ProjectLevelVcsManager vcsManager
) {
139 myLocationCache
= new RepositoryLocationCache(project
);
140 myCachesHolder
= new CachesHolder(project
, myLocationCache
);
141 myTaskQueue
= new ProgressManagerQueue(project
, VcsBundle
.message("committed.changes.refresh.progress"));
142 myVcsManager
= vcsManager
;
143 Disposer
.register(project
, new Disposable() {
144 public void dispose() {
145 cancelRefreshTimer();
148 myExternallyLoadedChangeLists
= new ConcurrentHashMap
<String
, Pair
<Long
, List
<CommittedChangeList
>>>();
151 public MessageBus
getMessageBus() {
155 public State
getState() {
159 public void loadState(State state
) {
161 updateRefreshTimer();
165 public CommittedChangesProvider
getProviderForProject() {
166 final AbstractVcs
[] vcss
= myVcsManager
.getAllActiveVcss();
167 List
<AbstractVcs
> vcsWithProviders
= new ArrayList
<AbstractVcs
>();
168 for(AbstractVcs vcs
: vcss
) {
169 if (vcs
.getCommittedChangesProvider() != null) {
170 vcsWithProviders
.add(vcs
);
173 if (vcsWithProviders
.isEmpty()) {
176 if (vcsWithProviders
.size() == 1) {
177 return vcsWithProviders
.get(0).getCommittedChangesProvider();
179 return new CompositeCommittedChangesProvider(myProject
, vcsWithProviders
.toArray(new AbstractVcs
[vcsWithProviders
.size()]));
182 public boolean isMaxCountSupportedForProject() {
183 for(AbstractVcs vcs
: myVcsManager
.getAllActiveVcss()) {
184 final CommittedChangesProvider provider
= vcs
.getCommittedChangesProvider();
185 if (provider
instanceof CachingCommittedChangesProvider
) {
186 final CachingCommittedChangesProvider cachingProvider
= (CachingCommittedChangesProvider
)provider
;
187 if (!cachingProvider
.isMaxCountSupported()) {
195 private class MyProjectChangesLoader
implements Runnable
{
196 private final ChangeBrowserSettings mySettings
;
197 private final int myMaxCount
;
198 private final boolean myCacheOnly
;
199 private final Consumer
<List
<CommittedChangeList
>> myConsumer
;
200 private final Consumer
<List
<VcsException
>> myErrorConsumer
;
202 private final LinkedHashSet
<CommittedChangeList
> myResult
= new LinkedHashSet
<CommittedChangeList
>();
203 private final List
<VcsException
> myExceptions
= new ArrayList
<VcsException
>();
204 private boolean myDisposed
= false;
206 private MyProjectChangesLoader(ChangeBrowserSettings settings
, int maxCount
, boolean cacheOnly
,
207 Consumer
<List
<CommittedChangeList
>> consumer
, Consumer
<List
<VcsException
>> errorConsumer
) {
208 mySettings
= settings
;
209 myMaxCount
= maxCount
;
210 myCacheOnly
= cacheOnly
;
211 myConsumer
= consumer
;
212 myErrorConsumer
= errorConsumer
;
216 for(AbstractVcs vcs
: myVcsManager
.getAllActiveVcss()) {
217 final CommittedChangesProvider provider
= vcs
.getCommittedChangesProvider();
218 if (provider
== null) continue;
220 final VcsCommittedListsZipper vcsZipper
= provider
.getZipper();
221 CommittedListsSequencesZipper zipper
= null;
222 if (vcsZipper
!= null) {
223 zipper
= new CommittedListsSequencesZipper(vcsZipper
);
225 boolean zipSupported
= zipper
!= null;
227 final Map
<VirtualFile
, RepositoryLocation
> map
= myCachesHolder
.getAllRootsUnderVcs(vcs
);
229 for (VirtualFile root
: map
.keySet()) {
230 if (myProject
.isDisposed()) return;
232 final RepositoryLocation location
= map
.get(root
);
235 final List
<CommittedChangeList
> lists
= getChanges(mySettings
, root
, vcs
, myMaxCount
, myCacheOnly
, provider
, location
);
238 zipper
.add(location
, lists
);
240 myResult
.addAll(lists
);
244 catch (VcsException e
) {
247 catch(ProcessCanceledException e
) {
253 myResult
.addAll(zipper
.execute());
257 ApplicationManager
.getApplication().invokeLater(new Runnable() {
259 LOG
.info("FINISHED CommittedChangesCache.getProjectChangesAsync - execution in queue");
260 if (myProject
.isDisposed()) {
263 if (myExceptions
.size() > 0) {
264 myErrorConsumer
.consume(myExceptions
);
266 else if (!myDisposed
) {
267 myConsumer
.consume(new ArrayList
<CommittedChangeList
>(myResult
));
270 }, ModalityState
.NON_MODAL
);
274 public void getProjectChangesAsync(final ChangeBrowserSettings settings
,
276 final boolean cacheOnly
,
277 final Consumer
<List
<CommittedChangeList
>> consumer
,
278 final Consumer
<List
<VcsException
>> errorConsumer
) {
279 final MyProjectChangesLoader loader
= new MyProjectChangesLoader(settings
, maxCount
, cacheOnly
, consumer
, errorConsumer
);
280 myTaskQueue
.run(loader
);
284 public List
<CommittedChangeList
> getChanges(ChangeBrowserSettings settings
, final VirtualFile file
, @NotNull final AbstractVcs vcs
,
285 final int maxCount
, final boolean cacheOnly
, final CommittedChangesProvider provider
,
286 final RepositoryLocation location
) throws VcsException
{
287 if (settings
instanceof CompositeCommittedChangesProvider
.CompositeChangeBrowserSettings
) {
288 settings
= ((CompositeCommittedChangesProvider
.CompositeChangeBrowserSettings
) settings
).get(vcs
);
290 if (provider
instanceof CachingCommittedChangesProvider
) {
293 ChangesCacheFile cacheFile
= myCachesHolder
.getCacheFile(vcs
, file
, location
);
294 if (!cacheFile
.isEmpty()) {
295 return cacheFile
.readChanges(settings
, maxCount
);
300 if (canGetFromCache(vcs
, settings
, file
, location
, maxCount
)) {
301 return getChangesWithCaching(vcs
, settings
, file
, location
, maxCount
);
305 catch (IOException e
) {
309 //noinspection unchecked
310 return provider
.getCommittedChanges(settings
, location
, maxCount
);
313 private boolean canGetFromCache(final AbstractVcs vcs
, final ChangeBrowserSettings settings
,
314 final VirtualFile root
, final RepositoryLocation location
, final int maxCount
) throws IOException
{
315 ChangesCacheFile cacheFile
= myCachesHolder
.getCacheFile(vcs
, root
, location
);
316 if (cacheFile
.isEmpty()) {
317 return true; // we'll initialize the cache and check again after that
319 if (settings
.USE_DATE_BEFORE_FILTER
&& !settings
.USE_DATE_AFTER_FILTER
) {
320 return cacheFile
.hasCompleteHistory();
322 if (settings
.USE_CHANGE_BEFORE_FILTER
&& !settings
.USE_CHANGE_AFTER_FILTER
) {
323 return cacheFile
.hasCompleteHistory();
326 boolean hasDateFilter
= settings
.USE_DATE_AFTER_FILTER
|| settings
.USE_DATE_BEFORE_FILTER
|| settings
.USE_CHANGE_AFTER_FILTER
|| settings
.USE_CHANGE_BEFORE_FILTER
;
327 boolean hasNonDateFilter
= settings
.isNonDateFilterSpecified();
328 if (!hasDateFilter
&& hasNonDateFilter
) {
329 return cacheFile
.hasCompleteHistory();
331 if (settings
.USE_DATE_AFTER_FILTER
&& settings
.getDateAfter().getTime() < cacheFile
.getFirstCachedDate().getTime()) {
332 return cacheFile
.hasCompleteHistory();
334 if (settings
.USE_CHANGE_AFTER_FILTER
&& settings
.getChangeAfterFilter().longValue() < cacheFile
.getFirstCachedChangelist()) {
335 return cacheFile
.hasCompleteHistory();
340 public boolean hasCachesForAnyRoot() {
342 return hasCachesWithEmptiness(false);
344 catch (ProcessCanceledException e
) {
349 public boolean hasEmptyCaches() {
351 return hasCachesWithEmptiness(true);
353 catch (ProcessCanceledException e
) {
358 private boolean hasCachesWithEmptiness(final boolean emptiness
) {
359 final Ref
<Boolean
> resultRef
= new Ref
<Boolean
>(Boolean
.FALSE
);
360 myCachesHolder
.iterateAllCaches(new NotNullFunction
<ChangesCacheFile
, Boolean
>() {
362 public Boolean
fun(final ChangesCacheFile changesCacheFile
) {
364 if (changesCacheFile
.isEmpty() == emptiness
) {
369 catch (IOException e
) {
375 return resultRef
.get();
379 public Iterator
<ChangesBunch
> getBackBunchedIterator(final AbstractVcs vcs
, final VirtualFile root
, final RepositoryLocation location
, final int bunchSize
) {
380 final ChangesCacheFile cacheFile
= myCachesHolder
.getCacheFile(vcs
, root
, location
);
382 if (! cacheFile
.isEmpty()) {
383 return cacheFile
.getBackBunchedIterator(bunchSize
);
386 catch (IOException e
) {
392 private List
<CommittedChangeList
> getChangesWithCaching(final AbstractVcs vcs
,
393 final ChangeBrowserSettings settings
,
394 final VirtualFile root
,
395 final RepositoryLocation location
,
396 final int maxCount
) throws VcsException
, IOException
{
397 ChangesCacheFile cacheFile
= myCachesHolder
.getCacheFile(vcs
, root
, location
);
398 if (cacheFile
.isEmpty()) {
399 List
<CommittedChangeList
> changes
= initCache(cacheFile
);
400 if (canGetFromCache(vcs
, settings
, root
, location
, maxCount
)) {
401 settings
.filterChanges(changes
);
402 return trimToSize(changes
, maxCount
);
404 //noinspection unchecked
405 return cacheFile
.getProvider().getCommittedChanges(settings
, location
, maxCount
);
408 List
<CommittedChangeList
> changes
= cacheFile
.readChanges(settings
, maxCount
);
409 List
<CommittedChangeList
> newChanges
= refreshCache(cacheFile
);
410 settings
.filterChanges(newChanges
);
411 changes
.addAll(newChanges
);
412 return trimToSize(changes
, maxCount
);
417 public void refreshAllCaches() throws IOException
, VcsException
{
418 final Collection
<ChangesCacheFile
> files
= myCachesHolder
.getAllCaches();
419 for(ChangesCacheFile file
: files
) {
420 if (file
.isEmpty()) {
429 private List
<CommittedChangeList
> initCache(final ChangesCacheFile cacheFile
) throws VcsException
, IOException
{
430 debug("Initializing cache for " + cacheFile
.getLocation());
431 final CachingCommittedChangesProvider provider
= cacheFile
.getProvider();
432 final RepositoryLocation location
= cacheFile
.getLocation();
433 final ChangeBrowserSettings settings
= provider
.createDefaultSettings();
435 if (isMaxCountSupportedForProject()) {
436 maxCount
= myState
.getInitialCount();
439 settings
.USE_DATE_AFTER_FILTER
= true;
440 Calendar calendar
= Calendar
.getInstance();
441 calendar
.add(Calendar
.DAY_OF_YEAR
, -myState
.getInitialDays());
442 settings
.setDateAfter(calendar
.getTime());
444 //noinspection unchecked
445 List
<CommittedChangeList
> changes
= provider
.getCommittedChanges(settings
, location
, maxCount
);
446 // when initially initializing cache, assume all changelists are locally available
447 writeChangesInReadAction(cacheFile
, changes
); // this sorts changes in chronological order
448 if (maxCount
> 0 && changes
.size() < myState
.getInitialCount()) {
449 cacheFile
.setHaveCompleteHistory(true);
451 if (changes
.size() > 0) {
452 myBus
.syncPublisher(COMMITTED_TOPIC
).changesLoaded(location
, changes
);
457 // todo: fix - would externally loaded nesseccerily for file? i.e. just not efficient now
458 private List
<CommittedChangeList
> refreshCache(final ChangesCacheFile cacheFile
) throws VcsException
, IOException
{
459 final List
<CommittedChangeList
> newLists
= new ArrayList
<CommittedChangeList
>();
461 final CachingCommittedChangesProvider provider
= cacheFile
.getProvider();
462 final RepositoryLocation location
= cacheFile
.getLocation();
464 final Pair
<Long
, List
<CommittedChangeList
>> externalLists
= myExternallyLoadedChangeLists
.get(location
.getKey());
465 final long latestChangeList
= getLatestListForFile(cacheFile
);
466 if ((externalLists
!= null) && (latestChangeList
== externalLists
.first
.longValue())) {
467 newLists
.addAll(appendLoadedChanges(cacheFile
, location
, externalLists
.second
));
468 myExternallyLoadedChangeLists
.clear();
471 final ChangeBrowserSettings defaultSettings
= provider
.createDefaultSettings();
473 if (provider
.refreshCacheByNumber()) {
474 final long number
= cacheFile
.getLastCachedChangelist();
475 debug("Refreshing cache for " + location
+ " since #" + number
);
477 defaultSettings
.CHANGE_AFTER
= Long
.toString(number
);
478 defaultSettings
.USE_CHANGE_AFTER_FILTER
= true;
481 maxCount
= myState
.getInitialCount();
485 final Date date
= cacheFile
.getLastCachedDate();
486 debug("Refreshing cache for " + location
+ " since " + date
);
487 defaultSettings
.setDateAfter(date
);
488 defaultSettings
.USE_DATE_AFTER_FILTER
= true;
490 final List
<CommittedChangeList
> newChanges
= provider
.getCommittedChanges(defaultSettings
, location
, maxCount
);
491 debug("Loaded " + newChanges
.size() + " new changelists");
492 newLists
.addAll(appendLoadedChanges(cacheFile
, location
, newChanges
));
497 private static void debug(@NonNls String message
) {
501 private List
<CommittedChangeList
> appendLoadedChanges(final ChangesCacheFile cacheFile
, final RepositoryLocation location
,
502 final List
<CommittedChangeList
> newChanges
) throws IOException
{
503 final List
<CommittedChangeList
> savedChanges
= writeChangesInReadAction(cacheFile
, newChanges
);
504 if (savedChanges
.size() > 0) {
505 myBus
.syncPublisher(COMMITTED_TOPIC
).changesLoaded(location
, savedChanges
);
510 private static List
<CommittedChangeList
> writeChangesInReadAction(final ChangesCacheFile cacheFile
,
511 final List
<CommittedChangeList
> newChanges
) throws IOException
{
512 // ensure that changes are loaded before taking read action, to avoid stalling UI
513 for(CommittedChangeList changeList
: newChanges
) {
514 changeList
.getChanges();
516 final Ref
<IOException
> ref
= new Ref
<IOException
>();
517 final List
<CommittedChangeList
> savedChanges
= ApplicationManager
.getApplication().runReadAction(new Computable
<List
<CommittedChangeList
>>() {
518 public List
<CommittedChangeList
> compute() {
520 return cacheFile
.writeChanges(newChanges
); // skip duplicates;
522 catch (IOException e
) {
534 private static List
<CommittedChangeList
> trimToSize(final List
<CommittedChangeList
> changes
, final int maxCount
) {
536 while(changes
.size() > maxCount
) {
543 private List
<CommittedChangeList
> loadIncomingChanges() {
544 final List
<CommittedChangeList
> result
= new ArrayList
<CommittedChangeList
>();
545 final Collection
<ChangesCacheFile
> caches
= myCachesHolder
.getAllCaches();
547 final MultiMap
<AbstractVcs
, Pair
<RepositoryLocation
, List
<CommittedChangeList
>>> byVcs
=
548 new MultiMap
<AbstractVcs
, Pair
<RepositoryLocation
, List
<CommittedChangeList
>>>();
550 for(ChangesCacheFile cache
: caches
) {
552 if (!cache
.isEmpty()) {
553 debug("Loading incoming changes for " + cache
.getLocation());
554 final List
<CommittedChangeList
> incomingChanges
= cache
.loadIncomingChanges();
555 byVcs
.putValue(cache
.getVcs(), new Pair
<RepositoryLocation
, List
<CommittedChangeList
>>(cache
.getLocation(), incomingChanges
));
558 catch (IOException e
) {
563 for (AbstractVcs vcs
: byVcs
.keySet()) {
564 final CommittedChangesProvider committedChangesProvider
= vcs
.getCommittedChangesProvider();
565 VcsCommittedListsZipper vcsZipper
= committedChangesProvider
.getZipper();
566 if (vcsZipper
!= null) {
567 final VcsCommittedListsZipper incomingZipper
= new IncomingListsZipper(vcsZipper
);
568 final CommittedListsSequencesZipper zipper
= new CommittedListsSequencesZipper(incomingZipper
);
569 for (Pair
<RepositoryLocation
, List
<CommittedChangeList
>> pair
: byVcs
.get(vcs
)) {
570 zipper
.add(pair
.getFirst(), pair
.getSecond());
572 result
.addAll(zipper
.execute());
574 for (Pair
<RepositoryLocation
, List
<CommittedChangeList
>> pair
: byVcs
.get(vcs
)) {
575 result
.addAll(pair
.getSecond());
580 myCachedIncomingChangeLists
= result
;
581 debug("Incoming changes loaded");
582 notifyIncomingChangesUpdated(null);
586 private class IncomingListsZipper
extends VcsCommittedListsZipperAdapter
{
587 private final VcsCommittedListsZipper myVcsZipper
;
589 private IncomingListsZipper(final VcsCommittedListsZipper vcsZipper
) {
591 myVcsZipper
= vcsZipper
;
594 public Pair
<List
<RepositoryLocationGroup
>, List
<RepositoryLocation
>> groupLocations(final List
<RepositoryLocation
> in
) {
595 return myVcsZipper
.groupLocations(in
);
599 public CommittedChangeList
zip(final RepositoryLocationGroup group
, final List
<CommittedChangeList
> lists
) {
600 if (lists
.size() == 1) {
603 final CommittedChangeList victim
= lists
.get(0) instanceof ReceivedChangeList ?
(((ReceivedChangeList
) lists
.get(0)).getBaseList()) :
605 final ReceivedChangeList result
= new ReceivedChangeList(victim
);
606 result
.setForcePartial(false);
607 final Set
<Change
> baseChanges
= new HashSet
<Change
>();
609 for (CommittedChangeList list
: lists
) {
610 baseChanges
.addAll(list
instanceof ReceivedChangeList ?
((ReceivedChangeList
) list
).getBaseList().getChanges() : list
.getChanges());
612 final Collection
<Change
> changes
= list
.getChanges();
613 for (Change change
: changes
) {
614 if (! result
.getChanges().contains(change
)) {
615 result
.addChange(change
);
619 result
.setForcePartial(baseChanges
.size() != result
.getChanges().size());
624 public long getNumber(final CommittedChangeList list
) {
625 return myVcsZipper
.getNumber(list
);
629 public void loadIncomingChangesAsync(@Nullable final Consumer
<List
<CommittedChangeList
>> consumer
) {
630 debug("Loading incoming changes");
631 final Runnable task
= new Runnable() {
633 final List
<CommittedChangeList
> list
= loadIncomingChanges();
634 if (consumer
!= null) {
635 consumer
.consume(new ArrayList
<CommittedChangeList
>(list
));
639 myTaskQueue
.run(task
);
643 public List
<CommittedChangeList
> getCachedIncomingChanges() {
644 return myCachedIncomingChangeLists
;
647 public void processUpdatedFiles(final UpdatedFiles updatedFiles
) {
648 final Runnable task
= new Runnable() {
650 debug("Processing updated files");
651 final Collection
<ChangesCacheFile
> caches
= myCachesHolder
.getAllCaches();
652 for(final ChangesCacheFile cache
: caches
) {
653 myPendingUpdateCount
++;
655 if (cache
.isEmpty()) {
656 pendingUpdateProcessed();
659 debug("Processing updated files in " + cache
.getLocation());
660 boolean needRefresh
= cache
.processUpdatedFiles(updatedFiles
, myNewIncomingChanges
);
662 debug("Found unaccounted files, requesting refresh");
663 // todo do we need double-queueing here???
664 processUpdatedFilesAfterRefresh(cache
, updatedFiles
);
667 debug("Clearing cached incoming changelists");
668 myCachedIncomingChangeLists
= null;
669 pendingUpdateProcessed();
672 catch (IOException e
) {
678 myTaskQueue
.run(task
);
681 private void pendingUpdateProcessed() {
682 myPendingUpdateCount
--;
683 if (myPendingUpdateCount
== 0) {
684 notifyIncomingChangesUpdated(myNewIncomingChanges
);
685 myNewIncomingChanges
.clear();
689 private void processUpdatedFilesAfterRefresh(final ChangesCacheFile cache
, final UpdatedFiles updatedFiles
) {
690 refreshCacheAsync(cache
, false, new RefreshResultConsumer() {
691 public void receivedChanges(final List
<CommittedChangeList
> committedChangeLists
) {
693 debug("Processing updated files after refresh in " + cache
.getLocation());
694 boolean result
= true;
695 if (committedChangeLists
.size() > 0) {
696 // received some new changelists, try to process updated files again
697 result
= cache
.processUpdatedFiles(updatedFiles
, myNewIncomingChanges
);
699 debug(result ?
"Still have unaccounted files" : "No more unaccounted files");
700 // for svn, we won't get exact revision numbers in updatedFiles, so we have to double-check by
701 // checking revisions we have locally
703 cache
.refreshIncomingChanges();
704 debug("Clearing cached incoming changelists");
705 myCachedIncomingChangeLists
= null;
707 pendingUpdateProcessed();
709 catch (IOException e
) {
712 catch(VcsException e
) {
713 notifyRefreshError(e
);
717 public void receivedError(VcsException ex
) {
718 notifyRefreshError(ex
);
723 private void notifyIncomingChangesUpdated(@Nullable final Collection
<CommittedChangeList
> receivedChanges
) {
724 final ArrayList
<CommittedChangeList
> listCopy
= receivedChanges
== null ?
null : new ArrayList
<CommittedChangeList
>(receivedChanges
);
725 myBus
.syncPublisher(COMMITTED_TOPIC
).incomingChangesUpdated(listCopy
);
728 private void notifyRefreshError(final VcsException e
) {
729 myBus
.syncPublisher(COMMITTED_TOPIC
).refreshErrorStatusChanged(e
);
732 public boolean isRefreshingIncomingChanges() {
733 return myRefreshingIncomingChanges
;
736 public boolean refreshIncomingChanges() {
737 boolean hasChanges
= false;
738 final Collection
<ChangesCacheFile
> caches
= myCachesHolder
.getAllCaches();
739 for(ChangesCacheFile file
: caches
) {
741 if (file
.isEmpty()) {
744 debug("Refreshing incoming changes for " + file
.getLocation());
745 boolean changesForCache
= file
.refreshIncomingChanges();
746 hasChanges
|= changesForCache
;
748 catch (IOException e
) {
751 catch(VcsException e
) {
752 notifyRefreshError(e
);
758 public void refreshIncomingChangesAsync() {
759 debug("Refreshing incoming changes in background");
760 myRefreshingIncomingChanges
= true;
761 final Runnable task
= new Runnable() {
763 refreshIncomingChanges();
765 ApplicationManager
.getApplication().invokeLater(new Runnable() {
767 myRefreshingIncomingChanges
= false;
768 debug("Incoming changes refresh complete, clearing cached incoming changes");
769 notifyReloadIncomingChanges();
771 }, ModalityState
.NON_MODAL
);
774 myTaskQueue
.run(task
);
777 public void refreshAllCachesAsync(final boolean initIfEmpty
) {
778 final Runnable task
= new Runnable() {
780 final List
<ChangesCacheFile
> files
= myCachesHolder
.getAllCaches();
781 final RefreshResultConsumer notifyConsumer
= new RefreshResultConsumer() {
782 private VcsException myError
= null;
783 private int myCount
= 0;
785 public void receivedChanges(List
<CommittedChangeList
> changes
) {
786 if (changes
.size() > 0) {
787 notifyReloadIncomingChanges();
789 myProject
.getMessageBus().syncPublisher(CommittedChangesTreeBrowser
.ITEMS_RELOADED
).emptyRefresh();
794 public void receivedError(VcsException ex
) {
799 private void checkDone() {
801 if (myCount
== files
.size()) {
802 notifyRefreshError(myError
);
806 for(ChangesCacheFile file
: files
) {
807 refreshCacheAsync(file
, initIfEmpty
, notifyConsumer
, false);
811 myTaskQueue
.run(task
);
814 private void notifyReloadIncomingChanges() {
815 myCachedIncomingChangeLists
= null;
816 notifyIncomingChangesUpdated(null);
819 private void refreshCacheAsync(final ChangesCacheFile cache
, final boolean initIfEmpty
,
820 @Nullable final RefreshResultConsumer consumer
) {
821 refreshCacheAsync(cache
, initIfEmpty
, consumer
, true);
824 private void refreshCacheAsync(final ChangesCacheFile cache
, final boolean initIfEmpty
,
825 @Nullable final RefreshResultConsumer consumer
, final boolean asynch
) {
827 if (!initIfEmpty
&& cache
.isEmpty()) {
831 catch (IOException e
) {
835 final Runnable task
= new Runnable() {
838 final List
<CommittedChangeList
> list
;
839 if (initIfEmpty
&& cache
.isEmpty()) {
840 list
= initCache(cache
);
843 list
= refreshCache(cache
);
845 if (consumer
!= null) {
846 consumer
.receivedChanges(list
);
849 catch(ProcessCanceledException ex
) {
852 catch (IOException e
) {
855 catch (VcsException e
) {
856 if (consumer
!= null) {
857 consumer
.receivedError(e
);
863 myTaskQueue
.run(task
);
869 private void updateRefreshTimer() {
870 cancelRefreshTimer();
871 if (myState
.isRefreshEnabled()) {
872 myRefresnRunnable
= new MyRefreshRunnable(this);
873 // if "schedule with fixed rate" is used, then after waking up from stand-by mode, events are generated for inactive period
874 // it does not make sense
875 myFuture
= JobScheduler
.getScheduler().scheduleWithFixedDelay(myRefresnRunnable
,
876 myState
.getRefreshInterval()*60, myState
.getRefreshInterval()*60,
881 private void cancelRefreshTimer() {
882 if (myRefresnRunnable
!= null) {
883 myRefresnRunnable
.cancel();
884 myRefresnRunnable
= null;
886 if (myFuture
!= null) {
887 myFuture
.cancel(false);
893 public Pair
<CommittedChangeList
, Change
> getIncomingChangeList(final VirtualFile file
) {
894 if (myCachedIncomingChangeLists
!= null) {
895 File ioFile
= new File(file
.getPath());
896 for(CommittedChangeList changeList
: myCachedIncomingChangeLists
) {
897 for(Change change
: changeList
.getChanges()) {
898 if (change
.affectsFile(ioFile
)) {
899 return Pair
.create(changeList
, change
);
907 private long getLatestListForFile(final ChangesCacheFile file
) {
909 if ((file
== null) || (file
.isEmpty())) {
912 return file
.getLastCachedChangelist();
914 catch (IOException e
) {
919 public CachesHolder
getCachesHolder() {
920 return myCachesHolder
;
923 public void submitExternallyLoaded(final RepositoryLocation location
, final long myLastCl
, final List
<CommittedChangeList
> lists
) {
924 myExternallyLoadedChangeLists
.put(location
.getKey(), new Pair
<Long
, List
<CommittedChangeList
>>(myLastCl
, lists
));
927 private interface RefreshResultConsumer
{
928 void receivedChanges(List
<CommittedChangeList
> changes
);
929 void receivedError(VcsException ex
);
932 private static class MyRefreshRunnable
implements Runnable
{
933 private CommittedChangesCache myCache
;
935 private MyRefreshRunnable(final CommittedChangesCache cache
) {
939 private void cancel() {
944 final CommittedChangesCache cache
= myCache
;
945 if (cache
== null) return;
946 cache
.refreshAllCachesAsync(false);
947 final List
<ChangesCacheFile
> list
= cache
.getCachesHolder().getAllCaches();
948 for(ChangesCacheFile file
: list
) {
949 if (file
.getProvider().refreshIncomingWithCommitted()) {
950 cache
.refreshIncomingChangesAsync();
957 public RepositoryLocationCache
getLocationCache() {
958 return myLocationCache
;