586a55341f620ba6b0f5d9f27e7bfc9fe5016138
[fedora-idea.git] / plugins / svn4idea / src / org / jetbrains / idea / svn / SvnVcs.java
blob586a55341f620ba6b0f5d9f27e7bfc9fe5016138
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 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);
171 LOG.debug("ct");
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;
197 else {
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) {
222 if (asynchronous) {
223 myCopiesRefreshManager.getCopiesRefresh().asynchRequest();
224 } else {
225 if (ApplicationManager.getApplication().isDispatchThread()) {
226 ProgressManager.getInstance().runProcessWithProgressSynchronously(new Runnable() {
227 public void run() {
228 myCopiesRefreshManager.getCopiesRefresh().synchRequest();
230 }, SvnBundle.message("refreshing.working.copies.roots.progress.text"), true, myProject);
231 } else {
232 myCopiesRefreshManager.getCopiesRefresh().synchRequest();
238 @Override
239 public boolean checkImmediateParentsBeforeCommit() {
240 return true;
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;
248 try {
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) {
260 } finally {
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();
274 try {
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());
286 finally {
287 final Application appManager = ApplicationManager.getApplication();
288 if (appManager.isDispatchThread()) {
289 appManager.executeOnPooledThread(new Runnable() {
290 public void run() {
291 plVcsManager.stopBackgroundVcsOperation();
294 } else {
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())) {
306 try {
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();
337 }));
342 @Override
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() {
358 public void run() {
359 postStartup();
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();
366 return;
368 ProgressManager.getInstance().runProcessWithProgressSynchronously(new Runnable() {
369 public void run() {
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;
387 @Override
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;
429 @NotNull
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));
441 return repos;
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));
448 return repos;
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));
490 return access;
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");
523 return "Subversion";
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);
540 @NotNull
541 public CheckinEnvironment getCheckinEnvironment() {
542 if (myCheckinEnvironment == null) {
543 myCheckinEnvironment = new SvnCheckinEnvironment(this);
545 return myCheckinEnvironment;
548 @NotNull
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);
597 @Nullable
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));
622 return propValue;
625 public boolean fileExistsInVcs(FilePath path) {
626 File file = path.getIOFile();
627 SVNStatus status;
628 try {
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) {
643 return false;
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);
659 @Nullable
660 public SVNInfo getInfo(final VirtualFile file) {
661 try {
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);
667 return info;
669 catch (SVNException e) {
670 return null;
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) {
682 myValue = 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() {
697 return myIsLocked;
700 public SVNStatus getStatus() {
701 return myValue;
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) {
712 myValue = 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() {
726 return myValue;
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;
737 myLog = log;
740 public void log(final SVNLogType logType, final Throwable th, final Level logLevel) {
741 if (myLoggingEnabled) {
742 myLog.info(th);
746 public void log(final SVNLogType logType, final String message, final Level logLevel) {
747 if (myLoggingEnabled) {
748 myLog.info(message);
752 public void log(final SVNLogType logType, final String message, final byte[] data) {
753 if (myLoggingEnabled) {
754 if (data != null) {
755 try {
756 myLog.info(message + "\n" + new String(data, "UTF-8"));
758 catch (UnsupportedEncodingException e) {
759 myLog.info(message + "\n" + new String(data));
761 } else {
762 myLog.info(message);
768 public FileStatus[] getProvidedStatuses() {
769 return new FileStatus[]{SvnFileStatus.EXTERNAL,
770 SvnFileStatus.OBSTRUCTED,
771 SvnFileStatus.REPLACED};
775 @Override @NotNull
776 public CommittedChangesProvider<SvnChangeList, ChangeBrowserSettings> getCommittedChangesProvider() {
777 if (myCommittedChangesProvider == null) {
778 myCommittedChangesProvider = new SvnCommittedChangesProvider(myProject);
780 return myCommittedChangesProvider;
783 @Nullable
784 @Override
785 public VcsRevisionNumber parseRevisionNumber(final String revisionNumberString) {
786 final SVNRevision revision = SVNRevision.parse(revisionNumberString);
787 if (revision.equals(SVNRevision.UNDEFINED)) {
788 return null;
790 return new SvnRevisionNumber(revision);
793 @Override
794 public String getRevisionPattern() {
795 return ourIntegerPattern;
798 @Override
799 public boolean isVersionedDirectory(final VirtualFile dir) {
800 return SvnUtil.seemsLikeVersionedDir(dir);
803 @NotNull
804 public SvnFileUrlMapping getSvnFileUrlMapping() {
805 if (myMapping == null) {
806 myMapping = SvnFileUrlMappingImpl.getInstance(myProject);
808 return myMapping;
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()));
825 return infos;
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())) {
835 return true;
838 return false;
841 public void doUpgrade() {
842 ApplicationManager.getApplication().invokeLater(new Runnable() {
843 public void run() {
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);
855 @Override
856 public RootsConvertor getCustomConvertor() {
857 return getSvnFileUrlMapping();
860 @Override
861 public MergeProvider getMergeProvider() {
862 if (myMergeProvider == null) {
863 myMergeProvider = new SvnMergeProvider(myProject);
865 return myMergeProvider;
868 @Override
869 public List<AnAction> getAdditionalActionsForLocalChange() {
870 return Arrays.<AnAction>asList(new ShowPropertiesDiffWithLocalAction());
873 private String keyForVf(final VirtualFile vf) {
874 return vf.getUrl();
877 @Override
878 public boolean allowsNestedRoots() {
879 return SvnConfiguration.getInstance(myProject).DETECT_NESTED_COPIES;
882 @Override
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>();
889 for (S s : in) {
890 final VirtualFile vf = convertor.convert(s);
892 final File ioFile = new File(vf.getPath());
893 SVNURL url = mapping.getUrlForFile(ioFile);
894 if (url == null) {
895 url = SvnUtil.getUrl(ioFile);
896 if (url == null) {
897 notMatched.add(s);
898 continue;
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) {
908 return o.getSrc();
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);
915 return converted;
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) {
924 myFile = file;
925 myUrl = url;
926 mySrc = src;
929 public T getSrc() {
930 return mySrc;
933 public VirtualFile getVirtualFile() {
934 return myFile;
937 public String getUrl() {
938 return myUrl;
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() {
963 return ourKey;
966 @Override
967 public boolean isVcsBackgroundOperationsAllowed(VirtualFile root) {
968 return ThreeState.YES.equals(myAuthNotifier.isAuthenticatedFor(root));
971 @Override
972 public CommittedChangeList getRevisionChanges(VcsFileRevision revision, VirtualFile file) throws VcsException {
973 return ShowAllSubmittedFilesAction.loadRevisions(getProject(), (SvnFileRevision)revision, file);