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
.*;
53 name
= "SvnFileUrlMappingImpl",
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
) {
92 myMapping
= new SvnMapping();
93 myMoreRealMapping
= new SvnMapping();
94 myHelper
= new MyRootsHelper(vcsManager
);
95 myChecker
= new SvnCompatibilityChecker(project
);
96 myTempSink
= new NestedCopiesSink();
100 public SVNURL
getUrlForFile(final File file
) {
101 final RootUrlInfo rootUrlInfo
= getWcRootForFilePath(file
);
102 if (rootUrlInfo
== 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();
115 final String relativePath
= absolutePath
.substring(rootAbsPath
.length());
117 return rootUrlInfo
.getAbsoluteUrlAsUrl().appendPath(FileUtil
.toSystemIndependentName(relativePath
), true);
119 catch (SVNException e
) {
126 public String
getLocalPath(final String url
) {
127 synchronized (myMonitor
) {
128 final String rootUrl
= getUrlRootForUrl(url
);
129 if (rootUrl
== null) {
132 final RootUrlInfo parentInfo
= myMapping
.byUrl(rootUrl
);
133 if (parentInfo
== 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()));
146 public RootUrlInfo
getWcRootForFilePath(final File file
) {
147 synchronized (myMonitor
) {
148 final String root
= getRootForPath(file
);
153 return myMapping
.byFile(root
);
157 public boolean rootsDiffer() {
158 synchronized (myMonitor
) {
159 return myMapping
.isRootsDifferFromSettings();
164 public RootUrlInfo
getWcRootForUrl(final String url
) {
165 synchronized (myMonitor
) {
166 final String rootUrl
= getUrlRootForUrl(url
);
167 if (rootUrl
== null) {
171 final RootUrlInfo result
= myMapping
.byUrl(rootUrl
);
172 if (result
== null) {
173 LOG
.info("Inconsistent maps for url:" + url
+ " found root url: " + rootUrl
);
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()) {
208 public void acceptNestedData(final Set
<NestedCopiesBuilder
.MyPointInfo
> set
) {
212 private boolean init() {
213 synchronized (myMonitor
) {
214 final boolean result
= myInitialized
;
215 myInitialized
= true;
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
);
249 atomicSectionsAware
.enter();
250 synchronized (myMonitor
) {
251 myMapping
.copyFrom(mapping
);
252 myMoreRealMapping
.copyFrom(groupedMapping
);
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
;
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());
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
);
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());
323 // clear what was reported before (could be for currently-not-existing roots)
326 clManager
.invokeAfterUpdate(new Runnable() {
328 final List
<RootUrlInfo
> nestedRoots
= new ArrayList
<RootUrlInfo
>();
330 final NestedCopiesData data
= myGate
.get();
331 for (NestedCopiesBuilder
.MyPointInfo info
: data
.getSet()) {
332 if (NestedCopyType
.external
.equals(info
.getType()) || NestedCopyType
.switched
.equals(info
.getType())) {
333 final File infoFile
= new File(info
.getFile().getPath());
334 boolean copyFound
= false;
335 for (RootUrlInfo topRoot
: myTopRoots
) {
336 if (topRoot
.getIoFile().equals(infoFile
)) {
337 topRoot
.setType(info
.getType());
346 final SVNStatus svnStatus
= SvnUtil
.getStatus(myVcs
, infoFile
);
347 if (svnStatus
.getURL() == null) continue;
348 info
.setUrl(svnStatus
.getURL());
349 info
.setFormat(WorkingCopyFormat
.getInstance(svnStatus
.getWorkingCopyFormat()));
351 catch (Exception e
) {
355 for (RootUrlInfo topRoot
: myTopRoots
) {
356 if (VfsUtil
.isAncestor(topRoot
.getVirtualFile(), info
.getFile(), true)) {
357 final SVNURL repoRoot
= myRepositoryRoots
.ask(info
.getUrl());
358 if (repoRoot
!= null) {
359 final RootUrlInfo rootInfo
= new RootUrlInfo(repoRoot
, info
.getUrl(), info
.getFormat(), info
.getFile(), topRoot
.getRoot());
360 rootInfo
.setType(info
.getType());
361 nestedRoots
.add(rootInfo
);
367 // check those top roots which ARE externals, but that was not detected due to they itself were the status request target
368 //new SvnNestedTypeRechecker(myVcs.getProject(), myTopRoots).run();
370 myTopRoots
.addAll(nestedRoots
);
371 myApplier
.apply(myVcs
, myAtomicSectionsAware
, myTopRoots
, myLonelyRoots
);
373 }, InvokeAfterUpdateMode
.SILENT_CALLBACK_POOLED
, null, new Consumer
<VcsDirtyScopeManager
>() {
374 public void consume(VcsDirtyScopeManager vcsDirtyScopeManager
) {
376 vcsDirtyScopeManager
.filesDirty(null, basicVfRoots
);
383 private static SVNStatus
getExternalItemStatus(final SvnVcs vcs
, final File file
) {
384 final SVNStatusClient statusClient
= vcs
.createStatusClient();
386 if (file
.isDirectory()) {
387 return statusClient
.doStatus(file
, false);
389 final File parent
= file
.getParentFile();
390 if (parent
!= null) {
391 statusClient
.setFilesProvider(new ISVNStatusFileProvider() {
392 public Map
getChildrenFiles(File parent
) {
393 return Collections
.singletonMap(file
.getAbsolutePath(), file
);
396 final Ref
<SVNStatus
> refStatus
= new Ref
<SVNStatus
>();
397 statusClient
.doStatus(parent
, SVNRevision
.WORKING
, SVNDepth
.FILES
, false, true, false, false, new ISVNStatusHandler() {
398 public void handleStatus(final SVNStatus status
) throws SVNException
{
399 if (file
.equals(status
.getFile())) {
400 refStatus
.set(status
);
404 return refStatus
.get();
408 catch (SVNException e
) {
414 private static class RepositoryRoots
{
415 private final SvnVcs myVcs
;
416 private final Set
<SVNURL
> myRoots
;
418 private RepositoryRoots(final SvnVcs vcs
) {
420 myRoots
= new HashSet
<SVNURL
>();
423 public void register(final SVNURL url
) {
427 public SVNURL
ask(final SVNURL url
) {
428 for (SVNURL root
: myRoots
) {
429 if (root
.equals(SVNURLUtil
.getCommonURLAncestor(root
, url
))) {
433 final SVNURL newRoot
= SvnUtil
.getRepositoryRoot(myVcs
, url
);
434 if (newRoot
!= null) {
435 myRoots
.add(newRoot
);
442 public String
getUrlRootForUrl(final String currentUrl
) {
443 for (String url
: myMapping
.getUrls()) {
444 if (SVNPathUtil
.isAncestor(url
, currentUrl
)) {
452 public String
getRootForPath(final File currentPath
) {
453 String convertedPath
= currentPath
.getAbsolutePath();
454 convertedPath
= (currentPath
.isDirectory() && (! convertedPath
.endsWith(File
.separator
))) ? convertedPath
+ File
.separator
:
456 final List
<String
> paths
;
457 synchronized (myMonitor
) {
458 paths
= new ArrayList
<String
>(myMapping
.getFileRoots());
460 Collections
.sort(paths
, StringLenComparator
.getDescendingInstance());
462 for (String path
: paths
) {
463 if (FileUtil
.startsWith(convertedPath
, path
)) {
470 public VirtualFile
[] getNotFilteredRoots() {
471 return myHelper
.executeDefended();
474 public boolean isEmpty() {
475 synchronized (myMonitor
) {
476 return myMapping
.isEmpty();
480 public SvnMappingSavedPart
getState() {
481 final SvnMappingSavedPart result
= new SvnMappingSavedPart();
483 final SvnMapping mapping
= new SvnMapping();
484 final SvnMapping realMapping
= new SvnMapping();
485 synchronized (myMonitor
) {
486 mapping
.copyFrom(myMapping
);
487 realMapping
.copyFrom(myMoreRealMapping
);
490 for (RootUrlInfo info
: mapping
.getAllCopies()) {
491 result
.add(convert(info
));
493 for (RootUrlInfo info
: realMapping
.getAllCopies()) {
494 result
.addReal(convert(info
));
499 private SvnCopyRootSimple
convert(final RootUrlInfo info
) {
500 final SvnCopyRootSimple copy
= new SvnCopyRootSimple();
501 copy
.myVcsRoot
= FileUtil
.toSystemDependentName(info
.getRoot().getPath());
502 copy
.myCopyRoot
= info
.getIoFile().getAbsolutePath();
506 public void loadState(final SvnMappingSavedPart state
) {
507 final SvnMapping mapping
= new SvnMapping();
508 final SvnMapping realMapping
= new SvnMapping();
511 fillMapping(mapping
, state
.getMappingRoots());
512 fillMapping(realMapping
, state
.getMoreRealMappingRoots());
513 } catch (ProcessCanceledException e
) {
515 } catch (Throwable t
) {
520 synchronized (myMonitor
) {
521 myMapping
.copyFrom(mapping
);
522 myMoreRealMapping
.copyFrom(realMapping
);
526 private void fillMapping(final SvnMapping mapping
, final List
<SvnCopyRootSimple
> list
) {
527 final LocalFileSystem lfs
= LocalFileSystem
.getInstance();
529 for (SvnCopyRootSimple simple
: list
) {
530 final VirtualFile copyRoot
= lfs
.findFileByIoFile(new File(simple
.myCopyRoot
));
531 final VirtualFile vcsRoot
= lfs
.findFileByIoFile(new File(simple
.myVcsRoot
));
533 if (copyRoot
== null || vcsRoot
== null) continue;
535 final SvnVcs vcs
= SvnVcs
.getInstance(myProject
);
536 final SVNInfo svnInfo
= vcs
.getInfo(copyRoot
);
537 if ((svnInfo
== null) || (svnInfo
.getRepositoryRootURL() == null)) continue;
539 mapping
.add(new RootUrlInfo(svnInfo
.getRepositoryRootURL(), svnInfo
.getURL(),
540 SvnFormatSelector
.getWorkingCopyFormat(svnInfo
.getFile()), copyRoot
, vcsRoot
));
544 public void projectOpened() {
547 public void projectClosed() {
551 public String
getComponentName() {
552 return "SvnFileUrlMappingImpl";
555 public void initComponent() {
558 public void disposeComponent() {