IDEADEV-40382 (On creating files under .idea directory they are not added to VCS)
[fedora-idea.git] / plugins / svn4idea / src / org / jetbrains / idea / svn / SvnFileSystemListener.java
blobcb888e36bb413db2f59e8ab8dcb741462f16d121
1 /**
2 * @copyright
3 * ====================================================================
4 * Copyright (c) 2003-2004 QintSoft. All rights reserved.
6 * This software is licensed as described in the file COPYING, which
7 * you should have received as part of this distribution. The terms
8 * are also available at http://subversion.tigris.org/license-1.html.
9 * If newer versions of this license are posted there, you may use a
10 * newer version instead, at your option.
12 * This software consists of voluntary contributions made by many
13 * individuals. For exact contribution history, see the revision
14 * history and logs, available at http://svnup.tigris.org/.
15 * ====================================================================
16 * @endcopyright
19 * Copyright 2000-2005 JetBrains s.r.o.
21 * Licensed under the Apache License, Version 2.0 (the "License");
22 * you may not use this file except in compliance with the License.
23 * You may obtain a copy of the License at
25 * http://www.apache.org/licenses/LICENSE-2.0
27 * Unless required by applicable law or agreed to in writing, software
28 * distributed under the License is distributed on an "AS IS" BASIS,
29 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
30 * See the License for the specific language governing permissions and
31 * limitations under the License.
33 package org.jetbrains.idea.svn;
35 import com.intellij.openapi.command.CommandEvent;
36 import com.intellij.openapi.command.CommandListener;
37 import com.intellij.openapi.command.undo.UndoManager;
38 import com.intellij.openapi.diagnostic.Logger;
39 import com.intellij.openapi.progress.ProgressManager;
40 import com.intellij.openapi.project.Project;
41 import com.intellij.openapi.project.ProjectManager;
42 import com.intellij.openapi.ui.DialogWrapper;
43 import com.intellij.openapi.util.Pair;
44 import com.intellij.openapi.util.io.FileUtil;
45 import com.intellij.openapi.vcs.*;
46 import com.intellij.openapi.vcs.actions.VcsContextFactory;
47 import com.intellij.openapi.vcs.changes.ChangeListManager;
48 import com.intellij.openapi.vcs.changes.VcsDirtyScopeManager;
49 import com.intellij.openapi.vfs.LocalFileOperationsHandler;
50 import com.intellij.openapi.vfs.LocalFileSystem;
51 import com.intellij.openapi.vfs.VirtualFile;
52 import com.intellij.openapi.vfs.newvfs.RefreshQueue;
53 import com.intellij.openapi.vfs.newvfs.RefreshSession;
54 import com.intellij.util.ThrowableConsumer;
55 import com.intellij.vcsUtil.ActionWithTempFile;
56 import org.jetbrains.annotations.NotNull;
57 import org.jetbrains.annotations.Nullable;
58 import org.jetbrains.idea.svn.dialogs.SelectIgnorePatternsToRemoveOnDeleteDialog;
59 import org.jetbrains.idea.svn.ignore.SvnPropertyService;
60 import org.tmatesoft.svn.core.SVNErrorCode;
61 import org.tmatesoft.svn.core.SVNException;
62 import org.tmatesoft.svn.core.SVNNodeKind;
63 import org.tmatesoft.svn.core.internal.wc.SVNFileUtil;
64 import org.tmatesoft.svn.core.wc.*;
66 import java.io.File;
67 import java.io.IOException;
68 import java.util.*;
70 public class SvnFileSystemListener implements LocalFileOperationsHandler, CommandListener {
71 private static final Logger LOG = Logger.getInstance("#org.jetbrains.idea.svn.SvnFileSystemListener");
72 private LocalFileSystem myLfs;
74 private static class AddedFileInfo {
75 private final Project myProject;
76 private final VirtualFile myDir;
77 private final String myName;
78 @Nullable private final File myCopyFrom;
79 private final boolean myRecursive;
81 public AddedFileInfo(final Project project, final VirtualFile dir, final String name, @Nullable final File copyFrom, boolean recursive) {
82 myProject = project;
83 myDir = dir;
84 myName = name;
85 myCopyFrom = copyFrom;
86 myRecursive = recursive;
90 private static class DeletedFileInfo {
91 private final Project myProject;
92 private final File myFile;
94 public DeletedFileInfo(final Project project, final File file) {
95 myProject = project;
96 myFile = file;
100 private static class MovedFileInfo {
101 private final Project myProject;
102 private final File mySrc;
103 private final File myDst;
105 private MovedFileInfo(final Project project, final File src, final File dst) {
106 myProject = project;
107 mySrc = src;
108 myDst = dst;
112 private final Map<Project, Map<String, IgnoredFileInfo>> myIgnoredInfo = new HashMap<Project, Map<String, IgnoredFileInfo>>();
114 private final List<AddedFileInfo> myAddedFiles = new ArrayList<AddedFileInfo>();
115 private final List<DeletedFileInfo> myDeletedFiles = new ArrayList<DeletedFileInfo>();
116 private final List<MovedFileInfo> myMovedFiles = new ArrayList<MovedFileInfo>();
117 private final Map<Project, List<VcsException>> myMoveExceptions = new HashMap<Project, List<VcsException>>();
118 private final List<VirtualFile> myFilesToRefresh = new ArrayList<VirtualFile>();
119 @Nullable private File myStorageForUndo;
120 private final List<Pair<File, File>> myUndoStorageContents = new ArrayList<Pair<File, File>>();
121 private boolean myUndoingMove = false;
123 public SvnFileSystemListener() {
124 myLfs = LocalFileSystem.getInstance();
127 private void addToMoveExceptions(final Project project, final SVNException e) {
128 List<VcsException> exceptionList = myMoveExceptions.get(project);
129 if (exceptionList == null) {
130 exceptionList = new ArrayList<VcsException>();
131 myMoveExceptions.put(project, exceptionList);
133 VcsException vcsException;
134 if (SVNErrorCode.ENTRY_EXISTS.equals(e.getErrorMessage().getErrorCode())) {
135 vcsException = new VcsException(Arrays.asList("Target of move operation is already under version control.",
136 "Subversion move had not been performed. ", e.getMessage()));
137 } else {
138 vcsException = new VcsException(e);
140 exceptionList.add(vcsException);
143 @Nullable
144 public File copy(final VirtualFile file, final VirtualFile toDir, final String copyName) throws IOException {
145 SvnVcs vcs = getVCS(toDir);
146 if (vcs == null) {
147 vcs = getVCS(file);
149 if (vcs == null) {
150 return null;
153 File srcFile = new File(file.getPath());
154 File destFile = new File(new File(toDir.getPath()), copyName);
155 final boolean dstDirUnderControl = SVNWCUtil.isVersionedDirectory(destFile.getParentFile());
156 if ((! dstDirUnderControl) && !isPendingAdd(toDir)) {
157 return null;
160 if (!SVNWCUtil.isVersionedDirectory(srcFile.getParentFile())) {
161 myAddedFiles.add(new AddedFileInfo(vcs.getProject(), toDir, copyName, null, false));
162 return null;
165 final SVNStatus fileStatus = getFileStatus(vcs, srcFile);
166 if (fileStatus != null && fileStatus.getContentsStatus() == SVNStatusType.STATUS_ADDED) {
167 myAddedFiles.add(new AddedFileInfo(vcs.getProject(), toDir, copyName, null, false));
168 return null;
171 if (sameRoot(vcs, file.getParent(), toDir)) {
172 myAddedFiles.add(new AddedFileInfo(vcs.getProject(), toDir, copyName, srcFile, false));
173 return null;
176 myAddedFiles.add(new AddedFileInfo(vcs.getProject(), toDir, copyName, null, false));
177 return null;
180 private boolean sameRoot(final SvnVcs vcs, final VirtualFile srcDir, final VirtualFile dstDir) {
181 final UUIDHelper helper = new UUIDHelper(vcs);
182 final String srcUUID = helper.getRepositoryUUID(srcDir);
183 final String dstUUID = helper.getRepositoryUUID(dstDir);
185 return (srcUUID != null) && (dstUUID != null) && (srcUUID.equals(dstUUID));
188 private class UUIDHelper {
189 private final SVNWCClient myWcClient;
191 private UUIDHelper(final SvnVcs vcs) {
192 myWcClient = vcs.createWCClient();
196 * passed dir must be under VC control (it is assumed)
198 @Nullable
199 public String getRepositoryUUID(final VirtualFile dir) {
200 try {
201 final SVNInfo info1 = myWcClient.doInfo(new File(dir.getPath()), SVNRevision.WORKING);
202 if ((info1 == null) || (info1.getRepositoryUUID() == null)) {
203 // go deeper if current parent was added (if parent was added, it theoretically could NOT know its repo UUID)
204 final VirtualFile parent = dir.getParent();
205 if (parent == null) {
206 return null;
208 if (isPendingAdd(parent)) {
209 return getRepositoryUUID(parent);
211 } else {
212 return info1.getRepositoryUUID();
214 } catch (SVNException e) {
215 // go to return default
217 return null;
221 public boolean move(VirtualFile file, VirtualFile toDir) throws IOException {
222 File srcFile = getIOFile(file);
223 File dstFile = new File(getIOFile(toDir), file.getName());
225 final SvnVcs vcs = getVCS(toDir);
226 final SvnVcs sourceVcs = getVCS(file);
227 if (vcs == null && sourceVcs == null) return false;
229 if (vcs == null) {
230 return false;
232 if (sourceVcs == null) {
233 return createItem(toDir, file.getName(), file.isDirectory(), true);
236 if (isPendingAdd(toDir)) {
237 myMovedFiles.add(new MovedFileInfo(sourceVcs.getProject(), srcFile, dstFile));
238 return true;
240 else {
241 final VirtualFile oldParent = file.getParent();
242 myFilesToRefresh.add(oldParent);
243 myFilesToRefresh.add(toDir);
244 return doMove(sourceVcs, srcFile, dstFile);
248 public boolean rename(VirtualFile file, String newName) throws IOException {
249 File srcFile = getIOFile(file);
250 File dstFile = new File(srcFile.getParentFile(), newName);
251 SvnVcs vcs = getVCS(file);
252 if (vcs != null) {
253 myFilesToRefresh.add(file.getParent());
254 return doMove(vcs, srcFile, dstFile);
256 return false;
259 private boolean doMove(@NotNull SvnVcs vcs, final File src, final File dst) {
260 SVNMoveClient mover = vcs.createMoveClient();
261 long srcTime = src.lastModified();
262 try {
263 if (isUndo(vcs)) {
264 myUndoingMove = true;
265 restoreFromUndoStorage(dst);
266 mover.undoMove(src, dst);
268 else {
269 // if src is not under version control, do usual move.
270 SVNStatus srcStatus = getFileStatus(vcs, src);
271 if (srcStatus == null || srcStatus.getContentsStatus() == SVNStatusType.STATUS_UNVERSIONED ||
272 srcStatus.getContentsStatus() == SVNStatusType.STATUS_EXTERNAL ||
273 srcStatus.getContentsStatus() == SVNStatusType.STATUS_MISSING ||
274 srcStatus.getContentsStatus() == SVNStatusType.STATUS_OBSTRUCTED) {
275 return false;
277 // todo move back?
278 mover.doMove(src, dst);
280 dst.setLastModified(srcTime);
282 vcs.pathChanged(src, dst);
284 catch (SVNException e) {
285 addToMoveExceptions(vcs.getProject(), e);
286 return false;
288 return true;
291 private void restoreFromUndoStorage(final File dst) {
292 String normPath = FileUtil.toSystemIndependentName(dst.getPath());
293 for (Iterator<Pair<File, File>> it = myUndoStorageContents.iterator(); it.hasNext();) {
294 Pair<File, File> e = it.next();
295 final String p = FileUtil.toSystemIndependentName(e.first.getPath());
296 if (p.startsWith(normPath)) {
297 try {
298 FileUtil.rename(e.second, e.first);
300 catch (IOException ex) {
301 LOG.error(ex);
302 FileUtil.asyncDelete(e.second);
304 it.remove();
307 if (myStorageForUndo != null) {
308 final File[] files = myStorageForUndo.listFiles();
309 if (files == null || files.length == 0) {
310 FileUtil.asyncDelete(myStorageForUndo);
311 myStorageForUndo = null;
317 public boolean createFile(VirtualFile dir, String name) throws IOException {
318 return createItem(dir, name, false, false);
321 public boolean createDirectory(VirtualFile dir, String name) throws IOException {
322 return createItem(dir, name, true, false);
326 * delete file or directory (both 'undo' and 'do' modes)
327 * unversioned: do nothing, return false
328 * obstructed: do nothing, return false
329 * external or wc root: do nothing, return false
330 * missing: do nothing, return false
331 * <p/>
332 * versioned: schedule for deletion, return true
333 * added: schedule for deletion (make unversioned), return true
334 * copied, but not scheduled: schedule for deletion, return true
335 * replaced: schedule for deletion, return true
336 * <p/>
337 * deleted: do nothing, return true (strange)
339 public boolean delete(VirtualFile file) throws IOException {
340 SvnVcs vcs = getVCS(file);
341 if (vcs != null && SvnUtil.isAdminDirectory(file)) {
342 return true;
344 File ioFile = getIOFile(file);
345 if (!SVNWCUtil.isVersionedDirectory(ioFile.getParentFile())) {
346 return false;
348 else {
349 try {
350 if (SVNWCUtil.isWorkingCopyRoot(ioFile)) {
351 return false;
353 } catch (SVNException e) {
358 SVNStatus status = getFileStatus(ioFile);
360 if (status == null ||
361 status.getContentsStatus() == SVNStatusType.STATUS_UNVERSIONED ||
362 status.getContentsStatus() == SVNStatusType.STATUS_OBSTRUCTED ||
363 status.getContentsStatus() == SVNStatusType.STATUS_MISSING ||
364 status.getContentsStatus() == SVNStatusType.STATUS_EXTERNAL) {
365 return false;
366 } else if (status.getContentsStatus() == SVNStatusType.STATUS_IGNORED) {
367 if (vcs != null && (file.getParent() != null)) {
368 if (! isUndoOrRedo(vcs.getProject())) {
369 putIgnoreInfo(file, vcs, ioFile);
372 return false;
374 else if (status.getContentsStatus() == SVNStatusType.STATUS_DELETED) {
375 if (isUndo(vcs)) {
376 moveToUndoStorage(file);
378 return true;
380 else {
381 if (vcs != null) {
382 if (status.getContentsStatus() == SVNStatusType.STATUS_ADDED) {
383 try {
384 final SVNWCClient wcClient = vcs.createWCClient();
385 wcClient.doRevert(ioFile, false);
387 catch (SVNException e) {
388 // ignore
391 else {
392 myDeletedFiles.add(new DeletedFileInfo(vcs.getProject(), ioFile));
393 // packages deleted from disk should not be deleted from svn (IDEADEV-16066)
394 if (file.isDirectory() || isUndo(vcs)) return true;
397 return false;
401 private void putIgnoreInfo(VirtualFile file, SvnVcs vcs, File ioFile) {
402 final String key = file.getParent().getPresentableUrl();
403 Map<String, IgnoredFileInfo> map = myIgnoredInfo.get(vcs.getProject());
404 if (map != null) {
405 final IgnoredFileInfo info = map.get(key);
406 if (info != null) {
407 info.addFileName(file.getName());
408 return;
411 final Set<String> existingPatterns = SvnPropertyService.getIgnoreStringsUnder(vcs, file.getParent());
412 if (existingPatterns != null) {
413 if (map == null) {
414 map = new HashMap<String, IgnoredFileInfo>();
415 myIgnoredInfo.put(vcs.getProject(), map);
417 final File parentIo = ioFile.getParentFile();
418 final IgnoredFileInfo info = new IgnoredFileInfo(parentIo, existingPatterns);
419 info.addFileName(file.getName());
420 map.put(key, info);
424 private void moveToUndoStorage(final VirtualFile file) {
425 if (myStorageForUndo == null) {
426 try {
427 myStorageForUndo = FileUtil.createTempDirectory("svnUndoStorage", "");
429 catch (IOException e) {
430 LOG.error(e);
431 return;
434 final File tmpFile = FileUtil.findSequentNonexistentFile(myStorageForUndo, "tmp", "");
435 myUndoStorageContents.add(0, new Pair<File, File>(new File(file.getPath()), tmpFile));
436 new File(file.getPath()).renameTo(tmpFile);
440 * add file or directory:
441 * <p/>
442 * parent directory is:
443 * unversioned: do nothing, return false
444 * versioned:
445 * entry is:
446 * null: create entry, schedule for addition
447 * missing: do nothing, return false
448 * deleted, 'do' mode: try to create entry and it schedule for addition if kind is the same, otherwise do nothing, return false.
449 * deleted: 'undo' mode: try to revert non-recursively, if kind is the same, otherwise do nothing, return false.
450 * anything else: return false.
452 private boolean createItem(VirtualFile dir, String name, boolean directory, final boolean recursive) throws IOException {
453 SvnVcs vcs = getVCS(dir);
454 if (vcs == null) {
455 return false;
457 if (isUndo(vcs) && SvnUtil.isAdminDirectory(dir, name)) {
458 return false;
460 File ioDir = getIOFile(dir);
461 boolean pendingAdd = isPendingAdd(dir);
462 if (!SVNWCUtil.isVersionedDirectory(ioDir) && !pendingAdd) {
463 return false;
465 SVNWCClient wcClient = vcs.createWCClient();
466 File targetFile = new File(ioDir, name);
467 SVNStatus status = getFileStatus(vcs, targetFile);
469 if (status == null || status.getContentsStatus() == SVNStatusType.STATUS_NONE) {
470 myAddedFiles.add(new AddedFileInfo(vcs.getProject(), dir, name, null, recursive));
471 return false;
473 else if (status.getContentsStatus() == SVNStatusType.STATUS_MISSING) {
474 return false;
476 else if (status.getContentsStatus() == SVNStatusType.STATUS_DELETED) {
477 SVNNodeKind kind = status.getKind();
478 // kind differs.
479 if ((directory && kind != SVNNodeKind.DIR) || (!directory && kind != SVNNodeKind.FILE)) {
480 return false;
482 try {
483 if (isUndo(vcs)) {
484 wcClient.doRevert(targetFile, false);
485 return true;
487 myAddedFiles.add(new AddedFileInfo(vcs.getProject(), dir, name, null, recursive));
488 return false;
490 catch (SVNException e) {
491 SVNFileUtil.deleteAll(targetFile, true);
492 return false;
495 return false;
498 private boolean isPendingAdd(final VirtualFile dir) {
499 for(AddedFileInfo i: myAddedFiles) {
500 if (i.myDir == dir.getParent() && i.myName.equals(dir.getName())) {
501 return true;
504 return false;
507 public void commandStarted(CommandEvent event) {
508 myUndoingMove = false;
509 final Project project = event.getProject();
510 if (project == null) return;
511 commandStarted(project);
514 void commandStarted(final Project project) {
515 myUndoingMove = false;
516 myMoveExceptions.remove(project);
517 myIgnoredInfo.remove(project);
520 public void beforeCommandFinished(CommandEvent event) {
523 public void commandFinished(CommandEvent event) {
524 final Project project = event.getProject();
525 if (project == null) return;
526 commandFinished(project);
529 void commandFinished(final Project project) {
530 if (myAddedFiles.size() > 0) {
531 processAddedFiles(project);
533 if (myDeletedFiles.size() > 0) {
534 processDeletedFiles(project);
536 processMovedFiles(project);
538 final List<VcsException> exceptionList = myMoveExceptions.get(project);
539 if ((exceptionList != null) && (! exceptionList.isEmpty())) {
540 AbstractVcsHelper.getInstance(project).showErrors(exceptionList, SvnBundle.message("move.files.errors.title"));
543 dealWithIgnorePatterns(project);
545 if (myFilesToRefresh.size() > 0) {
546 refreshFiles(project);
550 private void dealWithIgnorePatterns(Project project) {
551 final Map<String, IgnoredFileInfo> map = myIgnoredInfo.get(project);
552 if (map != null) {
553 final SvnVcs vcs = SvnVcs.getInstance(project);
554 final ProgressManager progressManager = ProgressManager.getInstance();
555 final Runnable prepare = new Runnable() {
556 public void run() {
557 for (Iterator<String> iterator = map.keySet().iterator(); iterator.hasNext();) {
558 final String key = iterator.next();
559 final IgnoredFileInfo info = map.get(key);
560 info.calculatePatterns(vcs);
561 if (info.getPatterns().isEmpty()) {
562 iterator.remove();
567 progressManager.runProcessWithProgressSynchronously(prepare, SvnBundle.message("gather.ignore.patterns.info.progress.title"), false, project);
568 if (map.isEmpty()) return;
570 final SelectIgnorePatternsToRemoveOnDeleteDialog dialog = new SelectIgnorePatternsToRemoveOnDeleteDialog(project, map);
571 dialog.show();
572 final Collection<IgnoredFileInfo> result = dialog.getResult();
573 if ((dialog.getExitCode() == DialogWrapper.OK_EXIT_CODE) && (! result.isEmpty())) {
574 final List<VcsException> exceptions = new ArrayList<VcsException>(0);
576 final Runnable deletePatterns = new Runnable() {
577 public void run() {
578 for (IgnoredFileInfo info : result) {
579 try {
580 info.getOldPatterns().removeAll(info.getPatterns());
581 SvnPropertyService.setIgnores(vcs, info.getOldPatterns(), info.getFile());
583 catch (SVNException e) {
584 exceptions.add(new VcsException(e));
589 progressManager.runProcessWithProgressSynchronously(deletePatterns, "Removing selected 'svn:ignore' patterns", false, project);
590 if (! exceptions.isEmpty()) {
591 AbstractVcsHelper.getInstance(project).showErrors(exceptions, SvnBundle.message("remove.ignore.patterns.errors.title"));
597 private void refreshFiles(final Project project) {
598 final List<VirtualFile> toRefreshFiles = new ArrayList<VirtualFile>();
599 final List<VirtualFile> toRefreshDirs = new ArrayList<VirtualFile>();
600 for (VirtualFile file : myFilesToRefresh) {
601 if (file == null) continue;
602 if (file.isDirectory()) {
603 toRefreshDirs.add(file);
604 } else {
605 toRefreshFiles.add(file);
608 // if refresh asynchronously, local changes would also be notified that they are dirty asynchronously,
609 // and commit could be executed while not all changes are visible
610 final RefreshSession session = RefreshQueue.getInstance().createSession(false, true, new Runnable() {
611 public void run() {
612 if (project.isDisposed()) return;
613 filterOutInvalid(toRefreshFiles);
614 filterOutInvalid(toRefreshDirs);
616 final VcsDirtyScopeManager vcsDirtyScopeManager = VcsDirtyScopeManager.getInstance(project);
617 vcsDirtyScopeManager.filesDirty(toRefreshFiles, toRefreshDirs);
620 filterOutInvalid(myFilesToRefresh);
621 session.addAllFiles(myFilesToRefresh);
622 session.launch();
623 myFilesToRefresh.clear();
626 private void filterOutInvalid(final Collection<VirtualFile> files) {
627 for (Iterator<VirtualFile> iterator = files.iterator(); iterator.hasNext();) {
628 final VirtualFile file = iterator.next();
629 if ((! file.isValid()) || (! file.exists())) {
630 LOG.info("Refresh root is not valid: " + file.getPath());
631 iterator.remove();
636 private void processAddedFiles(Project project) {
637 SvnVcs vcs = SvnVcs.getInstance(project);
638 List<VirtualFile> addedVFiles = new ArrayList<VirtualFile>();
639 Map<VirtualFile, File> copyFromMap = new HashMap<VirtualFile, File>();
640 final Set<VirtualFile> recursiveItems = new HashSet<VirtualFile>();
641 for (Iterator<AddedFileInfo> it = myAddedFiles.iterator(); it.hasNext();) {
642 AddedFileInfo addedFileInfo = it.next();
643 if (addedFileInfo.myProject == project) {
644 it.remove();
645 final File ioFile = new File(getIOFile(addedFileInfo.myDir), addedFileInfo.myName);
646 VirtualFile addedFile = addedFileInfo.myDir.findChild(addedFileInfo.myName);
647 if (addedFile == null) {
648 addedFile = myLfs.refreshAndFindFileByIoFile(ioFile);
650 if (addedFile != null) {
651 final SVNStatus fileStatus = getFileStatus(vcs, ioFile);
652 if (fileStatus == null || fileStatus.getContentsStatus() != SVNStatusType.STATUS_IGNORED) {
653 boolean isIgnored = ChangeListManager.getInstance(addedFileInfo.myProject).isIgnoredFile(addedFile);
654 if (!isIgnored) {
655 addedVFiles.add(addedFile);
656 copyFromMap.put(addedFile, addedFileInfo.myCopyFrom);
657 if (addedFileInfo.myRecursive) {
658 recursiveItems.add(addedFile);
665 if (addedVFiles.size() == 0) return;
666 final VcsShowConfirmationOption.Value value = vcs.getAddConfirmation().getValue();
667 if (value != VcsShowConfirmationOption.Value.DO_NOTHING_SILENTLY) {
668 final AbstractVcsHelper vcsHelper = AbstractVcsHelper.getInstance(project);
669 Collection<VirtualFile> filesToProcess;
670 if (value == VcsShowConfirmationOption.Value.DO_ACTION_SILENTLY) {
671 filesToProcess = addedVFiles;
673 else {
674 final String singleFilePrompt;
675 if (addedVFiles.size() == 1 && addedVFiles.get(0).isDirectory()) {
676 singleFilePrompt = SvnBundle.message("confirmation.text.add.dir");
678 else {
679 singleFilePrompt = SvnBundle.message("confirmation.text.add.file");
681 filesToProcess = vcsHelper.selectFilesToProcess(addedVFiles, SvnBundle.message("confirmation.title.add.multiple.files"),
682 null,
683 SvnBundle.message("confirmation.title.add.file"), singleFilePrompt,
684 vcs.getAddConfirmation());
686 if (filesToProcess != null) {
687 final List<VcsException> exceptions = new ArrayList<VcsException>();
688 SVNWCClient wcClient = vcs.createWCClient();
689 final SVNCopyClient copyClient = vcs.createCopyClient();
690 for(VirtualFile file: filesToProcess) {
691 final File ioFile = new File(file.getPath());
692 try {
693 final File copyFrom = copyFromMap.get(file);
694 if (copyFrom != null) {
695 try {
696 new ActionWithTempFile(ioFile) {
697 protected void executeInternal() throws VcsException {
698 try {
699 // not recursive
700 final SVNCopySource[] copySource = new SVNCopySource[]
701 {new SVNCopySource(SVNRevision.WORKING, SVNRevision.WORKING, copyFrom)};
702 copyClient.doCopy(copySource, ioFile, false, true, true);
704 catch (SVNException e) {
705 throw new VcsException(e);
708 }.execute();
710 catch (VcsException e) {
711 exceptions.add(e);
714 else {
715 wcClient.doAdd(ioFile, true, false, false, recursiveItems.contains(file));
717 VcsDirtyScopeManager.getInstance(project).fileDirty(file);
719 catch (SVNException e) {
720 exceptions.add(new VcsException(e));
723 if (!exceptions.isEmpty()) {
724 vcsHelper.showErrors(exceptions, SvnBundle.message("add.files.errors.title"));
730 private void processDeletedFiles(Project project) {
731 final List<FilePath> deletedFiles = new ArrayList<FilePath>();
732 for (Iterator<DeletedFileInfo> it = myDeletedFiles.iterator(); it.hasNext();) {
733 DeletedFileInfo deletedFileInfo = it.next();
734 if (deletedFileInfo.myProject == project) {
735 it.remove();
736 final FilePath filePath = VcsContextFactory.SERVICE.getInstance().createFilePathOn(deletedFileInfo.myFile);
737 deletedFiles.add(filePath);
740 if (deletedFiles.size() == 0 || myUndoingMove) return;
741 SvnVcs vcs = SvnVcs.getInstance(project);
742 final VcsShowConfirmationOption.Value value = vcs.getDeleteConfirmation().getValue();
743 if (value != VcsShowConfirmationOption.Value.DO_NOTHING_SILENTLY) {
744 final AbstractVcsHelper vcsHelper = AbstractVcsHelper.getInstance(project);
745 Collection<FilePath> filesToProcess;
746 if (value == VcsShowConfirmationOption.Value.DO_ACTION_SILENTLY) {
747 filesToProcess = new ArrayList<FilePath>(deletedFiles);
749 else {
751 final String singleFilePrompt;
752 if (deletedFiles.size() == 1 && deletedFiles.get(0).isDirectory()) {
753 singleFilePrompt = SvnBundle.message("confirmation.text.delete.dir");
755 else {
756 singleFilePrompt = SvnBundle.message("confirmation.text.delete.file");
758 final Collection<FilePath> files = vcsHelper
759 .selectFilePathsToProcess(deletedFiles, SvnBundle.message("confirmation.title.delete.multiple.files"), null,
760 SvnBundle.message("confirmation.title.delete.file"), singleFilePrompt, vcs.getDeleteConfirmation());
761 filesToProcess = (files == null) ? null : new ArrayList<FilePath>(files);
763 if (filesToProcess != null) {
764 List<VcsException> exceptions = new ArrayList<VcsException>();
765 SVNWCClient wcClient = vcs.createWCClient();
766 for(FilePath file: filesToProcess) {
767 VirtualFile vFile = file.getVirtualFile(); // for deleted directories
768 File ioFile = new File(file.getPath());
769 try {
770 wcClient.doDelete(ioFile, true, false);
771 if (vFile != null && vFile.isValid() && vFile.isDirectory()) {
772 vFile.refresh(true, true);
773 VcsDirtyScopeManager.getInstance(project).dirDirtyRecursively(vFile);
775 else {
776 VcsDirtyScopeManager.getInstance(project).fileDirty(file);
779 catch (SVNException e) {
780 exceptions.add(new VcsException(e));
783 if (!exceptions.isEmpty()) {
784 vcsHelper.showErrors(exceptions, SvnBundle.message("delete.files.errors.title"));
787 for (FilePath file : deletedFiles) {
788 final FilePath parent = file.getParentPath();
789 if (parent != null) {
790 myFilesToRefresh.add(parent.getVirtualFile());
793 if (filesToProcess != null) {
794 deletedFiles.removeAll(filesToProcess);
796 for (FilePath file : deletedFiles) {
797 FileUtil.delete(file.getIOFile());
802 private void processMovedFiles(final Project project) {
803 for (Iterator<MovedFileInfo> iterator = myMovedFiles.iterator(); iterator.hasNext();) {
804 MovedFileInfo movedFileInfo = iterator.next();
805 if (movedFileInfo.myProject == project) {
806 doMove(SvnVcs.getInstance(project), movedFileInfo.mySrc, movedFileInfo.myDst);
807 iterator.remove();
812 public void undoTransparentActionStarted() {
815 public void undoTransparentActionFinished() {
818 @Nullable
819 private static SvnVcs getVCS(VirtualFile file) {
820 Project[] projects = ProjectManager.getInstance().getOpenProjects();
821 for (Project project : projects) {
822 AbstractVcs vcs = ProjectLevelVcsManager.getInstance(project).getVcsFor(file);
823 if (vcs instanceof SvnVcs) {
824 return (SvnVcs)vcs;
827 return null;
831 private static File getIOFile(VirtualFile vf) {
832 return new File(vf.getPath()).getAbsoluteFile();
835 @Nullable
836 private static SVNStatus getFileStatus(File file) {
837 final SVNClientManager clientManager = SVNClientManager.newInstance();
838 try {
839 SVNStatusClient stClient = clientManager.getStatusClient();
840 return getFileStatus(file, stClient);
842 finally {
843 clientManager.dispose();
847 @Nullable
848 private static SVNStatus getFileStatus(SvnVcs vcs, File file) {
849 SVNStatusClient stClient = vcs.createStatusClient();
850 return getFileStatus(file, stClient);
853 @Nullable
854 private static SVNStatus getFileStatus(final File file, final SVNStatusClient stClient) {
855 try {
856 return stClient.doStatus(file, false);
858 catch (SVNException e) {
859 return null;
863 private static boolean isUndoOrRedo(@NotNull final Project project) {
864 final UndoManager undoManager = UndoManager.getInstance(project);
865 return undoManager.isUndoInProgress() || undoManager.isRedoInProgress();
868 private static boolean isUndo(SvnVcs vcs) {
869 if (vcs == null || vcs.getProject() == null) {
870 return false;
872 Project p = vcs.getProject();
873 return UndoManager.getInstance(p).isUndoInProgress();
876 public void afterDone(final ThrowableConsumer<LocalFileOperationsHandler, IOException> invoker) {