@Nullable
[idea/community.git] / platform / vcs-impl / src / com / intellij / openapi / vcs / impl / ProjectLevelVcsManagerImpl.java
1 /*
2  * Copyright 2000-2009 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.openapi.application.ApplicationManager;
19 import com.intellij.openapi.application.ModalityState;
20 import com.intellij.openapi.components.ProjectComponent;
21 import com.intellij.openapi.components.StorageScheme;
22 import com.intellij.openapi.diagnostic.Logger;
23 import com.intellij.openapi.editor.Editor;
24 import com.intellij.openapi.editor.EditorFactory;
25 import com.intellij.openapi.editor.EditorSettings;
26 import com.intellij.openapi.editor.markup.TextAttributes;
27 import com.intellij.openapi.progress.ProcessCanceledException;
28 import com.intellij.openapi.project.DumbAwareRunnable;
29 import com.intellij.openapi.project.Project;
30 import com.intellij.openapi.project.ex.ProjectEx;
31 import com.intellij.openapi.startup.StartupManager;
32 import com.intellij.openapi.util.*;
33 import com.intellij.openapi.util.io.FileUtil;
34 import com.intellij.openapi.util.registry.Registry;
35 import com.intellij.openapi.vcs.*;
36 import com.intellij.openapi.vcs.changes.ChangesUtil;
37 import com.intellij.openapi.vcs.checkout.CompositeCheckoutListener;
38 import com.intellij.openapi.vcs.ex.ProjectLevelVcsManagerEx;
39 import com.intellij.openapi.vcs.history.VcsHistoryCache;
40 import com.intellij.openapi.vcs.impl.projectlevelman.*;
41 import com.intellij.openapi.vcs.update.ActionInfo;
42 import com.intellij.openapi.vcs.update.UpdateInfoTree;
43 import com.intellij.openapi.vcs.update.UpdatedFiles;
44 import com.intellij.openapi.vfs.LocalFileSystem;
45 import com.intellij.openapi.vfs.VfsUtil;
46 import com.intellij.openapi.vfs.VirtualFile;
47 import com.intellij.openapi.wm.ToolWindow;
48 import com.intellij.openapi.wm.ToolWindowAnchor;
49 import com.intellij.openapi.wm.ToolWindowId;
50 import com.intellij.openapi.wm.ToolWindowManager;
51 import com.intellij.ui.content.Content;
52 import com.intellij.ui.content.ContentFactory;
53 import com.intellij.ui.content.ContentManager;
54 import com.intellij.util.ContentsUtil;
55 import com.intellij.util.Icons;
56 import com.intellij.util.Processor;
57 import com.intellij.util.containers.Convertor;
58 import com.intellij.util.messages.MessageBus;
59 import com.intellij.util.messages.MessageBusConnection;
60 import com.intellij.util.ui.EditorAdapter;
61 import org.jdom.Attribute;
62 import org.jdom.DataConversionException;
63 import org.jdom.Element;
64 import org.jetbrains.annotations.NonNls;
65 import org.jetbrains.annotations.NotNull;
66 import org.jetbrains.annotations.Nullable;
67
68 import javax.swing.*;
69 import java.awt.*;
70 import java.util.*;
71 import java.util.List;
72
73 public class ProjectLevelVcsManagerImpl extends ProjectLevelVcsManagerEx implements ProjectComponent, JDOMExternalizable {
74   private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.vcs.impl.ProjectLevelVcsManagerImpl");
75   public static final String SETTINGS_EDITED_MANUALLY = "settingsEditedManually";
76
77   private final ProjectLevelVcsManagerSerialization mySerialization;
78   private final OptionsAndConfirmations myOptionsAndConfirmations;
79
80   private final NewMappings myMappings;
81   private final Project myProject;
82   private final MessageBus myMessageBus;
83   private final MappingsToRoots myMappingsToRoots;
84
85   private ContentManager myContentManager;
86   private EditorAdapter myEditorAdapter;
87
88   private final VcsInitialization myInitialization;
89
90   @NonNls private static final String ELEMENT_MAPPING = "mapping";
91   @NonNls private static final String ATTRIBUTE_DIRECTORY = "directory";
92   @NonNls private static final String ATTRIBUTE_VCS = "vcs";
93   @NonNls private static final String ATTRIBUTE_DEFAULT_PROJECT = "defaultProject";
94   @NonNls private static final String ELEMENT_ROOT_SETTINGS = "rootSettings";
95   @NonNls private static final String ATTRIBUTE_CLASS = "class";
96
97   private boolean myMappingsLoaded = false;
98   private boolean myHaveLegacyVcsConfiguration = false;
99   private final DefaultVcsRootPolicy myDefaultVcsRootPolicy;
100
101   private volatile int myBackgroundOperationCounter = 0;
102
103   private final Map<VcsBackgroundableActions, BackgroundableActionEnabledHandler> myBackgroundableActionHandlerMap;
104
105   private final List<Pair<String, TextAttributes>> myPendingOutput = new ArrayList<Pair<String, TextAttributes>>();
106   private VcsEventsListenerManagerImpl myVcsEventListenerManager;
107
108   private final VcsHistoryCache myVcsHistoryCache;
109   private MessageBusConnection myConnect;
110   private VcsListener myVcsListener;
111   private final ExcludedFileIndex myExcludedIndex;
112
113   public ProjectLevelVcsManagerImpl(Project project, final FileStatusManager manager, MessageBus messageBus, final ExcludedFileIndex excludedFileIndex) {
114     myProject = project;
115     myMessageBus = messageBus;
116     mySerialization = new ProjectLevelVcsManagerSerialization();
117     myOptionsAndConfirmations = new OptionsAndConfirmations();
118
119     myDefaultVcsRootPolicy = DefaultVcsRootPolicy.getInstance(project);
120
121     myBackgroundableActionHandlerMap = new HashMap<VcsBackgroundableActions, BackgroundableActionEnabledHandler>();
122     myInitialization = new VcsInitialization(myProject);
123     myMappings = new NewMappings(myProject, myMessageBus, this, manager);
124     myMappingsToRoots = new MappingsToRoots(myMappings, myProject);
125
126     if (! myProject.isDefault()) {
127       myVcsEventListenerManager = new VcsEventsListenerManagerImpl();
128     }
129
130     myVcsHistoryCache = new VcsHistoryCache();
131     myConnect = myMessageBus.connect();
132     myVcsListener = new VcsListener() {
133       @Override
134       public void directoryMappingChanged() {
135         myVcsHistoryCache.clear();
136       }
137     };
138     myConnect.subscribe(ProjectLevelVcsManager.VCS_CONFIGURATION_CHANGED, myVcsListener);
139     myExcludedIndex = excludedFileIndex;
140   }
141
142   public void initComponent() {
143     myOptionsAndConfirmations.init(new Convertor<String, VcsShowConfirmationOption.Value>() {
144       public VcsShowConfirmationOption.Value convert(String o) {
145         return mySerialization.getInitOptionValue(o);
146       }
147     });
148   }
149
150   public void registerVcs(AbstractVcs vcs) {
151     AllVcses.getInstance(myProject).registerManually(vcs);
152   }
153
154   @Nullable
155   public AbstractVcs findVcsByName(String name) {
156     if (name == null) return null;
157     if (myProject.isDisposed()) return null;
158     return AllVcses.getInstance(myProject).getByName(name);
159   }
160
161   @Nullable
162   public VcsDescriptor getDescriptor(final String name) {
163     if (name == null) return null;
164     if (myProject.isDisposed()) return null;
165     return AllVcses.getInstance(myProject).getDescriptor(name);
166   }
167
168   public VcsDescriptor[] getAllVcss() {
169     return AllVcses.getInstance(myProject).getAll();
170   }
171
172   public boolean haveVcses() {
173     return ! AllVcses.getInstance(myProject).isEmpty();
174   }
175
176   public void disposeComponent() {
177     if (myEditorAdapter != null) {
178       final Editor editor = myEditorAdapter.getEditor();
179       if (! editor.isDisposed()) {
180         EditorFactory.getInstance().releaseEditor(editor);
181       }
182     }
183     myMappings.disposeMe();
184     myConnect.disconnect();
185     myContentManager = null;
186
187     ToolWindowManager toolWindowManager = ToolWindowManager.getInstance(myProject);
188     if (toolWindowManager != null && toolWindowManager.getToolWindow(ToolWindowId.VCS) != null) {
189       toolWindowManager.unregisterToolWindow(ToolWindowId.VCS);
190     }
191   }
192
193   public void projectOpened() {
194     final StartupManager manager = StartupManager.getInstance(myProject);
195     manager.registerPostStartupActivity(new DumbAwareRunnable() {
196       public void run() {
197         ToolWindowManager toolWindowManager = ToolWindowManager.getInstance(myProject);
198         if (toolWindowManager != null) { // Can be null in tests
199           ToolWindow toolWindow =
200             toolWindowManager.registerToolWindow(ToolWindowId.VCS, true, ToolWindowAnchor.BOTTOM, myProject, true);
201           myContentManager = toolWindow.getContentManager();
202           toolWindow.setIcon(Icons.VCS_SMALL_TAB);
203           toolWindow.installWatcher(myContentManager);
204         } else {
205           myContentManager = ContentFactory.SERVICE.getInstance().createContentManager(true, myProject);
206         }
207       }
208     });
209   }
210
211   public void projectClosed() {
212   }
213
214   @NotNull
215   public String getComponentName() {
216     return "ProjectLevelVcsManager";
217   }
218
219   public boolean checkAllFilesAreUnder(AbstractVcs abstractVcs, VirtualFile[] files) {
220     if (files == null) return false;
221     for (VirtualFile file : files) {
222       if (getVcsFor(file) != abstractVcs) {
223         return false;
224       }
225     }
226     return true;
227   }
228
229   @Nullable
230   public AbstractVcs getVcsFor(@NotNull VirtualFile file) {
231     final String vcsName = myMappings.getVcsFor(file);
232     if (vcsName == null || vcsName.length() == 0) {
233       return null;
234     }
235     return AllVcses.getInstance(myProject).getByName(vcsName);
236   }
237
238   @Nullable
239   public AbstractVcs getVcsFor(final FilePath file) {
240     return ApplicationManager.getApplication().runReadAction(new Computable<AbstractVcs>() {
241       @Nullable
242       public AbstractVcs compute() {
243         if (!ApplicationManager.getApplication().isUnitTestMode() && !myProject.isInitialized()) return null;
244         if (myProject.isDisposed()) throw new ProcessCanceledException();
245         VirtualFile vFile = ChangesUtil.findValidParent(file);
246         if (vFile != null) {
247           return getVcsFor(vFile);
248         }
249         return null;
250       }
251     });
252   }
253
254   @Nullable
255   public VirtualFile getVcsRootFor(final @Nullable VirtualFile file) {
256     final VcsDirectoryMapping mapping = myMappings.getMappingFor(file);
257     if (mapping == null) {
258       return null;
259     }
260     final String directory = mapping.getDirectory();
261     if (directory.length() == 0) {
262       return myDefaultVcsRootPolicy.getVcsRootFor(file);
263     }
264     return LocalFileSystem.getInstance().findFileByPath(directory);
265   }
266
267   @Nullable
268   public VcsRoot getVcsRootObjectFor(final VirtualFile file) {
269     final VcsDirectoryMapping mapping = myMappings.getMappingFor(file);
270     if (mapping == null) {
271       return null;
272     }
273     final String directory = mapping.getDirectory();
274     final AbstractVcs vcs = findVcsByName(mapping.getVcs());
275     if (directory.length() == 0) {
276       return new VcsRoot(vcs, myDefaultVcsRootPolicy.getVcsRootFor(file));
277     }
278     return new VcsRoot(vcs, LocalFileSystem.getInstance().findFileByPath(directory));
279   }
280
281   @Nullable
282   public VirtualFile getVcsRootFor(final FilePath file) {
283     return ApplicationManager.getApplication().runReadAction(new Computable<VirtualFile>() {
284       @Nullable
285       public VirtualFile compute() {
286         if (myProject.isDisposed()) return null;
287         VirtualFile vFile = ChangesUtil.findValidParent(file);
288         if (vFile != null) {
289           return getVcsRootFor(vFile);
290         }
291         return null;
292       }
293     });
294   }
295
296   @Override
297   public VcsRoot getVcsRootObjectFor(final FilePath file) {
298     return ApplicationManager.getApplication().runReadAction(new Computable<VcsRoot>() {
299       @Nullable
300       public VcsRoot compute() {
301         VirtualFile vFile = ChangesUtil.findValidParent(file);
302         if (vFile != null) {
303           return getVcsRootObjectFor(vFile);
304         }
305         return null;
306       }
307     });
308   }
309
310   public void unregisterVcs(AbstractVcs vcs) {
311     if (! ApplicationManager.getApplication().isUnitTestMode() && myMappings.haveActiveVcs(vcs.getName())) {
312       // unlikely
313       LOG.warn("Active vcs '" + vcs.getName() + "' is being unregistered. Remove from mappings first.");
314     }
315     myMappings.beingUnregistered(vcs.getName());
316     AllVcses.getInstance(myProject).unregisterManually(vcs);
317   }
318
319   public ContentManager getContentManager() {
320     return myContentManager;
321   }
322
323   public boolean checkVcsIsActive(AbstractVcs vcs) {
324     return checkVcsIsActive(vcs.getName());
325   }
326
327   public boolean checkVcsIsActive(final String vcsName) {
328     return myMappings.haveActiveVcs(vcsName);
329   }
330
331   public AbstractVcs[] getAllActiveVcss() {
332     return myMappings.getActiveVcses();
333   }
334
335   public boolean hasActiveVcss() {
336     return myMappings.hasActiveVcss();
337   }
338
339   public boolean hasAnyMappings() {
340     return ! myMappings.isEmpty();
341   }
342
343 public void addMessageToConsoleWindow(final String message, final TextAttributes attributes) {
344     if (!Registry.is("vcs.showConsole")) return;
345
346     ApplicationManager.getApplication().invokeLater(new Runnable() {
347       public void run() {
348         // for default and disposed projects the ContentManager is not available.
349         if (myProject.isDisposed() || myProject.isDefault()) return;
350         final ContentManager contentManager = getContentManager();
351         if (contentManager == null) {
352           myPendingOutput.add(new Pair<String, TextAttributes>(message, attributes));
353         }
354         else {
355           getOrCreateConsoleContent(contentManager);
356           myEditorAdapter.appendString(message, attributes);
357         }
358       }
359     }, ModalityState.defaultModalityState());
360   }
361
362   private Content getOrCreateConsoleContent(final ContentManager contentManager) {
363     final String displayName = VcsBundle.message("vcs.console.toolwindow.display.name");
364     Content content = contentManager.findContent(displayName);
365     if (content == null) {
366       final EditorFactory editorFactory = EditorFactory.getInstance();
367       final Editor editor = editorFactory.createViewer(editorFactory.createDocument(""), myProject);
368       EditorSettings editorSettings = editor.getSettings();
369       editorSettings.setLineMarkerAreaShown(false);
370       editorSettings.setIndentGuidesShown(false);
371       editorSettings.setLineNumbersShown(false);
372       editorSettings.setFoldingOutlineShown(false);
373
374       myEditorAdapter = new EditorAdapter(editor, myProject);
375       final JPanel panel = new JPanel(new BorderLayout());
376       panel.add(editor.getComponent(), BorderLayout.CENTER);
377
378       content = ContentFactory.SERVICE.getInstance().createContent(panel, displayName, true);
379       contentManager.addContent(content);
380
381       for (Pair<String, TextAttributes> pair : myPendingOutput) {
382         myEditorAdapter.appendString(pair.first, pair.second);
383       }
384       myPendingOutput.clear();
385     }
386     return content;
387   }
388
389   @NotNull
390   public VcsShowSettingOption getOptions(VcsConfiguration.StandardOption option) {
391     return myOptionsAndConfirmations.getOptions(option);
392   }
393
394   public List<VcsShowOptionsSettingImpl> getAllOptions() {
395     return myOptionsAndConfirmations.getAllOptions();
396   }
397
398   @NotNull
399   public VcsShowSettingOption getStandardOption(@NotNull VcsConfiguration.StandardOption option, @NotNull AbstractVcs vcs) {
400     final VcsShowOptionsSettingImpl options = (VcsShowOptionsSettingImpl) getOptions(option);
401     options.addApplicableVcs(vcs);
402     return options;
403   }
404
405   @NotNull
406   public VcsShowSettingOption getOrCreateCustomOption(@NotNull String vcsActionName, @NotNull AbstractVcs vcs) {
407     return myOptionsAndConfirmations.getOrCreateCustomOption(vcsActionName, vcs);
408   }
409
410   public void showProjectOperationInfo(final UpdatedFiles updatedFiles, String displayActionName) {
411     showUpdateProjectInfo(updatedFiles, displayActionName, ActionInfo.STATUS, false);
412   }
413
414   public UpdateInfoTree showUpdateProjectInfo(UpdatedFiles updatedFiles, String displayActionName, ActionInfo actionInfo, boolean canceled) {
415     if (! myProject.isOpen() || myProject.isDisposed()) return null;
416     ContentManager contentManager = getContentManager();
417     if (contentManager == null) {
418       return null;  // content manager is made null during dispose; flag is set later
419     }
420     final UpdateInfoTree updateInfoTree = new UpdateInfoTree(contentManager, myProject, updatedFiles, displayActionName, actionInfo);
421     Content content = ContentFactory.SERVICE.getInstance().createContent(updateInfoTree, canceled ?
422       VcsBundle.message("toolwindow.title.update.action.canceled.info", displayActionName) :
423       VcsBundle.message("toolwindow.title.update.action.info", displayActionName), true);
424     Disposer.register(content, updateInfoTree);
425     ContentsUtil.addContent(contentManager, content, true);
426     ToolWindowManager.getInstance(myProject).getToolWindow(ToolWindowId.VCS).activate(null);
427     updateInfoTree.expandRootChildren();
428     return updateInfoTree;
429   }
430
431   public void cleanupMappings() {
432     myMappings.cleanupMappings();
433   }
434
435   public List<VcsDirectoryMapping> getDirectoryMappings() {
436     return myMappings.getDirectoryMappings();
437   }
438
439   public List<VcsDirectoryMapping> getDirectoryMappings(final AbstractVcs vcs) {
440     return myMappings.getDirectoryMappings(vcs.getName());
441   }
442
443   @Nullable
444   public VcsDirectoryMapping getDirectoryMappingFor(final FilePath path) {
445     return ApplicationManager.getApplication().runReadAction(new Computable<VcsDirectoryMapping>() {
446       @Nullable
447       public VcsDirectoryMapping compute() {
448         VirtualFile vFile = ChangesUtil.findValidParent(path);
449         if (vFile != null) {
450           return myMappings.getMappingFor(vFile);
451         }
452         return null;
453       }
454     });
455   }
456
457   public boolean hasExplicitMapping(final FilePath f) {
458     VirtualFile vFile = ChangesUtil.findValidParent(f);
459     if (vFile == null) return false;
460     return hasExplicitMapping(vFile);
461   }
462
463   public boolean hasExplicitMapping(final VirtualFile vFile) {
464     final VcsDirectoryMapping mapping = myMappings.getMappingFor(vFile);
465     return mapping != null && ! mapping.isDefaultMapping();
466   }
467
468   public void setDirectoryMapping(final String path, final String activeVcsName) {
469     if (myMappingsLoaded) return;            // ignore per-module VCS settings if the mapping table was loaded from .ipr
470     myHaveLegacyVcsConfiguration = true;
471     myMappings.setMapping(FileUtil.toSystemIndependentName(path), activeVcsName);
472   }
473
474   public void setAutoDirectoryMapping(String path, String activeVcsName) {
475     final List<VirtualFile> defaultRoots = myMappings.getDefaultRoots();
476     if (defaultRoots.size() == 1 && "".equals(myMappings.haveDefaultMapping())) {
477       myMappings.removeDirectoryMapping(new VcsDirectoryMapping("", ""));
478     }
479     myMappings.setMapping(path, activeVcsName);
480   }
481
482   public void removeDirectoryMapping(VcsDirectoryMapping mapping) {
483     myMappings.removeDirectoryMapping(mapping);
484   }
485
486   public void setDirectoryMappings(final List<VcsDirectoryMapping> items) {
487     myHaveLegacyVcsConfiguration = true;
488     myMappings.setDirectoryMappings(items);
489   }
490
491   public void iterateVcsRoot(final VirtualFile root, final Processor<FilePath> iterator) {
492     VcsRootIterator.iterateVcsRoot(myProject, root, iterator);
493   }
494
495   public void readExternal(Element element) throws InvalidDataException {
496     mySerialization.readExternalUtil(element, myOptionsAndConfirmations);
497     final Attribute attribute = element.getAttribute(SETTINGS_EDITED_MANUALLY);
498     if (attribute != null) {
499       try {
500         myHaveLegacyVcsConfiguration = attribute.getBooleanValue();
501       }
502       catch (DataConversionException e) {
503         //
504       }
505     }
506   }
507
508   public void writeExternal(Element element) throws WriteExternalException {
509     mySerialization.writeExternalUtil(element, myOptionsAndConfirmations);
510     element.setAttribute(SETTINGS_EDITED_MANUALLY, String.valueOf(myHaveLegacyVcsConfiguration));
511   }
512
513   @NotNull
514   public VcsShowConfirmationOption getStandardConfirmation(@NotNull VcsConfiguration.StandardConfirmation option,
515                                                            @NotNull AbstractVcs vcs) {
516     final VcsShowConfirmationOptionImpl result = getConfirmation(option);
517     result.addApplicableVcs(vcs);
518     return result;
519   }
520
521   public List<VcsShowConfirmationOptionImpl> getAllConfirmations() {
522     return myOptionsAndConfirmations.getAllConfirmations();
523   }
524
525   @NotNull
526   public VcsShowConfirmationOptionImpl getConfirmation(VcsConfiguration.StandardConfirmation option) {
527     return myOptionsAndConfirmations.getConfirmation(option);
528   }
529
530   private final Map<VcsListener, MessageBusConnection> myAdapters = new HashMap<VcsListener, MessageBusConnection>();
531
532   public void addVcsListener(VcsListener listener) {
533     final MessageBusConnection connection = myMessageBus.connect();
534     connection.subscribe(VCS_CONFIGURATION_CHANGED, listener);
535     myAdapters.put(listener, connection);
536   }
537
538   public void removeVcsListener(VcsListener listener) {
539     final MessageBusConnection connection = myAdapters.remove(listener);
540     if (connection != null) {
541       connection.disconnect();
542     }
543   }
544
545   public void startBackgroundVcsOperation() {
546     myBackgroundOperationCounter++;
547   }
548
549   public void stopBackgroundVcsOperation() {
550     // in fact, the condition is "should not be called under ApplicationManager.invokeLater() and similiar"
551     assert ! ApplicationManager.getApplication().isDispatchThread();
552     LOG.assertTrue(myBackgroundOperationCounter > 0, "myBackgroundOperationCounter > 0");
553     myBackgroundOperationCounter--;
554   }
555
556   public boolean isBackgroundVcsOperationRunning() {
557     return myBackgroundOperationCounter > 0;
558   }
559
560   public List<VirtualFile> getRootsUnderVcsWithoutFiltering(final AbstractVcs vcs) {
561     return myMappings.getMappingsAsFilesUnderVcs(vcs);
562   }
563
564   public VirtualFile[] getRootsUnderVcs(AbstractVcs vcs) {
565     return myMappingsToRoots.getRootsUnderVcs(vcs);
566   }
567
568   public List<VirtualFile> getDetailedVcsMappings(final AbstractVcs vcs) {
569     return myMappingsToRoots.getDetailedVcsMappings(vcs);
570   }
571
572   public VirtualFile[] getAllVersionedRoots() {
573     List<VirtualFile> vFiles = new ArrayList<VirtualFile>();
574     final AbstractVcs[] vcses = myMappings.getActiveVcses();
575     for (AbstractVcs vcs : vcses) {
576       Collections.addAll(vFiles, getRootsUnderVcs(vcs));
577     }
578     return VfsUtil.toVirtualFileArray(vFiles);
579   }
580
581   @NotNull
582   public VcsRoot[] getAllVcsRoots() {
583     List<VcsRoot> vcsRoots = new ArrayList<VcsRoot>();
584     final AbstractVcs[] vcses = myMappings.getActiveVcses();
585     for (AbstractVcs vcs : vcses) {
586       final VirtualFile[] roots = getRootsUnderVcs(vcs);
587       for(VirtualFile root: roots) {
588         vcsRoots.add(new VcsRoot(vcs, root));
589       }
590     }
591     return vcsRoots.toArray(new VcsRoot[vcsRoots.size()]);
592   }
593
594   public void updateActiveVcss() {
595     // not needed
596   }
597
598   public void notifyDirectoryMappingChanged() {
599     myMessageBus.syncPublisher(VCS_CONFIGURATION_CHANGED).directoryMappingChanged();
600   }
601
602   public void readDirectoryMappings(final Element element) {
603     myMappings.clear();
604
605     final List<VcsDirectoryMapping> mappingsList = new ArrayList<VcsDirectoryMapping>();
606     final List list = element.getChildren(ELEMENT_MAPPING);
607     boolean haveNonEmptyMappings = false;
608     for(Object childObj: list) {
609       Element child = (Element) childObj;
610       final String vcs = child.getAttributeValue(ATTRIBUTE_VCS);
611       if (vcs != null && vcs.length() > 0) {
612         haveNonEmptyMappings = true;
613       }
614       VcsDirectoryMapping mapping = new VcsDirectoryMapping(child.getAttributeValue(ATTRIBUTE_DIRECTORY), vcs);
615       mappingsList.add(mapping);
616
617       Element rootSettingsElement = child.getChild(ELEMENT_ROOT_SETTINGS);
618       if (rootSettingsElement != null) {
619         String className = rootSettingsElement.getAttributeValue(ATTRIBUTE_CLASS);
620         AbstractVcs vcsInstance = findVcsByName(mapping.getVcs());
621         if (vcsInstance != null && className != null) {
622           final VcsRootSettings rootSettings = vcsInstance.createEmptyVcsRootSettings();
623           if (rootSettings != null) {
624             try {
625               rootSettings.readExternal(rootSettingsElement);
626               mapping.setRootSettings(rootSettings);
627             } catch (InvalidDataException e) {
628               LOG.error("Failed to load VCS root settings class "+ className + " for VCS " + vcsInstance.getClass().getName(), e);
629             }
630           }
631         }
632       }
633     }
634     boolean defaultProject = Boolean.TRUE.toString().equals(element.getAttributeValue(ATTRIBUTE_DEFAULT_PROJECT));
635     // run autodetection if there's no VCS in default project and 
636     if (haveNonEmptyMappings || !defaultProject) {
637       myMappingsLoaded = true;
638     }
639     myMappings.setDirectoryMappings(mappingsList);
640   }
641
642   public void writeDirectoryMappings(final Element element) {
643     if (myProject.isDefault()) {
644       element.setAttribute(ATTRIBUTE_DEFAULT_PROJECT, Boolean.TRUE.toString());
645     }
646     for(VcsDirectoryMapping mapping: getDirectoryMappings()) {
647       Element child = new Element(ELEMENT_MAPPING);
648       child.setAttribute(ATTRIBUTE_DIRECTORY, mapping.getDirectory());
649       child.setAttribute(ATTRIBUTE_VCS, mapping.getVcs());
650       final VcsRootSettings rootSettings = mapping.getRootSettings();
651       if (rootSettings != null) {
652         Element rootSettingsElement = new Element(ELEMENT_ROOT_SETTINGS);
653         rootSettingsElement.setAttribute(ATTRIBUTE_CLASS, rootSettings.getClass().getName());
654         try {
655           rootSettings.writeExternal(rootSettingsElement);
656           child.addContent(rootSettingsElement);
657         }
658         catch (WriteExternalException e) {
659           // don't add element
660         }
661       }
662       element.addContent(child);
663     }
664   }
665
666   public boolean needAutodetectMappings() {
667     return !myHaveLegacyVcsConfiguration && !myMappingsLoaded;
668   }
669
670   /**
671    * Used to guess VCS for automatic mapping through a look into a working copy
672    */
673   @Nullable
674   public AbstractVcs findVersioningVcs(VirtualFile file) {
675     final VcsDescriptor[] vcsDescriptors = getAllVcss();
676     VcsDescriptor probableVcs = null;
677     for (VcsDescriptor vcsDescriptor : vcsDescriptors) {
678       if (vcsDescriptor.probablyUnderVcs(file)) {
679         if (probableVcs != null) {
680           return null;
681         }
682         probableVcs = vcsDescriptor;
683       }
684     }
685     return probableVcs == null ? null : findVcsByName(probableVcs.getName());
686   }
687
688   public CheckoutProvider.Listener getCompositeCheckoutListener() {
689     return new CompositeCheckoutListener(myProject);
690   }
691
692   @Override
693   public VcsEventsListenerManager getVcsEventsListenerManager() {
694     return myVcsEventListenerManager;
695   }
696
697   public void fireDirectoryMappingsChanged() {
698     if (myProject.isOpen() && ! myProject.isDisposed()) {
699       myMappings.mappingsChanged();
700     }
701   }
702
703   public String haveDefaultMapping() {
704     return myMappings.haveDefaultMapping();
705   }
706
707   @Override
708   protected VcsEnvironmentsProxyCreator getProxyCreator() {
709     return myVcsEventListenerManager;
710   }
711
712   public BackgroundableActionEnabledHandler getBackgroundableActionHandler(final VcsBackgroundableActions action) {
713     ApplicationManager.getApplication().assertIsDispatchThread();
714
715     BackgroundableActionEnabledHandler result = myBackgroundableActionHandlerMap.get(action);
716     if (result == null) {
717       result = new BackgroundableActionEnabledHandler();
718       myBackgroundableActionHandlerMap.put(action, result);
719     }
720     return result;
721   }
722
723   public void addInitializationRequest(final VcsInitObject vcsInitObject, final Runnable runnable) {
724     ApplicationManager.getApplication().runReadAction(new Runnable() {
725       public void run() {
726         myInitialization.add(vcsInitObject, runnable);
727       }
728     });
729   }
730
731   public boolean isFileInContent(final VirtualFile vf) {
732     return vf != null && (myExcludedIndex.isInContent(vf) || isFileInBaseDir(vf) || vf.equals(myProject.getBaseDir()) ||
733                             hasExplicitMapping(vf) || isInDirectoryBasedRoot(vf)) && ! myExcludedIndex.isExcludedFile(vf);
734   }
735
736   private boolean isInDirectoryBasedRoot(final VirtualFile file) {
737     if (file == null) return false;
738     final StorageScheme storageScheme = ((ProjectEx) myProject).getStateStore().getStorageScheme();
739     if (StorageScheme.DIRECTORY_BASED.equals(storageScheme)) {
740       final VirtualFile baseDir = myProject.getBaseDir();
741       if (baseDir == null) return false;
742       final VirtualFile ideaDir = baseDir.findChild(Project.DIRECTORY_STORE_FOLDER);
743       return ideaDir != null && ideaDir.isValid() && ideaDir.isDirectory() && VfsUtil.isAncestor(ideaDir, file, false);
744     }
745     return false;
746   }
747
748   private boolean isFileInBaseDir(final VirtualFile file) {
749     VirtualFile parent = file.getParent();
750     return !file.isDirectory() && parent != null && parent.equals(myProject.getBaseDir());
751   }
752
753   // inner roots disclosed
754   public static List<VirtualFile> getRootsUnder(final List<VirtualFile> roots, final VirtualFile underWhat) {
755     final List<VirtualFile> result = new ArrayList<VirtualFile>(roots.size());
756     for (VirtualFile root : roots) {
757       if (VfsUtil.isAncestor(underWhat, root, false)) {
758         result.add(root);
759       }
760     }
761     return result;
762   }
763
764   public VcsHistoryCache getVcsHistoryCache() {
765     return myVcsHistoryCache;
766   }
767 }