vcs: Refactored ChangeListManagerImpl.addUnversionedFiles() - code simplified, method...
[idea/community.git] / platform / vcs-impl / src / com / intellij / openapi / vcs / changes / ChangeListManagerImpl.java
1 /*
2  * Copyright 2000-2014 JetBrains s.r.o.
3  *
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
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
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.
15  */
16 package com.intellij.openapi.vcs.changes;
17
18 import com.intellij.ide.highlighter.WorkspaceFileType;
19 import com.intellij.lifecycle.PeriodicalTasksCloser;
20 import com.intellij.openapi.application.ApplicationManager;
21 import com.intellij.openapi.application.ModalityState;
22 import com.intellij.openapi.application.RuntimeInterruptedException;
23 import com.intellij.openapi.components.ProjectComponent;
24 import com.intellij.openapi.diagnostic.Logger;
25 import com.intellij.openapi.fileEditor.FileDocumentManager;
26 import com.intellij.openapi.module.Module;
27 import com.intellij.openapi.module.ModuleManager;
28 import com.intellij.openapi.progress.EmptyProgressIndicator;
29 import com.intellij.openapi.progress.ProcessCanceledException;
30 import com.intellij.openapi.progress.ProgressIndicator;
31 import com.intellij.openapi.project.DumbAwareRunnable;
32 import com.intellij.openapi.project.Project;
33 import com.intellij.openapi.roots.ModuleRootManager;
34 import com.intellij.openapi.roots.ProjectFileIndex;
35 import com.intellij.openapi.roots.impl.DirectoryIndexExcludePolicy;
36 import com.intellij.openapi.ui.MessageType;
37 import com.intellij.openapi.ui.Messages;
38 import com.intellij.openapi.util.*;
39 import com.intellij.openapi.util.io.FileUtil;
40 import com.intellij.openapi.util.registry.Registry;
41 import com.intellij.openapi.util.text.StringUtil;
42 import com.intellij.openapi.vcs.*;
43 import com.intellij.openapi.vcs.changes.conflicts.ChangelistConflictTracker;
44 import com.intellij.openapi.vcs.changes.ui.CommitHelper;
45 import com.intellij.openapi.vcs.checkin.CheckinEnvironment;
46 import com.intellij.openapi.vcs.checkin.CheckinHandler;
47 import com.intellij.openapi.vcs.impl.*;
48 import com.intellij.openapi.vcs.readOnlyHandler.ReadonlyStatusHandlerImpl;
49 import com.intellij.openapi.vcs.ui.VcsBalloonProblemNotifier;
50 import com.intellij.openapi.vfs.*;
51 import com.intellij.ui.EditorNotifications;
52 import com.intellij.util.*;
53 import com.intellij.util.concurrency.Semaphore;
54 import com.intellij.util.containers.ContainerUtil;
55 import com.intellij.util.containers.MultiMap;
56 import com.intellij.util.continuation.ContinuationPause;
57 import com.intellij.util.messages.Topic;
58 import com.intellij.vcsUtil.Rethrow;
59 import com.intellij.vcsUtil.VcsUtil;
60 import org.jdom.Element;
61 import org.jetbrains.annotations.NonNls;
62 import org.jetbrains.annotations.NotNull;
63 import org.jetbrains.annotations.Nullable;
64 import org.jetbrains.annotations.TestOnly;
65
66 import javax.swing.*;
67 import java.io.File;
68 import java.util.*;
69 import java.util.concurrent.ScheduledExecutorService;
70 import java.util.concurrent.ScheduledThreadPoolExecutor;
71 import java.util.concurrent.atomic.AtomicReference;
72
73 /**
74  * @author max
75  */
76 public class ChangeListManagerImpl extends ChangeListManagerEx implements ProjectComponent, ChangeListOwner, JDOMExternalizable,
77                                                                           RoamingTypeDisabled {
78   public static final Logger LOG = Logger.getInstance("#com.intellij.openapi.vcs.changes.ChangeListManagerImpl");
79   private static final String EXCLUDED_CONVERTED_TO_IGNORED_OPTION = "EXCLUDED_CONVERTED_TO_IGNORED";
80
81   private final Project myProject;
82   private final VcsConfiguration myConfig;
83   private final ChangesViewI myChangesViewManager;
84   private final FileStatusManager myFileStatusManager;
85   private final UpdateRequestsQueue myUpdater;
86
87   private static final AtomicReference<ScheduledExecutorService> ourUpdateAlarm = new AtomicReference<ScheduledExecutorService>();
88   static {
89     ourUpdateAlarm.set(createChangeListExecutor());
90   }
91
92   private static ScheduledThreadPoolExecutor createChangeListExecutor() {
93     return VcsUtil.createExecutor("Change List Updater");
94   }
95
96   private final Modifier myModifier;
97
98   private FileHolderComposite myComposite;
99
100   private ChangeListWorker myWorker;
101   private VcsException myUpdateException = null;
102   private Factory<JComponent> myAdditionalInfo;
103
104   private final EventDispatcher<ChangeListListener> myListeners = EventDispatcher.create(ChangeListListener.class);
105
106   private final Object myDataLock = new Object();
107
108   private final List<CommitExecutor> myExecutors = new ArrayList<CommitExecutor>();
109
110   private final IgnoredFilesComponent myIgnoredIdeaLevel;
111   private boolean myExcludedConvertedToIgnored;
112   private volatile ProgressIndicator myUpdateChangesProgressIndicator = createProgressIndicator();
113
114   public static final Topic<LocalChangeListsLoadedListener> LISTS_LOADED = new Topic<LocalChangeListsLoadedListener>(
115     "LOCAL_CHANGE_LISTS_LOADED", LocalChangeListsLoadedListener.class);
116
117   private boolean myShowLocalChangesInvalidated;
118   private final AtomicReference<String> myFreezeName;
119
120   // notifies myListeners on the same thread that local changes update is done
121   private final DelayedNotificator myDelayedNotificator;
122
123   private final VcsListener myVcsListener = new VcsListener() {
124     @Override
125     public void directoryMappingChanged() {
126       VcsDirtyScopeManager.getInstance(myProject).markEverythingDirty();
127     }
128   };
129   private final ChangelistConflictTracker myConflictTracker;
130   private VcsDirtyScopeManager myDirtyScopeManager;
131   private final VcsDirtyScopeVfsListener myVfsListener;
132
133   private boolean myModalNotificationsBlocked;
134   @NotNull private final Collection<LocalChangeList> myListsToBeDeleted = new HashSet<LocalChangeList>();
135
136   public static ChangeListManagerImpl getInstanceImpl(final Project project) {
137     return (ChangeListManagerImpl)PeriodicalTasksCloser.getInstance().safeGetComponent(project, ChangeListManager.class);
138   }
139
140   void setDirtyScopeManager(VcsDirtyScopeManager dirtyScopeManager) {
141     myDirtyScopeManager = dirtyScopeManager;
142   }
143
144   public ChangeListManagerImpl(Project project, final VcsConfiguration config) {
145     myProject = project;
146     myConfig = config;
147     myFreezeName = new AtomicReference<String>(null);
148     myAdditionalInfo = null;
149     myChangesViewManager = myProject.isDefault() ? new DummyChangesView(myProject) : ChangesViewManager.getInstance(myProject);
150     myVfsListener = ApplicationManager.getApplication().getComponent(VcsDirtyScopeVfsListener.class);
151     myFileStatusManager = FileStatusManager.getInstance(myProject);
152     myComposite = new FileHolderComposite(project);
153     myIgnoredIdeaLevel = new IgnoredFilesComponent(myProject, true);
154     myUpdater = new UpdateRequestsQueue(myProject, ourUpdateAlarm, new ActualUpdater());
155
156     myWorker = new ChangeListWorker(myProject, new MyChangesDeltaForwarder(myProject, ourUpdateAlarm));
157     myDelayedNotificator = new DelayedNotificator(myListeners, ourUpdateAlarm);
158     myModifier = new Modifier(myWorker, myDelayedNotificator);
159
160     myConflictTracker = new ChangelistConflictTracker(project, this, myFileStatusManager, EditorNotifications.getInstance(project));
161
162     myListeners.addListener(new ChangeListAdapter() {
163       @Override
164       public void defaultListChanged(final ChangeList oldDefaultList, ChangeList newDefaultList) {
165         final LocalChangeList oldList = (LocalChangeList)oldDefaultList;
166         if (oldDefaultList == null || oldList.hasDefaultName() || oldDefaultList.equals(newDefaultList)) return;
167
168         if (!ApplicationManager.getApplication().isUnitTestMode() &&
169             oldDefaultList.getChanges().isEmpty() &&
170             !oldList.isReadOnly()) {
171
172           invokeAfterUpdate(new Runnable() {
173             @Override
174             public void run() {
175               if (getChangeList(oldList.getId()) == null) {
176                 return; // removed already  
177               }
178               switch (config.REMOVE_EMPTY_INACTIVE_CHANGELISTS) {
179                 case SHOW_CONFIRMATION:
180                   if (myModalNotificationsBlocked) {
181                     myListsToBeDeleted.add(oldList);
182                     return;
183                   }
184
185                   if (!showRemoveEmptyChangeListsProposal(config, Collections.singletonList(oldList))) {
186                     return;
187                   }
188                   break;
189                 case DO_NOTHING_SILENTLY:
190                   return;
191                 case DO_ACTION_SILENTLY:
192                   break;
193               }
194               removeChangeList(oldList);
195             }
196           }, InvokeAfterUpdateMode.SILENT, null, null);
197         }
198       }
199     });
200   }
201
202   /**
203    * Shows the proposal to delete one or more changelists that were default and became empty.
204    *
205    * @return true if the changelists have to be deleted, false if not.
206    */
207   private boolean showRemoveEmptyChangeListsProposal(@NotNull final VcsConfiguration config, @NotNull Collection<LocalChangeList> lists) {
208     if (lists.isEmpty()) {
209       return false;
210     }
211
212     final String question;
213     if (lists.size() == 1) {
214       question = String.format("<html>The empty changelist '%s' is no longer active.<br>Do you want to remove it?</html>",
215                                StringUtil.first(lists.iterator().next().getName(), 30, true));
216     }
217     else {
218       question = String.format("<html>Empty changelists<br/>%s are no longer active.<br>Do you want to remove them?</html>",
219                                StringUtil.join(lists, new Function<LocalChangeList, String>() {
220                                  @Override
221                                  public String fun(LocalChangeList list) {
222                                    return StringUtil.first(list.getName(), 30, true);
223                                  }
224                                }, "<br/>"));
225     }
226
227     VcsConfirmationDialog dialog = new VcsConfirmationDialog(myProject, new VcsShowConfirmationOption() {
228       @Override
229       public Value getValue() {
230         return config.REMOVE_EMPTY_INACTIVE_CHANGELISTS;
231       }
232
233       @Override
234       public void setValue(Value value) {
235         config.REMOVE_EMPTY_INACTIVE_CHANGELISTS = value;
236       }
237
238       @Override
239       public boolean isPersistent() {
240         return true;
241       }
242     }, question, "&Remember my choice");
243     return dialog.showAndGet();
244   }
245
246   @Override
247   @CalledInAwt
248   public void blockModalNotifications() {
249     myModalNotificationsBlocked = true;
250   }
251
252   @Override
253   @CalledInAwt
254   public void unblockModalNotifications() {
255     myModalNotificationsBlocked = false;
256     if (myListsToBeDeleted.isEmpty()) {
257       return;
258     }
259     if (showRemoveEmptyChangeListsProposal(myConfig, myListsToBeDeleted)) {
260       for (LocalChangeList list : myListsToBeDeleted) {
261         removeChangeList(list);
262       }
263     }
264     myListsToBeDeleted.clear();
265   }
266
267   @Override
268   public void projectOpened() {
269     initializeForNewProject();
270
271     final ProjectLevelVcsManager vcsManager = ProjectLevelVcsManager.getInstance(myProject);
272     if (ApplicationManager.getApplication().isUnitTestMode()) {
273       myUpdater.initialized();
274       vcsManager.addVcsListener(myVcsListener);
275     }
276     else {
277       ((ProjectLevelVcsManagerImpl)vcsManager).addInitializationRequest(
278         VcsInitObject.CHANGE_LIST_MANAGER, new DumbAwareRunnable() {
279           @Override
280           public void run() {
281             myUpdater.initialized();
282             broadcastStateAfterLoad();
283             vcsManager.addVcsListener(myVcsListener);
284           }
285         });
286     }
287
288     myConflictTracker.startTracking();
289   }
290
291   private void broadcastStateAfterLoad() {
292     final List<LocalChangeList> listCopy;
293     synchronized (myDataLock) {
294       listCopy = getChangeListsCopy();
295     }
296     if (!myProject.isDisposed()) {
297       myProject.getMessageBus().syncPublisher(LISTS_LOADED).processLoadedLists(listCopy);
298     }
299   }
300
301   private void initializeForNewProject() {
302     ApplicationManager.getApplication().runReadAction(new Runnable() {
303       @Override
304       public void run() {
305         synchronized (myDataLock) {
306           if (myWorker.isEmpty()) {
307             final LocalChangeList list = myWorker.addChangeList(VcsBundle.message("changes.default.changelist.name"), null, null);
308             setDefaultChangeList(list);
309
310             if (myIgnoredIdeaLevel.isEmpty()) {
311               final String name = myProject.getName();
312               myIgnoredIdeaLevel.add(IgnoredBeanFactory.ignoreFile(name + WorkspaceFileType.DOT_DEFAULT_EXTENSION, myProject));
313               myIgnoredIdeaLevel.add(IgnoredBeanFactory.ignoreFile(Project.DIRECTORY_STORE_FOLDER + "/workspace.xml", myProject));
314             }
315           }
316           if (!Registry.is("ide.hide.excluded.files") && !myExcludedConvertedToIgnored) {
317             convertExcludedToIgnored();
318             myExcludedConvertedToIgnored = true;
319           }
320         }
321       }
322     });
323   }
324
325   void convertExcludedToIgnored() {
326     for (DirectoryIndexExcludePolicy policy : DirectoryIndexExcludePolicy.EP_NAME.getExtensions(myProject)) {
327       for (VirtualFile file : policy.getExcludeRootsForProject()) {
328         addDirectoryToIgnoreImplicitly(file.getPath());
329       }
330     }
331
332     ProjectFileIndex fileIndex = ProjectFileIndex.SERVICE.getInstance(myProject);
333     VirtualFileManager virtualFileManager = VirtualFileManager.getInstance();
334     for (Module module : ModuleManager.getInstance(myProject).getModules()) {
335       for (String url : ModuleRootManager.getInstance(module).getExcludeRootUrls()) {
336         VirtualFile file = virtualFileManager.findFileByUrl(url);
337         if (file != null && !fileIndex.isExcluded(file)) {
338           //root is included into some inner module so it shouldn't be ignored
339           continue;
340         }
341         addDirectoryToIgnoreImplicitly(VfsUtilCore.urlToPath(url));
342       }
343     }
344   }
345
346   @Override
347   public void projectClosed() {
348     ProjectLevelVcsManager.getInstance(myProject).removeVcsListener(myVcsListener);
349
350     synchronized (myDataLock) {
351       myUpdateChangesProgressIndicator.cancel();
352     }
353
354     myUpdater.stop();
355     myConflictTracker.stopTracking();
356   }
357
358   @Override
359   @NotNull @NonNls
360   public String getComponentName() {
361     return "ChangeListManager";
362   }
363
364   @Override
365   public void initComponent() {
366   }
367
368   @Override
369   public void disposeComponent() {
370   }
371
372   /**
373    * update itself might produce actions done on AWT thread (invoked-after),
374    * so waiting for its completion on AWT thread is not good runnable is invoked on AWT thread
375    */
376   @Override
377   public void invokeAfterUpdate(final Runnable afterUpdate,
378                                 final InvokeAfterUpdateMode mode,
379                                 @Nullable final String title,
380                                 @Nullable final ModalityState state) {
381     myUpdater.invokeAfterUpdate(afterUpdate, mode, title, null, state);
382   }
383
384   @Override
385   public void invokeAfterUpdate(final Runnable afterUpdate, final InvokeAfterUpdateMode mode, final String title,
386                                 final Consumer<VcsDirtyScopeManager> dirtyScopeManagerFiller, final ModalityState state) {
387     myUpdater.invokeAfterUpdate(afterUpdate, mode, title, dirtyScopeManagerFiller, state);
388   }
389
390   static class DisposedException extends RuntimeException {}
391
392   @Override
393   public void freeze(final ContinuationPause context, final String reason) {
394     myUpdater.setIgnoreBackgroundOperation(true);
395     // this update is nessesary for git, to refresh local changes before
396     invokeAfterUpdate(new Runnable() {
397       @Override
398       public void run() {
399         freezeImmediately(reason);
400         context.ping();
401       }
402     }, InvokeAfterUpdateMode.SILENT_CALLBACK_POOLED, "", ModalityState.NON_MODAL);
403     context.suspend();
404   }
405
406   @Override
407   public void freezeImmediately(@Nullable String reason) {
408     myUpdater.setIgnoreBackgroundOperation(false);
409     myUpdater.pause();
410     myUpdateChangesProgressIndicator.cancel();
411     myFreezeName.set(reason);
412   }
413
414   @Override
415   public void letGo() {
416     myUpdater.go();
417     myFreezeName.set(null);
418   }
419
420   @Override
421   public String isFreezed() {
422     return myFreezeName.get();
423   }
424
425   @Override
426   public void scheduleUpdate() {
427     myUpdater.schedule();
428   }
429
430   @Override
431   public void scheduleUpdate(boolean updateUnversionedFiles) {
432     myUpdater.schedule();
433   }
434
435   private class ActualUpdater implements Runnable {
436     @Override
437     public void run() {
438       updateImmediately();
439     }
440   }
441
442   private void filterOutIgnoredFiles(final List<VcsDirtyScope> scopes) {
443     final Set<VirtualFile> refreshFiles = new HashSet<VirtualFile>();
444     try {
445       synchronized (myDataLock) {
446         final IgnoredFilesHolder fileHolder = (IgnoredFilesHolder)myComposite.get(FileHolder.HolderType.IGNORED);
447
448         for (Iterator<VcsDirtyScope> iterator = scopes.iterator(); iterator.hasNext(); ) {
449           final VcsModifiableDirtyScope scope = (VcsModifiableDirtyScope)iterator.next();
450           final VcsDirtyScopeModifier modifier = scope.getModifier();
451           if (modifier != null) {
452             fileHolder.notifyVcsStarted(scope.getVcs());
453             final Iterator<FilePath> filesIterator = modifier.getDirtyFilesIterator();
454             while (filesIterator.hasNext()) {
455               final FilePath dirtyFile = filesIterator.next();
456               if ((dirtyFile.getVirtualFile() != null) && isIgnoredFile(dirtyFile.getVirtualFile())) {
457                 filesIterator.remove();
458                 fileHolder.addFile(dirtyFile.getVirtualFile());
459                 refreshFiles.add(dirtyFile.getVirtualFile());
460               }
461             }
462             final Collection<VirtualFile> roots = modifier.getAffectedVcsRoots();
463             for (VirtualFile root : roots) {
464               final Iterator<FilePath> dirIterator = modifier.getDirtyDirectoriesIterator(root);
465               while (dirIterator.hasNext()) {
466                 final FilePath dir = dirIterator.next();
467                 if ((dir.getVirtualFile() != null) && isIgnoredFile(dir.getVirtualFile())) {
468                   dirIterator.remove();
469                   fileHolder.addFile(dir.getVirtualFile());
470                   refreshFiles.add(dir.getVirtualFile());
471                 }
472               }
473             }
474             modifier.recheckDirtyKeys();
475             if (scope.isEmpty()) {
476               iterator.remove();
477             }
478           }
479         }
480       }
481     }
482     catch (Exception ex) {
483       LOG.error(ex);
484     }
485     catch (AssertionError ex) {
486       LOG.error(ex);
487     }
488     for (VirtualFile file : refreshFiles) {
489       myFileStatusManager.fileStatusChanged(file);
490     }
491   }
492
493   private void updateImmediately() {
494     final ProjectLevelVcsManager vcsManager = ProjectLevelVcsManager.getInstance(myProject);
495     if (!vcsManager.hasActiveVcss()) return;
496
497     final VcsInvalidated invalidated = myDirtyScopeManager.retrieveScopes();
498     if (checkScopeIsEmpty(invalidated)) return;
499
500     final boolean wasEverythingDirty = invalidated.isEverythingDirty();
501     final List<VcsDirtyScope> scopes = invalidated.getScopes();
502
503     try {
504       checkIfDisposed();
505
506       // copy existsing data to objects that would be updated.
507       // mark for "modifier" that update started (it would create duplicates of modification commands done by user during update;
508       // after update of copies of objects is complete, it would apply the same modifications to copies.)
509       final DataHolder dataHolder;
510       synchronized (myDataLock) {
511         dataHolder = new DataHolder((FileHolderComposite)myComposite.copy(), myWorker.copy(), wasEverythingDirty);
512         myModifier.enterUpdate();
513         if (wasEverythingDirty) {
514           myUpdateException = null;
515           myAdditionalInfo = null;
516         }
517       }
518       final String scopeInString = (!LOG.isDebugEnabled()) ? "" : StringUtil.join(scopes, new Function<VcsDirtyScope, String>() {
519         @Override
520         public String fun(VcsDirtyScope scope) {
521           return scope.toString();
522         }
523       }, "->\n");
524       LOG.debug("refresh procedure started, everything = " + wasEverythingDirty + " dirty scope: " + scopeInString);
525       dataHolder.notifyStart();
526       myChangesViewManager.scheduleRefresh();
527
528       myUpdateChangesProgressIndicator = createProgressIndicator();
529
530       iterateScopes(dataHolder, scopes, wasEverythingDirty);
531
532       final boolean takeChanges = (myUpdateException == null);
533       if (takeChanges) {
534         // update IDEA-level ignored files
535         updateIgnoredFiles(dataHolder.getComposite());
536       }
537
538       clearCurrentRevisionsCache(invalidated);
539       // for the case of project being closed we need a read action here -> to be more consistent
540       ApplicationManager.getApplication().runReadAction(new Runnable() {
541         @Override
542         public void run() {
543           if (myProject.isDisposed()) {
544             return;
545           }
546           synchronized (myDataLock) {
547             // do same modifications to change lists as was done during update + do delayed notifications
548             dataHolder.notifyEnd();
549             // should be applied for notifications to be delivered (they were delayed) - anyway whether we take changes or not
550             myModifier.finishUpdate(dataHolder.getChangeListWorker());
551             // update member from copy
552             if (takeChanges) {
553               final ChangeListWorker oldWorker = myWorker;
554               myWorker = dataHolder.getChangeListWorker();
555               myWorker.onAfterWorkerSwitch(oldWorker);
556               myModifier.setWorker(myWorker);
557               LOG.debug("refresh procedure finished, unversioned size: " +
558                         dataHolder.getComposite().getVFHolder(FileHolder.HolderType.UNVERSIONED).getSize() + "\n changes: " + myWorker);
559               final boolean statusChanged = !myComposite.equals(dataHolder.getComposite());
560               myComposite = dataHolder.getComposite();
561               if (statusChanged) {
562                 myDelayedNotificator.getProxyDispatcher().unchangedFileStatusChanged();
563               }
564             }
565             myShowLocalChangesInvalidated = false;
566           }
567         }
568       });
569
570       for (VcsDirtyScope scope : scopes) {
571         AbstractVcs vcs = scope.getVcs();
572         if (vcs != null && vcs.isTrackingUnchangedContent()) {
573           scope.iterateExistingInsideScope(new Processor<VirtualFile>() {
574             @Override
575             public boolean process(VirtualFile file) {
576               LastUnchangedContentTracker.markUntouched(file); //todo what if it has become dirty again during update?
577               return true;
578             }
579           });
580         }
581       }
582
583
584       myChangesViewManager.scheduleRefresh();
585     }
586     catch (DisposedException e) {
587       // OK, we're finishing all the stuff now.
588     }
589     catch (ProcessCanceledException e) {
590       // OK, we're finishing all the stuff now.
591     }
592     catch (RuntimeInterruptedException ignore) {
593     }
594     catch (Exception ex) {
595       LOG.error(ex);
596     }
597     catch (AssertionError ex) {
598       LOG.error(ex);
599     }
600     finally {
601       myDirtyScopeManager.changesProcessed();
602
603       synchronized (myDataLock) {
604         myDelayedNotificator.getProxyDispatcher().changeListUpdateDone();
605         myChangesViewManager.scheduleRefresh();
606       }
607     }
608   }
609
610   private boolean checkScopeIsAllIgnored(VcsInvalidated invalidated) {
611     if (!invalidated.isEverythingDirty()) {
612       filterOutIgnoredFiles(invalidated.getScopes());
613       if (invalidated.isEmpty()) {
614         return true;
615       }
616     }
617     return false;
618   }
619
620   private boolean checkScopeIsEmpty(VcsInvalidated invalidated) {
621     if (invalidated == null || invalidated.isEmpty()) {
622       // a hack here; but otherwise everything here should be refactored ;)
623       if (invalidated != null && invalidated.isEmpty() && invalidated.isEverythingDirty()) {
624         VcsDirtyScopeManager.getInstance(myProject).markEverythingDirty();
625       }
626       return true;
627     }
628     return checkScopeIsAllIgnored(invalidated);
629   }
630
631   private void iterateScopes(DataHolder dataHolder, List<VcsDirtyScope> scopes, boolean wasEverythingDirty) {
632     final ChangeListManagerGate gate = dataHolder.getChangeListWorker().createSelfGate();
633     // do actual requests about file statuses
634     Getter<Boolean> disposedGetter = new Getter<Boolean>() {
635       @Override
636       public Boolean get() {
637         return myProject.isDisposed() || myUpdater.getIsStoppedGetter().get();
638       }
639     };
640     final UpdatingChangeListBuilder builder = new UpdatingChangeListBuilder(dataHolder.getChangeListWorker(),
641                                                                             dataHolder.getComposite(), disposedGetter, myIgnoredIdeaLevel,
642                                                                             gate);
643
644     for (final VcsDirtyScope scope : scopes) {
645       myUpdateChangesProgressIndicator.checkCanceled();
646
647       final AbstractVcs vcs = scope.getVcs();
648       if (vcs == null) continue;
649       scope.setWasEverythingDirty(wasEverythingDirty);
650       final VcsModifiableDirtyScope adjustedScope = vcs.adjustDirtyScope((VcsModifiableDirtyScope)scope);
651
652       myChangesViewManager.setBusy(true);
653       dataHolder.notifyStartProcessingChanges(adjustedScope);
654
655       actualUpdate(builder, adjustedScope, vcs, dataHolder, gate);
656
657       if (myUpdateException != null) break;
658     }
659     synchronized (myDataLock) {
660       if (myAdditionalInfo == null) {
661         myAdditionalInfo = builder.getAdditionalInfo();
662       }
663     }
664   }
665
666   private void clearCurrentRevisionsCache(final VcsInvalidated invalidated) {
667     final ContentRevisionCache cache = ProjectLevelVcsManager.getInstance(myProject).getContentRevisionCache();
668     if (invalidated.isEverythingDirty()) {
669       cache.clearAllCurrent();
670     }
671     else {
672       cache.clearScope(invalidated.getScopes());
673     }
674   }
675
676   @NotNull
677   private static ProgressIndicator createProgressIndicator() {
678     return new EmptyProgressIndicator();
679   }
680
681   private class DataHolder {
682     private final boolean myWasEverythingDirty;
683     final FileHolderComposite myComposite;
684     final ChangeListWorker myChangeListWorker;
685
686     private DataHolder(FileHolderComposite composite, ChangeListWorker changeListWorker, boolean wasEverythingDirty) {
687       myComposite = composite;
688       myChangeListWorker = changeListWorker;
689       myWasEverythingDirty = wasEverythingDirty;
690     }
691
692     public void notifyStart() {
693       if (myWasEverythingDirty) {
694         myComposite.cleanAll();
695         myChangeListWorker.notifyStartProcessingChanges(null);
696       }
697     }
698
699     public void notifyStartProcessingChanges(@NotNull final VcsModifiableDirtyScope scope) {
700       if (!myWasEverythingDirty) {
701         myComposite.cleanAndAdjustScope(scope);
702         myChangeListWorker.notifyStartProcessingChanges(scope);
703       }
704
705       myComposite.notifyVcsStarted(scope.getVcs());
706       myChangeListWorker.notifyVcsStarted(scope.getVcs());
707     }
708
709     public void notifyDoneProcessingChanges() {
710       if (!myWasEverythingDirty) {
711         myChangeListWorker.notifyDoneProcessingChanges(myDelayedNotificator.getProxyDispatcher());
712       }
713     }
714
715     public void notifyEnd() {
716       if (myWasEverythingDirty) {
717         myChangeListWorker.notifyDoneProcessingChanges(myDelayedNotificator.getProxyDispatcher());
718       }
719     }
720
721     public FileHolderComposite getComposite() {
722       return myComposite;
723     }
724
725     public ChangeListWorker getChangeListWorker() {
726       return myChangeListWorker;
727     }
728   }
729
730   private void actualUpdate(final UpdatingChangeListBuilder builder, final VcsDirtyScope scope, final AbstractVcs vcs,
731                             final DataHolder dataHolder, final ChangeListManagerGate gate) {
732     try {
733       final ChangeProvider changeProvider = vcs.getChangeProvider();
734       if (changeProvider != null) {
735         final FoldersCutDownWorker foldersCutDownWorker = new FoldersCutDownWorker();
736         try {
737           builder.setCurrent(scope, foldersCutDownWorker);
738           changeProvider.getChanges(scope, builder, myUpdateChangesProgressIndicator, gate);
739         }
740         catch (final VcsException e) {
741           handleUpdateException(e);
742         }
743       }
744     }
745     catch (ProcessCanceledException ignore) {
746     }
747     catch (Throwable t) {
748       LOG.debug(t);
749       Rethrow.reThrowRuntime(t);
750     }
751     finally {
752       if (!myUpdater.isStopped()) {
753         dataHolder.notifyDoneProcessingChanges();
754       }
755     }
756   }
757
758   private void handleUpdateException(final VcsException e) {
759     LOG.info(e);
760
761     if (e instanceof VcsConnectionProblem) {
762       ApplicationManager.getApplication().invokeLater(new Runnable() {
763         @Override
764         public void run() {
765           ((VcsConnectionProblem)e).attemptQuickFix(false);
766         }
767       });
768     }
769
770     if (myUpdateException == null) {
771       if (ApplicationManager.getApplication().isUnitTestMode()) {
772         AbstractVcsHelper helper = AbstractVcsHelper.getInstance(myProject);
773         if (helper instanceof AbstractVcsHelperImpl && ((AbstractVcsHelperImpl)helper).handleCustom(e)) {
774           return;
775         }
776         //noinspection CallToPrintStackTrace
777         e.printStackTrace();
778       }
779       myUpdateException = e;
780     }
781   }
782
783   private void checkIfDisposed() {
784     if (myUpdater.isStopped()) throw new DisposedException();
785   }
786
787   public static boolean isUnder(final Change change, final VcsDirtyScope scope) {
788     final ContentRevision before = change.getBeforeRevision();
789     final ContentRevision after = change.getAfterRevision();
790     return before != null && scope.belongsTo(before.getFile()) || after != null && scope.belongsTo(after.getFile());
791   }
792
793   @Override
794   public List<LocalChangeList> getChangeListsCopy() {
795     synchronized (myDataLock) {
796       return myWorker.getListsCopy();
797     }
798   }
799
800   /**
801    * @deprecated this method made equivalent to {@link #getChangeListsCopy()} so to don't be confused by method name,
802    * better use {@link #getChangeListsCopy()}
803    */
804   @Override
805   @NotNull
806   public List<LocalChangeList> getChangeLists() {
807     synchronized (myDataLock) {
808       return getChangeListsCopy();
809     }
810   }
811
812   @Override
813   public List<File> getAffectedPaths() {
814     synchronized (myDataLock) {
815       return myWorker.getAffectedPaths();
816     }
817   }
818
819   @Override
820   @NotNull
821   public List<VirtualFile> getAffectedFiles() {
822     synchronized (myDataLock) {
823       return myWorker.getAffectedFiles();
824     }
825   }
826
827   @Override
828   @NotNull
829   public Collection<Change> getAllChanges() {
830     synchronized (myDataLock) {
831       return myWorker.getAllChanges();
832     }
833   }
834
835   public List<VirtualFile> getUnversionedFiles() {
836     synchronized (myDataLock) {
837       return myComposite.getVFHolder(FileHolder.HolderType.UNVERSIONED).getFiles();
838     }
839   }
840
841   Couple<Integer> getUnversionedFilesSize() {
842     synchronized (myDataLock) {
843       final VirtualFileHolder holder = myComposite.getVFHolder(FileHolder.HolderType.UNVERSIONED);
844       return Couple.of(holder.getSize(), holder.getNumDirs());
845     }
846   }
847
848   @Override
849   public List<VirtualFile> getModifiedWithoutEditing() {
850     synchronized (myDataLock) {
851       return myComposite.getVFHolder(FileHolder.HolderType.MODIFIED_WITHOUT_EDITING).getFiles();
852     }
853   }
854
855   /**
856    * @return only roots for ignored folders, and ignored files
857    */
858   List<VirtualFile> getIgnoredFiles() {
859     synchronized (myDataLock) {
860       return new ArrayList<VirtualFile>(myComposite.getIgnoredFileHolder().values());
861     }
862   }
863
864   public List<VirtualFile> getLockedFolders() {
865     synchronized (myDataLock) {
866       return myComposite.getVFHolder(FileHolder.HolderType.LOCKED).getFiles();
867     }
868   }
869
870   Map<VirtualFile, LogicalLock> getLogicallyLockedFolders() {
871     synchronized (myDataLock) {
872       return new HashMap<VirtualFile, LogicalLock>(
873         ((LogicallyLockedHolder)myComposite.get(FileHolder.HolderType.LOGICALLY_LOCKED)).getMap());
874     }
875   }
876
877   public boolean isLogicallyLocked(final VirtualFile file) {
878     synchronized (myDataLock) {
879       return ((LogicallyLockedHolder)myComposite.get(FileHolder.HolderType.LOGICALLY_LOCKED)).containsKey(file);
880     }
881   }
882
883   public boolean isContainedInLocallyDeleted(final FilePath filePath) {
884     synchronized (myDataLock) {
885       return myWorker.isContainedInLocallyDeleted(filePath);
886     }
887   }
888
889   public List<LocallyDeletedChange> getDeletedFiles() {
890     synchronized (myDataLock) {
891       return myWorker.getLocallyDeleted().getFiles();
892     }
893   }
894
895   MultiMap<String, VirtualFile> getSwitchedFilesMap() {
896     synchronized (myDataLock) {
897       return myWorker.getSwitchedHolder().getBranchToFileMap();
898     }
899   }
900
901   @Nullable
902   Map<VirtualFile, String> getSwitchedRoots() {
903     synchronized (myDataLock) {
904       return ((SwitchedFileHolder)myComposite.get(FileHolder.HolderType.ROOT_SWITCH)).getFilesMapCopy();
905     }
906   }
907
908   public VcsException getUpdateException() {
909     synchronized (myDataLock) {
910       return myUpdateException;
911     }
912   }
913
914   public Factory<JComponent> getAdditionalUpdateInfo() {
915     synchronized (myDataLock) {
916       return myAdditionalInfo;
917     }
918   }
919
920   @Override
921   public boolean isFileAffected(final VirtualFile file) {
922     synchronized (myDataLock) {
923       return myWorker.getStatus(file) != null;
924     }
925   }
926
927   @Override
928   @Nullable
929   public LocalChangeList findChangeList(final String name) {
930     synchronized (myDataLock) {
931       return myWorker.getCopyByName(name);
932     }
933   }
934
935   @Override
936   public LocalChangeList getChangeList(String id) {
937     synchronized (myDataLock) {
938       return myWorker.getChangeList(id);
939     }
940   }
941
942   @Override
943   public LocalChangeList addChangeList(@NotNull final String name, @Nullable final String comment) {
944     return addChangeList(name, comment, null);
945   }
946
947   @Override
948   public LocalChangeList addChangeList(@NotNull final String name, @Nullable final String comment, @Nullable final Object data) {
949     return ApplicationManager.getApplication().runReadAction(new Computable<LocalChangeList>() {
950       @Override
951       public LocalChangeList compute() {
952         synchronized (myDataLock) {
953           final LocalChangeList changeList = myModifier.addChangeList(name, comment, data);
954           myChangesViewManager.scheduleRefresh();
955           return changeList;
956         }
957       }
958     });
959   }
960
961
962   @Override
963   public void removeChangeList(final String name) {
964     ApplicationManager.getApplication().runReadAction(new Runnable() {
965       @Override
966       public void run() {
967         synchronized (myDataLock) {
968           myModifier.removeChangeList(name);
969           myChangesViewManager.scheduleRefresh();
970         }
971       }
972     });
973   }
974
975   @Override
976   public void removeChangeList(LocalChangeList list) {
977     removeChangeList(list.getName());
978   }
979
980   /**
981    * does no modification to change lists, only notification is sent
982    */
983   @Override
984   @NotNull
985   public Runnable prepareForChangeDeletion(final Collection<Change> changes) {
986     final Map<String, LocalChangeList> lists = new HashMap<String, LocalChangeList>();
987     final Map<String, List<Change>> map;
988     synchronized (myDataLock) {
989       map = myWorker.listsForChanges(changes, lists);
990     }
991     return new Runnable() {
992       @Override
993       public void run() {
994         final ChangeListListener multicaster = myDelayedNotificator.getProxyDispatcher();
995         ApplicationManager.getApplication().runReadAction(new Runnable() {
996           @Override
997           public void run() {
998             synchronized (myDataLock) {
999               for (Map.Entry<String, List<Change>> entry : map.entrySet()) {
1000                 final List<Change> changes = entry.getValue();
1001                 for (Iterator<Change> iterator = changes.iterator(); iterator.hasNext(); ) {
1002                   final Change change = iterator.next();
1003                   if (getChangeList(change) != null) {
1004                     // was not actually rolled back
1005                     iterator.remove();
1006                   }
1007                 }
1008                 multicaster.changesRemoved(changes, lists.get(entry.getKey()));
1009               }
1010               for (String listName : map.keySet()) {
1011                 final LocalChangeList byName = myWorker.getCopyByName(listName);
1012                 if (byName != null && byName.getChanges().isEmpty() && !byName.isDefault() && !byName.isReadOnly()) {
1013                   myWorker.removeChangeList(listName);
1014                 }
1015               }
1016             }
1017           }
1018         });
1019       }
1020     };
1021   }
1022
1023   @Override
1024   public void setDefaultChangeList(@NotNull final LocalChangeList list) {
1025     ApplicationManager.getApplication().runReadAction(new Runnable() {
1026       @Override
1027       public void run() {
1028         synchronized (myDataLock) {
1029           myModifier.setDefault(list.getName());
1030         }
1031       }
1032     });
1033     myChangesViewManager.scheduleRefresh();
1034   }
1035
1036   @Override
1037   @Nullable
1038   public LocalChangeList getDefaultChangeList() {
1039     synchronized (myDataLock) {
1040       return myWorker.getDefaultListCopy();
1041     }
1042   }
1043
1044   @Override
1045   public boolean isDefaultChangeList(ChangeList list) {
1046     return list instanceof LocalChangeList && myWorker.isDefaultList((LocalChangeList)list);
1047   }
1048
1049   @Override
1050   @NotNull
1051   public Collection<LocalChangeList> getInvolvedListsFilterChanges(final Collection<Change> changes, final List<Change> validChanges) {
1052     synchronized (myDataLock) {
1053       return myWorker.getInvolvedListsFilterChanges(changes, validChanges);
1054     }
1055   }
1056
1057   @Override
1058   @Nullable
1059   public LocalChangeList getChangeList(@NotNull Change change) {
1060     synchronized (myDataLock) {
1061       return myWorker.listForChange(change);
1062     }
1063   }
1064
1065   @Override
1066   public String getChangeListNameIfOnlyOne(final Change[] changes) {
1067     synchronized (myDataLock) {
1068       return myWorker.listNameIfOnlyOne(changes);
1069     }
1070   }
1071
1072   /**
1073    * @deprecated better use normal comparison, with equals
1074    */
1075   @Override
1076   @Nullable
1077   public LocalChangeList getIdentityChangeList(Change change) {
1078     synchronized (myDataLock) {
1079       final List<LocalChangeList> lists = myWorker.getListsCopy();
1080       for (LocalChangeList list : lists) {
1081         for (Change oldChange : list.getChanges()) {
1082           if (oldChange == change) {
1083             return list;
1084           }
1085         }
1086       }
1087       return null;
1088     }
1089   }
1090
1091   @Override
1092   public boolean isInUpdate() {
1093     synchronized (myDataLock) {
1094       return myModifier.isInsideUpdate() || myShowLocalChangesInvalidated;
1095     }
1096   }
1097
1098   @Override
1099   @Nullable
1100   public Change getChange(@NotNull VirtualFile file) {
1101     synchronized (myDataLock) {
1102       final LocalChangeList list = myWorker.getListCopy(file);
1103       if (list != null) {
1104         for (Change change : list.getChanges()) {
1105           final ContentRevision afterRevision = change.getAfterRevision();
1106           if (afterRevision != null) {
1107             String revisionPath = FileUtil.toSystemIndependentName(afterRevision.getFile().getIOFile().getPath());
1108             if (FileUtil.pathsEqual(revisionPath, file.getPath())) return change;
1109           }
1110           final ContentRevision beforeRevision = change.getBeforeRevision();
1111           if (beforeRevision != null) {
1112             String revisionPath = FileUtil.toSystemIndependentName(beforeRevision.getFile().getIOFile().getPath());
1113             if (FileUtil.pathsEqual(revisionPath, file.getPath())) return change;
1114           }
1115         }
1116       }
1117
1118       return null;
1119     }
1120   }
1121
1122   @Override
1123   public LocalChangeList getChangeList(@NotNull VirtualFile file) {
1124     synchronized (myDataLock) {
1125       return myWorker.getListCopy(file);
1126     }
1127   }
1128
1129   @Override
1130   @Nullable
1131   public Change getChange(final FilePath file) {
1132     synchronized (myDataLock) {
1133       return myWorker.getChangeForPath(file);
1134     }
1135   }
1136
1137   @Override
1138   public boolean isUnversioned(VirtualFile file) {
1139     synchronized (myDataLock) {
1140       return myComposite.getVFHolder(FileHolder.HolderType.UNVERSIONED).containsFile(file);
1141     }
1142   }
1143
1144   @Override
1145   @NotNull
1146   public FileStatus getStatus(VirtualFile file) {
1147     synchronized (myDataLock) {
1148       if (myComposite.getVFHolder(FileHolder.HolderType.UNVERSIONED).containsFile(file)) return FileStatus.UNKNOWN;
1149       if (myComposite.getVFHolder(FileHolder.HolderType.MODIFIED_WITHOUT_EDITING).containsFile(file)) return FileStatus.HIJACKED;
1150       if (myComposite.getIgnoredFileHolder().containsFile(file)) return FileStatus.IGNORED;
1151
1152       final boolean switched = myWorker.isSwitched(file);
1153       final FileStatus status = myWorker.getStatus(file);
1154       if (status != null) {
1155         return FileStatus.NOT_CHANGED.equals(status) && switched ? FileStatus.SWITCHED : status;
1156       }
1157       if (switched) return FileStatus.SWITCHED;
1158       return FileStatus.NOT_CHANGED;
1159     }
1160   }
1161
1162   @Override
1163   @NotNull
1164   public Collection<Change> getChangesIn(VirtualFile dir) {
1165     return getChangesIn(new FilePathImpl(dir));
1166   }
1167
1168   @NotNull
1169   @Override
1170   public ThreeState haveChangesUnder(@NotNull final VirtualFile vf) {
1171     if (!vf.isValid() || !vf.isDirectory()) return ThreeState.NO;
1172     synchronized (myDataLock) {
1173       return myWorker.haveChangesUnder(vf);
1174     }
1175   }
1176
1177   @Override
1178   @NotNull
1179   public Collection<Change> getChangesIn(final FilePath dirPath) {
1180     synchronized (myDataLock) {
1181       return myWorker.getChangesIn(dirPath);
1182     }
1183   }
1184
1185   @Override
1186   public void moveChangesTo(final LocalChangeList list, final Change... changes) {
1187     ApplicationManager.getApplication().runReadAction(new Runnable() {
1188       @Override
1189       public void run() {
1190         synchronized (myDataLock) {
1191           myModifier.moveChangesTo(list.getName(), changes);
1192         }
1193       }
1194     });
1195     myChangesViewManager.scheduleRefresh();
1196   }
1197
1198   @Override
1199   public void addUnversionedFiles(final LocalChangeList list, @NotNull final List<VirtualFile> files) {
1200     addUnversionedFiles(list, files, new Condition<FileStatus>() {
1201       @Override
1202       public boolean value(FileStatus status) {
1203         return status == FileStatus.UNKNOWN;
1204       }
1205     });
1206   }
1207
1208   // TODO this is for quick-fix for GitAdd problem. To be removed after proper fix
1209   // (which should introduce something like VcsAddRemoveEnvironment)
1210   @Deprecated
1211   public void addUnversionedFiles(final LocalChangeList list, @NotNull final List<VirtualFile> files,
1212                                   final Condition<FileStatus> statusChecker) {
1213     final List<VcsException> exceptions = new ArrayList<VcsException>();
1214     final Set<VirtualFile> allProcessedFiles = new HashSet<VirtualFile>();
1215     ChangesUtil.processVirtualFilesByVcs(myProject, files, new ChangesUtil.PerVcsProcessor<VirtualFile>() {
1216       @Override
1217       public void process(final AbstractVcs vcs, final List<VirtualFile> items) {
1218         final CheckinEnvironment environment = vcs.getCheckinEnvironment();
1219         if (environment != null) {
1220           Set<VirtualFile> descendants = getUnversionedDescendantsRecursively(items, statusChecker);
1221           Set<VirtualFile> parents =
1222             vcs.areDirectoriesVersionedItems() ? getUnversionedParents(items, statusChecker) : Collections.<VirtualFile>emptySet();
1223
1224           // it is assumed that not-added parents of files passed to scheduleUnversionedFilesForAddition() will also be added to vcs
1225           // (inside the method) - so common add logic just needs to refresh statuses of parents
1226           List<VcsException> result = environment.scheduleUnversionedFilesForAddition(ContainerUtil.newArrayList(descendants));
1227
1228           allProcessedFiles.addAll(descendants);
1229           allProcessedFiles.addAll(parents);
1230           if (result != null) {
1231             exceptions.addAll(result);
1232           }
1233         }
1234       }
1235     });
1236
1237     if (exceptions.size() > 0) {
1238       StringBuilder message = new StringBuilder(VcsBundle.message("error.adding.files.prompt"));
1239       for (VcsException ex : exceptions) {
1240         message.append("\n").append(ex.getMessage());
1241       }
1242       Messages.showErrorDialog(myProject, message.toString(), VcsBundle.message("error.adding.files.title"));
1243     }
1244
1245     for (VirtualFile file : allProcessedFiles) {
1246       myFileStatusManager.fileStatusChanged(file);
1247     }
1248     VcsDirtyScopeManager.getInstance(myProject).filesDirty(allProcessedFiles, null);
1249
1250     if (!list.isDefault()) {
1251       // find the changes for the added files and move them to the necessary changelist
1252       invokeAfterUpdate(new Runnable() {
1253         @Override
1254         public void run() {
1255           ApplicationManager.getApplication().runReadAction(new Runnable() {
1256             @Override
1257             public void run() {
1258               synchronized (myDataLock) {
1259                 List<Change> changesToMove = new ArrayList<Change>();
1260                 final LocalChangeList defaultList = getDefaultChangeList();
1261                 for (Change change : defaultList.getChanges()) {
1262                   final ContentRevision afterRevision = change.getAfterRevision();
1263                   if (afterRevision != null) {
1264                     VirtualFile vFile = afterRevision.getFile().getVirtualFile();
1265                     if (allProcessedFiles.contains(vFile)) {
1266                       changesToMove.add(change);
1267                     }
1268                   }
1269                 }
1270
1271                 if (changesToMove.size() > 0) {
1272                   moveChangesTo(list, changesToMove.toArray(new Change[changesToMove.size()]));
1273                 }
1274               }
1275             }
1276           });
1277
1278           myChangesViewManager.scheduleRefresh();
1279         }
1280       }, InvokeAfterUpdateMode.BACKGROUND_NOT_CANCELLABLE_NOT_AWT, VcsBundle.message("change.lists.manager.add.unversioned"), null);
1281     }
1282     else {
1283       myChangesViewManager.scheduleRefresh();
1284     }
1285   }
1286
1287   @NotNull
1288   private Set<VirtualFile> getUnversionedDescendantsRecursively(@NotNull List<VirtualFile> items,
1289                                                                 @NotNull final Condition<FileStatus> condition) {
1290     final Set<VirtualFile> result = ContainerUtil.newHashSet();
1291     Processor<VirtualFile> addToResultProcessor = new Processor<VirtualFile>() {
1292       @Override
1293       public boolean process(VirtualFile file) {
1294         if (condition.value(getStatus(file))) {
1295           result.add(file);
1296         }
1297         return true;
1298       }
1299     };
1300
1301     for (VirtualFile item : items) {
1302       VcsRootIterator.iterateVfUnderVcsRoot(myProject, item, addToResultProcessor);
1303     }
1304
1305     return result;
1306   }
1307
1308   @NotNull
1309   private Set<VirtualFile> getUnversionedParents(@NotNull Collection<VirtualFile> items, @NotNull Condition<FileStatus> condition) {
1310     HashSet<VirtualFile> result = ContainerUtil.newHashSet();
1311
1312     for (VirtualFile item : items) {
1313       VirtualFile parent = item.getParent();
1314
1315       while (parent != null && condition.value(getStatus(parent))) {
1316         result.add(parent);
1317         parent = parent.getParent();
1318       }
1319     }
1320
1321     return result;
1322   }
1323
1324   @Override
1325   public Project getProject() {
1326     return myProject;
1327   }
1328
1329   @Override
1330   public void addChangeListListener(ChangeListListener listener) {
1331     myListeners.addListener(listener);
1332   }
1333
1334
1335   @Override
1336   public void removeChangeListListener(ChangeListListener listener) {
1337     myListeners.removeListener(listener);
1338   }
1339
1340   @Override
1341   public void registerCommitExecutor(CommitExecutor executor) {
1342     myExecutors.add(executor);
1343   }
1344
1345   @Override
1346   public void commitChanges(LocalChangeList changeList, List<Change> changes) {
1347     doCommit(changeList, changes, false);
1348   }
1349
1350   private boolean doCommit(final LocalChangeList changeList, final List<Change> changes, final boolean synchronously) {
1351     FileDocumentManager.getInstance().saveAllDocuments();
1352     return new CommitHelper(myProject, changeList, changes, changeList.getName(),
1353                             StringUtil.isEmpty(changeList.getComment()) ? changeList.getName() : changeList.getComment(),
1354                             new ArrayList<CheckinHandler>(), false, synchronously, FunctionUtil.nullConstant(), null).doCommit();
1355   }
1356
1357   @Override
1358   public void commitChangesSynchronously(LocalChangeList changeList, List<Change> changes) {
1359     doCommit(changeList, changes, true);
1360   }
1361
1362   @Override
1363   public boolean commitChangesSynchronouslyWithResult(final LocalChangeList changeList, final List<Change> changes) {
1364     return doCommit(changeList, changes, true);
1365   }
1366
1367   @Override
1368   @SuppressWarnings({"unchecked"})
1369   public void readExternal(Element element) throws InvalidDataException {
1370     if (!myProject.isDefault()) {
1371       synchronized (myDataLock) {
1372         myIgnoredIdeaLevel.clear();
1373         new ChangeListManagerSerialization(myIgnoredIdeaLevel, myWorker).readExternal(element);
1374         if ((!myWorker.isEmpty()) && getDefaultChangeList() == null) {
1375           setDefaultChangeList(myWorker.getListsCopy().get(0));
1376         }
1377       }
1378       myExcludedConvertedToIgnored = Boolean.parseBoolean(JDOMExternalizerUtil.readField(element, EXCLUDED_CONVERTED_TO_IGNORED_OPTION));
1379       myConflictTracker.loadState(element);
1380     }
1381   }
1382
1383   @Override
1384   public void writeExternal(Element element) throws WriteExternalException {
1385     if (!myProject.isDefault()) {
1386       final IgnoredFilesComponent ignoredFilesComponent;
1387       final ChangeListWorker worker;
1388       synchronized (myDataLock) {
1389         ignoredFilesComponent = new IgnoredFilesComponent(myIgnoredIdeaLevel);
1390         worker = myWorker.copy();
1391       }
1392       new ChangeListManagerSerialization(ignoredFilesComponent, worker).writeExternal(element);
1393       if (myExcludedConvertedToIgnored) {
1394         JDOMExternalizerUtil.writeField(element, EXCLUDED_CONVERTED_TO_IGNORED_OPTION, String.valueOf(true));
1395       }
1396       myConflictTracker.saveState(element);
1397     }
1398   }
1399
1400   // used in TeamCity
1401   @Override
1402   public void reopenFiles(List<FilePath> paths) {
1403     final ReadonlyStatusHandlerImpl readonlyStatusHandler = (ReadonlyStatusHandlerImpl)ReadonlyStatusHandler.getInstance(myProject);
1404     final boolean savedOption = readonlyStatusHandler.getState().SHOW_DIALOG;
1405     readonlyStatusHandler.getState().SHOW_DIALOG = false;
1406     try {
1407       readonlyStatusHandler.ensureFilesWritable(collectFiles(paths));
1408     }
1409     finally {
1410       readonlyStatusHandler.getState().SHOW_DIALOG = savedOption;
1411     }
1412   }
1413
1414   @Override
1415   public List<CommitExecutor> getRegisteredExecutors() {
1416     return Collections.unmodifiableList(myExecutors);
1417   }
1418
1419   private static class MyDirtyFilesScheduler {
1420     private static final int ourPiecesLimit = 100;
1421     final List<VirtualFile> myFiles = new ArrayList<VirtualFile>();
1422     final List<VirtualFile> myDirs = new ArrayList<VirtualFile>();
1423     private boolean myEveryThing;
1424     private int myCnt;
1425     private final Project myProject;
1426
1427     private MyDirtyFilesScheduler(final Project project) {
1428       myProject = project;
1429       myCnt = 0;
1430       myEveryThing = false;
1431     }
1432
1433     public void accept(final Collection<VirtualFile> coll) {
1434       for (VirtualFile vf : coll) {
1435         if (myCnt > ourPiecesLimit) {
1436           myEveryThing = true;
1437           break;
1438         }
1439         if (vf.isDirectory()) {
1440           myDirs.add(vf);
1441         }
1442         else {
1443           myFiles.add(vf);
1444         }
1445         ++myCnt;
1446       }
1447     }
1448
1449     public void arise() {
1450       final VcsDirtyScopeManager vcsDirtyScopeManager = VcsDirtyScopeManager.getInstance(myProject);
1451       if (myEveryThing) {
1452         vcsDirtyScopeManager.markEverythingDirty();
1453       }
1454       else {
1455         vcsDirtyScopeManager.filesDirty(myFiles, myDirs);
1456       }
1457     }
1458   }
1459
1460   @Override
1461   public void addFilesToIgnore(final IgnoredFileBean... filesToIgnore) {
1462     myIgnoredIdeaLevel.add(filesToIgnore);
1463     scheduleUnversionedUpdate();
1464   }
1465
1466   @Override
1467   public void addDirectoryToIgnoreImplicitly(@NotNull String path) {
1468     myIgnoredIdeaLevel.addIgnoredDirectoryImplicitly(path, myProject);
1469   }
1470
1471   public IgnoredFilesComponent getIgnoredFilesComponent() {
1472     return myIgnoredIdeaLevel;
1473   }
1474
1475   private void scheduleUnversionedUpdate() {
1476     final MyDirtyFilesScheduler scheduler = new MyDirtyFilesScheduler(myProject);
1477
1478     synchronized (myDataLock) {
1479       final VirtualFileHolder unversionedHolder = myComposite.getVFHolder(FileHolder.HolderType.UNVERSIONED);
1480       final IgnoredFilesHolder ignoredHolder = (IgnoredFilesHolder)myComposite.get(FileHolder.HolderType.IGNORED);
1481
1482       scheduler.accept(unversionedHolder.getFiles());
1483       scheduler.accept(ignoredHolder.values());
1484     }
1485
1486     scheduler.arise();
1487   }
1488
1489   @Override
1490   public void setFilesToIgnore(final IgnoredFileBean... filesToIgnore) {
1491     myIgnoredIdeaLevel.set(filesToIgnore);
1492     scheduleUnversionedUpdate();
1493   }
1494
1495   private void updateIgnoredFiles(final FileHolderComposite composite) {
1496     final VirtualFileHolder vfHolder = composite.getVFHolder(FileHolder.HolderType.UNVERSIONED);
1497     final List<VirtualFile> unversionedFiles = vfHolder.getFiles();
1498     exchangeWithIgnored(composite, vfHolder, unversionedFiles);
1499
1500     final VirtualFileHolder vfModifiedHolder = composite.getVFHolder(FileHolder.HolderType.MODIFIED_WITHOUT_EDITING);
1501     final List<VirtualFile> modifiedFiles = vfModifiedHolder.getFiles();
1502     exchangeWithIgnored(composite, vfModifiedHolder, modifiedFiles);
1503   }
1504
1505   private void exchangeWithIgnored(FileHolderComposite composite, VirtualFileHolder vfHolder, List<VirtualFile> unversionedFiles) {
1506     for (VirtualFile file : unversionedFiles) {
1507       if (isIgnoredFile(file)) {
1508         vfHolder.removeFile(file);
1509         composite.getIgnoredFileHolder().addFile(file);
1510       }
1511     }
1512   }
1513
1514   @Override
1515   public IgnoredFileBean[] getFilesToIgnore() {
1516     return myIgnoredIdeaLevel.getFilesToIgnore();
1517   }
1518
1519   @Override
1520   public boolean isIgnoredFile(@NotNull VirtualFile file) {
1521     return myIgnoredIdeaLevel.isIgnoredFile(file);
1522   }
1523
1524   @Override
1525   @Nullable
1526   public String getSwitchedBranch(final VirtualFile file) {
1527     synchronized (myDataLock) {
1528       return myWorker.getBranchForFile(file);
1529     }
1530   }
1531
1532   @Override
1533   public String getDefaultListName() {
1534     synchronized (myDataLock) {
1535       return myWorker.getDefaultListName();
1536     }
1537   }
1538
1539   private static VirtualFile[] collectFiles(final List<FilePath> paths) {
1540     final ArrayList<VirtualFile> result = new ArrayList<VirtualFile>();
1541     for (FilePath path : paths) {
1542       if (path.getVirtualFile() != null) {
1543         result.add(path.getVirtualFile());
1544       }
1545     }
1546
1547     return VfsUtilCore.toVirtualFileArray(result);
1548   }
1549
1550   @Override
1551   public boolean setReadOnly(final String name, final boolean value) {
1552     return ApplicationManager.getApplication().runReadAction(new Computable<Boolean>() {
1553       @Override
1554       public Boolean compute() {
1555         synchronized (myDataLock) {
1556           final boolean result = myModifier.setReadOnly(name, value);
1557           myChangesViewManager.scheduleRefresh();
1558           return result;
1559         }
1560       }
1561     });
1562   }
1563
1564   @Override
1565   public boolean editName(@NotNull final String fromName, @NotNull final String toName) {
1566     return ApplicationManager.getApplication().runReadAction(new Computable<Boolean>() {
1567       @Override
1568       public Boolean compute() {
1569         synchronized (myDataLock) {
1570           final boolean result = myModifier.editName(fromName, toName);
1571           myChangesViewManager.scheduleRefresh();
1572           return result;
1573         }
1574       }
1575     });
1576   }
1577
1578   @Override
1579   public String editComment(@NotNull final String fromName, final String newComment) {
1580     return ApplicationManager.getApplication().runReadAction(new Computable<String>() {
1581       @Override
1582       public String compute() {
1583         synchronized (myDataLock) {
1584           final String oldComment = myModifier.editComment(fromName, newComment);
1585           myChangesViewManager.scheduleRefresh();
1586           return oldComment;
1587         }
1588       }
1589     });
1590   }
1591
1592   @TestOnly
1593   public void waitUntilRefreshed() {
1594     myVfsListener.flushDirt();
1595     myUpdater.waitUntilRefreshed();
1596     waitUpdateAlarm();
1597   }
1598
1599   // this is for perforce tests to ensure that LastSuccessfulUpdateTracker receives the event it needs
1600   private static void waitUpdateAlarm() {
1601     final Semaphore semaphore = new Semaphore();
1602     semaphore.down();
1603     ourUpdateAlarm.get().execute(new Runnable() {
1604       @Override
1605       public void run() {
1606         semaphore.up();
1607       }
1608     });
1609     semaphore.waitFor();
1610   }
1611
1612   public void stopEveryThingIfInTestMode() {
1613     assert ApplicationManager.getApplication().isUnitTestMode();
1614     ourUpdateAlarm.get().shutdownNow();
1615     ourUpdateAlarm.set(createChangeListExecutor());
1616   }
1617
1618   public void forceGoInTestMode() {
1619     assert ApplicationManager.getApplication().isUnitTestMode();
1620     myUpdater.forceGo();
1621   }
1622
1623   public void executeOnUpdaterThread(Runnable r) {
1624     ourUpdateAlarm.get().execute(r);
1625   }
1626
1627   @Override
1628   @TestOnly
1629   public boolean ensureUpToDate(final boolean canBeCanceled) {
1630     if (ApplicationManager.getApplication().isDispatchThread()) {
1631       updateImmediately();
1632       return true;
1633     }
1634     myVfsListener.flushDirt();
1635     final EnsureUpToDateFromNonAWTThread worker = new EnsureUpToDateFromNonAWTThread(myProject);
1636     worker.execute();
1637     myUpdater.waitUntilRefreshed();
1638     waitUpdateAlarm();
1639     return worker.isDone();
1640   }
1641
1642   @Override
1643   public int getChangeListsNumber() {
1644     synchronized (myDataLock) {
1645       return myWorker.getChangeListsNumber();
1646     }
1647   }
1648
1649   // only a light attempt to show that some dirty scope request is asynchronously coming
1650   // for users to see changes are not valid
1651   // (commit -> asynch synch VFS -> asynch vcs dirty scope)
1652   public void showLocalChangesInvalidated() {
1653     synchronized (myDataLock) {
1654       myShowLocalChangesInvalidated = true;
1655     }
1656   }
1657
1658   public ChangelistConflictTracker getConflictTracker() {
1659     return myConflictTracker;
1660   }
1661
1662   private static class MyChangesDeltaForwarder implements PlusMinusModify<BaseRevision> {
1663     private final RemoteRevisionsCache myRevisionsCache;
1664     private final ProjectLevelVcsManager myVcsManager;
1665     private final Project myProject;
1666     private final AtomicReference<ScheduledExecutorService> myService;
1667
1668     public MyChangesDeltaForwarder(final Project project, final AtomicReference<ScheduledExecutorService> service) {
1669       myProject = project;
1670       myService = service;
1671       myRevisionsCache = RemoteRevisionsCache.getInstance(project);
1672       myVcsManager = ProjectLevelVcsManager.getInstance(project);
1673     }
1674
1675     @Override
1676     public void modify(final BaseRevision was, final BaseRevision become) {
1677       myService.get().submit(new Runnable() {
1678         @Override
1679         public void run() {
1680           final AbstractVcs vcs = getVcs(was);
1681           if (vcs != null) {
1682             myRevisionsCache.plus(Pair.create(was.getPath(), vcs));
1683           }
1684           // maybe define modify method?
1685           myProject.getMessageBus().syncPublisher(VcsAnnotationRefresher.LOCAL_CHANGES_CHANGED).dirty(become);
1686         }
1687       });
1688     }
1689
1690     @Override
1691     public void plus(final BaseRevision baseRevision) {
1692       myService.get().submit(new Runnable() {
1693         @Override
1694         public void run() {
1695           final AbstractVcs vcs = getVcs(baseRevision);
1696           if (vcs != null) {
1697             myRevisionsCache.plus(Pair.create(baseRevision.getPath(), vcs));
1698           }
1699           myProject.getMessageBus().syncPublisher(VcsAnnotationRefresher.LOCAL_CHANGES_CHANGED).dirty(baseRevision);
1700         }
1701       });
1702     }
1703
1704     @Override
1705     public void minus(final BaseRevision baseRevision) {
1706       myService.get().submit(new Runnable() {
1707         @Override
1708         public void run() {
1709           final AbstractVcs vcs = getVcs(baseRevision);
1710           if (vcs != null) {
1711             myRevisionsCache.minus(Pair.create(baseRevision.getPath(), vcs));
1712           }
1713           myProject.getMessageBus().syncPublisher(VcsAnnotationRefresher.LOCAL_CHANGES_CHANGED).dirty(baseRevision.getPath());
1714         }
1715       });
1716     }
1717
1718     @Nullable
1719     private AbstractVcs getVcs(final BaseRevision baseRevision) {
1720       VcsKey vcsKey = baseRevision.getVcs();
1721       if (vcsKey == null) {
1722         final String path = baseRevision.getPath();
1723         vcsKey = findVcs(path);
1724         if (vcsKey == null) return null;
1725       }
1726       return myVcsManager.findVcsByName(vcsKey.getName());
1727     }
1728
1729     @Nullable
1730     private VcsKey findVcs(final String path) {
1731       // does not matter directory or not
1732       final VirtualFile vf = LocalFileSystem.getInstance().refreshAndFindFileByIoFile(new File(path));
1733       if (vf == null) return null;
1734       final AbstractVcs vcs = myVcsManager.getVcsFor(vf);
1735       return vcs == null ? null : vcs.getKeyInstanceMethod();
1736     }
1737   }
1738
1739   @Override
1740   public boolean isFreezedWithNotification(String modalTitle) {
1741     final String freezeReason = isFreezed();
1742     if (freezeReason != null) {
1743       if (modalTitle != null) {
1744         Messages.showErrorDialog(myProject, freezeReason, modalTitle);
1745       }
1746       else {
1747         VcsBalloonProblemNotifier.showOverChangesView(myProject, freezeReason, MessageType.WARNING);
1748       }
1749     }
1750     return freezeReason != null;
1751   }
1752 }