blob: bd88045300640ae44b84084287d30d0a6858f9ac [file] [log] [blame]
/*
* Copyright 2000-2010 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.diff.impl.patch.formove;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.registry.Registry;
import com.intellij.openapi.vcs.*;
import com.intellij.openapi.vcs.changes.ChangeListManager;
import com.intellij.openapi.vcs.changes.SortByVcsRoots;
import com.intellij.openapi.vcs.checkin.CheckinEnvironment;
import com.intellij.openapi.vfs.VfsUtil;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.util.FilePathByPathComparator;
import com.intellij.util.containers.Convertor;
import com.intellij.util.containers.MultiMap;
import java.util.*;
public class TriggerAdditionOrDeletion {
private final Collection<FilePath> myExisting;
private final Collection<FilePath> myDeleted;
private final Set<FilePath> myAffected;
private final Project myProject;
private final boolean mySilentAddDelete;
private ProjectLevelVcsManager myVcsManager;
private AbstractVcsHelper myVcsHelper;
private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.diff.impl.patch.formove.TriggerAdditionOrDeletion");
private final VcsFileListenerContextHelper myVcsFileListenerContextHelper;
private MultiMap<VcsRoot, FilePath> myPreparedAddition;
private MultiMap<VcsRoot, FilePath> myPreparedDeletion;
public TriggerAdditionOrDeletion(final Project project, boolean silentAddDelete) {
myProject = project;
mySilentAddDelete = Registry.is("vcs.add.remove.silent"); // TODO there is no sense in making add/remove non-silent anywhen; wait for users feedback, then remove
myExisting = new HashSet<FilePath>();
myDeleted = new HashSet<FilePath>();
myVcsManager = ProjectLevelVcsManager.getInstance(myProject);
myVcsHelper = AbstractVcsHelper.getInstance(myProject);
myAffected = new HashSet<FilePath>();
myVcsFileListenerContextHelper = VcsFileListenerContextHelper.getInstance(myProject);
}
public void addExisting(final Collection<FilePath> files) {
myExisting.addAll(files);
logFiles("FOR ADD", files);
}
public void addDeleted(final Collection<FilePath> files) {
myDeleted.addAll(files);
logFiles("FOR DELETION", files);
}
private void logFiles(final String name, final Collection<FilePath> files) {
/*final StringBuilder sb = new StringBuilder(name);
sb.append(": ");
for (FilePath file : files) {
sb.append(file.getPath()).append('\n');
}
sb.append('\n');
LOG.info(sb.toString());*/
}
public void prepare() {
if (myExisting.isEmpty() && myDeleted.isEmpty()) return;
final SortByVcsRoots<FilePath> sortByVcsRoots = new SortByVcsRoots<FilePath>(myProject, new Convertor.IntoSelf());
if (! myExisting.isEmpty()) {
processAddition(sortByVcsRoots);
}
if (! myDeleted.isEmpty()) {
processDeletion(sortByVcsRoots);
}
}
public void processIt() {
if (myPreparedDeletion != null) {
for (Map.Entry<VcsRoot, Collection<FilePath>> entry : myPreparedDeletion.entrySet()) {
final VcsRoot vcsRoot = entry.getKey();
final CheckinEnvironment localChangesProvider = vcsRoot.getVcs().getCheckinEnvironment();
if (localChangesProvider == null) continue;
final Collection<FilePath> filePaths = entry.getValue();
if (vcsRoot.getVcs().fileListenerIsSynchronous()) {
myAffected.addAll(filePaths);
continue;
}
askUserIfNeededDeletion(vcsRoot.getVcs(), (List<FilePath>)filePaths);
myAffected.addAll(filePaths);
localChangesProvider.scheduleMissingFileForDeletion((List<FilePath>)filePaths);
}
}
if (myPreparedAddition != null) {
for (Map.Entry<VcsRoot, Collection<FilePath>> entry : myPreparedAddition.entrySet()) {
final VcsRoot vcsRoot = entry.getKey();
final CheckinEnvironment localChangesProvider = vcsRoot.getVcs().getCheckinEnvironment();
if (localChangesProvider == null) continue;
final Collection<FilePath> filePaths = entry.getValue();
if (vcsRoot.getVcs().fileListenerIsSynchronous()) {
myAffected.addAll(filePaths);
continue;
}
askUserIfNeededAddition(vcsRoot.getVcs(), (List<FilePath>)filePaths);
myAffected.addAll(filePaths);
localChangesProvider.scheduleUnversionedFilesForAddition(ObjectsConvertor.fp2vf(filePaths));
}
}
}
public Set<FilePath> getAffected() {
return myAffected;
}
private void processDeletion(SortByVcsRoots<FilePath> sortByVcsRoots) {
final MultiMap<VcsRoot, FilePath> map = sortByVcsRoots.sort(myDeleted);
myPreparedDeletion = new MultiMap<VcsRoot, FilePath>();
for (VcsRoot vcsRoot : map.keySet()) {
if (vcsRoot != null && vcsRoot.getVcs() != null) {
final CheckinEnvironment localChangesProvider = vcsRoot.getVcs().getCheckinEnvironment();
if (localChangesProvider == null) continue;
final boolean takeDirs = vcsRoot.getVcs().areDirectoriesVersionedItems();
final Collection<FilePath> files = map.get(vcsRoot);
final List<FilePath> toBeDeleted = new LinkedList<FilePath>();
for (FilePath file : files) {
final FilePath parent = file.getParentPath();
if ((takeDirs || (! file.isDirectory())) && parent != null && parent.getIOFile().exists()) {
toBeDeleted.add(file);
}
}
if (toBeDeleted.isEmpty()) return;
if (! vcsRoot.getVcs().fileListenerIsSynchronous()) {
for (FilePath filePath : toBeDeleted) {
myVcsFileListenerContextHelper.ignoreDeleted(filePath);
}
}
myPreparedDeletion.put(vcsRoot, toBeDeleted);
}
}
}
private void processAddition(SortByVcsRoots<FilePath> sortByVcsRoots) {
for (FilePath filePath : myExisting) {
filePath.hardRefresh();
}
final MultiMap<VcsRoot, FilePath> map = sortByVcsRoots.sort(myExisting);
myPreparedAddition = new MultiMap<VcsRoot, FilePath>();
for (VcsRoot vcsRoot : map.keySet()) {
if (vcsRoot != null && vcsRoot.getVcs() != null) {
final CheckinEnvironment localChangesProvider = vcsRoot.getVcs().getCheckinEnvironment();
if (localChangesProvider == null) continue;
final boolean takeDirs = vcsRoot.getVcs().areDirectoriesVersionedItems();
final Collection<FilePath> files = map.get(vcsRoot);
final List<FilePath> toBeAdded;
if (takeDirs) {
final RecursiveCheckAdder adder = new RecursiveCheckAdder(vcsRoot.getPath());
for (FilePath file : files) {
adder.process(file);
}
toBeAdded = adder.getToBeAdded();
} else {
toBeAdded = new LinkedList<FilePath>();
for (FilePath file : files) {
if (! file.isDirectory()) {
toBeAdded.add(file);
}
}
}
if (toBeAdded.isEmpty()) {
return;
}
Collections.sort(toBeAdded, FilePathByPathComparator.getInstance());
if (! vcsRoot.getVcs().fileListenerIsSynchronous()) {
for (FilePath filePath : toBeAdded) {
myVcsFileListenerContextHelper.ignoreAdded(filePath.getVirtualFile());
}
}
myPreparedAddition.put(vcsRoot, toBeAdded);
}
}
}
private void askUserIfNeededAddition(final AbstractVcs vcs, final List<FilePath> toBeAdded) {
if (mySilentAddDelete) return;
final VcsShowConfirmationOption confirmationOption = myVcsManager.getStandardConfirmation(VcsConfiguration.StandardConfirmation.ADD, vcs);
if (VcsShowConfirmationOption.Value.DO_NOTHING_SILENTLY.equals(confirmationOption.getValue())) {
toBeAdded.clear();
} else if (VcsShowConfirmationOption.Value.SHOW_CONFIRMATION.equals(confirmationOption.getValue())) {
final Collection<FilePath> files = myVcsHelper.selectFilePathsToProcess(toBeAdded, "Select files to add to " + vcs.getDisplayName(), null,
"Schedule for addition", "Do you want to schedule the following file for addition to " + vcs.getDisplayName() + "\n{0}", confirmationOption);
if (files == null) {
toBeAdded.clear();
} else {
toBeAdded.retainAll(files);
}
}
}
private void askUserIfNeededDeletion(final AbstractVcs vcs, final List<FilePath> toBeDeleted) {
if (mySilentAddDelete) return;
final VcsShowConfirmationOption confirmationOption = myVcsManager.getStandardConfirmation(VcsConfiguration.StandardConfirmation.REMOVE, vcs);
if (VcsShowConfirmationOption.Value.DO_NOTHING_SILENTLY.equals(confirmationOption.getValue())) {
toBeDeleted.clear();
} else if (VcsShowConfirmationOption.Value.SHOW_CONFIRMATION.equals(confirmationOption.getValue())) {
final Collection<FilePath> files = myVcsHelper.selectFilePathsToProcess(toBeDeleted, "Select files to remove from " + vcs.getDisplayName(), null,
"Schedule for deletion", "Do you want to schedule the following file for deletion from " + vcs.getDisplayName() + "\n{0}", confirmationOption);
if (files == null) {
toBeDeleted.clear();
} else {
toBeDeleted.retainAll(files);
}
}
}
private class RecursiveCheckAdder {
private final Set<FilePath> myToBeAdded;
private ChangeListManager myChangeListManager;
private final VirtualFile myRoot;
private RecursiveCheckAdder(final VirtualFile root) {
myRoot = root;
myToBeAdded = new HashSet<FilePath>();
myChangeListManager = ChangeListManager.getInstance(myProject);
}
public void process(final FilePath path) {
FilePath current = path;
while (current != null) {
VirtualFile vf = current.getVirtualFile();
if (vf == null) {
current.hardRefresh();
vf = current.getVirtualFile();
if (vf == null) {
return;
}
}
if (! VfsUtil.isAncestor(myRoot, vf, true)) return;
myToBeAdded.add(current);
current = current.getParentPath();
}
}
public List<FilePath> getToBeAdded() {
return new ArrayList<FilePath>(myToBeAdded);
}
}
}