logging for "git does not return any changes"
[fedora-idea.git] / platform / vcs-impl / src / com / intellij / openapi / vcs / changes / ChangeListManagerImpl.java
blobcaaf67221e3586cd3c29bbf2d2fc69c9650381d1
1 /*
2 * Copyright 2000-2009 JetBrains s.r.o.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
16 package com.intellij.openapi.vcs.changes;
18 import com.intellij.ide.highlighter.WorkspaceFileType;
19 import com.intellij.lifecycle.AtomicSectionsAware;
20 import com.intellij.lifecycle.ControlledAlarmFactory;
21 import com.intellij.lifecycle.SlowlyClosingAlarm;
22 import com.intellij.openapi.application.ApplicationManager;
23 import com.intellij.openapi.application.ModalityState;
24 import com.intellij.openapi.components.ProjectComponent;
25 import com.intellij.openapi.diagnostic.Logger;
26 import com.intellij.openapi.progress.EmptyProgressIndicator;
27 import com.intellij.openapi.progress.ProcessCanceledException;
28 import com.intellij.openapi.progress.ProgressIndicator;
29 import com.intellij.openapi.project.DumbAwareRunnable;
30 import com.intellij.openapi.project.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;
56 import javax.swing.*;
57 import java.io.File;
58 import java.util.*;
59 import java.util.concurrent.ExecutorService;
60 import java.util.concurrent.ScheduledExecutorService;
62 /**
63 * @author max
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) {
114 myProject = 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);
136 else {
137 ((ProjectLevelVcsManagerImpl) ProjectLevelVcsManager.getInstance(myProject)).addInitializationRequest(
138 VcsInitObject.CHANGE_LIST_MANAGER, new DumbAwareRunnable() {
139 public void run() {
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();
185 myUpdater.stop();
186 myConflictTracker.stopTracking();
189 @NotNull @NonNls
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;
236 try {
237 dirtyScopeManager = ((VcsDirtyScopeManagerImpl) VcsDirtyScopeManager.getInstanceChecked(myProject));
239 catch(ProcessCanceledException ex) {
240 return;
242 catch(Exception ex) {
243 LOG.error(ex);
244 return;
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();
252 return;
254 final boolean wasEverythingDirty = invalidated.isEverythingDirty();
255 final List<VcsDirtyScope> scopes = invalidated.getScopes();
257 try {
258 checkIfDisposed();
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() {
289 @Override
290 public boolean isCanceled() {
291 return myUpdater.isStopped() || atomicSectionsAware.shouldExitAsap();
293 @Override
294 public void checkCanceled() {
295 checkIfDisposed();
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);
314 try {
315 actualUpdate(wasEverythingDirty, composite, builder, adjustedScope, vcs, changeListWorker, gate);
317 catch (Throwable t) {
318 LOG.info(t);
319 if (t instanceof Error) {
320 throw (Error) t;
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
342 if (takeChanges) {
343 myWorker.takeData(changeListWorker);
346 if (takeChanges && updateUnversionedFiles) {
347 boolean statusChanged = !myComposite.equals(composite);
348 myComposite = composite;
349 if (statusChanged) {
350 myDelayedNotificator.getProxyDispatcher().unchangedFileStatusChanged();
354 if (takeChanges) {
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) {
368 LOG.error(ex);
370 catch(AssertionError ex) {
371 LOG.error(ex);
373 finally {
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) {
386 try {
387 final ChangeProvider changeProvider = vcs.getChangeProvider();
388 if (changeProvider != null) {
389 final FoldersCutDownWorker foldersCutDownWorker = new FoldersCutDownWorker();
390 try {
391 builder.setCurrent(scope, foldersCutDownWorker);
392 changeProvider.getChanges(scope, builder, myUpdateChangesProgressIndicator, gate);
394 catch (VcsException e) {
395 LOG.info(e);
396 if (myUpdateException == null) {
397 myUpdateException = e;
400 composite.getIgnoredFileHolder().calculateChildren();
403 finally {
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();
427 * @deprecated
428 * this method made equivalent to {@link #getChangeListsCopy()} so to don't be confused by method name,
429 * better use {@link #getChangeListsCopy()}
431 @NotNull
432 public List<LocalChangeList> getChangeLists() {
433 synchronized (myDataLock) {
434 return getChangeListsCopy();
438 public List<File> getAffectedPaths() {
439 synchronized (myDataLock) {
440 return myWorker.getAffectedPaths();
444 @NotNull
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;
506 @Nullable
507 public LocalChangeList findChangeList(final String name) {
508 synchronized (myDataLock) {
509 return myWorker.getCopyByName(name);
513 @Override
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();
524 return changeList;
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
542 @NotNull
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() {
550 public void run() {
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
559 iterator.remove();
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();
576 @Nullable
577 public LocalChangeList getDefaultChangeList() {
578 synchronized (myDataLock) {
579 return myWorker.getDefaultListCopy();
583 @Override
584 public boolean isDefaultChangeList(ChangeList list) {
585 return list instanceof LocalChangeList && myWorker.isDefaultList((LocalChangeList)list);
588 @NotNull
589 public Collection<LocalChangeList> getInvolvedListsFilterChanges(final Collection<Change> changes, final List<Change> validChanges) {
590 synchronized (myDataLock) {
591 return myWorker.getInvolvedListsFilterChanges(changes, validChanges);
595 @Nullable
596 public LocalChangeList getChangeList(Change change) {
597 synchronized (myDataLock) {
598 return myWorker.listForChange(change);
602 @Override
603 public String getChangeListNameIfOnlyOne(final Change[] changes) {
604 synchronized (myDataLock) {
605 return myWorker.listNameIfOnlyOne(changes);
610 * @deprecated
611 * better use normal comparison, with equals
613 @Nullable
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) {
620 return list;
624 return null;
628 @Override
629 public boolean isInUpdate() {
630 synchronized (myDataLock) {
631 return myModifier.isInsideUpdate() || myShowLocalChangesInvalidated;
635 @Nullable
636 public Change getChange(@NotNull VirtualFile file) {
637 synchronized (myDataLock) {
638 final LocalChangeList list = myWorker.getListCopy(file);
639 if (list != null) {
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;
654 return null;
658 @Override
659 public LocalChangeList getChangeList(@NotNull VirtualFile file) {
660 synchronized (myDataLock) {
661 return myWorker.getListCopy(file);
665 @Nullable
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);
676 @NotNull
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) {
685 return status;
687 if (myWorker.isSwitched(file)) return FileStatus.SWITCHED;
688 return FileStatus.NOT_CHANGED;
692 @NotNull
693 public Collection<Change> getChangesIn(VirtualFile dir) {
694 return getChangesIn(new FilePathImpl(dir));
697 @NotNull
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() {
709 public void run() {
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() {
745 public void run() {
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);
767 } else {
768 myChangesViewManager.scheduleRefresh();
772 public Project getProject() {
773 return myProject;
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);
834 // used in TeamCity
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;
839 try {
840 readonlyStatusHandler.ensureFilesWritable(collectFiles(paths));
842 finally {
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);
897 @Nullable
898 public String getSwitchedBranch(final VirtualFile file) {
899 synchronized (myDataLock) {
900 return myWorker.getBranchForFile(file);
904 @Override
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();
926 return result;
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();
934 return result;
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();
942 return oldComment;
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);
951 worker.execute();
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() {
981 public void run() {
982 myRevisionsCache.plus(getCorrectedPair(stringAbstractVcsPair));
987 public void minus(final Pair<String, AbstractVcs> stringAbstractVcsPair) {
988 myAlarm.addRequest(new Runnable() {
989 public void run() {
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;
1004 @Nullable
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();