2 * Copyright 2000-2010 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
.Notification
;
23 import com
.intellij
.notification
.NotificationListener
;
24 import com
.intellij
.notification
.NotificationType
;
25 import com
.intellij
.notification
.Notifications
;
26 import com
.intellij
.openapi
.actionSystem
.AnAction
;
27 import com
.intellij
.openapi
.application
.Application
;
28 import com
.intellij
.openapi
.application
.ApplicationManager
;
29 import com
.intellij
.openapi
.diagnostic
.Logger
;
30 import com
.intellij
.openapi
.options
.Configurable
;
31 import com
.intellij
.openapi
.progress
.ProcessCanceledException
;
32 import com
.intellij
.openapi
.progress
.ProgressManager
;
33 import com
.intellij
.openapi
.project
.DumbAwareRunnable
;
34 import com
.intellij
.openapi
.project
.Project
;
35 import com
.intellij
.openapi
.startup
.StartupManager
;
36 import com
.intellij
.openapi
.ui
.DialogWrapper
;
37 import com
.intellij
.openapi
.ui
.Messages
;
38 import com
.intellij
.openapi
.util
.Pair
;
39 import com
.intellij
.openapi
.util
.SystemInfo
;
40 import com
.intellij
.openapi
.util
.Trinity
;
41 import com
.intellij
.openapi
.vcs
.*;
42 import com
.intellij
.openapi
.vcs
.annotate
.AnnotationProvider
;
43 import com
.intellij
.openapi
.vcs
.changes
.*;
44 import com
.intellij
.openapi
.vcs
.checkin
.CheckinEnvironment
;
45 import com
.intellij
.openapi
.vcs
.diff
.DiffProvider
;
46 import com
.intellij
.openapi
.vcs
.history
.VcsFileRevision
;
47 import com
.intellij
.openapi
.vcs
.history
.VcsHistoryProvider
;
48 import com
.intellij
.openapi
.vcs
.history
.VcsRevisionNumber
;
49 import com
.intellij
.openapi
.vcs
.merge
.MergeProvider
;
50 import com
.intellij
.openapi
.vcs
.rollback
.RollbackEnvironment
;
51 import com
.intellij
.openapi
.vcs
.update
.UpdateEnvironment
;
52 import com
.intellij
.openapi
.vcs
.versionBrowser
.ChangeBrowserSettings
;
53 import com
.intellij
.openapi
.vcs
.versionBrowser
.CommittedChangeList
;
54 import com
.intellij
.openapi
.vfs
.VirtualFile
;
55 import com
.intellij
.openapi
.vfs
.VirtualFileManager
;
56 import com
.intellij
.util
.ThreeState
;
57 import com
.intellij
.util
.containers
.Convertor
;
58 import com
.intellij
.util
.containers
.SoftHashMap
;
59 import com
.intellij
.util
.messages
.MessageBus
;
60 import com
.intellij
.util
.messages
.MessageBusConnection
;
61 import com
.intellij
.util
.messages
.Topic
;
62 import org
.jetbrains
.annotations
.NonNls
;
63 import org
.jetbrains
.annotations
.NotNull
;
64 import org
.jetbrains
.annotations
.Nullable
;
65 import org
.jetbrains
.idea
.svn
.actions
.ShowAllSubmittedFilesAction
;
66 import org
.jetbrains
.idea
.svn
.actions
.ShowPropertiesDiffWithLocalAction
;
67 import org
.jetbrains
.idea
.svn
.actions
.SvnMergeProvider
;
68 import org
.jetbrains
.idea
.svn
.annotate
.SvnAnnotationProvider
;
69 import org
.jetbrains
.idea
.svn
.checkin
.SvnCheckinEnvironment
;
70 import org
.jetbrains
.idea
.svn
.dialogs
.SvnFormatWorker
;
71 import org
.jetbrains
.idea
.svn
.dialogs
.WCInfo
;
72 import org
.jetbrains
.idea
.svn
.history
.*;
73 import org
.jetbrains
.idea
.svn
.rollback
.SvnRollbackEnvironment
;
74 import org
.jetbrains
.idea
.svn
.update
.SvnIntegrateEnvironment
;
75 import org
.jetbrains
.idea
.svn
.update
.SvnUpdateEnvironment
;
76 import org
.tmatesoft
.svn
.core
.*;
77 import org
.tmatesoft
.svn
.core
.auth
.ISVNAuthenticationManager
;
78 import org
.tmatesoft
.svn
.core
.internal
.io
.dav
.DAVRepositoryFactory
;
79 import org
.tmatesoft
.svn
.core
.internal
.io
.fs
.FSRepositoryFactory
;
80 import org
.tmatesoft
.svn
.core
.internal
.io
.svn
.SVNRepositoryFactoryImpl
;
81 import org
.tmatesoft
.svn
.core
.internal
.util
.jna
.SVNJNAUtil
;
82 import org
.tmatesoft
.svn
.core
.internal
.wc
.SVNAdminUtil
;
83 import org
.tmatesoft
.svn
.core
.internal
.wc
.admin
.SVNAdminArea14
;
84 import org
.tmatesoft
.svn
.core
.internal
.wc
.admin
.SVNAdminAreaFactory
;
85 import org
.tmatesoft
.svn
.core
.internal
.wc
.admin
.SVNWCAccess
;
86 import org
.tmatesoft
.svn
.core
.io
.SVNRepository
;
87 import org
.tmatesoft
.svn
.core
.io
.SVNRepositoryFactory
;
88 import org
.tmatesoft
.svn
.core
.wc
.*;
89 import org
.tmatesoft
.svn
.util
.SVNDebugLog
;
90 import org
.tmatesoft
.svn
.util
.SVNDebugLogAdapter
;
91 import org
.tmatesoft
.svn
.util
.SVNLogType
;
93 import javax
.swing
.event
.HyperlinkEvent
;
95 import java
.io
.UnsupportedEncodingException
;
97 import java
.util
.logging
.Level
;
99 @SuppressWarnings({"IOResourceOpenedButNotSafelyClosed"})
100 public class SvnVcs
extends AbstractVcs
{
101 private final static Logger REFRESH_LOG
= Logger
.getInstance("#svn_refresh");
103 private static final Logger LOG
= Logger
.getInstance("org.jetbrains.idea.svn.SvnVcs");
104 @NonNls public static final String VCS_NAME
= "svn";
105 private static final VcsKey ourKey
= createKey(VCS_NAME
);
106 private final Map
<String
, Map
<String
, Pair
<SVNPropertyValue
, Trinity
<Long
, Long
, Long
>>>> myPropertyCache
= new SoftHashMap
<String
, Map
<String
, Pair
<SVNPropertyValue
, Trinity
<Long
, Long
, Long
>>>>();
108 private final SvnConfiguration myConfiguration
;
109 private final SvnEntriesFileListener myEntriesFileListener
;
111 private CheckinEnvironment myCheckinEnvironment
;
112 private RollbackEnvironment myRollbackEnvironment
;
113 private UpdateEnvironment mySvnUpdateEnvironment
;
114 private UpdateEnvironment mySvnIntegrateEnvironment
;
115 private VcsHistoryProvider mySvnHistoryProvider
;
116 private AnnotationProvider myAnnotationProvider
;
117 private DiffProvider mySvnDiffProvider
;
118 private final VcsShowConfirmationOption myAddConfirmation
;
119 private final VcsShowConfirmationOption myDeleteConfirmation
;
120 private EditFileProvider myEditFilesProvider
;
121 private SvnCommittedChangesProvider myCommittedChangesProvider
;
122 private final VcsShowSettingOption myCheckoutOptions
;
124 private ChangeProvider myChangeProvider
;
125 private MergeProvider myMergeProvider
;
127 @NonNls public static final String LOG_PARAMETER_NAME
= "javasvn.log";
128 public static final String pathToEntries
= SvnUtil
.SVN_ADMIN_DIR_NAME
+ File
.separatorChar
+ SvnUtil
.ENTRIES_FILE_NAME
;
129 public static final String pathToDirProps
= SvnUtil
.SVN_ADMIN_DIR_NAME
+ File
.separatorChar
+ SvnUtil
.DIR_PROPS_FILE_NAME
;
130 private final SvnChangelistListener myChangeListListener
;
132 private SvnCopiesRefreshManager myCopiesRefreshManager
;
133 private SvnFileUrlMappingImpl myMapping
;
134 private final MyFrameStateListener myFrameStateListener
;
136 public static final Topic
<Runnable
> ROOTS_RELOADED
= new Topic
<Runnable
>("ROOTS_RELOADED", Runnable
.class);
137 private VcsListener myVcsListener
;
139 private final RootsToWorkingCopies myRootsToWorkingCopies
;
140 private final SvnAuthenticationNotifier myAuthNotifier
;
143 SVNJNAUtil
.setJNAEnabled(true);
144 SvnHttpAuthMethodsDefaultChecker
.check();
146 //noinspection UseOfArchaicSystemPropertyAccessors
147 final JavaSVNDebugLogger logger
= new JavaSVNDebugLogger(Boolean
.getBoolean(LOG_PARAMETER_NAME
), LOG
);
148 SVNDebugLog
.setDefaultLog(logger
);
149 SVNAdminAreaFactory
.setSelector(new SvnFormatSelector());
151 DAVRepositoryFactory
.setup();
152 SVNRepositoryFactoryImpl
.setup();
153 FSRepositoryFactory
.setup();
155 // non-optimized writing is fast enough on Linux/MacOS, and somewhat more reliable
156 if (SystemInfo
.isWindows
) {
157 SVNAdminArea14
.setOptimizedWritingEnabled(true);
160 if (! SVNJNAUtil
.isJNAPresent()) {
161 LOG
.warn("JNA is not found by svnkit library");
165 private static Boolean
booleanProperty(final String systemParameterName
) {
166 return Boolean
.valueOf(System
.getProperty(systemParameterName
));
169 public SvnVcs(final Project project
, MessageBus bus
, SvnConfiguration svnConfiguration
, final ChangeListManager changeListManager
,
170 final VcsDirtyScopeManager vcsDirtyScopeManager
) {
171 super(project
, VCS_NAME
);
173 myRootsToWorkingCopies
= new RootsToWorkingCopies(myProject
);
174 myConfiguration
= svnConfiguration
;
175 myAuthNotifier
= new SvnAuthenticationNotifier(this);
177 dumpFileStatus(FileStatus
.ADDED
);
178 dumpFileStatus(FileStatus
.DELETED
);
179 dumpFileStatus(FileStatus
.MERGE
);
180 dumpFileStatus(FileStatus
.MODIFIED
);
181 dumpFileStatus(FileStatus
.NOT_CHANGED
);
182 dumpFileStatus(FileStatus
.UNKNOWN
);
184 dumpFileStatus(SvnFileStatus
.REPLACED
);
185 dumpFileStatus(SvnFileStatus
.EXTERNAL
);
186 dumpFileStatus(SvnFileStatus
.OBSTRUCTED
);
188 myEntriesFileListener
= new SvnEntriesFileListener(project
);
190 final ProjectLevelVcsManager vcsManager
= ProjectLevelVcsManager
.getInstance(project
);
191 myAddConfirmation
= vcsManager
.getStandardConfirmation(VcsConfiguration
.StandardConfirmation
.ADD
, this);
192 myDeleteConfirmation
= vcsManager
.getStandardConfirmation(VcsConfiguration
.StandardConfirmation
.REMOVE
, this);
193 myCheckoutOptions
= vcsManager
.getStandardOption(VcsConfiguration
.StandardOption
.CHECKOUT
, this);
195 if (myProject
.isDefault()) {
196 myChangeListListener
= null;
199 upgradeIfNeeded(bus
);
201 myChangeListListener
= new SvnChangelistListener(myProject
, createChangelistClient());
203 myVcsListener
= new VcsListener() {
204 public void directoryMappingChanged() {
205 invokeRefreshSvnRoots(true);
210 myFrameStateListener
= new MyFrameStateListener(changeListManager
, vcsDirtyScopeManager
);
213 public void postStartup() {
214 if (myProject
.isDefault()) return;
215 myCopiesRefreshManager
= new SvnCopiesRefreshManager(myProject
, (SvnFileUrlMappingImpl
) getSvnFileUrlMapping());
217 invokeRefreshSvnRoots(true);
220 public void invokeRefreshSvnRoots(final boolean asynchronous
) {
221 REFRESH_LOG
.debug("refresh: ", new Throwable());
222 if (myCopiesRefreshManager
!= null) {
224 myCopiesRefreshManager
.getCopiesRefresh().asynchRequest();
226 if (ApplicationManager
.getApplication().isDispatchThread()) {
227 ProgressManager
.getInstance().runProcessWithProgressSynchronously(new Runnable() {
229 myCopiesRefreshManager
.getCopiesRefresh().synchRequest();
231 }, SvnBundle
.message("refreshing.working.copies.roots.progress.text"), true, myProject
);
233 myCopiesRefreshManager
.getCopiesRefresh().synchRequest();
240 public boolean checkImmediateParentsBeforeCommit() {
244 private void upgradeIfNeeded(final MessageBus bus
) {
245 final MessageBusConnection connection
= bus
.connect();
246 connection
.subscribe(ChangeListManagerImpl
.LISTS_LOADED
, new LocalChangeListsLoadedListener() {
247 public void processLoadedLists(final List
<LocalChangeList
> lists
) {
248 SvnConfiguration
.SvnSupportOptions supportOptions
= null;
250 ChangeListManager
.getInstanceChecked(myProject
).setReadOnly(SvnChangeProvider
.ourDefaultListName
, true);
252 supportOptions
= myConfiguration
.getSupportOptions();
254 upgradeToRecentVersion(supportOptions
);
255 if (! supportOptions
.changeListsSynchronized()) {
256 processChangeLists(lists
);
259 catch (ProcessCanceledException e
) {
262 if (supportOptions
!= null) {
263 supportOptions
.upgrade();
267 connection
.disconnect();
272 public void processChangeLists(final List
<LocalChangeList
> lists
) {
273 final ProjectLevelVcsManager plVcsManager
= ProjectLevelVcsManager
.getInstanceChecked(myProject
);
274 plVcsManager
.startBackgroundVcsOperation();
276 final SVNChangelistClient client
= createChangelistClient();
277 for (LocalChangeList list
: lists
) {
278 if (! list
.isDefault()) {
279 final Collection
<Change
> changes
= list
.getChanges();
280 for (Change change
: changes
) {
281 correctListForRevision(plVcsManager
, change
.getBeforeRevision(), client
, list
.getName());
282 correctListForRevision(plVcsManager
, change
.getAfterRevision(), client
, list
.getName());
288 final Application appManager
= ApplicationManager
.getApplication();
289 if (appManager
.isDispatchThread()) {
290 appManager
.executeOnPooledThread(new Runnable() {
292 plVcsManager
.stopBackgroundVcsOperation();
296 plVcsManager
.stopBackgroundVcsOperation();
301 private void correctListForRevision(final ProjectLevelVcsManager plVcsManager
, final ContentRevision revision
,
302 final SVNChangelistClient client
, final String name
) {
303 if (revision
!= null) {
304 final FilePath path
= revision
.getFile();
305 final AbstractVcs vcs
= plVcsManager
.getVcsFor(path
);
306 if ((vcs
!= null) && VCS_NAME
.equals(vcs
.getName())) {
308 client
.doAddToChangelist(new File
[] {path
.getIOFile()}, SVNDepth
.EMPTY
, name
, null);
310 catch (SVNException e
) {
311 // left in default list
317 private final static String UPGRADE_SUBVERSION_FORMAT
= "Subversion";
319 private void upgradeToRecentVersion(final SvnConfiguration
.SvnSupportOptions supportOptions
) {
320 if (! supportOptions
.upgradeTo16Asked()) {
321 final SvnWorkingCopyChecker workingCopyChecker
= new SvnWorkingCopyChecker();
323 if (workingCopyChecker
.upgradeNeeded()) {
325 Notifications
.Bus
.notify(new Notification(UPGRADE_SUBVERSION_FORMAT
, SvnBundle
.message("upgrade.format.to16.question.title"),
326 "Old format Subversion working copies <a href=\"\">could be upgraded to version 1.6</a>.",
327 NotificationType
.INFORMATION
, new NotificationListener() {
328 public void hyperlinkUpdate(@NotNull Notification notification
, @NotNull HyperlinkEvent event
) {
329 final int upgradeAnswer
= Messages
.showYesNoDialog(SvnBundle
.message("upgrade.format.to16.question.text",
330 SvnBundle
.message("label.where.svn.format.can.be.changed.text", SvnBundle
.message("action.show.svn.map.text.reference"))),
331 SvnBundle
.message("upgrade.format.to16.question.title"), Messages
.getWarningIcon());
332 if (DialogWrapper
.OK_EXIT_CODE
== upgradeAnswer
) {
333 workingCopyChecker
.doUpgrade();
336 notification
.expire();
344 public void activate() {
345 final ProjectLevelVcsManager vcsManager
= ProjectLevelVcsManager
.getInstance(myProject
);
346 if (! myProject
.isDefault()) {
347 ChangeListManager
.getInstance(myProject
).addChangeListListener(myChangeListListener
);
348 vcsManager
.addVcsListener(myVcsListener
);
351 SvnApplicationSettings
.getInstance().svnActivated();
352 VirtualFileManager
.getInstance().addVirtualFileListener(myEntriesFileListener
);
353 // this will initialize its inner listener for committed changes upload
354 LoadedRevisionsCache
.getInstance(myProject
);
355 FrameStateManager
.getInstance().addListener(myFrameStateListener
);
357 // do one time after project loaded
358 StartupManager
.getInstance(myProject
).runWhenProjectIsInitialized(new DumbAwareRunnable() {
362 // for IDEA, it takes 2 minutes - and anyway this can be done in background, no sence...
363 // once it could be mistaken about copies for 2 minutes on start...
365 /*if (! myMapping.getAllWcInfos().isEmpty()) {
366 invokeRefreshSvnRoots();
369 ProgressManager.getInstance().runProcessWithProgressSynchronously(new Runnable() {
371 myCopiesRefreshManager.getCopiesRefresh().ensureInit();
373 }, SvnBundle.message("refreshing.working.copies.roots.progress.text"), true, myProject);*/
377 vcsManager
.addVcsListener(myRootsToWorkingCopies
);
380 public RootsToWorkingCopies
getRootsToWorkingCopies() {
381 return myRootsToWorkingCopies
;
384 public SvnAuthenticationNotifier
getAuthNotifier() {
385 return myAuthNotifier
;
389 public void deactivate() {
390 FrameStateManager
.getInstance().removeListener(myFrameStateListener
);
392 final ProjectLevelVcsManager vcsManager
= ProjectLevelVcsManager
.getInstance(myProject
);
393 if (myVcsListener
!= null) {
394 vcsManager
.removeVcsListener(myVcsListener
);
397 VirtualFileManager
.getInstance().removeVirtualFileListener(myEntriesFileListener
);
398 SvnApplicationSettings
.getInstance().svnDeactivated();
399 new DefaultSVNRepositoryPool(null, null).shutdownConnections(true);
400 if (myCommittedChangesProvider
!= null) {
401 myCommittedChangesProvider
.deactivate();
403 if (myChangeListListener
!= null && (! myProject
.isDefault())) {
404 ChangeListManager
.getInstance(myProject
).removeChangeListListener(myChangeListListener
);
406 vcsManager
.removeVcsListener(myRootsToWorkingCopies
);
407 myRootsToWorkingCopies
.clear();
408 myAuthNotifier
.clear();
411 public VcsShowConfirmationOption
getAddConfirmation() {
412 return myAddConfirmation
;
415 public VcsShowConfirmationOption
getDeleteConfirmation() {
416 return myDeleteConfirmation
;
419 public VcsShowSettingOption
getCheckoutOptions() {
420 return myCheckoutOptions
;
423 public EditFileProvider
getEditFileProvider() {
424 if (myEditFilesProvider
== null) {
425 myEditFilesProvider
= new SvnEditFileProvider(this);
427 return myEditFilesProvider
;
431 public ChangeProvider
getChangeProvider() {
432 if (myChangeProvider
== null) {
433 myChangeProvider
= new SvnChangeProvider(this);
435 return myChangeProvider
;
438 public SVNRepository
createRepository(String url
) throws SVNException
{
439 SVNRepository repos
= SVNRepositoryFactory
.create(SVNURL
.parseURIEncoded(url
));
440 repos
.setAuthenticationManager(myConfiguration
.getAuthenticationManager(this));
441 repos
.setTunnelProvider(myConfiguration
.getOptions(myProject
));
445 public SVNRepository
createRepository(SVNURL url
) throws SVNException
{
446 SVNRepository repos
= SVNRepositoryFactory
.create(url
);
447 repos
.setAuthenticationManager(myConfiguration
.getAuthenticationManager(this));
448 repos
.setTunnelProvider(myConfiguration
.getOptions(myProject
));
452 public SVNUpdateClient
createUpdateClient() {
453 return new SVNUpdateClient(myConfiguration
.getAuthenticationManager(this), myConfiguration
.getOptions(myProject
));
456 public SVNStatusClient
createStatusClient() {
457 return new SVNStatusClient(myConfiguration
.getAuthenticationManager(this), myConfiguration
.getOptions(myProject
));
460 public SVNWCClient
createWCClient() {
461 return new SVNWCClient(myConfiguration
.getAuthenticationManager(this), myConfiguration
.getOptions(myProject
));
464 public SVNCopyClient
createCopyClient() {
465 return new SVNCopyClient(myConfiguration
.getAuthenticationManager(this), myConfiguration
.getOptions(myProject
));
468 public SVNMoveClient
createMoveClient() {
469 return new SVNMoveClient(myConfiguration
.getAuthenticationManager(this), myConfiguration
.getOptions(myProject
));
472 public SVNLogClient
createLogClient() {
473 return new SVNLogClient(myConfiguration
.getAuthenticationManager(this), myConfiguration
.getOptions(myProject
));
476 public SVNCommitClient
createCommitClient() {
477 return new SVNCommitClient(myConfiguration
.getAuthenticationManager(this), myConfiguration
.getOptions(myProject
));
480 public SVNDiffClient
createDiffClient() {
481 return new SVNDiffClient(myConfiguration
.getAuthenticationManager(this), myConfiguration
.getOptions(myProject
));
484 public SVNChangelistClient
createChangelistClient() {
485 return new SVNChangelistClient(myConfiguration
.getAuthenticationManager(this), myConfiguration
.getOptions(myProject
));
488 public SVNWCAccess
createWCAccess() {
489 final SVNWCAccess access
= SVNWCAccess
.newInstance(null);
490 access
.setOptions(myConfiguration
.getOptions(myProject
));
494 public ISVNOptions
getSvnOptions() {
495 return myConfiguration
.getOptions(myProject
);
498 public ISVNAuthenticationManager
getSvnAuthenticationManager() {
499 return myConfiguration
.getAuthenticationManager(this);
502 void dumpFileStatus(FileStatus fs
) {
503 if (LOG
.isDebugEnabled()) {
504 LOG
.debug("FileStatus:" + fs
.getText() + " " + fs
.getColor() + " " + " " + fs
.getClass().getName());
508 public UpdateEnvironment
getIntegrateEnvironment() {
509 if (mySvnIntegrateEnvironment
== null) {
510 mySvnIntegrateEnvironment
= new SvnIntegrateEnvironment(this);
512 return mySvnIntegrateEnvironment
;
515 public UpdateEnvironment
getUpdateEnvironment() {
516 if (mySvnUpdateEnvironment
== null) {
517 mySvnUpdateEnvironment
= new SvnUpdateEnvironment(this);
519 return mySvnUpdateEnvironment
;
522 public String
getDisplayName() {
523 LOG
.debug("getDisplayName");
527 public Configurable
getConfigurable() {
528 LOG
.debug("createConfigurable");
529 return new SvnConfigurable(myProject
);
533 public SvnConfiguration
getSvnConfiguration() {
534 return myConfiguration
;
537 public static SvnVcs
getInstance(Project project
) {
538 return (SvnVcs
) ProjectLevelVcsManager
.getInstance(project
).findVcsByName(VCS_NAME
);
542 public CheckinEnvironment
getCheckinEnvironment() {
543 if (myCheckinEnvironment
== null) {
544 myCheckinEnvironment
= new SvnCheckinEnvironment(this);
546 return myCheckinEnvironment
;
550 public RollbackEnvironment
getRollbackEnvironment() {
551 if (myRollbackEnvironment
== null) {
552 myRollbackEnvironment
= new SvnRollbackEnvironment(this);
554 return myRollbackEnvironment
;
557 public VcsHistoryProvider
getVcsHistoryProvider() {
558 // no heavy state, but it would be useful to have place to keep state in -> do not reuse instance
559 return new SvnHistoryProvider(this);
562 public VcsHistoryProvider
getVcsBlockHistoryProvider() {
563 return getVcsHistoryProvider();
566 public AnnotationProvider
getAnnotationProvider() {
567 if (myAnnotationProvider
== null) {
568 myAnnotationProvider
= new SvnAnnotationProvider(this);
570 return myAnnotationProvider
;
573 public SvnEntriesFileListener
getSvnEntriesFileListener() {
574 return myEntriesFileListener
;
577 public DiffProvider
getDiffProvider() {
578 if (mySvnDiffProvider
== null) {
579 mySvnDiffProvider
= new SvnDiffProvider(this);
581 return mySvnDiffProvider
;
584 private Trinity
<Long
, Long
, Long
> getTimestampForPropertiesChange(final File ioFile
, final boolean isDir
) {
585 final File dir
= isDir ? ioFile
: ioFile
.getParentFile();
586 final String relPath
= SVNAdminUtil
.getPropPath(ioFile
.getName(), isDir ? SVNNodeKind
.DIR
: SVNNodeKind
.FILE
, false);
587 final String relPathBase
= SVNAdminUtil
.getPropBasePath(ioFile
.getName(), isDir ? SVNNodeKind
.DIR
: SVNNodeKind
.FILE
, false);
588 final String relPathRevert
= SVNAdminUtil
.getPropRevertPath(ioFile
.getName(), isDir ? SVNNodeKind
.DIR
: SVNNodeKind
.FILE
, false);
589 return new Trinity
<Long
, Long
, Long
>(new File(dir
, relPath
).lastModified(), new File(dir
, relPathBase
).lastModified(),
590 new File(dir
, relPathRevert
).lastModified());
593 private boolean trinitiesEqual(final Trinity
<Long
, Long
, Long
> t1
, final Trinity
<Long
, Long
, Long
> t2
) {
594 if (t2
.first
== 0 && t2
.second
== 0 && t2
.third
== 0) return false;
595 return t1
.equals(t2
);
599 public SVNPropertyValue
getPropertyWithCaching(final VirtualFile file
, final String propName
) throws SVNException
{
600 Map
<String
, Pair
<SVNPropertyValue
, Trinity
<Long
, Long
, Long
>>> cachedMap
= myPropertyCache
.get(keyForVf(file
));
601 final Pair
<SVNPropertyValue
, Trinity
<Long
, Long
, Long
>> cachedValue
= (cachedMap
== null) ?
null : cachedMap
.get(propName
);
603 final File ioFile
= new File(file
.getPath());
604 final Trinity
<Long
, Long
, Long
> tsTrinity
= getTimestampForPropertiesChange(ioFile
, file
.isDirectory());
606 if (cachedValue
!= null) {
607 // zero means that a file was not found
608 if (trinitiesEqual(cachedValue
.getSecond(), tsTrinity
)) {
609 return cachedValue
.getFirst();
613 final SVNPropertyData value
= createWCClient().doGetProperty(ioFile
, propName
, SVNRevision
.WORKING
, SVNRevision
.WORKING
);
614 final SVNPropertyValue propValue
= (value
== null) ?
null : value
.getValue();
616 if (cachedMap
== null) {
617 cachedMap
= new HashMap
<String
, Pair
<SVNPropertyValue
, Trinity
<Long
, Long
, Long
>>>();
618 myPropertyCache
.put(keyForVf(file
), cachedMap
);
621 cachedMap
.put(propName
, new Pair
<SVNPropertyValue
, Trinity
<Long
, Long
, Long
>>(propValue
, tsTrinity
));
626 public boolean fileExistsInVcs(FilePath path
) {
627 File file
= path
.getIOFile();
630 status
= createStatusClient().doStatus(file
, false);
631 if (status
!= null) {
632 final SVNStatusType statusType
= status
.getContentsStatus();
633 if (statusType
== SVNStatusType
.STATUS_ADDED
) {
634 return status
.isCopied();
636 return !(status
.getContentsStatus() == SVNStatusType
.STATUS_UNVERSIONED
||
637 status
.getContentsStatus() == SVNStatusType
.STATUS_IGNORED
||
638 status
.getContentsStatus() == SVNStatusType
.STATUS_OBSTRUCTED
);
641 catch (SVNException e
) {
647 public boolean fileIsUnderVcs(FilePath path
) {
648 final ChangeListManager clManager
= ChangeListManager
.getInstance(myProject
);
649 return (! SvnStatusUtil
.isIgnoredInAnySense(clManager
, path
.getVirtualFile())) && (! clManager
.isUnversioned(path
.getVirtualFile()));
652 private static File
getEntriesFile(File file
) {
653 return file
.isDirectory() ?
new File(file
, pathToEntries
) : new File(file
.getParentFile(), pathToEntries
);
656 private static File
getDirPropsFile(File file
) {
657 return new File(file
, pathToDirProps
);
661 public SVNInfo
getInfo(final VirtualFile file
) {
663 SVNWCClient wcClient
= new SVNWCClient(getSvnAuthenticationManager(), getSvnOptions());
664 SVNInfo info
= wcClient
.doInfo(new File(file
.getPath()), SVNRevision
.WORKING
);
665 if (info
== null || info
.getRepositoryRootURL() == null) {
666 info
= wcClient
.doInfo(new File(file
.getPath()), SVNRevision
.HEAD
);
670 catch (SVNException e
) {
675 public static class SVNStatusHolder
{
677 private final SVNStatus myValue
;
678 private final long myEntriesTimestamp
;
679 private final long myFileTimestamp
;
680 private final boolean myIsLocked
;
682 public SVNStatusHolder(long entriesStamp
, long fileStamp
, SVNStatus value
) {
684 myEntriesTimestamp
= entriesStamp
;
685 myFileTimestamp
= fileStamp
;
686 myIsLocked
= value
!= null && value
.isLocked();
689 public long getEntriesTimestamp() {
690 return myEntriesTimestamp
;
693 public long getFileTimestamp() {
694 return myFileTimestamp
;
697 public boolean isLocked() {
701 public SVNStatus
getStatus() {
706 public static class SVNInfoHolder
{
708 private final SVNInfo myValue
;
709 private final long myEntriesTimestamp
;
710 private final long myFileTimestamp
;
712 public SVNInfoHolder(long entriesStamp
, long fileStamp
, SVNInfo value
) {
714 myEntriesTimestamp
= entriesStamp
;
715 myFileTimestamp
= fileStamp
;
718 public long getEntriesTimestamp() {
719 return myEntriesTimestamp
;
722 public long getFileTimestamp() {
723 return myFileTimestamp
;
726 public SVNInfo
getInfo() {
731 private static class JavaSVNDebugLogger
extends SVNDebugLogAdapter
{
732 private final boolean myLoggingEnabled
;
733 private final Logger myLog
;
734 @NonNls public static final String TRACE_LOG_PARAMETER_NAME
= "javasvn.log.trace";
736 public JavaSVNDebugLogger(boolean loggingEnabled
, Logger log
) {
737 myLoggingEnabled
= loggingEnabled
;
741 public void log(final SVNLogType logType
, final Throwable th
, final Level logLevel
) {
742 if (myLoggingEnabled
) {
747 public void log(final SVNLogType logType
, final String message
, final Level logLevel
) {
748 if (myLoggingEnabled
) {
753 public void log(final SVNLogType logType
, final String message
, final byte[] data
) {
754 if (myLoggingEnabled
) {
757 myLog
.info(message
+ "\n" + new String(data
, "UTF-8"));
759 catch (UnsupportedEncodingException e
) {
760 myLog
.info(message
+ "\n" + new String(data
));
769 public FileStatus
[] getProvidedStatuses() {
770 return new FileStatus
[]{SvnFileStatus
.EXTERNAL
,
771 SvnFileStatus
.OBSTRUCTED
,
772 SvnFileStatus
.REPLACED
};
777 public CommittedChangesProvider
<SvnChangeList
, ChangeBrowserSettings
> getCommittedChangesProvider() {
778 if (myCommittedChangesProvider
== null) {
779 myCommittedChangesProvider
= new SvnCommittedChangesProvider(myProject
);
781 return myCommittedChangesProvider
;
786 public VcsRevisionNumber
parseRevisionNumber(final String revisionNumberString
) {
787 final SVNRevision revision
= SVNRevision
.parse(revisionNumberString
);
788 if (revision
.equals(SVNRevision
.UNDEFINED
)) {
791 return new SvnRevisionNumber(revision
);
795 public String
getRevisionPattern() {
796 return ourIntegerPattern
;
800 public boolean isVersionedDirectory(final VirtualFile dir
) {
801 return SvnUtil
.seemsLikeVersionedDir(dir
);
805 public SvnFileUrlMapping
getSvnFileUrlMapping() {
806 if (myMapping
== null) {
807 myMapping
= SvnFileUrlMappingImpl
.getInstance(myProject
);
813 * Returns real working copies roots - if there is <Project Root> -> Subversion setting,
814 * and there is one working copy, will return one root
816 public List
<WCInfo
> getAllWcInfos() {
817 final SvnFileUrlMapping urlMapping
= getSvnFileUrlMapping();
819 final List
<RootUrlInfo
> infoList
= urlMapping
.getAllWcInfos();
820 final List
<WCInfo
> infos
= new ArrayList
<WCInfo
>();
821 for (RootUrlInfo info
: infoList
) {
822 final File file
= info
.getIoFile();
823 infos
.add(new WCInfo(file
.getAbsolutePath(), info
.getAbsoluteUrlAsUrl(),
824 SvnFormatSelector
.getWorkingCopyFormat(file
), info
.getRepositoryUrl(), SvnUtil
.isWorkingCopyRoot(file
), info
.getType()));
829 private class SvnWorkingCopyChecker
{
830 private List
<WCInfo
> myAllWcInfos
;
832 public boolean upgradeNeeded() {
833 myAllWcInfos
= getAllWcInfos();
834 for (WCInfo info
: myAllWcInfos
) {
835 if (! WorkingCopyFormat
.ONE_DOT_SIX
.equals(info
.getFormat())) {
842 public void doUpgrade() {
843 ApplicationManager
.getApplication().invokeLater(new Runnable() {
845 final SvnFormatWorker formatWorker
= new SvnFormatWorker(myProject
, WorkingCopyFormat
.ONE_DOT_SIX
, myAllWcInfos
);
846 // additionally ask about working copies with roots above the project root
847 formatWorker
.checkForOutsideCopies();
848 if (formatWorker
.haveStuffToConvert()) {
849 ProgressManager
.getInstance().run(formatWorker
);
857 public RootsConvertor
getCustomConvertor() {
858 return getSvnFileUrlMapping();
862 public MergeProvider
getMergeProvider() {
863 if (myMergeProvider
== null) {
864 myMergeProvider
= new SvnMergeProvider(myProject
);
866 return myMergeProvider
;
870 public List
<AnAction
> getAdditionalActionsForLocalChange() {
871 return Arrays
.<AnAction
>asList(new ShowPropertiesDiffWithLocalAction());
874 private String
keyForVf(final VirtualFile vf
) {
879 public boolean allowsNestedRoots() {
880 return SvnConfiguration
.getInstance(myProject
).DETECT_NESTED_COPIES
;
884 public <S
> List
<S
> filterUniqueRoots(final List
<S
> in
, final Convertor
<S
, VirtualFile
> convertor
) {
885 if (in
.size() <= 1) return in
;
887 final List
<MyPair
<S
>> infos
= new ArrayList
<MyPair
<S
>>(in
.size());
888 final SvnFileUrlMappingImpl mapping
= (SvnFileUrlMappingImpl
) getSvnFileUrlMapping();
889 final List
<S
> notMatched
= new LinkedList
<S
>();
891 final VirtualFile vf
= convertor
.convert(s
);
893 final File ioFile
= new File(vf
.getPath());
894 SVNURL url
= mapping
.getUrlForFile(ioFile
);
896 url
= SvnUtil
.getUrl(ioFile
);
902 infos
.add(new MyPair
<S
>(vf
, url
.toString(), s
));
904 final List
<MyPair
<S
>> filtered
= new ArrayList
<MyPair
<S
>>(infos
.size());
905 ForNestedRootChecker
.filterOutSuperfluousChildren(this, infos
, filtered
);
907 final List
<S
> converted
= ObjectsConvertor
.convert(filtered
, new Convertor
<MyPair
<S
>, S
>() {
908 public S
convert(final MyPair
<S
> o
) {
912 if (! notMatched
.isEmpty()) {
913 // potential bug is here: order is not kept. but seems it only occurs for cases where result is sorted after filtering so ok
914 converted
.addAll(notMatched
);
919 private static class MyPair
<T
> implements RootUrlPair
{
920 private final VirtualFile myFile
;
921 private final String myUrl
;
922 private final T mySrc
;
924 private MyPair(VirtualFile file
, String url
, T src
) {
934 public VirtualFile
getVirtualFile() {
938 public String
getUrl() {
943 private static class MyFrameStateListener
implements FrameStateListener
{
944 private final ChangeListManager myClManager
;
945 private final VcsDirtyScopeManager myDirtyScopeManager
;
947 private MyFrameStateListener(ChangeListManager clManager
, VcsDirtyScopeManager dirtyScopeManager
) {
948 myClManager
= clManager
;
949 myDirtyScopeManager
= dirtyScopeManager
;
952 public void onFrameDeactivated() {
955 public void onFrameActivated() {
956 final List
<VirtualFile
> folders
= ((ChangeListManagerImpl
)myClManager
).getLockedFolders();
957 if (! folders
.isEmpty()) {
958 myDirtyScopeManager
.filesDirty(null, folders
);
963 public static VcsKey
getKey() {
968 public boolean isVcsBackgroundOperationsAllowed(VirtualFile root
) {
969 return ThreeState
.YES
.equals(myAuthNotifier
.isAuthenticatedFor(root
));
973 public CommittedChangeList
getRevisionChanges(VcsFileRevision revision
, VirtualFile file
) throws VcsException
{
974 return ShowAllSubmittedFilesAction
.loadRevisions(getProject(), (SvnFileRevision
)revision
, file
);