SVN: "move everything in the directory" refactoring to work: first move, then delete
[fedora-idea.git] / plugins / svn4idea / src / org / jetbrains / idea / svn / SvnFileSystemListener.java
blob77135dd2d6a836d6d9787cadc8d76972779e9168
1 /*
2 * Copyright 2000-2009 JetBrains s.r.o.
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
18 package org.jetbrains.idea.svn;
20 import com.intellij.openapi.command.CommandAdapter;
21 import com.intellij.openapi.command.CommandEvent;
22 import com.intellij.openapi.command.undo.UndoManager;
23 import com.intellij.openapi.diagnostic.Logger;
24 import com.intellij.openapi.progress.ProgressManager;
25 import com.intellij.openapi.project.Project;
26 import com.intellij.openapi.project.ProjectManager;
27 import com.intellij.openapi.ui.DialogWrapper;
28 import com.intellij.openapi.util.Pair;
29 import com.intellij.openapi.util.io.FileUtil;
30 import com.intellij.openapi.vcs.*;
31 import com.intellij.openapi.vcs.actions.VcsContextFactory;
32 import com.intellij.openapi.vcs.changes.ChangeListManager;
33 import com.intellij.openapi.vcs.changes.VcsDirtyScopeManager;
34 import com.intellij.openapi.vfs.LocalFileOperationsHandler;
35 import com.intellij.openapi.vfs.LocalFileSystem;
36 import com.intellij.openapi.vfs.VirtualFile;
37 import com.intellij.openapi.vfs.newvfs.RefreshQueue;
38 import com.intellij.openapi.vfs.newvfs.RefreshSession;
39 import com.intellij.util.ThrowableConsumer;
40 import com.intellij.vcsUtil.ActionWithTempFile;
41 import org.jetbrains.annotations.NotNull;
42 import org.jetbrains.annotations.Nullable;
43 import org.jetbrains.idea.svn.dialogs.SelectIgnorePatternsToRemoveOnDeleteDialog;
44 import org.jetbrains.idea.svn.ignore.SvnPropertyService;
45 import org.tmatesoft.svn.core.SVNErrorCode;
46 import org.tmatesoft.svn.core.SVNException;
47 import org.tmatesoft.svn.core.SVNNodeKind;
48 import org.tmatesoft.svn.core.internal.wc.SVNFileUtil;
49 import org.tmatesoft.svn.core.wc.*;
51 import java.io.File;
52 import java.io.IOException;
53 import java.util.*;
55 public class SvnFileSystemListener extends CommandAdapter implements LocalFileOperationsHandler {
56 private static final Logger LOG = Logger.getInstance("#org.jetbrains.idea.svn.SvnFileSystemListener");
57 private final LocalFileSystem myLfs;
59 private static class AddedFileInfo {
60 private final Project myProject;
61 private final VirtualFile myDir;
62 private final String myName;
63 @Nullable private final File myCopyFrom;
64 private final boolean myRecursive;
66 public AddedFileInfo(final Project project, final VirtualFile dir, final String name, @Nullable final File copyFrom, boolean recursive) {
67 myProject = project;
68 myDir = dir;
69 myName = name;
70 myCopyFrom = copyFrom;
71 myRecursive = recursive;
75 private static class DeletedFileInfo {
76 private final Project myProject;
77 private final File myFile;
79 public DeletedFileInfo(final Project project, final File file) {
80 myProject = project;
81 myFile = file;
85 private static class MovedFileInfo {
86 private final Project myProject;
87 private final File mySrc;
88 private final File myDst;
90 private MovedFileInfo(final Project project, final File src, final File dst) {
91 myProject = project;
92 mySrc = src;
93 myDst = dst;
97 private final Map<Project, Map<String, IgnoredFileInfo>> myIgnoredInfo = new HashMap<Project, Map<String, IgnoredFileInfo>>();
99 private final List<AddedFileInfo> myAddedFiles = new ArrayList<AddedFileInfo>();
100 private final List<DeletedFileInfo> myDeletedFiles = new ArrayList<DeletedFileInfo>();
101 private final List<MovedFileInfo> myMovedFiles = new ArrayList<MovedFileInfo>();
102 private final Map<Project, List<VcsException>> myMoveExceptions = new HashMap<Project, List<VcsException>>();
103 private final List<VirtualFile> myFilesToRefresh = new ArrayList<VirtualFile>();
104 @Nullable private File myStorageForUndo;
105 private final List<Pair<File, File>> myUndoStorageContents = new ArrayList<Pair<File, File>>();
106 private boolean myUndoingMove = false;
108 public SvnFileSystemListener() {
109 myLfs = LocalFileSystem.getInstance();
112 private void addToMoveExceptions(final Project project, final SVNException e) {
113 List<VcsException> exceptionList = myMoveExceptions.get(project);
114 if (exceptionList == null) {
115 exceptionList = new ArrayList<VcsException>();
116 myMoveExceptions.put(project, exceptionList);
118 VcsException vcsException;
119 if (SVNErrorCode.ENTRY_EXISTS.equals(e.getErrorMessage().getErrorCode())) {
120 vcsException = new VcsException(Arrays.asList("Target of move operation is already under version control.",
121 "Subversion move had not been performed. ", e.getMessage()));
122 } else {
123 vcsException = new VcsException(e);
125 exceptionList.add(vcsException);
128 @Nullable
129 public File copy(final VirtualFile file, final VirtualFile toDir, final String copyName) throws IOException {
130 SvnVcs vcs = getVCS(toDir);
131 if (vcs == null) {
132 vcs = getVCS(file);
134 if (vcs == null) {
135 return null;
138 File srcFile = new File(file.getPath());
139 File destFile = new File(new File(toDir.getPath()), copyName);
140 final boolean dstDirUnderControl = SVNWCUtil.isVersionedDirectory(destFile.getParentFile());
141 if (! dstDirUnderControl && !isPendingAdd(toDir)) {
142 return null;
145 if (!SVNWCUtil.isVersionedDirectory(srcFile.getParentFile())) {
146 myAddedFiles.add(new AddedFileInfo(vcs.getProject(), toDir, copyName, null, false));
147 return null;
150 final SVNStatus fileStatus = getFileStatus(vcs, srcFile);
151 if (fileStatus != null && fileStatus.getContentsStatus() == SVNStatusType.STATUS_ADDED) {
152 myAddedFiles.add(new AddedFileInfo(vcs.getProject(), toDir, copyName, null, false));
153 return null;
156 if (sameRoot(vcs, file.getParent(), toDir)) {
157 myAddedFiles.add(new AddedFileInfo(vcs.getProject(), toDir, copyName, srcFile, false));
158 return null;
161 myAddedFiles.add(new AddedFileInfo(vcs.getProject(), toDir, copyName, null, false));
162 return null;
165 private boolean sameRoot(final SvnVcs vcs, final VirtualFile srcDir, final VirtualFile dstDir) {
166 final UUIDHelper helper = new UUIDHelper(vcs);
167 final String srcUUID = helper.getRepositoryUUID(srcDir);
168 final String dstUUID = helper.getRepositoryUUID(dstDir);
170 return srcUUID != null && dstUUID != null && srcUUID.equals(dstUUID);
173 private class UUIDHelper {
174 private final SVNWCClient myWcClient;
176 private UUIDHelper(final SvnVcs vcs) {
177 myWcClient = vcs.createWCClient();
181 * passed dir must be under VC control (it is assumed)
183 @Nullable
184 public String getRepositoryUUID(final VirtualFile dir) {
185 try {
186 final SVNInfo info1 = myWcClient.doInfo(new File(dir.getPath()), SVNRevision.WORKING);
187 if (info1 == null || info1.getRepositoryUUID() == null) {
188 // go deeper if current parent was added (if parent was added, it theoretically could NOT know its repo UUID)
189 final VirtualFile parent = dir.getParent();
190 if (parent == null) {
191 return null;
193 if (isPendingAdd(parent)) {
194 return getRepositoryUUID(parent);
196 } else {
197 return info1.getRepositoryUUID();
199 } catch (SVNException e) {
200 // go to return default
202 return null;
206 public boolean move(VirtualFile file, VirtualFile toDir) throws IOException {
207 File srcFile = getIOFile(file);
208 File dstFile = new File(getIOFile(toDir), file.getName());
210 final SvnVcs vcs = getVCS(toDir);
211 final SvnVcs sourceVcs = getVCS(file);
212 if (vcs == null && sourceVcs == null) return false;
214 if (vcs == null) {
215 return false;
217 if (sourceVcs == null) {
218 return createItem(toDir, file.getName(), file.isDirectory(), true);
221 if (isPendingAdd(toDir)) {
222 myMovedFiles.add(new MovedFileInfo(sourceVcs.getProject(), srcFile, dstFile));
223 return true;
225 else {
226 final VirtualFile oldParent = file.getParent();
227 myFilesToRefresh.add(oldParent);
228 myFilesToRefresh.add(toDir);
229 return doMove(sourceVcs, srcFile, dstFile);
233 public boolean rename(VirtualFile file, String newName) throws IOException {
234 File srcFile = getIOFile(file);
235 File dstFile = new File(srcFile.getParentFile(), newName);
236 SvnVcs vcs = getVCS(file);
237 if (vcs != null) {
238 myFilesToRefresh.add(file.getParent());
239 return doMove(vcs, srcFile, dstFile);
241 return false;
244 private boolean doMove(@NotNull SvnVcs vcs, final File src, final File dst) {
245 SVNMoveClient mover = vcs.createMoveClient();
246 long srcTime = src.lastModified();
247 try {
248 // delete old??? (deleted in listener, but we could do it here)
249 final String list = SvnChangelistListener.getCurrentMapping(vcs.getProject(), src);
250 if (isUndo(vcs)) {
251 myUndoingMove = true;
252 restoreFromUndoStorage(dst);
253 mover.undoMove(src, dst);
255 else {
256 // if src is not under version control, do usual move.
257 SVNStatus srcStatus = getFileStatus(vcs, src);
258 if (srcStatus == null || srcStatus.getContentsStatus() == SVNStatusType.STATUS_UNVERSIONED ||
259 srcStatus.getContentsStatus() == SVNStatusType.STATUS_EXTERNAL ||
260 srcStatus.getContentsStatus() == SVNStatusType.STATUS_MISSING ||
261 srcStatus.getContentsStatus() == SVNStatusType.STATUS_OBSTRUCTED) {
262 return false;
264 // todo move back?
265 mover.doMove(src, dst);
266 if (list != null) {
267 SvnChangelistListener.putUnderList(vcs.getProject(), list, dst);
270 dst.setLastModified(srcTime);
272 catch (SVNException e) {
273 addToMoveExceptions(vcs.getProject(), e);
274 return false;
276 return true;
279 private void restoreFromUndoStorage(final File dst) {
280 String normPath = FileUtil.toSystemIndependentName(dst.getPath());
281 for (Iterator<Pair<File, File>> it = myUndoStorageContents.iterator(); it.hasNext();) {
282 Pair<File, File> e = it.next();
283 final String p = FileUtil.toSystemIndependentName(e.first.getPath());
284 if (p.startsWith(normPath)) {
285 try {
286 FileUtil.rename(e.second, e.first);
288 catch (IOException ex) {
289 LOG.error(ex);
290 FileUtil.asyncDelete(e.second);
292 it.remove();
295 if (myStorageForUndo != null) {
296 final File[] files = myStorageForUndo.listFiles();
297 if (files == null || files.length == 0) {
298 FileUtil.asyncDelete(myStorageForUndo);
299 myStorageForUndo = null;
305 public boolean createFile(VirtualFile dir, String name) throws IOException {
306 return createItem(dir, name, false, false);
309 public boolean createDirectory(VirtualFile dir, String name) throws IOException {
310 return createItem(dir, name, true, false);
314 * delete file or directory (both 'undo' and 'do' modes)
315 * unversioned: do nothing, return false
316 * obstructed: do nothing, return false
317 * external or wc root: do nothing, return false
318 * missing: do nothing, return false
319 * <p/>
320 * versioned: schedule for deletion, return true
321 * added: schedule for deletion (make unversioned), return true
322 * copied, but not scheduled: schedule for deletion, return true
323 * replaced: schedule for deletion, return true
324 * <p/>
325 * deleted: do nothing, return true (strange)
327 public boolean delete(VirtualFile file) throws IOException {
328 SvnVcs vcs = getVCS(file);
329 if (vcs != null && SvnUtil.isAdminDirectory(file)) {
330 return true;
332 File ioFile = getIOFile(file);
333 if (!SVNWCUtil.isVersionedDirectory(ioFile.getParentFile())) {
334 return false;
336 try {
337 if (SVNWCUtil.isWorkingCopyRoot(ioFile)) {
338 return false;
340 } catch (SVNException e) {
344 SVNStatus status = getFileStatus(ioFile);
346 if (status == null ||
347 status.getContentsStatus() == SVNStatusType.STATUS_UNVERSIONED ||
348 status.getContentsStatus() == SVNStatusType.STATUS_OBSTRUCTED ||
349 status.getContentsStatus() == SVNStatusType.STATUS_MISSING ||
350 status.getContentsStatus() == SVNStatusType.STATUS_EXTERNAL) {
351 return false;
352 } else if (status.getContentsStatus() == SVNStatusType.STATUS_IGNORED) {
353 if (vcs != null && file.getParent() != null) {
354 if (! isUndoOrRedo(vcs.getProject())) {
355 putIgnoreInfo(file, vcs, ioFile);
358 return false;
360 else if (status.getContentsStatus() == SVNStatusType.STATUS_DELETED) {
361 if (isUndo(vcs)) {
362 moveToUndoStorage(file);
364 return true;
366 else {
367 if (vcs != null) {
368 if (status.getContentsStatus() == SVNStatusType.STATUS_ADDED) {
369 try {
370 final SVNWCClient wcClient = vcs.createWCClient();
371 wcClient.doRevert(ioFile, false);
373 catch (SVNException e) {
374 // ignore
377 else {
378 myDeletedFiles.add(new DeletedFileInfo(vcs.getProject(), ioFile));
379 // packages deleted from disk should not be deleted from svn (IDEADEV-16066)
380 if (file.isDirectory() || isUndo(vcs)) return true;
383 return false;
387 private void putIgnoreInfo(VirtualFile file, SvnVcs vcs, File ioFile) {
388 final String key = file.getParent().getPresentableUrl();
389 Map<String, IgnoredFileInfo> map = myIgnoredInfo.get(vcs.getProject());
390 if (map != null) {
391 final IgnoredFileInfo info = map.get(key);
392 if (info != null) {
393 info.addFileName(file.getName());
394 return;
397 final Set<String> existingPatterns = SvnPropertyService.getIgnoreStringsUnder(vcs, file.getParent());
398 if (existingPatterns != null) {
399 if (map == null) {
400 map = new HashMap<String, IgnoredFileInfo>();
401 myIgnoredInfo.put(vcs.getProject(), map);
403 final File parentIo = ioFile.getParentFile();
404 final IgnoredFileInfo info = new IgnoredFileInfo(parentIo, existingPatterns);
405 info.addFileName(file.getName());
406 map.put(key, info);
410 private void moveToUndoStorage(final VirtualFile file) {
411 if (myStorageForUndo == null) {
412 try {
413 myStorageForUndo = FileUtil.createTempDirectory("svnUndoStorage", "");
415 catch (IOException e) {
416 LOG.error(e);
417 return;
420 final File tmpFile = FileUtil.findSequentNonexistentFile(myStorageForUndo, "tmp", "");
421 myUndoStorageContents.add(0, new Pair<File, File>(new File(file.getPath()), tmpFile));
422 new File(file.getPath()).renameTo(tmpFile);
426 * add file or directory:
427 * <p/>
428 * parent directory is:
429 * unversioned: do nothing, return false
430 * versioned:
431 * entry is:
432 * null: create entry, schedule for addition
433 * missing: do nothing, return false
434 * deleted, 'do' mode: try to create entry and it schedule for addition if kind is the same, otherwise do nothing, return false.
435 * deleted: 'undo' mode: try to revert non-recursively, if kind is the same, otherwise do nothing, return false.
436 * anything else: return false.
438 private boolean createItem(VirtualFile dir, String name, boolean directory, final boolean recursive) {
439 SvnVcs vcs = getVCS(dir);
440 if (vcs == null) {
441 return false;
443 if (isUndo(vcs) && SvnUtil.isAdminDirectory(dir, name)) {
444 return false;
446 File ioDir = getIOFile(dir);
447 boolean pendingAdd = isPendingAdd(dir);
448 if (!SVNWCUtil.isVersionedDirectory(ioDir) && !pendingAdd) {
449 return false;
451 SVNWCClient wcClient = vcs.createWCClient();
452 File targetFile = new File(ioDir, name);
453 SVNStatus status = getFileStatus(vcs, targetFile);
455 if (status == null || status.getContentsStatus() == SVNStatusType.STATUS_NONE) {
456 myAddedFiles.add(new AddedFileInfo(vcs.getProject(), dir, name, null, recursive));
457 return false;
459 else if (status.getContentsStatus() == SVNStatusType.STATUS_MISSING) {
460 return false;
462 else if (status.getContentsStatus() == SVNStatusType.STATUS_DELETED) {
463 SVNNodeKind kind = status.getKind();
464 // kind differs.
465 if (directory && kind != SVNNodeKind.DIR || !directory && kind != SVNNodeKind.FILE) {
466 return false;
468 try {
469 if (isUndo(vcs)) {
470 wcClient.doRevert(targetFile, false);
471 return true;
473 myAddedFiles.add(new AddedFileInfo(vcs.getProject(), dir, name, null, recursive));
474 return false;
476 catch (SVNException e) {
477 SVNFileUtil.deleteAll(targetFile, true);
478 return false;
481 return false;
484 private boolean isPendingAdd(final VirtualFile dir) {
485 for(AddedFileInfo i: myAddedFiles) {
486 if (i.myDir == dir.getParent() && i.myName.equals(dir.getName())) {
487 return true;
490 return false;
493 public void commandStarted(CommandEvent event) {
494 myUndoingMove = false;
495 final Project project = event.getProject();
496 if (project == null) return;
497 commandStarted(project);
500 void commandStarted(final Project project) {
501 myUndoingMove = false;
502 myMoveExceptions.remove(project);
503 myIgnoredInfo.remove(project);
506 public void commandFinished(CommandEvent event) {
507 final Project project = event.getProject();
508 if (project == null) return;
509 commandFinished(project);
512 void commandFinished(final Project project) {
513 if (!myAddedFiles.isEmpty()) {
514 processAddedFiles(project);
516 processMovedFiles(project);
517 if (!myDeletedFiles.isEmpty()) {
518 processDeletedFiles(project);
521 final List<VcsException> exceptionList = myMoveExceptions.get(project);
522 if (exceptionList != null && ! exceptionList.isEmpty()) {
523 AbstractVcsHelper.getInstance(project).showErrors(exceptionList, SvnBundle.message("move.files.errors.title"));
526 dealWithIgnorePatterns(project);
528 if (!myFilesToRefresh.isEmpty()) {
529 refreshFiles(project);
533 private void dealWithIgnorePatterns(Project project) {
534 final Map<String, IgnoredFileInfo> map = myIgnoredInfo.get(project);
535 if (map != null) {
536 final SvnVcs vcs = SvnVcs.getInstance(project);
537 final ProgressManager progressManager = ProgressManager.getInstance();
538 final Runnable prepare = new Runnable() {
539 public void run() {
540 for (Iterator<String> iterator = map.keySet().iterator(); iterator.hasNext();) {
541 final String key = iterator.next();
542 final IgnoredFileInfo info = map.get(key);
543 info.calculatePatterns(vcs);
544 if (info.getPatterns().isEmpty()) {
545 iterator.remove();
550 progressManager.runProcessWithProgressSynchronously(prepare, SvnBundle.message("gather.ignore.patterns.info.progress.title"), false, project);
551 if (map.isEmpty()) return;
553 final SelectIgnorePatternsToRemoveOnDeleteDialog dialog = new SelectIgnorePatternsToRemoveOnDeleteDialog(project, map);
554 dialog.show();
555 final Collection<IgnoredFileInfo> result = dialog.getResult();
556 if (dialog.getExitCode() == DialogWrapper.OK_EXIT_CODE && ! result.isEmpty()) {
557 final List<VcsException> exceptions = new ArrayList<VcsException>(0);
559 final Runnable deletePatterns = new Runnable() {
560 public void run() {
561 for (IgnoredFileInfo info : result) {
562 try {
563 info.getOldPatterns().removeAll(info.getPatterns());
564 SvnPropertyService.setIgnores(vcs, info.getOldPatterns(), info.getFile());
566 catch (SVNException e) {
567 exceptions.add(new VcsException(e));
572 progressManager.runProcessWithProgressSynchronously(deletePatterns, "Removing selected 'svn:ignore' patterns", false, project);
573 if (! exceptions.isEmpty()) {
574 AbstractVcsHelper.getInstance(project).showErrors(exceptions, SvnBundle.message("remove.ignore.patterns.errors.title"));
580 private void refreshFiles(final Project project) {
581 final List<VirtualFile> toRefreshFiles = new ArrayList<VirtualFile>();
582 final List<VirtualFile> toRefreshDirs = new ArrayList<VirtualFile>();
583 for (VirtualFile file : myFilesToRefresh) {
584 if (file == null) continue;
585 if (file.isDirectory()) {
586 toRefreshDirs.add(file);
587 } else {
588 toRefreshFiles.add(file);
591 // if refresh asynchronously, local changes would also be notified that they are dirty asynchronously,
592 // and commit could be executed while not all changes are visible
593 final RefreshSession session = RefreshQueue.getInstance().createSession(true, true, new Runnable() {
594 public void run() {
595 if (project.isDisposed()) return;
596 filterOutInvalid(toRefreshFiles);
597 filterOutInvalid(toRefreshDirs);
599 final VcsDirtyScopeManager vcsDirtyScopeManager = VcsDirtyScopeManager.getInstance(project);
600 vcsDirtyScopeManager.filesDirty(toRefreshFiles, toRefreshDirs);
603 filterOutInvalid(myFilesToRefresh);
604 session.addAllFiles(myFilesToRefresh);
605 session.launch();
606 myFilesToRefresh.clear();
609 private static void filterOutInvalid(final Collection<VirtualFile> files) {
610 for (Iterator<VirtualFile> iterator = files.iterator(); iterator.hasNext();) {
611 final VirtualFile file = iterator.next();
612 if (! file.isValid() || ! file.exists()) {
613 LOG.info("Refresh root is not valid: " + file.getPath());
614 iterator.remove();
619 private void processAddedFiles(Project project) {
620 SvnVcs vcs = SvnVcs.getInstance(project);
621 List<VirtualFile> addedVFiles = new ArrayList<VirtualFile>();
622 Map<VirtualFile, File> copyFromMap = new HashMap<VirtualFile, File>();
623 final Set<VirtualFile> recursiveItems = new HashSet<VirtualFile>();
624 for (Iterator<AddedFileInfo> it = myAddedFiles.iterator(); it.hasNext();) {
625 AddedFileInfo addedFileInfo = it.next();
626 if (addedFileInfo.myProject == project) {
627 it.remove();
628 final File ioFile = new File(getIOFile(addedFileInfo.myDir), addedFileInfo.myName);
629 VirtualFile addedFile = addedFileInfo.myDir.findChild(addedFileInfo.myName);
630 if (addedFile == null) {
631 addedFile = myLfs.refreshAndFindFileByIoFile(ioFile);
633 if (addedFile != null) {
634 final SVNStatus fileStatus = getFileStatus(vcs, ioFile);
635 if (fileStatus == null || fileStatus.getContentsStatus() != SVNStatusType.STATUS_IGNORED) {
636 boolean isIgnored = ChangeListManager.getInstance(addedFileInfo.myProject).isIgnoredFile(addedFile);
637 if (!isIgnored) {
638 addedVFiles.add(addedFile);
639 copyFromMap.put(addedFile, addedFileInfo.myCopyFrom);
640 if (addedFileInfo.myRecursive) {
641 recursiveItems.add(addedFile);
648 if (addedVFiles.isEmpty()) return;
649 final VcsShowConfirmationOption.Value value = vcs.getAddConfirmation().getValue();
650 if (value != VcsShowConfirmationOption.Value.DO_NOTHING_SILENTLY) {
651 final AbstractVcsHelper vcsHelper = AbstractVcsHelper.getInstance(project);
652 Collection<VirtualFile> filesToProcess;
653 if (value == VcsShowConfirmationOption.Value.DO_ACTION_SILENTLY) {
654 filesToProcess = addedVFiles;
656 else {
657 final String singleFilePrompt;
658 if (addedVFiles.size() == 1 && addedVFiles.get(0).isDirectory()) {
659 singleFilePrompt = SvnBundle.getString("confirmation.text.add.dir");
661 else {
662 singleFilePrompt = SvnBundle.getString("confirmation.text.add.file");
664 filesToProcess = vcsHelper.selectFilesToProcess(addedVFiles, SvnBundle.message("confirmation.title.add.multiple.files"),
665 null,
666 SvnBundle.message("confirmation.title.add.file"), singleFilePrompt,
667 vcs.getAddConfirmation());
669 if (filesToProcess != null) {
670 final List<VcsException> exceptions = new ArrayList<VcsException>();
671 SVNWCClient wcClient = vcs.createWCClient();
672 final SVNCopyClient copyClient = vcs.createCopyClient();
673 for(VirtualFile file: filesToProcess) {
674 final File ioFile = new File(file.getPath());
675 try {
676 final File copyFrom = copyFromMap.get(file);
677 if (copyFrom != null) {
678 try {
679 new ActionWithTempFile(ioFile) {
680 protected void executeInternal() throws VcsException {
681 try {
682 // not recursive
683 final SVNCopySource[] copySource = {new SVNCopySource(SVNRevision.WORKING, SVNRevision.WORKING, copyFrom)};
684 copyClient.doCopy(copySource, ioFile, false, true, true);
686 catch (SVNException e) {
687 throw new VcsException(e);
690 }.execute();
692 catch (VcsException e) {
693 exceptions.add(e);
696 else {
697 wcClient.doAdd(ioFile, true, false, false, true);
699 VcsDirtyScopeManager.getInstance(project).fileDirty(file);
701 catch (SVNException e) {
702 exceptions.add(new VcsException(e));
705 if (!exceptions.isEmpty()) {
706 vcsHelper.showErrors(exceptions, SvnBundle.message("add.files.errors.title"));
712 private void processDeletedFiles(Project project) {
713 final List<FilePath> deletedFiles = new ArrayList<FilePath>();
714 for (Iterator<DeletedFileInfo> it = myDeletedFiles.iterator(); it.hasNext();) {
715 DeletedFileInfo deletedFileInfo = it.next();
716 if (deletedFileInfo.myProject == project) {
717 it.remove();
718 final FilePath filePath = VcsContextFactory.SERVICE.getInstance().createFilePathOn(deletedFileInfo.myFile);
719 deletedFiles.add(filePath);
722 if (deletedFiles.isEmpty() || myUndoingMove) return;
723 SvnVcs vcs = SvnVcs.getInstance(project);
724 final VcsShowConfirmationOption.Value value = vcs.getDeleteConfirmation().getValue();
725 if (value != VcsShowConfirmationOption.Value.DO_NOTHING_SILENTLY) {
726 final AbstractVcsHelper vcsHelper = AbstractVcsHelper.getInstance(project);
727 Collection<FilePath> filesToProcess;
728 if (value == VcsShowConfirmationOption.Value.DO_ACTION_SILENTLY) {
729 filesToProcess = new ArrayList<FilePath>(deletedFiles);
731 else {
733 final String singleFilePrompt;
734 if (deletedFiles.size() == 1 && deletedFiles.get(0).isDirectory()) {
735 singleFilePrompt = SvnBundle.getString("confirmation.text.delete.dir");
737 else {
738 singleFilePrompt = SvnBundle.getString("confirmation.text.delete.file");
740 final Collection<FilePath> files = vcsHelper
741 .selectFilePathsToProcess(deletedFiles, SvnBundle.message("confirmation.title.delete.multiple.files"), null,
742 SvnBundle.message("confirmation.title.delete.file"), singleFilePrompt, vcs.getDeleteConfirmation());
743 filesToProcess = files == null ? null : new ArrayList<FilePath>(files);
745 if (filesToProcess != null) {
746 List<VcsException> exceptions = new ArrayList<VcsException>();
747 SVNWCClient wcClient = vcs.createWCClient();
748 for(FilePath file: filesToProcess) {
749 VirtualFile vFile = file.getVirtualFile(); // for deleted directories
750 File ioFile = new File(file.getPath());
751 try {
752 wcClient.doDelete(ioFile, true, false);
753 if (vFile != null && vFile.isValid() && vFile.isDirectory()) {
754 vFile.refresh(true, true);
755 VcsDirtyScopeManager.getInstance(project).dirDirtyRecursively(vFile);
757 else {
758 VcsDirtyScopeManager.getInstance(project).fileDirty(file);
761 catch (SVNException e) {
762 exceptions.add(new VcsException(e));
765 if (!exceptions.isEmpty()) {
766 vcsHelper.showErrors(exceptions, SvnBundle.message("delete.files.errors.title"));
769 for (FilePath file : deletedFiles) {
770 final FilePath parent = file.getParentPath();
771 if (parent != null) {
772 myFilesToRefresh.add(parent.getVirtualFile());
775 if (filesToProcess != null) {
776 deletedFiles.removeAll(filesToProcess);
778 for (FilePath file : deletedFiles) {
779 FileUtil.delete(file.getIOFile());
784 private void processMovedFiles(final Project project) {
785 for (Iterator<MovedFileInfo> iterator = myMovedFiles.iterator(); iterator.hasNext();) {
786 MovedFileInfo movedFileInfo = iterator.next();
787 if (movedFileInfo.myProject == project) {
788 doMove(SvnVcs.getInstance(project), movedFileInfo.mySrc, movedFileInfo.myDst);
789 iterator.remove();
794 @Nullable
795 private static SvnVcs getVCS(VirtualFile file) {
796 Project[] projects = ProjectManager.getInstance().getOpenProjects();
797 for (Project project : projects) {
798 AbstractVcs vcs = ProjectLevelVcsManager.getInstance(project).getVcsFor(file);
799 if (vcs instanceof SvnVcs) {
800 return (SvnVcs)vcs;
803 return null;
807 private static File getIOFile(VirtualFile vf) {
808 return new File(vf.getPath()).getAbsoluteFile();
811 @Nullable
812 private static SVNStatus getFileStatus(File file) {
813 final SVNClientManager clientManager = SVNClientManager.newInstance();
814 try {
815 SVNStatusClient stClient = clientManager.getStatusClient();
816 return getFileStatus(file, stClient);
818 finally {
819 clientManager.dispose();
823 @Nullable
824 private static SVNStatus getFileStatus(SvnVcs vcs, File file) {
825 SVNStatusClient stClient = vcs.createStatusClient();
826 return getFileStatus(file, stClient);
829 @Nullable
830 private static SVNStatus getFileStatus(final File file, final SVNStatusClient stClient) {
831 try {
832 return stClient.doStatus(file, false);
834 catch (SVNException e) {
835 return null;
839 private static boolean isUndoOrRedo(@NotNull final Project project) {
840 final UndoManager undoManager = UndoManager.getInstance(project);
841 return undoManager.isUndoInProgress() || undoManager.isRedoInProgress();
844 private static boolean isUndo(SvnVcs vcs) {
845 if (vcs == null || vcs.getProject() == null) {
846 return false;
848 Project p = vcs.getProject();
849 return UndoManager.getInstance(p).isUndoInProgress();
852 public void afterDone(final ThrowableConsumer<LocalFileOperationsHandler, IOException> invoker) {