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.
18 package org
.jetbrains
.idea
.svn
;
20 import com
.intellij
.ide
.FrameStateListener
;
21 import com
.intellij
.ide
.FrameStateManager
;
22 import com
.intellij
.notification
.*;
23 import com
.intellij
.openapi
.actionSystem
.AnAction
;
24 import com
.intellij
.openapi
.application
.Application
;
25 import com
.intellij
.openapi
.application
.ApplicationManager
;
26 import com
.intellij
.openapi
.diagnostic
.Logger
;
27 import com
.intellij
.openapi
.options
.Configurable
;
28 import com
.intellij
.openapi
.progress
.ProcessCanceledException
;
29 import com
.intellij
.openapi
.progress
.ProgressManager
;
30 import com
.intellij
.openapi
.project
.DumbAwareRunnable
;
31 import com
.intellij
.openapi
.project
.Project
;
32 import com
.intellij
.openapi
.startup
.StartupManager
;
33 import com
.intellij
.openapi
.ui
.DialogWrapper
;
34 import com
.intellij
.openapi
.ui
.Messages
;
35 import com
.intellij
.openapi
.util
.Pair
;
36 import com
.intellij
.openapi
.util
.SystemInfo
;
37 import com
.intellij
.openapi
.util
.Trinity
;
38 import com
.intellij
.openapi
.vcs
.*;
39 import com
.intellij
.openapi
.vcs
.annotate
.AnnotationProvider
;
40 import com
.intellij
.openapi
.vcs
.changes
.*;
41 import com
.intellij
.openapi
.vcs
.checkin
.CheckinEnvironment
;
42 import com
.intellij
.openapi
.vcs
.diff
.DiffProvider
;
43 import com
.intellij
.openapi
.vcs
.history
.VcsHistoryProvider
;
44 import com
.intellij
.openapi
.vcs
.history
.VcsRevisionNumber
;
45 import com
.intellij
.openapi
.vcs
.merge
.MergeProvider
;
46 import com
.intellij
.openapi
.vcs
.rollback
.RollbackEnvironment
;
47 import com
.intellij
.openapi
.vcs
.update
.UpdateEnvironment
;
48 import com
.intellij
.openapi
.vcs
.versionBrowser
.ChangeBrowserSettings
;
49 import com
.intellij
.openapi
.vfs
.VirtualFile
;
50 import com
.intellij
.openapi
.vfs
.VirtualFileManager
;
51 import com
.intellij
.util
.containers
.Convertor
;
52 import com
.intellij
.util
.containers
.SoftHashMap
;
53 import com
.intellij
.util
.messages
.MessageBus
;
54 import com
.intellij
.util
.messages
.MessageBusConnection
;
55 import com
.intellij
.util
.messages
.Topic
;
56 import org
.jetbrains
.annotations
.NonNls
;
57 import org
.jetbrains
.annotations
.NotNull
;
58 import org
.jetbrains
.annotations
.Nullable
;
59 import org
.jetbrains
.idea
.svn
.actions
.ShowPropertiesDiffWithLocalAction
;
60 import org
.jetbrains
.idea
.svn
.actions
.SvnMergeProvider
;
61 import org
.jetbrains
.idea
.svn
.annotate
.SvnAnnotationProvider
;
62 import org
.jetbrains
.idea
.svn
.checkin
.SvnCheckinEnvironment
;
63 import org
.jetbrains
.idea
.svn
.dialogs
.SvnFormatWorker
;
64 import org
.jetbrains
.idea
.svn
.dialogs
.WCInfo
;
65 import org
.jetbrains
.idea
.svn
.history
.LoadedRevisionsCache
;
66 import org
.jetbrains
.idea
.svn
.history
.SvnChangeList
;
67 import org
.jetbrains
.idea
.svn
.history
.SvnCommittedChangesProvider
;
68 import org
.jetbrains
.idea
.svn
.history
.SvnHistoryProvider
;
69 import org
.jetbrains
.idea
.svn
.rollback
.SvnRollbackEnvironment
;
70 import org
.jetbrains
.idea
.svn
.update
.SvnIntegrateEnvironment
;
71 import org
.jetbrains
.idea
.svn
.update
.SvnUpdateEnvironment
;
72 import org
.tmatesoft
.svn
.core
.*;
73 import org
.tmatesoft
.svn
.core
.auth
.ISVNAuthenticationManager
;
74 import org
.tmatesoft
.svn
.core
.internal
.io
.dav
.DAVRepositoryFactory
;
75 import org
.tmatesoft
.svn
.core
.internal
.io
.fs
.FSRepositoryFactory
;
76 import org
.tmatesoft
.svn
.core
.internal
.io
.svn
.SVNRepositoryFactoryImpl
;
77 import org
.tmatesoft
.svn
.core
.internal
.util
.jna
.SVNJNAUtil
;
78 import org
.tmatesoft
.svn
.core
.internal
.wc
.SVNAdminUtil
;
79 import org
.tmatesoft
.svn
.core
.internal
.wc
.admin
.SVNAdminArea14
;
80 import org
.tmatesoft
.svn
.core
.internal
.wc
.admin
.SVNAdminAreaFactory
;
81 import org
.tmatesoft
.svn
.core
.internal
.wc
.admin
.SVNWCAccess
;
82 import org
.tmatesoft
.svn
.core
.io
.SVNRepository
;
83 import org
.tmatesoft
.svn
.core
.io
.SVNRepositoryFactory
;
84 import org
.tmatesoft
.svn
.core
.wc
.*;
85 import org
.tmatesoft
.svn
.util
.SVNDebugLog
;
86 import org
.tmatesoft
.svn
.util
.SVNDebugLogAdapter
;
87 import org
.tmatesoft
.svn
.util
.SVNLogType
;
89 import javax
.swing
.event
.HyperlinkEvent
;
91 import java
.io
.UnsupportedEncodingException
;
93 import java
.util
.logging
.Level
;
95 @SuppressWarnings({"IOResourceOpenedButNotSafelyClosed"})
96 public class SvnVcs
extends AbstractVcs
{
97 private final static Logger REFRESH_LOG
= Logger
.getInstance("#svn_refresh");
99 private static final Logger LOG
= Logger
.getInstance("org.jetbrains.idea.svn.SvnVcs");
100 @NonNls public static final String VCS_NAME
= "svn";
101 private static final VcsKey ourKey
= createKey(VCS_NAME
);
102 private final Map
<String
, Map
<String
, Pair
<SVNPropertyValue
, Trinity
<Long
, Long
, Long
>>>> myPropertyCache
= new SoftHashMap
<String
, Map
<String
, Pair
<SVNPropertyValue
, Trinity
<Long
, Long
, Long
>>>>();
104 private final SvnConfiguration myConfiguration
;
105 private final SvnEntriesFileListener myEntriesFileListener
;
107 private CheckinEnvironment myCheckinEnvironment
;
108 private RollbackEnvironment myRollbackEnvironment
;
109 private UpdateEnvironment mySvnUpdateEnvironment
;
110 private UpdateEnvironment mySvnIntegrateEnvironment
;
111 private VcsHistoryProvider mySvnHistoryProvider
;
112 private AnnotationProvider myAnnotationProvider
;
113 private DiffProvider mySvnDiffProvider
;
114 private final VcsShowConfirmationOption myAddConfirmation
;
115 private final VcsShowConfirmationOption myDeleteConfirmation
;
116 private EditFileProvider myEditFilesProvider
;
117 private SvnCommittedChangesProvider myCommittedChangesProvider
;
118 private final VcsShowSettingOption myCheckoutOptions
;
120 private ChangeProvider myChangeProvider
;
121 private MergeProvider myMergeProvider
;
123 @NonNls public static final String LOG_PARAMETER_NAME
= "javasvn.log";
124 public static final String pathToEntries
= SvnUtil
.SVN_ADMIN_DIR_NAME
+ File
.separatorChar
+ SvnUtil
.ENTRIES_FILE_NAME
;
125 public static final String pathToDirProps
= SvnUtil
.SVN_ADMIN_DIR_NAME
+ File
.separatorChar
+ SvnUtil
.DIR_PROPS_FILE_NAME
;
126 private final SvnChangelistListener myChangeListListener
;
128 private SvnCopiesRefreshManager myCopiesRefreshManager
;
129 private SvnFileUrlMappingImpl myMapping
;
130 private final MyFrameStateListener myFrameStateListener
;
132 public static final Topic
<Runnable
> ROOTS_RELOADED
= new Topic
<Runnable
>("ROOTS_RELOADED", Runnable
.class);
133 private VcsListener myVcsListener
;
135 private final RootsToWorkingCopies myRootsToWorkingCopies
;
136 private final SvnAuthenticationNotifier myAuthNotifier
;
139 SvnHttpAuthMethodsDefaultChecker
.check();
141 //noinspection UseOfArchaicSystemPropertyAccessors
142 final JavaSVNDebugLogger logger
= new JavaSVNDebugLogger(Boolean
.getBoolean(LOG_PARAMETER_NAME
), LOG
);
143 SVNDebugLog
.setDefaultLog(logger
);
144 SVNAdminAreaFactory
.setSelector(new SvnFormatSelector());
146 DAVRepositoryFactory
.setup();
147 SVNRepositoryFactoryImpl
.setup();
148 FSRepositoryFactory
.setup();
150 // non-optimized writing is fast enough on Linux/MacOS, and somewhat more reliable
151 if (SystemInfo
.isWindows
) {
152 SVNAdminArea14
.setOptimizedWritingEnabled(true);
155 if (! SVNJNAUtil
.isJNAPresent()) {
156 LOG
.warn("JNA is not found by svnkit library");
160 private static Boolean
booleanProperty(final String systemParameterName
) {
161 return Boolean
.valueOf(System
.getProperty(systemParameterName
));
164 public SvnVcs(final Project project
, MessageBus bus
, SvnConfiguration svnConfiguration
, final ChangeListManager changeListManager
,
165 final VcsDirtyScopeManager vcsDirtyScopeManager
) {
166 super(project
, VCS_NAME
);
168 myRootsToWorkingCopies
= new RootsToWorkingCopies(myProject
);
169 myAuthNotifier
= new SvnAuthenticationNotifier(this);
170 myConfiguration
= svnConfiguration
;
172 dumpFileStatus(FileStatus
.ADDED
);
173 dumpFileStatus(FileStatus
.DELETED
);
174 dumpFileStatus(FileStatus
.MERGE
);
175 dumpFileStatus(FileStatus
.MODIFIED
);
176 dumpFileStatus(FileStatus
.NOT_CHANGED
);
177 dumpFileStatus(FileStatus
.UNKNOWN
);
179 dumpFileStatus(SvnFileStatus
.REPLACED
);
180 dumpFileStatus(SvnFileStatus
.EXTERNAL
);
181 dumpFileStatus(SvnFileStatus
.OBSTRUCTED
);
183 myEntriesFileListener
= new SvnEntriesFileListener(project
);
185 final ProjectLevelVcsManager vcsManager
= ProjectLevelVcsManager
.getInstance(project
);
186 myAddConfirmation
= vcsManager
.getStandardConfirmation(VcsConfiguration
.StandardConfirmation
.ADD
, this);
187 myDeleteConfirmation
= vcsManager
.getStandardConfirmation(VcsConfiguration
.StandardConfirmation
.REMOVE
, this);
188 myCheckoutOptions
= vcsManager
.getStandardOption(VcsConfiguration
.StandardOption
.CHECKOUT
, this);
190 if (myProject
.isDefault()) {
191 myChangeListListener
= null;
194 upgradeIfNeeded(bus
);
196 myChangeListListener
= new SvnChangelistListener(myProject
, createChangelistClient());
198 myVcsListener
= new VcsListener() {
199 public void directoryMappingChanged() {
200 invokeRefreshSvnRoots(true);
205 myFrameStateListener
= new MyFrameStateListener(changeListManager
, vcsDirtyScopeManager
);
208 public void postStartup() {
209 if (myProject
.isDefault()) return;
210 myCopiesRefreshManager
= new SvnCopiesRefreshManager(myProject
, (SvnFileUrlMappingImpl
) getSvnFileUrlMapping());
212 invokeRefreshSvnRoots(true);
215 public void invokeRefreshSvnRoots(final boolean asynchronous
) {
216 REFRESH_LOG
.debug("refresh: ", new Throwable());
217 if (myCopiesRefreshManager
!= null) {
219 myCopiesRefreshManager
.getCopiesRefresh().asynchRequest();
221 ProgressManager
.getInstance().runProcessWithProgressSynchronously(new Runnable() {
223 myCopiesRefreshManager
.getCopiesRefresh().synchRequest();
225 }, SvnBundle
.message("refreshing.working.copies.roots.progress.text"), true, myProject
);
231 public boolean checkImmediateParentsBeforeCommit() {
235 private void upgradeIfNeeded(final MessageBus bus
) {
236 final MessageBusConnection connection
= bus
.connect();
237 connection
.subscribe(ChangeListManagerImpl
.LISTS_LOADED
, new LocalChangeListsLoadedListener() {
238 public void processLoadedLists(final List
<LocalChangeList
> lists
) {
239 SvnConfiguration
.SvnSupportOptions supportOptions
= null;
241 ChangeListManager
.getInstanceChecked(myProject
).setReadOnly(SvnChangeProvider
.ourDefaultListName
, true);
243 supportOptions
= myConfiguration
.getSupportOptions();
245 upgradeToRecentVersion(supportOptions
);
246 if (! supportOptions
.changeListsSynchronized()) {
247 processChangeLists(lists
);
250 catch (ProcessCanceledException e
) {
253 if (supportOptions
!= null) {
254 supportOptions
.upgrade();
258 connection
.disconnect();
263 public void processChangeLists(final List
<LocalChangeList
> lists
) {
264 final ProjectLevelVcsManager plVcsManager
= ProjectLevelVcsManager
.getInstanceChecked(myProject
);
265 plVcsManager
.startBackgroundVcsOperation();
267 final SVNChangelistClient client
= createChangelistClient();
268 for (LocalChangeList list
: lists
) {
269 if (! list
.isDefault()) {
270 final Collection
<Change
> changes
= list
.getChanges();
271 for (Change change
: changes
) {
272 correctListForRevision(plVcsManager
, change
.getBeforeRevision(), client
, list
.getName());
273 correctListForRevision(plVcsManager
, change
.getAfterRevision(), client
, list
.getName());
279 final Application appManager
= ApplicationManager
.getApplication();
280 if (appManager
.isDispatchThread()) {
281 appManager
.executeOnPooledThread(new Runnable() {
283 plVcsManager
.stopBackgroundVcsOperation();
287 plVcsManager
.stopBackgroundVcsOperation();
292 private void correctListForRevision(final ProjectLevelVcsManager plVcsManager
, final ContentRevision revision
,
293 final SVNChangelistClient client
, final String name
) {
294 if (revision
!= null) {
295 final FilePath path
= revision
.getFile();
296 final AbstractVcs vcs
= plVcsManager
.getVcsFor(path
);
297 if ((vcs
!= null) && VCS_NAME
.equals(vcs
.getName())) {
299 client
.doAddToChangelist(new File
[] {path
.getIOFile()}, SVNDepth
.EMPTY
, name
, null);
301 catch (SVNException e
) {
302 // left in default list
308 private final static String UPGRADE_SUBVERSION_FORMAT
= "Subversion";
310 private void upgradeToRecentVersion(final SvnConfiguration
.SvnSupportOptions supportOptions
) {
311 if (! supportOptions
.upgradeTo16Asked()) {
312 final SvnWorkingCopyChecker workingCopyChecker
= new SvnWorkingCopyChecker();
314 if (workingCopyChecker
.upgradeNeeded()) {
316 Notifications
.Bus
.notify(new Notification(UPGRADE_SUBVERSION_FORMAT
, SvnBundle
.message("upgrade.format.to16.question.title"),
317 "Old format Subversion working copies <a href=\"\">could be upgraded to version 1.6</a>.",
318 NotificationType
.INFORMATION
, new NotificationListener() {
319 public void hyperlinkUpdate(@NotNull Notification notification
, @NotNull HyperlinkEvent event
) {
320 final int upgradeAnswer
= Messages
.showYesNoDialog(SvnBundle
.message("upgrade.format.to16.question.text",
321 SvnBundle
.message("label.where.svn.format.can.be.changed.text", SvnBundle
.message("action.show.svn.map.text"))),
322 SvnBundle
.message("upgrade.format.to16.question.title"), Messages
.getWarningIcon());
323 if (DialogWrapper
.OK_EXIT_CODE
== upgradeAnswer
) {
324 workingCopyChecker
.doUpgrade();
327 notification
.expire();
335 public void activate() {
336 final ProjectLevelVcsManager vcsManager
= ProjectLevelVcsManager
.getInstance(myProject
);
337 if (! myProject
.isDefault()) {
338 ChangeListManager
.getInstance(myProject
).addChangeListListener(myChangeListListener
);
339 vcsManager
.addVcsListener(myVcsListener
);
342 SvnApplicationSettings
.getInstance().svnActivated();
343 VirtualFileManager
.getInstance().addVirtualFileListener(myEntriesFileListener
);
344 // this will initialize its inner listener for committed changes upload
345 LoadedRevisionsCache
.getInstance(myProject
);
346 FrameStateManager
.getInstance().addListener(myFrameStateListener
);
348 // do one time after project loaded
349 StartupManager
.getInstance(myProject
).runWhenProjectIsInitialized(new DumbAwareRunnable() {
353 // for IDEA, it takes 2 minutes - and anyway this can be done in background, no sence...
354 // once it could be mistaken about copies for 2 minutes on start...
356 /*if (! myMapping.getAllWcInfos().isEmpty()) {
357 invokeRefreshSvnRoots();
360 ProgressManager.getInstance().runProcessWithProgressSynchronously(new Runnable() {
362 myCopiesRefreshManager.getCopiesRefresh().ensureInit();
364 }, SvnBundle.message("refreshing.working.copies.roots.progress.text"), true, myProject);*/
368 vcsManager
.addVcsListener(myRootsToWorkingCopies
);
371 public RootsToWorkingCopies
getRootsToWorkingCopies() {
372 return myRootsToWorkingCopies
;
375 public SvnAuthenticationNotifier
getAuthNotifier() {
376 return myAuthNotifier
;
380 public void deactivate() {
381 FrameStateManager
.getInstance().removeListener(myFrameStateListener
);
383 final ProjectLevelVcsManager vcsManager
= ProjectLevelVcsManager
.getInstance(myProject
);
384 if (myVcsListener
!= null) {
385 vcsManager
.removeVcsListener(myVcsListener
);
388 VirtualFileManager
.getInstance().removeVirtualFileListener(myEntriesFileListener
);
389 SvnApplicationSettings
.getInstance().svnDeactivated();
390 new DefaultSVNRepositoryPool(null, null).shutdownConnections(true);
391 if (myCommittedChangesProvider
!= null) {
392 myCommittedChangesProvider
.deactivate();
394 if (myChangeListListener
!= null && (! myProject
.isDefault())) {
395 ChangeListManager
.getInstance(myProject
).removeChangeListListener(myChangeListListener
);
397 vcsManager
.removeVcsListener(myRootsToWorkingCopies
);
398 myRootsToWorkingCopies
.clear();
399 myAuthNotifier
.clear();
402 public VcsShowConfirmationOption
getAddConfirmation() {
403 return myAddConfirmation
;
406 public VcsShowConfirmationOption
getDeleteConfirmation() {
407 return myDeleteConfirmation
;
410 public VcsShowSettingOption
getCheckoutOptions() {
411 return myCheckoutOptions
;
414 public EditFileProvider
getEditFileProvider() {
415 if (myEditFilesProvider
== null) {
416 myEditFilesProvider
= new SvnEditFileProvider(this);
418 return myEditFilesProvider
;
422 public ChangeProvider
getChangeProvider() {
423 if (myChangeProvider
== null) {
424 myChangeProvider
= new SvnChangeProvider(this);
426 return myChangeProvider
;
429 public SVNRepository
createRepository(String url
) throws SVNException
{
430 SVNRepository repos
= SVNRepositoryFactory
.create(SVNURL
.parseURIEncoded(url
));
431 repos
.setAuthenticationManager(myConfiguration
.getAuthenticationManager(this));
432 repos
.setTunnelProvider(myConfiguration
.getOptions(myProject
));
436 public SVNRepository
createRepository(SVNURL url
) throws SVNException
{
437 SVNRepository repos
= SVNRepositoryFactory
.create(url
);
438 repos
.setAuthenticationManager(myConfiguration
.getAuthenticationManager(this));
439 repos
.setTunnelProvider(myConfiguration
.getOptions(myProject
));
443 public SVNUpdateClient
createUpdateClient() {
444 return new SVNUpdateClient(myConfiguration
.getAuthenticationManager(this), myConfiguration
.getOptions(myProject
));
447 public SVNStatusClient
createStatusClient() {
448 return new SVNStatusClient(myConfiguration
.getAuthenticationManager(this), myConfiguration
.getOptions(myProject
));
451 public SVNWCClient
createWCClient() {
452 return new SVNWCClient(myConfiguration
.getAuthenticationManager(this), myConfiguration
.getOptions(myProject
));
455 public SVNCopyClient
createCopyClient() {
456 return new SVNCopyClient(myConfiguration
.getAuthenticationManager(this), myConfiguration
.getOptions(myProject
));
459 public SVNMoveClient
createMoveClient() {
460 return new SVNMoveClient(myConfiguration
.getAuthenticationManager(this), myConfiguration
.getOptions(myProject
));
463 public SVNLogClient
createLogClient() {
464 return new SVNLogClient(myConfiguration
.getAuthenticationManager(this), myConfiguration
.getOptions(myProject
));
467 public SVNCommitClient
createCommitClient() {
468 return new SVNCommitClient(myConfiguration
.getAuthenticationManager(this), myConfiguration
.getOptions(myProject
));
471 public SVNDiffClient
createDiffClient() {
472 return new SVNDiffClient(myConfiguration
.getAuthenticationManager(this), myConfiguration
.getOptions(myProject
));
475 public SVNChangelistClient
createChangelistClient() {
476 return new SVNChangelistClient(myConfiguration
.getAuthenticationManager(this), myConfiguration
.getOptions(myProject
));
479 public SVNWCAccess
createWCAccess() {
480 final SVNWCAccess access
= SVNWCAccess
.newInstance(null);
481 access
.setOptions(myConfiguration
.getOptions(myProject
));
485 public ISVNOptions
getSvnOptions() {
486 return myConfiguration
.getOptions(myProject
);
489 public ISVNAuthenticationManager
getSvnAuthenticationManager() {
490 return myConfiguration
.getAuthenticationManager(this);
493 void dumpFileStatus(FileStatus fs
) {
494 if (LOG
.isDebugEnabled()) {
495 LOG
.debug("FileStatus:" + fs
.getText() + " " + fs
.getColor() + " " + " " + fs
.getClass().getName());
499 public UpdateEnvironment
getIntegrateEnvironment() {
500 if (mySvnIntegrateEnvironment
== null) {
501 mySvnIntegrateEnvironment
= new SvnIntegrateEnvironment(this);
503 return mySvnIntegrateEnvironment
;
506 public UpdateEnvironment
getUpdateEnvironment() {
507 if (mySvnUpdateEnvironment
== null) {
508 mySvnUpdateEnvironment
= new SvnUpdateEnvironment(this);
510 return mySvnUpdateEnvironment
;
513 public String
getDisplayName() {
514 LOG
.debug("getDisplayName");
518 public Configurable
getConfigurable() {
519 LOG
.debug("createConfigurable");
520 return new SvnConfigurable(myProject
);
524 public SvnConfiguration
getSvnConfiguration() {
525 return myConfiguration
;
528 public static SvnVcs
getInstance(Project project
) {
529 return (SvnVcs
) ProjectLevelVcsManager
.getInstance(project
).findVcsByName(VCS_NAME
);
533 public CheckinEnvironment
getCheckinEnvironment() {
534 if (myCheckinEnvironment
== null) {
535 myCheckinEnvironment
= new SvnCheckinEnvironment(this);
537 return myCheckinEnvironment
;
541 public RollbackEnvironment
getRollbackEnvironment() {
542 if (myRollbackEnvironment
== null) {
543 myRollbackEnvironment
= new SvnRollbackEnvironment(this);
545 return myRollbackEnvironment
;
548 public VcsHistoryProvider
getVcsHistoryProvider() {
549 // no heavy state, but it would be useful to have place to keep state in -> do not reuse instance
550 return new SvnHistoryProvider(this);
553 public VcsHistoryProvider
getVcsBlockHistoryProvider() {
554 return getVcsHistoryProvider();
557 public AnnotationProvider
getAnnotationProvider() {
558 if (myAnnotationProvider
== null) {
559 myAnnotationProvider
= new SvnAnnotationProvider(this);
561 return myAnnotationProvider
;
564 public SvnEntriesFileListener
getSvnEntriesFileListener() {
565 return myEntriesFileListener
;
568 public DiffProvider
getDiffProvider() {
569 if (mySvnDiffProvider
== null) {
570 mySvnDiffProvider
= new SvnDiffProvider(this);
572 return mySvnDiffProvider
;
575 private Trinity
<Long
, Long
, Long
> getTimestampForPropertiesChange(final File ioFile
, final boolean isDir
) {
576 final File dir
= isDir ? ioFile
: ioFile
.getParentFile();
577 final String relPath
= SVNAdminUtil
.getPropPath(ioFile
.getName(), isDir ? SVNNodeKind
.DIR
: SVNNodeKind
.FILE
, false);
578 final String relPathBase
= SVNAdminUtil
.getPropBasePath(ioFile
.getName(), isDir ? SVNNodeKind
.DIR
: SVNNodeKind
.FILE
, false);
579 final String relPathRevert
= SVNAdminUtil
.getPropRevertPath(ioFile
.getName(), isDir ? SVNNodeKind
.DIR
: SVNNodeKind
.FILE
, false);
580 return new Trinity
<Long
, Long
, Long
>(new File(dir
, relPath
).lastModified(), new File(dir
, relPathBase
).lastModified(),
581 new File(dir
, relPathRevert
).lastModified());
584 private boolean trinitiesEqual(final Trinity
<Long
, Long
, Long
> t1
, final Trinity
<Long
, Long
, Long
> t2
) {
585 if (t2
.first
== 0 && t2
.second
== 0 && t2
.third
== 0) return false;
586 return t1
.equals(t2
);
590 public SVNPropertyValue
getPropertyWithCaching(final VirtualFile file
, final String propName
) throws SVNException
{
591 Map
<String
, Pair
<SVNPropertyValue
, Trinity
<Long
, Long
, Long
>>> cachedMap
= myPropertyCache
.get(keyForVf(file
));
592 final Pair
<SVNPropertyValue
, Trinity
<Long
, Long
, Long
>> cachedValue
= (cachedMap
== null) ?
null : cachedMap
.get(propName
);
594 final File ioFile
= new File(file
.getPath());
595 final Trinity
<Long
, Long
, Long
> tsTrinity
= getTimestampForPropertiesChange(ioFile
, file
.isDirectory());
597 if (cachedValue
!= null) {
598 // zero means that a file was not found
599 if (trinitiesEqual(cachedValue
.getSecond(), tsTrinity
)) {
600 return cachedValue
.getFirst();
604 final SVNPropertyData value
= createWCClient().doGetProperty(ioFile
, propName
, SVNRevision
.WORKING
, SVNRevision
.WORKING
);
605 final SVNPropertyValue propValue
= (value
== null) ?
null : value
.getValue();
607 if (cachedMap
== null) {
608 cachedMap
= new HashMap
<String
, Pair
<SVNPropertyValue
, Trinity
<Long
, Long
, Long
>>>();
609 myPropertyCache
.put(keyForVf(file
), cachedMap
);
612 cachedMap
.put(propName
, new Pair
<SVNPropertyValue
, Trinity
<Long
, Long
, Long
>>(propValue
, tsTrinity
));
617 public boolean fileExistsInVcs(FilePath path
) {
618 File file
= path
.getIOFile();
621 status
= createStatusClient().doStatus(file
, false);
622 if (status
!= null) {
623 final SVNStatusType statusType
= status
.getContentsStatus();
624 if (statusType
== SVNStatusType
.STATUS_ADDED
) {
625 return status
.isCopied();
627 return !(status
.getContentsStatus() == SVNStatusType
.STATUS_UNVERSIONED
||
628 status
.getContentsStatus() == SVNStatusType
.STATUS_IGNORED
||
629 status
.getContentsStatus() == SVNStatusType
.STATUS_OBSTRUCTED
);
632 catch (SVNException e
) {
638 public boolean fileIsUnderVcs(FilePath path
) {
639 final ChangeListManager clManager
= ChangeListManager
.getInstance(myProject
);
640 return (! SvnStatusUtil
.isIgnoredInAnySense(clManager
, path
.getVirtualFile())) && (! clManager
.isUnversioned(path
.getVirtualFile()));
643 private static File
getEntriesFile(File file
) {
644 return file
.isDirectory() ?
new File(file
, pathToEntries
) : new File(file
.getParentFile(), pathToEntries
);
647 private static File
getDirPropsFile(File file
) {
648 return new File(file
, pathToDirProps
);
652 public SVNInfo
getInfo(final VirtualFile file
) {
654 SVNWCClient wcClient
= new SVNWCClient(getSvnAuthenticationManager(), getSvnOptions());
655 SVNInfo info
= wcClient
.doInfo(new File(file
.getPath()), SVNRevision
.WORKING
);
656 if (info
== null || info
.getRepositoryRootURL() == null) {
657 info
= wcClient
.doInfo(new File(file
.getPath()), SVNRevision
.HEAD
);
661 catch (SVNException e
) {
666 public static class SVNStatusHolder
{
668 private final SVNStatus myValue
;
669 private final long myEntriesTimestamp
;
670 private final long myFileTimestamp
;
671 private final boolean myIsLocked
;
673 public SVNStatusHolder(long entriesStamp
, long fileStamp
, SVNStatus value
) {
675 myEntriesTimestamp
= entriesStamp
;
676 myFileTimestamp
= fileStamp
;
677 myIsLocked
= value
!= null && value
.isLocked();
680 public long getEntriesTimestamp() {
681 return myEntriesTimestamp
;
684 public long getFileTimestamp() {
685 return myFileTimestamp
;
688 public boolean isLocked() {
692 public SVNStatus
getStatus() {
697 public static class SVNInfoHolder
{
699 private final SVNInfo myValue
;
700 private final long myEntriesTimestamp
;
701 private final long myFileTimestamp
;
703 public SVNInfoHolder(long entriesStamp
, long fileStamp
, SVNInfo value
) {
705 myEntriesTimestamp
= entriesStamp
;
706 myFileTimestamp
= fileStamp
;
709 public long getEntriesTimestamp() {
710 return myEntriesTimestamp
;
713 public long getFileTimestamp() {
714 return myFileTimestamp
;
717 public SVNInfo
getInfo() {
722 private static class JavaSVNDebugLogger
extends SVNDebugLogAdapter
{
723 private final boolean myLoggingEnabled
;
724 private final Logger myLog
;
725 @NonNls public static final String TRACE_LOG_PARAMETER_NAME
= "javasvn.log.trace";
727 public JavaSVNDebugLogger(boolean loggingEnabled
, Logger log
) {
728 myLoggingEnabled
= loggingEnabled
;
732 public void log(final SVNLogType logType
, final Throwable th
, final Level logLevel
) {
733 if (myLoggingEnabled
) {
738 public void log(final SVNLogType logType
, final String message
, final Level logLevel
) {
739 if (myLoggingEnabled
) {
744 public void log(final SVNLogType logType
, final String message
, final byte[] data
) {
745 if (myLoggingEnabled
) {
748 myLog
.info(message
+ "\n" + new String(data
, "UTF-8"));
750 catch (UnsupportedEncodingException e
) {
751 myLog
.info(message
+ "\n" + new String(data
));
760 public FileStatus
[] getProvidedStatuses() {
761 return new FileStatus
[]{SvnFileStatus
.EXTERNAL
,
762 SvnFileStatus
.OBSTRUCTED
,
763 SvnFileStatus
.REPLACED
};
768 public CommittedChangesProvider
<SvnChangeList
, ChangeBrowserSettings
> getCommittedChangesProvider() {
769 if (myCommittedChangesProvider
== null) {
770 myCommittedChangesProvider
= new SvnCommittedChangesProvider(myProject
);
772 return myCommittedChangesProvider
;
777 public VcsRevisionNumber
parseRevisionNumber(final String revisionNumberString
) {
778 final SVNRevision revision
= SVNRevision
.parse(revisionNumberString
);
779 if (revision
.equals(SVNRevision
.UNDEFINED
)) {
782 return new SvnRevisionNumber(revision
);
786 public String
getRevisionPattern() {
787 return ourIntegerPattern
;
791 public boolean isVersionedDirectory(final VirtualFile dir
) {
792 return SvnUtil
.seemsLikeVersionedDir(dir
);
796 public SvnFileUrlMapping
getSvnFileUrlMapping() {
797 if (myMapping
== null) {
798 myMapping
= SvnFileUrlMappingImpl
.getInstance(myProject
);
804 * Returns real working copies roots - if there is <Project Root> -> Subversion setting,
805 * and there is one working copy, will return one root
807 public List
<WCInfo
> getAllWcInfos() {
808 final SvnFileUrlMapping urlMapping
= getSvnFileUrlMapping();
810 final List
<RootUrlInfo
> infoList
= urlMapping
.getAllWcInfos();
811 final List
<WCInfo
> infos
= new ArrayList
<WCInfo
>();
812 for (RootUrlInfo info
: infoList
) {
813 final File file
= info
.getIoFile();
814 infos
.add(new WCInfo(file
.getAbsolutePath(), info
.getAbsoluteUrlAsUrl(),
815 SvnFormatSelector
.getWorkingCopyFormat(file
), info
.getRepositoryUrl(), SvnUtil
.isWorkingCopyRoot(file
)));
820 private class SvnWorkingCopyChecker
{
821 private List
<WCInfo
> myAllWcInfos
;
823 public boolean upgradeNeeded() {
824 myAllWcInfos
= getAllWcInfos();
825 for (WCInfo info
: myAllWcInfos
) {
826 if (! WorkingCopyFormat
.ONE_DOT_SIX
.equals(info
.getFormat())) {
833 public void doUpgrade() {
834 ApplicationManager
.getApplication().invokeLater(new Runnable() {
836 final SvnFormatWorker formatWorker
= new SvnFormatWorker(myProject
, WorkingCopyFormat
.ONE_DOT_SIX
, myAllWcInfos
);
837 // additionally ask about working copies with roots above the project root
838 formatWorker
.checkForOutsideCopies();
839 if (formatWorker
.haveStuffToConvert()) {
840 ProgressManager
.getInstance().run(formatWorker
);
848 public RootsConvertor
getCustomConvertor() {
849 return getSvnFileUrlMapping();
853 public MergeProvider
getMergeProvider() {
854 if (myMergeProvider
== null) {
855 myMergeProvider
= new SvnMergeProvider(myProject
);
857 return myMergeProvider
;
861 public List
<AnAction
> getAdditionalActionsForLocalChange() {
862 return Arrays
.<AnAction
>asList(new ShowPropertiesDiffWithLocalAction());
865 private String
keyForVf(final VirtualFile vf
) {
870 public boolean allowsNestedRoots() {
871 return SvnConfiguration
.getInstance(myProject
).DETECT_NESTED_COPIES
;
875 public <S
> List
<S
> filterUniqueRoots(final List
<S
> in
, final Convertor
<S
, VirtualFile
> convertor
) {
876 if (in
.size() <= 1) return in
;
878 final List
<MyPair
<S
>> infos
= new ArrayList
<MyPair
<S
>>(in
.size());
879 final SvnFileUrlMappingImpl mapping
= (SvnFileUrlMappingImpl
) getSvnFileUrlMapping();
880 final List
<S
> notMatched
= new LinkedList
<S
>();
882 final VirtualFile vf
= convertor
.convert(s
);
884 final File ioFile
= new File(vf
.getPath());
885 SVNURL url
= mapping
.getUrlForFile(ioFile
);
887 url
= SvnUtil
.getUrl(ioFile
);
893 infos
.add(new MyPair
<S
>(vf
, url
.toString(), s
));
895 final List
<MyPair
<S
>> filtered
= new ArrayList
<MyPair
<S
>>(infos
.size());
896 ForNestedRootChecker
.filterOutSuperfluousChildren(this, infos
, filtered
);
898 final List
<S
> converted
= ObjectsConvertor
.convert(filtered
, new Convertor
<MyPair
<S
>, S
>() {
899 public S
convert(final MyPair
<S
> o
) {
903 if (! notMatched
.isEmpty()) {
904 // potential bug is here: order is not kept. but seems it only occurs for cases where result is sorted after filtering so ok
905 converted
.addAll(notMatched
);
910 private static class MyPair
<T
> implements RootUrlPair
{
911 private final VirtualFile myFile
;
912 private final String myUrl
;
913 private final T mySrc
;
915 private MyPair(VirtualFile file
, String url
, T src
) {
925 public VirtualFile
getVirtualFile() {
929 public String
getUrl() {
934 private static class MyFrameStateListener
implements FrameStateListener
{
935 private final ChangeListManager myClManager
;
936 private final VcsDirtyScopeManager myDirtyScopeManager
;
938 private MyFrameStateListener(ChangeListManager clManager
, VcsDirtyScopeManager dirtyScopeManager
) {
939 myClManager
= clManager
;
940 myDirtyScopeManager
= dirtyScopeManager
;
943 public void onFrameDeactivated() {
946 public void onFrameActivated() {
947 final List
<VirtualFile
> folders
= ((ChangeListManagerImpl
)myClManager
).getLockedFolders();
948 if (! folders
.isEmpty()) {
949 myDirtyScopeManager
.filesDirty(null, folders
);
954 public static VcsKey
getKey() {