/* * Copyright 2000-2014 JetBrains s.r.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.intellij.openapi.vcs.changes; import com.intellij.ide.highlighter.WorkspaceFileType; import com.intellij.lifecycle.PeriodicalTasksCloser; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.application.ModalityState; import com.intellij.openapi.application.RuntimeInterruptedException; import com.intellij.openapi.components.ProjectComponent; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.fileEditor.FileDocumentManager; import com.intellij.openapi.module.Module; import com.intellij.openapi.module.ModuleManager; import com.intellij.openapi.progress.EmptyProgressIndicator; import com.intellij.openapi.progress.ProcessCanceledException; import com.intellij.openapi.progress.ProgressIndicator; import com.intellij.openapi.project.DumbAwareRunnable; import com.intellij.openapi.project.Project; import com.intellij.openapi.roots.ModuleRootManager; import com.intellij.openapi.roots.impl.DirectoryIndexExcludePolicy; import com.intellij.openapi.ui.MessageType; import com.intellij.openapi.ui.Messages; import com.intellij.openapi.util.*; import com.intellij.openapi.util.io.FileUtil; import com.intellij.openapi.util.registry.Registry; import com.intellij.openapi.util.text.StringUtil; import com.intellij.openapi.vcs.*; import com.intellij.openapi.vcs.changes.conflicts.ChangelistConflictTracker; import com.intellij.openapi.vcs.changes.ui.CommitHelper; import com.intellij.openapi.vcs.checkin.CheckinEnvironment; import com.intellij.openapi.vcs.checkin.CheckinHandler; import com.intellij.openapi.vcs.impl.*; import com.intellij.openapi.vcs.readOnlyHandler.ReadonlyStatusHandlerImpl; import com.intellij.openapi.vcs.ui.VcsBalloonProblemNotifier; import com.intellij.openapi.vfs.LocalFileSystem; import com.intellij.openapi.vfs.VfsUtil; import com.intellij.openapi.vfs.VfsUtilCore; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.ui.EditorNotifications; import com.intellij.util.*; import com.intellij.util.concurrency.Semaphore; import com.intellij.util.containers.MultiMap; import com.intellij.util.continuation.ContinuationPause; import com.intellij.util.messages.Topic; import com.intellij.vcsUtil.Rethrow; import com.intellij.vcsUtil.VcsUtil; import org.jdom.Element; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.TestOnly; import javax.swing.*; import java.io.File; import java.util.*; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledThreadPoolExecutor; import java.util.concurrent.atomic.AtomicReference; /** * @author max */ public class ChangeListManagerImpl extends ChangeListManagerEx implements ProjectComponent, ChangeListOwner, JDOMExternalizable, RoamingTypeDisabled { public static final Logger LOG = Logger.getInstance("#com.intellij.openapi.vcs.changes.ChangeListManagerImpl"); private static final String EXCLUDED_CONVERTED_TO_IGNORED_OPTION = "EXCLUDED_CONVERTED_TO_IGNORED"; private final Project myProject; private final VcsConfiguration myConfig; private final ChangesViewI myChangesViewManager; private final FileStatusManager myFileStatusManager; private final UpdateRequestsQueue myUpdater; private static final AtomicReference ourUpdateAlarm = new AtomicReference(); static { ourUpdateAlarm.set(createChangeListExecutor()); } private static ScheduledThreadPoolExecutor createChangeListExecutor() { return VcsUtil.createExecutor("Change List Updater"); } private final Modifier myModifier; private FileHolderComposite myComposite; private ChangeListWorker myWorker; private VcsException myUpdateException = null; private Factory myAdditionalInfo; private final EventDispatcher myListeners = EventDispatcher.create(ChangeListListener.class); private final Object myDataLock = new Object(); private final List myExecutors = new ArrayList(); private final IgnoredFilesComponent myIgnoredIdeaLevel; private boolean myExcludedConvertedToIgnored; private ProgressIndicator myUpdateChangesProgressIndicator; public static final Topic LISTS_LOADED = new Topic( "LOCAL_CHANGE_LISTS_LOADED", LocalChangeListsLoadedListener.class); private boolean myShowLocalChangesInvalidated; private AtomicReference myFreezeName; // notifies myListeners on the same thread that local changes update is done private final DelayedNotificator myDelayedNotificator; private final VcsListener myVcsListener = new VcsListener() { public void directoryMappingChanged() { VcsDirtyScopeManager.getInstance(myProject).markEverythingDirty(); } }; private final ChangelistConflictTracker myConflictTracker; private VcsDirtyScopeManager myDirtyScopeManager; private VcsDirtyScopeVfsListener myVfsListener; private boolean myModalNotificationsBlocked; @NotNull private final Collection myListsToBeDeleted = new HashSet(); public static ChangeListManagerImpl getInstanceImpl(final Project project) { return (ChangeListManagerImpl)PeriodicalTasksCloser.getInstance().safeGetComponent(project, ChangeListManager.class); } void setDirtyScopeManager(VcsDirtyScopeManager dirtyScopeManager) { myDirtyScopeManager = dirtyScopeManager; } public ChangeListManagerImpl(Project project, final VcsConfiguration config) { myProject = project; myConfig = config; myFreezeName = new AtomicReference(null); myAdditionalInfo = null; myChangesViewManager = myProject.isDefault() ? new DummyChangesView(myProject) : ChangesViewManager.getInstance(myProject); myVfsListener = ApplicationManager.getApplication().getComponent(VcsDirtyScopeVfsListener.class); myFileStatusManager = FileStatusManager.getInstance(myProject); myComposite = new FileHolderComposite(project); myIgnoredIdeaLevel = new IgnoredFilesComponent(myProject, true); myUpdater = new UpdateRequestsQueue(myProject, ourUpdateAlarm, new ActualUpdater()); myWorker = new ChangeListWorker(myProject, new MyChangesDeltaForwarder(myProject, ourUpdateAlarm)); myDelayedNotificator = new DelayedNotificator(myListeners, ourUpdateAlarm); myModifier = new Modifier(myWorker, myDelayedNotificator); myConflictTracker = new ChangelistConflictTracker(project, this, myFileStatusManager, EditorNotifications.getInstance(project)); myListeners.addListener(new ChangeListAdapter() { @Override public void defaultListChanged(final ChangeList oldDefaultList, ChangeList newDefaultList) { final LocalChangeList oldList = (LocalChangeList)oldDefaultList; if (oldDefaultList == null || oldList.hasDefaultName() || oldDefaultList.equals(newDefaultList)) return; if (!ApplicationManager.getApplication().isUnitTestMode() && oldDefaultList.getChanges().isEmpty() && !oldList.isReadOnly()) { invokeAfterUpdate(new Runnable() { public void run() { if (getChangeList(oldList.getId()) == null) { return; // removed already } switch (config.REMOVE_EMPTY_INACTIVE_CHANGELISTS) { case SHOW_CONFIRMATION: if (myModalNotificationsBlocked) { myListsToBeDeleted.add(oldList); return; } if (!showRemoveEmptyChangeListsProposal(config, Collections.singletonList(oldList))) { return; } break; case DO_NOTHING_SILENTLY: return; case DO_ACTION_SILENTLY: break; } removeChangeList(oldList); } }, InvokeAfterUpdateMode.SILENT, null, null); } } }); } /** * Shows the proposal to delete one or more changelists that were default and became empty. * * @return true if the changelists have to be deleted, false if not. */ private boolean showRemoveEmptyChangeListsProposal(@NotNull final VcsConfiguration config, @NotNull Collection lists) { if (lists.isEmpty()) { return false; } final String question; if (lists.size() == 1) { question = String.format("The empty changelist '%s' is no longer active.
Do you want to remove it?", StringUtil.first(lists.iterator().next().getName(), 30, true)); } else { question = String.format("Empty changelists
%s are no longer active.
Do you want to remove them?", StringUtil.join(lists, new Function() { @Override public String fun(LocalChangeList list) { return StringUtil.first(list.getName(), 30, true); } }, "
")); } VcsConfirmationDialog dialog = new VcsConfirmationDialog(myProject, new VcsShowConfirmationOption() { public Value getValue() { return config.REMOVE_EMPTY_INACTIVE_CHANGELISTS; } public void setValue(Value value) { config.REMOVE_EMPTY_INACTIVE_CHANGELISTS = value; } @Override public boolean isPersistent() { return true; } }, question, "&Remember my choice"); dialog.show(); return dialog.isOK(); } @Override @CalledInAwt public void blockModalNotifications() { myModalNotificationsBlocked = true; } @Override @CalledInAwt public void unblockModalNotifications() { myModalNotificationsBlocked = false; if (myListsToBeDeleted.isEmpty()) { return; } if (showRemoveEmptyChangeListsProposal(myConfig, myListsToBeDeleted)) { for (LocalChangeList list : myListsToBeDeleted) { removeChangeList(list); } } myListsToBeDeleted.clear(); } public void projectOpened() { initializeForNewProject(); final ProjectLevelVcsManager vcsManager = ProjectLevelVcsManager.getInstance(myProject); if (ApplicationManager.getApplication().isUnitTestMode()) { myUpdater.initialized(); vcsManager.addVcsListener(myVcsListener); } else { ((ProjectLevelVcsManagerImpl)vcsManager).addInitializationRequest( VcsInitObject.CHANGE_LIST_MANAGER, new DumbAwareRunnable() { public void run() { myUpdater.initialized(); broadcastStateAfterLoad(); vcsManager.addVcsListener(myVcsListener); } }); } myConflictTracker.startTracking(); } private void broadcastStateAfterLoad() { final List listCopy; synchronized (myDataLock) { listCopy = getChangeListsCopy(); } if (!myProject.isDisposed()) { myProject.getMessageBus().syncPublisher(LISTS_LOADED).processLoadedLists(listCopy); } } private void initializeForNewProject() { ApplicationManager.getApplication().runReadAction(new Runnable() { @Override public void run() { synchronized (myDataLock) { if (myWorker.isEmpty()) { final LocalChangeList list = myWorker.addChangeList(VcsBundle.message("changes.default.changelist.name"), null, null); setDefaultChangeList(list); if (myIgnoredIdeaLevel.isEmpty()) { final String name = myProject.getName(); myIgnoredIdeaLevel.add(IgnoredBeanFactory.ignoreFile(name + WorkspaceFileType.DOT_DEFAULT_EXTENSION, myProject)); myIgnoredIdeaLevel.add(IgnoredBeanFactory.ignoreFile(Project.DIRECTORY_STORE_FOLDER + "/workspace.xml", myProject)); } } if (!Registry.is("ide.hide.excluded.files") && !myExcludedConvertedToIgnored) { convertExcludedToIgnored(); myExcludedConvertedToIgnored = true; } } } }); } void convertExcludedToIgnored() { for (DirectoryIndexExcludePolicy policy : DirectoryIndexExcludePolicy.EP_NAME.getExtensions(myProject)) { for (VirtualFile file : policy.getExcludeRootsForProject()) { addDirectoryToIgnoreImplicitly(file.getPath()); } } for (Module module : ModuleManager.getInstance(myProject).getModules()) { for (String url : ModuleRootManager.getInstance(module).getExcludeRootUrls()) { addDirectoryToIgnoreImplicitly(VfsUtilCore.urlToPath(url)); } } } public void projectClosed() { ProjectLevelVcsManager.getInstance(myProject).removeVcsListener(myVcsListener); synchronized (myDataLock) { if (myUpdateChangesProgressIndicator != null) { myUpdateChangesProgressIndicator.cancel(); } } myUpdater.stop(); myConflictTracker.stopTracking(); } @NotNull @NonNls public String getComponentName() { return "ChangeListManager"; } public void initComponent() { } public void disposeComponent() { } /** * update itself might produce actions done on AWT thread (invoked-after), * so waiting for its completion on AWT thread is not good runnable is invoked on AWT thread */ public void invokeAfterUpdate(final Runnable afterUpdate, final InvokeAfterUpdateMode mode, @Nullable final String title, @Nullable final ModalityState state) { myUpdater.invokeAfterUpdate(afterUpdate, mode, title, null, state); } public void invokeAfterUpdate(final Runnable afterUpdate, final InvokeAfterUpdateMode mode, final String title, final Consumer dirtyScopeManagerFiller, final ModalityState state) { myUpdater.invokeAfterUpdate(afterUpdate, mode, title, dirtyScopeManagerFiller, state); } static class DisposedException extends RuntimeException {} public void freeze(final ContinuationPause context, final String reason) { myUpdater.setIgnoreBackgroundOperation(true); // this update is nessesary for git, to refresh local changes before invokeAfterUpdate(new Runnable() { @Override public void run() { freezeImmediately(reason); context.ping(); } }, InvokeAfterUpdateMode.SILENT_CALLBACK_POOLED, "", ModalityState.NON_MODAL); context.suspend(); } @Override public void freezeImmediately(@Nullable String reason) { myUpdater.setIgnoreBackgroundOperation(false); myUpdater.pause(); myFreezeName.set(reason); } @Override public void letGo() { myUpdater.go(); myFreezeName.set(null); } public String isFreezed() { return myFreezeName.get(); } public void scheduleUpdate() { myUpdater.schedule(); } public void scheduleUpdate(boolean updateUnversionedFiles) { myUpdater.schedule(); } private class ActualUpdater implements Runnable { @Override public void run() { updateImmediately(); } } private void filterOutIgnoredFiles(final List scopes) { final Set refreshFiles = new HashSet(); try { synchronized (myDataLock) { final IgnoredFilesHolder fileHolder = (IgnoredFilesHolder)myComposite.get(FileHolder.HolderType.IGNORED); for (Iterator iterator = scopes.iterator(); iterator.hasNext(); ) { final VcsModifiableDirtyScope scope = (VcsModifiableDirtyScope)iterator.next(); final VcsDirtyScopeModifier modifier = scope.getModifier(); if (modifier != null) { fileHolder.notifyVcsStarted(scope.getVcs()); final Iterator filesIterator = modifier.getDirtyFilesIterator(); while (filesIterator.hasNext()) { final FilePath dirtyFile = filesIterator.next(); if ((dirtyFile.getVirtualFile() != null) && isIgnoredFile(dirtyFile.getVirtualFile())) { filesIterator.remove(); fileHolder.addFile(dirtyFile.getVirtualFile()); refreshFiles.add(dirtyFile.getVirtualFile()); } } final Collection roots = modifier.getAffectedVcsRoots(); for (VirtualFile root : roots) { final Iterator dirIterator = modifier.getDirtyDirectoriesIterator(root); while (dirIterator.hasNext()) { final FilePath dir = dirIterator.next(); if ((dir.getVirtualFile() != null) && isIgnoredFile(dir.getVirtualFile())) { dirIterator.remove(); fileHolder.addFile(dir.getVirtualFile()); refreshFiles.add(dir.getVirtualFile()); } } } modifier.recheckDirtyKeys(); if (scope.isEmpty()) { iterator.remove(); } } } } } catch (Exception ex) { LOG.error(ex); } catch (AssertionError ex) { LOG.error(ex); } for (VirtualFile file : refreshFiles) { myFileStatusManager.fileStatusChanged(file); } } private void updateImmediately() { final DataHolder dataHolder; final ProjectLevelVcsManager vcsManager = ProjectLevelVcsManager.getInstance(myProject); if (!vcsManager.hasActiveVcss()) return; final VcsInvalidated invalidated = myDirtyScopeManager.retrieveScopes(); if (checkScopeIsEmpty(invalidated)) return; final boolean wasEverythingDirty = invalidated.isEverythingDirty(); final List scopes = invalidated.getScopes(); try { checkIfDisposed(); // copy existsing data to objects that would be updated. // mark for "modifier" that update started (it would create duplicates of modification commands done by user during update; // after update of copies of objects is complete, it would apply the same modifications to copies.) synchronized (myDataLock) { dataHolder = new DataHolder((FileHolderComposite)myComposite.copy(), myWorker.copy(), wasEverythingDirty); myModifier.enterUpdate(); if (wasEverythingDirty) { myUpdateException = null; myAdditionalInfo = null; } } final String scopeInString = (!LOG.isDebugEnabled()) ? "" : StringUtil.join(scopes, new Function() { @Override public String fun(VcsDirtyScope scope) { return scope.toString(); } }, "->\n"); LOG.debug("refresh procedure started, everything = " + wasEverythingDirty + " dirty scope: " + scopeInString); dataHolder.notifyStart(); myChangesViewManager.scheduleRefresh(); myUpdateChangesProgressIndicator = createProgressIndicator(); iterateScopes(dataHolder, scopes, wasEverythingDirty); final boolean takeChanges = (myUpdateException == null); if (takeChanges) { // update IDEA-level ignored files updateIgnoredFiles(dataHolder.getComposite()); } clearCurrentRevisionsCache(invalidated); // for the case of project being closed we need a read action here -> to be more consistent ApplicationManager.getApplication().runReadAction(new Runnable() { public void run() { if (myProject.isDisposed()) { return; } synchronized (myDataLock) { // do same modifications to change lists as was done during update + do delayed notifications dataHolder.notifyEnd(); // should be applied for notifications to be delivered (they were delayed) - anyway whether we take changes or not myModifier.finishUpdate(dataHolder.getChangeListWorker()); // update member from copy if (takeChanges) { final ChangeListWorker oldWorker = myWorker; myWorker = dataHolder.getChangeListWorker(); myWorker.onAfterWorkerSwitch(oldWorker); myModifier.setWorker(myWorker); LOG.debug("refresh procedure finished, unversioned size: " + dataHolder.getComposite().getVFHolder(FileHolder.HolderType.UNVERSIONED).getSize() + "\n changes: " + myWorker); final boolean statusChanged = !myComposite.equals(dataHolder.getComposite()); myComposite = dataHolder.getComposite(); if (statusChanged) { myDelayedNotificator.getProxyDispatcher().unchangedFileStatusChanged(); } } myShowLocalChangesInvalidated = false; } } }); for (VcsDirtyScope scope : scopes) { AbstractVcs vcs = scope.getVcs(); if (vcs != null && vcs.isTrackingUnchangedContent()) { scope.iterateExistingInsideScope(new Processor() { @Override public boolean process(VirtualFile file) { LastUnchangedContentTracker.markUntouched(file); //todo what if it has become dirty again during update? return true; } }); } } myChangesViewManager.scheduleRefresh(); } catch (DisposedException e) { // OK, we're finishing all the stuff now. } catch (ProcessCanceledException e) { // OK, we're finishing all the stuff now. } catch (RuntimeInterruptedException ignore) { } catch (Exception ex) { LOG.error(ex); } catch (AssertionError ex) { LOG.error(ex); } finally { myDirtyScopeManager.changesProcessed(); synchronized (myDataLock) { myDelayedNotificator.getProxyDispatcher().changeListUpdateDone(); myChangesViewManager.scheduleRefresh(); } } } private boolean checkScopeIsAllIgnored(VcsInvalidated invalidated) { if (!invalidated.isEverythingDirty()) { filterOutIgnoredFiles(invalidated.getScopes()); if (invalidated.isEmpty()) { return true; } } return false; } private boolean checkScopeIsEmpty(VcsInvalidated invalidated) { if (invalidated == null || invalidated.isEmpty()) { // a hack here; but otherwise everything here should be refactored ;) if (invalidated != null && invalidated.isEmpty() && invalidated.isEverythingDirty()) { VcsDirtyScopeManager.getInstance(myProject).markEverythingDirty(); } return true; } return checkScopeIsAllIgnored(invalidated); } private void iterateScopes(DataHolder dataHolder, List scopes, boolean wasEverythingDirty) { final ChangeListManagerGate gate = dataHolder.getChangeListWorker().createSelfGate(); // do actual requests about file statuses Getter disposedGetter = new Getter() { @Override public Boolean get() { return myProject.isDisposed() || myUpdater.getIsStoppedGetter().get(); } }; final UpdatingChangeListBuilder builder = new UpdatingChangeListBuilder(dataHolder.getChangeListWorker(), dataHolder.getComposite(), disposedGetter, myIgnoredIdeaLevel, gate); for (final VcsDirtyScope scope : scopes) { myUpdateChangesProgressIndicator.checkCanceled(); final AbstractVcs vcs = scope.getVcs(); if (vcs == null) continue; scope.setWasEverythingDirty(wasEverythingDirty); final VcsModifiableDirtyScope adjustedScope = vcs.adjustDirtyScope((VcsModifiableDirtyScope)scope); myChangesViewManager.setBusy(true); dataHolder.notifyStartProcessingChanges(adjustedScope); actualUpdate(builder, adjustedScope, vcs, dataHolder, gate); if (myUpdateException != null) break; } synchronized (myDataLock) { if (myAdditionalInfo == null) { myAdditionalInfo = builder.getAdditionalInfo(); } } } private void clearCurrentRevisionsCache(final VcsInvalidated invalidated) { final ContentRevisionCache cache = ProjectLevelVcsManager.getInstance(myProject).getContentRevisionCache(); if (invalidated.isEverythingDirty()) { cache.clearAllCurrent(); } else { cache.clearScope(invalidated.getScopes()); } } private EmptyProgressIndicator createProgressIndicator() { return new EmptyProgressIndicator() { @Override public boolean isCanceled() { return myUpdater.isStopped(); } @Override public void checkCanceled() { checkIfDisposed(); } }; } private class DataHolder { private final boolean myWasEverythingDirty; final FileHolderComposite myComposite; final ChangeListWorker myChangeListWorker; private DataHolder(FileHolderComposite composite, ChangeListWorker changeListWorker, boolean wasEverythingDirty) { myComposite = composite; myChangeListWorker = changeListWorker; myWasEverythingDirty = wasEverythingDirty; } public void notifyStart() { if (myWasEverythingDirty) { myComposite.cleanAll(); myChangeListWorker.notifyStartProcessingChanges(null); } } public void notifyStartProcessingChanges(@NotNull final VcsModifiableDirtyScope scope) { if (!myWasEverythingDirty) { myComposite.cleanAndAdjustScope(scope); myChangeListWorker.notifyStartProcessingChanges(scope); } myComposite.notifyVcsStarted(scope.getVcs()); myChangeListWorker.notifyVcsStarted(scope.getVcs()); } public void notifyDoneProcessingChanges() { if (!myWasEverythingDirty) { myChangeListWorker.notifyDoneProcessingChanges(myDelayedNotificator.getProxyDispatcher()); } } public void notifyEnd() { if (myWasEverythingDirty) { myChangeListWorker.notifyDoneProcessingChanges(myDelayedNotificator.getProxyDispatcher()); } } public FileHolderComposite getComposite() { return myComposite; } public ChangeListWorker getChangeListWorker() { return myChangeListWorker; } } private void actualUpdate(final UpdatingChangeListBuilder builder, final VcsDirtyScope scope, final AbstractVcs vcs, final DataHolder dataHolder, final ChangeListManagerGate gate) { try { final ChangeProvider changeProvider = vcs.getChangeProvider(); if (changeProvider != null) { final FoldersCutDownWorker foldersCutDownWorker = new FoldersCutDownWorker(); try { builder.setCurrent(scope, foldersCutDownWorker); changeProvider.getChanges(scope, builder, myUpdateChangesProgressIndicator, gate); } catch (final VcsException e) { handleUpdateException(e); } } } catch (ProcessCanceledException ignore) { } catch (Throwable t) { LOG.debug(t); Rethrow.reThrowRuntime(t); } finally { if (!myUpdater.isStopped()) { dataHolder.notifyDoneProcessingChanges(); } } } private void handleUpdateException(final VcsException e) { LOG.info(e); if (e instanceof VcsConnectionProblem) { ApplicationManager.getApplication().invokeLater(new Runnable() { @Override public void run() { ((VcsConnectionProblem)e).attemptQuickFix(false); } }); } if (myUpdateException == null) { if (ApplicationManager.getApplication().isUnitTestMode()) { AbstractVcsHelper helper = AbstractVcsHelper.getInstance(myProject); if (helper instanceof AbstractVcsHelperImpl && ((AbstractVcsHelperImpl)helper).handleCustom(e)) { return; } //noinspection CallToPrintStackTrace e.printStackTrace(); } myUpdateException = e; } } private void checkIfDisposed() { if (myUpdater.isStopped()) throw new DisposedException(); } public static boolean isUnder(final Change change, final VcsDirtyScope scope) { final ContentRevision before = change.getBeforeRevision(); final ContentRevision after = change.getAfterRevision(); return before != null && scope.belongsTo(before.getFile()) || after != null && scope.belongsTo(after.getFile()); } public List getChangeListsCopy() { synchronized (myDataLock) { return myWorker.getListsCopy(); } } /** * @deprecated this method made equivalent to {@link #getChangeListsCopy()} so to don't be confused by method name, * better use {@link #getChangeListsCopy()} */ @NotNull public List getChangeLists() { synchronized (myDataLock) { return getChangeListsCopy(); } } public List getAffectedPaths() { synchronized (myDataLock) { return myWorker.getAffectedPaths(); } } @NotNull public List getAffectedFiles() { synchronized (myDataLock) { return myWorker.getAffectedFiles(); } } @NotNull public Collection getAllChanges() { synchronized (myDataLock) { return myWorker.getAllChanges(); } } public List getUnversionedFiles() { synchronized (myDataLock) { return myComposite.getVFHolder(FileHolder.HolderType.UNVERSIONED).getFiles(); } } Couple getUnversionedFilesSize() { synchronized (myDataLock) { final VirtualFileHolder holder = myComposite.getVFHolder(FileHolder.HolderType.UNVERSIONED); return Couple.of(holder.getSize(), holder.getNumDirs()); } } @Override public List getModifiedWithoutEditing() { synchronized (myDataLock) { return myComposite.getVFHolder(FileHolder.HolderType.MODIFIED_WITHOUT_EDITING).getFiles(); } } /** * @return only roots for ignored folders, and ignored files */ List getIgnoredFiles() { synchronized (myDataLock) { return new ArrayList(myComposite.getIgnoredFileHolder().values()); } } public List getLockedFolders() { synchronized (myDataLock) { return myComposite.getVFHolder(FileHolder.HolderType.LOCKED).getFiles(); } } Map getLogicallyLockedFolders() { synchronized (myDataLock) { return new HashMap( ((LogicallyLockedHolder)myComposite.get(FileHolder.HolderType.LOGICALLY_LOCKED)).getMap()); } } public boolean isLogicallyLocked(final VirtualFile file) { synchronized (myDataLock) { return ((LogicallyLockedHolder)myComposite.get(FileHolder.HolderType.LOGICALLY_LOCKED)).containsKey(file); } } public boolean isContainedInLocallyDeleted(final FilePath filePath) { synchronized (myDataLock) { return myWorker.isContainedInLocallyDeleted(filePath); } } public List getDeletedFiles() { synchronized (myDataLock) { return myWorker.getLocallyDeleted().getFiles(); } } MultiMap getSwitchedFilesMap() { synchronized (myDataLock) { return myWorker.getSwitchedHolder().getBranchToFileMap(); } } @Nullable Map getSwitchedRoots() { synchronized (myDataLock) { return ((SwitchedFileHolder)myComposite.get(FileHolder.HolderType.ROOT_SWITCH)).getFilesMapCopy(); } } public VcsException getUpdateException() { synchronized (myDataLock) { return myUpdateException; } } public Factory getAdditionalUpdateInfo() { synchronized (myDataLock) { return myAdditionalInfo; } } public boolean isFileAffected(final VirtualFile file) { synchronized (myDataLock) { return myWorker.getStatus(file) != null; } } @Nullable public LocalChangeList findChangeList(final String name) { synchronized (myDataLock) { return myWorker.getCopyByName(name); } } @Override public LocalChangeList getChangeList(String id) { synchronized (myDataLock) { return myWorker.getChangeList(id); } } public LocalChangeList addChangeList(@NotNull final String name, @Nullable final String comment) { return addChangeList(name, comment, null); } @Override public LocalChangeList addChangeList(@NotNull final String name, @Nullable final String comment, @Nullable final Object data) { return ApplicationManager.getApplication().runReadAction(new Computable() { @Override public LocalChangeList compute() { synchronized (myDataLock) { final LocalChangeList changeList = myModifier.addChangeList(name, comment, data); myChangesViewManager.scheduleRefresh(); return changeList; } } }); } public void removeChangeList(final String name) { ApplicationManager.getApplication().runReadAction(new Runnable() { @Override public void run() { synchronized (myDataLock) { myModifier.removeChangeList(name); myChangesViewManager.scheduleRefresh(); } } }); } public void removeChangeList(LocalChangeList list) { removeChangeList(list.getName()); } /** * does no modification to change lists, only notification is sent */ @NotNull public Runnable prepareForChangeDeletion(final Collection changes) { final Map lists = new HashMap(); final Map> map; synchronized (myDataLock) { map = myWorker.listsForChanges(changes, lists); } return new Runnable() { public void run() { final ChangeListListener multicaster = myDelayedNotificator.getProxyDispatcher(); ApplicationManager.getApplication().runReadAction(new Runnable() { @Override public void run() { synchronized (myDataLock) { for (Map.Entry> entry : map.entrySet()) { final List changes = entry.getValue(); for (Iterator iterator = changes.iterator(); iterator.hasNext(); ) { final Change change = iterator.next(); if (getChangeList(change) != null) { // was not actually rolled back iterator.remove(); } } multicaster.changesRemoved(changes, lists.get(entry.getKey())); } for (String listName : map.keySet()) { final LocalChangeList byName = myWorker.getCopyByName(listName); if (byName != null && byName.getChanges().isEmpty() && !byName.isDefault() && !byName.isReadOnly()) { myWorker.removeChangeList(listName); } } } } }); } }; } public void setDefaultChangeList(@NotNull final LocalChangeList list) { ApplicationManager.getApplication().runReadAction(new Runnable() { @Override public void run() { synchronized (myDataLock) { myModifier.setDefault(list.getName()); } } }); myChangesViewManager.scheduleRefresh(); } @Nullable public LocalChangeList getDefaultChangeList() { synchronized (myDataLock) { return myWorker.getDefaultListCopy(); } } @Override public boolean isDefaultChangeList(ChangeList list) { return list instanceof LocalChangeList && myWorker.isDefaultList((LocalChangeList)list); } @NotNull public Collection getInvolvedListsFilterChanges(final Collection changes, final List validChanges) { synchronized (myDataLock) { return myWorker.getInvolvedListsFilterChanges(changes, validChanges); } } @Nullable public LocalChangeList getChangeList(@NotNull Change change) { synchronized (myDataLock) { return myWorker.listForChange(change); } } @Override public String getChangeListNameIfOnlyOne(final Change[] changes) { synchronized (myDataLock) { return myWorker.listNameIfOnlyOne(changes); } } /** * @deprecated better use normal comparison, with equals */ @Nullable public LocalChangeList getIdentityChangeList(Change change) { synchronized (myDataLock) { final List lists = myWorker.getListsCopy(); for (LocalChangeList list : lists) { for (Change oldChange : list.getChanges()) { if (oldChange == change) { return list; } } } return null; } } @Override public boolean isInUpdate() { synchronized (myDataLock) { return myModifier.isInsideUpdate() || myShowLocalChangesInvalidated; } } @Nullable public Change getChange(@NotNull VirtualFile file) { synchronized (myDataLock) { final LocalChangeList list = myWorker.getListCopy(file); if (list != null) { for (Change change : list.getChanges()) { final ContentRevision afterRevision = change.getAfterRevision(); if (afterRevision != null) { String revisionPath = FileUtil.toSystemIndependentName(afterRevision.getFile().getIOFile().getPath()); if (FileUtil.pathsEqual(revisionPath, file.getPath())) return change; } final ContentRevision beforeRevision = change.getBeforeRevision(); if (beforeRevision != null) { String revisionPath = FileUtil.toSystemIndependentName(beforeRevision.getFile().getIOFile().getPath()); if (FileUtil.pathsEqual(revisionPath, file.getPath())) return change; } } } return null; } } @Override public LocalChangeList getChangeList(@NotNull VirtualFile file) { synchronized (myDataLock) { return myWorker.getListCopy(file); } } @Nullable public Change getChange(final FilePath file) { synchronized (myDataLock) { return myWorker.getChangeForPath(file); } } public boolean isUnversioned(VirtualFile file) { synchronized (myDataLock) { return myComposite.getVFHolder(FileHolder.HolderType.UNVERSIONED).containsFile(file); } } @NotNull public FileStatus getStatus(VirtualFile file) { synchronized (myDataLock) { if (myComposite.getVFHolder(FileHolder.HolderType.UNVERSIONED).containsFile(file)) return FileStatus.UNKNOWN; if (myComposite.getVFHolder(FileHolder.HolderType.MODIFIED_WITHOUT_EDITING).containsFile(file)) return FileStatus.HIJACKED; if (myComposite.getIgnoredFileHolder().containsFile(file)) return FileStatus.IGNORED; final boolean switched = myWorker.isSwitched(file); final FileStatus status = myWorker.getStatus(file); if (status != null) { return FileStatus.NOT_CHANGED.equals(status) && switched ? FileStatus.SWITCHED : status; } if (switched) return FileStatus.SWITCHED; return FileStatus.NOT_CHANGED; } } @NotNull public Collection getChangesIn(VirtualFile dir) { return getChangesIn(new FilePathImpl(dir)); } @NotNull @Override public ThreeState haveChangesUnder(@NotNull final VirtualFile vf) { if (!vf.isValid() || !vf.isDirectory()) return ThreeState.NO; synchronized (myDataLock) { return myWorker.haveChangesUnder(vf); } } @NotNull public Collection getChangesIn(final FilePath dirPath) { synchronized (myDataLock) { return myWorker.getChangesIn(dirPath); } } public void moveChangesTo(final LocalChangeList list, final Change... changes) { ApplicationManager.getApplication().runReadAction(new Runnable() { @Override public void run() { synchronized (myDataLock) { myModifier.moveChangesTo(list.getName(), changes); } } }); myChangesViewManager.scheduleRefresh(); } @Override public void addUnversionedFiles(final LocalChangeList list, @NotNull final List files) { addUnversionedFiles(list, files, new Condition() { @Override public boolean value(FileStatus status) { return status == FileStatus.UNKNOWN; } }); } // TODO this is for quick-fix for GitAdd problem. To be removed after proper fix // (which should introduce something like VcsAddRemoveEnvironment) @Deprecated public void addUnversionedFiles(final LocalChangeList list, @NotNull final List files, final Condition statusChecker) { final List exceptions = new ArrayList(); final Set allProcessedFiles = new HashSet(); ChangesUtil.processVirtualFilesByVcs(myProject, files, new ChangesUtil.PerVcsProcessor() { public void process(final AbstractVcs vcs, final List items) { final CheckinEnvironment environment = vcs.getCheckinEnvironment(); if (environment != null) { final Set descendant = new HashSet(); for (VirtualFile item : items) { final Processor addProcessor = new Processor() { @Override public boolean process(VirtualFile file) { if (statusChecker.value(getStatus(file))) { descendant.add(file); } return true; } }; VcsRootIterator.iterateVfUnderVcsRoot(myProject, item, addProcessor); } final List result = environment.scheduleUnversionedFilesForAddition(new ArrayList(descendant)); allProcessedFiles.addAll(descendant); if (result != null) { exceptions.addAll(result); } } } }); if (exceptions.size() > 0) { StringBuilder message = new StringBuilder(VcsBundle.message("error.adding.files.prompt")); for (VcsException ex : exceptions) { message.append("\n").append(ex.getMessage()); } Messages.showErrorDialog(myProject, message.toString(), VcsBundle.message("error.adding.files.title")); } for (VirtualFile file : allProcessedFiles) { myFileStatusManager.fileStatusChanged(file); } VcsDirtyScopeManager.getInstance(myProject).filesDirty(allProcessedFiles, null); if (!list.isDefault()) { // find the changes for the added files and move them to the necessary changelist invokeAfterUpdate(new Runnable() { public void run() { ApplicationManager.getApplication().runReadAction(new Runnable() { @Override public void run() { synchronized (myDataLock) { List changesToMove = new ArrayList(); final LocalChangeList defaultList = getDefaultChangeList(); for (Change change : defaultList.getChanges()) { final ContentRevision afterRevision = change.getAfterRevision(); if (afterRevision != null) { VirtualFile vFile = afterRevision.getFile().getVirtualFile(); if (allProcessedFiles.contains(vFile)) { changesToMove.add(change); } } } if (changesToMove.size() > 0) { moveChangesTo(list, changesToMove.toArray(new Change[changesToMove.size()])); } } } }); myChangesViewManager.scheduleRefresh(); } }, InvokeAfterUpdateMode.BACKGROUND_NOT_CANCELLABLE_NOT_AWT, VcsBundle.message("change.lists.manager.add.unversioned"), null); } else { myChangesViewManager.scheduleRefresh(); } } public Project getProject() { return myProject; } public void addChangeListListener(ChangeListListener listener) { myListeners.addListener(listener); } public void removeChangeListListener(ChangeListListener listener) { myListeners.removeListener(listener); } public void registerCommitExecutor(CommitExecutor executor) { myExecutors.add(executor); } public void commitChanges(LocalChangeList changeList, List changes) { doCommit(changeList, changes, false); } private boolean doCommit(final LocalChangeList changeList, final List changes, final boolean synchronously) { FileDocumentManager.getInstance().saveAllDocuments(); return new CommitHelper(myProject, changeList, changes, changeList.getName(), StringUtil.isEmpty(changeList.getComment()) ? changeList.getName() : changeList.getComment(), new ArrayList(), false, synchronously, NullableFunction.NULL, null).doCommit(); } public void commitChangesSynchronously(LocalChangeList changeList, List changes) { doCommit(changeList, changes, true); } public boolean commitChangesSynchronouslyWithResult(final LocalChangeList changeList, final List changes) { return doCommit(changeList, changes, true); } @SuppressWarnings({"unchecked"}) public void readExternal(Element element) throws InvalidDataException { if (!myProject.isDefault()) { synchronized (myDataLock) { myIgnoredIdeaLevel.clear(); new ChangeListManagerSerialization(myIgnoredIdeaLevel, myWorker).readExternal(element); if ((!myWorker.isEmpty()) && getDefaultChangeList() == null) { setDefaultChangeList(myWorker.getListsCopy().get(0)); } } myExcludedConvertedToIgnored = Boolean.parseBoolean(JDOMExternalizerUtil.readField(element, EXCLUDED_CONVERTED_TO_IGNORED_OPTION)); myConflictTracker.loadState(element); } } public void writeExternal(Element element) throws WriteExternalException { if (!myProject.isDefault()) { final IgnoredFilesComponent ignoredFilesComponent; final ChangeListWorker worker; synchronized (myDataLock) { ignoredFilesComponent = new IgnoredFilesComponent(myIgnoredIdeaLevel); worker = myWorker.copy(); } new ChangeListManagerSerialization(ignoredFilesComponent, worker).writeExternal(element); if (myExcludedConvertedToIgnored) { JDOMExternalizerUtil.writeField(element, EXCLUDED_CONVERTED_TO_IGNORED_OPTION, String.valueOf(true)); } myConflictTracker.saveState(element); } } // used in TeamCity public void reopenFiles(List paths) { final ReadonlyStatusHandlerImpl readonlyStatusHandler = (ReadonlyStatusHandlerImpl)ReadonlyStatusHandlerImpl.getInstance(myProject); final boolean savedOption = readonlyStatusHandler.getState().SHOW_DIALOG; readonlyStatusHandler.getState().SHOW_DIALOG = false; try { readonlyStatusHandler.ensureFilesWritable(collectFiles(paths)); } finally { readonlyStatusHandler.getState().SHOW_DIALOG = savedOption; } } public List getRegisteredExecutors() { return Collections.unmodifiableList(myExecutors); } private static class MyDirtyFilesScheduler { private final static int ourPiecesLimit = 100; final List myFiles = new ArrayList(); final List myDirs = new ArrayList(); private boolean myEveryThing; private int myCnt; private final Project myProject; private MyDirtyFilesScheduler(final Project project) { myProject = project; myCnt = 0; myEveryThing = false; } public void accept(final Collection coll) { for (VirtualFile vf : coll) { if (myCnt > ourPiecesLimit) { myEveryThing = true; break; } if (vf.isDirectory()) { myDirs.add(vf); } else { myFiles.add(vf); } ++myCnt; } } public void arise() { final VcsDirtyScopeManager vcsDirtyScopeManager = VcsDirtyScopeManager.getInstance(myProject); if (myEveryThing) { vcsDirtyScopeManager.markEverythingDirty(); } else { vcsDirtyScopeManager.filesDirty(myFiles, myDirs); } } } public void addFilesToIgnore(final IgnoredFileBean... filesToIgnore) { myIgnoredIdeaLevel.add(filesToIgnore); scheduleUnversionedUpdate(); } @Override public void addDirectoryToIgnoreImplicitly(@NotNull String path) { myIgnoredIdeaLevel.addIgnoredDirectoryImplicitly(path, myProject); } public IgnoredFilesComponent getIgnoredFilesComponent() { return myIgnoredIdeaLevel; } private void scheduleUnversionedUpdate() { final MyDirtyFilesScheduler scheduler = new MyDirtyFilesScheduler(myProject); synchronized (myDataLock) { final VirtualFileHolder unversionedHolder = myComposite.getVFHolder(FileHolder.HolderType.UNVERSIONED); final IgnoredFilesHolder ignoredHolder = (IgnoredFilesHolder)myComposite.get(FileHolder.HolderType.IGNORED); scheduler.accept(unversionedHolder.getFiles()); scheduler.accept(ignoredHolder.values()); } scheduler.arise(); } public void setFilesToIgnore(final IgnoredFileBean... filesToIgnore) { myIgnoredIdeaLevel.set(filesToIgnore); scheduleUnversionedUpdate(); } private void updateIgnoredFiles(final FileHolderComposite composite) { final VirtualFileHolder vfHolder = composite.getVFHolder(FileHolder.HolderType.UNVERSIONED); final List unversionedFiles = vfHolder.getFiles(); exchangeWithIgnored(composite, vfHolder, unversionedFiles); final VirtualFileHolder vfModifiedHolder = composite.getVFHolder(FileHolder.HolderType.MODIFIED_WITHOUT_EDITING); final List modifiedFiles = vfModifiedHolder.getFiles(); exchangeWithIgnored(composite, vfModifiedHolder, modifiedFiles); } private void exchangeWithIgnored(FileHolderComposite composite, VirtualFileHolder vfHolder, List unversionedFiles) { for (VirtualFile file : unversionedFiles) { if (isIgnoredFile(file)) { vfHolder.removeFile(file); composite.getIgnoredFileHolder().addFile(file); } } } public IgnoredFileBean[] getFilesToIgnore() { return myIgnoredIdeaLevel.getFilesToIgnore(); } public boolean isIgnoredFile(@NotNull VirtualFile file) { return myIgnoredIdeaLevel.isIgnoredFile(file); } @Nullable public String getSwitchedBranch(final VirtualFile file) { synchronized (myDataLock) { return myWorker.getBranchForFile(file); } } @Override public String getDefaultListName() { synchronized (myDataLock) { return myWorker.getDefaultListName(); } } private static VirtualFile[] collectFiles(final List paths) { final ArrayList result = new ArrayList(); for (FilePath path : paths) { if (path.getVirtualFile() != null) { result.add(path.getVirtualFile()); } } return VfsUtil.toVirtualFileArray(result); } public boolean setReadOnly(final String name, final boolean value) { return ApplicationManager.getApplication().runReadAction(new Computable() { @Override public Boolean compute() { synchronized (myDataLock) { final boolean result = myModifier.setReadOnly(name, value); myChangesViewManager.scheduleRefresh(); return result; } } }); } public boolean editName(@NotNull final String fromName, @NotNull final String toName) { return ApplicationManager.getApplication().runReadAction(new Computable() { @Override public Boolean compute() { synchronized (myDataLock) { final boolean result = myModifier.editName(fromName, toName); myChangesViewManager.scheduleRefresh(); return result; } } }); } public String editComment(@NotNull final String fromName, final String newComment) { return ApplicationManager.getApplication().runReadAction(new Computable() { @Override public String compute() { synchronized (myDataLock) { final String oldComment = myModifier.editComment(fromName, newComment); myChangesViewManager.scheduleRefresh(); return oldComment; } } }); } @TestOnly public void waitUntilRefreshed() { myVfsListener.flushDirt(); myUpdater.waitUntilRefreshed(); waitUpdateAlarm(); } // this is for perforce tests to ensure that LastSuccessfulUpdateTracker receives the event it needs private static void waitUpdateAlarm() { final Semaphore semaphore = new Semaphore(); semaphore.down(); ourUpdateAlarm.get().execute(new Runnable() { @Override public void run() { semaphore.up(); } }); semaphore.waitFor(); } public void stopEveryThingIfInTestMode() { assert ApplicationManager.getApplication().isUnitTestMode(); ourUpdateAlarm.get().shutdownNow(); ourUpdateAlarm.set(createChangeListExecutor()); } public void forceGoInTestMode() { assert ApplicationManager.getApplication().isUnitTestMode(); myUpdater.forceGo(); } public void executeOnUpdaterThread(Runnable r) { ourUpdateAlarm.get().execute(r); } @TestOnly public boolean ensureUpToDate(final boolean canBeCanceled) { if (ApplicationManager.getApplication().isDispatchThread()) { updateImmediately(); return true; } myVfsListener.flushDirt(); final EnsureUpToDateFromNonAWTThread worker = new EnsureUpToDateFromNonAWTThread(myProject); worker.execute(); myUpdater.waitUntilRefreshed(); waitUpdateAlarm(); return worker.isDone(); } @Override public int getChangeListsNumber() { synchronized (myDataLock) { return myWorker.getChangeListsNumber(); } } // only a light attempt to show that some dirty scope request is asynchronously coming // for users to see changes are not valid // (commit -> asynch synch VFS -> asynch vcs dirty scope) public void showLocalChangesInvalidated() { synchronized (myDataLock) { myShowLocalChangesInvalidated = true; } } public ChangelistConflictTracker getConflictTracker() { return myConflictTracker; } private static class MyChangesDeltaForwarder implements PlusMinusModify { private RemoteRevisionsCache myRevisionsCache; private final ProjectLevelVcsManager myVcsManager; private final Project myProject; private final AtomicReference myService; public MyChangesDeltaForwarder(final Project project, final AtomicReference service) { myProject = project; myService = service; myRevisionsCache = RemoteRevisionsCache.getInstance(project); myVcsManager = ProjectLevelVcsManager.getInstance(project); } @Override public void modify(final BaseRevision was, final BaseRevision become) { myService.get().submit(new Runnable() { public void run() { final AbstractVcs vcs = getVcs(was); if (vcs != null) { myRevisionsCache.plus(Pair.create(was.getPath(), vcs)); } // maybe define modify method? myProject.getMessageBus().syncPublisher(VcsAnnotationRefresher.LOCAL_CHANGES_CHANGED).dirty(become); } }); } public void plus(final BaseRevision baseRevision) { myService.get().submit(new Runnable() { public void run() { final AbstractVcs vcs = getVcs(baseRevision); if (vcs != null) { myRevisionsCache.plus(Pair.create(baseRevision.getPath(), vcs)); } myProject.getMessageBus().syncPublisher(VcsAnnotationRefresher.LOCAL_CHANGES_CHANGED).dirty(baseRevision); } }); } public void minus(final BaseRevision baseRevision) { myService.get().submit(new Runnable() { public void run() { final AbstractVcs vcs = getVcs(baseRevision); if (vcs != null) { myRevisionsCache.minus(Pair.create(baseRevision.getPath(), vcs)); } myProject.getMessageBus().syncPublisher(VcsAnnotationRefresher.LOCAL_CHANGES_CHANGED).dirty(baseRevision.getPath()); } }); } @Nullable private AbstractVcs getVcs(final BaseRevision baseRevision) { VcsKey vcsKey = baseRevision.getVcs(); if (vcsKey == null) { final String path = baseRevision.getPath(); vcsKey = findVcs(path); if (vcsKey == null) return null; } return myVcsManager.findVcsByName(vcsKey.getName()); } @Nullable private VcsKey findVcs(final String path) { // does not matter directory or not final VirtualFile vf = LocalFileSystem.getInstance().refreshAndFindFileByIoFile(new File(path)); if (vf == null) return null; final AbstractVcs vcs = myVcsManager.getVcsFor(vf); return vcs == null ? null : vcs.getKeyInstanceMethod(); } } public boolean isFreezedWithNotification(String modalTitle) { final String freezeReason = isFreezed(); if (freezeReason != null) { if (modalTitle != null) { Messages.showErrorDialog(myProject, freezeReason, modalTitle); } else { VcsBalloonProblemNotifier.showOverChangesView(myProject, freezeReason, MessageType.WARNING); } } return freezeReason != null; } }