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
.VirtualFile
;
51 import com
.intellij
.openapi
.vfs
.newvfs
.RefreshQueue
;
52 import com
.intellij
.openapi
.vfs
.newvfs
.RefreshSession
;
53 import com
.intellij
.util
.ThrowableConsumer
;
54 import com
.intellij
.vcsUtil
.ActionWithTempFile
;
55 import org
.jetbrains
.annotations
.NotNull
;
56 import org
.jetbrains
.annotations
.Nullable
;
57 import org
.jetbrains
.idea
.svn
.dialogs
.SelectIgnorePatternsToRemoveOnDeleteDialog
;
58 import org
.jetbrains
.idea
.svn
.ignore
.SvnPropertyService
;
59 import org
.tmatesoft
.svn
.core
.SVNErrorCode
;
60 import org
.tmatesoft
.svn
.core
.SVNException
;
61 import org
.tmatesoft
.svn
.core
.SVNNodeKind
;
62 import org
.tmatesoft
.svn
.core
.internal
.wc
.SVNFileUtil
;
63 import org
.tmatesoft
.svn
.core
.wc
.*;
66 import java
.io
.IOException
;
69 public class SvnFileSystemListener
implements LocalFileOperationsHandler
, CommandListener
{
70 private static final Logger LOG
= Logger
.getInstance("#org.jetbrains.idea.svn.SvnFileSystemListener");
72 private static class AddedFileInfo
{
73 private final Project myProject
;
74 private final VirtualFile myDir
;
75 private final String myName
;
76 @Nullable private final File myCopyFrom
;
77 private final boolean myRecursive
;
79 public AddedFileInfo(final Project project
, final VirtualFile dir
, final String name
, @Nullable final File copyFrom
, boolean recursive
) {
83 myCopyFrom
= copyFrom
;
84 myRecursive
= recursive
;
88 private static class DeletedFileInfo
{
89 private final Project myProject
;
90 private final File myFile
;
92 public DeletedFileInfo(final Project project
, final File file
) {
98 private static class MovedFileInfo
{
99 private final Project myProject
;
100 private final File mySrc
;
101 private final File myDst
;
103 private MovedFileInfo(final Project project
, final File src
, final File dst
) {
110 private final Map
<Project
, Map
<String
, IgnoredFileInfo
>> myIgnoredInfo
= new HashMap
<Project
, Map
<String
, IgnoredFileInfo
>>();
112 private final List
<AddedFileInfo
> myAddedFiles
= new ArrayList
<AddedFileInfo
>();
113 private final List
<DeletedFileInfo
> myDeletedFiles
= new ArrayList
<DeletedFileInfo
>();
114 private final List
<MovedFileInfo
> myMovedFiles
= new ArrayList
<MovedFileInfo
>();
115 private final Map
<Project
, List
<VcsException
>> myMoveExceptions
= new HashMap
<Project
, List
<VcsException
>>();
116 private final List
<VirtualFile
> myFilesToRefresh
= new ArrayList
<VirtualFile
>();
117 @Nullable private File myStorageForUndo
;
118 private final List
<Pair
<File
, File
>> myUndoStorageContents
= new ArrayList
<Pair
<File
, File
>>();
119 private boolean myUndoingMove
= false;
121 private void addToMoveExceptions(final Project project
, final SVNException e
) {
122 List
<VcsException
> exceptionList
= myMoveExceptions
.get(project
);
123 if (exceptionList
== null) {
124 exceptionList
= new ArrayList
<VcsException
>();
125 myMoveExceptions
.put(project
, exceptionList
);
127 VcsException vcsException
;
128 if (SVNErrorCode
.ENTRY_EXISTS
.equals(e
.getErrorMessage().getErrorCode())) {
129 vcsException
= new VcsException(Arrays
.asList("Target of move operation is already under version control.",
130 "Subversion move had not been performed. ", e
.getMessage()));
132 vcsException
= new VcsException(e
);
134 exceptionList
.add(vcsException
);
138 public File
copy(final VirtualFile file
, final VirtualFile toDir
, final String copyName
) throws IOException
{
139 SvnVcs vcs
= getVCS(toDir
);
147 File srcFile
= new File(file
.getPath());
148 File destFile
= new File(new File(toDir
.getPath()), copyName
);
149 final boolean dstDirUnderControl
= SVNWCUtil
.isVersionedDirectory(destFile
.getParentFile());
150 if ((! dstDirUnderControl
) && !isPendingAdd(toDir
)) {
154 if (!SVNWCUtil
.isVersionedDirectory(srcFile
.getParentFile())) {
155 myAddedFiles
.add(new AddedFileInfo(vcs
.getProject(), toDir
, copyName
, null, false));
159 final SVNStatus fileStatus
= getFileStatus(vcs
, srcFile
);
160 if (fileStatus
!= null && fileStatus
.getContentsStatus() == SVNStatusType
.STATUS_ADDED
) {
161 myAddedFiles
.add(new AddedFileInfo(vcs
.getProject(), toDir
, copyName
, null, false));
165 if (sameRoot(vcs
, file
.getParent(), toDir
)) {
166 myAddedFiles
.add(new AddedFileInfo(vcs
.getProject(), toDir
, copyName
, srcFile
, false));
170 myAddedFiles
.add(new AddedFileInfo(vcs
.getProject(), toDir
, copyName
, null, false));
174 private boolean sameRoot(final SvnVcs vcs
, final VirtualFile srcDir
, final VirtualFile dstDir
) {
175 final UUIDHelper helper
= new UUIDHelper(vcs
);
176 final String srcUUID
= helper
.getRepositoryUUID(srcDir
);
177 final String dstUUID
= helper
.getRepositoryUUID(dstDir
);
179 return (srcUUID
!= null) && (dstUUID
!= null) && (srcUUID
.equals(dstUUID
));
182 private class UUIDHelper
{
183 private final SVNWCClient myWcClient
;
185 private UUIDHelper(final SvnVcs vcs
) {
186 myWcClient
= vcs
.createWCClient();
190 * passed dir must be under VC control (it is assumed)
193 public String
getRepositoryUUID(final VirtualFile dir
) {
195 final SVNInfo info1
= myWcClient
.doInfo(new File(dir
.getPath()), SVNRevision
.WORKING
);
196 if ((info1
== null) || (info1
.getRepositoryUUID() == null)) {
197 // go deeper if current parent was added (if parent was added, it theoretically could NOT know its repo UUID)
198 final VirtualFile parent
= dir
.getParent();
199 if (parent
== null) {
202 if (isPendingAdd(parent
)) {
203 return getRepositoryUUID(parent
);
206 return info1
.getRepositoryUUID();
208 } catch (SVNException e
) {
209 // go to return default
215 public boolean move(VirtualFile file
, VirtualFile toDir
) throws IOException
{
216 File srcFile
= getIOFile(file
);
217 File dstFile
= new File(getIOFile(toDir
), file
.getName());
219 final SvnVcs vcs
= getVCS(toDir
);
220 final SvnVcs sourceVcs
= getVCS(file
);
221 if (vcs
== null && sourceVcs
== null) return false;
226 if (sourceVcs
== null) {
227 return createItem(toDir
, file
.getName(), file
.isDirectory(), true);
230 if (isPendingAdd(toDir
)) {
231 myMovedFiles
.add(new MovedFileInfo(sourceVcs
.getProject(), srcFile
, dstFile
));
235 final VirtualFile oldParent
= file
.getParent();
236 myFilesToRefresh
.add(oldParent
);
237 myFilesToRefresh
.add(toDir
);
238 return doMove(sourceVcs
, srcFile
, dstFile
);
242 public boolean rename(VirtualFile file
, String newName
) throws IOException
{
243 File srcFile
= getIOFile(file
);
244 File dstFile
= new File(srcFile
.getParentFile(), newName
);
245 SvnVcs vcs
= getVCS(file
);
247 myFilesToRefresh
.add(file
.getParent());
248 return doMove(vcs
, srcFile
, dstFile
);
253 private boolean doMove(@NotNull SvnVcs vcs
, final File src
, final File dst
) {
254 SVNMoveClient mover
= vcs
.createMoveClient();
255 long srcTime
= src
.lastModified();
258 myUndoingMove
= true;
259 restoreFromUndoStorage(dst
);
260 mover
.undoMove(src
, dst
);
263 // if src is not under version control, do usual move.
264 SVNStatus srcStatus
= getFileStatus(vcs
, src
);
265 if (srcStatus
== null || srcStatus
.getContentsStatus() == SVNStatusType
.STATUS_UNVERSIONED
||
266 srcStatus
.getContentsStatus() == SVNStatusType
.STATUS_EXTERNAL
||
267 srcStatus
.getContentsStatus() == SVNStatusType
.STATUS_MISSING
||
268 srcStatus
.getContentsStatus() == SVNStatusType
.STATUS_OBSTRUCTED
) {
272 mover
.doMove(src
, dst
);
274 dst
.setLastModified(srcTime
);
276 vcs
.pathChanged(src
, dst
);
278 catch (SVNException e
) {
279 addToMoveExceptions(vcs
.getProject(), e
);
285 private void restoreFromUndoStorage(final File dst
) {
286 String normPath
= FileUtil
.toSystemIndependentName(dst
.getPath());
287 for (Iterator
<Pair
<File
, File
>> it
= myUndoStorageContents
.iterator(); it
.hasNext();) {
288 Pair
<File
, File
> e
= it
.next();
289 final String p
= FileUtil
.toSystemIndependentName(e
.first
.getPath());
290 if (p
.startsWith(normPath
)) {
292 FileUtil
.rename(e
.second
, e
.first
);
294 catch (IOException ex
) {
296 FileUtil
.asyncDelete(e
.second
);
301 if (myStorageForUndo
!= null) {
302 final File
[] files
= myStorageForUndo
.listFiles();
303 if (files
== null || files
.length
== 0) {
304 FileUtil
.asyncDelete(myStorageForUndo
);
305 myStorageForUndo
= null;
311 public boolean createFile(VirtualFile dir
, String name
) throws IOException
{
312 return createItem(dir
, name
, false, false);
315 public boolean createDirectory(VirtualFile dir
, String name
) throws IOException
{
316 return createItem(dir
, name
, true, false);
320 * delete file or directory (both 'undo' and 'do' modes)
321 * unversioned: do nothing, return false
322 * obstructed: do nothing, return false
323 * external or wc root: do nothing, return false
324 * missing: do nothing, return false
326 * versioned: schedule for deletion, return true
327 * added: schedule for deletion (make unversioned), return true
328 * copied, but not scheduled: schedule for deletion, return true
329 * replaced: schedule for deletion, return true
331 * deleted: do nothing, return true (strange)
333 public boolean delete(VirtualFile file
) throws IOException
{
334 SvnVcs vcs
= getVCS(file
);
335 if (vcs
!= null && SvnUtil
.isAdminDirectory(file
)) {
338 File ioFile
= getIOFile(file
);
339 if (!SVNWCUtil
.isVersionedDirectory(ioFile
.getParentFile())) {
344 if (SVNWCUtil
.isWorkingCopyRoot(ioFile
)) {
347 } catch (SVNException e
) {
352 SVNStatus status
= getFileStatus(ioFile
);
354 if (status
== null ||
355 status
.getContentsStatus() == SVNStatusType
.STATUS_UNVERSIONED
||
356 status
.getContentsStatus() == SVNStatusType
.STATUS_OBSTRUCTED
||
357 status
.getContentsStatus() == SVNStatusType
.STATUS_MISSING
||
358 status
.getContentsStatus() == SVNStatusType
.STATUS_EXTERNAL
) {
360 } else if (status
.getContentsStatus() == SVNStatusType
.STATUS_IGNORED
) {
361 if (vcs
!= null && (file
.getParent() != null)) {
362 if (! isUndoOrRedo(vcs
.getProject())) {
363 putIgnoreInfo(file
, vcs
, ioFile
);
368 else if (status
.getContentsStatus() == SVNStatusType
.STATUS_DELETED
) {
370 moveToUndoStorage(file
);
376 if (status
.getContentsStatus() == SVNStatusType
.STATUS_ADDED
) {
378 final SVNWCClient wcClient
= vcs
.createWCClient();
379 wcClient
.doRevert(ioFile
, false);
381 catch (SVNException e
) {
386 myDeletedFiles
.add(new DeletedFileInfo(vcs
.getProject(), ioFile
));
387 // packages deleted from disk should not be deleted from svn (IDEADEV-16066)
388 if (file
.isDirectory() || isUndo(vcs
)) return true;
395 private void putIgnoreInfo(VirtualFile file
, SvnVcs vcs
, File ioFile
) {
396 final String key
= file
.getParent().getPresentableUrl();
397 Map
<String
, IgnoredFileInfo
> map
= myIgnoredInfo
.get(vcs
.getProject());
399 final IgnoredFileInfo info
= map
.get(key
);
401 info
.addFileName(file
.getName());
405 final Set
<String
> existingPatterns
= SvnPropertyService
.getIgnoreStringsUnder(vcs
, file
.getParent());
406 if (existingPatterns
!= null) {
408 map
= new HashMap
<String
, IgnoredFileInfo
>();
409 myIgnoredInfo
.put(vcs
.getProject(), map
);
411 final File parentIo
= ioFile
.getParentFile();
412 final IgnoredFileInfo info
= new IgnoredFileInfo(parentIo
, existingPatterns
);
413 info
.addFileName(file
.getName());
418 private void moveToUndoStorage(final VirtualFile file
) {
419 if (myStorageForUndo
== null) {
421 myStorageForUndo
= FileUtil
.createTempDirectory("svnUndoStorage", "");
423 catch (IOException e
) {
428 final File tmpFile
= FileUtil
.findSequentNonexistentFile(myStorageForUndo
, "tmp", "");
429 myUndoStorageContents
.add(0, new Pair
<File
, File
>(new File(file
.getPath()), tmpFile
));
430 new File(file
.getPath()).renameTo(tmpFile
);
434 * add file or directory:
436 * parent directory is:
437 * unversioned: do nothing, return false
440 * null: create entry, schedule for addition
441 * missing: do nothing, return false
442 * deleted, 'do' mode: try to create entry and it schedule for addition if kind is the same, otherwise do nothing, return false.
443 * deleted: 'undo' mode: try to revert non-recursively, if kind is the same, otherwise do nothing, return false.
444 * anything else: return false.
446 private boolean createItem(VirtualFile dir
, String name
, boolean directory
, final boolean recursive
) throws IOException
{
447 SvnVcs vcs
= getVCS(dir
);
451 if (isUndo(vcs
) && SvnUtil
.isAdminDirectory(dir
, name
)) {
454 File ioDir
= getIOFile(dir
);
455 boolean pendingAdd
= isPendingAdd(dir
);
456 if (!SVNWCUtil
.isVersionedDirectory(ioDir
) && !pendingAdd
) {
459 SVNWCClient wcClient
= vcs
.createWCClient();
460 File targetFile
= new File(ioDir
, name
);
461 SVNStatus status
= getFileStatus(vcs
, targetFile
);
463 if (status
== null || status
.getContentsStatus() == SVNStatusType
.STATUS_NONE
) {
464 myAddedFiles
.add(new AddedFileInfo(vcs
.getProject(), dir
, name
, null, recursive
));
467 else if (status
.getContentsStatus() == SVNStatusType
.STATUS_MISSING
) {
470 else if (status
.getContentsStatus() == SVNStatusType
.STATUS_DELETED
) {
471 SVNNodeKind kind
= status
.getKind();
473 if ((directory
&& kind
!= SVNNodeKind
.DIR
) || (!directory
&& kind
!= SVNNodeKind
.FILE
)) {
478 wcClient
.doRevert(targetFile
, false);
481 myAddedFiles
.add(new AddedFileInfo(vcs
.getProject(), dir
, name
, null, recursive
));
484 catch (SVNException e
) {
485 SVNFileUtil
.deleteAll(targetFile
, true);
492 private boolean isPendingAdd(final VirtualFile dir
) {
493 for(AddedFileInfo i
: myAddedFiles
) {
494 if (i
.myDir
== dir
.getParent() && i
.myName
.equals(dir
.getName())) {
501 public void commandStarted(CommandEvent event
) {
502 myUndoingMove
= false;
503 final Project project
= event
.getProject();
504 if (project
== null) return;
505 commandStarted(project
);
508 void commandStarted(final Project project
) {
509 myUndoingMove
= false;
510 myMoveExceptions
.remove(project
);
511 myIgnoredInfo
.remove(project
);
514 public void beforeCommandFinished(CommandEvent event
) {
517 public void commandFinished(CommandEvent event
) {
518 final Project project
= event
.getProject();
519 if (project
== null) return;
520 commandFinished(project
);
523 void commandFinished(final Project project
) {
524 if (myAddedFiles
.size() > 0) {
525 processAddedFiles(project
);
527 if (myDeletedFiles
.size() > 0) {
528 processDeletedFiles(project
);
530 processMovedFiles(project
);
532 final List
<VcsException
> exceptionList
= myMoveExceptions
.get(project
);
533 if ((exceptionList
!= null) && (! exceptionList
.isEmpty())) {
534 AbstractVcsHelper
.getInstance(project
).showErrors(exceptionList
, SvnBundle
.message("move.files.errors.title"));
537 dealWithIgnorePatterns(project
);
539 if (myFilesToRefresh
.size() > 0) {
540 refreshFiles(project
);
544 private void dealWithIgnorePatterns(Project project
) {
545 final Map
<String
, IgnoredFileInfo
> map
= myIgnoredInfo
.get(project
);
547 final SvnVcs vcs
= SvnVcs
.getInstance(project
);
548 final ProgressManager progressManager
= ProgressManager
.getInstance();
549 final Runnable prepare
= new Runnable() {
551 for (Iterator
<String
> iterator
= map
.keySet().iterator(); iterator
.hasNext();) {
552 final String key
= iterator
.next();
553 final IgnoredFileInfo info
= map
.get(key
);
554 info
.calculatePatterns(vcs
);
555 if (info
.getPatterns().isEmpty()) {
561 progressManager
.runProcessWithProgressSynchronously(prepare
, SvnBundle
.message("gather.ignore.patterns.info.progress.title"), false, project
);
562 if (map
.isEmpty()) return;
564 final SelectIgnorePatternsToRemoveOnDeleteDialog dialog
= new SelectIgnorePatternsToRemoveOnDeleteDialog(project
, map
);
566 final Collection
<IgnoredFileInfo
> result
= dialog
.getResult();
567 if ((dialog
.getExitCode() == DialogWrapper
.OK_EXIT_CODE
) && (! result
.isEmpty())) {
568 final List
<VcsException
> exceptions
= new ArrayList
<VcsException
>(0);
570 final Runnable deletePatterns
= new Runnable() {
572 for (IgnoredFileInfo info
: result
) {
574 info
.getOldPatterns().removeAll(info
.getPatterns());
575 SvnPropertyService
.setIgnores(vcs
, info
.getOldPatterns(), info
.getFile());
577 catch (SVNException e
) {
578 exceptions
.add(new VcsException(e
));
583 progressManager
.runProcessWithProgressSynchronously(deletePatterns
, "Removing selected 'svn:ignore' patterns", false, project
);
584 if (! exceptions
.isEmpty()) {
585 AbstractVcsHelper
.getInstance(project
).showErrors(exceptions
, SvnBundle
.message("remove.ignore.patterns.errors.title"));
591 private void refreshFiles(final Project project
) {
592 final List
<VirtualFile
> toRefreshFiles
= new ArrayList
<VirtualFile
>();
593 final List
<VirtualFile
> toRefreshDirs
= new ArrayList
<VirtualFile
>();
594 for (VirtualFile file
: myFilesToRefresh
) {
595 if (file
== null) continue;
596 if (file
.isDirectory()) {
597 toRefreshDirs
.add(file
);
599 toRefreshFiles
.add(file
);
602 // if refresh asynchronously, local changes would also be notified that they are dirty asynchronously,
603 // and commit could be executed while not all changes are visible
604 final RefreshSession session
= RefreshQueue
.getInstance().createSession(false, true, new Runnable() {
606 if (project
.isDisposed()) return;
607 filterOutInvalid(toRefreshFiles
);
608 filterOutInvalid(toRefreshDirs
);
610 final VcsDirtyScopeManager vcsDirtyScopeManager
= VcsDirtyScopeManager
.getInstance(project
);
611 vcsDirtyScopeManager
.filesDirty(toRefreshFiles
, toRefreshDirs
);
614 filterOutInvalid(myFilesToRefresh
);
615 session
.addAllFiles(myFilesToRefresh
);
617 myFilesToRefresh
.clear();
620 private void filterOutInvalid(final Collection
<VirtualFile
> files
) {
621 for (Iterator
<VirtualFile
> iterator
= files
.iterator(); iterator
.hasNext();) {
622 final VirtualFile file
= iterator
.next();
623 if ((! file
.isValid()) || (! file
.exists())) {
624 LOG
.info("Refresh root is not valid: " + file
.getPath());
630 private void processAddedFiles(Project project
) {
631 SvnVcs vcs
= SvnVcs
.getInstance(project
);
632 List
<VirtualFile
> addedVFiles
= new ArrayList
<VirtualFile
>();
633 Map
<VirtualFile
, File
> copyFromMap
= new HashMap
<VirtualFile
, File
>();
634 final Set
<VirtualFile
> recursiveItems
= new HashSet
<VirtualFile
>();
635 for (Iterator
<AddedFileInfo
> it
= myAddedFiles
.iterator(); it
.hasNext();) {
636 AddedFileInfo addedFileInfo
= it
.next();
637 if (addedFileInfo
.myProject
== project
) {
639 VirtualFile addedFile
= addedFileInfo
.myDir
.findChild(addedFileInfo
.myName
);
640 if (addedFile
!= null) {
641 final SVNStatus fileStatus
= getFileStatus(vcs
, new File(getIOFile(addedFileInfo
.myDir
), addedFileInfo
.myName
));
642 if (fileStatus
== null || fileStatus
.getContentsStatus() != SVNStatusType
.STATUS_IGNORED
) {
643 boolean isIgnored
= ChangeListManager
.getInstance(addedFileInfo
.myProject
).isIgnoredFile(addedFile
);
645 addedVFiles
.add(addedFile
);
646 copyFromMap
.put(addedFile
, addedFileInfo
.myCopyFrom
);
647 if (addedFileInfo
.myRecursive
) {
648 recursiveItems
.add(addedFile
);
655 if (addedVFiles
.size() == 0) return;
656 final VcsShowConfirmationOption
.Value value
= vcs
.getAddConfirmation().getValue();
657 if (value
!= VcsShowConfirmationOption
.Value
.DO_NOTHING_SILENTLY
) {
658 final AbstractVcsHelper vcsHelper
= AbstractVcsHelper
.getInstance(project
);
659 Collection
<VirtualFile
> filesToProcess
;
660 if (value
== VcsShowConfirmationOption
.Value
.DO_ACTION_SILENTLY
) {
661 filesToProcess
= addedVFiles
;
664 final String singleFilePrompt
;
665 if (addedVFiles
.size() == 1 && addedVFiles
.get(0).isDirectory()) {
666 singleFilePrompt
= SvnBundle
.message("confirmation.text.add.dir");
669 singleFilePrompt
= SvnBundle
.message("confirmation.text.add.file");
671 filesToProcess
= vcsHelper
.selectFilesToProcess(addedVFiles
, SvnBundle
.message("confirmation.title.add.multiple.files"),
673 SvnBundle
.message("confirmation.title.add.file"), singleFilePrompt
,
674 vcs
.getAddConfirmation());
676 if (filesToProcess
!= null) {
677 final List
<VcsException
> exceptions
= new ArrayList
<VcsException
>();
678 SVNWCClient wcClient
= vcs
.createWCClient();
679 final SVNCopyClient copyClient
= vcs
.createCopyClient();
680 for(VirtualFile file
: filesToProcess
) {
681 final File ioFile
= new File(file
.getPath());
683 final File copyFrom
= copyFromMap
.get(file
);
684 if (copyFrom
!= null) {
686 new ActionWithTempFile(ioFile
) {
687 protected void executeInternal() throws VcsException
{
690 final SVNCopySource
[] copySource
= new SVNCopySource
[]
691 {new SVNCopySource(SVNRevision
.WORKING
, SVNRevision
.WORKING
, copyFrom
)};
692 copyClient
.doCopy(copySource
, ioFile
, false, true, true);
694 catch (SVNException e
) {
695 throw new VcsException(e
);
700 catch (VcsException e
) {
705 wcClient
.doAdd(ioFile
, true, false, false, recursiveItems
.contains(file
));
707 VcsDirtyScopeManager
.getInstance(project
).fileDirty(file
);
709 catch (SVNException e
) {
710 exceptions
.add(new VcsException(e
));
713 if (!exceptions
.isEmpty()) {
714 vcsHelper
.showErrors(exceptions
, SvnBundle
.message("add.files.errors.title"));
720 private void processDeletedFiles(Project project
) {
721 final List
<FilePath
> deletedFiles
= new ArrayList
<FilePath
>();
722 for (Iterator
<DeletedFileInfo
> it
= myDeletedFiles
.iterator(); it
.hasNext();) {
723 DeletedFileInfo deletedFileInfo
= it
.next();
724 if (deletedFileInfo
.myProject
== project
) {
726 final FilePath filePath
= VcsContextFactory
.SERVICE
.getInstance().createFilePathOn(deletedFileInfo
.myFile
);
727 deletedFiles
.add(filePath
);
730 if (deletedFiles
.size() == 0 || myUndoingMove
) return;
731 SvnVcs vcs
= SvnVcs
.getInstance(project
);
732 final VcsShowConfirmationOption
.Value value
= vcs
.getDeleteConfirmation().getValue();
733 if (value
!= VcsShowConfirmationOption
.Value
.DO_NOTHING_SILENTLY
) {
734 final AbstractVcsHelper vcsHelper
= AbstractVcsHelper
.getInstance(project
);
735 Collection
<FilePath
> filesToProcess
;
736 if (value
== VcsShowConfirmationOption
.Value
.DO_ACTION_SILENTLY
) {
737 filesToProcess
= new ArrayList
<FilePath
>(deletedFiles
);
741 final String singleFilePrompt
;
742 if (deletedFiles
.size() == 1 && deletedFiles
.get(0).isDirectory()) {
743 singleFilePrompt
= SvnBundle
.message("confirmation.text.delete.dir");
746 singleFilePrompt
= SvnBundle
.message("confirmation.text.delete.file");
748 final Collection
<FilePath
> files
= vcsHelper
749 .selectFilePathsToProcess(deletedFiles
, SvnBundle
.message("confirmation.title.delete.multiple.files"), null,
750 SvnBundle
.message("confirmation.title.delete.file"), singleFilePrompt
, vcs
.getDeleteConfirmation());
751 filesToProcess
= (files
== null) ?
null : new ArrayList
<FilePath
>(files
);
753 if (filesToProcess
!= null) {
754 List
<VcsException
> exceptions
= new ArrayList
<VcsException
>();
755 SVNWCClient wcClient
= vcs
.createWCClient();
756 for(FilePath file
: filesToProcess
) {
757 VirtualFile vFile
= file
.getVirtualFile(); // for deleted directories
758 File ioFile
= new File(file
.getPath());
760 wcClient
.doDelete(ioFile
, true, false);
761 if (vFile
!= null && vFile
.isValid() && vFile
.isDirectory()) {
762 vFile
.refresh(true, true);
763 VcsDirtyScopeManager
.getInstance(project
).dirDirtyRecursively(vFile
);
766 VcsDirtyScopeManager
.getInstance(project
).fileDirty(file
);
769 catch (SVNException e
) {
770 exceptions
.add(new VcsException(e
));
773 if (!exceptions
.isEmpty()) {
774 vcsHelper
.showErrors(exceptions
, SvnBundle
.message("delete.files.errors.title"));
777 for (FilePath file
: deletedFiles
) {
778 final FilePath parent
= file
.getParentPath();
779 if (parent
!= null) {
780 myFilesToRefresh
.add(parent
.getVirtualFile());
783 if (filesToProcess
!= null) {
784 deletedFiles
.removeAll(filesToProcess
);
786 for (FilePath file
: deletedFiles
) {
787 FileUtil
.delete(file
.getIOFile());
792 private void processMovedFiles(final Project project
) {
793 for (Iterator
<MovedFileInfo
> iterator
= myMovedFiles
.iterator(); iterator
.hasNext();) {
794 MovedFileInfo movedFileInfo
= iterator
.next();
795 if (movedFileInfo
.myProject
== project
) {
796 doMove(SvnVcs
.getInstance(project
), movedFileInfo
.mySrc
, movedFileInfo
.myDst
);
802 public void undoTransparentActionStarted() {
805 public void undoTransparentActionFinished() {
809 private static SvnVcs
getVCS(VirtualFile file
) {
810 Project
[] projects
= ProjectManager
.getInstance().getOpenProjects();
811 for (Project project
: projects
) {
812 AbstractVcs vcs
= ProjectLevelVcsManager
.getInstance(project
).getVcsFor(file
);
813 if (vcs
instanceof SvnVcs
) {
821 private static File
getIOFile(VirtualFile vf
) {
822 return new File(vf
.getPath()).getAbsoluteFile();
826 private static SVNStatus
getFileStatus(File file
) {
827 final SVNClientManager clientManager
= SVNClientManager
.newInstance();
829 SVNStatusClient stClient
= clientManager
.getStatusClient();
830 return getFileStatus(file
, stClient
);
833 clientManager
.dispose();
838 private static SVNStatus
getFileStatus(SvnVcs vcs
, File file
) {
839 SVNStatusClient stClient
= vcs
.createStatusClient();
840 return getFileStatus(file
, stClient
);
844 private static SVNStatus
getFileStatus(final File file
, final SVNStatusClient stClient
) {
846 return stClient
.doStatus(file
, false);
848 catch (SVNException e
) {
853 private static boolean isUndoOrRedo(@NotNull final Project project
) {
854 final UndoManager undoManager
= UndoManager
.getInstance(project
);
855 return undoManager
.isUndoInProgress() || undoManager
.isRedoInProgress();
858 private static boolean isUndo(SvnVcs vcs
) {
859 if (vcs
== null || vcs
.getProject() == null) {
862 Project p
= vcs
.getProject();
863 return UndoManager
.getInstance(p
).isUndoInProgress();
866 public void afterDone(final ThrowableConsumer
<LocalFileOperationsHandler
, IOException
> invoker
) {