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
.DumbService
;
31 import com
.intellij
.openapi
.project
.Project
;
32 import com
.intellij
.openapi
.startup
.StartupManager
;
33 import com
.intellij
.openapi
.ui
.Messages
;
34 import com
.intellij
.openapi
.util
.*;
35 import com
.intellij
.openapi
.util
.io
.FileUtil
;
36 import com
.intellij
.openapi
.vcs
.*;
37 import com
.intellij
.openapi
.vcs
.changes
.conflicts
.ChangelistConflictTracker
;
38 import com
.intellij
.openapi
.vcs
.changes
.ui
.CommitHelper
;
39 import com
.intellij
.openapi
.vcs
.checkin
.CheckinEnvironment
;
40 import com
.intellij
.openapi
.vcs
.checkin
.CheckinHandler
;
41 import com
.intellij
.openapi
.vcs
.impl
.ProjectLevelVcsManagerImpl
;
42 import com
.intellij
.openapi
.vcs
.impl
.VcsInitObject
;
43 import com
.intellij
.openapi
.vcs
.readOnlyHandler
.ReadonlyStatusHandlerImpl
;
44 import com
.intellij
.openapi
.vfs
.VfsUtil
;
45 import com
.intellij
.openapi
.vfs
.VirtualFile
;
46 import com
.intellij
.util
.ConcurrencyUtil
;
47 import com
.intellij
.util
.Consumer
;
48 import com
.intellij
.util
.EventDispatcher
;
49 import com
.intellij
.util
.containers
.MultiMap
;
50 import com
.intellij
.util
.messages
.Topic
;
51 import org
.jdom
.Element
;
52 import org
.jetbrains
.annotations
.NonNls
;
53 import org
.jetbrains
.annotations
.NotNull
;
54 import org
.jetbrains
.annotations
.Nullable
;
59 import java
.util
.concurrent
.ExecutorService
;
60 import java
.util
.concurrent
.ScheduledExecutorService
;
65 public class ChangeListManagerImpl
extends ChangeListManagerEx
implements ProjectComponent
, ChangeListOwner
, JDOMExternalizable
{
66 private static final Logger LOG
= Logger
.getInstance("#com.intellij.openapi.vcs.changes.ChangeListManagerImpl");
68 private final Project myProject
;
69 private final ChangesViewManager myChangesViewManager
;
70 private final FileStatusManager myFileStatusManager
;
71 private final UpdateRequestsQueue myUpdater
;
73 @SuppressWarnings({"FieldAccessedSynchronizedAndUnsynchronized"})
74 private static final ScheduledExecutorService ourUpdateAlarm
= ConcurrencyUtil
.newSingleScheduledThreadExecutor("Change List Updater", Thread
.MIN_PRIORITY
+ 1);
76 private final Modifier myModifier
;
78 private FileHolderComposite myComposite
;
80 private final ChangeListWorker myWorker
;
81 private VcsException myUpdateException
= null;
83 @SuppressWarnings({"FieldAccessedSynchronizedAndUnsynchronized"})
84 private final EventDispatcher
<ChangeListListener
> myListeners
= EventDispatcher
.create(ChangeListListener
.class);
86 private final Object myDataLock
= new Object();
88 private final List
<CommitExecutor
> myExecutors
= new ArrayList
<CommitExecutor
>();
90 private final IgnoredFilesComponent myIgnoredIdeaLevel
;
91 private ProgressIndicator myUpdateChangesProgressIndicator
;
93 public static final Key
<Object
> DOCUMENT_BEING_COMMITTED_KEY
= new Key
<Object
>("DOCUMENT_BEING_COMMITTED");
95 public static final Topic
<LocalChangeListsLoadedListener
> LISTS_LOADED
= new Topic
<LocalChangeListsLoadedListener
>(
96 "LOCAL_CHANGE_LISTS_LOADED", LocalChangeListsLoadedListener
.class);
98 private boolean myShowLocalChangesInvalidated
;
100 private final DelayedNotificator myDelayedNotificator
;
102 private final VcsListener myVcsListener
= new VcsListener() {
103 public void directoryMappingChanged() {
104 VcsDirtyScopeManager
.getInstanceChecked(myProject
).markEverythingDirty();
107 private final ChangelistConflictTracker myConflictTracker
;
109 public static ChangeListManagerImpl
getInstanceImpl(final Project project
) {
110 return (ChangeListManagerImpl
) project
.getComponent(ChangeListManager
.class);
113 public ChangeListManagerImpl(final Project project
) {
115 myChangesViewManager
= ChangesViewManager
.getInstance(myProject
);
116 myFileStatusManager
= FileStatusManager
.getInstance(myProject
);
117 myComposite
= new FileHolderComposite(project
);
118 myIgnoredIdeaLevel
= new IgnoredFilesComponent(myProject
);
119 myUpdater
= new UpdateRequestsQueue(myProject
, ourUpdateAlarm
, new ActualUpdater());
121 myWorker
= new ChangeListWorker(myProject
, new MyChangesDeltaForwarder(myProject
, ourUpdateAlarm
));
122 myDelayedNotificator
= new DelayedNotificator(myListeners
, ourUpdateAlarm
);
123 myModifier
= new Modifier(myWorker
, myDelayedNotificator
);
125 myConflictTracker
= new ChangelistConflictTracker(project
, this, myFileStatusManager
);
128 public void projectOpened() {
129 initializeForNewProject();
131 if (ApplicationManager
.getApplication().isUnitTestMode()) {
132 myWorker
.initialized();
133 myUpdater
.initialized();
134 ProjectLevelVcsManager
.getInstance(myProject
).addVcsListener(myVcsListener
);
137 ((ProjectLevelVcsManagerImpl
) ProjectLevelVcsManager
.getInstance(myProject
)).addInitializationRequest(
138 VcsInitObject
.CHANGE_LIST_MANAGER
, new DumbAwareRunnable() {
140 myWorker
.initialized();
141 myUpdater
.initialized();
142 broadcastStateAfterLoad();
143 ProjectLevelVcsManager
.getInstance(myProject
).addVcsListener(myVcsListener
);
148 myConflictTracker
.startTracking();
151 private void broadcastStateAfterLoad() {
152 final List
<LocalChangeList
> listCopy
;
153 synchronized (myDataLock
) {
154 listCopy
= getChangeListsCopy();
156 if (! listCopy
.isEmpty()) {
157 myProject
.getMessageBus().syncPublisher(LISTS_LOADED
).processLoadedLists(listCopy
);
161 private void initializeForNewProject() {
162 synchronized (myDataLock
) {
163 if (myWorker
.isEmpty()) {
164 final LocalChangeList list
= myWorker
.addChangeList(VcsBundle
.message("changes.default.changlist.name"), null);
165 setDefaultChangeList(list
);
167 if (myIgnoredIdeaLevel
.isEmpty()) {
168 final String name
= myProject
.getName();
169 myIgnoredIdeaLevel
.add(IgnoredBeanFactory
.ignoreFile(name
+ WorkspaceFileType
.DOT_DEFAULT_EXTENSION
, myProject
));
170 myIgnoredIdeaLevel
.add(IgnoredBeanFactory
.ignoreFile(Project
.DIRECTORY_STORE_FOLDER
+ "/workspace.xml", myProject
));
176 public void projectClosed() {
177 ProjectLevelVcsManager
.getInstance(myProject
).removeVcsListener(myVcsListener
);
179 synchronized (myDataLock
) {
180 if (myUpdateChangesProgressIndicator
!= null) {
181 myUpdateChangesProgressIndicator
.cancel();
186 myConflictTracker
.stopTracking();
190 public String
getComponentName() {
191 return "ChangeListManager";
194 public void initComponent() {
197 public void disposeComponent() {
201 * update itself might produce actions done on AWT thread (invoked-after),
202 * so waiting for its completion on AWT thread is not good
204 * runnable is invoked on AWT thread
206 public void invokeAfterUpdate(final Runnable afterUpdate
, final InvokeAfterUpdateMode mode
, final String title
, final ModalityState state
) {
207 myUpdater
.invokeAfterUpdate(afterUpdate
, mode
, title
, null, state
);
210 public void invokeAfterUpdate(final Runnable afterUpdate
, final InvokeAfterUpdateMode mode
, final String title
,
211 final Consumer
<VcsDirtyScopeManager
> dirtyScopeManagerFiller
, final ModalityState state
) {
212 myUpdater
.invokeAfterUpdate(afterUpdate
, mode
, title
, dirtyScopeManagerFiller
, state
);
215 static class DisposedException
extends RuntimeException
{}
217 public void scheduleUpdate() {
218 myUpdater
.schedule(true);
221 public void scheduleUpdate(boolean updateUnversionedFiles
) {
222 myUpdater
.schedule(updateUnversionedFiles
);
225 private class ActualUpdater
implements LocalChangesUpdater
{
226 public void execute(boolean updateUnversioned
, AtomicSectionsAware atomicSectionsAware
) {
227 updateImmediately(updateUnversioned
, atomicSectionsAware
);
231 private void updateImmediately(final boolean updateUnversionedFiles
, final AtomicSectionsAware atomicSectionsAware
) {
232 FileHolderComposite composite
;
233 ChangeListWorker changeListWorker
;
235 final VcsDirtyScopeManagerImpl dirtyScopeManager
;
237 dirtyScopeManager
= ((VcsDirtyScopeManagerImpl
) VcsDirtyScopeManager
.getInstanceChecked(myProject
));
239 catch(ProcessCanceledException ex
) {
242 catch(Exception ex
) {
246 final VcsInvalidated invalidated
= dirtyScopeManager
.retrieveScopes();
247 if (invalidated
== null || invalidated
.isEmpty()) {
248 // a hack here; but otherwise everything here should be refactored ;)
249 if (invalidated
.isEmpty() && invalidated
.isEverythingDirty()) {
250 VcsDirtyScopeManager
.getInstance(myProject
).markEverythingDirty();
254 final boolean wasEverythingDirty
= invalidated
.isEverythingDirty();
255 final List
<VcsDirtyScope
> scopes
= invalidated
.getScopes();
260 // copy existsing data to objects that would be updated.
261 // mark for "modifier" that update started (it would create duplicates of modification commands done by user during update;
262 // after update of copies of objects is complete, it would apply the same modifications to copies.)
263 synchronized (myDataLock
) {
264 changeListWorker
= myWorker
.copy();
265 composite
= updateUnversionedFiles ?
(FileHolderComposite
) myComposite
.copy() : myComposite
;
266 myModifier
.enterUpdate();
267 if (wasEverythingDirty
) {
268 myUpdateException
= null;
270 if (updateUnversionedFiles
&& wasEverythingDirty
) {
271 composite
.cleanAll();
274 if (wasEverythingDirty
) {
275 changeListWorker
.notifyStartProcessingChanges(null);
277 myChangesViewManager
.scheduleRefresh();
279 final ChangeListManagerGate gate
= changeListWorker
.createSelfGate();
281 // do actual requests about file statuses
282 final UpdatingChangeListBuilder builder
= new UpdatingChangeListBuilder(changeListWorker
, composite
, new Getter
<Boolean
>() {
283 public Boolean
get() {
284 return myUpdater
.isStopped();
286 }, updateUnversionedFiles
, myIgnoredIdeaLevel
, gate
);
288 myUpdateChangesProgressIndicator
= new EmptyProgressIndicator() {
290 public boolean isCanceled() {
291 return myUpdater
.isStopped() || atomicSectionsAware
.shouldExitAsap();
294 public void checkCanceled() {
296 atomicSectionsAware
.checkShouldExit();
299 for (final VcsDirtyScope scope
: scopes
) {
300 atomicSectionsAware
.checkShouldExit();
302 final AbstractVcs vcs
= scope
.getVcs();
303 if (vcs
== null) continue;
304 final VcsAppendableDirtyScope adjustedScope
= vcs
.adjustDirtyScope((VcsAppendableDirtyScope
) scope
);
306 myChangesViewManager
.updateProgressText(VcsBundle
.message("changes.update.progress.message", vcs
.getDisplayName()), false);
307 if (! wasEverythingDirty
) {
308 changeListWorker
.notifyStartProcessingChanges(adjustedScope
);
310 if (updateUnversionedFiles
&& !wasEverythingDirty
) {
311 composite
.cleanScope(adjustedScope
);
315 actualUpdate(wasEverythingDirty
, composite
, builder
, adjustedScope
, vcs
, changeListWorker
, gate
);
317 catch (Throwable t
) {
319 if (t
instanceof Error
) {
321 } else if (t
instanceof RuntimeException
) {
322 throw (RuntimeException
) t
;
324 throw new RuntimeException(t
);
327 if (myUpdateException
!= null) break;
330 final boolean takeChanges
= (myUpdateException
== null);
332 synchronized (myDataLock
) {
333 // do same modifications to change lists as was done during update + do delayed notifications
334 if (wasEverythingDirty
) {
335 changeListWorker
.notifyDoneProcessingChanges(myDelayedNotificator
.getProxyDispatcher());
337 myModifier
.exitUpdate();
338 // should be applied for notifications to be delivered (they were delayed)
339 myModifier
.apply(changeListWorker
);
340 myModifier
.clearQueue();
341 // update member from copy
343 myWorker
.takeData(changeListWorker
);
346 if (takeChanges
&& updateUnversionedFiles
) {
347 boolean statusChanged
= !myComposite
.equals(composite
);
348 myComposite
= composite
;
350 myDelayedNotificator
.getProxyDispatcher().unchangedFileStatusChanged();
355 updateIgnoredFiles(false);
357 myShowLocalChangesInvalidated
= false;
359 myChangesViewManager
.scheduleRefresh();
361 catch (DisposedException e
) {
362 // OK, we're finishing all the stuff now.
364 catch(ProcessCanceledException e
) {
365 // OK, we're finishing all the stuff now.
367 catch(Exception ex
) {
370 catch(AssertionError ex
) {
374 dirtyScopeManager
.changesProcessed();
376 synchronized (myDataLock
) {
377 myDelayedNotificator
.getProxyDispatcher().changeListUpdateDone();
378 myChangesViewManager
.scheduleRefresh();
383 private void actualUpdate(final boolean wasEverythingDirty
, final FileHolderComposite composite
, final UpdatingChangeListBuilder builder
,
384 final VcsDirtyScope scope
, final AbstractVcs vcs
, final ChangeListWorker changeListWorker
,
385 final ChangeListManagerGate gate
) {
387 final ChangeProvider changeProvider
= vcs
.getChangeProvider();
388 if (changeProvider
!= null) {
389 final FoldersCutDownWorker foldersCutDownWorker
= new FoldersCutDownWorker();
391 builder
.setCurrent(scope
, foldersCutDownWorker
);
392 changeProvider
.getChanges(scope
, builder
, myUpdateChangesProgressIndicator
, gate
);
394 catch (VcsException e
) {
396 if (myUpdateException
== null) {
397 myUpdateException
= e
;
400 composite
.getIgnoredFileHolder().calculateChildren();
404 if ((! myUpdater
.isStopped()) && !wasEverythingDirty
) {
405 changeListWorker
.notifyDoneProcessingChanges(myDelayedNotificator
.getProxyDispatcher());
410 private void checkIfDisposed() {
411 if (myUpdater
.isStopped()) throw new DisposedException();
414 static boolean isUnder(final Change change
, final VcsDirtyScope scope
) {
415 final ContentRevision before
= change
.getBeforeRevision();
416 final ContentRevision after
= change
.getAfterRevision();
417 return before
!= null && scope
.belongsTo(before
.getFile()) || after
!= null && scope
.belongsTo(after
.getFile());
420 public List
<LocalChangeList
> getChangeListsCopy() {
421 synchronized (myDataLock
) {
422 return myWorker
.getListsCopy();
428 * this method made equivalent to {@link #getChangeListsCopy()} so to don't be confused by method name,
429 * better use {@link #getChangeListsCopy()}
432 public List
<LocalChangeList
> getChangeLists() {
433 synchronized (myDataLock
) {
434 return getChangeListsCopy();
438 public List
<File
> getAffectedPaths() {
439 synchronized (myDataLock
) {
440 return myWorker
.getAffectedPaths();
445 public List
<VirtualFile
> getAffectedFiles() {
446 synchronized (myDataLock
) {
447 return myWorker
.getAffectedFiles();
451 List
<VirtualFile
> getUnversionedFiles() {
452 return new ArrayList
<VirtualFile
>(myComposite
.getVFHolder(FileHolder
.HolderType
.UNVERSIONED
).getFiles());
455 List
<VirtualFile
> getModifiedWithoutEditing() {
456 return new ArrayList
<VirtualFile
>(myComposite
.getVFHolder(FileHolder
.HolderType
.MODIFIED_WITHOUT_EDITING
).getFiles());
460 * @return only roots for ignored folders, and ignored files
462 List
<VirtualFile
> getIgnoredFiles() {
463 return new ArrayList
<VirtualFile
>(myComposite
.getIgnoredFileHolder().getBranchToFileMap().values());
466 public List
<VirtualFile
> getLockedFolders() {
467 return new ArrayList
<VirtualFile
>(myComposite
.getVFHolder(FileHolder
.HolderType
.LOCKED
).getFiles());
470 Map
<VirtualFile
, LogicalLock
> getLogicallyLockedFolders() {
471 return new HashMap
<VirtualFile
, LogicalLock
>(((LogicallyLockedHolder
) myComposite
.get(FileHolder
.HolderType
.LOGICALLY_LOCKED
)).getMap());
474 public boolean isLogicallyLocked(final VirtualFile file
) {
475 return ((LogicallyLockedHolder
) myComposite
.get(FileHolder
.HolderType
.LOGICALLY_LOCKED
)).getMap().containsKey(file
);
478 public boolean isContainedInLocallyDeleted(final FilePath filePath
) {
479 synchronized (myDataLock
) {
480 return myWorker
.isContainedInLocallyDeleted(filePath
);
484 public List
<LocallyDeletedChange
> getDeletedFiles() {
485 synchronized (myDataLock
) {
486 return myWorker
.getLocallyDeleted().getFiles();
490 MultiMap
<String
, VirtualFile
> getSwitchedFilesMap() {
491 synchronized (myDataLock
) {
492 return myWorker
.getSwitchedHolder().getBranchToFileMap();
496 public VcsException
getUpdateException() {
497 return myUpdateException
;
500 public boolean isFileAffected(final VirtualFile file
) {
501 synchronized (myDataLock
) {
502 return myWorker
.getStatus(file
) != null;
507 public LocalChangeList
findChangeList(final String name
) {
508 synchronized (myDataLock
) {
509 return myWorker
.getCopyByName(name
);
514 public LocalChangeList
getChangeList(String id
) {
515 synchronized (myDataLock
) {
516 return myWorker
.getChangeList(id
);
520 public LocalChangeList
addChangeList(@NotNull String name
, final String comment
) {
521 synchronized (myDataLock
) {
522 final LocalChangeList changeList
= myModifier
.addChangeList(name
, comment
);
523 myChangesViewManager
.scheduleRefresh();
528 public void removeChangeList(final String name
) {
529 synchronized (myDataLock
) {
530 myModifier
.removeChangeList(name
);
531 myChangesViewManager
.scheduleRefresh();
535 public void removeChangeList(LocalChangeList list
) {
536 removeChangeList(list
.getName());
540 * does no modification to change lists, only notification is sent
543 public Runnable
prepareForChangeDeletion(final Collection
<Change
> changes
) {
544 final Map
<String
, LocalChangeList
> lists
= new HashMap
<String
, LocalChangeList
>();
545 final Map
<String
, List
<Change
>> map
;
546 synchronized (myDataLock
) {
547 map
= myWorker
.listsForChanges(changes
, lists
);
549 return new Runnable() {
551 final ChangeListListener multicaster
= myDelayedNotificator
.getProxyDispatcher();
552 synchronized (myDataLock
) {
553 for (Map
.Entry
<String
, List
<Change
>> entry
: map
.entrySet()) {
554 final List
<Change
> changes
= entry
.getValue();
555 for (Iterator
<Change
> iterator
= changes
.iterator(); iterator
.hasNext();) {
556 final Change change
= iterator
.next();
557 if (getChangeList(change
) != null) {
558 // was not actually rolled back
562 multicaster
.changesRemoved(changes
, lists
.get(entry
.getKey()));
569 public void setDefaultChangeList(@NotNull LocalChangeList list
) {
570 synchronized (myDataLock
) {
571 myModifier
.setDefault(list
.getName());
572 myChangesViewManager
.scheduleRefresh();
577 public LocalChangeList
getDefaultChangeList() {
578 synchronized (myDataLock
) {
579 return myWorker
.getDefaultListCopy();
584 public boolean isDefaultChangeList(ChangeList list
) {
585 return list
instanceof LocalChangeList
&& myWorker
.isDefaultList((LocalChangeList
)list
);
589 public Collection
<LocalChangeList
> getInvolvedListsFilterChanges(final Collection
<Change
> changes
, final List
<Change
> validChanges
) {
590 synchronized (myDataLock
) {
591 return myWorker
.getInvolvedListsFilterChanges(changes
, validChanges
);
596 public LocalChangeList
getChangeList(Change change
) {
597 synchronized (myDataLock
) {
598 return myWorker
.listForChange(change
);
603 public String
getChangeListNameIfOnlyOne(final Change
[] changes
) {
604 synchronized (myDataLock
) {
605 return myWorker
.listNameIfOnlyOne(changes
);
611 * better use normal comparison, with equals
614 public LocalChangeList
getIdentityChangeList(Change change
) {
615 synchronized (myDataLock
) {
616 final List
<LocalChangeList
> lists
= myWorker
.getListsCopy();
617 for (LocalChangeList list
: lists
) {
618 for(Change oldChange
: list
.getChanges()) {
619 if (oldChange
== change
) {
629 public boolean isInUpdate() {
630 synchronized (myDataLock
) {
631 return myModifier
.isInsideUpdate() || myShowLocalChangesInvalidated
;
636 public Change
getChange(@NotNull VirtualFile file
) {
637 synchronized (myDataLock
) {
638 final LocalChangeList list
= myWorker
.getListCopy(file
);
640 for (Change change
: list
.getChanges()) {
641 final ContentRevision afterRevision
= change
.getAfterRevision();
642 if (afterRevision
!= null) {
643 String revisionPath
= FileUtil
.toSystemIndependentName(afterRevision
.getFile().getIOFile().getPath());
644 if (FileUtil
.pathsEqual(revisionPath
, file
.getPath())) return change
;
646 final ContentRevision beforeRevision
= change
.getBeforeRevision();
647 if (beforeRevision
!= null) {
648 String revisionPath
= FileUtil
.toSystemIndependentName(beforeRevision
.getFile().getIOFile().getPath());
649 if (FileUtil
.pathsEqual(revisionPath
, file
.getPath())) return change
;
659 public LocalChangeList
getChangeList(@NotNull VirtualFile file
) {
660 synchronized (myDataLock
) {
661 return myWorker
.getListCopy(file
);
666 public Change
getChange(final FilePath file
) {
667 synchronized (myDataLock
) {
668 return myWorker
.getChangeForPath(file
);
672 public boolean isUnversioned(VirtualFile file
) {
673 return myComposite
.getVFHolder(FileHolder
.HolderType
.UNVERSIONED
).containsFile(file
);
677 public FileStatus
getStatus(VirtualFile file
) {
678 synchronized (myDataLock
) {
679 if (myComposite
.getVFHolder(FileHolder
.HolderType
.UNVERSIONED
).containsFile(file
)) return FileStatus
.UNKNOWN
;
680 if (myComposite
.getVFHolder(FileHolder
.HolderType
.MODIFIED_WITHOUT_EDITING
).containsFile(file
)) return FileStatus
.HIJACKED
;
681 if (myComposite
.getIgnoredFileHolder().containsFile(file
)) return FileStatus
.IGNORED
;
683 final FileStatus status
= myWorker
.getStatus(file
);
684 if (status
!= null) {
687 if (myWorker
.isSwitched(file
)) return FileStatus
.SWITCHED
;
688 return FileStatus
.NOT_CHANGED
;
693 public Collection
<Change
> getChangesIn(VirtualFile dir
) {
694 return getChangesIn(new FilePathImpl(dir
));
698 public Collection
<Change
> getChangesIn(final FilePath dirPath
) {
699 synchronized (myDataLock
) {
700 return myWorker
.getChangesIn(dirPath
);
704 public void moveChangesTo(LocalChangeList list
, final Change
[] changes
) {
705 synchronized (myDataLock
) {
706 myModifier
.moveChangesTo(list
.getName(), changes
);
708 SwingUtilities
.invokeLater(new Runnable() {
710 myChangesViewManager
.refreshView();
715 public void addUnversionedFiles(final LocalChangeList list
, @NotNull final List
<VirtualFile
> files
) {
716 final List
<VcsException
> exceptions
= new ArrayList
<VcsException
>();
717 ChangesUtil
.processVirtualFilesByVcs(myProject
, files
, new ChangesUtil
.PerVcsProcessor
<VirtualFile
>() {
718 public void process(final AbstractVcs vcs
, final List
<VirtualFile
> items
) {
719 final CheckinEnvironment environment
= vcs
.getCheckinEnvironment();
720 if (environment
!= null) {
721 final List
<VcsException
> result
= environment
.scheduleUnversionedFilesForAddition(items
);
722 if (result
!= null) {
723 exceptions
.addAll(result
);
729 if (exceptions
.size() > 0) {
730 StringBuilder message
= new StringBuilder(VcsBundle
.message("error.adding.files.prompt"));
731 for(VcsException ex
: exceptions
) {
732 message
.append("\n").append(ex
.getMessage());
734 Messages
.showErrorDialog(myProject
, message
.toString(), VcsBundle
.message("error.adding.files.title"));
737 for (VirtualFile file
: files
) {
738 myFileStatusManager
.fileStatusChanged(file
);
740 VcsDirtyScopeManager
.getInstance(myProject
).filesDirty(files
, null);
742 if (!list
.isDefault()) {
743 // find the changes for the added files and move them to the necessary changelist
744 invokeAfterUpdate(new Runnable() {
746 synchronized (myDataLock
) {
747 List
<Change
> changesToMove
= new ArrayList
<Change
>();
748 final LocalChangeList defaultList
= getDefaultChangeList();
749 for(Change change
: defaultList
.getChanges()) {
750 final ContentRevision afterRevision
= change
.getAfterRevision();
751 if (afterRevision
!= null) {
752 VirtualFile vFile
= afterRevision
.getFile().getVirtualFile();
753 if (files
.contains(vFile
)) {
754 changesToMove
.add(change
);
759 if (changesToMove
.size() > 0) {
760 moveChangesTo(list
, changesToMove
.toArray(new Change
[changesToMove
.size()]));
764 myChangesViewManager
.scheduleRefresh();
766 }, InvokeAfterUpdateMode
.BACKGROUND_NOT_CANCELLABLE
, VcsBundle
.message("change.lists.manager.add.unversioned"), null);
768 myChangesViewManager
.scheduleRefresh();
772 public Project
getProject() {
776 public void addChangeListListener(ChangeListListener listener
) {
777 myListeners
.addListener(listener
);
781 public void removeChangeListListener(ChangeListListener listener
) {
782 myListeners
.removeListener(listener
);
785 public void registerCommitExecutor(CommitExecutor executor
) {
786 myExecutors
.add(executor
);
789 public void commitChanges(LocalChangeList changeList
, List
<Change
> changes
) {
790 doCommit(changeList
, changes
, false);
793 private boolean doCommit(final LocalChangeList changeList
, final List
<Change
> changes
, final boolean synchronously
) {
794 return new CommitHelper(myProject
, changeList
, changes
, changeList
.getName(),
795 changeList
.getComment(), new ArrayList
<CheckinHandler
>(), false, synchronously
, null).doCommit();
798 public void commitChangesSynchronously(LocalChangeList changeList
, List
<Change
> changes
) {
799 doCommit(changeList
, changes
, true);
802 public boolean commitChangesSynchronouslyWithResult(final LocalChangeList changeList
, final List
<Change
> changes
) {
803 return doCommit(changeList
, changes
, true);
806 @SuppressWarnings({"unchecked"})
807 public void readExternal(Element element
) throws InvalidDataException
{
808 if (! myProject
.isDefault()) {
809 synchronized (myDataLock
) {
810 myIgnoredIdeaLevel
.clear();
811 new ChangeListManagerSerialization(myIgnoredIdeaLevel
, myWorker
).readExternal(element
);
812 if ((! myWorker
.isEmpty()) && getDefaultChangeList() == null) {
813 setDefaultChangeList(myWorker
.getListsCopy().get(0));
816 myConflictTracker
.loadState(element
);
820 public void writeExternal(Element element
) throws WriteExternalException
{
821 if (! myProject
.isDefault()) {
822 final IgnoredFilesComponent ignoredFilesComponent
;
823 final ChangeListWorker worker
;
824 synchronized (myDataLock
) {
825 ignoredFilesComponent
= new IgnoredFilesComponent(myProject
);
826 ignoredFilesComponent
.add(myIgnoredIdeaLevel
.getFilesToIgnore());
827 worker
= myWorker
.copy();
829 new ChangeListManagerSerialization(ignoredFilesComponent
, worker
).writeExternal(element
);
830 myConflictTracker
.saveState(element
);
835 public void reopenFiles(List
<FilePath
> paths
) {
836 final ReadonlyStatusHandlerImpl readonlyStatusHandler
= (ReadonlyStatusHandlerImpl
)ReadonlyStatusHandlerImpl
.getInstance(myProject
);
837 final boolean savedOption
= readonlyStatusHandler
.getState().SHOW_DIALOG
;
838 readonlyStatusHandler
.getState().SHOW_DIALOG
= false;
840 readonlyStatusHandler
.ensureFilesWritable(collectFiles(paths
));
843 readonlyStatusHandler
.getState().SHOW_DIALOG
= savedOption
;
847 public List
<CommitExecutor
> getRegisteredExecutors() {
848 return Collections
.unmodifiableList(myExecutors
);
851 public void addFilesToIgnore(final IgnoredFileBean
... filesToIgnore
) {
852 myIgnoredIdeaLevel
.add(filesToIgnore
);
853 updateIgnoredFiles(true);
856 public void setFilesToIgnore(final IgnoredFileBean
... filesToIgnore
) {
857 myIgnoredIdeaLevel
.set(filesToIgnore
);
858 updateIgnoredFiles(true);
861 private void updateIgnoredFiles(final boolean checkIgnored
) {
862 synchronized (myDataLock
) {
863 List
<VirtualFile
> unversionedFiles
= myComposite
.getVFHolder(FileHolder
.HolderType
.UNVERSIONED
).getFiles();
864 //List<VirtualFile> ignoredFiles = myComposite.getVFHolder(FileHolder.HolderType.IGNORED).getFiles();
865 boolean somethingChanged
= false;
866 for(VirtualFile file
: unversionedFiles
) {
867 if (isIgnoredFile(file
)) {
868 somethingChanged
= true;
869 myComposite
.getVFHolder(FileHolder
.HolderType
.UNVERSIONED
).removeFile(file
);
870 myComposite
.getIgnoredFileHolder().addFile(file
, "", false);
873 /*if (checkIgnored) {
874 for(VirtualFile file: ignoredFiles) {
875 if (!isIgnoredFile(file)) {
876 somethingChanged = true;
877 // the file may have been reported as ignored by the VCS, so we can't directly move it to unversioned files
878 VcsDirtyScopeManager.getInstance(myProject).fileDirty(file);
882 if (somethingChanged
) {
883 myFileStatusManager
.fileStatusesChanged();
884 myChangesViewManager
.scheduleRefresh();
889 public IgnoredFileBean
[] getFilesToIgnore() {
890 return myIgnoredIdeaLevel
.getFilesToIgnore();
893 public boolean isIgnoredFile(@NotNull VirtualFile file
) {
894 return myIgnoredIdeaLevel
.isIgnoredFile(file
);
898 public String
getSwitchedBranch(final VirtualFile file
) {
899 synchronized (myDataLock
) {
900 return myWorker
.getBranchForFile(file
);
905 public String
getDefaultListName() {
906 synchronized (myDataLock
) {
907 return myWorker
.getDefaultListName();
911 private static VirtualFile
[] collectFiles(final List
<FilePath
> paths
) {
912 final ArrayList
<VirtualFile
> result
= new ArrayList
<VirtualFile
>();
913 for (FilePath path
: paths
) {
914 if (path
.getVirtualFile() != null) {
915 result
.add(path
.getVirtualFile());
919 return VfsUtil
.toVirtualFileArray(result
);
922 public boolean setReadOnly(final String name
, final boolean value
) {
923 synchronized (myDataLock
) {
924 final boolean result
= myModifier
.setReadOnly(name
, value
);
925 myChangesViewManager
.scheduleRefresh();
930 public boolean editName(@NotNull final String fromName
, @NotNull final String toName
) {
931 synchronized (myDataLock
) {
932 final boolean result
= myModifier
.editName(fromName
, toName
);
933 myChangesViewManager
.scheduleRefresh();
938 public String
editComment(@NotNull final String fromName
, final String newComment
) {
939 synchronized (myDataLock
) {
940 final String oldComment
= myModifier
.editComment(fromName
, newComment
);
941 myChangesViewManager
.scheduleRefresh();
947 * Can be called only from not AWT thread; to do smthg after ChangeListManager refresh, call invokeAfterUpdate
949 public boolean ensureUpToDate(final boolean canBeCanceled
) {
950 final EnsureUpToDateFromNonAWTThread worker
= new EnsureUpToDateFromNonAWTThread(myProject
);
952 return worker
.isDone();
955 // only a light attempt to show that some dirty scope request is asynchronously coming
956 // for users to see changes are not valid
957 // (commit -> asynch synch VFS -> asynch vcs dirty scope)
958 public void showLocalChangesInvalidated() {
959 synchronized (myDataLock
) {
960 myShowLocalChangesInvalidated
= true;
964 public ChangelistConflictTracker
getConflictTracker() {
965 return myConflictTracker
;
968 private static class MyChangesDeltaForwarder
implements PlusMinus
<Pair
<String
, AbstractVcs
>> {
969 private SlowlyClosingAlarm myAlarm
;
970 private RemoteRevisionsCache myRevisionsCache
;
971 private final ProjectLevelVcsManager myVcsManager
;
973 public MyChangesDeltaForwarder(final Project project
, final ExecutorService service
) {
974 myAlarm
= ControlledAlarmFactory
.createOnSharedThread(project
, "changes delta consumer forwarder", service
);
975 myRevisionsCache
= RemoteRevisionsCache
.getInstance(project
);
976 myVcsManager
= ProjectLevelVcsManager
.getInstance(project
);
979 public void plus(final Pair
<String
, AbstractVcs
> stringAbstractVcsPair
) {
980 myAlarm
.addRequest(new Runnable() {
982 myRevisionsCache
.plus(getCorrectedPair(stringAbstractVcsPair
));
987 public void minus(final Pair
<String
, AbstractVcs
> stringAbstractVcsPair
) {
988 myAlarm
.addRequest(new Runnable() {
990 myRevisionsCache
.minus(getCorrectedPair(stringAbstractVcsPair
));
995 private Pair
<String
, AbstractVcs
> getCorrectedPair(final Pair
<String
, AbstractVcs
> stringAbstractVcsPair
) {
996 Pair
<String
, AbstractVcs
> correctedPair
= stringAbstractVcsPair
;
997 if (stringAbstractVcsPair
.getSecond() == null) {
998 final String path
= stringAbstractVcsPair
.getFirst();
999 correctedPair
= new Pair
<String
, AbstractVcs
>(path
, myVcsManager
.findVcsByName(findVcs(path
).getName()));
1001 return correctedPair
;
1005 private VcsKey
findVcs(final String path
) {
1006 // does not matter directory or not
1007 final AbstractVcs vcs
= myVcsManager
.getVcsFor(FilePathImpl
.create(new File(path
), false));
1008 return vcs
== null ?
null : vcs
.getKeyInstanceMethod();