IDEADEV-39612 (Deadlock in ChangeListManagerImpl)
[fedora-idea.git] / vcs-impl / src / com / intellij / openapi / vcs / changes / ChangeListManagerImpl.java
blobb9b3ba467f56a16fdf3012917b073395b32e1de6
1 package com.intellij.openapi.vcs.changes;
3 import com.intellij.ide.highlighter.WorkspaceFileType;
4 import com.intellij.lifecycle.AtomicSectionsAware;
5 import com.intellij.lifecycle.ControlledAlarmFactory;
6 import com.intellij.lifecycle.SlowlyClosingAlarm;
7 import com.intellij.openapi.application.ApplicationManager;
8 import com.intellij.openapi.application.ModalityState;
9 import com.intellij.openapi.components.ProjectComponent;
10 import com.intellij.openapi.diagnostic.Logger;
11 import com.intellij.openapi.progress.EmptyProgressIndicator;
12 import com.intellij.openapi.progress.ProcessCanceledException;
13 import com.intellij.openapi.progress.ProgressIndicator;
14 import com.intellij.openapi.project.DumbAwareRunnable;
15 import com.intellij.openapi.project.Project;
16 import com.intellij.openapi.startup.StartupManager;
17 import com.intellij.openapi.ui.Messages;
18 import com.intellij.openapi.util.*;
19 import com.intellij.openapi.util.io.FileUtil;
20 import com.intellij.openapi.vcs.*;
21 import com.intellij.openapi.vcs.changes.ui.CommitHelper;
22 import com.intellij.openapi.vcs.checkin.CheckinEnvironment;
23 import com.intellij.openapi.vcs.checkin.CheckinHandler;
24 import com.intellij.openapi.vcs.readOnlyHandler.ReadonlyStatusHandlerImpl;
25 import com.intellij.openapi.vfs.VirtualFile;
26 import com.intellij.util.ConcurrencyUtil;
27 import com.intellij.util.Consumer;
28 import com.intellij.util.EventDispatcher;
29 import com.intellij.util.containers.MultiMap;
30 import com.intellij.util.messages.Topic;
31 import org.jdom.Element;
32 import org.jetbrains.annotations.NonNls;
33 import org.jetbrains.annotations.NotNull;
34 import org.jetbrains.annotations.Nullable;
36 import javax.swing.*;
37 import java.io.File;
38 import java.util.*;
39 import java.util.concurrent.ExecutorService;
40 import java.util.concurrent.ScheduledExecutorService;
42 /**
43 * @author max
45 public class ChangeListManagerImpl extends ChangeListManagerEx implements ProjectComponent, ChangeListOwner, JDOMExternalizable {
46 private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.vcs.changes.ChangeListManagerImpl");
48 private final Project myProject;
49 private final ChangesViewManager myChangesViewManager;
50 private final FileStatusManager myFileStatusManager;
51 private final UpdateRequestsQueue myUpdater;
53 @SuppressWarnings({"FieldAccessedSynchronizedAndUnsynchronized"})
54 private static final ScheduledExecutorService ourUpdateAlarm = ConcurrencyUtil.newSingleScheduledThreadExecutor("Change List Updater");
56 private final Modifier myModifier;
58 private FileHolderComposite myComposite;
60 private final ChangeListWorker myWorker;
61 private VcsException myUpdateException = null;
63 @SuppressWarnings({"FieldAccessedSynchronizedAndUnsynchronized"})
64 private final EventDispatcher<ChangeListListener> myListeners = EventDispatcher.create(ChangeListListener.class);
66 private final Object myDataLock = new Object();
68 private final List<CommitExecutor> myExecutors = new ArrayList<CommitExecutor>();
70 private final IgnoredFilesComponent myIgnoredIdeaLevel;
71 private ProgressIndicator myUpdateChangesProgressIndicator;
73 public static final Key<Object> DOCUMENT_BEING_COMMITTED_KEY = new Key<Object>("DOCUMENT_BEING_COMMITTED");
75 public static final Topic<LocalChangeListsLoadedListener> LISTS_LOADED = new Topic<LocalChangeListsLoadedListener>(
76 "LOCAL_CHANGE_LISTS_LOADED", LocalChangeListsLoadedListener.class);
78 private boolean myShowLocalChangesInvalidated;
80 private final DelayedNotificator myDelayedNotificator;
82 private final VcsListener myVcsListener = new VcsListener() {
83 public void directoryMappingChanged() {
84 VcsDirtyScopeManager.getInstanceChecked(myProject).markEverythingDirty();
85 scheduleUpdate();
89 public static ChangeListManagerImpl getInstanceImpl(final Project project) {
90 return (ChangeListManagerImpl) project.getComponent(ChangeListManager.class);
93 public ChangeListManagerImpl(final Project project) {
94 myProject = project;
95 myChangesViewManager = ChangesViewManager.getInstance(myProject);
96 myFileStatusManager = FileStatusManager.getInstance(myProject);
97 myComposite = new FileHolderComposite(project);
98 myIgnoredIdeaLevel = new IgnoredFilesComponent(myProject);
99 myUpdater = new UpdateRequestsQueue(myProject, ourUpdateAlarm, new ActualUpdater());
101 myWorker = new ChangeListWorker(myProject, new MyChangesDeltaForwarder(myProject, ourUpdateAlarm));
102 myDelayedNotificator = new DelayedNotificator(myListeners, ourUpdateAlarm);
103 myModifier = new Modifier(myWorker, myDelayedNotificator);
106 public void projectOpened() {
107 initializeForNewProject();
109 if (ApplicationManager.getApplication().isUnitTestMode()) {
110 myWorker.initialized();
111 myUpdater.initialized();
112 ProjectLevelVcsManager.getInstance(myProject).addVcsListener(myVcsListener);
114 else {
115 StartupManager.getInstance(myProject).registerPostStartupActivity(new DumbAwareRunnable() {
116 public void run() {
117 myWorker.initialized();
118 myUpdater.initialized();
119 broadcastStateAfterLoad();
120 ProjectLevelVcsManager.getInstance(myProject).addVcsListener(myVcsListener);
126 private void broadcastStateAfterLoad() {
127 final List<LocalChangeList> listCopy;
128 synchronized (myDataLock) {
129 listCopy = getChangeListsCopy();
131 if (! listCopy.isEmpty()) {
132 myProject.getMessageBus().syncPublisher(LISTS_LOADED).processLoadedLists(listCopy);
136 private void initializeForNewProject() {
137 synchronized (myDataLock) {
138 if (myWorker.isEmpty()) {
139 final LocalChangeList list = myWorker.addChangeList(VcsBundle.message("changes.default.changlist.name"), null);
140 setDefaultChangeList(list);
142 if (myIgnoredIdeaLevel.isEmpty()) {
143 final String name = myProject.getName();
144 myIgnoredIdeaLevel.add(IgnoredBeanFactory.ignoreFile(name + WorkspaceFileType.DOT_DEFAULT_EXTENSION, myProject));
145 myIgnoredIdeaLevel.add(IgnoredBeanFactory.ignoreFile(Project.DIRECTORY_STORE_FOLDER + "/workspace.xml", myProject));
151 public void projectClosed() {
152 ProjectLevelVcsManager.getInstance(myProject).removeVcsListener(myVcsListener);
154 synchronized (myDataLock) {
155 if (myUpdateChangesProgressIndicator != null) {
156 myUpdateChangesProgressIndicator.cancel();
160 myUpdater.stop();
163 @NotNull @NonNls
164 public String getComponentName() {
165 return "ChangeListManager";
168 public void initComponent() {
171 public void disposeComponent() {
175 * update itself might produce actions done on AWT thread (invoked-after),
176 * so waiting for its completion on AWT thread is not good
178 * runnable is invoked on AWT thread
180 public void invokeAfterUpdate(final Runnable afterUpdate, final InvokeAfterUpdateMode mode, final String title, final ModalityState state) {
181 myUpdater.invokeAfterUpdate(afterUpdate, mode, title, null, state);
184 public void invokeAfterUpdate(final Runnable afterUpdate, final InvokeAfterUpdateMode mode, final String title,
185 final Consumer<VcsDirtyScopeManager> dirtyScopeManagerFiller, final ModalityState state) {
186 myUpdater.invokeAfterUpdate(afterUpdate, mode, title, dirtyScopeManagerFiller, state);
189 static class DisposedException extends RuntimeException {}
191 public void scheduleUpdate() {
192 myUpdater.schedule(true);
195 public void scheduleUpdate(boolean updateUnversionedFiles) {
196 myUpdater.schedule(updateUnversionedFiles);
199 private class ActualUpdater implements LocalChangesUpdater {
200 public void execute(boolean updateUnversioned, AtomicSectionsAware atomicSectionsAware) {
201 updateImmediately(updateUnversioned, atomicSectionsAware);
205 private void updateImmediately(final boolean updateUnversionedFiles, final AtomicSectionsAware atomicSectionsAware) {
206 FileHolderComposite composite;
207 ChangeListWorker changeListWorker;
209 final VcsDirtyScopeManagerImpl dirtyScopeManager;
210 try {
211 dirtyScopeManager = ((VcsDirtyScopeManagerImpl) VcsDirtyScopeManager.getInstanceChecked(myProject));
213 catch(ProcessCanceledException ex) {
214 return;
216 catch(Exception ex) {
217 LOG.error(ex);
218 return;
220 final VcsInvalidated invalidated = dirtyScopeManager.retrieveScopes();
221 if (invalidated == null || invalidated.isEmpty()) {
222 return;
224 final boolean wasEverythingDirty = invalidated.isEverythingDirty();
225 final List<VcsDirtyScope> scopes = invalidated.getScopes();
227 try {
228 checkIfDisposed();
230 // copy existsing data to objects that would be updated.
231 // mark for "modifier" that update started (it would create duplicates of modification commands done by user during update;
232 // after update of copies of objects is complete, it would apply the same modifications to copies.)
233 synchronized (myDataLock) {
234 changeListWorker = myWorker.copy();
235 composite = updateUnversionedFiles ? (FileHolderComposite) myComposite.copy() : myComposite;
236 myModifier.enterUpdate();
237 if (wasEverythingDirty) {
238 myUpdateException = null;
240 if (updateUnversionedFiles && wasEverythingDirty) {
241 composite.cleanAll();
244 if (wasEverythingDirty) {
245 changeListWorker.notifyStartProcessingChanges(null);
247 myChangesViewManager.scheduleRefresh();
249 final ChangeListManagerGate gate = changeListWorker.createSelfGate();
251 // do actual requests about file statuses
252 final UpdatingChangeListBuilder builder = new UpdatingChangeListBuilder(changeListWorker, composite, new Getter<Boolean>() {
253 public Boolean get() {
254 return myUpdater.isStopped();
256 }, updateUnversionedFiles, myIgnoredIdeaLevel, gate);
258 myUpdateChangesProgressIndicator = new EmptyProgressIndicator() {
259 @Override
260 public boolean isCanceled() {
261 return myUpdater.isStopped() || atomicSectionsAware.shouldExitAsap();
263 @Override
264 public void checkCanceled() {
265 checkIfDisposed();
266 atomicSectionsAware.checkShouldExit();
269 for (final VcsDirtyScope scope : scopes) {
270 atomicSectionsAware.checkShouldExit();
272 final AbstractVcs vcs = scope.getVcs();
273 if (vcs == null) continue;
275 myChangesViewManager.updateProgressText(VcsBundle.message("changes.update.progress.message", vcs.getDisplayName()), false);
276 if (! wasEverythingDirty) {
277 changeListWorker.notifyStartProcessingChanges(scope);
279 if (updateUnversionedFiles && !wasEverythingDirty) {
280 composite.cleanScope(scope);
282 actualUpdate(wasEverythingDirty, composite, builder, scope, vcs, changeListWorker, gate);
283 if (myUpdateException != null) break;
286 final boolean takeChanges = (myUpdateException == null);
288 synchronized (myDataLock) {
289 // do same modifications to change lists as was done during update + do delayed notifications
290 if (wasEverythingDirty) {
291 changeListWorker.notifyDoneProcessingChanges(myDelayedNotificator.getProxyDispatcher());
293 myModifier.exitUpdate();
294 // should be applied for notifications to be delivered (they were delayed)
295 myModifier.apply(changeListWorker);
296 myModifier.clearQueue();
297 // update member from copy
298 if (takeChanges) {
299 myWorker.takeData(changeListWorker);
302 if (takeChanges && updateUnversionedFiles) {
303 boolean statusChanged = !myComposite.equals(composite);
304 myComposite = composite;
305 if (statusChanged) {
306 myDelayedNotificator.getProxyDispatcher().unchangedFileStatusChanged();
310 if (takeChanges) {
311 updateIgnoredFiles(false);
313 myShowLocalChangesInvalidated = false;
315 myChangesViewManager.scheduleRefresh();
317 catch (DisposedException e) {
318 // OK, we're finishing all the stuff now.
320 catch(ProcessCanceledException e) {
321 // OK, we're finishing all the stuff now.
323 catch(Exception ex) {
324 LOG.error(ex);
326 catch(AssertionError ex) {
327 LOG.error(ex);
329 finally {
330 synchronized (myDataLock) {
331 myDelayedNotificator.getProxyDispatcher().changeListUpdateDone();
332 myChangesViewManager.scheduleRefresh();
337 private void actualUpdate(final boolean wasEverythingDirty, final FileHolderComposite composite, final UpdatingChangeListBuilder builder,
338 final VcsDirtyScope scope, final AbstractVcs vcs, final ChangeListWorker changeListWorker,
339 final ChangeListManagerGate gate) {
340 try {
341 final ChangeProvider changeProvider = vcs.getChangeProvider();
342 if (changeProvider != null) {
343 final FoldersCutDownWorker foldersCutDownWorker = new FoldersCutDownWorker();
344 try {
345 builder.setCurrent(scope, foldersCutDownWorker);
346 changeProvider.getChanges(scope, builder, myUpdateChangesProgressIndicator, gate);
348 catch (VcsException e) {
349 LOG.info(e);
350 if (myUpdateException == null) {
351 myUpdateException = e;
354 composite.getIgnoredFileHolder().calculateChildren();
357 finally {
358 if ((! myUpdater.isStopped()) && !wasEverythingDirty) {
359 changeListWorker.notifyDoneProcessingChanges(myDelayedNotificator.getProxyDispatcher());
364 private void checkIfDisposed() {
365 if (myUpdater.isStopped()) throw new DisposedException();
368 static boolean isUnder(final Change change, final VcsDirtyScope scope) {
369 final ContentRevision before = change.getBeforeRevision();
370 final ContentRevision after = change.getAfterRevision();
371 return before != null && scope.belongsTo(before.getFile()) || after != null && scope.belongsTo(after.getFile());
374 public List<LocalChangeList> getChangeListsCopy() {
375 synchronized (myDataLock) {
376 return myWorker.getListsCopy();
381 * @deprecated
382 * this method made equivalent to {@link #getChangeListsCopy()} so to don't be confused by method name,
383 * better use {@link #getChangeListsCopy()}
385 @NotNull
386 public List<LocalChangeList> getChangeLists() {
387 synchronized (myDataLock) {
388 return getChangeListsCopy();
392 public List<File> getAffectedPaths() {
393 synchronized (myDataLock) {
394 return myWorker.getAffectedPaths();
398 @NotNull
399 public List<VirtualFile> getAffectedFiles() {
400 synchronized (myDataLock) {
401 return myWorker.getAffectedFiles();
405 List<VirtualFile> getUnversionedFiles() {
406 return new ArrayList<VirtualFile>(myComposite.getVFHolder(FileHolder.HolderType.UNVERSIONED).getFiles());
409 List<VirtualFile> getModifiedWithoutEditing() {
410 return new ArrayList<VirtualFile>(myComposite.getVFHolder(FileHolder.HolderType.MODIFIED_WITHOUT_EDITING).getFiles());
414 * @return only roots for ignored folders, and ignored files
416 List<VirtualFile> getIgnoredFiles() {
417 return new ArrayList<VirtualFile>(myComposite.getIgnoredFileHolder().getBranchToFileMap().values());
420 public List<VirtualFile> getLockedFolders() {
421 return new ArrayList<VirtualFile>(myComposite.getVFHolder(FileHolder.HolderType.LOCKED).getFiles());
424 Map<VirtualFile, LogicalLock> getLogicallyLockedFolders() {
425 return new HashMap<VirtualFile, LogicalLock>(((LogicallyLockedHolder) myComposite.get(FileHolder.HolderType.LOGICALLY_LOCKED)).getMap());
428 public boolean isLogicallyLocked(final VirtualFile file) {
429 return ((LogicallyLockedHolder) myComposite.get(FileHolder.HolderType.LOGICALLY_LOCKED)).getMap().containsKey(file);
432 public boolean isContainedInLocallyDeleted(final FilePath filePath) {
433 synchronized (myDataLock) {
434 return myWorker.isContainedInLocallyDeleted(filePath);
438 public List<LocallyDeletedChange> getDeletedFiles() {
439 synchronized (myDataLock) {
440 return myWorker.getLocallyDeleted().getFiles();
444 MultiMap<String, VirtualFile> getSwitchedFilesMap() {
445 synchronized (myDataLock) {
446 return myWorker.getSwitchedHolder().getBranchToFileMap();
450 public VcsException getUpdateException() {
451 return myUpdateException;
454 public boolean isFileAffected(final VirtualFile file) {
455 synchronized (myDataLock) {
456 return myWorker.getStatus(file) != null;
460 @Nullable
461 public LocalChangeList findChangeList(final String name) {
462 synchronized (myDataLock) {
463 return myWorker.getCopyByName(name);
467 @Override
468 public LocalChangeList getChangeList(String id) {
469 synchronized (myDataLock) {
470 return myWorker.getChangeList(id);
474 public LocalChangeList addChangeList(@NotNull String name, final String comment) {
475 synchronized (myDataLock) {
476 final LocalChangeList changeList = myModifier.addChangeList(name, comment);
477 myChangesViewManager.scheduleRefresh();
478 return changeList;
482 public void removeChangeList(final String name) {
483 synchronized (myDataLock) {
484 myModifier.removeChangeList(name);
485 myChangesViewManager.scheduleRefresh();
489 public void removeChangeList(LocalChangeList list) {
490 removeChangeList(list.getName());
494 * does no modification to change lists, only notification is sent
496 @NotNull
497 public Runnable prepareForChangeDeletion(final Collection<Change> changes) {
498 final Map<String, LocalChangeList> lists = new HashMap<String, LocalChangeList>();
499 final Map<String, List<Change>> map;
500 synchronized (myDataLock) {
501 map = myWorker.listsForChanges(changes, lists);
503 return new Runnable() {
504 public void run() {
505 final ChangeListListener multicaster = myDelayedNotificator.getProxyDispatcher();
506 synchronized (myDataLock) {
507 for (Map.Entry<String, List<Change>> entry : map.entrySet()) {
508 final List<Change> changes = entry.getValue();
509 for (Iterator<Change> iterator = changes.iterator(); iterator.hasNext();) {
510 final Change change = iterator.next();
511 if (getChangeList(change) != null) {
512 // was not actually rolled back
513 iterator.remove();
516 multicaster.changesRemoved(changes, lists.get(entry.getKey()));
523 public void setDefaultChangeList(@NotNull LocalChangeList list) {
524 synchronized (myDataLock) {
525 myModifier.setDefault(list.getName());
526 myChangesViewManager.scheduleRefresh();
530 @Nullable
531 public LocalChangeList getDefaultChangeList() {
532 synchronized (myDataLock) {
533 return myWorker.getDefaultListCopy();
537 @NotNull
538 public Collection<LocalChangeList> getInvolvedListsFilterChanges(final Collection<Change> changes, final List<Change> validChanges) {
539 synchronized (myDataLock) {
540 return myWorker.getInvolvedListsFilterChanges(changes, validChanges);
544 @Nullable
545 public LocalChangeList getChangeList(Change change) {
546 synchronized (myDataLock) {
547 return myWorker.listForChange(change);
551 @Override
552 public String getChangeListNameIfOnlyOne(final Change[] changes) {
553 synchronized (myDataLock) {
554 return myWorker.listNameIfOnlyOne(changes);
559 * @deprecated
560 * better use normal comparison, with equals
562 @Nullable
563 public LocalChangeList getIdentityChangeList(Change change) {
564 synchronized (myDataLock) {
565 final List<LocalChangeList> lists = myWorker.getListsCopy();
566 for (LocalChangeList list : lists) {
567 for(Change oldChange: list.getChanges()) {
568 if (oldChange == change) {
569 return list;
573 return null;
577 @Override
578 public boolean isInUpdate() {
579 synchronized (myDataLock) {
580 return myModifier.isInsideUpdate() || myShowLocalChangesInvalidated;
584 @Nullable
585 public Change getChange(VirtualFile file) {
586 synchronized (myDataLock) {
587 final LocalChangeList list = myWorker.getListCopy(file);
588 if (list != null) {
589 for (Change change : list.getChanges()) {
590 final ContentRevision afterRevision = change.getAfterRevision();
591 if (afterRevision != null) {
592 String revisionPath = FileUtil.toSystemIndependentName(afterRevision.getFile().getIOFile().getPath());
593 if (FileUtil.pathsEqual(revisionPath, file.getPath())) return change;
595 final ContentRevision beforeRevision = change.getBeforeRevision();
596 if (beforeRevision != null) {
597 String revisionPath = FileUtil.toSystemIndependentName(beforeRevision.getFile().getIOFile().getPath());
598 if (FileUtil.pathsEqual(revisionPath, file.getPath())) return change;
603 return null;
607 @Nullable
608 public Change getChange(final FilePath file) {
609 synchronized (myDataLock) {
610 return myWorker.getChangeForPath(file);
614 public boolean isUnversioned(VirtualFile file) {
615 return myComposite.getVFHolder(FileHolder.HolderType.UNVERSIONED).containsFile(file);
618 @NotNull
619 public FileStatus getStatus(VirtualFile file) {
620 synchronized (myDataLock) {
621 if (myComposite.getVFHolder(FileHolder.HolderType.UNVERSIONED).containsFile(file)) return FileStatus.UNKNOWN;
622 if (myComposite.getVFHolder(FileHolder.HolderType.MODIFIED_WITHOUT_EDITING).containsFile(file)) return FileStatus.HIJACKED;
623 if (myComposite.getIgnoredFileHolder().containsFile(file)) return FileStatus.IGNORED;
625 final FileStatus status = myWorker.getStatus(file);
626 if (status != null) {
627 return status;
629 if (myWorker.isSwitched(file)) return FileStatus.SWITCHED;
630 return FileStatus.NOT_CHANGED;
634 @NotNull
635 public Collection<Change> getChangesIn(VirtualFile dir) {
636 return getChangesIn(new FilePathImpl(dir));
639 @NotNull
640 public Collection<Change> getChangesIn(final FilePath dirPath) {
641 synchronized (myDataLock) {
642 return myWorker.getChangesIn(dirPath);
646 public void moveChangesTo(LocalChangeList list, final Change[] changes) {
647 synchronized (myDataLock) {
648 myModifier.moveChangesTo(list.getName(), changes);
650 SwingUtilities.invokeLater(new Runnable() {
651 public void run() {
652 myChangesViewManager.refreshView();
657 public void addUnversionedFiles(final LocalChangeList list, @NotNull final List<VirtualFile> files) {
658 final List<VcsException> exceptions = new ArrayList<VcsException>();
659 ChangesUtil.processVirtualFilesByVcs(myProject, files, new ChangesUtil.PerVcsProcessor<VirtualFile>() {
660 public void process(final AbstractVcs vcs, final List<VirtualFile> items) {
661 final CheckinEnvironment environment = vcs.getCheckinEnvironment();
662 if (environment != null) {
663 final List<VcsException> result = environment.scheduleUnversionedFilesForAddition(items);
664 if (result != null) {
665 exceptions.addAll(result);
671 if (exceptions.size() > 0) {
672 StringBuilder message = new StringBuilder(VcsBundle.message("error.adding.files.prompt"));
673 for(VcsException ex: exceptions) {
674 message.append("\n").append(ex.getMessage());
676 Messages.showErrorDialog(myProject, message.toString(), VcsBundle.message("error.adding.files.title"));
679 for (VirtualFile file : files) {
680 myFileStatusManager.fileStatusChanged(file);
682 VcsDirtyScopeManager.getInstance(myProject).filesDirty(files, null);
684 if (!list.isDefault()) {
685 // find the changes for the added files and move them to the necessary changelist
686 invokeAfterUpdate(new Runnable() {
687 public void run() {
688 synchronized (myDataLock) {
689 List<Change> changesToMove = new ArrayList<Change>();
690 final LocalChangeList defaultList = getDefaultChangeList();
691 for(Change change: defaultList.getChanges()) {
692 final ContentRevision afterRevision = change.getAfterRevision();
693 if (afterRevision != null) {
694 VirtualFile vFile = afterRevision.getFile().getVirtualFile();
695 if (files.contains(vFile)) {
696 changesToMove.add(change);
701 if (changesToMove.size() > 0) {
702 moveChangesTo(list, changesToMove.toArray(new Change[changesToMove.size()]));
706 myChangesViewManager.scheduleRefresh();
708 }, InvokeAfterUpdateMode.BACKGROUND_NOT_CANCELLABLE, VcsBundle.message("change.lists.manager.add.unversioned"), null);
709 } else {
710 myChangesViewManager.scheduleRefresh();
714 public Project getProject() {
715 return myProject;
718 public void addChangeListListener(ChangeListListener listener) {
719 myListeners.addListener(listener);
723 public void removeChangeListListener(ChangeListListener listener) {
724 myListeners.removeListener(listener);
727 public void registerCommitExecutor(CommitExecutor executor) {
728 myExecutors.add(executor);
731 public void commitChanges(LocalChangeList changeList, List<Change> changes) {
732 doCommit(changeList, changes, false);
735 private boolean doCommit(final LocalChangeList changeList, final List<Change> changes, final boolean synchronously) {
736 return new CommitHelper(myProject, changeList, changes, changeList.getName(),
737 changeList.getComment(), new ArrayList<CheckinHandler>(), false, synchronously, null).doCommit();
740 public void commitChangesSynchronously(LocalChangeList changeList, List<Change> changes) {
741 doCommit(changeList, changes, true);
744 public boolean commitChangesSynchronouslyWithResult(final LocalChangeList changeList, final List<Change> changes) {
745 return doCommit(changeList, changes, true);
748 @SuppressWarnings({"unchecked"})
749 public void readExternal(Element element) throws InvalidDataException {
750 if (! myProject.isDefault()) {
751 synchronized (myDataLock) {
752 myIgnoredIdeaLevel.clear();
753 new ChangeListManagerSerialization(myIgnoredIdeaLevel, myWorker).readExternal(element);
754 if ((! myWorker.isEmpty()) && getDefaultChangeList() == null) {
755 setDefaultChangeList(myWorker.getListsCopy().get(0));
761 public void writeExternal(Element element) throws WriteExternalException {
762 if (! myProject.isDefault()) {
763 final IgnoredFilesComponent ignoredFilesComponent;
764 final ChangeListWorker worker;
765 synchronized (myDataLock) {
766 ignoredFilesComponent = new IgnoredFilesComponent(myProject);
767 ignoredFilesComponent.add(myIgnoredIdeaLevel.getFilesToIgnore());
768 worker = myWorker.copy();
770 new ChangeListManagerSerialization(ignoredFilesComponent, worker).writeExternal(element);
774 // used in TeamCity
775 public void reopenFiles(List<FilePath> paths) {
776 final ReadonlyStatusHandlerImpl readonlyStatusHandler = (ReadonlyStatusHandlerImpl)ReadonlyStatusHandlerImpl.getInstance(myProject);
777 final boolean savedOption = readonlyStatusHandler.getState().SHOW_DIALOG;
778 readonlyStatusHandler.getState().SHOW_DIALOG = false;
779 try {
780 readonlyStatusHandler.ensureFilesWritable(collectFiles(paths));
782 finally {
783 readonlyStatusHandler.getState().SHOW_DIALOG = savedOption;
787 public List<CommitExecutor> getRegisteredExecutors() {
788 return Collections.unmodifiableList(myExecutors);
791 public void addFilesToIgnore(final IgnoredFileBean... filesToIgnore) {
792 myIgnoredIdeaLevel.add(filesToIgnore);
793 updateIgnoredFiles(true);
796 public void setFilesToIgnore(final IgnoredFileBean... filesToIgnore) {
797 myIgnoredIdeaLevel.set(filesToIgnore);
798 updateIgnoredFiles(true);
801 private void updateIgnoredFiles(final boolean checkIgnored) {
802 synchronized (myDataLock) {
803 List<VirtualFile> unversionedFiles = myComposite.getVFHolder(FileHolder.HolderType.UNVERSIONED).getFiles();
804 //List<VirtualFile> ignoredFiles = myComposite.getVFHolder(FileHolder.HolderType.IGNORED).getFiles();
805 boolean somethingChanged = false;
806 for(VirtualFile file: unversionedFiles) {
807 if (isIgnoredFile(file)) {
808 somethingChanged = true;
809 myComposite.getVFHolder(FileHolder.HolderType.UNVERSIONED).removeFile(file);
810 myComposite.getIgnoredFileHolder().addFile(file, "", false);
813 /*if (checkIgnored) {
814 for(VirtualFile file: ignoredFiles) {
815 if (!isIgnoredFile(file)) {
816 somethingChanged = true;
817 // the file may have been reported as ignored by the VCS, so we can't directly move it to unversioned files
818 VcsDirtyScopeManager.getInstance(myProject).fileDirty(file);
822 if (somethingChanged) {
823 myFileStatusManager.fileStatusesChanged();
824 myChangesViewManager.scheduleRefresh();
829 public IgnoredFileBean[] getFilesToIgnore() {
830 return myIgnoredIdeaLevel.getFilesToIgnore();
833 public boolean isIgnoredFile(@NotNull VirtualFile file) {
834 return myIgnoredIdeaLevel.isIgnoredFile(file);
837 @Nullable
838 public String getSwitchedBranch(final VirtualFile file) {
839 synchronized (myDataLock) {
840 return myWorker.getBranchForFile(file);
844 @Override
845 public String getDefaultListName() {
846 synchronized (myDataLock) {
847 return myWorker.getDefaultListName();
851 private static VirtualFile[] collectFiles(final List<FilePath> paths) {
852 final ArrayList<VirtualFile> result = new ArrayList<VirtualFile>();
853 for (FilePath path : paths) {
854 if (path.getVirtualFile() != null) {
855 result.add(path.getVirtualFile());
859 return result.toArray(new VirtualFile[result.size()]);
862 public boolean setReadOnly(final String name, final boolean value) {
863 synchronized (myDataLock) {
864 final boolean result = myModifier.setReadOnly(name, value);
865 myChangesViewManager.scheduleRefresh();
866 return result;
870 public boolean editName(@NotNull final String fromName, @NotNull final String toName) {
871 synchronized (myDataLock) {
872 final boolean result = myModifier.editName(fromName, toName);
873 myChangesViewManager.scheduleRefresh();
874 return result;
878 public String editComment(@NotNull final String fromName, final String newComment) {
879 synchronized (myDataLock) {
880 final String oldComment = myModifier.editComment(fromName, newComment);
881 myChangesViewManager.scheduleRefresh();
882 return oldComment;
887 * Can be called only from not AWT thread; to do smthg after ChangeListManager refresh, call invokeAfterUpdate
889 public boolean ensureUpToDate(final boolean canBeCanceled) {
890 final EnsureUpToDateFromNonAWTThread worker = new EnsureUpToDateFromNonAWTThread(myProject);
891 worker.execute();
892 return worker.isDone();
895 // only a light attempt to show that some dirty scope request is asynchronously coming
896 // for users to see changes are not valid
897 // (commit -> asynch synch VFS -> asynch vcs dirty scope)
898 public void showLocalChangesInvalidated() {
899 synchronized (myDataLock) {
900 myShowLocalChangesInvalidated = true;
904 private static class MyChangesDeltaForwarder implements PlusMinus<Pair<String, AbstractVcs>> {
905 private SlowlyClosingAlarm myAlarm;
906 private RemoteRevisionsCache myRevisionsCache;
907 private final ProjectLevelVcsManager myVcsManager;
909 public MyChangesDeltaForwarder(final Project project, final ExecutorService service) {
910 myAlarm = ControlledAlarmFactory.createOnSharedThread(project, "changes delta consumer forwarder", service);
911 myRevisionsCache = RemoteRevisionsCache.getInstance(project);
912 myVcsManager = ProjectLevelVcsManager.getInstance(project);
915 public void plus(final Pair<String, AbstractVcs> stringAbstractVcsPair) {
916 myAlarm.addRequest(new Runnable() {
917 public void run() {
918 myRevisionsCache.plus(getCorrectedPair(stringAbstractVcsPair));
923 public void minus(final Pair<String, AbstractVcs> stringAbstractVcsPair) {
924 myAlarm.addRequest(new Runnable() {
925 public void run() {
926 myRevisionsCache.minus(getCorrectedPair(stringAbstractVcsPair));
931 private Pair<String, AbstractVcs> getCorrectedPair(final Pair<String, AbstractVcs> stringAbstractVcsPair) {
932 Pair<String, AbstractVcs> correctedPair = stringAbstractVcsPair;
933 if (stringAbstractVcsPair.getSecond() == null) {
934 final String path = stringAbstractVcsPair.getFirst();
935 correctedPair = new Pair<String, AbstractVcs>(path, myVcsManager.findVcsByName(findVcs(path).getName()));
937 return correctedPair;
940 @Nullable
941 private VcsKey findVcs(final String path) {
942 // does not matter directory or not
943 final AbstractVcs vcs = myVcsManager.getVcsFor(FilePathImpl.create(new File(path), false));
944 return vcs == null ? null : vcs.getKeyInstanceMethod();