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
;
58 import java
.util
.concurrent
.ExecutorService
;
59 import java
.util
.concurrent
.ScheduledExecutorService
;
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
) {
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
);
136 ((ProjectLevelVcsManagerImpl
) ProjectLevelVcsManager
.getInstance(myProject
)).addInitializationRequest(
137 VcsInitObject
.CHANGE_LIST_MANAGER
, new DumbAwareRunnable() {
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();
185 myConflictTracker
.stopTracking();
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
;
236 dirtyScopeManager
= ((VcsDirtyScopeManagerImpl
) VcsDirtyScopeManager
.getInstanceChecked(myProject
));
238 catch(ProcessCanceledException ex
) {
241 catch(Exception ex
) {
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();
253 final boolean wasEverythingDirty
= invalidated
.isEverythingDirty();
254 final List
<VcsDirtyScope
> scopes
= invalidated
.getScopes();
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() {
289 public boolean isCanceled() {
290 return myUpdater
.isStopped() || atomicSectionsAware
.shouldExitAsap();
293 public void checkCanceled() {
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
);
314 actualUpdate(wasEverythingDirty
, composite
, builder
, adjustedScope
, vcs
, changeListWorker
, gate
);
316 catch (Throwable t
) {
318 if (t
instanceof Error
) {
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
342 myWorker
.takeData(changeListWorker
);
345 if (takeChanges
&& updateUnversionedFiles
) {
346 boolean statusChanged
= !myComposite
.equals(composite
);
347 myComposite
= composite
;
349 myDelayedNotificator
.getProxyDispatcher().unchangedFileStatusChanged();
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
) {
369 catch(AssertionError ex
) {
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
) {
386 final ChangeProvider changeProvider
= vcs
.getChangeProvider();
387 if (changeProvider
!= null) {
388 final FoldersCutDownWorker foldersCutDownWorker
= new FoldersCutDownWorker();
390 builder
.setCurrent(scope
, foldersCutDownWorker
);
391 changeProvider
.getChanges(scope
, builder
, myUpdateChangesProgressIndicator
, gate
);
393 catch (VcsException e
) {
395 if (myUpdateException
== null) {
396 myUpdateException
= e
;
399 composite
.getIgnoredFileHolder().calculateChildren();
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();
427 * this method made equivalent to {@link #getChangeListsCopy()} so to don't be confused by method name,
428 * better use {@link #getChangeListsCopy()}
431 public List
<LocalChangeList
> getChangeLists() {
432 synchronized (myDataLock
) {
433 return getChangeListsCopy();
437 public List
<File
> getAffectedPaths() {
438 synchronized (myDataLock
) {
439 return myWorker
.getAffectedPaths();
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();
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;
513 public LocalChangeList
findChangeList(final String name
) {
514 synchronized (myDataLock
) {
515 return myWorker
.getCopyByName(name
);
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();
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
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() {
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
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();
583 public LocalChangeList
getDefaultChangeList() {
584 synchronized (myDataLock
) {
585 return myWorker
.getDefaultListCopy();
590 public boolean isDefaultChangeList(ChangeList list
) {
591 return list
instanceof LocalChangeList
&& myWorker
.isDefaultList((LocalChangeList
)list
);
595 public Collection
<LocalChangeList
> getInvolvedListsFilterChanges(final Collection
<Change
> changes
, final List
<Change
> validChanges
) {
596 synchronized (myDataLock
) {
597 return myWorker
.getInvolvedListsFilterChanges(changes
, validChanges
);
602 public LocalChangeList
getChangeList(Change change
) {
603 synchronized (myDataLock
) {
604 return myWorker
.listForChange(change
);
609 public String
getChangeListNameIfOnlyOne(final Change
[] changes
) {
610 synchronized (myDataLock
) {
611 return myWorker
.listNameIfOnlyOne(changes
);
617 * better use normal comparison, with equals
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
) {
635 public boolean isInUpdate() {
636 synchronized (myDataLock
) {
637 return myModifier
.isInsideUpdate() || myShowLocalChangesInvalidated
;
642 public Change
getChange(@NotNull VirtualFile file
) {
643 synchronized (myDataLock
) {
644 final LocalChangeList list
= myWorker
.getListCopy(file
);
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
;
665 public LocalChangeList
getChangeList(@NotNull VirtualFile file
) {
666 synchronized (myDataLock
) {
667 return myWorker
.getListCopy(file
);
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
);
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) {
693 if (myWorker
.isSwitched(file
)) return FileStatus
.SWITCHED
;
694 return FileStatus
.NOT_CHANGED
;
699 public Collection
<Change
> getChangesIn(VirtualFile dir
) {
700 return getChangesIn(new FilePathImpl(dir
));
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() {
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() {
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);
774 myChangesViewManager
.scheduleRefresh();
778 public Project
getProject() {
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
);
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;
846 readonlyStatusHandler
.ensureFilesWritable(collectFiles(paths
));
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
);
904 public String
getSwitchedBranch(final VirtualFile file
) {
905 synchronized (myDataLock
) {
906 return myWorker
.getBranchForFile(file
);
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();
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();
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();
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
);
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() {
988 myRevisionsCache
.plus(getCorrectedPair(stringAbstractVcsPair
));
993 public void minus(final Pair
<String
, AbstractVcs
> stringAbstractVcsPair
) {
994 myAlarm
.addRequest(new Runnable() {
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
;
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();