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