1 package org
.jetbrains
.idea
.svn
;
3 import com
.intellij
.lifecycle
.AtomicSectionsAware
;
4 import com
.intellij
.openapi
.components
.PersistentStateComponent
;
5 import com
.intellij
.openapi
.components
.ProjectComponent
;
6 import com
.intellij
.openapi
.components
.State
;
7 import com
.intellij
.openapi
.components
.Storage
;
8 import com
.intellij
.openapi
.diagnostic
.Logger
;
9 import com
.intellij
.openapi
.progress
.ProcessCanceledException
;
10 import com
.intellij
.openapi
.project
.Project
;
11 import com
.intellij
.openapi
.util
.Getter
;
12 import com
.intellij
.openapi
.util
.Ref
;
13 import com
.intellij
.openapi
.util
.io
.FileUtil
;
14 import com
.intellij
.openapi
.vcs
.ObjectsConvertor
;
15 import com
.intellij
.openapi
.vcs
.ProjectLevelVcsManager
;
16 import com
.intellij
.openapi
.vcs
.changes
.ChangeListManager
;
17 import com
.intellij
.openapi
.vcs
.changes
.InvokeAfterUpdateMode
;
18 import com
.intellij
.openapi
.vcs
.changes
.VcsDirtyScopeManager
;
19 import com
.intellij
.openapi
.vcs
.impl
.StringLenComparator
;
20 import com
.intellij
.openapi
.vfs
.LocalFileSystem
;
21 import com
.intellij
.openapi
.vfs
.VfsUtil
;
22 import com
.intellij
.openapi
.vfs
.VirtualFile
;
23 import com
.intellij
.util
.Consumer
;
24 import com
.intellij
.util
.containers
.Convertor
;
25 import org
.jetbrains
.annotations
.NotNull
;
26 import org
.jetbrains
.annotations
.Nullable
;
27 import org
.tmatesoft
.svn
.core
.SVNDepth
;
28 import org
.tmatesoft
.svn
.core
.SVNException
;
29 import org
.tmatesoft
.svn
.core
.SVNURL
;
30 import org
.tmatesoft
.svn
.core
.internal
.util
.SVNPathUtil
;
31 import org
.tmatesoft
.svn
.core
.internal
.util
.SVNURLUtil
;
32 import org
.tmatesoft
.svn
.core
.wc
.*;
38 name
= "SvnFileUrlMappingImpl",
42 file
= "$WORKSPACE_FILE$"
45 class SvnFileUrlMappingImpl
implements SvnFileUrlMapping
, PersistentStateComponent
<SvnMappingSavedPart
>, ProjectComponent
{
46 private static final Logger LOG
= Logger
.getInstance("#org.jetbrains.idea.svn.SvnFileUrlMappingImpl");
48 private final SvnCompatibilityChecker myChecker
;
49 private final Object myMonitor
= new Object();
50 // strictly: what real roots are under what vcs mappings
51 private final SvnMapping myMapping
;
52 // grouped; if there are several mappings one under another, will return the upmost
53 private final SvnMapping myMoreRealMapping
;
54 private final MyRootsHelper myHelper
;
55 private final Project myProject
;
56 private final NestedCopiesSink myTempSink
;
57 private boolean myInitialized
;
59 private class MyRootsHelper
extends ThreadLocalDefendedInvoker
<VirtualFile
[]> {
60 private final ProjectLevelVcsManager myPlVcsManager
;
62 private MyRootsHelper(final ProjectLevelVcsManager vcsManager
) {
63 myPlVcsManager
= vcsManager
;
66 protected VirtualFile
[] execute() {
67 return myPlVcsManager
.getRootsUnderVcs(SvnVcs
.getInstance(myProject
));
71 public static SvnFileUrlMappingImpl
getInstance(final Project project
) {
72 return project
.getComponent(SvnFileUrlMappingImpl
.class);
75 private SvnFileUrlMappingImpl(final Project project
, final ProjectLevelVcsManager vcsManager
) {
77 myMapping
= new SvnMapping();
78 myMoreRealMapping
= new SvnMapping();
79 myHelper
= new MyRootsHelper(vcsManager
);
80 myChecker
= new SvnCompatibilityChecker(project
);
81 myTempSink
= new NestedCopiesSink();
85 public SVNURL
getUrlForFile(final File file
) {
86 final RootUrlInfo rootUrlInfo
= getWcRootForFilePath(file
);
87 if (rootUrlInfo
== null) {
91 final String absolutePath
= file
.getAbsolutePath();
92 final String rootAbsPath
= rootUrlInfo
.getIoFile().getAbsolutePath();
93 if (absolutePath
.length() < rootAbsPath
.length()) {
94 // remove last separator from etalon name
95 if (absolutePath
.equals(rootAbsPath
.substring(0, rootAbsPath
.length() - 1))) {
96 return rootUrlInfo
.getAbsoluteUrlAsUrl();
100 final String relativePath
= absolutePath
.substring(rootAbsPath
.length());
102 return rootUrlInfo
.getAbsoluteUrlAsUrl().appendPath(FileUtil
.toSystemIndependentName(relativePath
), true);
104 catch (SVNException e
) {
111 public String
getLocalPath(final String url
) {
112 synchronized (myMonitor
) {
113 final String rootUrl
= getUrlRootForUrl(url
);
114 if (rootUrl
== null) {
117 final RootUrlInfo parentInfo
= myMapping
.byUrl(rootUrl
);
118 if (parentInfo
== null) {
122 return fileByUrl(parentInfo
.getIoFile().getAbsolutePath(), rootUrl
, url
).getAbsolutePath();
126 public static File
fileByUrl(final String parentPath
, final String parentUrl
, final String childUrl
) {
127 return new File(parentPath
, childUrl
.substring(parentUrl
.length()));
131 public RootUrlInfo
getWcRootForFilePath(final File file
) {
132 synchronized (myMonitor
) {
133 final String root
= getRootForPath(file
);
138 return myMapping
.byFile(root
);
142 public boolean rootsDiffer() {
143 synchronized (myMonitor
) {
144 return myMapping
.isRootsDifferFromSettings();
149 public RootUrlInfo
getWcRootForUrl(final String url
) {
150 synchronized (myMonitor
) {
151 final String rootUrl
= getUrlRootForUrl(url
);
152 if (rootUrl
== null) {
156 final RootUrlInfo result
= myMapping
.byUrl(rootUrl
);
157 if (result
== null) {
158 LOG
.info("Inconsistent maps for url:" + url
+ " found root url: " + rootUrl
);
166 * Returns real working copies roots - if there is <Project Root> -> Subversion setting,
167 * and there is one working copy, will return one root
169 public List
<RootUrlInfo
> getAllWcInfos() {
170 synchronized (myMonitor
) {
171 // a copy is created inside
172 return myMoreRealMapping
.getAllCopies();
176 public List
<VirtualFile
> convertRoots(final List
<VirtualFile
> result
) {
177 if (myHelper
.isInside()) return result
;
179 synchronized (myMonitor
) {
180 final List
<VirtualFile
> cachedRoots
= myMapping
.getUnderVcsRoots();
181 final List
<VirtualFile
> lonelyRoots
= myMapping
.getLonelyRoots();
182 if (! lonelyRoots
.isEmpty()) {
183 myChecker
.reportNoRoots(lonelyRoots
);
185 if (cachedRoots
.isEmpty()) {
193 public void acceptNestedData(final Set
<NestedCopiesBuilder
.MyPointInfo
> set
) {
197 private boolean init() {
198 synchronized (myMonitor
) {
199 final boolean result
= myInitialized
;
200 myInitialized
= true;
205 public void realRefresh(final AtomicSectionsAware atomicSectionsAware
) {
206 final SvnVcs vcs
= SvnVcs
.getInstance(myProject
);
207 final VirtualFile
[] roots
= myHelper
.executeDefended();
209 final CopiesApplier copiesApplier
= new CopiesApplier();
210 final CopiesDetector copiesDetector
= new CopiesDetector(atomicSectionsAware
, vcs
, copiesApplier
, new Getter
<NestedCopiesData
>() {
211 public NestedCopiesData
get() {
212 return myTempSink
.receive();
215 // do not send additional request for nested copies when in init state
216 copiesDetector
.detectCopyRoots(roots
, init());
219 private class CopiesApplier
{
220 public void apply(final SvnVcs vcs
, final AtomicSectionsAware atomicSectionsAware
, final List
<RootUrlInfo
> roots
,
221 final List
<VirtualFile
> lonelyRoots
) {
222 final SvnMapping mapping
= new SvnMapping();
223 mapping
.addAll(roots
);
224 mapping
.reportLonelyRoots(lonelyRoots
);
226 final SvnMapping groupedMapping
= new SvnMapping();
227 final List
<RootUrlInfo
> filtered
= new ArrayList
<RootUrlInfo
>();
228 ForNestedRootChecker
.filterOutSuperfluousChildren(vcs
, roots
, filtered
);
230 groupedMapping
.addAll(filtered
);
234 atomicSectionsAware
.enter();
235 synchronized (myMonitor
) {
236 myMapping
.copyFrom(mapping
);
237 myMoreRealMapping
.copyFrom(groupedMapping
);
240 atomicSectionsAware
.exit();
242 myProject
.getMessageBus().syncPublisher(SvnVcs
.ROOTS_RELOADED
).run();
246 private static class CopiesDetector
{
247 private final AtomicSectionsAware myAtomicSectionsAware
;
248 private final SvnVcs myVcs
;
249 private final CopiesApplier myApplier
;
250 private final List
<VirtualFile
> myLonelyRoots
;
251 private final List
<RootUrlInfo
> myTopRoots
;
252 private final RepositoryRoots myRepositoryRoots
;
253 private final Getter
<NestedCopiesData
> myGate
;
255 private CopiesDetector(final AtomicSectionsAware atomicSectionsAware
, final SvnVcs vcs
, final CopiesApplier applier
,
256 final Getter
<NestedCopiesData
> gate
) {
257 myAtomicSectionsAware
= atomicSectionsAware
;
261 myTopRoots
= new ArrayList
<RootUrlInfo
>();
262 myLonelyRoots
= new ArrayList
<VirtualFile
>();
263 myRepositoryRoots
= new RepositoryRoots(myVcs
);
266 public void detectCopyRoots(final VirtualFile
[] roots
, final boolean clearState
) {
267 final Getter
<Boolean
> cancelGetter
= new Getter
<Boolean
>() {
268 public Boolean
get() {
269 return myAtomicSectionsAware
.shouldExitAsap();
273 for (final VirtualFile vcsRoot
: roots
) {
274 final List
<Real
> foundRoots
= ForNestedRootChecker
.getAllNestedWorkingCopies(vcsRoot
, myVcs
, false, cancelGetter
);
275 if (foundRoots
.isEmpty()) {
276 myLonelyRoots
.add(vcsRoot
);
278 // filter out bad(?) items
279 for (Real foundRoot
: foundRoots
) {
280 final SVNURL repoRoot
= foundRoot
.getInfo().getRepositoryRootURL();
281 if (repoRoot
== null) {
282 LOG
.info("Error: cannot find repository URL for versioned folder: " + foundRoot
.getFile().getPath());
284 myRepositoryRoots
.register(repoRoot
);
285 myTopRoots
.add(new RootUrlInfo(repoRoot
, foundRoot
.getInfo().getURL(),
286 SvnFormatSelector
.getWorkingCopyFormat(foundRoot
.getInfo().getFile()), foundRoot
.getFile(), vcsRoot
));
291 if (! SvnConfiguration
.getInstance(myVcs
.getProject()).DETECT_NESTED_COPIES
) {
292 myApplier
.apply(myVcs
, myAtomicSectionsAware
, myTopRoots
, myLonelyRoots
);
294 addNestedRoots(clearState
);
298 private void addNestedRoots(final boolean clearState
) {
299 final List
<VirtualFile
> basicVfRoots
= ObjectsConvertor
.convert(myTopRoots
, new Convertor
<RootUrlInfo
, VirtualFile
>() {
300 public VirtualFile
convert(final RootUrlInfo real
) {
301 return real
.getVirtualFile();
305 final ChangeListManager clManager
= ChangeListManager
.getInstance(myVcs
.getProject());
308 // clear what was reported before (could be for currently-not-existing roots)
311 clManager
.invokeAfterUpdate(new Runnable() {
313 final List
<RootUrlInfo
> nestedRoots
= new ArrayList
<RootUrlInfo
>();
315 for (NestedCopiesBuilder
.MyPointInfo info
: myGate
.get().getSet()) {
316 if (NestedCopyType
.external
.equals(info
.getType())) {
318 final SVNStatus svnStatus
= SvnUtil
.getStatus(myVcs
, new File(info
.getFile().getPath()));
319 if (svnStatus
.getURL() == null) continue;
320 info
.setUrl(svnStatus
.getURL());
321 info
.setFormat(WorkingCopyFormat
.getInstance(svnStatus
.getWorkingCopyFormat()));
323 catch (Exception e
) {
327 for (RootUrlInfo topRoot
: myTopRoots
) {
328 if (VfsUtil
.isAncestor(topRoot
.getVirtualFile(), info
.getFile(), true)) {
329 final SVNURL repoRoot
= myRepositoryRoots
.ask(info
.getUrl());
330 if (repoRoot
!= null) {
331 nestedRoots
.add(new RootUrlInfo(repoRoot
, info
.getUrl(), info
.getFormat(), info
.getFile(), topRoot
.getRoot()));
338 myTopRoots
.addAll(nestedRoots
);
339 myApplier
.apply(myVcs
, myAtomicSectionsAware
, myTopRoots
, myLonelyRoots
);
341 }, InvokeAfterUpdateMode
.SILENT_CALLBACK_POOLED
, null, new Consumer
<VcsDirtyScopeManager
>() {
342 public void consume(VcsDirtyScopeManager vcsDirtyScopeManager
) {
344 vcsDirtyScopeManager
.filesDirty(null, basicVfRoots
);
351 private static SVNStatus
getExternalItemStatus(final SvnVcs vcs
, final File file
) {
352 final SVNStatusClient statusClient
= vcs
.createStatusClient();
354 if (file
.isDirectory()) {
355 return statusClient
.doStatus(file
, false);
357 final File parent
= file
.getParentFile();
358 if (parent
!= null) {
359 statusClient
.setFilesProvider(new ISVNStatusFileProvider() {
360 public Map
getChildrenFiles(File parent
) {
361 return Collections
.singletonMap(file
.getAbsolutePath(), file
);
364 final Ref
<SVNStatus
> refStatus
= new Ref
<SVNStatus
>();
365 statusClient
.doStatus(parent
, SVNRevision
.WORKING
, SVNDepth
.FILES
, false, true, false, false, new ISVNStatusHandler() {
366 public void handleStatus(final SVNStatus status
) throws SVNException
{
367 if (file
.equals(status
.getFile())) {
368 refStatus
.set(status
);
372 return refStatus
.get();
376 catch (SVNException e
) {
382 private static class RepositoryRoots
{
383 private final SvnVcs myVcs
;
384 private final Set
<SVNURL
> myRoots
;
386 private RepositoryRoots(final SvnVcs vcs
) {
388 myRoots
= new HashSet
<SVNURL
>();
391 public void register(final SVNURL url
) {
395 public SVNURL
ask(final SVNURL url
) {
396 for (SVNURL root
: myRoots
) {
397 if (root
.equals(SVNURLUtil
.getCommonURLAncestor(root
, url
))) {
401 final SVNURL newRoot
= SvnUtil
.getRepositoryRoot(myVcs
, url
);
402 if (newRoot
!= null) {
403 myRoots
.add(newRoot
);
410 public String
getUrlRootForUrl(final String currentUrl
) {
411 for (String url
: myMapping
.getUrls()) {
412 if (SVNPathUtil
.isAncestor(url
, currentUrl
)) {
420 public String
getRootForPath(final File currentPath
) {
421 String convertedPath
= currentPath
.getAbsolutePath();
422 convertedPath
= (currentPath
.isDirectory() && (! convertedPath
.endsWith(File
.separator
))) ? convertedPath
+ File
.separator
:
424 final List
<String
> paths
;
425 synchronized (myMonitor
) {
426 paths
= new ArrayList
<String
>(myMapping
.getFileRoots());
428 Collections
.sort(paths
, StringLenComparator
.getDescendingInstance());
430 for (String path
: paths
) {
431 if (FileUtil
.startsWith(convertedPath
, path
)) {
438 public VirtualFile
[] getNotFilteredRoots() {
439 return myHelper
.executeDefended();
442 public boolean isEmpty() {
443 synchronized (myMonitor
) {
444 return myMapping
.isEmpty();
448 public SvnMappingSavedPart
getState() {
449 final SvnMappingSavedPart result
= new SvnMappingSavedPart();
451 final SvnMapping mapping
= new SvnMapping();
452 final SvnMapping realMapping
= new SvnMapping();
453 synchronized (myMonitor
) {
454 mapping
.copyFrom(myMapping
);
455 realMapping
.copyFrom(myMoreRealMapping
);
458 for (RootUrlInfo info
: mapping
.getAllCopies()) {
459 result
.add(convert(info
));
461 for (RootUrlInfo info
: realMapping
.getAllCopies()) {
462 result
.addReal(convert(info
));
467 private SvnCopyRootSimple
convert(final RootUrlInfo info
) {
468 final SvnCopyRootSimple copy
= new SvnCopyRootSimple();
469 copy
.myVcsRoot
= FileUtil
.toSystemDependentName(info
.getRoot().getPath());
470 copy
.myCopyRoot
= info
.getIoFile().getAbsolutePath();
474 public void loadState(final SvnMappingSavedPart state
) {
475 final SvnMapping mapping
= new SvnMapping();
476 final SvnMapping realMapping
= new SvnMapping();
479 fillMapping(mapping
, state
.getMappingRoots());
480 fillMapping(realMapping
, state
.getMoreRealMappingRoots());
481 } catch (ProcessCanceledException e
) {
483 } catch (Throwable t
) {
488 synchronized (myMonitor
) {
489 myMapping
.copyFrom(mapping
);
490 myMoreRealMapping
.copyFrom(realMapping
);
494 private void fillMapping(final SvnMapping mapping
, final List
<SvnCopyRootSimple
> list
) {
495 final LocalFileSystem lfs
= LocalFileSystem
.getInstance();
497 for (SvnCopyRootSimple simple
: list
) {
498 final VirtualFile copyRoot
= lfs
.findFileByIoFile(new File(simple
.myCopyRoot
));
499 final VirtualFile vcsRoot
= lfs
.findFileByIoFile(new File(simple
.myVcsRoot
));
501 if (copyRoot
== null || vcsRoot
== null) continue;
503 final SvnVcs vcs
= SvnVcs
.getInstance(myProject
);
504 final SVNInfo svnInfo
= vcs
.getInfo(copyRoot
);
505 if ((svnInfo
== null) || (svnInfo
.getRepositoryRootURL() == null)) continue;
507 mapping
.add(new RootUrlInfo(svnInfo
.getRepositoryRootURL(), svnInfo
.getURL(),
508 SvnFormatSelector
.getWorkingCopyFormat(svnInfo
.getFile()), copyRoot
, vcsRoot
));
512 public void projectOpened() {
515 public void projectClosed() {
519 public String
getComponentName() {
520 return "SvnFileUrlMappingImpl";
523 public void initComponent() {
526 public void disposeComponent() {