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 SvnHttpAuthMethodsDefaultChecker
.check();
145 //noinspection UseOfArchaicSystemPropertyAccessors
146 final JavaSVNDebugLogger logger
= new JavaSVNDebugLogger(Boolean
.getBoolean(LOG_PARAMETER_NAME
), LOG
);
147 SVNDebugLog
.setDefaultLog(logger
);
148 SVNAdminAreaFactory
.setSelector(new SvnFormatSelector());
150 DAVRepositoryFactory
.setup();
151 SVNRepositoryFactoryImpl
.setup();
152 FSRepositoryFactory
.setup();
154 // non-optimized writing is fast enough on Linux/MacOS, and somewhat more reliable
155 if (SystemInfo
.isWindows
) {
156 SVNAdminArea14
.setOptimizedWritingEnabled(true);
159 if (! SVNJNAUtil
.isJNAPresent()) {
160 LOG
.warn("JNA is not found by svnkit library");
164 private static Boolean
booleanProperty(final String systemParameterName
) {
165 return Boolean
.valueOf(System
.getProperty(systemParameterName
));
168 public SvnVcs(final Project project
, MessageBus bus
, SvnConfiguration svnConfiguration
, final ChangeListManager changeListManager
,
169 final VcsDirtyScopeManager vcsDirtyScopeManager
) {
170 super(project
, VCS_NAME
);
172 myRootsToWorkingCopies
= new RootsToWorkingCopies(myProject
);
173 myConfiguration
= svnConfiguration
;
174 myAuthNotifier
= new SvnAuthenticationNotifier(this);
176 dumpFileStatus(FileStatus
.ADDED
);
177 dumpFileStatus(FileStatus
.DELETED
);
178 dumpFileStatus(FileStatus
.MERGE
);
179 dumpFileStatus(FileStatus
.MODIFIED
);
180 dumpFileStatus(FileStatus
.NOT_CHANGED
);
181 dumpFileStatus(FileStatus
.UNKNOWN
);
183 dumpFileStatus(SvnFileStatus
.REPLACED
);
184 dumpFileStatus(SvnFileStatus
.EXTERNAL
);
185 dumpFileStatus(SvnFileStatus
.OBSTRUCTED
);
187 myEntriesFileListener
= new SvnEntriesFileListener(project
);
189 final ProjectLevelVcsManager vcsManager
= ProjectLevelVcsManager
.getInstance(project
);
190 myAddConfirmation
= vcsManager
.getStandardConfirmation(VcsConfiguration
.StandardConfirmation
.ADD
, this);
191 myDeleteConfirmation
= vcsManager
.getStandardConfirmation(VcsConfiguration
.StandardConfirmation
.REMOVE
, this);
192 myCheckoutOptions
= vcsManager
.getStandardOption(VcsConfiguration
.StandardOption
.CHECKOUT
, this);
194 if (myProject
.isDefault()) {
195 myChangeListListener
= null;
198 upgradeIfNeeded(bus
);
200 myChangeListListener
= new SvnChangelistListener(myProject
, createChangelistClient());
202 myVcsListener
= new VcsListener() {
203 public void directoryMappingChanged() {
204 invokeRefreshSvnRoots(true);
209 myFrameStateListener
= new MyFrameStateListener(changeListManager
, vcsDirtyScopeManager
);
212 public void postStartup() {
213 if (myProject
.isDefault()) return;
214 myCopiesRefreshManager
= new SvnCopiesRefreshManager(myProject
, (SvnFileUrlMappingImpl
) getSvnFileUrlMapping());
216 invokeRefreshSvnRoots(true);
219 public void invokeRefreshSvnRoots(final boolean asynchronous
) {
220 REFRESH_LOG
.debug("refresh: ", new Throwable());
221 if (myCopiesRefreshManager
!= null) {
223 myCopiesRefreshManager
.getCopiesRefresh().asynchRequest();
225 if (ApplicationManager
.getApplication().isDispatchThread()) {
226 ProgressManager
.getInstance().runProcessWithProgressSynchronously(new Runnable() {
228 myCopiesRefreshManager
.getCopiesRefresh().synchRequest();
230 }, SvnBundle
.message("refreshing.working.copies.roots.progress.text"), true, myProject
);
232 myCopiesRefreshManager
.getCopiesRefresh().synchRequest();
239 public boolean checkImmediateParentsBeforeCommit() {
243 private void upgradeIfNeeded(final MessageBus bus
) {
244 final MessageBusConnection connection
= bus
.connect();
245 connection
.subscribe(ChangeListManagerImpl
.LISTS_LOADED
, new LocalChangeListsLoadedListener() {
246 public void processLoadedLists(final List
<LocalChangeList
> lists
) {
247 SvnConfiguration
.SvnSupportOptions supportOptions
= null;
249 ChangeListManager
.getInstanceChecked(myProject
).setReadOnly(SvnChangeProvider
.ourDefaultListName
, true);
251 supportOptions
= myConfiguration
.getSupportOptions();
253 upgradeToRecentVersion(supportOptions
);
254 if (! supportOptions
.changeListsSynchronized()) {
255 processChangeLists(lists
);
258 catch (ProcessCanceledException e
) {
261 if (supportOptions
!= null) {
262 supportOptions
.upgrade();
266 connection
.disconnect();
271 public void processChangeLists(final List
<LocalChangeList
> lists
) {
272 final ProjectLevelVcsManager plVcsManager
= ProjectLevelVcsManager
.getInstanceChecked(myProject
);
273 plVcsManager
.startBackgroundVcsOperation();
275 final SVNChangelistClient client
= createChangelistClient();
276 for (LocalChangeList list
: lists
) {
277 if (! list
.isDefault()) {
278 final Collection
<Change
> changes
= list
.getChanges();
279 for (Change change
: changes
) {
280 correctListForRevision(plVcsManager
, change
.getBeforeRevision(), client
, list
.getName());
281 correctListForRevision(plVcsManager
, change
.getAfterRevision(), client
, list
.getName());
287 final Application appManager
= ApplicationManager
.getApplication();
288 if (appManager
.isDispatchThread()) {
289 appManager
.executeOnPooledThread(new Runnable() {
291 plVcsManager
.stopBackgroundVcsOperation();
295 plVcsManager
.stopBackgroundVcsOperation();
300 private void correctListForRevision(final ProjectLevelVcsManager plVcsManager
, final ContentRevision revision
,
301 final SVNChangelistClient client
, final String name
) {
302 if (revision
!= null) {
303 final FilePath path
= revision
.getFile();
304 final AbstractVcs vcs
= plVcsManager
.getVcsFor(path
);
305 if ((vcs
!= null) && VCS_NAME
.equals(vcs
.getName())) {
307 client
.doAddToChangelist(new File
[] {path
.getIOFile()}, SVNDepth
.EMPTY
, name
, null);
309 catch (SVNException e
) {
310 // left in default list
316 private final static String UPGRADE_SUBVERSION_FORMAT
= "Subversion";
318 private void upgradeToRecentVersion(final SvnConfiguration
.SvnSupportOptions supportOptions
) {
319 if (! supportOptions
.upgradeTo16Asked()) {
320 final SvnWorkingCopyChecker workingCopyChecker
= new SvnWorkingCopyChecker();
322 if (workingCopyChecker
.upgradeNeeded()) {
324 Notifications
.Bus
.notify(new Notification(UPGRADE_SUBVERSION_FORMAT
, SvnBundle
.message("upgrade.format.to16.question.title"),
325 "Old format Subversion working copies <a href=\"\">could be upgraded to version 1.6</a>.",
326 NotificationType
.INFORMATION
, new NotificationListener() {
327 public void hyperlinkUpdate(@NotNull Notification notification
, @NotNull HyperlinkEvent event
) {
328 final int upgradeAnswer
= Messages
.showYesNoDialog(SvnBundle
.message("upgrade.format.to16.question.text",
329 SvnBundle
.message("label.where.svn.format.can.be.changed.text", SvnBundle
.message("action.show.svn.map.text.reference"))),
330 SvnBundle
.message("upgrade.format.to16.question.title"), Messages
.getWarningIcon());
331 if (DialogWrapper
.OK_EXIT_CODE
== upgradeAnswer
) {
332 workingCopyChecker
.doUpgrade();
335 notification
.expire();
343 public void activate() {
344 final ProjectLevelVcsManager vcsManager
= ProjectLevelVcsManager
.getInstance(myProject
);
345 if (! myProject
.isDefault()) {
346 ChangeListManager
.getInstance(myProject
).addChangeListListener(myChangeListListener
);
347 vcsManager
.addVcsListener(myVcsListener
);
350 SvnApplicationSettings
.getInstance().svnActivated();
351 VirtualFileManager
.getInstance().addVirtualFileListener(myEntriesFileListener
);
352 // this will initialize its inner listener for committed changes upload
353 LoadedRevisionsCache
.getInstance(myProject
);
354 FrameStateManager
.getInstance().addListener(myFrameStateListener
);
356 // do one time after project loaded
357 StartupManager
.getInstance(myProject
).runWhenProjectIsInitialized(new DumbAwareRunnable() {
361 // for IDEA, it takes 2 minutes - and anyway this can be done in background, no sence...
362 // once it could be mistaken about copies for 2 minutes on start...
364 /*if (! myMapping.getAllWcInfos().isEmpty()) {
365 invokeRefreshSvnRoots();
368 ProgressManager.getInstance().runProcessWithProgressSynchronously(new Runnable() {
370 myCopiesRefreshManager.getCopiesRefresh().ensureInit();
372 }, SvnBundle.message("refreshing.working.copies.roots.progress.text"), true, myProject);*/
376 vcsManager
.addVcsListener(myRootsToWorkingCopies
);
379 public RootsToWorkingCopies
getRootsToWorkingCopies() {
380 return myRootsToWorkingCopies
;
383 public SvnAuthenticationNotifier
getAuthNotifier() {
384 return myAuthNotifier
;
388 public void deactivate() {
389 FrameStateManager
.getInstance().removeListener(myFrameStateListener
);
391 final ProjectLevelVcsManager vcsManager
= ProjectLevelVcsManager
.getInstance(myProject
);
392 if (myVcsListener
!= null) {
393 vcsManager
.removeVcsListener(myVcsListener
);
396 VirtualFileManager
.getInstance().removeVirtualFileListener(myEntriesFileListener
);
397 SvnApplicationSettings
.getInstance().svnDeactivated();
398 new DefaultSVNRepositoryPool(null, null).shutdownConnections(true);
399 if (myCommittedChangesProvider
!= null) {
400 myCommittedChangesProvider
.deactivate();
402 if (myChangeListListener
!= null && (! myProject
.isDefault())) {
403 ChangeListManager
.getInstance(myProject
).removeChangeListListener(myChangeListListener
);
405 vcsManager
.removeVcsListener(myRootsToWorkingCopies
);
406 myRootsToWorkingCopies
.clear();
407 myAuthNotifier
.clear();
410 public VcsShowConfirmationOption
getAddConfirmation() {
411 return myAddConfirmation
;
414 public VcsShowConfirmationOption
getDeleteConfirmation() {
415 return myDeleteConfirmation
;
418 public VcsShowSettingOption
getCheckoutOptions() {
419 return myCheckoutOptions
;
422 public EditFileProvider
getEditFileProvider() {
423 if (myEditFilesProvider
== null) {
424 myEditFilesProvider
= new SvnEditFileProvider(this);
426 return myEditFilesProvider
;
430 public ChangeProvider
getChangeProvider() {
431 if (myChangeProvider
== null) {
432 myChangeProvider
= new SvnChangeProvider(this);
434 return myChangeProvider
;
437 public SVNRepository
createRepository(String url
) throws SVNException
{
438 SVNRepository repos
= SVNRepositoryFactory
.create(SVNURL
.parseURIEncoded(url
));
439 repos
.setAuthenticationManager(myConfiguration
.getAuthenticationManager(this));
440 repos
.setTunnelProvider(myConfiguration
.getOptions(myProject
));
444 public SVNRepository
createRepository(SVNURL url
) throws SVNException
{
445 SVNRepository repos
= SVNRepositoryFactory
.create(url
);
446 repos
.setAuthenticationManager(myConfiguration
.getAuthenticationManager(this));
447 repos
.setTunnelProvider(myConfiguration
.getOptions(myProject
));
451 public SVNUpdateClient
createUpdateClient() {
452 return new SVNUpdateClient(myConfiguration
.getAuthenticationManager(this), myConfiguration
.getOptions(myProject
));
455 public SVNStatusClient
createStatusClient() {
456 return new SVNStatusClient(myConfiguration
.getAuthenticationManager(this), myConfiguration
.getOptions(myProject
));
459 public SVNWCClient
createWCClient() {
460 return new SVNWCClient(myConfiguration
.getAuthenticationManager(this), myConfiguration
.getOptions(myProject
));
463 public SVNCopyClient
createCopyClient() {
464 return new SVNCopyClient(myConfiguration
.getAuthenticationManager(this), myConfiguration
.getOptions(myProject
));
467 public SVNMoveClient
createMoveClient() {
468 return new SVNMoveClient(myConfiguration
.getAuthenticationManager(this), myConfiguration
.getOptions(myProject
));
471 public SVNLogClient
createLogClient() {
472 return new SVNLogClient(myConfiguration
.getAuthenticationManager(this), myConfiguration
.getOptions(myProject
));
475 public SVNCommitClient
createCommitClient() {
476 return new SVNCommitClient(myConfiguration
.getAuthenticationManager(this), myConfiguration
.getOptions(myProject
));
479 public SVNDiffClient
createDiffClient() {
480 return new SVNDiffClient(myConfiguration
.getAuthenticationManager(this), myConfiguration
.getOptions(myProject
));
483 public SVNChangelistClient
createChangelistClient() {
484 return new SVNChangelistClient(myConfiguration
.getAuthenticationManager(this), myConfiguration
.getOptions(myProject
));
487 public SVNWCAccess
createWCAccess() {
488 final SVNWCAccess access
= SVNWCAccess
.newInstance(null);
489 access
.setOptions(myConfiguration
.getOptions(myProject
));
493 public ISVNOptions
getSvnOptions() {
494 return myConfiguration
.getOptions(myProject
);
497 public ISVNAuthenticationManager
getSvnAuthenticationManager() {
498 return myConfiguration
.getAuthenticationManager(this);
501 void dumpFileStatus(FileStatus fs
) {
502 if (LOG
.isDebugEnabled()) {
503 LOG
.debug("FileStatus:" + fs
.getText() + " " + fs
.getColor() + " " + " " + fs
.getClass().getName());
507 public UpdateEnvironment
getIntegrateEnvironment() {
508 if (mySvnIntegrateEnvironment
== null) {
509 mySvnIntegrateEnvironment
= new SvnIntegrateEnvironment(this);
511 return mySvnIntegrateEnvironment
;
514 public UpdateEnvironment
getUpdateEnvironment() {
515 if (mySvnUpdateEnvironment
== null) {
516 mySvnUpdateEnvironment
= new SvnUpdateEnvironment(this);
518 return mySvnUpdateEnvironment
;
521 public String
getDisplayName() {
522 LOG
.debug("getDisplayName");
526 public Configurable
getConfigurable() {
527 LOG
.debug("createConfigurable");
528 return new SvnConfigurable(myProject
);
532 public SvnConfiguration
getSvnConfiguration() {
533 return myConfiguration
;
536 public static SvnVcs
getInstance(Project project
) {
537 return (SvnVcs
) ProjectLevelVcsManager
.getInstance(project
).findVcsByName(VCS_NAME
);
541 public CheckinEnvironment
getCheckinEnvironment() {
542 if (myCheckinEnvironment
== null) {
543 myCheckinEnvironment
= new SvnCheckinEnvironment(this);
545 return myCheckinEnvironment
;
549 public RollbackEnvironment
getRollbackEnvironment() {
550 if (myRollbackEnvironment
== null) {
551 myRollbackEnvironment
= new SvnRollbackEnvironment(this);
553 return myRollbackEnvironment
;
556 public VcsHistoryProvider
getVcsHistoryProvider() {
557 // no heavy state, but it would be useful to have place to keep state in -> do not reuse instance
558 return new SvnHistoryProvider(this);
561 public VcsHistoryProvider
getVcsBlockHistoryProvider() {
562 return getVcsHistoryProvider();
565 public AnnotationProvider
getAnnotationProvider() {
566 if (myAnnotationProvider
== null) {
567 myAnnotationProvider
= new SvnAnnotationProvider(this);
569 return myAnnotationProvider
;
572 public SvnEntriesFileListener
getSvnEntriesFileListener() {
573 return myEntriesFileListener
;
576 public DiffProvider
getDiffProvider() {
577 if (mySvnDiffProvider
== null) {
578 mySvnDiffProvider
= new SvnDiffProvider(this);
580 return mySvnDiffProvider
;
583 private Trinity
<Long
, Long
, Long
> getTimestampForPropertiesChange(final File ioFile
, final boolean isDir
) {
584 final File dir
= isDir ? ioFile
: ioFile
.getParentFile();
585 final String relPath
= SVNAdminUtil
.getPropPath(ioFile
.getName(), isDir ? SVNNodeKind
.DIR
: SVNNodeKind
.FILE
, false);
586 final String relPathBase
= SVNAdminUtil
.getPropBasePath(ioFile
.getName(), isDir ? SVNNodeKind
.DIR
: SVNNodeKind
.FILE
, false);
587 final String relPathRevert
= SVNAdminUtil
.getPropRevertPath(ioFile
.getName(), isDir ? SVNNodeKind
.DIR
: SVNNodeKind
.FILE
, false);
588 return new Trinity
<Long
, Long
, Long
>(new File(dir
, relPath
).lastModified(), new File(dir
, relPathBase
).lastModified(),
589 new File(dir
, relPathRevert
).lastModified());
592 private boolean trinitiesEqual(final Trinity
<Long
, Long
, Long
> t1
, final Trinity
<Long
, Long
, Long
> t2
) {
593 if (t2
.first
== 0 && t2
.second
== 0 && t2
.third
== 0) return false;
594 return t1
.equals(t2
);
598 public SVNPropertyValue
getPropertyWithCaching(final VirtualFile file
, final String propName
) throws SVNException
{
599 Map
<String
, Pair
<SVNPropertyValue
, Trinity
<Long
, Long
, Long
>>> cachedMap
= myPropertyCache
.get(keyForVf(file
));
600 final Pair
<SVNPropertyValue
, Trinity
<Long
, Long
, Long
>> cachedValue
= (cachedMap
== null) ?
null : cachedMap
.get(propName
);
602 final File ioFile
= new File(file
.getPath());
603 final Trinity
<Long
, Long
, Long
> tsTrinity
= getTimestampForPropertiesChange(ioFile
, file
.isDirectory());
605 if (cachedValue
!= null) {
606 // zero means that a file was not found
607 if (trinitiesEqual(cachedValue
.getSecond(), tsTrinity
)) {
608 return cachedValue
.getFirst();
612 final SVNPropertyData value
= createWCClient().doGetProperty(ioFile
, propName
, SVNRevision
.WORKING
, SVNRevision
.WORKING
);
613 final SVNPropertyValue propValue
= (value
== null) ?
null : value
.getValue();
615 if (cachedMap
== null) {
616 cachedMap
= new HashMap
<String
, Pair
<SVNPropertyValue
, Trinity
<Long
, Long
, Long
>>>();
617 myPropertyCache
.put(keyForVf(file
), cachedMap
);
620 cachedMap
.put(propName
, new Pair
<SVNPropertyValue
, Trinity
<Long
, Long
, Long
>>(propValue
, tsTrinity
));
625 public boolean fileExistsInVcs(FilePath path
) {
626 File file
= path
.getIOFile();
629 status
= createStatusClient().doStatus(file
, false);
630 if (status
!= null) {
631 final SVNStatusType statusType
= status
.getContentsStatus();
632 if (statusType
== SVNStatusType
.STATUS_ADDED
) {
633 return status
.isCopied();
635 return !(status
.getContentsStatus() == SVNStatusType
.STATUS_UNVERSIONED
||
636 status
.getContentsStatus() == SVNStatusType
.STATUS_IGNORED
||
637 status
.getContentsStatus() == SVNStatusType
.STATUS_OBSTRUCTED
);
640 catch (SVNException e
) {
646 public boolean fileIsUnderVcs(FilePath path
) {
647 final ChangeListManager clManager
= ChangeListManager
.getInstance(myProject
);
648 return (! SvnStatusUtil
.isIgnoredInAnySense(clManager
, path
.getVirtualFile())) && (! clManager
.isUnversioned(path
.getVirtualFile()));
651 private static File
getEntriesFile(File file
) {
652 return file
.isDirectory() ?
new File(file
, pathToEntries
) : new File(file
.getParentFile(), pathToEntries
);
655 private static File
getDirPropsFile(File file
) {
656 return new File(file
, pathToDirProps
);
660 public SVNInfo
getInfo(final VirtualFile file
) {
662 SVNWCClient wcClient
= new SVNWCClient(getSvnAuthenticationManager(), getSvnOptions());
663 SVNInfo info
= wcClient
.doInfo(new File(file
.getPath()), SVNRevision
.WORKING
);
664 if (info
== null || info
.getRepositoryRootURL() == null) {
665 info
= wcClient
.doInfo(new File(file
.getPath()), SVNRevision
.HEAD
);
669 catch (SVNException e
) {
674 public static class SVNStatusHolder
{
676 private final SVNStatus myValue
;
677 private final long myEntriesTimestamp
;
678 private final long myFileTimestamp
;
679 private final boolean myIsLocked
;
681 public SVNStatusHolder(long entriesStamp
, long fileStamp
, SVNStatus value
) {
683 myEntriesTimestamp
= entriesStamp
;
684 myFileTimestamp
= fileStamp
;
685 myIsLocked
= value
!= null && value
.isLocked();
688 public long getEntriesTimestamp() {
689 return myEntriesTimestamp
;
692 public long getFileTimestamp() {
693 return myFileTimestamp
;
696 public boolean isLocked() {
700 public SVNStatus
getStatus() {
705 public static class SVNInfoHolder
{
707 private final SVNInfo myValue
;
708 private final long myEntriesTimestamp
;
709 private final long myFileTimestamp
;
711 public SVNInfoHolder(long entriesStamp
, long fileStamp
, SVNInfo value
) {
713 myEntriesTimestamp
= entriesStamp
;
714 myFileTimestamp
= fileStamp
;
717 public long getEntriesTimestamp() {
718 return myEntriesTimestamp
;
721 public long getFileTimestamp() {
722 return myFileTimestamp
;
725 public SVNInfo
getInfo() {
730 private static class JavaSVNDebugLogger
extends SVNDebugLogAdapter
{
731 private final boolean myLoggingEnabled
;
732 private final Logger myLog
;
733 @NonNls public static final String TRACE_LOG_PARAMETER_NAME
= "javasvn.log.trace";
735 public JavaSVNDebugLogger(boolean loggingEnabled
, Logger log
) {
736 myLoggingEnabled
= loggingEnabled
;
740 public void log(final SVNLogType logType
, final Throwable th
, final Level logLevel
) {
741 if (myLoggingEnabled
) {
746 public void log(final SVNLogType logType
, final String message
, final Level logLevel
) {
747 if (myLoggingEnabled
) {
752 public void log(final SVNLogType logType
, final String message
, final byte[] data
) {
753 if (myLoggingEnabled
) {
756 myLog
.info(message
+ "\n" + new String(data
, "UTF-8"));
758 catch (UnsupportedEncodingException e
) {
759 myLog
.info(message
+ "\n" + new String(data
));
768 public FileStatus
[] getProvidedStatuses() {
769 return new FileStatus
[]{SvnFileStatus
.EXTERNAL
,
770 SvnFileStatus
.OBSTRUCTED
,
771 SvnFileStatus
.REPLACED
};
776 public CommittedChangesProvider
<SvnChangeList
, ChangeBrowserSettings
> getCommittedChangesProvider() {
777 if (myCommittedChangesProvider
== null) {
778 myCommittedChangesProvider
= new SvnCommittedChangesProvider(myProject
);
780 return myCommittedChangesProvider
;
785 public VcsRevisionNumber
parseRevisionNumber(final String revisionNumberString
) {
786 final SVNRevision revision
= SVNRevision
.parse(revisionNumberString
);
787 if (revision
.equals(SVNRevision
.UNDEFINED
)) {
790 return new SvnRevisionNumber(revision
);
794 public String
getRevisionPattern() {
795 return ourIntegerPattern
;
799 public boolean isVersionedDirectory(final VirtualFile dir
) {
800 return SvnUtil
.seemsLikeVersionedDir(dir
);
804 public SvnFileUrlMapping
getSvnFileUrlMapping() {
805 if (myMapping
== null) {
806 myMapping
= SvnFileUrlMappingImpl
.getInstance(myProject
);
812 * Returns real working copies roots - if there is <Project Root> -> Subversion setting,
813 * and there is one working copy, will return one root
815 public List
<WCInfo
> getAllWcInfos() {
816 final SvnFileUrlMapping urlMapping
= getSvnFileUrlMapping();
818 final List
<RootUrlInfo
> infoList
= urlMapping
.getAllWcInfos();
819 final List
<WCInfo
> infos
= new ArrayList
<WCInfo
>();
820 for (RootUrlInfo info
: infoList
) {
821 final File file
= info
.getIoFile();
822 infos
.add(new WCInfo(file
.getAbsolutePath(), info
.getAbsoluteUrlAsUrl(),
823 SvnFormatSelector
.getWorkingCopyFormat(file
), info
.getRepositoryUrl(), SvnUtil
.isWorkingCopyRoot(file
), info
.getType()));
828 private class SvnWorkingCopyChecker
{
829 private List
<WCInfo
> myAllWcInfos
;
831 public boolean upgradeNeeded() {
832 myAllWcInfos
= getAllWcInfos();
833 for (WCInfo info
: myAllWcInfos
) {
834 if (! WorkingCopyFormat
.ONE_DOT_SIX
.equals(info
.getFormat())) {
841 public void doUpgrade() {
842 ApplicationManager
.getApplication().invokeLater(new Runnable() {
844 final SvnFormatWorker formatWorker
= new SvnFormatWorker(myProject
, WorkingCopyFormat
.ONE_DOT_SIX
, myAllWcInfos
);
845 // additionally ask about working copies with roots above the project root
846 formatWorker
.checkForOutsideCopies();
847 if (formatWorker
.haveStuffToConvert()) {
848 ProgressManager
.getInstance().run(formatWorker
);
856 public RootsConvertor
getCustomConvertor() {
857 return getSvnFileUrlMapping();
861 public MergeProvider
getMergeProvider() {
862 if (myMergeProvider
== null) {
863 myMergeProvider
= new SvnMergeProvider(myProject
);
865 return myMergeProvider
;
869 public List
<AnAction
> getAdditionalActionsForLocalChange() {
870 return Arrays
.<AnAction
>asList(new ShowPropertiesDiffWithLocalAction());
873 private String
keyForVf(final VirtualFile vf
) {
878 public boolean allowsNestedRoots() {
879 return SvnConfiguration
.getInstance(myProject
).DETECT_NESTED_COPIES
;
883 public <S
> List
<S
> filterUniqueRoots(final List
<S
> in
, final Convertor
<S
, VirtualFile
> convertor
) {
884 if (in
.size() <= 1) return in
;
886 final List
<MyPair
<S
>> infos
= new ArrayList
<MyPair
<S
>>(in
.size());
887 final SvnFileUrlMappingImpl mapping
= (SvnFileUrlMappingImpl
) getSvnFileUrlMapping();
888 final List
<S
> notMatched
= new LinkedList
<S
>();
890 final VirtualFile vf
= convertor
.convert(s
);
892 final File ioFile
= new File(vf
.getPath());
893 SVNURL url
= mapping
.getUrlForFile(ioFile
);
895 url
= SvnUtil
.getUrl(ioFile
);
901 infos
.add(new MyPair
<S
>(vf
, url
.toString(), s
));
903 final List
<MyPair
<S
>> filtered
= new ArrayList
<MyPair
<S
>>(infos
.size());
904 ForNestedRootChecker
.filterOutSuperfluousChildren(this, infos
, filtered
);
906 final List
<S
> converted
= ObjectsConvertor
.convert(filtered
, new Convertor
<MyPair
<S
>, S
>() {
907 public S
convert(final MyPair
<S
> o
) {
911 if (! notMatched
.isEmpty()) {
912 // potential bug is here: order is not kept. but seems it only occurs for cases where result is sorted after filtering so ok
913 converted
.addAll(notMatched
);
918 private static class MyPair
<T
> implements RootUrlPair
{
919 private final VirtualFile myFile
;
920 private final String myUrl
;
921 private final T mySrc
;
923 private MyPair(VirtualFile file
, String url
, T src
) {
933 public VirtualFile
getVirtualFile() {
937 public String
getUrl() {
942 private static class MyFrameStateListener
implements FrameStateListener
{
943 private final ChangeListManager myClManager
;
944 private final VcsDirtyScopeManager myDirtyScopeManager
;
946 private MyFrameStateListener(ChangeListManager clManager
, VcsDirtyScopeManager dirtyScopeManager
) {
947 myClManager
= clManager
;
948 myDirtyScopeManager
= dirtyScopeManager
;
951 public void onFrameDeactivated() {
954 public void onFrameActivated() {
955 final List
<VirtualFile
> folders
= ((ChangeListManagerImpl
)myClManager
).getLockedFolders();
956 if (! folders
.isEmpty()) {
957 myDirtyScopeManager
.filesDirty(null, folders
);
962 public static VcsKey
getKey() {
967 public boolean isVcsBackgroundOperationsAllowed(VirtualFile root
) {
968 return ThreeState
.YES
.equals(myAuthNotifier
.isAuthenticatedFor(root
));
972 public CommittedChangeList
getRevisionChanges(VcsFileRevision revision
, VirtualFile file
) throws VcsException
{
973 return ShowAllSubmittedFilesAction
.loadRevisions(getProject(), (SvnFileRevision
)revision
, file
);