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.
16 package org
.jetbrains
.idea
.svn
.rollback
;
18 import com
.intellij
.openapi
.util
.Pair
;
19 import com
.intellij
.openapi
.util
.Trinity
;
20 import com
.intellij
.openapi
.util
.io
.FileUtil
;
21 import com
.intellij
.openapi
.vcs
.FilePath
;
22 import com
.intellij
.openapi
.vcs
.VcsException
;
23 import com
.intellij
.openapi
.vcs
.changes
.Change
;
24 import com
.intellij
.openapi
.vcs
.changes
.ChangesUtil
;
25 import com
.intellij
.openapi
.vcs
.changes
.ContentRevision
;
26 import com
.intellij
.openapi
.vcs
.changes
.EmptyChangelistBuilder
;
27 import com
.intellij
.openapi
.vcs
.rollback
.DefaultRollbackEnvironment
;
28 import com
.intellij
.openapi
.vcs
.rollback
.RollbackProgressListener
;
29 import com
.intellij
.openapi
.vfs
.VfsUtil
;
30 import com
.intellij
.openapi
.vfs
.VirtualFile
;
31 import org
.jetbrains
.annotations
.NotNull
;
32 import org
.jetbrains
.annotations
.Nullable
;
33 import org
.jetbrains
.idea
.svn
.MoveRenameReplaceCheck
;
34 import org
.jetbrains
.idea
.svn
.SvnBundle
;
35 import org
.jetbrains
.idea
.svn
.SvnChangeProvider
;
36 import org
.jetbrains
.idea
.svn
.SvnVcs
;
37 import org
.tmatesoft
.svn
.core
.SVNErrorCode
;
38 import org
.tmatesoft
.svn
.core
.SVNException
;
39 import org
.tmatesoft
.svn
.core
.SVNNodeKind
;
40 import org
.tmatesoft
.svn
.core
.wc
.*;
48 public class SvnRollbackEnvironment
extends DefaultRollbackEnvironment
{
49 private final SvnVcs mySvnVcs
;
51 public SvnRollbackEnvironment(SvnVcs svnVcs
) {
56 public String
getRollbackOperationName() {
57 return SvnBundle
.message("action.name.revert");
60 public void rollbackChanges(List
<Change
> changes
, final List
<VcsException
> exceptions
, @NotNull final RollbackProgressListener listener
) {
61 listener
.indeterminate();
62 final SvnChangeProvider changeProvider
= (SvnChangeProvider
) mySvnVcs
.getChangeProvider();
64 final UnversionedFilesGroupCollector collector
= new UnversionedFilesGroupCollector();
66 final ChangesChecker checker
= new ChangesChecker(changeProvider
, collector
);
67 checker
.gather(changes
);
68 exceptions
.addAll(checker
.getExceptions());
70 final SVNWCClient client
= mySvnVcs
.createWCClient();
71 client
.setEventHandler(new ISVNEventHandler() {
72 public void handleEvent(SVNEvent event
, double progress
) {
73 if (event
.getAction() == SVNEventAction
.REVERT
) {
74 final File file
= event
.getFile();
76 listener
.accept(file
);
79 if (event
.getAction() == SVNEventAction
.FAILED_REVERT
) {
80 exceptions
.add(new VcsException("Revert failed"));
84 public void checkCancelled() {
85 listener
.checkCanceled();
92 final Reverter reverter
= new Reverter(client
, exceptions
);
93 reverter
.revert(checker
.getForAdds(), true);
94 reverter
.revert(checker
.getForDeletes(), true);
95 final List
<File
> edits
= checker
.getForEdits();
96 reverter
.revert(edits
.toArray(new File
[edits
.size()]), false);
98 final List
<Trinity
<File
, File
, File
>> fromTo
= collector
.getFromTo();
99 for (Trinity
<File
, File
, File
> trinity
: fromTo
) {
100 if (trinity
.getFirst().exists()) {
101 // parent successfully renamed/moved
102 trinity
.getSecond().renameTo(trinity
.getThird());
105 final List
<Pair
<File
, File
>> toBeDeleted
= collector
.getToBeDeleted();
106 for (Pair
<File
, File
> pair
: toBeDeleted
) {
107 if (pair
.getFirst().exists()) {
108 FileUtil
.delete(pair
.getSecond());
113 private static class Reverter
{
114 private final SVNWCClient myClient
;
115 private final List
<VcsException
> myExceptions
;
117 private Reverter(SVNWCClient client
, List
<VcsException
> exceptions
) {
119 myExceptions
= exceptions
;
122 public void revert(final File
[] files
, final boolean recursive
) {
123 if (files
.length
== 0) return;
125 myClient
.doRevert(files
, recursive
);
127 catch (SVNException e
) {
128 if (e
.getErrorMessage().getErrorCode() != SVNErrorCode
.WC_NOT_DIRECTORY
) {
129 // skip errors on unversioned resources.
130 myExceptions
.add(new VcsException(e
));
136 public void rollbackMissingFileDeletion(List
<FilePath
> filePaths
, final List
<VcsException
> exceptions
,
137 final RollbackProgressListener listener
) {
138 final SVNWCClient wcClient
= mySvnVcs
.createWCClient();
140 List
<File
> files
= ChangesUtil
.filePathsToFiles(filePaths
);
141 for (File file
: files
) {
142 listener
.accept(file
);
144 SVNInfo info
= wcClient
.doInfo(file
, SVNRevision
.BASE
);
145 if (info
!= null && info
.getKind() == SVNNodeKind
.FILE
) {
146 wcClient
.doRevert(file
, false);
148 // do update to restore missing directory.
149 mySvnVcs
.createUpdateClient().doUpdate(file
, SVNRevision
.HEAD
, true);
152 catch (SVNException e
) {
153 exceptions
.add(new VcsException(e
));
158 private static class UnversionedFilesGroupCollector
extends EmptyChangelistBuilder
{
159 private File myCurrentBeforeFile
;
160 private final List
<Pair
<File
, File
>> myToBeDeleted
;
161 private final List
<Trinity
<File
, File
, File
>> myFromTo
;
163 private UnversionedFilesGroupCollector() {
164 myFromTo
= new ArrayList
<Trinity
<File
, File
, File
>>();
165 myToBeDeleted
= new ArrayList
<Pair
<File
, File
>>();
169 public void processUnversionedFile(final VirtualFile file
) {
170 final File to
= new File(myCurrentBeforeFile
, file
.getName());
171 myFromTo
.add(new Trinity
<File
, File
, File
>(myCurrentBeforeFile
, new File(file
.getPath()), to
));
174 public void setBefore(@NotNull final File beforeFile
, @NotNull final File afterFile
) {
175 myCurrentBeforeFile
= beforeFile
;
176 myToBeDeleted
.add(new Pair
<File
, File
>(beforeFile
, afterFile
));
179 public List
<Pair
<File
, File
>> getToBeDeleted() {
180 return myToBeDeleted
;
183 public List
<Trinity
<File
, File
, File
>> getFromTo() {
188 // both adds and deletes
189 private static abstract class SuperfluousRemover
{
190 private final Set
<File
> myParentPaths
;
192 private SuperfluousRemover() {
193 myParentPaths
= new HashSet
<File
>();
197 protected abstract File
accept(final Change change
);
199 public void check(final File file
) {
200 for (Iterator
<File
> iterator
= myParentPaths
.iterator(); iterator
.hasNext();) {
201 final File parentPath
= iterator
.next();
202 if (VfsUtil
.isAncestor(parentPath
, file
, true)) {
204 } else if (VfsUtil
.isAncestor(file
, parentPath
, true)) {
206 // remove others; dont check for 1st variant any more
207 for (; iterator
.hasNext();) {
208 final File innerParentPath
= iterator
.next();
209 if (VfsUtil
.isAncestor(file
, innerParentPath
, true)) {
213 // will be added in the end
216 myParentPaths
.add(file
);
219 public Set
<File
> getParentPaths() {
220 return myParentPaths
;
224 private static class ChangesChecker
{
225 private final SuperfluousRemover myForAdds
;
226 private final SuperfluousRemover myForDeletes
;
227 private final List
<File
> myForEdits
;
229 private final SvnChangeProvider myChangeProvider
;
230 private final UnversionedFilesGroupCollector myCollector
;
232 private final List
<VcsException
> myExceptions
;
234 private ChangesChecker(SvnChangeProvider changeProvider
, UnversionedFilesGroupCollector collector
) {
235 myChangeProvider
= changeProvider
;
236 myCollector
= collector
;
238 myForAdds
= new SuperfluousRemover() {
241 protected File
accept(Change change
) {
242 final ContentRevision beforeRevision
= change
.getBeforeRevision();
243 final ContentRevision afterRevision
= change
.getAfterRevision();
244 if (beforeRevision
== null || MoveRenameReplaceCheck
.check(change
)) {
245 return afterRevision
.getFile().getIOFile();
251 myForDeletes
= new SuperfluousRemover() {
254 protected File
accept(Change change
) {
255 final ContentRevision beforeRevision
= change
.getBeforeRevision();
256 final ContentRevision afterRevision
= change
.getAfterRevision();
257 if (afterRevision
== null || MoveRenameReplaceCheck
.check(change
)) {
258 return beforeRevision
.getFile().getIOFile();
264 myForEdits
= new ArrayList
<File
>();
265 myExceptions
= new ArrayList
<VcsException
>();
268 public void gather(final List
<Change
> changes
) {
269 for (Change change
: changes
) {
270 final ContentRevision beforeRevision
= change
.getBeforeRevision();
271 final ContentRevision afterRevision
= change
.getAfterRevision();
273 if (MoveRenameReplaceCheck
.check(change
)) {
274 myCollector
.setBefore(beforeRevision
.getFile().getIOFile(), afterRevision
.getFile().getIOFile());
276 myChangeProvider
.getChanges(afterRevision
.getFile(), false, myCollector
);
278 catch (SVNException e
) {
279 myExceptions
.add(new VcsException(e
));
283 boolean checked
= getAddDelete(myForAdds
, change
);
284 checked
|= getAddDelete(myForDeletes
, change
);
287 myForEdits
.add(afterRevision
.getFile().getIOFile());
292 private boolean getAddDelete(final SuperfluousRemover superfluousRemover
, final Change change
) {
293 final File file
= superfluousRemover
.accept(change
);
295 superfluousRemover
.check(file
);
301 public File
[] getForAdds() {
302 return convert(myForAdds
.getParentPaths());
305 public File
[] getForDeletes() {
306 return convert(myForDeletes
.getParentPaths());
309 private File
[] convert(final Collection
<File
> paths
) {
310 return paths
.toArray(new File
[paths
.size()]);
313 public List
<VcsException
> getExceptions() {
317 public List
<File
> getForEdits() {