blob: 02185ec7f9a09884d98697b22d53f0f65e57d19c [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 org.jetbrains.idea.svn;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.command.CommandAdapter;
import com.intellij.openapi.command.CommandEvent;
import com.intellij.openapi.command.undo.UndoManager;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.fileEditor.FileDocumentManager;
import com.intellij.openapi.progress.ProgressManager;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.project.ProjectManager;
import com.intellij.openapi.util.Comparing;
import com.intellij.openapi.util.Couple;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.vcs.*;
import com.intellij.openapi.vcs.actions.VcsContextFactory;
import com.intellij.openapi.vcs.changes.ChangeListManager;
import com.intellij.openapi.vcs.changes.VcsDirtyScopeManager;
import com.intellij.openapi.vfs.LocalFileOperationsHandler;
import com.intellij.openapi.vfs.LocalFileSystem;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.openapi.vfs.newvfs.RefreshQueue;
import com.intellij.util.Processor;
import com.intellij.util.ThrowableConsumer;
import com.intellij.util.containers.Convertor;
import com.intellij.util.containers.MultiMap;
import com.intellij.vcsUtil.ActionWithTempFile;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.idea.svn.api.Depth;
import org.jetbrains.idea.svn.api.NodeKind;
import org.jetbrains.idea.svn.commandLine.SvnBindException;
import org.jetbrains.idea.svn.info.Info;
import org.jetbrains.idea.svn.status.Status;
import org.jetbrains.idea.svn.status.StatusType;
import org.tmatesoft.svn.core.SVNErrorCode;
import org.tmatesoft.svn.core.SVNException;
import org.tmatesoft.svn.core.internal.wc.SVNFileUtil;
import org.tmatesoft.svn.core.wc.SVNMoveClient;
import java.io.File;
import java.io.IOException;
import java.util.*;
public class SvnFileSystemListener extends CommandAdapter implements LocalFileOperationsHandler {
private static final Logger LOG = Logger.getInstance("#org.jetbrains.idea.svn.SvnFileSystemListener");
private final LocalFileSystem myLfs;
private static class AddedFileInfo {
private final VirtualFile myDir;
private final String myName;
@Nullable private final File myCopyFrom;
private final boolean myRecursive;
public AddedFileInfo(final VirtualFile dir, final String name, @Nullable final File copyFrom, boolean recursive) {
myDir = dir;
myName = name;
myCopyFrom = copyFrom;
myRecursive = recursive;
}
}
private static class MovedFileInfo {
private final Project myProject;
private final File mySrc;
private final File myDst;
private MovedFileInfo(final Project project, final File src, final File dst) {
myProject = project;
mySrc = src;
myDst = dst;
}
}
private final MultiMap<Project, AddedFileInfo> myAddedFiles = new MultiMap<Project, AddedFileInfo>();
private final MultiMap<Project, File> myDeletedFiles = new MultiMap<Project, File>();
private final List<MovedFileInfo> myMovedFiles = new ArrayList<MovedFileInfo>();
private final Map<Project, List<VcsException>> myMoveExceptions = new HashMap<Project, List<VcsException>>();
private final List<VirtualFile> myFilesToRefresh = new ArrayList<VirtualFile>();
@Nullable private File myStorageForUndo;
private final List<Couple<File>> myUndoStorageContents = new ArrayList<Couple<File>>();
private boolean myUndoingMove = false;
public SvnFileSystemListener() {
myLfs = LocalFileSystem.getInstance();
}
private void addToMoveExceptions(@NotNull final Project project, @NotNull final Exception e) {
List<VcsException> exceptionList = myMoveExceptions.get(project);
if (exceptionList == null) {
exceptionList = new ArrayList<VcsException>();
myMoveExceptions.put(project, exceptionList);
}
exceptionList.add(handleMoveException(e));
}
private VcsException handleMoveException(@NotNull Exception e) {
VcsException vcsException;
if (e instanceof SVNException && SVNErrorCode.ENTRY_EXISTS.equals(((SVNException)e).getErrorMessage().getErrorCode())) {
vcsException = createMoveTargetExistsError(e);
}
else if (e instanceof SvnBindException && ((SvnBindException)e).contains(SVNErrorCode.ENTRY_EXISTS)) {
vcsException = createMoveTargetExistsError(e);
}
else if (e instanceof VcsException) {
vcsException = (VcsException)e;
}
else {
vcsException = new VcsException(e);
}
return vcsException;
}
private static VcsException createMoveTargetExistsError(@NotNull Exception e) {
return new VcsException(Arrays.asList("Target of move operation is already under version control.",
"Subversion move had not been performed. ", e.getMessage()));
}
@Nullable
public File copy(final VirtualFile file, final VirtualFile toDir, final String copyName) throws IOException {
SvnVcs vcs = getVCS(toDir);
if (vcs == null) {
vcs = getVCS(file);
}
if (vcs == null) {
return null;
}
File srcFile = new File(file.getPath());
File destFile = new File(new File(toDir.getPath()), copyName);
final boolean dstDirUnderControl = SvnUtil.isSvnVersioned(vcs.getProject(), destFile.getParentFile());
if (! dstDirUnderControl && !isPendingAdd(vcs.getProject(), toDir)) {
return null;
}
if (! SvnUtil.isSvnVersioned(vcs.getProject(), srcFile.getParentFile())) {
myAddedFiles.putValue(vcs.getProject(), new AddedFileInfo(toDir, copyName, null, false));
return null;
}
final Status fileStatus = getFileStatus(vcs, srcFile);
if (fileStatus != null && fileStatus.is(StatusType.STATUS_ADDED)) {
myAddedFiles.putValue(vcs.getProject(), new AddedFileInfo(toDir, copyName, null, false));
return null;
}
if (sameRoot(vcs, file.getParent(), toDir)) {
myAddedFiles.putValue(vcs.getProject(), new AddedFileInfo(toDir, copyName, srcFile, false));
return null;
}
myAddedFiles.putValue(vcs.getProject(), new AddedFileInfo(toDir, copyName, null, false));
return null;
}
private boolean sameRoot(final SvnVcs vcs, final VirtualFile srcDir, final VirtualFile dstDir) {
final UUIDHelper helper = new UUIDHelper(vcs);
final String srcUUID = helper.getRepositoryUUID(vcs.getProject(), srcDir);
final String dstUUID = helper.getRepositoryUUID(vcs.getProject(), dstDir);
return srcUUID != null && dstUUID != null && srcUUID.equals(dstUUID);
}
private class UUIDHelper {
private final SvnVcs myVcs;
private UUIDHelper(final SvnVcs vcs) {
myVcs = vcs;
}
/**
* passed dir must be under VC control (it is assumed)
*/
@Nullable
public String getRepositoryUUID(final Project project, final VirtualFile dir) {
try {
final Info info1 = new RepeatSvnActionThroughBusy() {
@Override
protected void executeImpl() {
myT = myVcs.getInfo(new File(dir.getPath()));
}
}.compute();
if (info1 == null || info1.getRepositoryUUID() == null) {
// go deeper if current parent was added (if parent was added, it theoretically could NOT know its repo UUID)
final VirtualFile parent = dir.getParent();
if (parent == null) {
return null;
}
if (isPendingAdd(project, parent)) {
return getRepositoryUUID(project, parent);
}
} else {
return info1.getRepositoryUUID();
}
}
catch (VcsException e) {
// go to return default
}
return null;
}
}
public boolean move(VirtualFile file, VirtualFile toDir) throws IOException {
File srcFile = getIOFile(file);
File dstFile = new File(getIOFile(toDir), file.getName());
final SvnVcs vcs = getVCS(toDir);
final SvnVcs sourceVcs = getVCS(file);
if (vcs == null && sourceVcs == null) return false;
if (vcs == null) {
return false;
}
FileDocumentManager.getInstance().saveAllDocuments();
if (sourceVcs == null) {
return createItem(toDir, file.getName(), file.isDirectory(), true);
}
if (isPendingAdd(vcs.getProject(), toDir)) {
myMovedFiles.add(new MovedFileInfo(sourceVcs.getProject(), srcFile, dstFile));
return true;
}
else {
final VirtualFile oldParent = file.getParent();
myFilesToRefresh.add(oldParent);
myFilesToRefresh.add(toDir);
return doMove(sourceVcs, srcFile, dstFile);
}
}
public boolean rename(VirtualFile file, String newName) throws IOException {
File srcFile = getIOFile(file);
File dstFile = new File(srcFile.getParentFile(), newName);
SvnVcs vcs = getVCS(file);
if (vcs != null) {
FileDocumentManager.getInstance().saveAllDocuments();
myFilesToRefresh.add(file.getParent());
return doMove(vcs, srcFile, dstFile);
}
return false;
}
private boolean doMove(@NotNull SvnVcs vcs, final File src, final File dst) {
long srcTime = src.lastModified();
try {
final boolean isUndo = isUndo(vcs);
final String list = isUndo ? null : SvnChangelistListener.getCurrentMapping(vcs, src);
WorkingCopyFormat format = vcs.getWorkingCopyFormat(src);
final boolean is17OrLater = format.isOrGreater(WorkingCopyFormat.ONE_DOT_SEVEN);
if (is17OrLater) {
Status srcStatus = getFileStatus(vcs, src);
final File toDir = dst.getParentFile();
Status dstStatus = getFileStatus(vcs, toDir);
final boolean srcUnversioned = srcStatus == null || srcStatus.is(StatusType.STATUS_UNVERSIONED);
if (srcUnversioned && (dstStatus == null || dstStatus.is(StatusType.STATUS_UNVERSIONED))) {
return false;
}
if (srcUnversioned) {
Status dstWasStatus = getFileStatus(vcs, dst);
if (dstWasStatus == null || dstWasStatus.is(StatusType.STATUS_UNVERSIONED)) {
return false;
}
}
if (for17move(vcs, src, dst, isUndo, srcStatus)) return false;
} else {
if (for16move(vcs, src, dst, isUndo)) return false;
}
if (! isUndo && list != null) {
SvnChangelistListener.putUnderList(vcs.getProject(), list, dst);
}
dst.setLastModified(srcTime);
}
catch(VcsException e) {
addToMoveExceptions(vcs.getProject(), e);
return false;
}
return true;
}
private final static Set<StatusType> ourStatusesForUndoMove = new HashSet<StatusType>();
static {
ourStatusesForUndoMove.add(StatusType.STATUS_ADDED);
}
private boolean for17move(final SvnVcs vcs, final File src, final File dst, boolean undo, Status srcStatus) throws VcsException {
if (srcStatus != null && srcStatus.getCopyFromURL() == null) {
undo = false;
}
if (undo) {
myUndoingMove = true;
createRevertAction(vcs, dst, true).execute();
copyUnversionedMembersOfDirectory(src, dst);
if (srcStatus == null || srcStatus.is(StatusType.STATUS_UNVERSIONED)) {
FileUtil.delete(src);
} else {
createRevertAction(vcs, src, true).execute();
}
restoreFromUndoStorage(dst);
} else {
if (doUsualMove(vcs, src)) return true;
// check destination directory
final Status dstParentStatus = getFileStatus(vcs, dst.getParentFile());
if (dstParentStatus == null || dstParentStatus.is(StatusType.STATUS_UNVERSIONED)) {
try {
copyFileOrDir(src, dst);
}
catch (IOException e) {
throw new SvnBindException(e);
}
createDeleteAction(vcs, src, true).execute();
return false;
}
moveFileWithSvn(vcs, src, dst);
}
return false;
}
public static void moveFileWithSvn(final SvnVcs vcs, final File src, final File dst) throws VcsException {
new RepeatSvnActionThroughBusy() {
@Override
protected void executeImpl() throws VcsException {
vcs.getFactory(src).createCopyMoveClient().copy(src, dst, false, true);
}
}.execute();
}
private void copyUnversionedMembersOfDirectory(final File src, final File dst) throws SvnBindException {
if (src.isDirectory()) {
final SvnBindException[] exc = new SvnBindException[1];
FileUtil.processFilesRecursively(src, new Processor<File>() {
@Override
public boolean process(File file) {
String relativePath = FileUtil.getRelativePath(src, file);
File newFile = new File(dst, relativePath);
if (!newFile.exists()) {
try {
copyFileOrDir(src, dst);
}
catch (IOException e) {
exc[0] = new SvnBindException(e);
return false;
}
}
return true;
}
});
if (exc[0] != null) {
throw exc[0];
}
}
}
private void copyFileOrDir(File src, File dst) throws IOException {
if (src.isDirectory()) {
FileUtil.copyDir(src, dst);
} else {
FileUtil.copy(src, dst);
}
}
private boolean doUsualMove(SvnVcs vcs, File src) {
// if src is not under version control, do usual move.
Status srcStatus = getFileStatus(vcs, src);
return srcStatus == null ||
srcStatus.is(StatusType.STATUS_UNVERSIONED, StatusType.STATUS_OBSTRUCTED, StatusType.STATUS_MISSING, StatusType.STATUS_EXTERNAL);
}
private boolean for16move(SvnVcs vcs, final File src, final File dst, boolean undo) throws VcsException {
final SVNMoveClient mover = vcs.getSvnKitManager().createMoveClient();
if (undo) {
myUndoingMove = true;
restoreFromUndoStorage(dst);
new RepeatSvnActionThroughBusy() {
@Override
protected void executeImpl() throws VcsException {
try {
mover.undoMove(src, dst);
}
catch (SVNException e) {
throw new SvnBindException(e);
}
}
}.execute();
}
else {
// if src is not under version control, do usual move.
if (doUsualMove(vcs, src)) return true;
new RepeatSvnActionThroughBusy() {
@Override
protected void executeImpl() throws VcsException {
try {
mover.doMove(src, dst);
}
catch (SVNException e) {
throw new SvnBindException(e);
}
}
}.execute();
}
return false;
}
private void restoreFromUndoStorage(final File dst) {
String normPath = FileUtil.toSystemIndependentName(dst.getPath());
for (Iterator<Couple<File>> it = myUndoStorageContents.iterator(); it.hasNext();) {
Couple<File> e = it.next();
final String p = FileUtil.toSystemIndependentName(e.first.getPath());
if (p.startsWith(normPath)) {
try {
FileUtil.rename(e.second, e.first);
}
catch (IOException ex) {
LOG.error(ex);
FileUtil.asyncDelete(e.second);
}
it.remove();
}
}
if (myStorageForUndo != null) {
final File[] files = myStorageForUndo.listFiles();
if (files == null || files.length == 0) {
FileUtil.asyncDelete(myStorageForUndo);
myStorageForUndo = null;
}
}
}
public boolean createFile(VirtualFile dir, String name) throws IOException {
return createItem(dir, name, false, false);
}
public boolean createDirectory(VirtualFile dir, String name) throws IOException {
return createItem(dir, name, true, false);
}
/**
* delete file or directory (both 'undo' and 'do' modes)
* unversioned: do nothing, return false
* obstructed: do nothing, return false
* external or wc root: do nothing, return false
* missing: do nothing, return false
* <p/>
* versioned: schedule for deletion, return true
* added: schedule for deletion (make unversioned), return true
* copied, but not scheduled: schedule for deletion, return true
* replaced: schedule for deletion, return true
* <p/>
* deleted: do nothing, return true (strange)
*/
public boolean delete(VirtualFile file) throws IOException {
final SvnVcs vcs = getVCS(file);
if (vcs != null && SvnUtil.isAdminDirectory(file)) {
return true;
}
if (vcs == null) return false;
final VcsShowConfirmationOption.Value value = vcs.getDeleteConfirmation().getValue();
if (VcsShowConfirmationOption.Value.DO_NOTHING_SILENTLY.equals(value)) return false;
final File ioFile = getIOFile(file);
if (! SvnUtil.isSvnVersioned(vcs.getProject(), ioFile.getParentFile())) {
return false;
}
if (SvnUtil.isWorkingCopyRoot(ioFile)) {
return false;
}
Status status = getFileStatus(vcs, ioFile);
if (status == null ||
status.is(StatusType.STATUS_UNVERSIONED, StatusType.STATUS_OBSTRUCTED, StatusType.STATUS_MISSING, StatusType.STATUS_EXTERNAL,
StatusType.STATUS_IGNORED)) {
return false;
}
else if (status.is(StatusType.STATUS_DELETED)) {
if (isUndo(vcs)) {
moveToUndoStorage(file);
}
return true;
}
else {
if (vcs != null) {
if (isAboveSourceOfCopyOrMove(vcs.getProject(), ioFile)) {
myDeletedFiles.putValue(vcs.getProject(), ioFile);
return true;
}
if (status.is(StatusType.STATUS_ADDED)) {
try {
createRevertAction(vcs, ioFile, false).execute();
}
catch (VcsException e) {
// ignore
}
}
else {
myDeletedFiles.putValue(vcs.getProject(), ioFile);
// packages deleted from disk should not be deleted from svn (IDEADEV-16066)
if (file.isDirectory() || isUndo(vcs)) return true;
}
}
return false;
}
}
@NotNull
private RepeatSvnActionThroughBusy createRevertAction(@NotNull final SvnVcs vcs, @NotNull final File file, final boolean recursive) {
return new RepeatSvnActionThroughBusy() {
@Override
protected void executeImpl() throws VcsException {
vcs.getFactory(file).createRevertClient().revert(new File[]{file}, Depth.allOrFiles(recursive), null);
}
};
}
@NotNull
private RepeatSvnActionThroughBusy createDeleteAction(@NotNull final SvnVcs vcs, @NotNull final File file, final boolean force) {
return new RepeatSvnActionThroughBusy() {
@Override
protected void executeImpl() throws VcsException {
vcs.getFactory(file).createDeleteClient().delete(file, force, false, null);
}
};
}
private boolean isAboveSourceOfCopyOrMove(final Project p, File ioFile) {
for (MovedFileInfo file : myMovedFiles) {
if (FileUtil.isAncestor(ioFile, file.mySrc, false)) return true;
}
for (AddedFileInfo info : myAddedFiles.get(p)) {
if (info.myCopyFrom != null && FileUtil.isAncestor(ioFile, info.myCopyFrom, false)) return true;
}
return false;
}
private void moveToUndoStorage(final VirtualFile file) {
if (myStorageForUndo == null) {
try {
myStorageForUndo = FileUtil.createTempDirectory("svnUndoStorage", "");
}
catch (IOException e) {
LOG.error(e);
return;
}
}
final File tmpFile = FileUtil.findSequentNonexistentFile(myStorageForUndo, "tmp", "");
myUndoStorageContents.add(0, Couple.of(new File(file.getPath()), tmpFile));
new File(file.getPath()).renameTo(tmpFile);
}
/**
* add file or directory:
* <p/>
* parent directory is:
* unversioned: do nothing, return false
* versioned:
* entry is:
* null: create entry, schedule for addition
* missing: do nothing, return false
* deleted, 'do' mode: try to create entry and it schedule for addition if kind is the same, otherwise do nothing, return false.
* deleted: 'undo' mode: try to revert non-recursively, if kind is the same, otherwise do nothing, return false.
* anything else: return false.
*/
private boolean createItem(VirtualFile dir, String name, boolean directory, final boolean recursive) {
SvnVcs vcs = getVCS(dir);
if (vcs == null) {
return false;
}
final VcsShowConfirmationOption.Value value = vcs.getAddConfirmation().getValue();
if (VcsShowConfirmationOption.Value.DO_NOTHING_SILENTLY.equals(value)) return false;
if (isUndo(vcs) && SvnUtil.isAdminDirectory(dir, name)) {
return false;
}
File ioDir = getIOFile(dir);
boolean pendingAdd = isPendingAdd(vcs.getProject(), dir);
if (! SvnUtil.isSvnVersioned(vcs.getProject(), ioDir) && ! pendingAdd) {
return false;
}
final File targetFile = new File(ioDir, name);
Status status = getFileStatus(vcs, targetFile);
if (status == null || status.getContentsStatus() == StatusType.STATUS_NONE ||
status.getContentsStatus() == StatusType.STATUS_UNVERSIONED) {
myAddedFiles.putValue(vcs.getProject(), new AddedFileInfo(dir, name, null, recursive));
return false;
}
else if (status.is(StatusType.STATUS_MISSING)) {
return false;
}
else if (status.is(StatusType.STATUS_DELETED)) {
NodeKind kind = status.getKind();
// kind differs.
if (directory && !kind.isDirectory() || !directory && !kind.isFile()) {
return false;
}
try {
if (isUndo(vcs)) {
createRevertAction(vcs, targetFile, false).execute();
return true;
}
myAddedFiles.putValue(vcs.getProject(), new AddedFileInfo(dir, name, null, recursive));
return false;
}
catch (VcsException e) {
SVNFileUtil.deleteAll(targetFile, true);
return false;
}
}
return false;
}
private boolean isPendingAdd(final Project project, final VirtualFile dir) {
final Collection<AddedFileInfo> addedFileInfos = myAddedFiles.get(project);
for(AddedFileInfo i: addedFileInfos) {
if (Comparing.equal(i.myDir, dir.getParent()) && i.myName.equals(dir.getName())) {
return true;
}
}
return false;
}
public void commandStarted(CommandEvent event) {
myUndoingMove = false;
final Project project = event.getProject();
if (project == null) return;
commandStarted(project);
}
void commandStarted(final Project project) {
myUndoingMove = false;
myMoveExceptions.remove(project);
}
public void commandFinished(CommandEvent event) {
final Project project = event.getProject();
if (project == null) return;
commandFinished(project);
}
void commandFinished(final Project project) {
checkOverwrites(project);
if (myAddedFiles.containsKey(project)) {
processAddedFiles(project);
}
processMovedFiles(project);
if (myDeletedFiles.containsKey(project)) {
processDeletedFiles(project);
}
final List<VcsException> exceptionList = myMoveExceptions.get(project);
if (exceptionList != null && ! exceptionList.isEmpty()) {
AbstractVcsHelper.getInstance(project).showErrors(exceptionList, SvnBundle.message("move.files.errors.title"));
}
if (!myFilesToRefresh.isEmpty()) {
refreshFiles(project);
}
}
private void checkOverwrites(final Project project) {
final Collection<AddedFileInfo> addedFileInfos = myAddedFiles.get(project);
final Collection<File> deletedFiles = myDeletedFiles.get(project);
if (addedFileInfos.isEmpty() || deletedFiles.isEmpty()) return;
final Iterator<AddedFileInfo> iterator = addedFileInfos.iterator();
while (iterator.hasNext()) {
AddedFileInfo addedFileInfo = iterator.next();
final File ioFile = new File(addedFileInfo.myDir.getPath(), addedFileInfo.myName);
if (deletedFiles.remove(ioFile)) {
iterator.remove();
}
}
}
private void refreshFiles(final Project project) {
final List<VirtualFile> toRefreshFiles = new ArrayList<VirtualFile>();
final List<VirtualFile> toRefreshDirs = new ArrayList<VirtualFile>();
for (VirtualFile file : myFilesToRefresh) {
if (file == null) continue;
if (file.isDirectory()) {
toRefreshDirs.add(file);
} else {
toRefreshFiles.add(file);
}
}
// if refresh asynchronously, local changes would also be notified that they are dirty asynchronously,
// and commit could be executed while not all changes are visible
filterOutInvalid(myFilesToRefresh);
RefreshQueue.getInstance().refresh(true, true, new Runnable() {
public void run() {
if (project.isDisposed()) return;
filterOutInvalid(toRefreshFiles);
filterOutInvalid(toRefreshDirs);
final VcsDirtyScopeManager vcsDirtyScopeManager = VcsDirtyScopeManager.getInstance(project);
vcsDirtyScopeManager.filesDirty(toRefreshFiles, toRefreshDirs);
}
}, myFilesToRefresh);
myFilesToRefresh.clear();
}
private static void filterOutInvalid(final Collection<VirtualFile> files) {
for (Iterator<VirtualFile> iterator = files.iterator(); iterator.hasNext();) {
final VirtualFile file = iterator.next();
if (! file.isValid() || ! file.exists()) {
LOG.info("Refresh root is not valid: " + file.getPath());
iterator.remove();
}
}
}
private void processAddedFiles(Project project) {
SvnVcs vcs = SvnVcs.getInstance(project);
List<VirtualFile> addedVFiles = new ArrayList<VirtualFile>();
Map<VirtualFile, File> copyFromMap = new HashMap<VirtualFile, File>();
final Set<VirtualFile> recursiveItems = new HashSet<VirtualFile>();
fillAddedFiles(project, vcs, addedVFiles, copyFromMap, recursiveItems);
if (addedVFiles.isEmpty()) return;
final VcsShowConfirmationOption.Value value = vcs.getAddConfirmation().getValue();
if (value != VcsShowConfirmationOption.Value.DO_NOTHING_SILENTLY) {
final AbstractVcsHelper vcsHelper = AbstractVcsHelper.getInstance(project);
final Collection<VirtualFile> filesToProcess = promptAboutAddition(vcs, addedVFiles, value, vcsHelper);
if (filesToProcess != null && !filesToProcess.isEmpty()) {
final List<VcsException> exceptions = new ArrayList<VcsException>();
runInBackground(project, "Adding files to Subversion",
createAdditionRunnable(project, vcs, copyFromMap, filesToProcess, exceptions));
if (!exceptions.isEmpty()) {
vcsHelper.showErrors(exceptions, SvnBundle.message("add.files.errors.title"));
}
}
}
}
private void runInBackground(final Project project, final String name, final Runnable runnable) {
if (ApplicationManager.getApplication().isDispatchThread()) {
ProgressManager.getInstance().runProcessWithProgressSynchronously(runnable, name, false, project);
} else {
runnable.run();
}
}
private Runnable createAdditionRunnable(final Project project,
final SvnVcs vcs,
final Map<VirtualFile, File> copyFromMap,
final Collection<VirtualFile> filesToProcess,
final List<VcsException> exceptions) {
return new Runnable() {
@Override
public void run() {
for(VirtualFile file: filesToProcess) {
final File ioFile = new File(file.getPath());
try {
final File copyFrom = copyFromMap.get(file);
if (copyFrom != null) {
try {
new ActionWithTempFile(ioFile) {
protected void executeInternal() throws VcsException {
// not recursive
new RepeatSvnActionThroughBusy() {
@Override
protected void executeImpl() throws VcsException {
vcs.getFactory(copyFrom).createCopyMoveClient().copy(copyFrom, ioFile, true, false);
}
}.execute();
}
}.execute();
}
catch (VcsException e) {
exceptions.add(e);
}
}
else {
new RepeatSvnActionThroughBusy() {
@Override
protected void executeImpl() throws VcsException {
vcs.getFactory(ioFile).createAddClient().add(ioFile, null, false, false, true, null);
}
}.execute();
}
VcsDirtyScopeManager.getInstance(project).fileDirty(file);
}
catch (VcsException e) {
exceptions.add(e);
}
}
}
};
}
private Collection<VirtualFile> promptAboutAddition(SvnVcs vcs,
List<VirtualFile> addedVFiles,
VcsShowConfirmationOption.Value value,
AbstractVcsHelper vcsHelper) {
Collection<VirtualFile> filesToProcess;
if (value == VcsShowConfirmationOption.Value.DO_ACTION_SILENTLY) {
filesToProcess = addedVFiles;
}
else {
final String singleFilePrompt;
if (addedVFiles.size() == 1 && addedVFiles.get(0).isDirectory()) {
singleFilePrompt = SvnBundle.getString("confirmation.text.add.dir");
}
else {
singleFilePrompt = SvnBundle.getString("confirmation.text.add.file");
}
filesToProcess = vcsHelper.selectFilesToProcess(addedVFiles, SvnBundle.message("confirmation.title.add.multiple.files"),
null,
SvnBundle.message("confirmation.title.add.file"), singleFilePrompt,
vcs.getAddConfirmation());
}
return filesToProcess;
}
private void fillAddedFiles(Project project,
SvnVcs vcs,
List<VirtualFile> addedVFiles,
Map<VirtualFile, File> copyFromMap,
Set<VirtualFile> recursiveItems) {
final Collection<AddedFileInfo> addedFileInfos = myAddedFiles.remove(project);
final ChangeListManager changeListManager = ChangeListManager.getInstance(project);
for (AddedFileInfo addedFileInfo : addedFileInfos) {
final File ioFile = new File(getIOFile(addedFileInfo.myDir), addedFileInfo.myName);
VirtualFile addedFile = addedFileInfo.myDir.findChild(addedFileInfo.myName);
if (addedFile == null) {
addedFile = myLfs.refreshAndFindFileByIoFile(ioFile);
}
if (addedFile != null) {
final Status fileStatus = getFileStatus(vcs, ioFile);
if (fileStatus == null || !fileStatus.is(StatusType.STATUS_IGNORED)) {
boolean isIgnored = changeListManager.isIgnoredFile(addedFile);
if (!isIgnored) {
addedVFiles.add(addedFile);
copyFromMap.put(addedFile, addedFileInfo.myCopyFrom);
if (addedFileInfo.myRecursive) {
recursiveItems.add(addedFile);
}
}
}
}
}
}
private void processDeletedFiles(Project project) {
final List<Pair<FilePath, WorkingCopyFormat>> deletedFiles = new ArrayList<Pair<FilePath, WorkingCopyFormat>>();
final Collection<FilePath> filesToProcess = new ArrayList<FilePath>();
List<VcsException> exceptions = new ArrayList<VcsException>();
final AbstractVcsHelper vcsHelper = AbstractVcsHelper.getInstance(project);
try {
fillDeletedFiles(project, deletedFiles, filesToProcess);
if (deletedFiles.isEmpty() && filesToProcess.isEmpty() || myUndoingMove) return;
SvnVcs vcs = SvnVcs.getInstance(project);
final VcsShowConfirmationOption.Value value = vcs.getDeleteConfirmation().getValue();
if (value != VcsShowConfirmationOption.Value.DO_NOTHING_SILENTLY) {
if (! deletedFiles.isEmpty()) {
final Collection<FilePath> confirmed = promptAboutDeletion(deletedFiles, vcs, value, vcsHelper);
if (confirmed != null) {
filesToProcess.addAll(confirmed);
}
}
if (filesToProcess != null && ! filesToProcess.isEmpty()) {
runInBackground(project, "Deleting files from Subversion", createDeleteRunnable(project, vcs, filesToProcess, exceptions));
}
final List<FilePath> deletedFilesFiles = ObjectsConvertor.convert(deletedFiles, new Convertor<Pair<FilePath, WorkingCopyFormat>, FilePath>() {
@Override
public FilePath convert(Pair<FilePath, WorkingCopyFormat> o) {
return o.getFirst();
}
});
for (FilePath file : deletedFilesFiles) {
final FilePath parent = file.getParentPath();
if (parent != null) {
myFilesToRefresh.add(parent.getVirtualFile());
}
}
if (filesToProcess != null) {
deletedFilesFiles.removeAll(filesToProcess);
}
for (FilePath file : deletedFilesFiles) {
FileUtil.delete(file.getIOFile());
}
}
} catch (VcsException e) {
exceptions.add(e);
}
if (! exceptions.isEmpty()) {
vcsHelper.showErrors(exceptions, SvnBundle.message("delete.files.errors.title"));
}
}
private Runnable createDeleteRunnable(final Project project,
final SvnVcs vcs,
final Collection<FilePath> filesToProcess,
final List<VcsException> exceptions) {
return new Runnable() {
public void run() {
for(FilePath file: filesToProcess) {
VirtualFile vFile = file.getVirtualFile(); // for deleted directories
final File ioFile = new File(file.getPath());
try {
createDeleteAction(vcs, ioFile, true).execute();
if (vFile != null && vFile.isValid() && vFile.isDirectory()) {
vFile.refresh(true, true);
VcsDirtyScopeManager.getInstance(project).dirDirtyRecursively(vFile);
}
else {
VcsDirtyScopeManager.getInstance(project).fileDirty(file);
}
}
catch (VcsException e) {
exceptions.add(e);
}
}
}
};
}
private Collection<FilePath> promptAboutDeletion(List<Pair<FilePath, WorkingCopyFormat>> deletedFiles,
SvnVcs vcs,
VcsShowConfirmationOption.Value value,
AbstractVcsHelper vcsHelper) {
final Convertor<Pair<FilePath, WorkingCopyFormat>, FilePath> convertor =
new Convertor<Pair<FilePath, WorkingCopyFormat>, FilePath>() {
@Override
public FilePath convert(Pair<FilePath, WorkingCopyFormat> o) {
return o.getFirst();
}
};
Collection<FilePath> filesToProcess;
if (value == VcsShowConfirmationOption.Value.DO_ACTION_SILENTLY) {
filesToProcess = ObjectsConvertor.convert(deletedFiles, convertor);
} else {
final String singleFilePrompt;
if (deletedFiles.size() == 1 && deletedFiles.get(0).getFirst().isDirectory()) {
singleFilePrompt = deletedFiles.get(0).getSecond().isOrGreater(WorkingCopyFormat.ONE_DOT_SEVEN) ?
SvnBundle.getString("confirmation.text.delete.dir.17") :
SvnBundle.getString("confirmation.text.delete.dir");
}
else {
singleFilePrompt = SvnBundle.getString("confirmation.text.delete.file");
}
final Collection<FilePath> files = vcsHelper
.selectFilePathsToProcess(ObjectsConvertor.convert(deletedFiles, convertor), SvnBundle.message("confirmation.title.delete.multiple.files"), null,
SvnBundle.message("confirmation.title.delete.file"), singleFilePrompt, vcs.getDeleteConfirmation());
filesToProcess = files == null ? null : new ArrayList<FilePath>(files);
}
return filesToProcess;
}
private void fillDeletedFiles(Project project, List<Pair<FilePath, WorkingCopyFormat>> deletedFiles, Collection<FilePath> deleteAnyway)
throws VcsException {
final SvnVcs vcs = SvnVcs.getInstance(project);
final Collection<File> files = myDeletedFiles.remove(project);
for (final File file : files) {
final Status status = new RepeatSvnActionThroughBusy() {
@Override
protected void executeImpl() throws VcsException {
myT = vcs.getFactory(file).createStatusClient().doStatus(file, false);
}
}.compute();
boolean isAdded = StatusType.STATUS_ADDED.equals(status.getNodeStatus());
final FilePath filePath = VcsContextFactory.SERVICE.getInstance().createFilePathOn(file);
if (isAdded) {
deleteAnyway.add(filePath);
} else {
deletedFiles.add(Pair.create(filePath, vcs.getWorkingCopyFormat(file)));
}
}
}
private void processMovedFiles(final Project project) {
if (myMovedFiles.isEmpty()) return;
final Runnable runnable = new Runnable() {
public void run() {
for (Iterator<MovedFileInfo> iterator = myMovedFiles.iterator(); iterator.hasNext();) {
MovedFileInfo movedFileInfo = iterator.next();
if (movedFileInfo.myProject == project) {
doMove(SvnVcs.getInstance(project), movedFileInfo.mySrc, movedFileInfo.myDst);
iterator.remove();
}
}
}
};
runInBackground(project, "Moving files in Subversion", runnable);
}
@Nullable
private static SvnVcs getVCS(VirtualFile file) {
Project[] projects = ProjectManager.getInstance().getOpenProjects();
for (Project project : projects) {
AbstractVcs vcs = ProjectLevelVcsManager.getInstance(project).getVcsFor(file);
if (vcs instanceof SvnVcs) {
return (SvnVcs)vcs;
}
}
return null;
}
private static File getIOFile(VirtualFile vf) {
return new File(vf.getPath()).getAbsoluteFile();
}
@Nullable
private static Status getFileStatus(@NotNull final SvnVcs vcs, @NotNull final File file) {
try {
return new RepeatSvnActionThroughBusy() {
@Override
protected void executeImpl() throws VcsException {
myT = vcs.getFactory(file).createStatusClient().doStatus(file, false);
}
}.compute();
}
catch (VcsException e) {
return null;
}
}
private static boolean isUndoOrRedo(@NotNull final Project project) {
final UndoManager undoManager = UndoManager.getInstance(project);
return undoManager.isUndoInProgress() || undoManager.isRedoInProgress();
}
private static boolean isUndo(SvnVcs vcs) {
if (vcs == null || vcs.getProject() == null) {
return false;
}
Project p = vcs.getProject();
return UndoManager.getInstance(p).isUndoInProgress();
}
public void afterDone(final ThrowableConsumer<LocalFileOperationsHandler, IOException> invoker) {
}
}