Merge branch 'master' of git.labs.intellij.net:idea/community
[idea/community.git] / platform / vcs-impl / src / com / intellij / openapi / vcs / changes / ChangeListManagerImpl.java
1 /*
2  * Copyright 2000-2009 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.AtomicSectionsAware;
20 import com.intellij.openapi.application.ApplicationManager;
21 import com.intellij.openapi.application.ModalityState;
22 import com.intellij.openapi.components.ProjectComponent;
23 import com.intellij.openapi.diagnostic.Logger;
24 import com.intellij.openapi.progress.EmptyProgressIndicator;
25 import com.intellij.openapi.progress.ProcessCanceledException;
26 import com.intellij.openapi.progress.ProgressIndicator;
27 import com.intellij.openapi.project.DumbAwareRunnable;
28 import com.intellij.openapi.project.Project;
29 import com.intellij.openapi.ui.Messages;
30 import com.intellij.openapi.util.*;
31 import com.intellij.openapi.util.io.FileUtil;
32 import com.intellij.openapi.util.text.StringUtil;
33 import com.intellij.openapi.vcs.*;
34 import com.intellij.openapi.vcs.changes.conflicts.ChangelistConflictTracker;
35 import com.intellij.openapi.vcs.changes.ui.CommitHelper;
36 import com.intellij.openapi.vcs.checkin.CheckinEnvironment;
37 import com.intellij.openapi.vcs.checkin.CheckinHandler;
38 import com.intellij.openapi.vcs.impl.ProjectLevelVcsManagerImpl;
39 import com.intellij.openapi.vcs.impl.VcsInitObject;
40 import com.intellij.openapi.vcs.readOnlyHandler.ReadonlyStatusHandlerImpl;
41 import com.intellij.openapi.vfs.LocalFileSystem;
42 import com.intellij.openapi.vfs.VfsUtil;
43 import com.intellij.openapi.vfs.VirtualFile;
44 import com.intellij.ui.EditorNotifications;
45 import com.intellij.util.ConcurrencyUtil;
46 import com.intellij.util.Consumer;
47 import com.intellij.util.EventDispatcher;
48 import com.intellij.util.NullableFunction;
49 import com.intellij.util.containers.MultiMap;
50 import com.intellij.util.messages.Topic;
51 import org.jdom.Element;
52 import org.jetbrains.annotations.NonNls;
53 import org.jetbrains.annotations.NotNull;
54 import org.jetbrains.annotations.Nullable;
55
56 import javax.swing.*;
57 import java.io.File;
58 import java.util.*;
59 import java.util.concurrent.ExecutorService;
60 import java.util.concurrent.ScheduledExecutorService;
61
62 /**
63  * @author max
64  */
65 public class ChangeListManagerImpl extends ChangeListManagerEx implements ProjectComponent, ChangeListOwner, JDOMExternalizable {
66   private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.vcs.changes.ChangeListManagerImpl");
67
68   private final Project myProject;
69   private final ChangesViewManager myChangesViewManager;
70   private final FileStatusManager myFileStatusManager;
71   private final UpdateRequestsQueue myUpdater;
72
73   @SuppressWarnings({"FieldAccessedSynchronizedAndUnsynchronized"})
74   private static final ScheduledExecutorService ourUpdateAlarm = ConcurrencyUtil.newSingleScheduledThreadExecutor("Change List Updater", Thread.MIN_PRIORITY + 1);
75
76   private final Modifier myModifier;
77
78   private FileHolderComposite myComposite;
79
80   private final ChangeListWorker myWorker;
81   private VcsException myUpdateException = null;
82
83   @SuppressWarnings({"FieldAccessedSynchronizedAndUnsynchronized"})
84   private final EventDispatcher<ChangeListListener> myListeners = EventDispatcher.create(ChangeListListener.class);
85
86   private final Object myDataLock = new Object();
87
88   private final List<CommitExecutor> myExecutors = new ArrayList<CommitExecutor>();
89
90   private final IgnoredFilesComponent myIgnoredIdeaLevel;
91   private ProgressIndicator myUpdateChangesProgressIndicator;
92
93   public static final Key<Object> DOCUMENT_BEING_COMMITTED_KEY = new Key<Object>("DOCUMENT_BEING_COMMITTED");
94
95   public static final Topic<LocalChangeListsLoadedListener> LISTS_LOADED = new Topic<LocalChangeListsLoadedListener>(
96     "LOCAL_CHANGE_LISTS_LOADED", LocalChangeListsLoadedListener.class);
97
98   private boolean myShowLocalChangesInvalidated;
99
100   private final DelayedNotificator myDelayedNotificator;
101
102   private final VcsListener myVcsListener = new VcsListener() {
103     public void directoryMappingChanged() {
104       VcsDirtyScopeManager.getInstanceChecked(myProject).markEverythingDirty();
105     }
106   };
107   private final ChangelistConflictTracker myConflictTracker;
108
109   public static ChangeListManagerImpl getInstanceImpl(final Project project) {
110     return (ChangeListManagerImpl) project.getComponent(ChangeListManager.class);
111   }
112
113   public ChangeListManagerImpl(Project project, final VcsConfiguration config) {
114     myProject = project;
115     myChangesViewManager = ChangesViewManager.getInstance(myProject);
116     myFileStatusManager = FileStatusManager.getInstance(myProject);
117     myComposite = new FileHolderComposite(project);
118     myIgnoredIdeaLevel = new IgnoredFilesComponent(myProject);
119     myUpdater = new UpdateRequestsQueue(myProject, ourUpdateAlarm, new ActualUpdater());
120
121     myWorker = new ChangeListWorker(myProject, new MyChangesDeltaForwarder(myProject, ourUpdateAlarm));
122     myDelayedNotificator = new DelayedNotificator(myListeners, ourUpdateAlarm);
123     myModifier = new Modifier(myWorker, myDelayedNotificator);
124
125     myConflictTracker = new ChangelistConflictTracker(project, this, myFileStatusManager, EditorNotifications.getInstance(project));
126
127     myListeners.addListener(new ChangeListAdapter() {
128       @Override
129       public void defaultListChanged(final ChangeList oldDefaultList, ChangeList newDefaultList) {
130         if (((LocalChangeList)oldDefaultList).hasDefaultName()) return;
131         if (!ApplicationManager.getApplication().isUnitTestMode() &&
132           oldDefaultList instanceof LocalChangeList &&
133           oldDefaultList.getChanges().isEmpty() &&
134           !((LocalChangeList)oldDefaultList).isReadOnly()) {
135
136           invokeAfterUpdate(new Runnable() {
137             public void run() {
138               if (getChangeList(((LocalChangeList)oldDefaultList).getId()) == null) {
139                 return; // removed already  
140               }
141               switch (config.REMOVE_EMPTY_INACTIVE_CHANGELISTS) {
142
143                 case SHOW_CONFIRMATION:
144                   VcsConfirmationDialog dialog = new VcsConfirmationDialog(myProject, new VcsShowConfirmationOption() {
145                     public Value getValue() {
146                       return config.REMOVE_EMPTY_INACTIVE_CHANGELISTS;
147                     }
148
149                     public void setValue(Value value) {
150                       config.REMOVE_EMPTY_INACTIVE_CHANGELISTS = value;
151                     }
152
153                     @Override
154                     public boolean isPersistent() {
155                       return true;
156                     }
157                   }, "<html>The empty changelist '" + StringUtil.first(oldDefaultList.getName(), 30, true) + "' is no longer active.<br>" +
158                      "Do you want to remove it?</html>", "&Remember my choice");
159                   dialog.show();
160                   if (!dialog.isOK()) {
161                     return;
162                   }
163                   break;
164                 case DO_NOTHING_SILENTLY:
165                   return;
166                 case DO_ACTION_SILENTLY:
167                   break;
168               }
169               removeChangeList((LocalChangeList)oldDefaultList);
170             }
171           }, InvokeAfterUpdateMode.SILENT, null, null);
172         }
173       }
174     });
175   }
176
177   public void projectOpened() {
178     initializeForNewProject();
179
180     if (ApplicationManager.getApplication().isUnitTestMode()) {
181       myWorker.initialized();
182       myUpdater.initialized();
183       ProjectLevelVcsManager.getInstance(myProject).addVcsListener(myVcsListener);
184     }
185     else {
186       ((ProjectLevelVcsManagerImpl) ProjectLevelVcsManager.getInstance(myProject)).addInitializationRequest(
187         VcsInitObject.CHANGE_LIST_MANAGER, new DumbAwareRunnable() {
188         public void run() {
189           myWorker.initialized();
190           myUpdater.initialized();
191           broadcastStateAfterLoad();
192           ProjectLevelVcsManager.getInstance(myProject).addVcsListener(myVcsListener);
193         }
194       });
195     }
196
197     myConflictTracker.startTracking();
198   }
199
200   private void broadcastStateAfterLoad() {
201     final List<LocalChangeList> listCopy;
202     synchronized (myDataLock) {
203       listCopy = getChangeListsCopy();
204     }
205     if (! listCopy.isEmpty()) {
206       myProject.getMessageBus().syncPublisher(LISTS_LOADED).processLoadedLists(listCopy);
207     }
208   }
209
210   private void initializeForNewProject() {
211     synchronized (myDataLock) {
212       if (myWorker.isEmpty()) {
213         final LocalChangeList list = myWorker.addChangeList(VcsBundle.message("changes.default.changlist.name"), null);
214         setDefaultChangeList(list);
215
216         if (myIgnoredIdeaLevel.isEmpty()) {
217           final String name = myProject.getName();
218           myIgnoredIdeaLevel.add(IgnoredBeanFactory.ignoreFile(name + WorkspaceFileType.DOT_DEFAULT_EXTENSION, myProject));
219           myIgnoredIdeaLevel.add(IgnoredBeanFactory.ignoreFile(Project.DIRECTORY_STORE_FOLDER + "/workspace.xml", myProject));
220         }
221       }
222     }
223   }
224
225   public void projectClosed() {
226     ProjectLevelVcsManager.getInstance(myProject).removeVcsListener(myVcsListener);
227
228     synchronized (myDataLock) {
229       if (myUpdateChangesProgressIndicator != null) {
230         myUpdateChangesProgressIndicator.cancel();
231       }
232     }
233
234     myUpdater.stop();
235     myConflictTracker.stopTracking();
236   }
237
238   @NotNull @NonNls
239   public String getComponentName() {
240     return "ChangeListManager";
241   }
242
243   public void initComponent() {
244   }
245
246   public void disposeComponent() {
247   }
248
249   /**
250    * update itself might produce actions done on AWT thread (invoked-after),
251    * so waiting for its completion on AWT thread is not good
252    *
253    * runnable is invoked on AWT thread
254    */
255   public void invokeAfterUpdate(final Runnable afterUpdate, final InvokeAfterUpdateMode mode, final String title, final ModalityState state) {
256     myUpdater.invokeAfterUpdate(afterUpdate, mode, title, null, state);
257   }
258
259   public void invokeAfterUpdate(final Runnable afterUpdate, final InvokeAfterUpdateMode mode, final String title,
260                                 final Consumer<VcsDirtyScopeManager> dirtyScopeManagerFiller, final ModalityState state) {
261     myUpdater.invokeAfterUpdate(afterUpdate, mode, title, dirtyScopeManagerFiller, state);
262   }
263
264   static class DisposedException extends RuntimeException {}
265
266   public void scheduleUpdate() {
267     myUpdater.schedule(true);
268   }
269
270   public void scheduleUpdate(boolean updateUnversionedFiles) {
271     myUpdater.schedule(updateUnversionedFiles);
272   }
273
274   private class ActualUpdater implements LocalChangesUpdater {
275     public void execute(boolean updateUnversioned, AtomicSectionsAware atomicSectionsAware) {
276       updateImmediately(updateUnversioned, atomicSectionsAware);
277     }
278   }
279
280   private void updateImmediately(final boolean updateUnversionedFiles, final AtomicSectionsAware atomicSectionsAware) {
281     FileHolderComposite composite;
282     ChangeListWorker changeListWorker;
283
284     final VcsDirtyScopeManagerImpl dirtyScopeManager;
285     try {
286       dirtyScopeManager = ((VcsDirtyScopeManagerImpl) VcsDirtyScopeManager.getInstanceChecked(myProject));
287     }
288     catch(ProcessCanceledException ex) {
289       return;
290     }
291     catch(Exception ex) {
292       LOG.error(ex);
293       return;
294     }
295     final VcsInvalidated invalidated = dirtyScopeManager.retrieveScopes();
296     if (invalidated == null || invalidated.isEmpty()) {
297       // a hack here; but otherwise everything here should be refactored ;)
298       if (invalidated != null && invalidated.isEmpty() && invalidated.isEverythingDirty()) {
299         VcsDirtyScopeManager.getInstance(myProject).markEverythingDirty();
300       }
301       return;
302     }
303     final boolean wasEverythingDirty = invalidated.isEverythingDirty();
304     final List<VcsDirtyScope> scopes = invalidated.getScopes();
305
306     try {
307       checkIfDisposed();
308
309       // copy existsing data to objects that would be updated.
310       // mark for "modifier" that update started (it would create duplicates of modification commands done by user during update;
311       // after update of copies of objects is complete, it would apply the same modifications to copies.)
312       synchronized (myDataLock) {
313         changeListWorker = myWorker.copy();
314         composite = updateUnversionedFiles ? (FileHolderComposite) myComposite.copy() : myComposite;
315         myModifier.enterUpdate();
316         if (wasEverythingDirty) {
317           myUpdateException = null;
318         }
319         if (updateUnversionedFiles && wasEverythingDirty) {
320           composite.cleanAll();         
321         }
322       }
323       if (wasEverythingDirty) {
324         changeListWorker.notifyStartProcessingChanges(null);
325       }
326       myChangesViewManager.scheduleRefresh();
327
328       final ChangeListManagerGate gate = changeListWorker.createSelfGate();
329
330       // do actual requests about file statuses
331       final UpdatingChangeListBuilder builder = new UpdatingChangeListBuilder(changeListWorker, composite, new Getter<Boolean>() {
332         public Boolean get() {
333           return myUpdater.isStopped();
334         }
335       }, updateUnversionedFiles, myIgnoredIdeaLevel, gate);
336
337       myUpdateChangesProgressIndicator = new EmptyProgressIndicator() {
338         @Override
339         public boolean isCanceled() {
340           return myUpdater.isStopped() || atomicSectionsAware.shouldExitAsap();
341         }
342         @Override
343         public void checkCanceled() {
344           checkIfDisposed();
345           atomicSectionsAware.checkShouldExit();
346         }
347       };
348       for (final VcsDirtyScope scope : scopes) {
349         atomicSectionsAware.checkShouldExit();
350
351         final AbstractVcs vcs = scope.getVcs();
352         if (vcs == null) continue;
353         final VcsAppendableDirtyScope adjustedScope = vcs.adjustDirtyScope((VcsAppendableDirtyScope) scope);
354
355         myChangesViewManager.updateProgressText(VcsBundle.message("changes.update.progress.message", vcs.getDisplayName()), false);
356         if (! wasEverythingDirty) {
357           changeListWorker.notifyStartProcessingChanges(adjustedScope);
358         }
359         if (updateUnversionedFiles && !wasEverythingDirty) {
360           composite.cleanScope(adjustedScope);
361         }
362         
363         try {
364           actualUpdate(wasEverythingDirty, composite, builder, adjustedScope, vcs, changeListWorker, gate);
365         }
366         catch (Throwable t) {
367           LOG.info(t);
368           if (t instanceof Error) {
369             throw (Error) t;
370           } else if (t instanceof RuntimeException) {
371             throw (RuntimeException) t;
372           }
373           throw new RuntimeException(t);
374         }
375
376         if (myUpdateException != null) break;
377       }
378
379       final boolean takeChanges = (myUpdateException == null);
380       
381       synchronized (myDataLock) {
382         // do same modifications to change lists as was done during update + do delayed notifications
383         if (wasEverythingDirty) {
384           changeListWorker.notifyDoneProcessingChanges(myDelayedNotificator.getProxyDispatcher());
385         }
386         myModifier.exitUpdate();
387         // should be applied for notifications to be delivered (they were delayed)
388         myModifier.apply(changeListWorker);
389         myModifier.clearQueue();
390         // update member from copy
391         if (takeChanges) {
392           myWorker.takeData(changeListWorker);
393         }
394
395         if (takeChanges && updateUnversionedFiles) {
396           boolean statusChanged = !myComposite.equals(composite);
397           myComposite = composite;
398           if (statusChanged) {
399             myDelayedNotificator.getProxyDispatcher().unchangedFileStatusChanged();
400           }
401         }
402
403         if (takeChanges) {
404           updateIgnoredFiles(false);
405         }
406         myShowLocalChangesInvalidated = false;
407       }
408       myChangesViewManager.scheduleRefresh();
409     }
410     catch (DisposedException e) {
411       // OK, we're finishing all the stuff now.
412     }
413     catch(ProcessCanceledException e) {
414       // OK, we're finishing all the stuff now.
415     }
416     catch(Exception ex) {
417       LOG.error(ex);
418     }
419     catch(AssertionError ex) {
420       LOG.error(ex);
421     }
422     finally {
423       dirtyScopeManager.changesProcessed();
424       
425       synchronized (myDataLock) {
426         myDelayedNotificator.getProxyDispatcher().changeListUpdateDone();
427         myChangesViewManager.scheduleRefresh();
428       }
429     }
430   }
431
432   private void actualUpdate(final boolean wasEverythingDirty, final FileHolderComposite composite, final UpdatingChangeListBuilder builder,
433                             final VcsDirtyScope scope, final AbstractVcs vcs, final ChangeListWorker changeListWorker,
434                             final ChangeListManagerGate gate) {
435     try {
436       final ChangeProvider changeProvider = vcs.getChangeProvider();
437       if (changeProvider != null) {
438         final FoldersCutDownWorker foldersCutDownWorker = new FoldersCutDownWorker();
439         try {
440           builder.setCurrent(scope, foldersCutDownWorker);
441           changeProvider.getChanges(scope, builder, myUpdateChangesProgressIndicator, gate);
442         }
443         catch (VcsException e) {
444           LOG.info(e);
445           if (myUpdateException == null) {
446             myUpdateException = e;
447           }
448         }
449         composite.getIgnoredFileHolder().calculateChildren();
450       }
451     }
452     finally {
453       if ((! myUpdater.isStopped()) && !wasEverythingDirty) {
454         changeListWorker.notifyDoneProcessingChanges(myDelayedNotificator.getProxyDispatcher());
455       }
456     }
457   }
458
459   private void checkIfDisposed() {
460     if (myUpdater.isStopped()) throw new DisposedException();
461   }
462
463   static boolean isUnder(final Change change, final VcsDirtyScope scope) {
464     final ContentRevision before = change.getBeforeRevision();
465     final ContentRevision after = change.getAfterRevision();
466     return before != null && scope.belongsTo(before.getFile()) || after != null && scope.belongsTo(after.getFile());
467   }
468
469   public List<LocalChangeList> getChangeListsCopy() {
470     synchronized (myDataLock) {
471       return myWorker.getListsCopy();
472     }
473   }
474
475   /**
476    * @deprecated 
477    * this method made equivalent to {@link #getChangeListsCopy()} so to don't be confused by method name,
478    * better use {@link #getChangeListsCopy()}
479    */
480   @NotNull
481   public List<LocalChangeList> getChangeLists() {
482     synchronized (myDataLock) {
483       return getChangeListsCopy();
484     }
485   }
486
487   public List<File> getAffectedPaths() {
488     synchronized (myDataLock) {
489       return myWorker.getAffectedPaths();
490     }
491   }
492
493   @NotNull
494   public List<VirtualFile> getAffectedFiles() {
495     synchronized (myDataLock) {
496       return myWorker.getAffectedFiles();
497     }
498   }
499
500   public List<VirtualFile> getUnversionedFiles() {
501     return myComposite.getVFHolder(FileHolder.HolderType.UNVERSIONED).getFiles();
502   }
503
504   Pair<Integer, Integer> getUnversionedFilesSize() {
505     final VirtualFileHolder holder = myComposite.getVFHolder(FileHolder.HolderType.UNVERSIONED);
506     return new Pair<Integer, Integer>(holder.getSize(), holder.getNumDirs());
507   }
508
509   List<VirtualFile> getModifiedWithoutEditing() {
510     return new ArrayList<VirtualFile>(myComposite.getVFHolder(FileHolder.HolderType.MODIFIED_WITHOUT_EDITING).getFiles());
511   }
512
513   /**
514    * @return only roots for ignored folders, and ignored files
515    */
516   List<VirtualFile> getIgnoredFiles() {
517     return new ArrayList<VirtualFile>(myComposite.getIgnoredFileHolder().getBranchToFileMap().values());
518   }
519
520   public List<VirtualFile> getLockedFolders() {
521     return new ArrayList<VirtualFile>(myComposite.getVFHolder(FileHolder.HolderType.LOCKED).getFiles());
522   }
523
524   Map<VirtualFile, LogicalLock> getLogicallyLockedFolders() {
525     return new HashMap<VirtualFile, LogicalLock>(((LogicallyLockedHolder) myComposite.get(FileHolder.HolderType.LOGICALLY_LOCKED)).getMap());
526   }
527
528   public boolean isLogicallyLocked(final VirtualFile file) {
529     return ((LogicallyLockedHolder) myComposite.get(FileHolder.HolderType.LOGICALLY_LOCKED)).getMap().containsKey(file);
530   }
531
532   public boolean isContainedInLocallyDeleted(final FilePath filePath) {
533     synchronized (myDataLock) {
534       return myWorker.isContainedInLocallyDeleted(filePath);
535     }
536   }
537
538   public List<LocallyDeletedChange> getDeletedFiles() {
539     synchronized (myDataLock) {
540       return myWorker.getLocallyDeleted().getFiles();
541     }
542   }
543
544   MultiMap<String, VirtualFile> getSwitchedFilesMap() {
545     synchronized (myDataLock) {
546       return myWorker.getSwitchedHolder().getBranchToFileMap();
547     }
548   }
549
550   @Nullable
551   Map<VirtualFile, String> getSwitchedRoots() {
552     synchronized (myDataLock) {
553       return ((SwitchedFileHolder) myComposite.get(FileHolder.HolderType.ROOT_SWITCH)).getFilesMapCopy();
554     }
555   }
556
557   public VcsException getUpdateException() {
558     return myUpdateException;
559   }
560
561   public boolean isFileAffected(final VirtualFile file) {
562     synchronized (myDataLock) {
563       return myWorker.getStatus(file) != null;
564     }
565   }
566
567   @Nullable
568   public LocalChangeList findChangeList(final String name) {
569     synchronized (myDataLock) {
570       return myWorker.getCopyByName(name);
571     }
572   }
573
574   @Override
575   public LocalChangeList getChangeList(String id) {
576     synchronized (myDataLock) {
577       return myWorker.getChangeList(id);
578     }
579   }
580
581   public LocalChangeList addChangeList(@NotNull String name, final String comment) {
582     synchronized (myDataLock) {
583       final LocalChangeList changeList = myModifier.addChangeList(name, comment);
584       myChangesViewManager.scheduleRefresh();
585       return changeList;
586     }
587   }
588
589   public void removeChangeList(final String name) {
590     synchronized (myDataLock) {
591       myModifier.removeChangeList(name);
592       myChangesViewManager.scheduleRefresh();
593     }
594   }
595
596   public void removeChangeList(LocalChangeList list) {
597     removeChangeList(list.getName());
598   }
599
600   /**
601    * does no modification to change lists, only notification is sent
602    */
603   @NotNull
604   public Runnable prepareForChangeDeletion(final Collection<Change> changes) {
605     final Map<String, LocalChangeList> lists = new HashMap<String, LocalChangeList>();
606     final Map<String, List<Change>> map;
607     synchronized (myDataLock) {
608       map = myWorker.listsForChanges(changes, lists);
609     }
610     return new Runnable() {
611       public void run() {
612         final ChangeListListener multicaster = myDelayedNotificator.getProxyDispatcher();
613         synchronized (myDataLock) {
614           for (Map.Entry<String, List<Change>> entry : map.entrySet()) {
615             final List<Change> changes = entry.getValue();
616             for (Iterator<Change> iterator = changes.iterator(); iterator.hasNext();) {
617               final Change change = iterator.next();
618               if (getChangeList(change) != null) {
619                 // was not actually rolled back
620                 iterator.remove();
621               }
622             }
623             multicaster.changesRemoved(changes, lists.get(entry.getKey()));
624           }
625         }
626       }
627     };
628   }
629
630   public void setDefaultChangeList(@NotNull LocalChangeList list) {
631     synchronized (myDataLock) {
632       myModifier.setDefault(list.getName());
633       myChangesViewManager.scheduleRefresh();
634     }
635   }
636
637   @Nullable
638   public LocalChangeList getDefaultChangeList() {
639     synchronized (myDataLock) {
640       return myWorker.getDefaultListCopy();
641     }
642   }
643
644   @Override
645   public boolean isDefaultChangeList(ChangeList list) {
646     return list instanceof LocalChangeList && myWorker.isDefaultList((LocalChangeList)list);
647   }
648
649   @NotNull
650   public Collection<LocalChangeList> getInvolvedListsFilterChanges(final Collection<Change> changes, final List<Change> validChanges) {
651     synchronized (myDataLock) {
652       return myWorker.getInvolvedListsFilterChanges(changes, validChanges);
653     }
654   }
655
656   @Nullable
657   public LocalChangeList getChangeList(Change change) {
658     synchronized (myDataLock) {
659       return myWorker.listForChange(change);
660     }
661   }
662
663   @Override
664   public String getChangeListNameIfOnlyOne(final Change[] changes) {
665     synchronized (myDataLock) {
666       return myWorker.listNameIfOnlyOne(changes);
667     }
668   }
669
670   /**
671    * @deprecated
672    * better use normal comparison, with equals
673    */
674   @Nullable
675   public LocalChangeList getIdentityChangeList(Change change) {
676     synchronized (myDataLock) {
677       final List<LocalChangeList> lists = myWorker.getListsCopy();
678       for (LocalChangeList list : lists) {
679         for(Change oldChange: list.getChanges()) {
680           if (oldChange == change) {
681             return list;
682           }
683         }
684       }
685       return null;
686     }
687   }
688
689   @Override
690   public boolean isInUpdate() {
691     synchronized (myDataLock) {
692       return myModifier.isInsideUpdate() || myShowLocalChangesInvalidated;
693     }
694   }
695
696   @Nullable
697   public Change getChange(@NotNull VirtualFile file) {
698     synchronized (myDataLock) {
699       final LocalChangeList list = myWorker.getListCopy(file);
700       if (list != null) {
701         for (Change change : list.getChanges()) {
702           final ContentRevision afterRevision = change.getAfterRevision();
703           if (afterRevision != null) {
704             String revisionPath = FileUtil.toSystemIndependentName(afterRevision.getFile().getIOFile().getPath());
705             if (FileUtil.pathsEqual(revisionPath, file.getPath())) return change;
706           }
707           final ContentRevision beforeRevision = change.getBeforeRevision();
708           if (beforeRevision != null) {
709             String revisionPath = FileUtil.toSystemIndependentName(beforeRevision.getFile().getIOFile().getPath());
710             if (FileUtil.pathsEqual(revisionPath, file.getPath())) return change;
711           }
712         }
713       }
714
715       return null;
716     }
717   }
718
719   @Override
720   public LocalChangeList getChangeList(@NotNull VirtualFile file) {
721     synchronized (myDataLock) {
722       return myWorker.getListCopy(file);
723     }
724   }
725
726   @Nullable
727   public Change getChange(final FilePath file) {
728     synchronized (myDataLock) {
729       return myWorker.getChangeForPath(file);
730     }
731   }
732
733   public boolean isUnversioned(VirtualFile file) {
734     return myComposite.getVFHolder(FileHolder.HolderType.UNVERSIONED).containsFile(file);
735   }
736
737   @NotNull
738   public FileStatus getStatus(VirtualFile file) {
739     synchronized (myDataLock) {
740       if (myComposite.getVFHolder(FileHolder.HolderType.UNVERSIONED).containsFile(file)) return FileStatus.UNKNOWN;
741       if (myComposite.getVFHolder(FileHolder.HolderType.MODIFIED_WITHOUT_EDITING).containsFile(file)) return FileStatus.HIJACKED;
742       if (myComposite.getIgnoredFileHolder().containsFile(file)) return FileStatus.IGNORED;
743
744       final FileStatus status = myWorker.getStatus(file);
745       if (status != null) {
746         return status;
747       }
748       if (myWorker.isSwitched(file)) return FileStatus.SWITCHED;
749       return FileStatus.NOT_CHANGED;
750     }
751   }
752
753   @NotNull
754   public Collection<Change> getChangesIn(VirtualFile dir) {
755     return getChangesIn(new FilePathImpl(dir));
756   }
757
758   @NotNull
759   public Collection<Change> getChangesIn(final FilePath dirPath) {
760     synchronized (myDataLock) {
761       return myWorker.getChangesIn(dirPath);
762     }
763   }
764
765   public void moveChangesTo(LocalChangeList list, final Change[] changes) {
766     synchronized (myDataLock) {
767       myModifier.moveChangesTo(list.getName(), changes);
768     }
769     SwingUtilities.invokeLater(new Runnable() {
770       public void run() {
771         myChangesViewManager.refreshView();
772       }
773     });
774   }
775
776   public void addUnversionedFiles(final LocalChangeList list, @NotNull final List<VirtualFile> files) {
777     final List<VcsException> exceptions = new ArrayList<VcsException>();
778     ChangesUtil.processVirtualFilesByVcs(myProject, files, new ChangesUtil.PerVcsProcessor<VirtualFile>() {
779       public void process(final AbstractVcs vcs, final List<VirtualFile> items) {
780         final CheckinEnvironment environment = vcs.getCheckinEnvironment();
781         if (environment != null) {
782           final List<VcsException> result = environment.scheduleUnversionedFilesForAddition(items);
783           if (result != null) {
784             exceptions.addAll(result);
785           }
786         }
787       }
788     });
789
790     if (exceptions.size() > 0) {
791       StringBuilder message = new StringBuilder(VcsBundle.message("error.adding.files.prompt"));
792       for(VcsException ex: exceptions) {
793         message.append("\n").append(ex.getMessage());
794       }
795       Messages.showErrorDialog(myProject, message.toString(), VcsBundle.message("error.adding.files.title"));
796     }
797
798     for (VirtualFile file : files) {
799       myFileStatusManager.fileStatusChanged(file);
800     }
801     VcsDirtyScopeManager.getInstance(myProject).filesDirty(files, null);
802
803     if (!list.isDefault()) {
804       // find the changes for the added files and move them to the necessary changelist
805       invokeAfterUpdate(new Runnable() {
806         public void run() {
807           synchronized (myDataLock) {
808             List<Change> changesToMove = new ArrayList<Change>();
809             final LocalChangeList defaultList = getDefaultChangeList();
810             for(Change change: defaultList.getChanges()) {
811               final ContentRevision afterRevision = change.getAfterRevision();
812               if (afterRevision != null) {
813                 VirtualFile vFile = afterRevision.getFile().getVirtualFile();
814                 if (files.contains(vFile)) {
815                   changesToMove.add(change);
816                 }
817               }
818             }
819
820             if (changesToMove.size() > 0) {
821               moveChangesTo(list, changesToMove.toArray(new Change[changesToMove.size()]));
822             }
823           }
824
825           myChangesViewManager.scheduleRefresh();
826         }
827       },  InvokeAfterUpdateMode.BACKGROUND_NOT_CANCELLABLE_NOT_AWT, VcsBundle.message("change.lists.manager.add.unversioned"), null);
828     } else {
829       myChangesViewManager.scheduleRefresh();
830     }
831   }
832
833   public Project getProject() {
834     return myProject;
835   }
836
837   public void addChangeListListener(ChangeListListener listener) {
838     myListeners.addListener(listener);
839   }
840
841
842   public void removeChangeListListener(ChangeListListener listener) {
843     myListeners.removeListener(listener);
844   }
845
846   public void registerCommitExecutor(CommitExecutor executor) {
847     myExecutors.add(executor);
848   }
849
850   public void commitChanges(LocalChangeList changeList, List<Change> changes) {
851     doCommit(changeList, changes, false);
852   }
853
854   private boolean doCommit(final LocalChangeList changeList, final List<Change> changes, final boolean synchronously) {
855     return new CommitHelper(myProject, changeList, changes, changeList.getName(),
856                      changeList.getComment(), new ArrayList<CheckinHandler>(), false, synchronously, NullableFunction.NULL).doCommit();
857   }
858
859   public void commitChangesSynchronously(LocalChangeList changeList, List<Change> changes) {
860     doCommit(changeList, changes, true);
861   }
862
863   public boolean commitChangesSynchronouslyWithResult(final LocalChangeList changeList, final List<Change> changes) {
864     return doCommit(changeList, changes, true);
865   }
866
867   @SuppressWarnings({"unchecked"})
868   public void readExternal(Element element) throws InvalidDataException {
869     if (! myProject.isDefault()) {
870       synchronized (myDataLock) {
871         myIgnoredIdeaLevel.clear();
872         new ChangeListManagerSerialization(myIgnoredIdeaLevel, myWorker).readExternal(element);
873         if ((! myWorker.isEmpty()) && getDefaultChangeList() == null) {
874           setDefaultChangeList(myWorker.getListsCopy().get(0));
875         }
876       }
877       myConflictTracker.loadState(element);
878     }
879   }
880
881   public void writeExternal(Element element) throws WriteExternalException {
882     if (! myProject.isDefault()) {
883       final IgnoredFilesComponent ignoredFilesComponent;
884       final ChangeListWorker worker;
885       synchronized (myDataLock) {
886         ignoredFilesComponent = new IgnoredFilesComponent(myProject);
887         ignoredFilesComponent.add(myIgnoredIdeaLevel.getFilesToIgnore());
888         worker = myWorker.copy();
889       }
890       new ChangeListManagerSerialization(ignoredFilesComponent, worker).writeExternal(element);
891       myConflictTracker.saveState(element);
892     }
893   }
894
895   // used in TeamCity
896   public void reopenFiles(List<FilePath> paths) {
897     final ReadonlyStatusHandlerImpl readonlyStatusHandler = (ReadonlyStatusHandlerImpl)ReadonlyStatusHandlerImpl.getInstance(myProject);
898     final boolean savedOption = readonlyStatusHandler.getState().SHOW_DIALOG;
899     readonlyStatusHandler.getState().SHOW_DIALOG = false;
900     try {
901       readonlyStatusHandler.ensureFilesWritable(collectFiles(paths));
902     }
903     finally {
904       readonlyStatusHandler.getState().SHOW_DIALOG = savedOption;
905     }
906   }
907
908   public List<CommitExecutor> getRegisteredExecutors() {
909     return Collections.unmodifiableList(myExecutors);
910   }
911
912   public void addFilesToIgnore(final IgnoredFileBean... filesToIgnore) {
913     myIgnoredIdeaLevel.add(filesToIgnore);
914     updateIgnoredFiles(true);
915   }
916
917   public void setFilesToIgnore(final IgnoredFileBean... filesToIgnore) {
918     myIgnoredIdeaLevel.set(filesToIgnore);
919     updateIgnoredFiles(true);
920   }
921
922   private void updateIgnoredFiles(final boolean checkIgnored) {
923     synchronized (myDataLock) {
924       List<VirtualFile> unversionedFiles = myComposite.getVFHolder(FileHolder.HolderType.UNVERSIONED).getFiles();
925       //List<VirtualFile> ignoredFiles = myComposite.getVFHolder(FileHolder.HolderType.IGNORED).getFiles();
926       boolean somethingChanged = false;
927       for(VirtualFile file: unversionedFiles) {
928         if (isIgnoredFile(file)) {
929           somethingChanged = true;
930           myComposite.getVFHolder(FileHolder.HolderType.UNVERSIONED).removeFile(file);
931           myComposite.getIgnoredFileHolder().addFile(file, "", false);
932         }
933       }
934       /*if (checkIgnored) {
935         for(VirtualFile file: ignoredFiles) {
936           if (!isIgnoredFile(file)) {
937             somethingChanged = true;
938             // the file may have been reported as ignored by the VCS, so we can't directly move it to unversioned files
939             VcsDirtyScopeManager.getInstance(myProject).fileDirty(file);
940           }
941         }
942       }*/
943       if (somethingChanged) {
944         myFileStatusManager.fileStatusesChanged();
945         myChangesViewManager.scheduleRefresh();
946       }
947     }
948   }
949
950   public IgnoredFileBean[] getFilesToIgnore() {
951     return myIgnoredIdeaLevel.getFilesToIgnore();
952   }
953
954   public boolean isIgnoredFile(@NotNull VirtualFile file) {
955     return myIgnoredIdeaLevel.isIgnoredFile(file);
956   }
957
958   @Nullable
959   public String getSwitchedBranch(final VirtualFile file) {
960     synchronized (myDataLock) {
961       return myWorker.getBranchForFile(file);
962     }
963   }
964
965   @Override
966   public String getDefaultListName() {
967     synchronized (myDataLock) {
968       return myWorker.getDefaultListName();
969     }
970   }
971
972   private static VirtualFile[] collectFiles(final List<FilePath> paths) {
973     final ArrayList<VirtualFile> result = new ArrayList<VirtualFile>();
974     for (FilePath path : paths) {
975       if (path.getVirtualFile() != null) {
976         result.add(path.getVirtualFile());
977       }
978     }
979
980     return VfsUtil.toVirtualFileArray(result);
981   }
982
983   public boolean setReadOnly(final String name, final boolean value) {
984     synchronized (myDataLock) {
985       final boolean result = myModifier.setReadOnly(name, value);
986       myChangesViewManager.scheduleRefresh();
987       return result;
988     }
989   }
990
991   public boolean editName(@NotNull final String fromName, @NotNull final String toName) {
992     synchronized (myDataLock) {
993       final boolean result = myModifier.editName(fromName, toName);
994       myChangesViewManager.scheduleRefresh();
995       return result;
996     }
997   }
998
999   public String editComment(@NotNull final String fromName, final String newComment) {
1000     synchronized (myDataLock) {
1001       final String oldComment = myModifier.editComment(fromName, newComment);
1002       myChangesViewManager.scheduleRefresh();
1003       return oldComment;
1004     }
1005   }
1006
1007   /**
1008    * Can be called only from not AWT thread; to do smthg after ChangeListManager refresh, call invokeAfterUpdate
1009    */
1010   public boolean ensureUpToDate(final boolean canBeCanceled) {
1011     final EnsureUpToDateFromNonAWTThread worker = new EnsureUpToDateFromNonAWTThread(myProject);
1012     worker.execute();
1013     return worker.isDone();
1014   }
1015
1016   // only a light attempt to show that some dirty scope request is asynchronously coming
1017   // for users to see changes are not valid
1018   // (commit -> asynch synch VFS -> asynch vcs dirty scope)
1019   public void showLocalChangesInvalidated() {
1020     synchronized (myDataLock) {
1021       myShowLocalChangesInvalidated = true;
1022     }
1023   }
1024
1025   public ChangelistConflictTracker getConflictTracker() {
1026     return myConflictTracker;
1027   }
1028
1029   private static class MyChangesDeltaForwarder implements PlusMinus<Pair<String, AbstractVcs>> {
1030     //private SlowlyClosingAlarm myAlarm;
1031     private RemoteRevisionsCache myRevisionsCache;
1032     private final ProjectLevelVcsManager myVcsManager;
1033     private ExecutorWrapper myExecutorWrapper;
1034     private final ExecutorService myService;
1035
1036
1037     public MyChangesDeltaForwarder(final Project project, final ExecutorService service) {
1038       myService = service;
1039       //myAlarm = ControlledAlarmFactory.createOnSharedThread(project, UpdateRequestsQueue.LOCAL_CHANGES_UPDATE, service);
1040       myExecutorWrapper = new ExecutorWrapper(project, UpdateRequestsQueue.LOCAL_CHANGES_UPDATE);
1041       myRevisionsCache = RemoteRevisionsCache.getInstance(project);
1042       myVcsManager = ProjectLevelVcsManager.getInstance(project);
1043     }
1044
1045     public void plus(final Pair<String, AbstractVcs> stringAbstractVcsPair) {
1046       final Pair<String, AbstractVcs> correctedPair = getCorrectedPair(stringAbstractVcsPair);
1047       if (correctedPair == null) return;
1048       myService.submit(new Runnable() {
1049         public void run() {
1050           myExecutorWrapper.submit(new Consumer<AtomicSectionsAware>() {
1051             public void consume(AtomicSectionsAware atomicSectionsAware) {
1052               myRevisionsCache.plus(correctedPair);
1053             }
1054           });
1055         }
1056       });
1057     }
1058
1059     public void minus(final Pair<String, AbstractVcs> stringAbstractVcsPair) {
1060       final Pair<String, AbstractVcs> correctedPair = getCorrectedPair(stringAbstractVcsPair);
1061       if (correctedPair == null) return;
1062       myService.submit(new Runnable() {
1063         public void run() {
1064           myExecutorWrapper.submit(new Consumer<AtomicSectionsAware>() {
1065             public void consume(AtomicSectionsAware atomicSectionsAware) {
1066               myRevisionsCache.minus(correctedPair);
1067             }
1068           });
1069           myRevisionsCache.minus(correctedPair);
1070         }
1071       });
1072     }
1073
1074     @Nullable
1075     private Pair<String, AbstractVcs> getCorrectedPair(final Pair<String, AbstractVcs> stringAbstractVcsPair) {
1076       Pair<String, AbstractVcs> correctedPair = stringAbstractVcsPair;
1077       if (stringAbstractVcsPair.getSecond() == null) {
1078         final String path = stringAbstractVcsPair.getFirst();
1079         final VcsKey vcsKey = findVcs(path);
1080         if (vcsKey == null) return null;
1081         correctedPair = new Pair<String, AbstractVcs>(path, myVcsManager.findVcsByName(vcsKey.getName()));
1082       }
1083       return correctedPair;
1084     }
1085
1086     @Nullable
1087     private VcsKey findVcs(final String path) {
1088       // does not matter directory or not
1089       final VirtualFile vf = LocalFileSystem.getInstance().refreshAndFindFileByIoFile(new File(path));
1090       if (vf == null) return null;
1091       final AbstractVcs vcs = myVcsManager.getVcsFor(vf);
1092       return vcs == null ? null : vcs.getKeyInstanceMethod();
1093     }
1094   }
1095 }