blob: 57754f0190ccdb6368e78fab3c6daba7d4e44456 [file] [log] [blame]
/*
* 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.impl.projectlevelman;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.project.DumbAwareRunnable;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.MessageType;
import com.intellij.openapi.util.EmptyRunnable;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.util.Ref;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vcs.*;
import com.intellij.openapi.vcs.impl.DefaultVcsRootPolicy;
import com.intellij.openapi.vcs.impl.ProjectLevelVcsManagerImpl;
import com.intellij.openapi.vcs.impl.VcsInitObject;
import com.intellij.openapi.vcs.ui.VcsBalloonProblemNotifier;
import com.intellij.openapi.vfs.LocalFileSystem;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.util.containers.Convertor;
import com.intellij.util.messages.MessageBus;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.*;
public class NewMappings {
private final static Logger LOG = Logger.getInstance("#com.intellij.openapi.vcs.impl.projectlevelman.NewMappings");
private final Object myLock;
// vcs to mappings
private final Map<String, List<VcsDirectoryMapping>> myVcsToPaths;
private AbstractVcs[] myActiveVcses;
private VcsDirectoryMapping[] mySortedMappings;
private FileWatchRequestsManager myFileWatchRequestsManager;
private final DefaultVcsRootPolicy myDefaultVcsRootPolicy;
private final MessageBus myMessageBus;
private final ProjectLevelVcsManager myVcsManager;
private final FileStatusManager myFileStatusManager;
private final Project myProject;
private boolean myActivated;
public NewMappings(final Project project, final MessageBus messageBus, final ProjectLevelVcsManagerImpl vcsManager,
FileStatusManager fileStatusManager) {
myProject = project;
myMessageBus = messageBus;
myVcsManager = vcsManager;
myFileStatusManager = fileStatusManager;
myLock = new Object();
myVcsToPaths = new HashMap<String, List<VcsDirectoryMapping>>();
myFileWatchRequestsManager = new FileWatchRequestsManager(myProject, this, LocalFileSystem.getInstance());
myDefaultVcsRootPolicy = DefaultVcsRootPolicy.getInstance(project);
myActiveVcses = new AbstractVcs[0];
if (!myProject.isDefault()) {
final ArrayList<VcsDirectoryMapping> listStr = new ArrayList<VcsDirectoryMapping>();
final VcsDirectoryMapping mapping = new VcsDirectoryMapping("", "");
listStr.add(mapping);
myVcsToPaths.put("", listStr);
mySortedMappings = new VcsDirectoryMapping[]{mapping};
}
else {
mySortedMappings = VcsDirectoryMapping.EMPTY_ARRAY;
}
myActivated = false;
vcsManager.addInitializationRequest(VcsInitObject.MAPPINGS, new DumbAwareRunnable() {
public void run() {
if (!myProject.isDisposed()) {
activateActiveVcses();
}
}
});
}
// for tests
public void setFileWatchRequestsManager(FileWatchRequestsManager fileWatchRequestsManager) {
assert ApplicationManager.getApplication().isUnitTestMode();
myFileWatchRequestsManager = fileWatchRequestsManager;
}
public AbstractVcs[] getActiveVcses() {
synchronized (myLock) {
final AbstractVcs[] result = new AbstractVcs[myActiveVcses.length];
System.arraycopy(myActiveVcses, 0, result, 0, myActiveVcses.length);
return result;
}
}
public boolean hasActiveVcss() {
synchronized (myLock) {
return myActiveVcses.length > 0;
}
}
public void activateActiveVcses() {
synchronized (myLock) {
if (myActivated) return;
myActivated = true;
}
keepActiveVcs(EmptyRunnable.getInstance());
mappingsChanged();
}
@Modification
public void setMapping(final String path, final String activeVcsName) {
LOG.debug("setMapping path = '" + path + "' vcs = " + activeVcsName);
final VcsDirectoryMapping newMapping = new VcsDirectoryMapping(path, activeVcsName);
// do not add duplicates
synchronized (myLock) {
if (myVcsToPaths.containsKey(activeVcsName)) {
final List<VcsDirectoryMapping> vcsDirectoryMappings = myVcsToPaths.get(activeVcsName);
if ((vcsDirectoryMappings != null) && (vcsDirectoryMappings.contains(newMapping))) {
return;
}
}
}
final Ref<Boolean> switched = new Ref<Boolean>(Boolean.FALSE);
keepActiveVcs(new Runnable() {
public void run() {
// sorted -> map. sorted mappings are NOT changed;
switched.set(trySwitchVcs(path, activeVcsName));
if (!switched.get().booleanValue()) {
final List<VcsDirectoryMapping> newList = listForVcsFromMap(newMapping.getVcs());
newList.add(newMapping);
sortedMappingsByMap();
}
}
});
mappingsChanged();
}
private void keepActiveVcs(@NotNull Runnable runnable) {
final MyVcsActivator activator;
synchronized (myLock) {
if (!myActivated) {
runnable.run();
return;
}
final HashSet<String> old = new HashSet<String>();
for (AbstractVcs activeVcs : myActiveVcses) {
old.add(activeVcs.getName());
}
activator = new MyVcsActivator(old);
runnable.run();
restoreActiveVcses();
}
activator.activate(myVcsToPaths.keySet(), AllVcses.getInstance(myProject));
}
private void restoreActiveVcses() {
synchronized (myLock) {
final Set<String> set = myVcsToPaths.keySet();
final List<AbstractVcs> list = new ArrayList<AbstractVcs>(set.size());
for (String s : set) {
if (s.trim().length() == 0) continue;
final AbstractVcs vcs = AllVcses.getInstance(myProject).getByName(s);
if (vcs != null) {
list.add(vcs);
}
}
myActiveVcses = list.toArray(new AbstractVcs[list.size()]);
}
}
public void mappingsChanged() {
ApplicationManager.getApplication().runReadAction(new Runnable() {
@Override
public void run() {
if (myProject.isDisposed()) return;
myMessageBus.syncPublisher(ProjectLevelVcsManager.VCS_CONFIGURATION_CHANGED).directoryMappingChanged();
myFileStatusManager.fileStatusesChanged();
myFileWatchRequestsManager.ping();
}
});
}
@Modification
public void setDirectoryMappings(final List<VcsDirectoryMapping> items) {
LOG.debug("setDirectoryMappings, size: " + items.size());
MySetMappingsPreProcessor setMappingsPreProcessor = new MySetMappingsPreProcessor(items);
setMappingsPreProcessor.invoke();
final List<VcsDirectoryMapping> itemsCopy;
if (items.isEmpty()) {
itemsCopy = Collections.singletonList(new VcsDirectoryMapping("", ""));
}
else {
itemsCopy = items;
}
keepActiveVcs(new Runnable() {
public void run() {
myVcsToPaths.clear();
for (VcsDirectoryMapping mapping : itemsCopy) {
listForVcsFromMap(mapping.getVcs()).add(mapping);
}
sortedMappingsByMap();
}
});
mappingsChanged();
}
@Nullable
public VcsDirectoryMapping getMappingFor(@Nullable VirtualFile file) {
if (file == null) return null;
if (!file.isInLocalFileSystem()) {
return null;
}
return getMappingFor(file, myDefaultVcsRootPolicy.getMatchContext(file));
}
@Nullable
public VcsDirectoryMapping getMappingFor(final VirtualFile file, final Object parentModule) {
// if parentModule is not null it means that file belongs to the module so it isn't excluded
if (parentModule == null && myVcsManager.isIgnored(file)) {
return null;
}
// performance: calculate file path just once, rather than once per mapping
String path = file.getPath();
final String systemIndependentPath = FileUtil.toSystemIndependentName((file.isDirectory() && (!path.endsWith("/"))) ? (path + "/") : path);
final VcsDirectoryMapping[] mappings;
synchronized (myLock) {
mappings = mySortedMappings;
}
for (int i = mappings.length - 1; i >= 0; --i) {
final VcsDirectoryMapping mapping = mappings[i];
if (fileMatchesMapping(file, parentModule, systemIndependentPath, mapping)) {
return mapping;
}
}
return null;
}
@Nullable
public String getVcsFor(@NotNull VirtualFile file) {
VcsDirectoryMapping mapping = getMappingFor(file);
if (mapping == null) {
return null;
}
return mapping.getVcs();
}
private boolean fileMatchesMapping(final VirtualFile file,
final Object matchContext,
final String systemIndependentPath,
final VcsDirectoryMapping mapping) {
if (mapping.getDirectory().length() == 0) {
return myDefaultVcsRootPolicy.matchesDefaultMapping(file, matchContext);
}
return FileUtil.startsWith(systemIndependentPath, mapping.systemIndependentPath());
}
public List<VirtualFile> getMappingsAsFilesUnderVcs(@NotNull AbstractVcs vcs) {
final List<VirtualFile> result = new ArrayList<VirtualFile>();
final String vcsName = vcs.getName();
final List<VcsDirectoryMapping> mappings;
synchronized (myLock) {
final List<VcsDirectoryMapping> vcsMappings = myVcsToPaths.get(vcsName);
if (vcsMappings == null) return result;
mappings = new ArrayList<VcsDirectoryMapping>(vcsMappings);
}
for (VcsDirectoryMapping mapping : mappings) {
if (mapping.isDefaultMapping()) {
// todo callback here; don't like it
myDefaultVcsRootPolicy.addDefaultVcsRoots(this, vcsName, result);
}
else {
final VirtualFile file = LocalFileSystem.getInstance().findFileByPath(mapping.getDirectory());
if (file != null) {
result.add(file);
}
}
}
return result;
}
@Modification
public void disposeMe() {
LOG.debug("dispose me");
clearImpl();
}
@Modification
public void clear() {
LOG.debug("clear");
clearImpl();
mappingsChanged();
}
private void clearImpl() {
// if vcses were not mapped, there's nothing to clear
if ((myActiveVcses == null) || (myActiveVcses.length == 0)) return;
keepActiveVcs(new Runnable() {
public void run() {
myVcsToPaths.clear();
myActiveVcses = new AbstractVcs[0];
mySortedMappings = VcsDirectoryMapping.EMPTY_ARRAY;
}
});
myFileWatchRequestsManager.ping();
}
public List<VcsDirectoryMapping> getDirectoryMappings() {
synchronized (myLock) {
return Arrays.asList(mySortedMappings);
}
}
public List<VcsDirectoryMapping> getDirectoryMappings(String vcsName) {
synchronized (myLock) {
final List<VcsDirectoryMapping> mappings = myVcsToPaths.get(vcsName);
return mappings == null ? new ArrayList<VcsDirectoryMapping>() : new ArrayList<VcsDirectoryMapping>(mappings);
}
}
public void cleanupMappings() {
synchronized (myLock) {
removeRedundantMappings();
}
myFileWatchRequestsManager.ping();
}
@Nullable
public String haveDefaultMapping() {
synchronized (myLock) {
// empty mapping MUST be first
if (mySortedMappings.length == 0) return null;
return mySortedMappings[0].isDefaultMapping() ? mySortedMappings[0].getVcs() : null;
}
}
public boolean isEmpty() {
synchronized (myLock) {
return mySortedMappings.length == 0;
}
}
@Modification
public void removeDirectoryMapping(final VcsDirectoryMapping mapping) {
LOG.debug("remove mapping: " + mapping.getDirectory());
keepActiveVcs(new Runnable() {
public void run() {
if (removeVcsFromMap(mapping, mapping.getVcs())) {
sortedMappingsByMap();
}
}
});
mappingsChanged();
}
// todo area for optimization
private void removeRedundantMappings() {
final LocalFileSystem lfs = LocalFileSystem.getInstance();
final AllVcsesI allVcses = AllVcses.getInstance(myProject);
for (Iterator<String> iterator = myVcsToPaths.keySet().iterator(); iterator.hasNext(); ) {
final String vcsName = iterator.next();
final List<VcsDirectoryMapping> mappings = myVcsToPaths.get(vcsName);
final List<Pair<VirtualFile, VcsDirectoryMapping>> objects = ObjectsConvertor.convert(mappings,
new Convertor<VcsDirectoryMapping, Pair<VirtualFile, VcsDirectoryMapping>>() {
public Pair<VirtualFile, VcsDirectoryMapping> convert(final VcsDirectoryMapping dm) {
VirtualFile vf = lfs.findFileByPath(dm.getDirectory());
if (vf == null) {
vf = lfs.refreshAndFindFileByPath(dm.getDirectory());
}
return vf == null ? null : Pair.create(vf, dm);
}
}, ObjectsConvertor.NOT_NULL);
final List<Pair<VirtualFile, VcsDirectoryMapping>> filteredFiles;
// todo static
final Convertor<Pair<VirtualFile, VcsDirectoryMapping>, VirtualFile> fileConvertor =
new Convertor<Pair<VirtualFile, VcsDirectoryMapping>, VirtualFile>() {
public VirtualFile convert(Pair<VirtualFile, VcsDirectoryMapping> o) {
return o.getFirst();
}
};
if (StringUtil.isEmptyOrSpaces(vcsName)) {
filteredFiles = AbstractVcs.filterUniqueRootsDefault(objects, fileConvertor);
}
else {
final AbstractVcs<?> vcs = allVcses.getByName(vcsName);
if (vcs == null) {
VcsBalloonProblemNotifier.showOverChangesView(myProject, "VCS plugin not found for mapping to : '" + vcsName + "'", MessageType.ERROR);
continue;
}
filteredFiles = vcs.filterUniqueRoots(objects, fileConvertor);
}
final List<VcsDirectoryMapping> filteredMappings =
ObjectsConvertor.convert(filteredFiles, new Convertor<Pair<VirtualFile, VcsDirectoryMapping>, VcsDirectoryMapping>() {
public VcsDirectoryMapping convert(final Pair<VirtualFile, VcsDirectoryMapping> o) {
return o.getSecond();
}
});
// to calculate what had been removed
mappings.removeAll(filteredMappings);
if (filteredMappings.isEmpty()) {
iterator.remove();
}
else {
mappings.clear();
mappings.addAll(filteredMappings);
}
}
sortedMappingsByMap();
}
private boolean trySwitchVcs(final String path, final String activeVcsName) {
final String fixedPath = FileUtil.toSystemIndependentName(path);
for (VcsDirectoryMapping mapping : mySortedMappings) {
if (mapping.systemIndependentPath().equals(fixedPath)) {
final String oldVcs = mapping.getVcs();
if (!oldVcs.equals(activeVcsName)) {
migrateVcs(activeVcsName, mapping, oldVcs);
}
return true;
}
}
return false;
}
private void sortedMappingsByMap() {
final List<VcsDirectoryMapping> list = new ArrayList<VcsDirectoryMapping>();
for (List<VcsDirectoryMapping> mappingList : myVcsToPaths.values()) {
list.addAll(mappingList);
}
mySortedMappings = list.toArray(new VcsDirectoryMapping[list.size()]);
Arrays.sort(mySortedMappings, MyMappingsComparator.getInstance());
}
private void migrateVcs(String activeVcsName, VcsDirectoryMapping mapping, String oldVcs) {
mapping.setVcs(activeVcsName);
removeVcsFromMap(mapping, oldVcs);
final List<VcsDirectoryMapping> newList = listForVcsFromMap(activeVcsName);
newList.add(mapping);
}
private boolean removeVcsFromMap(VcsDirectoryMapping mapping, String oldVcs) {
final List<VcsDirectoryMapping> oldList = myVcsToPaths.get(oldVcs);
if (oldList == null) return false;
final boolean result = oldList.remove(mapping);
if (oldList.isEmpty()) {
myVcsToPaths.remove(oldVcs);
}
return result;
}
// todo don't like it
private List<VcsDirectoryMapping> listForVcsFromMap(String activeVcsName) {
List<VcsDirectoryMapping> newList = myVcsToPaths.get(activeVcsName);
if (newList == null) {
newList = new ArrayList<VcsDirectoryMapping>();
myVcsToPaths.put(activeVcsName, newList);
}
return newList;
}
private static class MyMappingsComparator implements Comparator<VcsDirectoryMapping> {
private static final MyMappingsComparator ourInstance = new MyMappingsComparator();
public static MyMappingsComparator getInstance() {
return ourInstance;
}
public int compare(@NotNull VcsDirectoryMapping m1, @NotNull VcsDirectoryMapping m2) {
return m1.getDirectory().compareTo(m2.getDirectory());
}
}
private static class MyVcsActivator {
private final Set<String> myOld;
public MyVcsActivator(final Set<String> old) {
myOld = old;
}
public void activate(final Set<String> newOne, final AllVcsesI vcsesI) {
final Set<String> toAdd = notInBottom(newOne, myOld);
final Set<String> toRemove = notInBottom(myOld, newOne);
if (toAdd != null) {
for (String s : toAdd) {
final AbstractVcs vcs = vcsesI.getByName(s);
if (vcs != null) {
try {
vcs.doActivate();
}
catch (VcsException e) {
// actually is not thrown (AbstractVcs#actualActivate())
}
}
else {
LOG.info("Error: activating non existing vcs: " + s);
}
}
}
if (toRemove != null) {
for (String s : toRemove) {
final AbstractVcs vcs = vcsesI.getByName(s);
if (vcs != null) {
try {
vcs.doDeactivate();
}
catch (VcsException e) {
// actually is not thrown (AbstractVcs#actualDeactivate())
}
}
else {
LOG.info("Error: removing non existing vcs: " + s);
}
}
}
}
@Nullable
private static Set<String> notInBottom(final Set<String> top, final Set<String> bottom) {
Set<String> notInBottom = null;
for (String topItem : top) {
// omit empty vcs: not a vcs
if (topItem.trim().length() == 0) continue;
if (!bottom.contains(topItem)) {
if (notInBottom == null) {
notInBottom = new HashSet<String>();
}
notInBottom.add(topItem);
}
}
return notInBottom;
}
}
public boolean haveActiveVcs(final String name) {
synchronized (myLock) {
return myVcsToPaths.containsKey(name);
}
}
@Modification
public void beingUnregistered(final String name) {
synchronized (myLock) {
keepActiveVcs(new Runnable() {
public void run() {
myVcsToPaths.remove(name);
sortedMappingsByMap();
}
});
}
mappingsChanged();
}
private static class MySetMappingsPreProcessor {
private final List<VcsDirectoryMapping> myItems;
private List<VcsDirectoryMapping> myItemsCopy;
public MySetMappingsPreProcessor(final List<VcsDirectoryMapping> items) {
myItems = items;
}
public List<VcsDirectoryMapping> getItemsCopy() {
return myItemsCopy;
}
public void invoke() {
if (myItems.isEmpty()) {
myItemsCopy = Collections.singletonList(new VcsDirectoryMapping("", ""));
}
else {
myItemsCopy = myItems;
}
}
}
private @interface Modification {
}
public List<VirtualFile> getDefaultRoots() {
synchronized (myLock) {
final String defaultVcs = haveDefaultMapping();
if (defaultVcs == null) return Collections.emptyList();
final List<VirtualFile> list = new ArrayList<VirtualFile>();
myDefaultVcsRootPolicy.addDefaultVcsRoots(this, defaultVcs, list);
if (StringUtil.isEmptyOrSpaces(defaultVcs)) {
return AbstractVcs.filterUniqueRootsDefault(list, Convertor.SELF);
}
else {
final AbstractVcs<?> vcs = AllVcses.getInstance(myProject).getByName(defaultVcs);
if (vcs == null) {
return AbstractVcs.filterUniqueRootsDefault(list, Convertor.SELF);
}
return vcs.filterUniqueRoots(list, Convertor.SELF);
}
}
}
}