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 * ====================================================================
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
.*;
67 import java
.io
.IOException
;
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
) {
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
) {
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
) {
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()));
138 vcsException
= new VcsException(e
);
140 exceptionList
.add(vcsException
);
144 public File
copy(final VirtualFile file
, final VirtualFile toDir
, final String copyName
) throws IOException
{
145 SvnVcs vcs
= getVCS(toDir
);
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
)) {
160 if (!SVNWCUtil
.isVersionedDirectory(srcFile
.getParentFile())) {
161 myAddedFiles
.add(new AddedFileInfo(vcs
.getProject(), toDir
, copyName
, null, false));
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));
171 if (sameRoot(vcs
, file
.getParent(), toDir
)) {
172 myAddedFiles
.add(new AddedFileInfo(vcs
.getProject(), toDir
, copyName
, srcFile
, false));
176 myAddedFiles
.add(new AddedFileInfo(vcs
.getProject(), toDir
, copyName
, null, false));
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)
199 public String
getRepositoryUUID(final VirtualFile dir
) {
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) {
208 if (isPendingAdd(parent
)) {
209 return getRepositoryUUID(parent
);
212 return info1
.getRepositoryUUID();
214 } catch (SVNException e
) {
215 // go to return default
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;
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
));
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
);
253 myFilesToRefresh
.add(file
.getParent());
254 return doMove(vcs
, srcFile
, dstFile
);
259 private boolean doMove(@NotNull SvnVcs vcs
, final File src
, final File dst
) {
260 SVNMoveClient mover
= vcs
.createMoveClient();
261 long srcTime
= src
.lastModified();
264 myUndoingMove
= true;
265 restoreFromUndoStorage(dst
);
266 mover
.undoMove(src
, dst
);
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
) {
278 mover
.doMove(src
, dst
);
280 dst
.setLastModified(srcTime
);
282 vcs
.pathChanged(src
, dst
);
284 catch (SVNException e
) {
285 addToMoveExceptions(vcs
.getProject(), e
);
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
)) {
298 FileUtil
.rename(e
.second
, e
.first
);
300 catch (IOException ex
) {
302 FileUtil
.asyncDelete(e
.second
);
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
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
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
)) {
344 File ioFile
= getIOFile(file
);
345 if (!SVNWCUtil
.isVersionedDirectory(ioFile
.getParentFile())) {
350 if (SVNWCUtil
.isWorkingCopyRoot(ioFile
)) {
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
) {
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
);
374 else if (status
.getContentsStatus() == SVNStatusType
.STATUS_DELETED
) {
376 moveToUndoStorage(file
);
382 if (status
.getContentsStatus() == SVNStatusType
.STATUS_ADDED
) {
384 final SVNWCClient wcClient
= vcs
.createWCClient();
385 wcClient
.doRevert(ioFile
, false);
387 catch (SVNException e
) {
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;
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());
405 final IgnoredFileInfo info
= map
.get(key
);
407 info
.addFileName(file
.getName());
411 final Set
<String
> existingPatterns
= SvnPropertyService
.getIgnoreStringsUnder(vcs
, file
.getParent());
412 if (existingPatterns
!= 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());
424 private void moveToUndoStorage(final VirtualFile file
) {
425 if (myStorageForUndo
== null) {
427 myStorageForUndo
= FileUtil
.createTempDirectory("svnUndoStorage", "");
429 catch (IOException e
) {
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:
442 * parent directory is:
443 * unversioned: do nothing, return false
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
);
457 if (isUndo(vcs
) && SvnUtil
.isAdminDirectory(dir
, name
)) {
460 File ioDir
= getIOFile(dir
);
461 boolean pendingAdd
= isPendingAdd(dir
);
462 if (!SVNWCUtil
.isVersionedDirectory(ioDir
) && !pendingAdd
) {
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
));
473 else if (status
.getContentsStatus() == SVNStatusType
.STATUS_MISSING
) {
476 else if (status
.getContentsStatus() == SVNStatusType
.STATUS_DELETED
) {
477 SVNNodeKind kind
= status
.getKind();
479 if ((directory
&& kind
!= SVNNodeKind
.DIR
) || (!directory
&& kind
!= SVNNodeKind
.FILE
)) {
484 wcClient
.doRevert(targetFile
, false);
487 myAddedFiles
.add(new AddedFileInfo(vcs
.getProject(), dir
, name
, null, recursive
));
490 catch (SVNException e
) {
491 SVNFileUtil
.deleteAll(targetFile
, true);
498 private boolean isPendingAdd(final VirtualFile dir
) {
499 for(AddedFileInfo i
: myAddedFiles
) {
500 if (i
.myDir
== dir
.getParent() && i
.myName
.equals(dir
.getName())) {
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
);
553 final SvnVcs vcs
= SvnVcs
.getInstance(project
);
554 final ProgressManager progressManager
= ProgressManager
.getInstance();
555 final Runnable prepare
= new Runnable() {
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()) {
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
);
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() {
578 for (IgnoredFileInfo info
: result
) {
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
);
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() {
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
);
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());
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
) {
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
);
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
;
674 final String singleFilePrompt
;
675 if (addedVFiles
.size() == 1 && addedVFiles
.get(0).isDirectory()) {
676 singleFilePrompt
= SvnBundle
.message("confirmation.text.add.dir");
679 singleFilePrompt
= SvnBundle
.message("confirmation.text.add.file");
681 filesToProcess
= vcsHelper
.selectFilesToProcess(addedVFiles
, SvnBundle
.message("confirmation.title.add.multiple.files"),
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());
693 final File copyFrom
= copyFromMap
.get(file
);
694 if (copyFrom
!= null) {
696 new ActionWithTempFile(ioFile
) {
697 protected void executeInternal() throws VcsException
{
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
);
710 catch (VcsException e
) {
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
) {
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
);
751 final String singleFilePrompt
;
752 if (deletedFiles
.size() == 1 && deletedFiles
.get(0).isDirectory()) {
753 singleFilePrompt
= SvnBundle
.message("confirmation.text.delete.dir");
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());
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
);
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
);
812 public void undoTransparentActionStarted() {
815 public void undoTransparentActionFinished() {
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
) {
831 private static File
getIOFile(VirtualFile vf
) {
832 return new File(vf
.getPath()).getAbsoluteFile();
836 private static SVNStatus
getFileStatus(File file
) {
837 final SVNClientManager clientManager
= SVNClientManager
.newInstance();
839 SVNStatusClient stClient
= clientManager
.getStatusClient();
840 return getFileStatus(file
, stClient
);
843 clientManager
.dispose();
848 private static SVNStatus
getFileStatus(SvnVcs vcs
, File file
) {
849 SVNStatusClient stClient
= vcs
.createStatusClient();
850 return getFileStatus(file
, stClient
);
854 private static SVNStatus
getFileStatus(final File file
, final SVNStatusClient stClient
) {
856 return stClient
.doStatus(file
, false);
858 catch (SVNException e
) {
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) {
872 Project p
= vcs
.getProject();
873 return UndoManager
.getInstance(p
).isUndoInProgress();
876 public void afterDone(final ThrowableConsumer
<LocalFileOperationsHandler
, IOException
> invoker
) {