| /* |
| * 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<ScheduledExecutorService> ourUpdateAlarm = new AtomicReference<ScheduledExecutorService>(); |
| 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<JComponent> myAdditionalInfo; |
| |
| private final EventDispatcher<ChangeListListener> myListeners = EventDispatcher.create(ChangeListListener.class); |
| |
| private final Object myDataLock = new Object(); |
| |
| private final List<CommitExecutor> myExecutors = new ArrayList<CommitExecutor>(); |
| |
| private final IgnoredFilesComponent myIgnoredIdeaLevel; |
| private boolean myExcludedConvertedToIgnored; |
| private ProgressIndicator myUpdateChangesProgressIndicator; |
| |
| public static final Topic<LocalChangeListsLoadedListener> LISTS_LOADED = new Topic<LocalChangeListsLoadedListener>( |
| "LOCAL_CHANGE_LISTS_LOADED", LocalChangeListsLoadedListener.class); |
| |
| private boolean myShowLocalChangesInvalidated; |
| private AtomicReference<String> 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<LocalChangeList> myListsToBeDeleted = new HashSet<LocalChangeList>(); |
| |
| 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<String>(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<LocalChangeList> lists) { |
| if (lists.isEmpty()) { |
| return false; |
| } |
| |
| final String question; |
| if (lists.size() == 1) { |
| question = String.format("<html>The empty changelist '%s' is no longer active.<br>Do you want to remove it?</html>", |
| StringUtil.first(lists.iterator().next().getName(), 30, true)); |
| } |
| else { |
| question = String.format("<html>Empty changelists<br/>%s are no longer active.<br>Do you want to remove them?</html>", |
| StringUtil.join(lists, new Function<LocalChangeList, String>() { |
| @Override |
| public String fun(LocalChangeList list) { |
| return StringUtil.first(list.getName(), 30, true); |
| } |
| }, "<br/>")); |
| } |
| |
| 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<LocalChangeList> 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<VcsDirtyScopeManager> 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<VcsDirtyScope> scopes) { |
| final Set<VirtualFile> refreshFiles = new HashSet<VirtualFile>(); |
| try { |
| synchronized (myDataLock) { |
| final IgnoredFilesHolder fileHolder = (IgnoredFilesHolder)myComposite.get(FileHolder.HolderType.IGNORED); |
| |
| for (Iterator<VcsDirtyScope> 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<FilePath> 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<VirtualFile> roots = modifier.getAffectedVcsRoots(); |
| for (VirtualFile root : roots) { |
| final Iterator<FilePath> 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<VcsDirtyScope> 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<VcsDirtyScope, String>() { |
| @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<VirtualFile>() { |
| @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<VcsDirtyScope> scopes, boolean wasEverythingDirty) { |
| final ChangeListManagerGate gate = dataHolder.getChangeListWorker().createSelfGate(); |
| // do actual requests about file statuses |
| Getter<Boolean> disposedGetter = new Getter<Boolean>() { |
| @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<LocalChangeList> 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<LocalChangeList> getChangeLists() { |
| synchronized (myDataLock) { |
| return getChangeListsCopy(); |
| } |
| } |
| |
| public List<File> getAffectedPaths() { |
| synchronized (myDataLock) { |
| return myWorker.getAffectedPaths(); |
| } |
| } |
| |
| @NotNull |
| public List<VirtualFile> getAffectedFiles() { |
| synchronized (myDataLock) { |
| return myWorker.getAffectedFiles(); |
| } |
| } |
| |
| @NotNull |
| public Collection<Change> getAllChanges() { |
| synchronized (myDataLock) { |
| return myWorker.getAllChanges(); |
| } |
| } |
| |
| public List<VirtualFile> getUnversionedFiles() { |
| synchronized (myDataLock) { |
| return myComposite.getVFHolder(FileHolder.HolderType.UNVERSIONED).getFiles(); |
| } |
| } |
| |
| Couple<Integer> getUnversionedFilesSize() { |
| synchronized (myDataLock) { |
| final VirtualFileHolder holder = myComposite.getVFHolder(FileHolder.HolderType.UNVERSIONED); |
| return Couple.of(holder.getSize(), holder.getNumDirs()); |
| } |
| } |
| |
| @Override |
| public List<VirtualFile> getModifiedWithoutEditing() { |
| synchronized (myDataLock) { |
| return myComposite.getVFHolder(FileHolder.HolderType.MODIFIED_WITHOUT_EDITING).getFiles(); |
| } |
| } |
| |
| /** |
| * @return only roots for ignored folders, and ignored files |
| */ |
| List<VirtualFile> getIgnoredFiles() { |
| synchronized (myDataLock) { |
| return new ArrayList<VirtualFile>(myComposite.getIgnoredFileHolder().values()); |
| } |
| } |
| |
| public List<VirtualFile> getLockedFolders() { |
| synchronized (myDataLock) { |
| return myComposite.getVFHolder(FileHolder.HolderType.LOCKED).getFiles(); |
| } |
| } |
| |
| Map<VirtualFile, LogicalLock> getLogicallyLockedFolders() { |
| synchronized (myDataLock) { |
| return new HashMap<VirtualFile, LogicalLock>( |
| ((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<LocallyDeletedChange> getDeletedFiles() { |
| synchronized (myDataLock) { |
| return myWorker.getLocallyDeleted().getFiles(); |
| } |
| } |
| |
| MultiMap<String, VirtualFile> getSwitchedFilesMap() { |
| synchronized (myDataLock) { |
| return myWorker.getSwitchedHolder().getBranchToFileMap(); |
| } |
| } |
| |
| @Nullable |
| Map<VirtualFile, String> getSwitchedRoots() { |
| synchronized (myDataLock) { |
| return ((SwitchedFileHolder)myComposite.get(FileHolder.HolderType.ROOT_SWITCH)).getFilesMapCopy(); |
| } |
| } |
| |
| public VcsException getUpdateException() { |
| synchronized (myDataLock) { |
| return myUpdateException; |
| } |
| } |
| |
| public Factory<JComponent> 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<LocalChangeList>() { |
| @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<Change> changes) { |
| final Map<String, LocalChangeList> lists = new HashMap<String, LocalChangeList>(); |
| final Map<String, List<Change>> 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<String, List<Change>> entry : map.entrySet()) { |
| final List<Change> changes = entry.getValue(); |
| for (Iterator<Change> 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<LocalChangeList> getInvolvedListsFilterChanges(final Collection<Change> changes, final List<Change> 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<LocalChangeList> 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<Change> 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<Change> 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<VirtualFile> files) { |
| addUnversionedFiles(list, files, new Condition<FileStatus>() { |
| @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<VirtualFile> files, |
| final Condition<FileStatus> statusChecker) { |
| final List<VcsException> exceptions = new ArrayList<VcsException>(); |
| final Set<VirtualFile> allProcessedFiles = new HashSet<VirtualFile>(); |
| ChangesUtil.processVirtualFilesByVcs(myProject, files, new ChangesUtil.PerVcsProcessor<VirtualFile>() { |
| public void process(final AbstractVcs vcs, final List<VirtualFile> items) { |
| final CheckinEnvironment environment = vcs.getCheckinEnvironment(); |
| if (environment != null) { |
| final Set<VirtualFile> descendant = new HashSet<VirtualFile>(); |
| for (VirtualFile item : items) { |
| final Processor<VirtualFile> addProcessor = new Processor<VirtualFile>() { |
| @Override |
| public boolean process(VirtualFile file) { |
| if (statusChecker.value(getStatus(file))) { |
| descendant.add(file); |
| } |
| return true; |
| } |
| }; |
| VcsRootIterator.iterateVfUnderVcsRoot(myProject, item, addProcessor); |
| } |
| final List<VcsException> result = environment.scheduleUnversionedFilesForAddition(new ArrayList<VirtualFile>(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<Change> changesToMove = new ArrayList<Change>(); |
| 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<Change> changes) { |
| doCommit(changeList, changes, false); |
| } |
| |
| private boolean doCommit(final LocalChangeList changeList, final List<Change> 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<CheckinHandler>(), false, synchronously, NullableFunction.NULL, null).doCommit(); |
| } |
| |
| public void commitChangesSynchronously(LocalChangeList changeList, List<Change> changes) { |
| doCommit(changeList, changes, true); |
| } |
| |
| public boolean commitChangesSynchronouslyWithResult(final LocalChangeList changeList, final List<Change> 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<FilePath> 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<CommitExecutor> getRegisteredExecutors() { |
| return Collections.unmodifiableList(myExecutors); |
| } |
| |
| private static class MyDirtyFilesScheduler { |
| private final static int ourPiecesLimit = 100; |
| final List<VirtualFile> myFiles = new ArrayList<VirtualFile>(); |
| final List<VirtualFile> myDirs = new ArrayList<VirtualFile>(); |
| 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<VirtualFile> 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<VirtualFile> unversionedFiles = vfHolder.getFiles(); |
| exchangeWithIgnored(composite, vfHolder, unversionedFiles); |
| |
| final VirtualFileHolder vfModifiedHolder = composite.getVFHolder(FileHolder.HolderType.MODIFIED_WITHOUT_EDITING); |
| final List<VirtualFile> modifiedFiles = vfModifiedHolder.getFiles(); |
| exchangeWithIgnored(composite, vfModifiedHolder, modifiedFiles); |
| } |
| |
| private void exchangeWithIgnored(FileHolderComposite composite, VirtualFileHolder vfHolder, List<VirtualFile> 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<FilePath> paths) { |
| final ArrayList<VirtualFile> result = new ArrayList<VirtualFile>(); |
| 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<Boolean>() { |
| @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<Boolean>() { |
| @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<String>() { |
| @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<BaseRevision> { |
| private RemoteRevisionsCache myRevisionsCache; |
| private final ProjectLevelVcsManager myVcsManager; |
| private final Project myProject; |
| private final AtomicReference<ScheduledExecutorService> myService; |
| |
| public MyChangesDeltaForwarder(final Project project, final AtomicReference<ScheduledExecutorService> 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; |
| } |
| } |