IDEADEV-41062 (Map help button of "Import into Subversion" dialog box)
[fedora-idea.git] / plugins / svn4idea / src / org / jetbrains / idea / svn / SvnFileUrlMappingImpl.java
blob41f8857d2c47f5bddee3cc9cab9562d9be1ac379
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.lifecycle.AtomicSectionsAware;
19 import com.intellij.openapi.components.PersistentStateComponent;
20 import com.intellij.openapi.components.ProjectComponent;
21 import com.intellij.openapi.components.State;
22 import com.intellij.openapi.components.Storage;
23 import com.intellij.openapi.diagnostic.Logger;
24 import com.intellij.openapi.progress.ProcessCanceledException;
25 import com.intellij.openapi.project.Project;
26 import com.intellij.openapi.util.Getter;
27 import com.intellij.openapi.util.Ref;
28 import com.intellij.openapi.util.io.FileUtil;
29 import com.intellij.openapi.vcs.ObjectsConvertor;
30 import com.intellij.openapi.vcs.ProjectLevelVcsManager;
31 import com.intellij.openapi.vcs.changes.ChangeListManager;
32 import com.intellij.openapi.vcs.changes.InvokeAfterUpdateMode;
33 import com.intellij.openapi.vcs.changes.VcsDirtyScopeManager;
34 import com.intellij.openapi.vcs.impl.StringLenComparator;
35 import com.intellij.openapi.vfs.LocalFileSystem;
36 import com.intellij.openapi.vfs.VfsUtil;
37 import com.intellij.openapi.vfs.VirtualFile;
38 import com.intellij.util.Consumer;
39 import com.intellij.util.containers.Convertor;
40 import org.jetbrains.annotations.NotNull;
41 import org.jetbrains.annotations.Nullable;
42 import org.tmatesoft.svn.core.SVNDepth;
43 import org.tmatesoft.svn.core.SVNException;
44 import org.tmatesoft.svn.core.SVNURL;
45 import org.tmatesoft.svn.core.internal.util.SVNPathUtil;
46 import org.tmatesoft.svn.core.internal.util.SVNURLUtil;
47 import org.tmatesoft.svn.core.wc.*;
49 import java.io.File;
50 import java.util.*;
52 @State(
53 name = "SvnFileUrlMappingImpl",
54 storages = {
55 @Storage(
56 id ="other",
57 file = "$WORKSPACE_FILE$"
60 class SvnFileUrlMappingImpl implements SvnFileUrlMapping, PersistentStateComponent<SvnMappingSavedPart>, ProjectComponent {
61 private static final Logger LOG = Logger.getInstance("#org.jetbrains.idea.svn.SvnFileUrlMappingImpl");
63 private final SvnCompatibilityChecker myChecker;
64 private final Object myMonitor = new Object();
65 // strictly: what real roots are under what vcs mappings
66 private final SvnMapping myMapping;
67 // grouped; if there are several mappings one under another, will return the upmost
68 private final SvnMapping myMoreRealMapping;
69 private final MyRootsHelper myHelper;
70 private final Project myProject;
71 private final NestedCopiesSink myTempSink;
72 private boolean myInitialized;
74 private class MyRootsHelper extends ThreadLocalDefendedInvoker<VirtualFile[]> {
75 private final ProjectLevelVcsManager myPlVcsManager;
77 private MyRootsHelper(final ProjectLevelVcsManager vcsManager) {
78 myPlVcsManager = vcsManager;
81 protected VirtualFile[] execute() {
82 return myPlVcsManager.getRootsUnderVcs(SvnVcs.getInstance(myProject));
86 public static SvnFileUrlMappingImpl getInstance(final Project project) {
87 return project.getComponent(SvnFileUrlMappingImpl.class);
90 private SvnFileUrlMappingImpl(final Project project, final ProjectLevelVcsManager vcsManager) {
91 myProject = project;
92 myMapping = new SvnMapping();
93 myMoreRealMapping = new SvnMapping();
94 myHelper = new MyRootsHelper(vcsManager);
95 myChecker = new SvnCompatibilityChecker(project);
96 myTempSink = new NestedCopiesSink();
99 @Nullable
100 public SVNURL getUrlForFile(final File file) {
101 final RootUrlInfo rootUrlInfo = getWcRootForFilePath(file);
102 if (rootUrlInfo == null) {
103 return null;
106 final String absolutePath = file.getAbsolutePath();
107 final String rootAbsPath = rootUrlInfo.getIoFile().getAbsolutePath();
108 if (absolutePath.length() < rootAbsPath.length()) {
109 // remove last separator from etalon name
110 if (absolutePath.equals(rootAbsPath.substring(0, rootAbsPath.length() - 1))) {
111 return rootUrlInfo.getAbsoluteUrlAsUrl();
113 return null;
115 final String relativePath = absolutePath.substring(rootAbsPath.length());
116 try {
117 return rootUrlInfo.getAbsoluteUrlAsUrl().appendPath(FileUtil.toSystemIndependentName(relativePath), true);
119 catch (SVNException e) {
120 LOG.info(e);
121 return null;
125 @Nullable
126 public String getLocalPath(final String url) {
127 synchronized (myMonitor) {
128 final String rootUrl = getUrlRootForUrl(url);
129 if (rootUrl == null) {
130 return null;
132 final RootUrlInfo parentInfo = myMapping.byUrl(rootUrl);
133 if (parentInfo == null) {
134 return null;
137 return fileByUrl(parentInfo.getIoFile().getAbsolutePath(), rootUrl, url).getAbsolutePath();
141 public static File fileByUrl(final String parentPath, final String parentUrl, final String childUrl) {
142 return new File(parentPath, childUrl.substring(parentUrl.length()));
145 @Nullable
146 public RootUrlInfo getWcRootForFilePath(final File file) {
147 synchronized (myMonitor) {
148 final String root = getRootForPath(file);
149 if (root == null) {
150 return null;
153 return myMapping.byFile(root);
157 public boolean rootsDiffer() {
158 synchronized (myMonitor) {
159 return myMapping.isRootsDifferFromSettings();
163 @Nullable
164 public RootUrlInfo getWcRootForUrl(final String url) {
165 synchronized (myMonitor) {
166 final String rootUrl = getUrlRootForUrl(url);
167 if (rootUrl == null) {
168 return null;
171 final RootUrlInfo result = myMapping.byUrl(rootUrl);
172 if (result == null) {
173 LOG.info("Inconsistent maps for url:" + url + " found root url: " + rootUrl);
174 return null;
176 return result;
181 * Returns real working copies roots - if there is <Project Root> -> Subversion setting,
182 * and there is one working copy, will return one root
184 public List<RootUrlInfo> getAllWcInfos() {
185 synchronized (myMonitor) {
186 // a copy is created inside
187 return myMoreRealMapping.getAllCopies();
191 public List<VirtualFile> convertRoots(final List<VirtualFile> result) {
192 if (myHelper.isInside()) return result;
194 synchronized (myMonitor) {
195 final List<VirtualFile> cachedRoots = myMapping.getUnderVcsRoots();
196 final List<VirtualFile> lonelyRoots = myMapping.getLonelyRoots();
197 if (! lonelyRoots.isEmpty()) {
198 myChecker.reportNoRoots(lonelyRoots);
200 if (cachedRoots.isEmpty()) {
201 // todo +-
202 return result;
204 return cachedRoots;
208 public void acceptNestedData(final Set<NestedCopiesBuilder.MyPointInfo> set) {
209 myTempSink.add(set);
212 private boolean init() {
213 synchronized (myMonitor) {
214 final boolean result = myInitialized;
215 myInitialized = true;
216 return result;
220 public void realRefresh(final AtomicSectionsAware atomicSectionsAware) {
221 final SvnVcs vcs = SvnVcs.getInstance(myProject);
222 final VirtualFile[] roots = myHelper.executeDefended();
224 final CopiesApplier copiesApplier = new CopiesApplier();
225 final CopiesDetector copiesDetector = new CopiesDetector(atomicSectionsAware, vcs, copiesApplier, new Getter<NestedCopiesData>() {
226 public NestedCopiesData get() {
227 return myTempSink.receive();
230 // do not send additional request for nested copies when in init state
231 copiesDetector.detectCopyRoots(roots, init());
234 private class CopiesApplier {
235 public void apply(final SvnVcs vcs, final AtomicSectionsAware atomicSectionsAware, final List<RootUrlInfo> roots,
236 final List<VirtualFile> lonelyRoots) {
237 final SvnMapping mapping = new SvnMapping();
238 mapping.addAll(roots);
239 mapping.reportLonelyRoots(lonelyRoots);
241 final SvnMapping groupedMapping = new SvnMapping();
242 final List<RootUrlInfo> filtered = new ArrayList<RootUrlInfo>();
243 ForNestedRootChecker.filterOutSuperfluousChildren(vcs, roots, filtered);
245 groupedMapping.addAll(filtered);
247 // apply
248 try {
249 atomicSectionsAware.enter();
250 synchronized (myMonitor) {
251 myMapping.copyFrom(mapping);
252 myMoreRealMapping.copyFrom(groupedMapping);
254 } finally {
255 atomicSectionsAware.exit();
257 myProject.getMessageBus().syncPublisher(SvnVcs.ROOTS_RELOADED).run();
261 private static class CopiesDetector {
262 private final AtomicSectionsAware myAtomicSectionsAware;
263 private final SvnVcs myVcs;
264 private final CopiesApplier myApplier;
265 private final List<VirtualFile> myLonelyRoots;
266 private final List<RootUrlInfo> myTopRoots;
267 private final RepositoryRoots myRepositoryRoots;
268 private final Getter<NestedCopiesData> myGate;
270 private CopiesDetector(final AtomicSectionsAware atomicSectionsAware, final SvnVcs vcs, final CopiesApplier applier,
271 final Getter<NestedCopiesData> gate) {
272 myAtomicSectionsAware = atomicSectionsAware;
273 myVcs = vcs;
274 myApplier = applier;
275 myGate = gate;
276 myTopRoots = new ArrayList<RootUrlInfo>();
277 myLonelyRoots = new ArrayList<VirtualFile>();
278 myRepositoryRoots = new RepositoryRoots(myVcs);
281 public void detectCopyRoots(final VirtualFile[] roots, final boolean clearState) {
282 final Getter<Boolean> cancelGetter = new Getter<Boolean>() {
283 public Boolean get() {
284 return myAtomicSectionsAware.shouldExitAsap();
288 for (final VirtualFile vcsRoot : roots) {
289 final List<Real> foundRoots = ForNestedRootChecker.getAllNestedWorkingCopies(vcsRoot, myVcs, false, cancelGetter);
290 if (foundRoots.isEmpty()) {
291 myLonelyRoots.add(vcsRoot);
293 // filter out bad(?) items
294 for (Real foundRoot : foundRoots) {
295 final SVNURL repoRoot = foundRoot.getInfo().getRepositoryRootURL();
296 if (repoRoot == null) {
297 LOG.info("Error: cannot find repository URL for versioned folder: " + foundRoot.getFile().getPath());
298 } else {
299 myRepositoryRoots.register(repoRoot);
300 myTopRoots.add(new RootUrlInfo(repoRoot, foundRoot.getInfo().getURL(),
301 SvnFormatSelector.getWorkingCopyFormat(foundRoot.getInfo().getFile()), foundRoot.getFile(), vcsRoot));
306 if (! SvnConfiguration.getInstance(myVcs.getProject()).DETECT_NESTED_COPIES) {
307 myApplier.apply(myVcs, myAtomicSectionsAware, myTopRoots, myLonelyRoots);
308 } else {
309 addNestedRoots(clearState);
313 private void addNestedRoots(final boolean clearState) {
314 final List<VirtualFile> basicVfRoots = ObjectsConvertor.convert(myTopRoots, new Convertor<RootUrlInfo, VirtualFile>() {
315 public VirtualFile convert(final RootUrlInfo real) {
316 return real.getVirtualFile();
320 final ChangeListManager clManager = ChangeListManager.getInstance(myVcs.getProject());
322 if (clearState) {
323 // clear what was reported before (could be for currently-not-existing roots)
324 myGate.get();
326 clManager.invokeAfterUpdate(new Runnable() {
327 public void run() {
328 final List<RootUrlInfo> nestedRoots = new ArrayList<RootUrlInfo>();
330 for (NestedCopiesBuilder.MyPointInfo info : myGate.get().getSet()) {
331 if (NestedCopyType.external.equals(info.getType())) {
332 try {
333 final SVNStatus svnStatus = SvnUtil.getStatus(myVcs, new File(info.getFile().getPath()));
334 if (svnStatus.getURL() == null) continue;
335 info.setUrl(svnStatus.getURL());
336 info.setFormat(WorkingCopyFormat.getInstance(svnStatus.getWorkingCopyFormat()));
338 catch (Exception e) {
339 continue;
342 for (RootUrlInfo topRoot : myTopRoots) {
343 if (VfsUtil.isAncestor(topRoot.getVirtualFile(), info.getFile(), true)) {
344 final SVNURL repoRoot = myRepositoryRoots.ask(info.getUrl());
345 if (repoRoot != null) {
346 nestedRoots.add(new RootUrlInfo(repoRoot, info.getUrl(), info.getFormat(), info.getFile(), topRoot.getRoot()));
348 break;
353 myTopRoots.addAll(nestedRoots);
354 myApplier.apply(myVcs, myAtomicSectionsAware, myTopRoots, myLonelyRoots);
356 }, InvokeAfterUpdateMode.SILENT_CALLBACK_POOLED, null, new Consumer<VcsDirtyScopeManager>() {
357 public void consume(VcsDirtyScopeManager vcsDirtyScopeManager) {
358 if (clearState) {
359 vcsDirtyScopeManager.filesDirty(null, basicVfRoots);
362 }, null);
366 private static SVNStatus getExternalItemStatus(final SvnVcs vcs, final File file) {
367 final SVNStatusClient statusClient = vcs.createStatusClient();
368 try {
369 if (file.isDirectory()) {
370 return statusClient.doStatus(file, false);
371 } else {
372 final File parent = file.getParentFile();
373 if (parent != null) {
374 statusClient.setFilesProvider(new ISVNStatusFileProvider() {
375 public Map getChildrenFiles(File parent) {
376 return Collections.singletonMap(file.getAbsolutePath(), file);
379 final Ref<SVNStatus> refStatus = new Ref<SVNStatus>();
380 statusClient.doStatus(parent, SVNRevision.WORKING, SVNDepth.FILES, false, true, false, false, new ISVNStatusHandler() {
381 public void handleStatus(final SVNStatus status) throws SVNException {
382 if (file.equals(status.getFile())) {
383 refStatus.set(status);
386 }, null);
387 return refStatus.get();
391 catch (SVNException e) {
394 return null;
397 private static class RepositoryRoots {
398 private final SvnVcs myVcs;
399 private final Set<SVNURL> myRoots;
401 private RepositoryRoots(final SvnVcs vcs) {
402 myVcs = vcs;
403 myRoots = new HashSet<SVNURL>();
406 public void register(final SVNURL url) {
407 myRoots.add(url);
410 public SVNURL ask(final SVNURL url) {
411 for (SVNURL root : myRoots) {
412 if (root.equals(SVNURLUtil.getCommonURLAncestor(root, url))) {
413 return root;
416 final SVNURL newRoot = SvnUtil.getRepositoryRoot(myVcs, url);
417 if (newRoot != null) {
418 myRoots.add(newRoot);
420 return newRoot;
424 @Nullable
425 public String getUrlRootForUrl(final String currentUrl) {
426 for (String url : myMapping.getUrls()) {
427 if (SVNPathUtil.isAncestor(url, currentUrl)) {
428 return url;
431 return null;
434 @Nullable
435 public String getRootForPath(final File currentPath) {
436 String convertedPath = currentPath.getAbsolutePath();
437 convertedPath = (currentPath.isDirectory() && (! convertedPath.endsWith(File.separator))) ? convertedPath + File.separator :
438 convertedPath;
439 final List<String> paths;
440 synchronized (myMonitor) {
441 paths = new ArrayList<String>(myMapping.getFileRoots());
443 Collections.sort(paths, StringLenComparator.getDescendingInstance());
445 for (String path : paths) {
446 if (FileUtil.startsWith(convertedPath, path)) {
447 return path;
450 return null;
453 public VirtualFile[] getNotFilteredRoots() {
454 return myHelper.executeDefended();
457 public boolean isEmpty() {
458 synchronized (myMonitor) {
459 return myMapping.isEmpty();
463 public SvnMappingSavedPart getState() {
464 final SvnMappingSavedPart result = new SvnMappingSavedPart();
466 final SvnMapping mapping = new SvnMapping();
467 final SvnMapping realMapping = new SvnMapping();
468 synchronized (myMonitor) {
469 mapping.copyFrom(myMapping);
470 realMapping.copyFrom(myMoreRealMapping);
473 for (RootUrlInfo info : mapping.getAllCopies()) {
474 result.add(convert(info));
476 for (RootUrlInfo info : realMapping.getAllCopies()) {
477 result.addReal(convert(info));
479 return result;
482 private SvnCopyRootSimple convert(final RootUrlInfo info) {
483 final SvnCopyRootSimple copy = new SvnCopyRootSimple();
484 copy.myVcsRoot = FileUtil.toSystemDependentName(info.getRoot().getPath());
485 copy.myCopyRoot = info.getIoFile().getAbsolutePath();
486 return copy;
489 public void loadState(final SvnMappingSavedPart state) {
490 final SvnMapping mapping = new SvnMapping();
491 final SvnMapping realMapping = new SvnMapping();
493 try {
494 fillMapping(mapping, state.getMappingRoots());
495 fillMapping(realMapping, state.getMoreRealMappingRoots());
496 } catch (ProcessCanceledException e) {
497 throw e;
498 } catch (Throwable t) {
499 LOG.info(t);
500 return;
503 synchronized (myMonitor) {
504 myMapping.copyFrom(mapping);
505 myMoreRealMapping.copyFrom(realMapping);
509 private void fillMapping(final SvnMapping mapping, final List<SvnCopyRootSimple> list) {
510 final LocalFileSystem lfs = LocalFileSystem.getInstance();
512 for (SvnCopyRootSimple simple : list) {
513 final VirtualFile copyRoot = lfs.findFileByIoFile(new File(simple.myCopyRoot));
514 final VirtualFile vcsRoot = lfs.findFileByIoFile(new File(simple.myVcsRoot));
516 if (copyRoot == null || vcsRoot == null) continue;
518 final SvnVcs vcs = SvnVcs.getInstance(myProject);
519 final SVNInfo svnInfo = vcs.getInfo(copyRoot);
520 if ((svnInfo == null) || (svnInfo.getRepositoryRootURL() == null)) continue;
522 mapping.add(new RootUrlInfo(svnInfo.getRepositoryRootURL(), svnInfo.getURL(),
523 SvnFormatSelector.getWorkingCopyFormat(svnInfo.getFile()), copyRoot, vcsRoot));
527 public void projectOpened() {
530 public void projectClosed() {
533 @NotNull
534 public String getComponentName() {
535 return "SvnFileUrlMappingImpl";
538 public void initComponent() {
541 public void disposeComponent() {