IDEA-51936 (IntelliJ hangs on synchronize)
[fedora-idea.git] / plugins / svn4idea / src / org / jetbrains / idea / svn / SvnVcs.java
blob0c33215249d5bed88f2177462586ec05e2e4c25d
1 /*
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;
94 import java.io.File;
95 import java.io.UnsupportedEncodingException;
96 import java.util.*;
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;
142 static {
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);
172 LOG.debug("ct");
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;
198 else {
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) {
223 if (asynchronous) {
224 myCopiesRefreshManager.getCopiesRefresh().asynchRequest();
225 } else {
226 if (ApplicationManager.getApplication().isDispatchThread()) {
227 ProgressManager.getInstance().runProcessWithProgressSynchronously(new Runnable() {
228 public void run() {
229 myCopiesRefreshManager.getCopiesRefresh().synchRequest();
231 }, SvnBundle.message("refreshing.working.copies.roots.progress.text"), true, myProject);
232 } else {
233 myCopiesRefreshManager.getCopiesRefresh().synchRequest();
239 @Override
240 public boolean checkImmediateParentsBeforeCommit() {
241 return true;
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;
249 try {
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) {
261 } finally {
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();
275 try {
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());
287 finally {
288 final Application appManager = ApplicationManager.getApplication();
289 if (appManager.isDispatchThread()) {
290 appManager.executeOnPooledThread(new Runnable() {
291 public void run() {
292 plVcsManager.stopBackgroundVcsOperation();
295 } else {
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())) {
307 try {
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();
338 }));
343 @Override
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() {
359 public void run() {
360 postStartup();
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();
367 return;
369 ProgressManager.getInstance().runProcessWithProgressSynchronously(new Runnable() {
370 public void run() {
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;
388 @Override
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;
430 @NotNull
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));
442 return repos;
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));
449 return repos;
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));
491 return access;
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");
524 return "Subversion";
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);
541 @NotNull
542 public CheckinEnvironment getCheckinEnvironment() {
543 if (myCheckinEnvironment == null) {
544 myCheckinEnvironment = new SvnCheckinEnvironment(this);
546 return myCheckinEnvironment;
549 @NotNull
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);
598 @Nullable
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));
623 return propValue;
626 public boolean fileExistsInVcs(FilePath path) {
627 File file = path.getIOFile();
628 SVNStatus status;
629 try {
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) {
644 return false;
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);
660 @Nullable
661 public SVNInfo getInfo(final VirtualFile file) {
662 try {
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);
668 return info;
670 catch (SVNException e) {
671 return null;
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) {
683 myValue = 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() {
698 return myIsLocked;
701 public SVNStatus getStatus() {
702 return myValue;
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) {
713 myValue = 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() {
727 return myValue;
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;
738 myLog = log;
741 public void log(final SVNLogType logType, final Throwable th, final Level logLevel) {
742 if (myLoggingEnabled) {
743 myLog.info(th);
747 public void log(final SVNLogType logType, final String message, final Level logLevel) {
748 if (myLoggingEnabled) {
749 myLog.info(message);
753 public void log(final SVNLogType logType, final String message, final byte[] data) {
754 if (myLoggingEnabled) {
755 if (data != null) {
756 try {
757 myLog.info(message + "\n" + new String(data, "UTF-8"));
759 catch (UnsupportedEncodingException e) {
760 myLog.info(message + "\n" + new String(data));
762 } else {
763 myLog.info(message);
769 public FileStatus[] getProvidedStatuses() {
770 return new FileStatus[]{SvnFileStatus.EXTERNAL,
771 SvnFileStatus.OBSTRUCTED,
772 SvnFileStatus.REPLACED};
776 @Override @NotNull
777 public CommittedChangesProvider<SvnChangeList, ChangeBrowserSettings> getCommittedChangesProvider() {
778 if (myCommittedChangesProvider == null) {
779 myCommittedChangesProvider = new SvnCommittedChangesProvider(myProject);
781 return myCommittedChangesProvider;
784 @Nullable
785 @Override
786 public VcsRevisionNumber parseRevisionNumber(final String revisionNumberString) {
787 final SVNRevision revision = SVNRevision.parse(revisionNumberString);
788 if (revision.equals(SVNRevision.UNDEFINED)) {
789 return null;
791 return new SvnRevisionNumber(revision);
794 @Override
795 public String getRevisionPattern() {
796 return ourIntegerPattern;
799 @Override
800 public boolean isVersionedDirectory(final VirtualFile dir) {
801 return SvnUtil.seemsLikeVersionedDir(dir);
804 @NotNull
805 public SvnFileUrlMapping getSvnFileUrlMapping() {
806 if (myMapping == null) {
807 myMapping = SvnFileUrlMappingImpl.getInstance(myProject);
809 return myMapping;
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()));
826 return infos;
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())) {
836 return true;
839 return false;
842 public void doUpgrade() {
843 ApplicationManager.getApplication().invokeLater(new Runnable() {
844 public void run() {
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);
856 @Override
857 public RootsConvertor getCustomConvertor() {
858 return getSvnFileUrlMapping();
861 @Override
862 public MergeProvider getMergeProvider() {
863 if (myMergeProvider == null) {
864 myMergeProvider = new SvnMergeProvider(myProject);
866 return myMergeProvider;
869 @Override
870 public List<AnAction> getAdditionalActionsForLocalChange() {
871 return Arrays.<AnAction>asList(new ShowPropertiesDiffWithLocalAction());
874 private String keyForVf(final VirtualFile vf) {
875 return vf.getUrl();
878 @Override
879 public boolean allowsNestedRoots() {
880 return SvnConfiguration.getInstance(myProject).DETECT_NESTED_COPIES;
883 @Override
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>();
890 for (S s : in) {
891 final VirtualFile vf = convertor.convert(s);
893 final File ioFile = new File(vf.getPath());
894 SVNURL url = mapping.getUrlForFile(ioFile);
895 if (url == null) {
896 url = SvnUtil.getUrl(ioFile);
897 if (url == null) {
898 notMatched.add(s);
899 continue;
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) {
909 return o.getSrc();
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);
916 return converted;
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) {
925 myFile = file;
926 myUrl = url;
927 mySrc = src;
930 public T getSrc() {
931 return mySrc;
934 public VirtualFile getVirtualFile() {
935 return myFile;
938 public String getUrl() {
939 return myUrl;
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() {
964 return ourKey;
967 @Override
968 public boolean isVcsBackgroundOperationsAllowed(VirtualFile root) {
969 return ThreeState.YES.equals(myAuthNotifier.isAuthenticatedFor(root));
972 @Override
973 public CommittedChangeList getRevisionChanges(VcsFileRevision revision, VirtualFile file) throws VcsException {
974 return ShowAllSubmittedFilesAction.loadRevisions(getProject(), (SvnFileRevision)revision, file);