IDEA-27603 (Indicate which branch is being worked on)
[fedora-idea.git] / platform / vcs-impl / src / com / intellij / openapi / vcs / changes / ChangeListManagerImpl.java
blobca62a9966f0b2ba9eb5976cc4545fc00e7944df6
1 /*
2 * Copyright 2000-2009 JetBrains s.r.o.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
16 package com.intellij.openapi.vcs.changes;
18 import com.intellij.ide.highlighter.WorkspaceFileType;
19 import com.intellij.lifecycle.AtomicSectionsAware;
20 import com.intellij.lifecycle.ControlledAlarmFactory;
21 import com.intellij.lifecycle.SlowlyClosingAlarm;
22 import com.intellij.openapi.application.ApplicationManager;
23 import com.intellij.openapi.application.ModalityState;
24 import com.intellij.openapi.components.ProjectComponent;
25 import com.intellij.openapi.diagnostic.Logger;
26 import com.intellij.openapi.progress.EmptyProgressIndicator;
27 import com.intellij.openapi.progress.ProcessCanceledException;
28 import com.intellij.openapi.progress.ProgressIndicator;
29 import com.intellij.openapi.project.DumbAwareRunnable;
30 import com.intellij.openapi.project.Project;
31 import com.intellij.openapi.ui.Messages;
32 import com.intellij.openapi.util.*;
33 import com.intellij.openapi.util.io.FileUtil;
34 import com.intellij.openapi.vcs.*;
35 import com.intellij.openapi.vcs.changes.conflicts.ChangelistConflictTracker;
36 import com.intellij.openapi.vcs.changes.ui.CommitHelper;
37 import com.intellij.openapi.vcs.checkin.CheckinEnvironment;
38 import com.intellij.openapi.vcs.checkin.CheckinHandler;
39 import com.intellij.openapi.vcs.impl.ProjectLevelVcsManagerImpl;
40 import com.intellij.openapi.vcs.impl.VcsInitObject;
41 import com.intellij.openapi.vcs.readOnlyHandler.ReadonlyStatusHandlerImpl;
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.containers.MultiMap;
49 import com.intellij.util.messages.Topic;
50 import org.jdom.Element;
51 import org.jetbrains.annotations.NonNls;
52 import org.jetbrains.annotations.NotNull;
53 import org.jetbrains.annotations.Nullable;
55 import javax.swing.*;
56 import java.io.File;
57 import java.util.*;
58 import java.util.concurrent.ExecutorService;
59 import java.util.concurrent.ScheduledExecutorService;
61 /**
62 * @author max
64 public class ChangeListManagerImpl extends ChangeListManagerEx implements ProjectComponent, ChangeListOwner, JDOMExternalizable {
65 private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.vcs.changes.ChangeListManagerImpl");
67 private final Project myProject;
68 private final ChangesViewManager myChangesViewManager;
69 private final FileStatusManager myFileStatusManager;
70 private final UpdateRequestsQueue myUpdater;
72 @SuppressWarnings({"FieldAccessedSynchronizedAndUnsynchronized"})
73 private static final ScheduledExecutorService ourUpdateAlarm = ConcurrencyUtil.newSingleScheduledThreadExecutor("Change List Updater", Thread.MIN_PRIORITY + 1);
75 private final Modifier myModifier;
77 private FileHolderComposite myComposite;
79 private final ChangeListWorker myWorker;
80 private VcsException myUpdateException = null;
82 @SuppressWarnings({"FieldAccessedSynchronizedAndUnsynchronized"})
83 private final EventDispatcher<ChangeListListener> myListeners = EventDispatcher.create(ChangeListListener.class);
85 private final Object myDataLock = new Object();
87 private final List<CommitExecutor> myExecutors = new ArrayList<CommitExecutor>();
89 private final IgnoredFilesComponent myIgnoredIdeaLevel;
90 private ProgressIndicator myUpdateChangesProgressIndicator;
92 public static final Key<Object> DOCUMENT_BEING_COMMITTED_KEY = new Key<Object>("DOCUMENT_BEING_COMMITTED");
94 public static final Topic<LocalChangeListsLoadedListener> LISTS_LOADED = new Topic<LocalChangeListsLoadedListener>(
95 "LOCAL_CHANGE_LISTS_LOADED", LocalChangeListsLoadedListener.class);
97 private boolean myShowLocalChangesInvalidated;
99 private final DelayedNotificator myDelayedNotificator;
101 private final VcsListener myVcsListener = new VcsListener() {
102 public void directoryMappingChanged() {
103 VcsDirtyScopeManager.getInstanceChecked(myProject).markEverythingDirty();
106 private final ChangelistConflictTracker myConflictTracker;
108 public static ChangeListManagerImpl getInstanceImpl(final Project project) {
109 return (ChangeListManagerImpl) project.getComponent(ChangeListManager.class);
112 public ChangeListManagerImpl(final Project project) {
113 myProject = project;
114 myChangesViewManager = ChangesViewManager.getInstance(myProject);
115 myFileStatusManager = FileStatusManager.getInstance(myProject);
116 myComposite = new FileHolderComposite(project);
117 myIgnoredIdeaLevel = new IgnoredFilesComponent(myProject);
118 myUpdater = new UpdateRequestsQueue(myProject, ourUpdateAlarm, new ActualUpdater());
120 myWorker = new ChangeListWorker(myProject, new MyChangesDeltaForwarder(myProject, ourUpdateAlarm));
121 myDelayedNotificator = new DelayedNotificator(myListeners, ourUpdateAlarm);
122 myModifier = new Modifier(myWorker, myDelayedNotificator);
124 myConflictTracker = new ChangelistConflictTracker(project, this, myFileStatusManager, EditorNotifications.getInstance(project));
127 public void projectOpened() {
128 initializeForNewProject();
130 if (ApplicationManager.getApplication().isUnitTestMode()) {
131 myWorker.initialized();
132 myUpdater.initialized();
133 ProjectLevelVcsManager.getInstance(myProject).addVcsListener(myVcsListener);
135 else {
136 ((ProjectLevelVcsManagerImpl) ProjectLevelVcsManager.getInstance(myProject)).addInitializationRequest(
137 VcsInitObject.CHANGE_LIST_MANAGER, new DumbAwareRunnable() {
138 public void run() {
139 myWorker.initialized();
140 myUpdater.initialized();
141 broadcastStateAfterLoad();
142 ProjectLevelVcsManager.getInstance(myProject).addVcsListener(myVcsListener);
147 myConflictTracker.startTracking();
150 private void broadcastStateAfterLoad() {
151 final List<LocalChangeList> listCopy;
152 synchronized (myDataLock) {
153 listCopy = getChangeListsCopy();
155 if (! listCopy.isEmpty()) {
156 myProject.getMessageBus().syncPublisher(LISTS_LOADED).processLoadedLists(listCopy);
160 private void initializeForNewProject() {
161 synchronized (myDataLock) {
162 if (myWorker.isEmpty()) {
163 final LocalChangeList list = myWorker.addChangeList(VcsBundle.message("changes.default.changlist.name"), null);
164 setDefaultChangeList(list);
166 if (myIgnoredIdeaLevel.isEmpty()) {
167 final String name = myProject.getName();
168 myIgnoredIdeaLevel.add(IgnoredBeanFactory.ignoreFile(name + WorkspaceFileType.DOT_DEFAULT_EXTENSION, myProject));
169 myIgnoredIdeaLevel.add(IgnoredBeanFactory.ignoreFile(Project.DIRECTORY_STORE_FOLDER + "/workspace.xml", myProject));
175 public void projectClosed() {
176 ProjectLevelVcsManager.getInstance(myProject).removeVcsListener(myVcsListener);
178 synchronized (myDataLock) {
179 if (myUpdateChangesProgressIndicator != null) {
180 myUpdateChangesProgressIndicator.cancel();
184 myUpdater.stop();
185 myConflictTracker.stopTracking();
188 @NotNull @NonNls
189 public String getComponentName() {
190 return "ChangeListManager";
193 public void initComponent() {
196 public void disposeComponent() {
200 * update itself might produce actions done on AWT thread (invoked-after),
201 * so waiting for its completion on AWT thread is not good
203 * runnable is invoked on AWT thread
205 public void invokeAfterUpdate(final Runnable afterUpdate, final InvokeAfterUpdateMode mode, final String title, final ModalityState state) {
206 myUpdater.invokeAfterUpdate(afterUpdate, mode, title, null, state);
209 public void invokeAfterUpdate(final Runnable afterUpdate, final InvokeAfterUpdateMode mode, final String title,
210 final Consumer<VcsDirtyScopeManager> dirtyScopeManagerFiller, final ModalityState state) {
211 myUpdater.invokeAfterUpdate(afterUpdate, mode, title, dirtyScopeManagerFiller, state);
214 static class DisposedException extends RuntimeException {}
216 public void scheduleUpdate() {
217 myUpdater.schedule(true);
220 public void scheduleUpdate(boolean updateUnversionedFiles) {
221 myUpdater.schedule(updateUnversionedFiles);
224 private class ActualUpdater implements LocalChangesUpdater {
225 public void execute(boolean updateUnversioned, AtomicSectionsAware atomicSectionsAware) {
226 updateImmediately(updateUnversioned, atomicSectionsAware);
230 private void updateImmediately(final boolean updateUnversionedFiles, final AtomicSectionsAware atomicSectionsAware) {
231 FileHolderComposite composite;
232 ChangeListWorker changeListWorker;
234 final VcsDirtyScopeManagerImpl dirtyScopeManager;
235 try {
236 dirtyScopeManager = ((VcsDirtyScopeManagerImpl) VcsDirtyScopeManager.getInstanceChecked(myProject));
238 catch(ProcessCanceledException ex) {
239 return;
241 catch(Exception ex) {
242 LOG.error(ex);
243 return;
245 final VcsInvalidated invalidated = dirtyScopeManager.retrieveScopes();
246 if (invalidated == null || invalidated.isEmpty()) {
247 // a hack here; but otherwise everything here should be refactored ;)
248 if (invalidated.isEmpty() && invalidated.isEverythingDirty()) {
249 VcsDirtyScopeManager.getInstance(myProject).markEverythingDirty();
251 return;
253 final boolean wasEverythingDirty = invalidated.isEverythingDirty();
254 final List<VcsDirtyScope> scopes = invalidated.getScopes();
256 try {
257 checkIfDisposed();
259 // copy existsing data to objects that would be updated.
260 // mark for "modifier" that update started (it would create duplicates of modification commands done by user during update;
261 // after update of copies of objects is complete, it would apply the same modifications to copies.)
262 synchronized (myDataLock) {
263 changeListWorker = myWorker.copy();
264 composite = updateUnversionedFiles ? (FileHolderComposite) myComposite.copy() : myComposite;
265 myModifier.enterUpdate();
266 if (wasEverythingDirty) {
267 myUpdateException = null;
269 if (updateUnversionedFiles && wasEverythingDirty) {
270 composite.cleanAll();
273 if (wasEverythingDirty) {
274 changeListWorker.notifyStartProcessingChanges(null);
276 myChangesViewManager.scheduleRefresh();
278 final ChangeListManagerGate gate = changeListWorker.createSelfGate();
280 // do actual requests about file statuses
281 final UpdatingChangeListBuilder builder = new UpdatingChangeListBuilder(changeListWorker, composite, new Getter<Boolean>() {
282 public Boolean get() {
283 return myUpdater.isStopped();
285 }, updateUnversionedFiles, myIgnoredIdeaLevel, gate);
287 myUpdateChangesProgressIndicator = new EmptyProgressIndicator() {
288 @Override
289 public boolean isCanceled() {
290 return myUpdater.isStopped() || atomicSectionsAware.shouldExitAsap();
292 @Override
293 public void checkCanceled() {
294 checkIfDisposed();
295 atomicSectionsAware.checkShouldExit();
298 for (final VcsDirtyScope scope : scopes) {
299 atomicSectionsAware.checkShouldExit();
301 final AbstractVcs vcs = scope.getVcs();
302 if (vcs == null) continue;
303 final VcsAppendableDirtyScope adjustedScope = vcs.adjustDirtyScope((VcsAppendableDirtyScope) scope);
305 myChangesViewManager.updateProgressText(VcsBundle.message("changes.update.progress.message", vcs.getDisplayName()), false);
306 if (! wasEverythingDirty) {
307 changeListWorker.notifyStartProcessingChanges(adjustedScope);
309 if (updateUnversionedFiles && !wasEverythingDirty) {
310 composite.cleanScope(adjustedScope);
313 try {
314 actualUpdate(wasEverythingDirty, composite, builder, adjustedScope, vcs, changeListWorker, gate);
316 catch (Throwable t) {
317 LOG.info(t);
318 if (t instanceof Error) {
319 throw (Error) t;
320 } else if (t instanceof RuntimeException) {
321 throw (RuntimeException) t;
323 throw new RuntimeException(t);
326 if (myUpdateException != null) break;
329 final boolean takeChanges = (myUpdateException == null);
331 synchronized (myDataLock) {
332 // do same modifications to change lists as was done during update + do delayed notifications
333 if (wasEverythingDirty) {
334 changeListWorker.notifyDoneProcessingChanges(myDelayedNotificator.getProxyDispatcher());
336 myModifier.exitUpdate();
337 // should be applied for notifications to be delivered (they were delayed)
338 myModifier.apply(changeListWorker);
339 myModifier.clearQueue();
340 // update member from copy
341 if (takeChanges) {
342 myWorker.takeData(changeListWorker);
345 if (takeChanges && updateUnversionedFiles) {
346 boolean statusChanged = !myComposite.equals(composite);
347 myComposite = composite;
348 if (statusChanged) {
349 myDelayedNotificator.getProxyDispatcher().unchangedFileStatusChanged();
353 if (takeChanges) {
354 updateIgnoredFiles(false);
356 myShowLocalChangesInvalidated = false;
358 myChangesViewManager.scheduleRefresh();
360 catch (DisposedException e) {
361 // OK, we're finishing all the stuff now.
363 catch(ProcessCanceledException e) {
364 // OK, we're finishing all the stuff now.
366 catch(Exception ex) {
367 LOG.error(ex);
369 catch(AssertionError ex) {
370 LOG.error(ex);
372 finally {
373 dirtyScopeManager.changesProcessed();
375 synchronized (myDataLock) {
376 myDelayedNotificator.getProxyDispatcher().changeListUpdateDone();
377 myChangesViewManager.scheduleRefresh();
382 private void actualUpdate(final boolean wasEverythingDirty, final FileHolderComposite composite, final UpdatingChangeListBuilder builder,
383 final VcsDirtyScope scope, final AbstractVcs vcs, final ChangeListWorker changeListWorker,
384 final ChangeListManagerGate gate) {
385 try {
386 final ChangeProvider changeProvider = vcs.getChangeProvider();
387 if (changeProvider != null) {
388 final FoldersCutDownWorker foldersCutDownWorker = new FoldersCutDownWorker();
389 try {
390 builder.setCurrent(scope, foldersCutDownWorker);
391 changeProvider.getChanges(scope, builder, myUpdateChangesProgressIndicator, gate);
393 catch (VcsException e) {
394 LOG.info(e);
395 if (myUpdateException == null) {
396 myUpdateException = e;
399 composite.getIgnoredFileHolder().calculateChildren();
402 finally {
403 if ((! myUpdater.isStopped()) && !wasEverythingDirty) {
404 changeListWorker.notifyDoneProcessingChanges(myDelayedNotificator.getProxyDispatcher());
409 private void checkIfDisposed() {
410 if (myUpdater.isStopped()) throw new DisposedException();
413 static boolean isUnder(final Change change, final VcsDirtyScope scope) {
414 final ContentRevision before = change.getBeforeRevision();
415 final ContentRevision after = change.getAfterRevision();
416 return before != null && scope.belongsTo(before.getFile()) || after != null && scope.belongsTo(after.getFile());
419 public List<LocalChangeList> getChangeListsCopy() {
420 synchronized (myDataLock) {
421 return myWorker.getListsCopy();
426 * @deprecated
427 * this method made equivalent to {@link #getChangeListsCopy()} so to don't be confused by method name,
428 * better use {@link #getChangeListsCopy()}
430 @NotNull
431 public List<LocalChangeList> getChangeLists() {
432 synchronized (myDataLock) {
433 return getChangeListsCopy();
437 public List<File> getAffectedPaths() {
438 synchronized (myDataLock) {
439 return myWorker.getAffectedPaths();
443 @NotNull
444 public List<VirtualFile> getAffectedFiles() {
445 synchronized (myDataLock) {
446 return myWorker.getAffectedFiles();
450 List<VirtualFile> getUnversionedFiles() {
451 return new ArrayList<VirtualFile>(myComposite.getVFHolder(FileHolder.HolderType.UNVERSIONED).getFiles());
454 List<VirtualFile> getModifiedWithoutEditing() {
455 return new ArrayList<VirtualFile>(myComposite.getVFHolder(FileHolder.HolderType.MODIFIED_WITHOUT_EDITING).getFiles());
459 * @return only roots for ignored folders, and ignored files
461 List<VirtualFile> getIgnoredFiles() {
462 return new ArrayList<VirtualFile>(myComposite.getIgnoredFileHolder().getBranchToFileMap().values());
465 public List<VirtualFile> getLockedFolders() {
466 return new ArrayList<VirtualFile>(myComposite.getVFHolder(FileHolder.HolderType.LOCKED).getFiles());
469 Map<VirtualFile, LogicalLock> getLogicallyLockedFolders() {
470 return new HashMap<VirtualFile, LogicalLock>(((LogicallyLockedHolder) myComposite.get(FileHolder.HolderType.LOGICALLY_LOCKED)).getMap());
473 public boolean isLogicallyLocked(final VirtualFile file) {
474 return ((LogicallyLockedHolder) myComposite.get(FileHolder.HolderType.LOGICALLY_LOCKED)).getMap().containsKey(file);
477 public boolean isContainedInLocallyDeleted(final FilePath filePath) {
478 synchronized (myDataLock) {
479 return myWorker.isContainedInLocallyDeleted(filePath);
483 public List<LocallyDeletedChange> getDeletedFiles() {
484 synchronized (myDataLock) {
485 return myWorker.getLocallyDeleted().getFiles();
489 MultiMap<String, VirtualFile> getSwitchedFilesMap() {
490 synchronized (myDataLock) {
491 return myWorker.getSwitchedHolder().getBranchToFileMap();
495 @Nullable
496 Map<VirtualFile, String> getSwitchedRoots() {
497 synchronized (myDataLock) {
498 return ((SwitchedFileHolder) myComposite.get(FileHolder.HolderType.ROOT_SWITCH)).getFilesMapCopy();
502 public VcsException getUpdateException() {
503 return myUpdateException;
506 public boolean isFileAffected(final VirtualFile file) {
507 synchronized (myDataLock) {
508 return myWorker.getStatus(file) != null;
512 @Nullable
513 public LocalChangeList findChangeList(final String name) {
514 synchronized (myDataLock) {
515 return myWorker.getCopyByName(name);
519 @Override
520 public LocalChangeList getChangeList(String id) {
521 synchronized (myDataLock) {
522 return myWorker.getChangeList(id);
526 public LocalChangeList addChangeList(@NotNull String name, final String comment) {
527 synchronized (myDataLock) {
528 final LocalChangeList changeList = myModifier.addChangeList(name, comment);
529 myChangesViewManager.scheduleRefresh();
530 return changeList;
534 public void removeChangeList(final String name) {
535 synchronized (myDataLock) {
536 myModifier.removeChangeList(name);
537 myChangesViewManager.scheduleRefresh();
541 public void removeChangeList(LocalChangeList list) {
542 removeChangeList(list.getName());
546 * does no modification to change lists, only notification is sent
548 @NotNull
549 public Runnable prepareForChangeDeletion(final Collection<Change> changes) {
550 final Map<String, LocalChangeList> lists = new HashMap<String, LocalChangeList>();
551 final Map<String, List<Change>> map;
552 synchronized (myDataLock) {
553 map = myWorker.listsForChanges(changes, lists);
555 return new Runnable() {
556 public void run() {
557 final ChangeListListener multicaster = myDelayedNotificator.getProxyDispatcher();
558 synchronized (myDataLock) {
559 for (Map.Entry<String, List<Change>> entry : map.entrySet()) {
560 final List<Change> changes = entry.getValue();
561 for (Iterator<Change> iterator = changes.iterator(); iterator.hasNext();) {
562 final Change change = iterator.next();
563 if (getChangeList(change) != null) {
564 // was not actually rolled back
565 iterator.remove();
568 multicaster.changesRemoved(changes, lists.get(entry.getKey()));
575 public void setDefaultChangeList(@NotNull LocalChangeList list) {
576 synchronized (myDataLock) {
577 myModifier.setDefault(list.getName());
578 myChangesViewManager.scheduleRefresh();
582 @Nullable
583 public LocalChangeList getDefaultChangeList() {
584 synchronized (myDataLock) {
585 return myWorker.getDefaultListCopy();
589 @Override
590 public boolean isDefaultChangeList(ChangeList list) {
591 return list instanceof LocalChangeList && myWorker.isDefaultList((LocalChangeList)list);
594 @NotNull
595 public Collection<LocalChangeList> getInvolvedListsFilterChanges(final Collection<Change> changes, final List<Change> validChanges) {
596 synchronized (myDataLock) {
597 return myWorker.getInvolvedListsFilterChanges(changes, validChanges);
601 @Nullable
602 public LocalChangeList getChangeList(Change change) {
603 synchronized (myDataLock) {
604 return myWorker.listForChange(change);
608 @Override
609 public String getChangeListNameIfOnlyOne(final Change[] changes) {
610 synchronized (myDataLock) {
611 return myWorker.listNameIfOnlyOne(changes);
616 * @deprecated
617 * better use normal comparison, with equals
619 @Nullable
620 public LocalChangeList getIdentityChangeList(Change change) {
621 synchronized (myDataLock) {
622 final List<LocalChangeList> lists = myWorker.getListsCopy();
623 for (LocalChangeList list : lists) {
624 for(Change oldChange: list.getChanges()) {
625 if (oldChange == change) {
626 return list;
630 return null;
634 @Override
635 public boolean isInUpdate() {
636 synchronized (myDataLock) {
637 return myModifier.isInsideUpdate() || myShowLocalChangesInvalidated;
641 @Nullable
642 public Change getChange(@NotNull VirtualFile file) {
643 synchronized (myDataLock) {
644 final LocalChangeList list = myWorker.getListCopy(file);
645 if (list != null) {
646 for (Change change : list.getChanges()) {
647 final ContentRevision afterRevision = change.getAfterRevision();
648 if (afterRevision != null) {
649 String revisionPath = FileUtil.toSystemIndependentName(afterRevision.getFile().getIOFile().getPath());
650 if (FileUtil.pathsEqual(revisionPath, file.getPath())) return change;
652 final ContentRevision beforeRevision = change.getBeforeRevision();
653 if (beforeRevision != null) {
654 String revisionPath = FileUtil.toSystemIndependentName(beforeRevision.getFile().getIOFile().getPath());
655 if (FileUtil.pathsEqual(revisionPath, file.getPath())) return change;
660 return null;
664 @Override
665 public LocalChangeList getChangeList(@NotNull VirtualFile file) {
666 synchronized (myDataLock) {
667 return myWorker.getListCopy(file);
671 @Nullable
672 public Change getChange(final FilePath file) {
673 synchronized (myDataLock) {
674 return myWorker.getChangeForPath(file);
678 public boolean isUnversioned(VirtualFile file) {
679 return myComposite.getVFHolder(FileHolder.HolderType.UNVERSIONED).containsFile(file);
682 @NotNull
683 public FileStatus getStatus(VirtualFile file) {
684 synchronized (myDataLock) {
685 if (myComposite.getVFHolder(FileHolder.HolderType.UNVERSIONED).containsFile(file)) return FileStatus.UNKNOWN;
686 if (myComposite.getVFHolder(FileHolder.HolderType.MODIFIED_WITHOUT_EDITING).containsFile(file)) return FileStatus.HIJACKED;
687 if (myComposite.getIgnoredFileHolder().containsFile(file)) return FileStatus.IGNORED;
689 final FileStatus status = myWorker.getStatus(file);
690 if (status != null) {
691 return status;
693 if (myWorker.isSwitched(file)) return FileStatus.SWITCHED;
694 return FileStatus.NOT_CHANGED;
698 @NotNull
699 public Collection<Change> getChangesIn(VirtualFile dir) {
700 return getChangesIn(new FilePathImpl(dir));
703 @NotNull
704 public Collection<Change> getChangesIn(final FilePath dirPath) {
705 synchronized (myDataLock) {
706 return myWorker.getChangesIn(dirPath);
710 public void moveChangesTo(LocalChangeList list, final Change[] changes) {
711 synchronized (myDataLock) {
712 myModifier.moveChangesTo(list.getName(), changes);
714 SwingUtilities.invokeLater(new Runnable() {
715 public void run() {
716 myChangesViewManager.refreshView();
721 public void addUnversionedFiles(final LocalChangeList list, @NotNull final List<VirtualFile> files) {
722 final List<VcsException> exceptions = new ArrayList<VcsException>();
723 ChangesUtil.processVirtualFilesByVcs(myProject, files, new ChangesUtil.PerVcsProcessor<VirtualFile>() {
724 public void process(final AbstractVcs vcs, final List<VirtualFile> items) {
725 final CheckinEnvironment environment = vcs.getCheckinEnvironment();
726 if (environment != null) {
727 final List<VcsException> result = environment.scheduleUnversionedFilesForAddition(items);
728 if (result != null) {
729 exceptions.addAll(result);
735 if (exceptions.size() > 0) {
736 StringBuilder message = new StringBuilder(VcsBundle.message("error.adding.files.prompt"));
737 for(VcsException ex: exceptions) {
738 message.append("\n").append(ex.getMessage());
740 Messages.showErrorDialog(myProject, message.toString(), VcsBundle.message("error.adding.files.title"));
743 for (VirtualFile file : files) {
744 myFileStatusManager.fileStatusChanged(file);
746 VcsDirtyScopeManager.getInstance(myProject).filesDirty(files, null);
748 if (!list.isDefault()) {
749 // find the changes for the added files and move them to the necessary changelist
750 invokeAfterUpdate(new Runnable() {
751 public void run() {
752 synchronized (myDataLock) {
753 List<Change> changesToMove = new ArrayList<Change>();
754 final LocalChangeList defaultList = getDefaultChangeList();
755 for(Change change: defaultList.getChanges()) {
756 final ContentRevision afterRevision = change.getAfterRevision();
757 if (afterRevision != null) {
758 VirtualFile vFile = afterRevision.getFile().getVirtualFile();
759 if (files.contains(vFile)) {
760 changesToMove.add(change);
765 if (changesToMove.size() > 0) {
766 moveChangesTo(list, changesToMove.toArray(new Change[changesToMove.size()]));
770 myChangesViewManager.scheduleRefresh();
772 }, InvokeAfterUpdateMode.BACKGROUND_NOT_CANCELLABLE, VcsBundle.message("change.lists.manager.add.unversioned"), null);
773 } else {
774 myChangesViewManager.scheduleRefresh();
778 public Project getProject() {
779 return myProject;
782 public void addChangeListListener(ChangeListListener listener) {
783 myListeners.addListener(listener);
787 public void removeChangeListListener(ChangeListListener listener) {
788 myListeners.removeListener(listener);
791 public void registerCommitExecutor(CommitExecutor executor) {
792 myExecutors.add(executor);
795 public void commitChanges(LocalChangeList changeList, List<Change> changes) {
796 doCommit(changeList, changes, false);
799 private boolean doCommit(final LocalChangeList changeList, final List<Change> changes, final boolean synchronously) {
800 return new CommitHelper(myProject, changeList, changes, changeList.getName(),
801 changeList.getComment(), new ArrayList<CheckinHandler>(), false, synchronously, null).doCommit();
804 public void commitChangesSynchronously(LocalChangeList changeList, List<Change> changes) {
805 doCommit(changeList, changes, true);
808 public boolean commitChangesSynchronouslyWithResult(final LocalChangeList changeList, final List<Change> changes) {
809 return doCommit(changeList, changes, true);
812 @SuppressWarnings({"unchecked"})
813 public void readExternal(Element element) throws InvalidDataException {
814 if (! myProject.isDefault()) {
815 synchronized (myDataLock) {
816 myIgnoredIdeaLevel.clear();
817 new ChangeListManagerSerialization(myIgnoredIdeaLevel, myWorker).readExternal(element);
818 if ((! myWorker.isEmpty()) && getDefaultChangeList() == null) {
819 setDefaultChangeList(myWorker.getListsCopy().get(0));
822 myConflictTracker.loadState(element);
826 public void writeExternal(Element element) throws WriteExternalException {
827 if (! myProject.isDefault()) {
828 final IgnoredFilesComponent ignoredFilesComponent;
829 final ChangeListWorker worker;
830 synchronized (myDataLock) {
831 ignoredFilesComponent = new IgnoredFilesComponent(myProject);
832 ignoredFilesComponent.add(myIgnoredIdeaLevel.getFilesToIgnore());
833 worker = myWorker.copy();
835 new ChangeListManagerSerialization(ignoredFilesComponent, worker).writeExternal(element);
836 myConflictTracker.saveState(element);
840 // used in TeamCity
841 public void reopenFiles(List<FilePath> paths) {
842 final ReadonlyStatusHandlerImpl readonlyStatusHandler = (ReadonlyStatusHandlerImpl)ReadonlyStatusHandlerImpl.getInstance(myProject);
843 final boolean savedOption = readonlyStatusHandler.getState().SHOW_DIALOG;
844 readonlyStatusHandler.getState().SHOW_DIALOG = false;
845 try {
846 readonlyStatusHandler.ensureFilesWritable(collectFiles(paths));
848 finally {
849 readonlyStatusHandler.getState().SHOW_DIALOG = savedOption;
853 public List<CommitExecutor> getRegisteredExecutors() {
854 return Collections.unmodifiableList(myExecutors);
857 public void addFilesToIgnore(final IgnoredFileBean... filesToIgnore) {
858 myIgnoredIdeaLevel.add(filesToIgnore);
859 updateIgnoredFiles(true);
862 public void setFilesToIgnore(final IgnoredFileBean... filesToIgnore) {
863 myIgnoredIdeaLevel.set(filesToIgnore);
864 updateIgnoredFiles(true);
867 private void updateIgnoredFiles(final boolean checkIgnored) {
868 synchronized (myDataLock) {
869 List<VirtualFile> unversionedFiles = myComposite.getVFHolder(FileHolder.HolderType.UNVERSIONED).getFiles();
870 //List<VirtualFile> ignoredFiles = myComposite.getVFHolder(FileHolder.HolderType.IGNORED).getFiles();
871 boolean somethingChanged = false;
872 for(VirtualFile file: unversionedFiles) {
873 if (isIgnoredFile(file)) {
874 somethingChanged = true;
875 myComposite.getVFHolder(FileHolder.HolderType.UNVERSIONED).removeFile(file);
876 myComposite.getIgnoredFileHolder().addFile(file, "", false);
879 /*if (checkIgnored) {
880 for(VirtualFile file: ignoredFiles) {
881 if (!isIgnoredFile(file)) {
882 somethingChanged = true;
883 // the file may have been reported as ignored by the VCS, so we can't directly move it to unversioned files
884 VcsDirtyScopeManager.getInstance(myProject).fileDirty(file);
888 if (somethingChanged) {
889 myFileStatusManager.fileStatusesChanged();
890 myChangesViewManager.scheduleRefresh();
895 public IgnoredFileBean[] getFilesToIgnore() {
896 return myIgnoredIdeaLevel.getFilesToIgnore();
899 public boolean isIgnoredFile(@NotNull VirtualFile file) {
900 return myIgnoredIdeaLevel.isIgnoredFile(file);
903 @Nullable
904 public String getSwitchedBranch(final VirtualFile file) {
905 synchronized (myDataLock) {
906 return myWorker.getBranchForFile(file);
910 @Override
911 public String getDefaultListName() {
912 synchronized (myDataLock) {
913 return myWorker.getDefaultListName();
917 private static VirtualFile[] collectFiles(final List<FilePath> paths) {
918 final ArrayList<VirtualFile> result = new ArrayList<VirtualFile>();
919 for (FilePath path : paths) {
920 if (path.getVirtualFile() != null) {
921 result.add(path.getVirtualFile());
925 return VfsUtil.toVirtualFileArray(result);
928 public boolean setReadOnly(final String name, final boolean value) {
929 synchronized (myDataLock) {
930 final boolean result = myModifier.setReadOnly(name, value);
931 myChangesViewManager.scheduleRefresh();
932 return result;
936 public boolean editName(@NotNull final String fromName, @NotNull final String toName) {
937 synchronized (myDataLock) {
938 final boolean result = myModifier.editName(fromName, toName);
939 myChangesViewManager.scheduleRefresh();
940 return result;
944 public String editComment(@NotNull final String fromName, final String newComment) {
945 synchronized (myDataLock) {
946 final String oldComment = myModifier.editComment(fromName, newComment);
947 myChangesViewManager.scheduleRefresh();
948 return oldComment;
953 * Can be called only from not AWT thread; to do smthg after ChangeListManager refresh, call invokeAfterUpdate
955 public boolean ensureUpToDate(final boolean canBeCanceled) {
956 final EnsureUpToDateFromNonAWTThread worker = new EnsureUpToDateFromNonAWTThread(myProject);
957 worker.execute();
958 return worker.isDone();
961 // only a light attempt to show that some dirty scope request is asynchronously coming
962 // for users to see changes are not valid
963 // (commit -> asynch synch VFS -> asynch vcs dirty scope)
964 public void showLocalChangesInvalidated() {
965 synchronized (myDataLock) {
966 myShowLocalChangesInvalidated = true;
970 public ChangelistConflictTracker getConflictTracker() {
971 return myConflictTracker;
974 private static class MyChangesDeltaForwarder implements PlusMinus<Pair<String, AbstractVcs>> {
975 private SlowlyClosingAlarm myAlarm;
976 private RemoteRevisionsCache myRevisionsCache;
977 private final ProjectLevelVcsManager myVcsManager;
979 public MyChangesDeltaForwarder(final Project project, final ExecutorService service) {
980 myAlarm = ControlledAlarmFactory.createOnSharedThread(project, "changes delta consumer forwarder", service);
981 myRevisionsCache = RemoteRevisionsCache.getInstance(project);
982 myVcsManager = ProjectLevelVcsManager.getInstance(project);
985 public void plus(final Pair<String, AbstractVcs> stringAbstractVcsPair) {
986 myAlarm.addRequest(new Runnable() {
987 public void run() {
988 myRevisionsCache.plus(getCorrectedPair(stringAbstractVcsPair));
993 public void minus(final Pair<String, AbstractVcs> stringAbstractVcsPair) {
994 myAlarm.addRequest(new Runnable() {
995 public void run() {
996 myRevisionsCache.minus(getCorrectedPair(stringAbstractVcsPair));
1001 private Pair<String, AbstractVcs> getCorrectedPair(final Pair<String, AbstractVcs> stringAbstractVcsPair) {
1002 Pair<String, AbstractVcs> correctedPair = stringAbstractVcsPair;
1003 if (stringAbstractVcsPair.getSecond() == null) {
1004 final String path = stringAbstractVcsPair.getFirst();
1005 correctedPair = new Pair<String, AbstractVcs>(path, myVcsManager.findVcsByName(findVcs(path).getName()));
1007 return correctedPair;
1010 @Nullable
1011 private VcsKey findVcs(final String path) {
1012 // does not matter directory or not
1013 final AbstractVcs vcs = myVcsManager.getVcsFor(FilePathImpl.create(new File(path), false));
1014 return vcs == null ? null : vcs.getKeyInstanceMethod();