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
.notification
.Notification
;
23 import com
.intellij
.notification
.NotificationDisplayType
;
24 import com
.intellij
.notification
.NotificationType
;
25 import com
.intellij
.notification
.Notifications
;
26 import com
.intellij
.openapi
.application
.ApplicationManager
;
27 import com
.intellij
.openapi
.application
.ModalityState
;
28 import com
.intellij
.openapi
.components
.ProjectComponent
;
29 import com
.intellij
.openapi
.diagnostic
.Logger
;
30 import com
.intellij
.openapi
.progress
.EmptyProgressIndicator
;
31 import com
.intellij
.openapi
.progress
.ProcessCanceledException
;
32 import com
.intellij
.openapi
.progress
.ProgressIndicator
;
33 import com
.intellij
.openapi
.project
.DumbAwareRunnable
;
34 import com
.intellij
.openapi
.project
.Project
;
35 import com
.intellij
.openapi
.ui
.Messages
;
36 import com
.intellij
.openapi
.util
.*;
37 import com
.intellij
.openapi
.util
.io
.FileUtil
;
38 import com
.intellij
.openapi
.vcs
.*;
39 import com
.intellij
.openapi
.vcs
.changes
.conflicts
.ChangelistConflictTracker
;
40 import com
.intellij
.openapi
.vcs
.changes
.ui
.CommitHelper
;
41 import com
.intellij
.openapi
.vcs
.checkin
.CheckinEnvironment
;
42 import com
.intellij
.openapi
.vcs
.checkin
.CheckinHandler
;
43 import com
.intellij
.openapi
.vcs
.impl
.ProjectLevelVcsManagerImpl
;
44 import com
.intellij
.openapi
.vcs
.impl
.VcsInitObject
;
45 import com
.intellij
.openapi
.vcs
.readOnlyHandler
.ReadonlyStatusHandlerImpl
;
46 import com
.intellij
.openapi
.vfs
.VfsUtil
;
47 import com
.intellij
.openapi
.vfs
.VirtualFile
;
48 import com
.intellij
.ui
.EditorNotifications
;
49 import com
.intellij
.util
.ConcurrencyUtil
;
50 import com
.intellij
.util
.Consumer
;
51 import com
.intellij
.util
.EventDispatcher
;
52 import com
.intellij
.util
.containers
.MultiMap
;
53 import com
.intellij
.util
.messages
.Topic
;
54 import org
.jdom
.Element
;
55 import org
.jetbrains
.annotations
.NonNls
;
56 import org
.jetbrains
.annotations
.NotNull
;
57 import org
.jetbrains
.annotations
.Nullable
;
62 import java
.util
.concurrent
.ExecutorService
;
63 import java
.util
.concurrent
.ScheduledExecutorService
;
68 public class ChangeListManagerImpl
extends ChangeListManagerEx
implements ProjectComponent
, ChangeListOwner
, JDOMExternalizable
{
69 private static final Logger LOG
= Logger
.getInstance("#com.intellij.openapi.vcs.changes.ChangeListManagerImpl");
71 private final Project myProject
;
72 private final ChangesViewManager myChangesViewManager
;
73 private final FileStatusManager myFileStatusManager
;
74 private final UpdateRequestsQueue myUpdater
;
76 @SuppressWarnings({"FieldAccessedSynchronizedAndUnsynchronized"})
77 private static final ScheduledExecutorService ourUpdateAlarm
= ConcurrencyUtil
.newSingleScheduledThreadExecutor("Change List Updater", Thread
.MIN_PRIORITY
+ 1);
79 private final Modifier myModifier
;
81 private FileHolderComposite myComposite
;
83 private final ChangeListWorker myWorker
;
84 private VcsException myUpdateException
= null;
86 @SuppressWarnings({"FieldAccessedSynchronizedAndUnsynchronized"})
87 private final EventDispatcher
<ChangeListListener
> myListeners
= EventDispatcher
.create(ChangeListListener
.class);
89 private final Object myDataLock
= new Object();
91 private final List
<CommitExecutor
> myExecutors
= new ArrayList
<CommitExecutor
>();
93 private final IgnoredFilesComponent myIgnoredIdeaLevel
;
94 private ProgressIndicator myUpdateChangesProgressIndicator
;
96 public static final Key
<Object
> DOCUMENT_BEING_COMMITTED_KEY
= new Key
<Object
>("DOCUMENT_BEING_COMMITTED");
98 public static final Topic
<LocalChangeListsLoadedListener
> LISTS_LOADED
= new Topic
<LocalChangeListsLoadedListener
>(
99 "LOCAL_CHANGE_LISTS_LOADED", LocalChangeListsLoadedListener
.class);
101 private boolean myShowLocalChangesInvalidated
;
103 private final DelayedNotificator myDelayedNotificator
;
105 private final VcsListener myVcsListener
= new VcsListener() {
106 public void directoryMappingChanged() {
107 VcsDirtyScopeManager
.getInstanceChecked(myProject
).markEverythingDirty();
110 private final ChangelistConflictTracker myConflictTracker
;
112 public static ChangeListManagerImpl
getInstanceImpl(final Project project
) {
113 return (ChangeListManagerImpl
) project
.getComponent(ChangeListManager
.class);
116 public ChangeListManagerImpl(final Project project
) {
118 myChangesViewManager
= ChangesViewManager
.getInstance(myProject
);
119 myFileStatusManager
= FileStatusManager
.getInstance(myProject
);
120 myComposite
= new FileHolderComposite(project
);
121 myIgnoredIdeaLevel
= new IgnoredFilesComponent(myProject
);
122 myUpdater
= new UpdateRequestsQueue(myProject
, ourUpdateAlarm
, new ActualUpdater());
124 myWorker
= new ChangeListWorker(myProject
, new MyChangesDeltaForwarder(myProject
, ourUpdateAlarm
));
125 myDelayedNotificator
= new DelayedNotificator(myListeners
, ourUpdateAlarm
);
126 myModifier
= new Modifier(myWorker
, myDelayedNotificator
);
128 myConflictTracker
= new ChangelistConflictTracker(project
, this, myFileStatusManager
, EditorNotifications
.getInstance(project
));
131 public void projectOpened() {
132 initializeForNewProject();
134 if (ApplicationManager
.getApplication().isUnitTestMode()) {
135 myWorker
.initialized();
136 myUpdater
.initialized();
137 ProjectLevelVcsManager
.getInstance(myProject
).addVcsListener(myVcsListener
);
140 ((ProjectLevelVcsManagerImpl
) ProjectLevelVcsManager
.getInstance(myProject
)).addInitializationRequest(
141 VcsInitObject
.CHANGE_LIST_MANAGER
, new DumbAwareRunnable() {
143 myWorker
.initialized();
144 myUpdater
.initialized();
145 broadcastStateAfterLoad();
146 ProjectLevelVcsManager
.getInstance(myProject
).addVcsListener(myVcsListener
);
151 myConflictTracker
.startTracking();
154 private void broadcastStateAfterLoad() {
155 final List
<LocalChangeList
> listCopy
;
156 synchronized (myDataLock
) {
157 listCopy
= getChangeListsCopy();
159 if (! listCopy
.isEmpty()) {
160 myProject
.getMessageBus().syncPublisher(LISTS_LOADED
).processLoadedLists(listCopy
);
164 private void initializeForNewProject() {
165 synchronized (myDataLock
) {
166 if (myWorker
.isEmpty()) {
167 final LocalChangeList list
= myWorker
.addChangeList(VcsBundle
.message("changes.default.changlist.name"), null);
168 setDefaultChangeList(list
);
170 if (myIgnoredIdeaLevel
.isEmpty()) {
171 final String name
= myProject
.getName();
172 myIgnoredIdeaLevel
.add(IgnoredBeanFactory
.ignoreFile(name
+ WorkspaceFileType
.DOT_DEFAULT_EXTENSION
, myProject
));
173 myIgnoredIdeaLevel
.add(IgnoredBeanFactory
.ignoreFile(Project
.DIRECTORY_STORE_FOLDER
+ "/workspace.xml", myProject
));
179 public void projectClosed() {
180 ProjectLevelVcsManager
.getInstance(myProject
).removeVcsListener(myVcsListener
);
182 synchronized (myDataLock
) {
183 if (myUpdateChangesProgressIndicator
!= null) {
184 myUpdateChangesProgressIndicator
.cancel();
189 myConflictTracker
.stopTracking();
193 public String
getComponentName() {
194 return "ChangeListManager";
197 public void initComponent() {
200 public void disposeComponent() {
204 * update itself might produce actions done on AWT thread (invoked-after),
205 * so waiting for its completion on AWT thread is not good
207 * runnable is invoked on AWT thread
209 public void invokeAfterUpdate(final Runnable afterUpdate
, final InvokeAfterUpdateMode mode
, final String title
, final ModalityState state
) {
210 myUpdater
.invokeAfterUpdate(afterUpdate
, mode
, title
, null, state
);
213 public void invokeAfterUpdate(final Runnable afterUpdate
, final InvokeAfterUpdateMode mode
, final String title
,
214 final Consumer
<VcsDirtyScopeManager
> dirtyScopeManagerFiller
, final ModalityState state
) {
215 myUpdater
.invokeAfterUpdate(afterUpdate
, mode
, title
, dirtyScopeManagerFiller
, state
);
218 static class DisposedException
extends RuntimeException
{}
220 public void scheduleUpdate() {
221 myUpdater
.schedule(true);
224 public void scheduleUpdate(boolean updateUnversionedFiles
) {
225 myUpdater
.schedule(updateUnversionedFiles
);
228 private class ActualUpdater
implements LocalChangesUpdater
{
229 public void execute(boolean updateUnversioned
, AtomicSectionsAware atomicSectionsAware
) {
230 updateImmediately(updateUnversioned
, atomicSectionsAware
);
234 private void updateImmediately(final boolean updateUnversionedFiles
, final AtomicSectionsAware atomicSectionsAware
) {
235 FileHolderComposite composite
;
236 ChangeListWorker changeListWorker
;
238 final VcsDirtyScopeManagerImpl dirtyScopeManager
;
240 dirtyScopeManager
= ((VcsDirtyScopeManagerImpl
) VcsDirtyScopeManager
.getInstanceChecked(myProject
));
242 catch(ProcessCanceledException ex
) {
245 catch(Exception ex
) {
249 final VcsInvalidated invalidated
= dirtyScopeManager
.retrieveScopes();
250 if (invalidated
== null || invalidated
.isEmpty()) {
251 // a hack here; but otherwise everything here should be refactored ;)
252 if (invalidated
.isEmpty() && invalidated
.isEverythingDirty()) {
253 VcsDirtyScopeManager
.getInstance(myProject
).markEverythingDirty();
257 final boolean wasEverythingDirty
= invalidated
.isEverythingDirty();
258 final List
<VcsDirtyScope
> scopes
= invalidated
.getScopes();
263 // copy existsing data to objects that would be updated.
264 // mark for "modifier" that update started (it would create duplicates of modification commands done by user during update;
265 // after update of copies of objects is complete, it would apply the same modifications to copies.)
266 synchronized (myDataLock
) {
267 changeListWorker
= myWorker
.copy();
268 composite
= updateUnversionedFiles ?
(FileHolderComposite
) myComposite
.copy() : myComposite
;
269 myModifier
.enterUpdate();
270 if (wasEverythingDirty
) {
271 myUpdateException
= null;
273 if (updateUnversionedFiles
&& wasEverythingDirty
) {
274 composite
.cleanAll();
277 if (wasEverythingDirty
) {
278 changeListWorker
.notifyStartProcessingChanges(null);
280 myChangesViewManager
.scheduleRefresh();
282 final ChangeListManagerGate gate
= changeListWorker
.createSelfGate();
284 // do actual requests about file statuses
285 final UpdatingChangeListBuilder builder
= new UpdatingChangeListBuilder(changeListWorker
, composite
, new Getter
<Boolean
>() {
286 public Boolean
get() {
287 return myUpdater
.isStopped();
289 }, updateUnversionedFiles
, myIgnoredIdeaLevel
, gate
);
291 myUpdateChangesProgressIndicator
= new EmptyProgressIndicator() {
293 public boolean isCanceled() {
294 return myUpdater
.isStopped() || atomicSectionsAware
.shouldExitAsap();
297 public void checkCanceled() {
299 atomicSectionsAware
.checkShouldExit();
302 for (final VcsDirtyScope scope
: scopes
) {
303 atomicSectionsAware
.checkShouldExit();
305 final AbstractVcs vcs
= scope
.getVcs();
306 if (vcs
== null) continue;
307 final VcsAppendableDirtyScope adjustedScope
= vcs
.adjustDirtyScope((VcsAppendableDirtyScope
) scope
);
309 myChangesViewManager
.updateProgressText(VcsBundle
.message("changes.update.progress.message", vcs
.getDisplayName()), false);
310 if (! wasEverythingDirty
) {
311 changeListWorker
.notifyStartProcessingChanges(adjustedScope
);
313 if (updateUnversionedFiles
&& !wasEverythingDirty
) {
314 composite
.cleanScope(adjustedScope
);
318 actualUpdate(wasEverythingDirty
, composite
, builder
, adjustedScope
, vcs
, changeListWorker
, gate
);
320 catch (Throwable t
) {
322 if (t
instanceof Error
) {
324 } else if (t
instanceof RuntimeException
) {
325 throw (RuntimeException
) t
;
327 throw new RuntimeException(t
);
330 if (myUpdateException
!= null) break;
333 final boolean takeChanges
= (myUpdateException
== null);
335 synchronized (myDataLock
) {
336 // do same modifications to change lists as was done during update + do delayed notifications
337 if (wasEverythingDirty
) {
338 changeListWorker
.notifyDoneProcessingChanges(myDelayedNotificator
.getProxyDispatcher());
340 myModifier
.exitUpdate();
341 // should be applied for notifications to be delivered (they were delayed)
342 myModifier
.apply(changeListWorker
);
343 myModifier
.clearQueue();
344 // update member from copy
346 myWorker
.takeData(changeListWorker
);
349 if (takeChanges
&& updateUnversionedFiles
) {
350 boolean statusChanged
= !myComposite
.equals(composite
);
351 myComposite
= composite
;
353 myDelayedNotificator
.getProxyDispatcher().unchangedFileStatusChanged();
358 updateIgnoredFiles(false);
360 myShowLocalChangesInvalidated
= false;
362 myChangesViewManager
.scheduleRefresh();
364 catch (DisposedException e
) {
365 // OK, we're finishing all the stuff now.
367 catch(ProcessCanceledException e
) {
368 // OK, we're finishing all the stuff now.
370 catch(Exception ex
) {
373 catch(AssertionError ex
) {
377 dirtyScopeManager
.changesProcessed();
379 synchronized (myDataLock
) {
380 myDelayedNotificator
.getProxyDispatcher().changeListUpdateDone();
381 myChangesViewManager
.scheduleRefresh();
386 private void actualUpdate(final boolean wasEverythingDirty
, final FileHolderComposite composite
, final UpdatingChangeListBuilder builder
,
387 final VcsDirtyScope scope
, final AbstractVcs vcs
, final ChangeListWorker changeListWorker
,
388 final ChangeListManagerGate gate
) {
390 final ChangeProvider changeProvider
= vcs
.getChangeProvider();
391 if (changeProvider
!= null) {
392 final FoldersCutDownWorker foldersCutDownWorker
= new FoldersCutDownWorker();
394 builder
.setCurrent(scope
, foldersCutDownWorker
);
395 changeProvider
.getChanges(scope
, builder
, myUpdateChangesProgressIndicator
, gate
);
397 catch (VcsException e
) {
399 if (myUpdateException
== null) {
400 myUpdateException
= e
;
403 composite
.getIgnoredFileHolder().calculateChildren();
407 if ((! myUpdater
.isStopped()) && !wasEverythingDirty
) {
408 changeListWorker
.notifyDoneProcessingChanges(myDelayedNotificator
.getProxyDispatcher());
413 private void checkIfDisposed() {
414 if (myUpdater
.isStopped()) throw new DisposedException();
417 static boolean isUnder(final Change change
, final VcsDirtyScope scope
) {
418 final ContentRevision before
= change
.getBeforeRevision();
419 final ContentRevision after
= change
.getAfterRevision();
420 return before
!= null && scope
.belongsTo(before
.getFile()) || after
!= null && scope
.belongsTo(after
.getFile());
423 public List
<LocalChangeList
> getChangeListsCopy() {
424 synchronized (myDataLock
) {
425 return myWorker
.getListsCopy();
431 * this method made equivalent to {@link #getChangeListsCopy()} so to don't be confused by method name,
432 * better use {@link #getChangeListsCopy()}
435 public List
<LocalChangeList
> getChangeLists() {
436 synchronized (myDataLock
) {
437 return getChangeListsCopy();
441 public List
<File
> getAffectedPaths() {
442 synchronized (myDataLock
) {
443 return myWorker
.getAffectedPaths();
448 public List
<VirtualFile
> getAffectedFiles() {
449 synchronized (myDataLock
) {
450 return myWorker
.getAffectedFiles();
454 List
<VirtualFile
> getUnversionedFiles() {
455 return new ArrayList
<VirtualFile
>(myComposite
.getVFHolder(FileHolder
.HolderType
.UNVERSIONED
).getFiles());
458 List
<VirtualFile
> getModifiedWithoutEditing() {
459 return new ArrayList
<VirtualFile
>(myComposite
.getVFHolder(FileHolder
.HolderType
.MODIFIED_WITHOUT_EDITING
).getFiles());
463 * @return only roots for ignored folders, and ignored files
465 List
<VirtualFile
> getIgnoredFiles() {
466 return new ArrayList
<VirtualFile
>(myComposite
.getIgnoredFileHolder().getBranchToFileMap().values());
469 public List
<VirtualFile
> getLockedFolders() {
470 return new ArrayList
<VirtualFile
>(myComposite
.getVFHolder(FileHolder
.HolderType
.LOCKED
).getFiles());
473 Map
<VirtualFile
, LogicalLock
> getLogicallyLockedFolders() {
474 return new HashMap
<VirtualFile
, LogicalLock
>(((LogicallyLockedHolder
) myComposite
.get(FileHolder
.HolderType
.LOGICALLY_LOCKED
)).getMap());
477 public boolean isLogicallyLocked(final VirtualFile file
) {
478 return ((LogicallyLockedHolder
) myComposite
.get(FileHolder
.HolderType
.LOGICALLY_LOCKED
)).getMap().containsKey(file
);
481 public boolean isContainedInLocallyDeleted(final FilePath filePath
) {
482 synchronized (myDataLock
) {
483 return myWorker
.isContainedInLocallyDeleted(filePath
);
487 public List
<LocallyDeletedChange
> getDeletedFiles() {
488 synchronized (myDataLock
) {
489 return myWorker
.getLocallyDeleted().getFiles();
493 MultiMap
<String
, VirtualFile
> getSwitchedFilesMap() {
494 synchronized (myDataLock
) {
495 return myWorker
.getSwitchedHolder().getBranchToFileMap();
500 Map
<VirtualFile
, String
> getSwitchedRoots() {
501 synchronized (myDataLock
) {
502 return ((SwitchedFileHolder
) myComposite
.get(FileHolder
.HolderType
.ROOT_SWITCH
)).getFilesMapCopy();
506 public VcsException
getUpdateException() {
507 return myUpdateException
;
510 public boolean isFileAffected(final VirtualFile file
) {
511 synchronized (myDataLock
) {
512 return myWorker
.getStatus(file
) != null;
517 public LocalChangeList
findChangeList(final String name
) {
518 synchronized (myDataLock
) {
519 return myWorker
.getCopyByName(name
);
524 public LocalChangeList
getChangeList(String id
) {
525 synchronized (myDataLock
) {
526 return myWorker
.getChangeList(id
);
530 public LocalChangeList
addChangeList(@NotNull String name
, final String comment
) {
531 synchronized (myDataLock
) {
532 final LocalChangeList changeList
= myModifier
.addChangeList(name
, comment
);
533 myChangesViewManager
.scheduleRefresh();
538 public void removeChangeList(final String name
) {
539 synchronized (myDataLock
) {
540 myModifier
.removeChangeList(name
);
541 myChangesViewManager
.scheduleRefresh();
545 public void removeChangeList(LocalChangeList list
) {
546 removeChangeList(list
.getName());
550 * does no modification to change lists, only notification is sent
553 public Runnable
prepareForChangeDeletion(final Collection
<Change
> changes
) {
554 final Map
<String
, LocalChangeList
> lists
= new HashMap
<String
, LocalChangeList
>();
555 final Map
<String
, List
<Change
>> map
;
556 synchronized (myDataLock
) {
557 map
= myWorker
.listsForChanges(changes
, lists
);
559 return new Runnable() {
561 final ChangeListListener multicaster
= myDelayedNotificator
.getProxyDispatcher();
562 synchronized (myDataLock
) {
563 for (Map
.Entry
<String
, List
<Change
>> entry
: map
.entrySet()) {
564 final List
<Change
> changes
= entry
.getValue();
565 for (Iterator
<Change
> iterator
= changes
.iterator(); iterator
.hasNext();) {
566 final Change change
= iterator
.next();
567 if (getChangeList(change
) != null) {
568 // was not actually rolled back
572 multicaster
.changesRemoved(changes
, lists
.get(entry
.getKey()));
579 public void setDefaultChangeList(@NotNull LocalChangeList list
) {
580 synchronized (myDataLock
) {
581 myModifier
.setDefault(list
.getName());
582 myChangesViewManager
.scheduleRefresh();
587 public LocalChangeList
getDefaultChangeList() {
588 synchronized (myDataLock
) {
589 return myWorker
.getDefaultListCopy();
594 public boolean isDefaultChangeList(ChangeList list
) {
595 return list
instanceof LocalChangeList
&& myWorker
.isDefaultList((LocalChangeList
)list
);
599 public Collection
<LocalChangeList
> getInvolvedListsFilterChanges(final Collection
<Change
> changes
, final List
<Change
> validChanges
) {
600 synchronized (myDataLock
) {
601 return myWorker
.getInvolvedListsFilterChanges(changes
, validChanges
);
606 public LocalChangeList
getChangeList(Change change
) {
607 synchronized (myDataLock
) {
608 return myWorker
.listForChange(change
);
613 public String
getChangeListNameIfOnlyOne(final Change
[] changes
) {
614 synchronized (myDataLock
) {
615 return myWorker
.listNameIfOnlyOne(changes
);
621 * better use normal comparison, with equals
624 public LocalChangeList
getIdentityChangeList(Change change
) {
625 synchronized (myDataLock
) {
626 final List
<LocalChangeList
> lists
= myWorker
.getListsCopy();
627 for (LocalChangeList list
: lists
) {
628 for(Change oldChange
: list
.getChanges()) {
629 if (oldChange
== change
) {
639 public boolean isInUpdate() {
640 synchronized (myDataLock
) {
641 return myModifier
.isInsideUpdate() || myShowLocalChangesInvalidated
;
646 public Change
getChange(@NotNull VirtualFile file
) {
647 synchronized (myDataLock
) {
648 final LocalChangeList list
= myWorker
.getListCopy(file
);
650 for (Change change
: list
.getChanges()) {
651 final ContentRevision afterRevision
= change
.getAfterRevision();
652 if (afterRevision
!= null) {
653 String revisionPath
= FileUtil
.toSystemIndependentName(afterRevision
.getFile().getIOFile().getPath());
654 if (FileUtil
.pathsEqual(revisionPath
, file
.getPath())) return change
;
656 final ContentRevision beforeRevision
= change
.getBeforeRevision();
657 if (beforeRevision
!= null) {
658 String revisionPath
= FileUtil
.toSystemIndependentName(beforeRevision
.getFile().getIOFile().getPath());
659 if (FileUtil
.pathsEqual(revisionPath
, file
.getPath())) return change
;
669 public LocalChangeList
getChangeList(@NotNull VirtualFile file
) {
670 synchronized (myDataLock
) {
671 return myWorker
.getListCopy(file
);
676 public Change
getChange(final FilePath file
) {
677 synchronized (myDataLock
) {
678 return myWorker
.getChangeForPath(file
);
682 public boolean isUnversioned(VirtualFile file
) {
683 return myComposite
.getVFHolder(FileHolder
.HolderType
.UNVERSIONED
).containsFile(file
);
687 public FileStatus
getStatus(VirtualFile file
) {
688 synchronized (myDataLock
) {
689 if (myComposite
.getVFHolder(FileHolder
.HolderType
.UNVERSIONED
).containsFile(file
)) return FileStatus
.UNKNOWN
;
690 if (myComposite
.getVFHolder(FileHolder
.HolderType
.MODIFIED_WITHOUT_EDITING
).containsFile(file
)) return FileStatus
.HIJACKED
;
691 if (myComposite
.getIgnoredFileHolder().containsFile(file
)) return FileStatus
.IGNORED
;
693 final FileStatus status
= myWorker
.getStatus(file
);
694 if (status
!= null) {
697 if (myWorker
.isSwitched(file
)) return FileStatus
.SWITCHED
;
698 return FileStatus
.NOT_CHANGED
;
703 public Collection
<Change
> getChangesIn(VirtualFile dir
) {
704 return getChangesIn(new FilePathImpl(dir
));
708 public Collection
<Change
> getChangesIn(final FilePath dirPath
) {
709 synchronized (myDataLock
) {
710 return myWorker
.getChangesIn(dirPath
);
714 public void moveChangesTo(LocalChangeList list
, final Change
[] changes
) {
715 synchronized (myDataLock
) {
716 myModifier
.moveChangesTo(list
.getName(), changes
);
718 SwingUtilities
.invokeLater(new Runnable() {
720 myChangesViewManager
.refreshView();
725 public void addUnversionedFiles(final LocalChangeList list
, @NotNull final List
<VirtualFile
> files
) {
726 final List
<VcsException
> exceptions
= new ArrayList
<VcsException
>();
727 ChangesUtil
.processVirtualFilesByVcs(myProject
, files
, new ChangesUtil
.PerVcsProcessor
<VirtualFile
>() {
728 public void process(final AbstractVcs vcs
, final List
<VirtualFile
> items
) {
729 final CheckinEnvironment environment
= vcs
.getCheckinEnvironment();
730 if (environment
!= null) {
731 final List
<VcsException
> result
= environment
.scheduleUnversionedFilesForAddition(items
);
732 if (result
!= null) {
733 exceptions
.addAll(result
);
739 if (exceptions
.size() > 0) {
740 StringBuilder message
= new StringBuilder(VcsBundle
.message("error.adding.files.prompt"));
741 for(VcsException ex
: exceptions
) {
742 message
.append("\n").append(ex
.getMessage());
744 Messages
.showErrorDialog(myProject
, message
.toString(), VcsBundle
.message("error.adding.files.title"));
747 for (VirtualFile file
: files
) {
748 myFileStatusManager
.fileStatusChanged(file
);
750 VcsDirtyScopeManager
.getInstance(myProject
).filesDirty(files
, null);
752 if (!list
.isDefault()) {
753 // find the changes for the added files and move them to the necessary changelist
754 invokeAfterUpdate(new Runnable() {
756 synchronized (myDataLock
) {
757 List
<Change
> changesToMove
= new ArrayList
<Change
>();
758 final LocalChangeList defaultList
= getDefaultChangeList();
759 for(Change change
: defaultList
.getChanges()) {
760 final ContentRevision afterRevision
= change
.getAfterRevision();
761 if (afterRevision
!= null) {
762 VirtualFile vFile
= afterRevision
.getFile().getVirtualFile();
763 if (files
.contains(vFile
)) {
764 changesToMove
.add(change
);
769 if (changesToMove
.size() > 0) {
770 moveChangesTo(list
, changesToMove
.toArray(new Change
[changesToMove
.size()]));
774 myChangesViewManager
.scheduleRefresh();
776 }, InvokeAfterUpdateMode
.BACKGROUND_NOT_CANCELLABLE
, VcsBundle
.message("change.lists.manager.add.unversioned"), null);
778 myChangesViewManager
.scheduleRefresh();
782 public Project
getProject() {
786 public void addChangeListListener(ChangeListListener listener
) {
787 myListeners
.addListener(listener
);
791 public void removeChangeListListener(ChangeListListener listener
) {
792 myListeners
.removeListener(listener
);
795 public void registerCommitExecutor(CommitExecutor executor
) {
796 myExecutors
.add(executor
);
799 public void commitChanges(LocalChangeList changeList
, List
<Change
> changes
) {
800 doCommit(changeList
, changes
, false);
803 private boolean doCommit(final LocalChangeList changeList
, final List
<Change
> changes
, final boolean synchronously
) {
804 return new CommitHelper(myProject
, changeList
, changes
, changeList
.getName(),
805 changeList
.getComment(), new ArrayList
<CheckinHandler
>(), false, synchronously
, null).doCommit();
808 public void commitChangesSynchronously(LocalChangeList changeList
, List
<Change
> changes
) {
809 doCommit(changeList
, changes
, true);
812 public boolean commitChangesSynchronouslyWithResult(final LocalChangeList changeList
, final List
<Change
> changes
) {
813 return doCommit(changeList
, changes
, true);
816 @SuppressWarnings({"unchecked"})
817 public void readExternal(Element element
) throws InvalidDataException
{
818 if (! myProject
.isDefault()) {
819 synchronized (myDataLock
) {
820 myIgnoredIdeaLevel
.clear();
821 new ChangeListManagerSerialization(myIgnoredIdeaLevel
, myWorker
).readExternal(element
);
822 if ((! myWorker
.isEmpty()) && getDefaultChangeList() == null) {
823 setDefaultChangeList(myWorker
.getListsCopy().get(0));
826 myConflictTracker
.loadState(element
);
830 public void writeExternal(Element element
) throws WriteExternalException
{
831 if (! myProject
.isDefault()) {
832 final IgnoredFilesComponent ignoredFilesComponent
;
833 final ChangeListWorker worker
;
834 synchronized (myDataLock
) {
835 ignoredFilesComponent
= new IgnoredFilesComponent(myProject
);
836 ignoredFilesComponent
.add(myIgnoredIdeaLevel
.getFilesToIgnore());
837 worker
= myWorker
.copy();
839 new ChangeListManagerSerialization(ignoredFilesComponent
, worker
).writeExternal(element
);
840 myConflictTracker
.saveState(element
);
845 public void reopenFiles(List
<FilePath
> paths
) {
846 final ReadonlyStatusHandlerImpl readonlyStatusHandler
= (ReadonlyStatusHandlerImpl
)ReadonlyStatusHandlerImpl
.getInstance(myProject
);
847 final boolean savedOption
= readonlyStatusHandler
.getState().SHOW_DIALOG
;
848 readonlyStatusHandler
.getState().SHOW_DIALOG
= false;
850 readonlyStatusHandler
.ensureFilesWritable(collectFiles(paths
));
853 readonlyStatusHandler
.getState().SHOW_DIALOG
= savedOption
;
857 public List
<CommitExecutor
> getRegisteredExecutors() {
858 return Collections
.unmodifiableList(myExecutors
);
861 public void addFilesToIgnore(final IgnoredFileBean
... filesToIgnore
) {
862 myIgnoredIdeaLevel
.add(filesToIgnore
);
863 updateIgnoredFiles(true);
866 public void setFilesToIgnore(final IgnoredFileBean
... filesToIgnore
) {
867 myIgnoredIdeaLevel
.set(filesToIgnore
);
868 updateIgnoredFiles(true);
871 private void updateIgnoredFiles(final boolean checkIgnored
) {
872 synchronized (myDataLock
) {
873 List
<VirtualFile
> unversionedFiles
= myComposite
.getVFHolder(FileHolder
.HolderType
.UNVERSIONED
).getFiles();
874 //List<VirtualFile> ignoredFiles = myComposite.getVFHolder(FileHolder.HolderType.IGNORED).getFiles();
875 boolean somethingChanged
= false;
876 for(VirtualFile file
: unversionedFiles
) {
877 if (isIgnoredFile(file
)) {
878 somethingChanged
= true;
879 myComposite
.getVFHolder(FileHolder
.HolderType
.UNVERSIONED
).removeFile(file
);
880 myComposite
.getIgnoredFileHolder().addFile(file
, "", false);
883 /*if (checkIgnored) {
884 for(VirtualFile file: ignoredFiles) {
885 if (!isIgnoredFile(file)) {
886 somethingChanged = true;
887 // the file may have been reported as ignored by the VCS, so we can't directly move it to unversioned files
888 VcsDirtyScopeManager.getInstance(myProject).fileDirty(file);
892 if (somethingChanged
) {
893 myFileStatusManager
.fileStatusesChanged();
894 myChangesViewManager
.scheduleRefresh();
899 public IgnoredFileBean
[] getFilesToIgnore() {
900 return myIgnoredIdeaLevel
.getFilesToIgnore();
903 public boolean isIgnoredFile(@NotNull VirtualFile file
) {
904 return myIgnoredIdeaLevel
.isIgnoredFile(file
);
908 public String
getSwitchedBranch(final VirtualFile file
) {
909 synchronized (myDataLock
) {
910 return myWorker
.getBranchForFile(file
);
915 public String
getDefaultListName() {
916 synchronized (myDataLock
) {
917 return myWorker
.getDefaultListName();
921 private static VirtualFile
[] collectFiles(final List
<FilePath
> paths
) {
922 final ArrayList
<VirtualFile
> result
= new ArrayList
<VirtualFile
>();
923 for (FilePath path
: paths
) {
924 if (path
.getVirtualFile() != null) {
925 result
.add(path
.getVirtualFile());
929 return VfsUtil
.toVirtualFileArray(result
);
932 public boolean setReadOnly(final String name
, final boolean value
) {
933 synchronized (myDataLock
) {
934 final boolean result
= myModifier
.setReadOnly(name
, value
);
935 myChangesViewManager
.scheduleRefresh();
940 public boolean editName(@NotNull final String fromName
, @NotNull final String toName
) {
941 synchronized (myDataLock
) {
942 final boolean result
= myModifier
.editName(fromName
, toName
);
943 myChangesViewManager
.scheduleRefresh();
948 public String
editComment(@NotNull final String fromName
, final String newComment
) {
949 synchronized (myDataLock
) {
950 final String oldComment
= myModifier
.editComment(fromName
, newComment
);
951 myChangesViewManager
.scheduleRefresh();
957 * Can be called only from not AWT thread; to do smthg after ChangeListManager refresh, call invokeAfterUpdate
959 public boolean ensureUpToDate(final boolean canBeCanceled
) {
960 final EnsureUpToDateFromNonAWTThread worker
= new EnsureUpToDateFromNonAWTThread(myProject
);
962 return worker
.isDone();
965 // only a light attempt to show that some dirty scope request is asynchronously coming
966 // for users to see changes are not valid
967 // (commit -> asynch synch VFS -> asynch vcs dirty scope)
968 public void showLocalChangesInvalidated() {
969 synchronized (myDataLock
) {
970 myShowLocalChangesInvalidated
= true;
974 public ChangelistConflictTracker
getConflictTracker() {
975 return myConflictTracker
;
978 private static class MyChangesDeltaForwarder
implements PlusMinus
<Pair
<String
, AbstractVcs
>> {
979 private SlowlyClosingAlarm myAlarm
;
980 private RemoteRevisionsCache myRevisionsCache
;
981 private final ProjectLevelVcsManager myVcsManager
;
983 public MyChangesDeltaForwarder(final Project project
, final ExecutorService service
) {
984 myAlarm
= ControlledAlarmFactory
.createOnSharedThread(project
, "changes delta consumer forwarder", service
);
985 myRevisionsCache
= RemoteRevisionsCache
.getInstance(project
);
986 myVcsManager
= ProjectLevelVcsManager
.getInstance(project
);
989 public void plus(final Pair
<String
, AbstractVcs
> stringAbstractVcsPair
) {
990 myAlarm
.addRequest(new Runnable() {
992 myRevisionsCache
.plus(getCorrectedPair(stringAbstractVcsPair
));
997 public void minus(final Pair
<String
, AbstractVcs
> stringAbstractVcsPair
) {
998 myAlarm
.addRequest(new Runnable() {
1000 myRevisionsCache
.minus(getCorrectedPair(stringAbstractVcsPair
));
1005 private Pair
<String
, AbstractVcs
> getCorrectedPair(final Pair
<String
, AbstractVcs
> stringAbstractVcsPair
) {
1006 Pair
<String
, AbstractVcs
> correctedPair
= stringAbstractVcsPair
;
1007 if (stringAbstractVcsPair
.getSecond() == null) {
1008 final String path
= stringAbstractVcsPair
.getFirst();
1009 correctedPair
= new Pair
<String
, AbstractVcs
>(path
, myVcsManager
.findVcsByName(findVcs(path
).getName()));
1011 return correctedPair
;
1015 private VcsKey
findVcs(final String path
) {
1016 // does not matter directory or not
1017 final AbstractVcs vcs
= myVcsManager
.getVcsFor(FilePathImpl
.create(new File(path
), false));
1018 return vcs
== null ?
null : vcs
.getKeyInstanceMethod();