update copyright
[fedora-idea.git] / plugins / svn4idea / src / org / jetbrains / idea / svn / rollback / SvnRollbackEnvironment.java
blobf4b5d0946caeb6a21b5c9aa8b190044ef3d7f08c
1 /*
2 * Copyright 2000-2009 JetBrains s.r.o.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
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.*;
42 import java.io.File;
43 import java.util.*;
45 /**
46 * @author yole
48 public class SvnRollbackEnvironment extends DefaultRollbackEnvironment {
49 private final SvnVcs mySvnVcs;
51 public SvnRollbackEnvironment(SvnVcs svnVcs) {
52 mySvnVcs = svnVcs;
55 @Override
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();
75 if (file != null) {
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();
87 });
89 // adds (deletes)
90 // deletes (adds)
91 // modifications
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) {
118 myClient = client;
119 myExceptions = exceptions;
122 public void revert(final File[] files, final boolean recursive) {
123 if (files.length == 0) return;
124 try {
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);
143 try {
144 SVNInfo info = wcClient.doInfo(file, SVNRevision.BASE);
145 if (info != null && info.getKind() == SVNNodeKind.FILE) {
146 wcClient.doRevert(file, false);
147 } else {
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>>();
168 @Override
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() {
184 return myFromTo;
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>();
196 @Nullable
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)) {
203 return;
204 } else if (VfsUtil.isAncestor(file, parentPath, true)) {
205 iterator.remove();
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)) {
210 iterator.remove();
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() {
239 @Nullable
240 @Override
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();
247 return null;
251 myForDeletes = new SuperfluousRemover() {
252 @Nullable
253 @Override
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();
260 return null;
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());
275 try {
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);
286 if (! checked) {
287 myForEdits.add(afterRevision.getFile().getIOFile());
292 private boolean getAddDelete(final SuperfluousRemover superfluousRemover, final Change change) {
293 final File file = superfluousRemover.accept(change);
294 if (file != null) {
295 superfluousRemover.check(file);
296 return true;
298 return false;
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() {
314 return myExceptions;
317 public List<File> getForEdits() {
318 return myForEdits;