f8b4a9be29964544affd2d6fc318c7215cb5d5b1
[idea/community.git] / platform / vcs-impl / src / com / intellij / openapi / vcs / impl / ProjectLevelVcsManagerImpl.java
1 /*
2  * Copyright 2000-2016 JetBrains s.r.o.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.intellij.openapi.vcs.impl;
17
18 import com.intellij.execution.filters.TextConsoleBuilderFactory;
19 import com.intellij.execution.ui.ConsoleView;
20 import com.intellij.execution.ui.ConsoleViewContentType;
21 import com.intellij.ide.AppLifecycleListener;
22 import com.intellij.openapi.Disposable;
23 import com.intellij.openapi.actionSystem.ActionManager;
24 import com.intellij.openapi.actionSystem.ActionPlaces;
25 import com.intellij.openapi.actionSystem.ActionToolbar;
26 import com.intellij.openapi.actionSystem.DefaultActionGroup;
27 import com.intellij.openapi.application.ApplicationManager;
28 import com.intellij.openapi.application.ModalityState;
29 import com.intellij.openapi.components.*;
30 import com.intellij.openapi.diagnostic.Logger;
31 import com.intellij.openapi.editor.markup.TextAttributes;
32 import com.intellij.openapi.extensions.Extensions;
33 import com.intellij.openapi.progress.ProcessCanceledException;
34 import com.intellij.openapi.progress.ProgressManager;
35 import com.intellij.openapi.project.Project;
36 import com.intellij.openapi.project.ProjectManager;
37 import com.intellij.openapi.project.ProjectManagerAdapter;
38 import com.intellij.openapi.roots.FileIndexFacade;
39 import com.intellij.openapi.util.*;
40 import com.intellij.openapi.util.io.FileUtil;
41 import com.intellij.openapi.util.registry.Registry;
42 import com.intellij.openapi.util.text.StringUtil;
43 import com.intellij.openapi.vcs.*;
44 import com.intellij.openapi.vcs.changes.ChangesUtil;
45 import com.intellij.openapi.vcs.changes.VcsAnnotationLocalChangesListener;
46 import com.intellij.openapi.vcs.changes.VcsAnnotationLocalChangesListenerImpl;
47 import com.intellij.openapi.vcs.checkout.CompositeCheckoutListener;
48 import com.intellij.openapi.vcs.ex.ProjectLevelVcsManagerEx;
49 import com.intellij.openapi.vcs.history.VcsHistoryCache;
50 import com.intellij.openapi.vcs.impl.projectlevelman.*;
51 import com.intellij.openapi.vcs.roots.VcsRootScanner;
52 import com.intellij.openapi.vcs.update.ActionInfo;
53 import com.intellij.openapi.vcs.update.UpdateInfoTree;
54 import com.intellij.openapi.vcs.update.UpdatedFiles;
55 import com.intellij.openapi.vcs.update.UpdatedFilesListener;
56 import com.intellij.openapi.vfs.LocalFileSystem;
57 import com.intellij.openapi.vfs.VfsUtilCore;
58 import com.intellij.openapi.vfs.VirtualFile;
59 import com.intellij.openapi.wm.ToolWindow;
60 import com.intellij.openapi.wm.ToolWindowId;
61 import com.intellij.openapi.wm.ToolWindowManager;
62 import com.intellij.project.ProjectKt;
63 import com.intellij.ui.content.Content;
64 import com.intellij.ui.content.ContentFactory;
65 import com.intellij.ui.content.ContentManager;
66 import com.intellij.util.ContentUtilEx;
67 import com.intellij.util.Processor;
68 import com.intellij.util.containers.ContainerUtil;
69 import com.intellij.util.messages.MessageBusConnection;
70 import com.intellij.util.text.DateFormatUtil;
71 import org.jdom.Attribute;
72 import org.jdom.DataConversionException;
73 import org.jdom.Element;
74 import org.jetbrains.annotations.CalledInAwt;
75 import org.jetbrains.annotations.*;
76
77 import javax.swing.*;
78 import java.awt.*;
79 import java.util.*;
80 import java.util.List;
81
82 @State(name = "ProjectLevelVcsManager", storages = @Storage(StoragePathMacros.WORKSPACE_FILE))
83 public class ProjectLevelVcsManagerImpl extends ProjectLevelVcsManagerEx implements ProjectComponent, PersistentStateComponent<Element> {
84   private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.vcs.impl.ProjectLevelVcsManagerImpl");
85   @NonNls private static final String SETTINGS_EDITED_MANUALLY = "settingsEditedManually";
86
87   private final ProjectLevelVcsManagerSerialization mySerialization;
88   private final OptionsAndConfirmations myOptionsAndConfirmations;
89
90   private final NewMappings myMappings;
91   private final Project myProject;
92   private final MappingsToRoots myMappingsToRoots;
93
94   private ContentManager myContentManager;
95   private ConsoleView myConsole;
96   private final Disposable myConsoleDisposer = new Disposable() {
97     @Override
98     public void dispose() {
99       if (myConsole != null) {
100         Disposer.dispose(myConsole);
101         myConsole = null;
102       }
103     }
104   };
105
106   private final VcsInitialization myInitialization;
107
108   @NonNls private static final String ELEMENT_MAPPING = "mapping";
109   @NonNls private static final String ATTRIBUTE_DIRECTORY = "directory";
110   @NonNls private static final String ATTRIBUTE_VCS = "vcs";
111   @NonNls private static final String ATTRIBUTE_DEFAULT_PROJECT = "defaultProject";
112   @NonNls private static final String ELEMENT_ROOT_SETTINGS = "rootSettings";
113   @NonNls private static final String ATTRIBUTE_CLASS = "class";
114
115   private boolean myMappingsLoaded;
116   private boolean myHaveLegacyVcsConfiguration;
117   private final DefaultVcsRootPolicy myDefaultVcsRootPolicy;
118
119   private volatile int myBackgroundOperationCounter;
120
121   private final Set<ActionKey> myBackgroundRunningTasks = ContainerUtil.newHashSet();
122
123   private final List<Pair<String, ConsoleViewContentType>> myPendingOutput = ContainerUtil.newArrayList();
124
125   private final VcsHistoryCache myVcsHistoryCache;
126   private final ContentRevisionCache myContentRevisionCache;
127   private final FileIndexFacade myExcludedIndex;
128   private final VcsFileListenerContextHelper myVcsFileListenerContextHelper;
129   private final VcsAnnotationLocalChangesListenerImpl myAnnotationLocalChangesListener;
130
131   public ProjectLevelVcsManagerImpl(Project project,
132                                     final FileStatusManager manager,
133                                     final FileIndexFacade excludedFileIndex,
134                                     ProjectManager projectManager,
135                                     DefaultVcsRootPolicy defaultVcsRootPolicy,
136                                     VcsFileListenerContextHelper vcsFileListenerContextHelper) {
137     myProject = project;
138     mySerialization = new ProjectLevelVcsManagerSerialization();
139     myOptionsAndConfirmations = new OptionsAndConfirmations();
140
141     myDefaultVcsRootPolicy = defaultVcsRootPolicy;
142
143     myInitialization = new VcsInitialization(myProject);
144     Disposer.register(project, myInitialization); // wait for the thread spawned in VcsInitialization to terminate
145     projectManager.addProjectManagerListener(project, new ProjectManagerAdapter() {
146       @Override
147       public void projectClosing(Project project) {
148         Disposer.dispose(myInitialization);
149       }
150     });
151     if (project.isDefault()) {
152       // default project is disposed in write action, so treat it differently
153       MessageBusConnection connection = ApplicationManager.getApplication().getMessageBus().connect();
154       connection.subscribe(AppLifecycleListener.TOPIC, new AppLifecycleListener.Adapter() {
155         @Override
156         public void appClosing() {
157           Disposer.dispose(myInitialization);
158         }
159       });
160     }
161     myMappings = new NewMappings(myProject, this, manager);
162     myMappingsToRoots = new MappingsToRoots(myMappings, myProject);
163
164     myVcsHistoryCache = new VcsHistoryCache();
165     myContentRevisionCache = new ContentRevisionCache();
166     myVcsFileListenerContextHelper = vcsFileListenerContextHelper;
167     VcsListener vcsListener = () -> {
168       myVcsHistoryCache.clear();
169       myVcsFileListenerContextHelper.possiblySwitchActivation(hasActiveVcss());
170     };
171     myExcludedIndex = excludedFileIndex;
172     MessageBusConnection connection = myProject.getMessageBus().connect();
173     connection.subscribe(ProjectLevelVcsManager.VCS_CONFIGURATION_CHANGED, vcsListener);
174     connection.subscribe(ProjectLevelVcsManager.VCS_CONFIGURATION_CHANGED_IN_PLUGIN, vcsListener);
175     connection.subscribe(UpdatedFilesListener.UPDATED_FILES, myContentRevisionCache::clearCurrent);
176     myAnnotationLocalChangesListener = new VcsAnnotationLocalChangesListenerImpl(myProject, this);
177   }
178
179   @Override
180   public void initComponent() {
181     myOptionsAndConfirmations.init(mySerialization::getInitOptionValue);
182   }
183
184   public void registerVcs(AbstractVcs vcs) {
185     AllVcses.getInstance(myProject).registerManually(vcs);
186   }
187
188   @Override
189   @Nullable
190   public AbstractVcs findVcsByName(String name) {
191     if (name == null) return null;
192     AbstractVcs result = myProject.isDisposed() ? null : AllVcses.getInstance(myProject).getByName(name);
193     ProgressManager.checkCanceled();
194     return result;
195   }
196
197   @Override
198   @Nullable
199   public VcsDescriptor getDescriptor(final String name) {
200     if (name == null) return null;
201     if (myProject.isDisposed()) return null;
202     return AllVcses.getInstance(myProject).getDescriptor(name);
203   }
204
205   @Override
206   public void iterateVfUnderVcsRoot(VirtualFile file, Processor<VirtualFile> processor) {
207     VcsRootIterator.iterateVfUnderVcsRoot(myProject, file, processor);
208   }
209
210   @Override
211   public VcsDescriptor[] getAllVcss() {
212     return AllVcses.getInstance(myProject).getAll();
213   }
214
215   public boolean haveVcses() {
216     return !AllVcses.getInstance(myProject).isEmpty();
217   }
218
219   @Override
220   public void disposeComponent() {
221     releaseConsole();
222     myMappings.disposeMe();
223     Disposer.dispose(myAnnotationLocalChangesListener);
224     myContentManager = null;
225
226     ToolWindowManager toolWindowManager = ToolWindowManager.getInstance(myProject);
227     if (toolWindowManager != null && toolWindowManager.getToolWindow(ToolWindowId.VCS) != null) {
228       toolWindowManager.unregisterToolWindow(ToolWindowId.VCS);
229     }
230   }
231
232   @NotNull
233   @Override
234   public VcsAnnotationLocalChangesListener getAnnotationLocalChangesListener() {
235     return myAnnotationLocalChangesListener;
236   }
237
238   @Override
239   public void projectOpened() {
240     addInitializationRequest(VcsInitObject.AFTER_COMMON, () -> {
241       if (!ApplicationManager.getApplication().isUnitTestMode()) {
242         VcsRootChecker[] checkers = Extensions.getExtensions(VcsRootChecker.EXTENSION_POINT_NAME);
243         if (checkers.length != 0) {
244           VcsRootScanner.start(myProject, checkers);
245         }
246       }
247     });
248   }
249
250   @Override
251   public void projectClosed() {
252     releaseConsole();
253   }
254
255   @Override
256   @NotNull
257   public String getComponentName() {
258     return "ProjectLevelVcsManager";
259   }
260
261   @Override
262   public boolean checkAllFilesAreUnder(AbstractVcs abstractVcs, VirtualFile[] files) {
263     if (files == null) return false;
264     for (VirtualFile file : files) {
265       if (getVcsFor(file) != abstractVcs) {
266         return false;
267       }
268     }
269     return true;
270   }
271
272   @Override
273   @Nullable
274   public AbstractVcs getVcsFor(@NotNull VirtualFile file) {
275     final String vcsName = myMappings.getVcsFor(file);
276     if (vcsName == null || vcsName.isEmpty()) {
277       return null;
278     }
279     return AllVcses.getInstance(myProject).getByName(vcsName);
280   }
281
282   @Override
283   @Nullable
284   public AbstractVcs getVcsFor(final FilePath file) {
285     final VirtualFile vFile = ChangesUtil.findValidParentAccurately(file);
286     return ApplicationManager.getApplication().runReadAction((Computable<AbstractVcs>)() -> {
287       if (!ApplicationManager.getApplication().isUnitTestMode() && !myProject.isInitialized()) return null;
288       if (myProject.isDisposed()) throw new ProcessCanceledException();
289       if (vFile != null) {
290         return getVcsFor(vFile);
291       }
292       return null;
293     });
294   }
295
296   @Override
297   @Nullable
298   public VirtualFile getVcsRootFor(@Nullable final VirtualFile file) {
299     final VcsDirectoryMapping mapping = myMappings.getMappingFor(file);
300     if (mapping == null) {
301       return null;
302     }
303     final String directory = mapping.getDirectory();
304     if (directory.isEmpty()) {
305       return myDefaultVcsRootPolicy.getVcsRootFor(file);
306     }
307     return LocalFileSystem.getInstance().findFileByPath(directory);
308   }
309
310   @Override
311   @Nullable
312   public VcsRoot getVcsRootObjectFor(final VirtualFile file) {
313     final VcsDirectoryMapping mapping = myMappings.getMappingFor(file);
314     if (mapping == null) {
315       return null;
316     }
317     final String directory = mapping.getDirectory();
318     final AbstractVcs vcs = findVcsByName(mapping.getVcs());
319     if (directory.isEmpty()) {
320       return new VcsRoot(vcs, myDefaultVcsRootPolicy.getVcsRootFor(file));
321     }
322     return new VcsRoot(vcs, LocalFileSystem.getInstance().findFileByPath(directory));
323   }
324
325   @Override
326   @Nullable
327   public VirtualFile getVcsRootFor(final FilePath file) {
328     if (myProject.isDisposed()) return null;
329     VirtualFile vFile = ChangesUtil.findValidParentAccurately(file);
330     if (vFile != null) {
331       return getVcsRootFor(vFile);
332     }
333     return null;
334   }
335
336   @Override
337   public VcsRoot getVcsRootObjectFor(final FilePath file) {
338     if (myProject.isDisposed()) {
339       return null;
340     }
341     VirtualFile vFile = ChangesUtil.findValidParentAccurately(file);
342     if (vFile != null) {
343       return getVcsRootObjectFor(vFile);
344     }
345     return null;
346   }
347
348   public void unregisterVcs(@NotNull AbstractVcs vcs) {
349     if (!ApplicationManager.getApplication().isUnitTestMode() && myMappings.haveActiveVcs(vcs.getName())) {
350       // unlikely
351       LOG.warn("Active vcs '" + vcs.getName() + "' is being unregistered. Remove from mappings first.");
352     }
353     myMappings.beingUnregistered(vcs.getName());
354     AllVcses.getInstance(myProject).unregisterManually(vcs);
355   }
356
357   @Override
358   public ContentManager getContentManager() {
359     if (myContentManager == null) {
360       ToolWindow changes = ToolWindowManager.getInstance(myProject).getToolWindow(ToolWindowId.VCS);
361       myContentManager = changes == null ? null : changes.getContentManager();
362     }
363     return myContentManager;
364   }
365
366   @Override
367   public boolean checkVcsIsActive(AbstractVcs vcs) {
368     return checkVcsIsActive(vcs.getName());
369   }
370
371   @Override
372   public boolean checkVcsIsActive(final String vcsName) {
373     return myMappings.haveActiveVcs(vcsName);
374   }
375
376   @Override
377   public AbstractVcs[] getAllActiveVcss() {
378     return myMappings.getActiveVcses();
379   }
380
381   @Override
382   public boolean hasActiveVcss() {
383     return myMappings.hasActiveVcss();
384   }
385
386   @Override
387   public boolean hasAnyMappings() {
388     return !myMappings.isEmpty();
389   }
390
391   @Deprecated
392   @Override
393   public void addMessageToConsoleWindow(final String message, final TextAttributes attributes) {
394     addMessageToConsoleWindow(message, new ConsoleViewContentType("", attributes));
395   }
396
397   @Override
398   public void addMessageToConsoleWindow(@Nullable final String message, @NotNull final ConsoleViewContentType contentType) {
399     if (!Registry.is("vcs.showConsole")) {
400       return;
401     }
402     if (StringUtil.isEmptyOrSpaces(message)) {
403       return;
404     }
405
406     ApplicationManager.getApplication().invokeLater(() -> {
407       // for default and disposed projects the ContentManager is not available.
408       if (myProject.isDisposed() || myProject.isDefault()) return;
409       final ContentManager contentManager = getContentManager();
410       if (contentManager == null) {
411         myPendingOutput.add(Pair.create(message, contentType));
412       }
413       else {
414         getOrCreateConsoleContent(contentManager);
415         printToConsole(message, contentType);
416       }
417     }, ModalityState.defaultModalityState());
418   }
419
420   private Content getOrCreateConsoleContent(final ContentManager contentManager) {
421     final String displayName = VcsBundle.message("vcs.console.toolwindow.display.name");
422     Content content = contentManager.findContent(displayName);
423     if (content == null) {
424       releaseConsole();
425
426       myConsole = TextConsoleBuilderFactory.getInstance().createBuilder(myProject).getConsole();
427
428       JPanel panel = new JPanel(new BorderLayout());
429       panel.add(myConsole.getComponent(), BorderLayout.CENTER);
430
431       ActionToolbar toolbar = ActionManager.getInstance()
432         .createActionToolbar(ActionPlaces.UNKNOWN, new DefaultActionGroup(myConsole.createConsoleActions()), false);
433       panel.add(toolbar.getComponent(), BorderLayout.WEST);
434
435       content = ContentFactory.SERVICE.getInstance().createContent(panel, displayName, true);
436       content.setDisposer(myConsoleDisposer);
437       contentManager.addContent(content);
438
439       for (Pair<String, ConsoleViewContentType> pair : myPendingOutput) {
440         printToConsole(pair.first, pair.second);
441       }
442       myPendingOutput.clear();
443     }
444     return content;
445   }
446
447   private void printToConsole(@NotNull String message, @NotNull ConsoleViewContentType contentType) {
448     myConsole.print(message + "\n", contentType);
449   }
450
451   private void releaseConsole() {
452     Disposer.dispose(myConsoleDisposer);
453   }
454
455   @Override
456   @NotNull
457   public VcsShowSettingOption getOptions(VcsConfiguration.StandardOption option) {
458     return myOptionsAndConfirmations.getOptions(option);
459   }
460
461   @Override
462   public List<VcsShowOptionsSettingImpl> getAllOptions() {
463     return myOptionsAndConfirmations.getAllOptions();
464   }
465
466   @Override
467   @NotNull
468   public VcsShowSettingOption getStandardOption(@NotNull VcsConfiguration.StandardOption option, @NotNull AbstractVcs vcs) {
469     final VcsShowOptionsSettingImpl options = (VcsShowOptionsSettingImpl)getOptions(option);
470     options.addApplicableVcs(vcs);
471     return options;
472   }
473
474   @Override
475   @NotNull
476   public VcsShowSettingOption getOrCreateCustomOption(@NotNull String vcsActionName, @NotNull AbstractVcs vcs) {
477     return myOptionsAndConfirmations.getOrCreateCustomOption(vcsActionName, vcs);
478   }
479
480   @Override
481   public void showProjectOperationInfo(final UpdatedFiles updatedFiles, String displayActionName) {
482     showUpdateProjectInfo(updatedFiles, displayActionName, ActionInfo.STATUS, false);
483   }
484
485   @Override
486   public UpdateInfoTree showUpdateProjectInfo(UpdatedFiles updatedFiles, String displayActionName, ActionInfo actionInfo, boolean canceled) {
487     if (!myProject.isOpen() || myProject.isDisposed()) return null;
488     ContentManager contentManager = getContentManager();
489     if (contentManager == null) {
490       return null;  // content manager is made null during dispose; flag is set later
491     }
492     final UpdateInfoTree updateInfoTree = new UpdateInfoTree(contentManager, myProject, updatedFiles, displayActionName, actionInfo);
493     ContentUtilEx.addTabbedContent(contentManager, updateInfoTree, "Update Info", DateFormatUtil.formatDateTime(System.currentTimeMillis()), true, updateInfoTree);
494     ToolWindowManager.getInstance(myProject).getToolWindow(ToolWindowId.VCS).activate(null);
495     updateInfoTree.expandRootChildren();
496     return updateInfoTree;
497   }
498
499   public void cleanupMappings() {
500     myMappings.cleanupMappings();
501   }
502
503   @Override
504   public List<VcsDirectoryMapping> getDirectoryMappings() {
505     return myMappings.getDirectoryMappings();
506   }
507
508   @Override
509   public List<VcsDirectoryMapping> getDirectoryMappings(final AbstractVcs vcs) {
510     return myMappings.getDirectoryMappings(vcs.getName());
511   }
512
513   @Override
514   @Nullable
515   public VcsDirectoryMapping getDirectoryMappingFor(final FilePath path) {
516     VirtualFile vFile = ChangesUtil.findValidParentAccurately(path);
517     if (vFile != null) {
518       return myMappings.getMappingFor(vFile);
519     }
520     return null;
521   }
522
523   private boolean hasExplicitMapping(final VirtualFile vFile) {
524     final VcsDirectoryMapping mapping = myMappings.getMappingFor(vFile);
525     return mapping != null && !mapping.isDefaultMapping();
526   }
527
528   @Override
529   public void setDirectoryMapping(final String path, final String activeVcsName) {
530     if (myMappingsLoaded) return;            // ignore per-module VCS settings if the mapping table was loaded from .ipr
531     myHaveLegacyVcsConfiguration = true;
532     myMappings.setMapping(FileUtil.toSystemIndependentName(path), activeVcsName);
533   }
534
535   public void setAutoDirectoryMapping(String path, String activeVcsName) {
536     final List<VirtualFile> defaultRoots = myMappings.getDefaultRoots();
537     if (defaultRoots.size() == 1 && StringUtil.isEmpty(myMappings.haveDefaultMapping())) {
538       myMappings.removeDirectoryMapping(new VcsDirectoryMapping("", ""));
539     }
540     myMappings.setMapping(path, activeVcsName);
541   }
542
543   public void removeDirectoryMapping(VcsDirectoryMapping mapping) {
544     myMappings.removeDirectoryMapping(mapping);
545   }
546
547   @Override
548   public void setDirectoryMappings(final List<VcsDirectoryMapping> items) {
549     myHaveLegacyVcsConfiguration = true;
550     myMappings.setDirectoryMappings(items);
551   }
552
553   @Override
554   public void iterateVcsRoot(final VirtualFile root, final Processor<FilePath> iterator) {
555     VcsRootIterator.iterateVcsRoot(myProject, root, iterator);
556   }
557
558   @Override
559   public void iterateVcsRoot(VirtualFile root,
560                              Processor<FilePath> iterator,
561                              @Nullable VirtualFileFilter directoryFilter) {
562     VcsRootIterator.iterateVcsRoot(myProject, root, iterator, directoryFilter);
563   }
564
565   @Nullable
566   @Override
567   public Element getState() {
568     Element element = new Element("state");
569     mySerialization.writeExternalUtil(element, myOptionsAndConfirmations);
570     if (myHaveLegacyVcsConfiguration) {
571       element.setAttribute(SETTINGS_EDITED_MANUALLY, "true");
572     }
573     return element;
574   }
575
576   @Override
577   public void loadState(Element state) {
578     mySerialization.readExternalUtil(state, myOptionsAndConfirmations);
579     final Attribute attribute = state.getAttribute(SETTINGS_EDITED_MANUALLY);
580     if (attribute != null) {
581       try {
582         myHaveLegacyVcsConfiguration = attribute.getBooleanValue();
583       }
584       catch (DataConversionException ignored) {
585       }
586     }
587   }
588
589   @Override
590   @NotNull
591   public VcsShowConfirmationOption getStandardConfirmation(@NotNull VcsConfiguration.StandardConfirmation option,
592                                                            AbstractVcs vcs) {
593     final VcsShowConfirmationOptionImpl result = getConfirmation(option);
594     if (vcs != null) {
595       result.addApplicableVcs(vcs);
596     }
597     return result;
598   }
599
600   @Override
601   public List<VcsShowConfirmationOptionImpl> getAllConfirmations() {
602     return myOptionsAndConfirmations.getAllConfirmations();
603   }
604
605   @Override
606   @NotNull
607   public VcsShowConfirmationOptionImpl getConfirmation(VcsConfiguration.StandardConfirmation option) {
608     return myOptionsAndConfirmations.getConfirmation(option);
609   }
610
611   private final Map<VcsListener, MessageBusConnection> myAdapters = new HashMap<>();
612
613   @Override
614   public void addVcsListener(VcsListener listener) {
615     MessageBusConnection connection = myProject.getMessageBus().connect();
616     connection.subscribe(VCS_CONFIGURATION_CHANGED, listener);
617     myAdapters.put(listener, connection);
618   }
619
620   @Override
621   public void removeVcsListener(VcsListener listener) {
622     final MessageBusConnection connection = myAdapters.remove(listener);
623     if (connection != null) {
624       connection.disconnect();
625     }
626   }
627
628   @Override
629   public void startBackgroundVcsOperation() {
630     myBackgroundOperationCounter++;
631   }
632
633   @Override
634   public void stopBackgroundVcsOperation() {
635     // in fact, the condition is "should not be called under ApplicationManager.invokeLater() and similar"
636     assert !ApplicationManager.getApplication().isDispatchThread() || ApplicationManager.getApplication().isUnitTestMode();
637     LOG.assertTrue(myBackgroundOperationCounter > 0, "myBackgroundOperationCounter > 0");
638     myBackgroundOperationCounter--;
639   }
640
641   @Override
642   public boolean isBackgroundVcsOperationRunning() {
643     return myBackgroundOperationCounter > 0;
644   }
645
646   @Override
647   public List<VirtualFile> getRootsUnderVcsWithoutFiltering(final AbstractVcs vcs) {
648     return myMappings.getMappingsAsFilesUnderVcs(vcs);
649   }
650
651   @Override
652   @NotNull
653   public VirtualFile[] getRootsUnderVcs(@NotNull AbstractVcs vcs) {
654     return myMappingsToRoots.getRootsUnderVcs(vcs);
655   }
656
657   @Override
658   public List<VirtualFile> getDetailedVcsMappings(final AbstractVcs vcs) {
659     return myMappingsToRoots.getDetailedVcsMappings(vcs);
660   }
661
662   @Override
663   public VirtualFile[] getAllVersionedRoots() {
664     List<VirtualFile> vFiles = new ArrayList<>();
665     final AbstractVcs[] vcses = myMappings.getActiveVcses();
666     for (AbstractVcs vcs : vcses) {
667       Collections.addAll(vFiles, getRootsUnderVcs(vcs));
668     }
669     return VfsUtilCore.toVirtualFileArray(vFiles);
670   }
671
672   @Override
673   @NotNull
674   public VcsRoot[] getAllVcsRoots() {
675     List<VcsRoot> vcsRoots = new ArrayList<>();
676     final AbstractVcs[] vcses = myMappings.getActiveVcses();
677     for (AbstractVcs vcs : vcses) {
678       final VirtualFile[] roots = getRootsUnderVcs(vcs);
679       for (VirtualFile root : roots) {
680         vcsRoots.add(new VcsRoot(vcs, root));
681       }
682     }
683     return vcsRoots.toArray(new VcsRoot[vcsRoots.size()]);
684   }
685
686   @Override
687   public void updateActiveVcss() {
688     // not needed
689   }
690
691   @Override
692   public void notifyDirectoryMappingChanged() {
693     myProject.getMessageBus().syncPublisher(VCS_CONFIGURATION_CHANGED).directoryMappingChanged();
694   }
695
696   void readDirectoryMappings(final Element element) {
697     myMappings.clear();
698
699     final List<VcsDirectoryMapping> mappingsList = new ArrayList<>();
700     boolean haveNonEmptyMappings = false;
701     for (Element child : element.getChildren(ELEMENT_MAPPING)) {
702       final String vcs = child.getAttributeValue(ATTRIBUTE_VCS);
703       if (vcs != null && !vcs.isEmpty()) {
704         haveNonEmptyMappings = true;
705       }
706       VcsDirectoryMapping mapping = new VcsDirectoryMapping(child.getAttributeValue(ATTRIBUTE_DIRECTORY), vcs);
707       mappingsList.add(mapping);
708
709       Element rootSettingsElement = child.getChild(ELEMENT_ROOT_SETTINGS);
710       if (rootSettingsElement != null) {
711         String className = rootSettingsElement.getAttributeValue(ATTRIBUTE_CLASS);
712         AbstractVcs vcsInstance = findVcsByName(mapping.getVcs());
713         if (vcsInstance != null && className != null) {
714           final VcsRootSettings rootSettings = vcsInstance.createEmptyVcsRootSettings();
715           if (rootSettings != null) {
716             try {
717               rootSettings.readExternal(rootSettingsElement);
718               mapping.setRootSettings(rootSettings);
719             }
720             catch (InvalidDataException e) {
721               LOG.error("Failed to load VCS root settings class " + className + " for VCS " + vcsInstance.getClass().getName(), e);
722             }
723           }
724         }
725       }
726     }
727     boolean defaultProject = Boolean.TRUE.toString().equals(element.getAttributeValue(ATTRIBUTE_DEFAULT_PROJECT));
728     // run autodetection if there's no VCS in default project and 
729     if (haveNonEmptyMappings || !defaultProject) {
730       myMappingsLoaded = true;
731     }
732     myMappings.setDirectoryMappings(mappingsList);
733   }
734
735   void writeDirectoryMappings(@NotNull Element element) {
736     if (myProject.isDefault()) {
737       element.setAttribute(ATTRIBUTE_DEFAULT_PROJECT, Boolean.TRUE.toString());
738     }
739     for (VcsDirectoryMapping mapping : getDirectoryMappings()) {
740       VcsRootSettings rootSettings = mapping.getRootSettings();
741       if (rootSettings == null && StringUtil.isEmpty(mapping.getDirectory()) && StringUtil.isEmpty(mapping.getVcs())) {
742         continue;
743       }
744
745       Element child = new Element(ELEMENT_MAPPING);
746       child.setAttribute(ATTRIBUTE_DIRECTORY, mapping.getDirectory());
747       child.setAttribute(ATTRIBUTE_VCS, mapping.getVcs());
748       if (rootSettings != null) {
749         Element rootSettingsElement = new Element(ELEMENT_ROOT_SETTINGS);
750         rootSettingsElement.setAttribute(ATTRIBUTE_CLASS, rootSettings.getClass().getName());
751         try {
752           rootSettings.writeExternal(rootSettingsElement);
753           child.addContent(rootSettingsElement);
754         }
755         catch (WriteExternalException e) {
756           // don't add element
757         }
758       }
759       element.addContent(child);
760     }
761   }
762
763   public boolean needAutodetectMappings() {
764     return !myHaveLegacyVcsConfiguration && !myMappingsLoaded;
765   }
766
767   /**
768    * Used to guess VCS for automatic mapping through a look into a working copy
769    */
770   @Override
771   @Nullable
772   public AbstractVcs findVersioningVcs(VirtualFile file) {
773     final VcsDescriptor[] vcsDescriptors = getAllVcss();
774     VcsDescriptor probableVcs = null;
775     for (VcsDescriptor vcsDescriptor : vcsDescriptors) {
776       if (vcsDescriptor.probablyUnderVcs(file)) {
777         if (probableVcs != null) {
778           return null;
779         }
780         probableVcs = vcsDescriptor;
781       }
782     }
783     return probableVcs == null ? null : findVcsByName(probableVcs.getName());
784   }
785
786   @Override
787   public CheckoutProvider.Listener getCompositeCheckoutListener() {
788     return new CompositeCheckoutListener(myProject);
789   }
790
791   @Override
792   @Nullable
793   public VcsEventsListenerManager getVcsEventsListenerManager() {
794     return null;
795   }
796
797   @Override
798   public void fireDirectoryMappingsChanged() {
799     if (myProject.isOpen() && !myProject.isDisposed()) {
800       myMappings.mappingsChanged();
801     }
802   }
803
804   @Override
805   public String haveDefaultMapping() {
806     return myMappings.haveDefaultMapping();
807   }
808
809   /**
810    * @deprecated {@link BackgroundableActionLock}
811    */
812   @Deprecated
813   public BackgroundableActionEnabledHandler getBackgroundableActionHandler(final VcsBackgroundableActions action) {
814     ApplicationManager.getApplication().assertIsDispatchThread();
815     return new BackgroundableActionEnabledHandler(myProject, action);
816   }
817
818   @CalledInAwt
819   boolean isBackgroundTaskRunning(@NotNull Object... keys) {
820     ApplicationManager.getApplication().assertIsDispatchThread();
821     return myBackgroundRunningTasks.contains(new ActionKey(keys));
822   }
823
824   @CalledInAwt
825   void startBackgroundTask(@NotNull Object... keys) {
826     ApplicationManager.getApplication().assertIsDispatchThread();
827     LOG.assertTrue(myBackgroundRunningTasks.add(new ActionKey(keys)));
828   }
829
830   @CalledInAwt
831   void stopBackgroundTask(@NotNull Object... keys) {
832     ApplicationManager.getApplication().assertIsDispatchThread();
833     LOG.assertTrue(myBackgroundRunningTasks.remove(new ActionKey(keys)));
834   }
835
836   public void addInitializationRequest(final VcsInitObject vcsInitObject, final Runnable runnable) {
837     ApplicationManager.getApplication().runReadAction(() -> myInitialization.add(vcsInitObject, runnable));
838   }
839
840   @Override
841   public boolean isFileInContent(@Nullable final VirtualFile vf) {
842     return ApplicationManager.getApplication().runReadAction((Computable<Boolean>)() ->
843       vf != null && (myExcludedIndex.isInContent(vf) || isFileInBaseDir(vf) || vf.equals(myProject.getBaseDir()) ||
844                      hasExplicitMapping(vf) || isInDirectoryBasedRoot(vf)
845                      || !Registry.is("ide.hide.excluded.files") && myExcludedIndex.isExcludedFile(vf))
846       && !isIgnored(vf));
847   }
848
849   @Override
850   public boolean isIgnored(VirtualFile vf) {
851     if (Registry.is("ide.hide.excluded.files")) {
852       return myExcludedIndex.isExcludedFile(vf);
853     }
854     else {
855       return myExcludedIndex.isUnderIgnored(vf);
856     }
857   }
858
859   private boolean isInDirectoryBasedRoot(@Nullable VirtualFile file) {
860     if (file != null && ProjectKt.isDirectoryBased(myProject)) {
861       return ProjectKt.getStateStore(myProject).isProjectFile(file);
862     }
863     return false;
864   }
865
866   private boolean isFileInBaseDir(final VirtualFile file) {
867     VirtualFile parent = file.getParent();
868     return !file.isDirectory() && parent != null && parent.equals(myProject.getBaseDir());
869   }
870
871   @Override
872   public VcsHistoryCache getVcsHistoryCache() {
873     return myVcsHistoryCache;
874   }
875
876   @Override
877   public ContentRevisionCache getContentRevisionCache() {
878     return myContentRevisionCache;
879   }
880
881   @TestOnly
882   public void waitForInitialized() {
883     myInitialization.waitForCompletion();
884   }
885
886   private static class ActionKey {
887     private final Object[] myObjects;
888
889     ActionKey(@NotNull Object... objects) {
890       myObjects = objects;
891     }
892
893     @Override
894     public final boolean equals(Object o) {
895       if (o == null || getClass() != o.getClass()) return false;
896       return Arrays.equals(myObjects, ((ActionKey)o).myObjects);
897     }
898
899     @Override
900     public final int hashCode() {
901       return Arrays.hashCode(myObjects);
902     }
903
904     @Override
905     public String toString() {
906       return getClass() + " - " + Arrays.toString(myObjects);
907     }
908   }
909 }