IDEA-26360 (Performance and inconsistency issues with svn:externals and "Detect neste...
[fedora-idea.git] / plugins / svn4idea / src / org / jetbrains / idea / svn / SvnChangeProvider.java
blob3e067f8b6a635ec73a8dc6412e84e68f56635f4c
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;
18 import com.intellij.openapi.diagnostic.Logger;
19 import com.intellij.openapi.progress.ProcessCanceledException;
20 import com.intellij.openapi.progress.ProgressIndicator;
21 import com.intellij.openapi.progress.ProgressManager;
22 import com.intellij.openapi.util.Comparing;
23 import com.intellij.openapi.util.text.StringUtil;
24 import com.intellij.openapi.vcs.FilePath;
25 import com.intellij.openapi.vcs.FilePathImpl;
26 import com.intellij.openapi.vcs.VcsBundle;
27 import com.intellij.openapi.vcs.VcsException;
28 import com.intellij.openapi.vcs.actions.VcsContextFactory;
29 import com.intellij.openapi.vcs.changes.*;
30 import com.intellij.openapi.vfs.VfsUtil;
31 import com.intellij.openapi.vfs.VirtualFile;
32 import com.intellij.util.EventDispatcher;
33 import org.jetbrains.annotations.NotNull;
34 import org.jetbrains.annotations.Nullable;
35 import org.jetbrains.idea.svn.actions.CleanupWorker;
36 import org.tmatesoft.svn.core.SVNDepth;
37 import org.tmatesoft.svn.core.SVNException;
38 import org.tmatesoft.svn.core.SVNNodeKind;
39 import org.tmatesoft.svn.core.SVNURL;
40 import org.tmatesoft.svn.core.internal.util.SVNPathUtil;
41 import org.tmatesoft.svn.core.wc.ISVNStatusFileProvider;
42 import org.tmatesoft.svn.core.wc.SVNStatus;
43 import org.tmatesoft.svn.core.wc.SVNStatusType;
45 import java.io.File;
46 import java.util.*;
48 /**
49 * @author max
50 * @author yole
52 public class SvnChangeProvider implements ChangeProvider {
53 private static final Logger LOG = Logger.getInstance("#org.jetbrains.idea.svn.SvnChangeProvider");
54 public static final String ourDefaultListName = VcsBundle.message("changes.default.changlist.name");
56 private final SvnVcs myVcs;
57 private final VcsContextFactory myFactory;
58 private SvnFileUrlMappingImpl mySvnFileUrlMapping;
60 public SvnChangeProvider(final SvnVcs vcs) {
61 myVcs = vcs;
62 myFactory = VcsContextFactory.SERVICE.getInstance();
63 mySvnFileUrlMapping = (SvnFileUrlMappingImpl) vcs.getSvnFileUrlMapping();
66 public void getChanges(final VcsDirtyScope dirtyScope, final ChangelistBuilder builder, ProgressIndicator progress,
67 final ChangeListManagerGate addGate) throws VcsException {
68 final SvnScopeZipper zipper = new SvnScopeZipper(dirtyScope);
69 zipper.run();
71 final Map<String, SvnScopeZipper.MyDirNonRecursive> nonRecursiveMap = zipper.getNonRecursiveDirs();
72 final ISVNStatusFileProvider fileProvider = createFileProvider(nonRecursiveMap);
74 try {
75 final SvnChangeProviderContext context = new SvnChangeProviderContext(myVcs, builder, progress);
77 final StatusWalkerPartnerImpl partner = new StatusWalkerPartnerImpl(myVcs, progress);
78 final NestedCopiesBuilder nestedCopiesBuilder = new NestedCopiesBuilder();
80 final EventDispatcher<StatusReceiver> statusReceiver = EventDispatcher.create(StatusReceiver.class);
81 statusReceiver.addListener(context);
82 statusReceiver.addListener(nestedCopiesBuilder);
84 final SvnRecursiveStatusWalker walker = new SvnRecursiveStatusWalker(statusReceiver.getMulticaster(), partner);
86 for (FilePath path : zipper.getRecursiveDirs()) {
87 walker.go(path, SVNDepth.INFINITY);
90 partner.setFileProvider(fileProvider);
91 for (SvnScopeZipper.MyDirNonRecursive item : nonRecursiveMap.values()) {
92 walker.go(item.getDir(), SVNDepth.FILES);
95 // they are taken under non recursive: ENTRIES file is read anyway, so we get to know parent status also for free
96 /*for (FilePath path : zipper.getSingleFiles()) {
97 FileStatus status = getParentStatus(context, path);
98 processFile(path, context, status, false, context.getClient());
99 }*/
101 processCopiedAndDeleted(context);
103 mySvnFileUrlMapping.acceptNestedData(nestedCopiesBuilder.getSet());
105 catch (SVNException e) {
106 throw new VcsException(e);
110 private ISVNStatusFileProvider createFileProvider(Map<String, SvnScopeZipper.MyDirNonRecursive> nonRecursiveMap) {
111 // translate into terms of File.getAbsolutePath()
112 final Map<String, Map> preparedMap = new HashMap<String, Map>();
113 for (SvnScopeZipper.MyDirNonRecursive item : nonRecursiveMap.values()) {
114 final Map result = new HashMap();
115 for (FilePath path : item.getChildrenList()) {
116 result.put(path.getName(), path.getIOFile());
118 preparedMap.put(item.getDir().getIOFile().getAbsolutePath(), result);
120 return new ISVNStatusFileProvider() {
121 public Map getChildrenFiles(File parent) {
122 return preparedMap.get(parent.getAbsolutePath());
127 private void processCopiedAndDeleted(final SvnChangeProviderContext context) throws SVNException {
128 for(SvnChangedFile copiedFile: context.getCopiedFiles()) {
129 if (context.isCanceled()) {
130 throw new ProcessCanceledException();
132 processCopiedFile(copiedFile, context.getBuilder(), context);
134 for(SvnChangedFile deletedFile: context.getDeletedFiles()) {
135 if (context.isCanceled()) {
136 throw new ProcessCanceledException();
138 context.processStatus(deletedFile.getFilePath(), deletedFile.getStatus());
142 public void getChanges(final FilePath path, final boolean recursive, final ChangelistBuilder builder) throws SVNException {
143 final SvnChangeProviderContext context = new SvnChangeProviderContext(myVcs, builder, null);
144 final StatusWalkerPartnerImpl partner = new StatusWalkerPartnerImpl(myVcs, ProgressManager.getInstance().getProgressIndicator());
145 final SvnRecursiveStatusWalker walker = new SvnRecursiveStatusWalker(context, partner);
146 walker.go(path, recursive ? SVNDepth.INFINITY : SVNDepth.IMMEDIATES);
147 processCopiedAndDeleted(context);
150 @Nullable
151 private String changeListNameFromStatus(final SVNStatus status) {
152 if (WorkingCopyFormat.getInstance(status.getWorkingCopyFormat()).supportsChangelists()) {
153 if (SVNNodeKind.FILE.equals(status.getKind())) {
154 final String clName = status.getChangelistName();
155 return (clName == null) ? null : clName;
158 // always null for earlier versions
159 return null;
162 private void processCopiedFile(SvnChangedFile copiedFile, ChangelistBuilder builder, SvnChangeProviderContext context) throws SVNException {
163 boolean foundRename = false;
164 final SVNStatus copiedStatus = copiedFile.getStatus();
165 final String copyFromURL = copiedFile.getCopyFromURL();
166 final FilePath copiedToPath = copiedFile.getFilePath();
168 // if copy target is _deleted_, treat like deleted, not moved!
169 /*for (Iterator<SvnChangedFile> iterator = context.getDeletedFiles().iterator(); iterator.hasNext();) {
170 final SvnChangedFile deletedFile = iterator.next();
171 final FilePath deletedPath = deletedFile.getFilePath();
173 if (Comparing.equal(deletedPath, copiedToPath)) {
174 return;
178 final Set<SvnChangedFile> deletedToDelete = new HashSet<SvnChangedFile>();
180 for (Iterator<SvnChangedFile> iterator = context.getDeletedFiles().iterator(); iterator.hasNext();) {
181 SvnChangedFile deletedFile = iterator.next();
182 final SVNStatus deletedStatus = deletedFile.getStatus();
183 if ((deletedStatus != null) && (deletedStatus.getURL() != null) && Comparing.equal(copyFromURL, deletedStatus.getURL().toString())) {
184 final String clName = changeListNameFromStatus(copiedFile.getStatus());
185 builder.processChangeInList(context.createMovedChange(createBeforeRevision(deletedFile, true),
186 CurrentContentRevision.create(copiedFile.getFilePath()), copiedStatus, deletedStatus), clName, SvnVcs.getKey());
187 deletedToDelete.add(deletedFile);
188 for(Iterator<SvnChangedFile> iterChild = context.getDeletedFiles().iterator(); iterChild.hasNext();) {
189 SvnChangedFile deletedChild = iterChild.next();
190 final SVNStatus childStatus = deletedChild.getStatus();
191 if (childStatus == null) {
192 continue;
194 final SVNURL childUrl = childStatus.getURL();
195 if (childUrl == null) {
196 continue;
198 final String childURL = childUrl.toString();
199 if (StringUtil.startsWithConcatenationOf(childURL, copyFromURL, "/")) {
200 String relativePath = childURL.substring(copyFromURL.length());
201 File newPath = new File(copiedFile.getFilePath().getIOFile(), relativePath);
202 FilePath newFilePath = myFactory.createFilePathOn(newPath);
203 if (!context.isDeleted(newFilePath)) {
204 builder.processChangeInList(context.createMovedChange(createBeforeRevision(deletedChild, true),
205 CurrentContentRevision.create(newFilePath),
206 context.getTreeConflictStatus(newPath), childStatus), clName, SvnVcs.getKey());
207 deletedToDelete.add(deletedChild);
211 foundRename = true;
212 break;
216 final List<SvnChangedFile> deletedFiles = context.getDeletedFiles();
217 for (SvnChangedFile file : deletedToDelete) {
218 deletedFiles.remove(file);
221 // handle the case when the deleted file wasn't included in the dirty scope - try searching for the local copy
222 // by building a relative url
223 if (!foundRename && copiedStatus.getURL() != null) {
224 File wcPath = guessWorkingCopyPath(copiedStatus.getFile(), copiedStatus.getURL(), copyFromURL);
225 SVNStatus status;
226 try {
227 status = context.getClient().doStatus(wcPath, false);
229 catch(SVNException ex) {
230 status = null;
232 if (status != null && status.getContentsStatus() == SVNStatusType.STATUS_DELETED) {
233 final FilePath filePath = myFactory.createFilePathOnDeleted(wcPath, false);
234 final SvnContentRevision beforeRevision = SvnContentRevision.create(myVcs, filePath, status.getCommittedRevision());
235 final ContentRevision afterRevision = CurrentContentRevision.create(copiedFile.getFilePath());
236 builder.processChangeInList(context.createMovedChange(beforeRevision, afterRevision, copiedStatus, status), changeListNameFromStatus(status),
237 SvnVcs.getKey());
238 foundRename = true;
242 if (!foundRename) {
243 // for debug
244 LOG.info("Rename not found for " + copiedFile.getFilePath().getPresentableUrl());
245 context.processStatus(copiedFile.getFilePath(), copiedStatus);
249 private SvnContentRevision createBeforeRevision(final SvnChangedFile changedFile, final boolean forDeleted) {
250 return SvnContentRevision.create(myVcs,
251 forDeleted ? FilePathImpl.createForDeletedFile(changedFile.getStatus().getFile(), changedFile.getFilePath().isDirectory()) :
252 changedFile.getFilePath(), changedFile.getStatus().getCommittedRevision());
255 private static File guessWorkingCopyPath(final File file, @NotNull final SVNURL url, final String copyFromURL) throws SVNException {
256 String copiedPath = url.getPath();
257 String copyFromPath = SVNURL.parseURIEncoded(copyFromURL).getPath();
258 String commonPathAncestor = SVNPathUtil.getCommonPathAncestor(copiedPath, copyFromPath);
259 int pathSegmentCount = SVNPathUtil.getSegmentsCount(copiedPath);
260 int ancestorSegmentCount = SVNPathUtil.getSegmentsCount(commonPathAncestor);
261 boolean startsWithSlash = file.getAbsolutePath().startsWith("/");
262 List<String> segments = StringUtil.split(file.getPath(), File.separator);
263 List<String> copyFromPathSegments = StringUtil.split(copyFromPath, "/");
264 List<String> resultSegments = new ArrayList<String>();
265 final int keepSegments = segments.size() - pathSegmentCount + ancestorSegmentCount;
266 for(int i=0; i< keepSegments; i++) {
267 resultSegments.add(segments.get(i));
269 for(int i=ancestorSegmentCount; i<copyFromPathSegments.size(); i++) {
270 resultSegments.add(copyFromPathSegments.get(i));
273 String result = StringUtil.join(resultSegments, "/");
274 if (startsWithSlash) {
275 result = "/" + result;
277 return new File(result);
280 public boolean isModifiedDocumentTrackingRequired() {
281 return true;
284 public void doCleanup(final List<VirtualFile> files) {
285 new CleanupWorker(VfsUtil.toVirtualFileArray(files), myVcs.getProject(), "action.Subversion.cleanup.progress.title").execute();