IDEA-93096
[idea/community.git] / plugins / svn4idea / src / org / jetbrains / idea / svn / SvnVcs.java
1 /*
2  * Copyright 2000-2011 JetBrains s.r.o.
3  *
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
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
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.
15  */
16
17
18 package org.jetbrains.idea.svn;
19
20 import com.intellij.ide.FrameStateListener;
21 import com.intellij.ide.FrameStateManager;
22 import com.intellij.idea.RareLogger;
23 import com.intellij.notification.*;
24 import com.intellij.notification.impl.NotificationsConfigurationImpl;
25 import com.intellij.openapi.actionSystem.AnAction;
26 import com.intellij.openapi.application.Application;
27 import com.intellij.openapi.application.ApplicationManager;
28 import com.intellij.openapi.diagnostic.Logger;
29 import com.intellij.openapi.options.Configurable;
30 import com.intellij.openapi.progress.ProcessCanceledException;
31 import com.intellij.openapi.progress.ProgressManager;
32 import com.intellij.openapi.project.DumbAwareRunnable;
33 import com.intellij.openapi.project.Project;
34 import com.intellij.openapi.startup.StartupManager;
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.util.text.StringUtil;
39 import com.intellij.openapi.vcs.*;
40 import com.intellij.openapi.vcs.annotate.AnnotationProvider;
41 import com.intellij.openapi.vcs.changes.*;
42 import com.intellij.openapi.vcs.checkin.CheckinEnvironment;
43 import com.intellij.openapi.vcs.diff.DiffProvider;
44 import com.intellij.openapi.vcs.history.VcsHistoryProvider;
45 import com.intellij.openapi.vcs.history.VcsRevisionNumber;
46 import com.intellij.openapi.vcs.merge.MergeProvider;
47 import com.intellij.openapi.vcs.rollback.RollbackEnvironment;
48 import com.intellij.openapi.vcs.update.UpdateEnvironment;
49 import com.intellij.openapi.vcs.versionBrowser.ChangeBrowserSettings;
50 import com.intellij.openapi.vcs.versionBrowser.CommittedChangeList;
51 import com.intellij.openapi.vfs.LocalFileSystem;
52 import com.intellij.openapi.vfs.VirtualFile;
53 import com.intellij.openapi.vfs.VirtualFileManager;
54 import com.intellij.util.Processor;
55 import com.intellij.util.ThreeState;
56 import com.intellij.util.containers.Convertor;
57 import com.intellij.util.containers.SoftHashMap;
58 import com.intellij.util.messages.MessageBus;
59 import com.intellij.util.messages.MessageBusConnection;
60 import com.intellij.util.messages.Topic;
61 import org.jetbrains.annotations.NonNls;
62 import org.jetbrains.annotations.NotNull;
63 import org.jetbrains.annotations.Nullable;
64 import org.jetbrains.idea.svn.actions.CleanupWorker;
65 import org.jetbrains.idea.svn.actions.ShowPropertiesDiffWithLocalAction;
66 import org.jetbrains.idea.svn.actions.SvnMergeProvider;
67 import org.jetbrains.idea.svn.annotate.SvnAnnotationProvider;
68 import org.jetbrains.idea.svn.checkin.SvnCheckinEnvironment;
69 import org.jetbrains.idea.svn.commandLine.SvnExecutableChecker;
70 import org.jetbrains.idea.svn.dialogs.SvnBranchPointsCalculator;
71 import org.jetbrains.idea.svn.dialogs.WCInfo;
72 import org.jetbrains.idea.svn.history.LoadedRevisionsCache;
73 import org.jetbrains.idea.svn.history.SvnChangeList;
74 import org.jetbrains.idea.svn.history.SvnCommittedChangesProvider;
75 import org.jetbrains.idea.svn.history.SvnHistoryProvider;
76 import org.jetbrains.idea.svn.lowLevel.SvnIdeaRepositoryPoolManager;
77 import org.jetbrains.idea.svn.rollback.SvnRollbackEnvironment;
78 import org.jetbrains.idea.svn.update.SvnIntegrateEnvironment;
79 import org.jetbrains.idea.svn.update.SvnUpdateEnvironment;
80 import org.tmatesoft.sqljet.core.SqlJetErrorCode;
81 import org.tmatesoft.sqljet.core.SqlJetException;
82 import org.tmatesoft.svn.core.*;
83 import org.tmatesoft.svn.core.auth.ISVNAuthenticationManager;
84 import org.tmatesoft.svn.core.internal.io.dav.DAVRepositoryFactory;
85 import org.tmatesoft.svn.core.internal.io.fs.FSRepositoryFactory;
86 import org.tmatesoft.svn.core.internal.io.svn.SVNRepositoryFactoryImpl;
87 import org.tmatesoft.svn.core.internal.util.jna.SVNJNAUtil;
88 import org.tmatesoft.svn.core.internal.wc.SVNAdminUtil;
89 import org.tmatesoft.svn.core.internal.wc.admin.SVNAdminArea14;
90 import org.tmatesoft.svn.core.internal.wc.admin.SVNAdminAreaFactory;
91 import org.tmatesoft.svn.core.internal.wc.admin.SVNWCAccess;
92 import org.tmatesoft.svn.core.io.SVNRepository;
93 import org.tmatesoft.svn.core.io.SVNRepositoryFactory;
94 import org.tmatesoft.svn.core.wc.*;
95 import org.tmatesoft.svn.util.SVNDebugLog;
96 import org.tmatesoft.svn.util.SVNDebugLogAdapter;
97 import org.tmatesoft.svn.util.SVNLogType;
98
99 import javax.swing.*;
100 import java.io.File;
101 import java.io.UnsupportedEncodingException;
102 import java.util.*;
103 import java.util.logging.Level;
104
105 @SuppressWarnings({"IOResourceOpenedButNotSafelyClosed"})
106 public class SvnVcs extends AbstractVcs<CommittedChangeList> {
107   private static final String KEEP_CONNECTIONS_KEY = "svn.keep.connections";
108   private static final Logger REFRESH_LOG = Logger.getInstance("#svn_refresh");
109
110   private static final int ourLogUsualInterval = 20 * 1000;
111   private static final int ourLogRareInterval = 30 * 1000;
112
113   private static final Set<SVNErrorCode> ourLogRarely = new HashSet<SVNErrorCode>(
114     Arrays.asList(new SVNErrorCode[]{SVNErrorCode.WC_UNSUPPORTED_FORMAT, SVNErrorCode.WC_CORRUPT, SVNErrorCode.WC_CORRUPT_TEXT_BASE,
115       SVNErrorCode.WC_NOT_FILE, SVNErrorCode.WC_NOT_DIRECTORY, SVNErrorCode.WC_PATH_NOT_FOUND}));
116
117   private static final Logger LOG = wrapLogger(Logger.getInstance("org.jetbrains.idea.svn.SvnVcs"));
118   @NonNls public static final String VCS_NAME = "svn";
119   private static final VcsKey ourKey = createKey(VCS_NAME);
120   public static final Topic<Runnable> WC_CONVERTED = new Topic<Runnable>("WC_CONVERTED", Runnable.class);
121   private final Map<String, Map<String, Pair<SVNPropertyValue, Trinity<Long, Long, Long>>>> myPropertyCache =
122     new SoftHashMap<String, Map<String, Pair<SVNPropertyValue, Trinity<Long, Long, Long>>>>();
123
124   private SvnIdeaRepositoryPoolManager myPool;
125   private final SvnConfiguration myConfiguration;
126   private final SvnEntriesFileListener myEntriesFileListener;
127
128   private CheckinEnvironment myCheckinEnvironment;
129   private RollbackEnvironment myRollbackEnvironment;
130   private UpdateEnvironment mySvnUpdateEnvironment;
131   private UpdateEnvironment mySvnIntegrateEnvironment;
132   private VcsHistoryProvider mySvnHistoryProvider;
133   private AnnotationProvider myAnnotationProvider;
134   private DiffProvider mySvnDiffProvider;
135   private final VcsShowConfirmationOption myAddConfirmation;
136   private final VcsShowConfirmationOption myDeleteConfirmation;
137   private EditFileProvider myEditFilesProvider;
138   private SvnCommittedChangesProvider myCommittedChangesProvider;
139   private final VcsShowSettingOption myCheckoutOptions;
140
141   private ChangeProvider myChangeProvider;
142   private MergeProvider myMergeProvider;
143   private final WorkingCopiesContent myWorkingCopiesContent;
144
145   @NonNls public static final String LOG_PARAMETER_NAME = "javasvn.log";
146   @NonNls public static final String TRACE_NATIVE_CALLS = "javasvn.log.native";
147   public static final String pathToEntries = SvnUtil.SVN_ADMIN_DIR_NAME + File.separatorChar + SvnUtil.ENTRIES_FILE_NAME;
148   public static final String pathToDirProps = SvnUtil.SVN_ADMIN_DIR_NAME + File.separatorChar + SvnUtil.DIR_PROPS_FILE_NAME;
149   private final SvnChangelistListener myChangeListListener;
150
151   private SvnCopiesRefreshManager myCopiesRefreshManager;
152   private SvnFileUrlMappingImpl myMapping;
153   private final MyFrameStateListener myFrameStateListener;
154
155   public static final Topic<Runnable> ROOTS_RELOADED = new Topic<Runnable>("ROOTS_RELOADED", Runnable.class);
156   private VcsListener myVcsListener;
157
158   private SvnBranchPointsCalculator mySvnBranchPointsCalculator;
159
160   private final RootsToWorkingCopies myRootsToWorkingCopies;
161   private final SvnAuthenticationNotifier myAuthNotifier;
162   private static RareLogger.LogFilter[] ourLogFilters;
163   private final SvnLoadedBrachesStorage myLoadedBranchesStorage;
164
165   public static final String SVNKIT_HTTP_SSL_PROTOCOLS = "svnkit.http.sslProtocols";
166   private final SvnExecutableChecker myChecker;
167
168   public static final Processor<Exception> ourBusyExceptionProcessor = new Processor<Exception>() {
169     @Override
170     public boolean process(Exception e) {
171       if (e instanceof SVNException) {
172         final SVNErrorCode errorCode = ((SVNException)e).getErrorMessage().getErrorCode();
173         if (SVNErrorCode.WC_LOCKED.equals(errorCode)) {
174           return true;
175         } else if (SVNErrorCode.SQLITE_ERROR.equals(errorCode)) {
176           Throwable cause = ((SVNException)e).getErrorMessage().getCause();
177           if (cause instanceof SqlJetException) {
178             return SqlJetErrorCode.BUSY.equals(((SqlJetException)cause).getErrorCode());
179           }
180         }
181       }
182       return false;
183     }
184   };
185
186   public void checkCommandLineVersion() {
187     myChecker.checkExecutableAndNotifyIfNeeded();
188   }
189
190   static {
191     final JavaSVNDebugLogger logger = new JavaSVNDebugLogger(Boolean.getBoolean(LOG_PARAMETER_NAME), Boolean.getBoolean(TRACE_NATIVE_CALLS), LOG);
192     SVNDebugLog.setDefaultLog(logger);
193
194     SVNJNAUtil.setJNAEnabled(true);
195     SvnHttpAuthMethodsDefaultChecker.check();
196
197     SVNAdminAreaFactory.setSelector(new SvnFormatSelector());
198
199     DAVRepositoryFactory.setup();
200     SVNRepositoryFactoryImpl.setup();
201     FSRepositoryFactory.setup();
202
203     // non-optimized writing is fast enough on Linux/MacOS, and somewhat more reliable
204     if (SystemInfo.isWindows) {
205       SVNAdminArea14.setOptimizedWritingEnabled(true);
206     }
207
208     if (!SVNJNAUtil.isJNAPresent()) {
209       LOG.warn("JNA is not found by svnkit library");
210     }
211     initLogFilters();
212
213     // Alexander Kitaev says it is default value (SSLv3) - since 8254
214     if (!SystemInfo.JAVA_RUNTIME_VERSION.startsWith("1.7") && System.getProperty(SVNKIT_HTTP_SSL_PROTOCOLS) == null) {
215       System.setProperty(SVNKIT_HTTP_SSL_PROTOCOLS, "SSLv3");
216     }
217   }
218
219   public SvnVcs(final Project project, MessageBus bus, SvnConfiguration svnConfiguration, final SvnLoadedBrachesStorage storage) {
220     super(project, VCS_NAME);
221     myLoadedBranchesStorage = storage;
222     LOG.debug("ct");
223     myRootsToWorkingCopies = new RootsToWorkingCopies(this);
224     myConfiguration = svnConfiguration;
225     myAuthNotifier = new SvnAuthenticationNotifier(this);
226
227     dumpFileStatus(FileStatus.ADDED);
228     dumpFileStatus(FileStatus.DELETED);
229     dumpFileStatus(FileStatus.MERGE);
230     dumpFileStatus(FileStatus.MODIFIED);
231     dumpFileStatus(FileStatus.NOT_CHANGED);
232     dumpFileStatus(FileStatus.UNKNOWN);
233
234     dumpFileStatus(SvnFileStatus.REPLACED);
235     dumpFileStatus(SvnFileStatus.EXTERNAL);
236     dumpFileStatus(SvnFileStatus.OBSTRUCTED);
237
238     final ProjectLevelVcsManager vcsManager = ProjectLevelVcsManager.getInstance(project);
239     myAddConfirmation = vcsManager.getStandardConfirmation(VcsConfiguration.StandardConfirmation.ADD, this);
240     myDeleteConfirmation = vcsManager.getStandardConfirmation(VcsConfiguration.StandardConfirmation.REMOVE, this);
241     myCheckoutOptions = vcsManager.getStandardOption(VcsConfiguration.StandardOption.CHECKOUT, this);
242
243     if (myProject.isDefault()) {
244       myChangeListListener = null;
245       myEntriesFileListener = null;
246     }
247     else {
248       myEntriesFileListener = new SvnEntriesFileListener(project);
249       upgradeIfNeeded(bus);
250
251       myChangeListListener = new SvnChangelistListener(myProject, this);
252
253       myVcsListener = new VcsListener() {
254         @Override
255         public void directoryMappingChanged() {
256           invokeRefreshSvnRoots(true);
257         }
258       };
259     }
260
261     myFrameStateListener = project.isDefault() ? null : new MyFrameStateListener(ChangeListManager.getInstance(project),
262                                                                                  VcsDirtyScopeManager.getInstance(project));
263     myWorkingCopiesContent = new WorkingCopiesContent(this);
264
265     // remove used some time before old notification group ids
266     correctNotificationIds();
267     myChecker = new SvnExecutableChecker(myProject);
268   }
269
270   private void correctNotificationIds() {
271     boolean notEmpty = NotificationsConfigurationImpl.getNotificationsConfigurationImpl().isRegistered("SVN_NO_JNA") ||
272                        NotificationsConfigurationImpl.getNotificationsConfigurationImpl().isRegistered("SVN_NO_CRYPT32") ||
273                        NotificationsConfigurationImpl.getNotificationsConfigurationImpl().isRegistered("SubversionId");
274     if (notEmpty) {
275       NotificationsConfigurationImpl.remove("SVN_NO_JNA", "SVN_NO_CRYPT32", "SubversionId");
276       NotificationsConfiguration.getNotificationsConfiguration().register(getDisplayName(), NotificationDisplayType.BALLOON);
277     }
278   }
279
280   public void postStartup() {
281     if (myProject.isDefault()) return;
282     myCopiesRefreshManager = new SvnCopiesRefreshManager(myProject, (SvnFileUrlMappingImpl) getSvnFileUrlMapping());
283     if (! myConfiguration.isCleanupRun()) {
284       SwingUtilities.invokeLater(new Runnable() {
285         @Override
286         public void run() {
287           cleanup17copies();
288           myConfiguration.setCleanupRun(true);
289         }
290       });
291     } else {
292       invokeRefreshSvnRoots(true);
293     }
294
295     myWorkingCopiesContent.activate();
296   }
297
298   private void cleanup17copies() {
299     new CleanupWorker(new VirtualFile[]{}, myProject, "action.Subversion.cleanup.progress.title") {
300       @Override
301       protected void chanceToFillRoots() {
302         myCopiesRefreshManager.getCopiesRefresh().synchRequest();
303         final List<WCInfo> infos = getAllWcInfos();
304         final LocalFileSystem lfs = LocalFileSystem.getInstance();
305         final List<VirtualFile> roots = new ArrayList<VirtualFile>(infos.size());
306         for (WCInfo info : infos) {
307           if (WorkingCopyFormat.ONE_DOT_SEVEN.equals(info.getFormat())) {
308             final VirtualFile file = lfs.refreshAndFindFileByIoFile(new File(info.getPath()));
309             if (file == null) {
310               LOG.info("Wasn't able to find virtual file for wc root: " + info.getPath());
311             } else {
312               roots.add(file);
313             }
314           }
315         }
316         myRoots = roots.toArray(new VirtualFile[roots.size()]);
317       }
318     }.execute();
319   }
320
321   public void invokeRefreshSvnRoots(final boolean asynchronous) {
322     REFRESH_LOG.debug("refresh: ", new Throwable());
323     if (myCopiesRefreshManager != null) {
324       if (asynchronous) {
325         myCopiesRefreshManager.getCopiesRefresh().asynchRequest();
326       }
327       else {
328         if (ApplicationManager.getApplication().isDispatchThread()) {
329           ProgressManager.getInstance().runProcessWithProgressSynchronously(new Runnable() {
330             @Override
331             public void run() {
332               myCopiesRefreshManager.getCopiesRefresh().synchRequest();
333             }
334           }, SvnBundle.message("refreshing.working.copies.roots.progress.text"), true, myProject);
335         }
336         else {
337           myCopiesRefreshManager.getCopiesRefresh().synchRequest();
338         }
339       }
340     }
341   }
342
343   @Override
344   public boolean checkImmediateParentsBeforeCommit() {
345     return true;
346   }
347
348   private void upgradeIfNeeded(final MessageBus bus) {
349     final MessageBusConnection connection = bus.connect();
350     connection.subscribe(ChangeListManagerImpl.LISTS_LOADED, new LocalChangeListsLoadedListener() {
351       @Override
352       public void processLoadedLists(final List<LocalChangeList> lists) {
353         if (lists.isEmpty()) return;
354         SvnConfiguration.SvnSupportOptions supportOptions = null;
355         try {
356           ChangeListManager.getInstance(myProject).setReadOnly(SvnChangeProvider.ourDefaultListName, true);
357           supportOptions = myConfiguration.getSupportOptions(myProject);
358
359           if (!supportOptions.changeListsSynchronized()) {
360             processChangeLists(lists);
361           }
362         }
363         catch (ProcessCanceledException e) {
364           //
365         }
366         finally {
367           if (supportOptions != null) {
368             supportOptions.upgrade();
369           }
370         }
371
372         connection.disconnect();
373       }
374     });
375   }
376
377   public void processChangeLists(final List<LocalChangeList> lists) {
378     final ProjectLevelVcsManager plVcsManager = ProjectLevelVcsManager.getInstanceChecked(myProject);
379     plVcsManager.startBackgroundVcsOperation();
380     try {
381       final SVNChangelistClient client = createChangelistClient();
382       for (LocalChangeList list : lists) {
383         if (!list.isDefault()) {
384           final Collection<Change> changes = list.getChanges();
385           for (Change change : changes) {
386             correctListForRevision(plVcsManager, change.getBeforeRevision(), client, list.getName());
387             correctListForRevision(plVcsManager, change.getAfterRevision(), client, list.getName());
388           }
389         }
390       }
391     }
392     finally {
393       final Application appManager = ApplicationManager.getApplication();
394       if (appManager.isDispatchThread()) {
395         appManager.executeOnPooledThread(new Runnable() {
396           @Override
397           public void run() {
398             plVcsManager.stopBackgroundVcsOperation();
399           }
400         });
401       }
402       else {
403         plVcsManager.stopBackgroundVcsOperation();
404       }
405     }
406   }
407
408   private static void correctListForRevision(final ProjectLevelVcsManager plVcsManager, final ContentRevision revision,
409                                              final SVNChangelistClient client, final String name) {
410     if (revision != null) {
411       final FilePath path = revision.getFile();
412       final AbstractVcs vcs = plVcsManager.getVcsFor(path);
413       if (vcs != null && VCS_NAME.equals(vcs.getName())) {
414         try {
415           client.doAddToChangelist(new File[]{path.getIOFile()}, SVNDepth.EMPTY, name, null);
416         }
417         catch (SVNException e) {
418           // left in default list
419         }
420       }
421     }
422   }
423
424   @Override
425   public void activate() {
426     createPool();
427     final ProjectLevelVcsManager vcsManager = ProjectLevelVcsManager.getInstance(myProject);
428     if (!myProject.isDefault()) {
429       ChangeListManager.getInstance(myProject).addChangeListListener(myChangeListListener);
430       vcsManager.addVcsListener(myVcsListener);
431     }
432
433     SvnApplicationSettings.getInstance().svnActivated();
434     if (myEntriesFileListener != null) {
435       VirtualFileManager.getInstance().addVirtualFileListener(myEntriesFileListener);
436     }
437     // this will initialize its inner listener for committed changes upload
438     LoadedRevisionsCache.getInstance(myProject);
439     FrameStateManager.getInstance().addListener(myFrameStateListener);
440
441     myAuthNotifier.init();
442     mySvnBranchPointsCalculator = new SvnBranchPointsCalculator(myProject);
443     mySvnBranchPointsCalculator.activate();
444
445     if (SystemInfo.isWindows) {
446       if (!SVNJNAUtil.isJNAPresent()) {
447         Notifications.Bus.notify(new Notification(getDisplayName(), "Subversion plugin: no JNA",
448                                                   "A problem with JNA initialization for svnkit library. Encryption is not available.",
449                                                   NotificationType.WARNING),
450                                  NotificationDisplayType.BALLOON, myProject);
451       }
452       else if (!SVNJNAUtil.isWinCryptEnabled()) {
453         Notifications.Bus.notify(new Notification(getDisplayName(), "Subversion plugin: no encryption",
454                                                   "A problem with encryption module (Crypt32.dll) initialization for svnkit library. Encryption is not available.",
455                                                   NotificationType.WARNING), NotificationDisplayType.BALLOON, myProject);
456       }
457     }
458
459     final SvnConfiguration.UseAcceleration accelerationType = SvnConfiguration.getInstance(myProject).myUseAcceleration;
460     if (SvnConfiguration.UseAcceleration.javaHL.equals(accelerationType)) {
461       CheckJavaHL.runtimeCheck(myProject);
462     }
463     else if (SvnConfiguration.UseAcceleration.commandLine.equals(accelerationType) &&
464              !ApplicationManager.getApplication().isHeadlessEnvironment()) {
465       myChecker.checkExecutableAndNotifyIfNeeded();
466     }
467
468     // do one time after project loaded
469     StartupManager.getInstance(myProject).runWhenProjectIsInitialized(new DumbAwareRunnable() {
470       @Override
471       public void run() {
472         postStartup();
473
474         // for IDEA, it takes 2 minutes - and anyway this can be done in background, no sense...
475         // once it could be mistaken about copies for 2 minutes on start...
476
477         /*if (! myMapping.getAllWcInfos().isEmpty()) {
478           invokeRefreshSvnRoots();
479           return;
480         }
481         ProgressManager.getInstance().runProcessWithProgressSynchronously(new Runnable() {
482           public void run() {
483             myCopiesRefreshManager.getCopiesRefresh().ensureInit();
484           }
485         }, SvnBundle.message("refreshing.working.copies.roots.progress.text"), true, myProject);*/
486       }
487     });
488
489     vcsManager.addVcsListener(myRootsToWorkingCopies);
490
491     myLoadedBranchesStorage.activate();
492   }
493
494   private static void initLogFilters() {
495     if (ourLogFilters != null) return;
496     ourLogFilters = new RareLogger.LogFilter[]{new RareLogger.LogFilter() {
497       @Override
498       public Object getKey(@NotNull org.apache.log4j.Level level,
499                            @NonNls String message,
500                            @Nullable Throwable t,
501                            @NonNls String... details) {
502         SVNException svnExc = null;
503         if (t instanceof SVNException) {
504           svnExc = (SVNException)t;
505         }
506         else if (t instanceof VcsException && t.getCause() instanceof SVNException) {
507           svnExc = (SVNException)t.getCause();
508         }
509         if (svnExc != null) {
510           // only filter a few cases
511           if (ourLogRarely.contains(svnExc.getErrorMessage().getErrorCode())) {
512             return svnExc.getErrorMessage().getErrorCode();
513           }
514         }
515         return null;
516       }
517
518       @Override
519       @NotNull
520       public Integer getAllowedLoggingInterval(org.apache.log4j.Level level, String message, Throwable t, String[] details) {
521         SVNException svnExc = null;
522         if (t instanceof SVNException) {
523           svnExc = (SVNException)t;
524         }
525         else if (t instanceof VcsException && t.getCause() instanceof SVNException) {
526           svnExc = (SVNException)t.getCause();
527         }
528         if (svnExc != null) {
529           if (ourLogRarely.contains(svnExc.getErrorMessage().getErrorCode())) {
530             return ourLogRareInterval;
531           }
532           else {
533             return ourLogUsualInterval;
534           }
535         }
536         return 0;
537       }
538     }};
539   }
540
541   public static Logger wrapLogger(final Logger logger) {
542     initLogFilters();
543     return RareLogger.wrap(logger, Boolean.getBoolean("svn.logger.fairsynch"), ourLogFilters);
544   }
545
546   public RootsToWorkingCopies getRootsToWorkingCopies() {
547     return myRootsToWorkingCopies;
548   }
549
550   public SvnAuthenticationNotifier getAuthNotifier() {
551     return myAuthNotifier;
552   }
553
554   @Override
555   public void deactivate() {
556     FrameStateManager.getInstance().removeListener(myFrameStateListener);
557
558     final ProjectLevelVcsManager vcsManager = ProjectLevelVcsManager.getInstance(myProject);
559     if (myVcsListener != null) {
560       vcsManager.removeVcsListener(myVcsListener);
561     }
562
563     if (myEntriesFileListener != null) {
564       VirtualFileManager.getInstance().removeVirtualFileListener(myEntriesFileListener);
565     }
566     SvnApplicationSettings.getInstance().svnDeactivated();
567     if (myCommittedChangesProvider != null) {
568       myCommittedChangesProvider.deactivate();
569     }
570     if (myChangeListListener != null && !myProject.isDefault()) {
571       ChangeListManager.getInstance(myProject).removeChangeListListener(myChangeListListener);
572     }
573     vcsManager.removeVcsListener(myRootsToWorkingCopies);
574     myRootsToWorkingCopies.clear();
575
576     myAuthNotifier.stop();
577     myAuthNotifier.clear();
578
579     mySvnBranchPointsCalculator.deactivate();
580     mySvnBranchPointsCalculator = null;
581     myWorkingCopiesContent.deactivate();
582     myLoadedBranchesStorage.deactivate();
583     myPool.dispose();
584     myPool = null;
585   }
586
587   public VcsShowConfirmationOption getAddConfirmation() {
588     return myAddConfirmation;
589   }
590
591   public VcsShowConfirmationOption getDeleteConfirmation() {
592     return myDeleteConfirmation;
593   }
594
595   public VcsShowSettingOption getCheckoutOptions() {
596     return myCheckoutOptions;
597   }
598
599   @Override
600   public EditFileProvider getEditFileProvider() {
601     if (myEditFilesProvider == null) {
602       myEditFilesProvider = new SvnEditFileProvider(this);
603     }
604     return myEditFilesProvider;
605   }
606
607   @Override
608   @NotNull
609   public ChangeProvider getChangeProvider() {
610     if (myChangeProvider == null) {
611       myChangeProvider = new SvnChangeProvider(this);
612     }
613     return myChangeProvider;
614   }
615
616   public SVNRepository createRepository(String url) throws SVNException {
617     SVNRepository repos = SVNRepositoryFactory.create(SVNURL.parseURIEncoded(url));
618     repos.setAuthenticationManager(myConfiguration.getAuthenticationManager(this));
619     repos.setTunnelProvider(myConfiguration.getOptions(myProject));
620     return repos;
621   }
622
623   public SVNRepository createRepository(SVNURL url) throws SVNException {
624     SVNRepository repos = SVNRepositoryFactory.create(url);
625     repos.setAuthenticationManager(myConfiguration.getAuthenticationManager(this));
626     repos.setTunnelProvider(myConfiguration.getOptions(myProject));
627     return repos;
628   }
629
630   private void createPool() {
631     if (myPool != null) return;
632     final String property = System.getProperty(KEEP_CONNECTIONS_KEY);
633     final boolean keep;
634     boolean unitTestMode = ApplicationManager.getApplication().isUnitTestMode();
635     // pool variant by default
636     if (StringUtil.isEmptyOrSpaces(property) || unitTestMode) {
637       keep = ! unitTestMode;  // default
638     } else {
639       keep = Boolean.getBoolean(KEEP_CONNECTIONS_KEY);
640     }
641     myPool = new SvnIdeaRepositoryPoolManager(false, myConfiguration.getAuthenticationManager(this), myConfiguration.getOptions(myProject));
642   }
643
644   @NotNull
645   private ISVNRepositoryPool getPool() {
646     if (myProject.isDisposed()) {
647       throw new ProcessCanceledException();
648     }
649     if (myPool == null) {
650       createPool();
651     }
652     return myPool;
653   }
654
655   public SVNUpdateClient createUpdateClient() {
656     final SVNUpdateClient client = new SVNUpdateClient(getPool(), myConfiguration.getOptions(myProject));
657     client.getOperationsFactory().setAuthenticationManager(myConfiguration.getAuthenticationManager(this));
658     return client;
659   }
660
661   public SVNStatusClient createStatusClient() {
662     SVNStatusClient client = new SVNStatusClient(getPool(), myConfiguration.getOptions(myProject));
663     client.getOperationsFactory().setAuthenticationManager(myConfiguration.getAuthenticationManager(this));
664     client.setIgnoreExternals(false);
665     return client;
666   }
667
668   public SVNWCClient createWCClient() {
669     final SVNWCClient client = new SVNWCClient(getPool(), myConfiguration.getOptions(myProject));
670     client.getOperationsFactory().setAuthenticationManager(myConfiguration.getAuthenticationManager(this));
671     return client;
672   }
673
674   public SVNCopyClient createCopyClient() {
675     final SVNCopyClient client = new SVNCopyClient(getPool(), myConfiguration.getOptions(myProject));
676     client.getOperationsFactory().setAuthenticationManager(myConfiguration.getAuthenticationManager(this));
677     return client;
678   }
679
680   public SVNMoveClient createMoveClient() {
681     final SVNMoveClient client = new SVNMoveClient(getPool(), myConfiguration.getOptions(myProject));
682     client.getOperationsFactory().setAuthenticationManager(myConfiguration.getAuthenticationManager(this));
683     return client;
684   }
685
686   public SVNLogClient createLogClient() {
687     final SVNLogClient client = new SVNLogClient(getPool(), myConfiguration.getOptions(myProject));
688     client.getOperationsFactory().setAuthenticationManager(myConfiguration.getAuthenticationManager(this));
689     return client;
690   }
691
692   public SVNCommitClient createCommitClient() {
693     final SVNCommitClient client = new SVNCommitClient(getPool(), myConfiguration.getOptions(myProject));
694     client.getOperationsFactory().setAuthenticationManager(myConfiguration.getAuthenticationManager(this));
695     return client;
696   }
697
698   public SVNDiffClient createDiffClient() {
699     final SVNDiffClient client = new SVNDiffClient(getPool(), myConfiguration.getOptions(myProject));
700     client.getOperationsFactory().setAuthenticationManager(myConfiguration.getAuthenticationManager(this));
701     return client;
702   }
703
704   public SVNChangelistClient createChangelistClient() {
705     final SVNChangelistClient client = new SVNChangelistClient(getPool(), myConfiguration.getOptions(myProject));
706     client.getOperationsFactory().setAuthenticationManager(myConfiguration.getAuthenticationManager(this));
707     return client;
708   }
709
710   public SVNWCAccess createWCAccess() {
711     final SVNWCAccess access = SVNWCAccess.newInstance(null);
712     access.setOptions(myConfiguration.getOptions(myProject));
713     return access;
714   }
715
716   public ISVNOptions getSvnOptions() {
717     return myConfiguration.getOptions(myProject);
718   }
719
720   public ISVNAuthenticationManager getSvnAuthenticationManager() {
721     return myConfiguration.getAuthenticationManager(this);
722   }
723
724   void dumpFileStatus(FileStatus fs) {
725     if (LOG.isDebugEnabled()) {
726       LOG.debug("FileStatus:" + fs.getText() + " " + fs.getColor() + " " + " " + fs.getClass().getName());
727     }
728   }
729
730   @Override
731   public UpdateEnvironment getIntegrateEnvironment() {
732     if (mySvnIntegrateEnvironment == null) {
733       mySvnIntegrateEnvironment = new SvnIntegrateEnvironment(this);
734     }
735     return mySvnIntegrateEnvironment;
736   }
737
738   @Override
739   public UpdateEnvironment createUpdateEnvironment() {
740     if (mySvnUpdateEnvironment == null) {
741       mySvnUpdateEnvironment = new SvnUpdateEnvironment(this);
742     }
743     return mySvnUpdateEnvironment;
744   }
745
746   @Override
747   public String getDisplayName() {
748     LOG.debug("getDisplayName");
749     return "Subversion";
750   }
751
752   @Override
753   public Configurable getConfigurable() {
754     LOG.debug("createConfigurable");
755     return new SvnConfigurable(myProject);
756   }
757
758
759   public SvnConfiguration getSvnConfiguration() {
760     return myConfiguration;
761   }
762
763   public static SvnVcs getInstance(Project project) {
764     return (SvnVcs)ProjectLevelVcsManager.getInstance(project).findVcsByName(VCS_NAME);
765   }
766
767   @Override
768   @NotNull
769   public CheckinEnvironment createCheckinEnvironment() {
770     if (myCheckinEnvironment == null) {
771       myCheckinEnvironment = new SvnCheckinEnvironment(this);
772     }
773     return myCheckinEnvironment;
774   }
775
776   @Override
777   @NotNull
778   public RollbackEnvironment createRollbackEnvironment() {
779     if (myRollbackEnvironment == null) {
780       myRollbackEnvironment = new SvnRollbackEnvironment(this);
781     }
782     return myRollbackEnvironment;
783   }
784
785   @Override
786   public VcsHistoryProvider getVcsHistoryProvider() {
787     // no heavy state, but it would be useful to have place to keep state in -> do not reuse instance
788     return new SvnHistoryProvider(this);
789   }
790
791   @Override
792   public VcsHistoryProvider getVcsBlockHistoryProvider() {
793     return getVcsHistoryProvider();
794   }
795
796   @Override
797   public AnnotationProvider getAnnotationProvider() {
798     if (myAnnotationProvider == null) {
799       myAnnotationProvider = new SvnAnnotationProvider(this);
800     }
801     return myAnnotationProvider;
802   }
803
804   public void addEntriesListener(final SvnEntriesListener listener) {
805     if (myEntriesFileListener != null) {
806       myEntriesFileListener.addListener(listener);
807     }
808   }
809
810   public void removeEntriesListener(final SvnEntriesListener listener) {
811     if (myEntriesFileListener != null) {
812       myEntriesFileListener.removeListener(listener);
813     }
814   }
815
816   public SvnEntriesFileListener getEntriesFileListener() {
817     return myEntriesFileListener;
818   }
819
820   @Override
821   public DiffProvider getDiffProvider() {
822     if (mySvnDiffProvider == null) {
823       mySvnDiffProvider = new SvnDiffProvider(this);
824     }
825     return mySvnDiffProvider;
826   }
827
828   private static Trinity<Long, Long, Long> getTimestampForPropertiesChange(final File ioFile, final boolean isDir) {
829     final File dir = isDir ? ioFile : ioFile.getParentFile();
830     final String relPath = SVNAdminUtil.getPropPath(ioFile.getName(), isDir ? SVNNodeKind.DIR : SVNNodeKind.FILE, false);
831     final String relPathBase = SVNAdminUtil.getPropBasePath(ioFile.getName(), isDir ? SVNNodeKind.DIR : SVNNodeKind.FILE, false);
832     final String relPathRevert = SVNAdminUtil.getPropRevertPath(ioFile.getName(), isDir ? SVNNodeKind.DIR : SVNNodeKind.FILE, false);
833     return new Trinity<Long, Long, Long>(new File(dir, relPath).lastModified(), new File(dir, relPathBase).lastModified(),
834                                          new File(dir, relPathRevert).lastModified());
835   }
836
837   private static boolean trinitiesEqual(final Trinity<Long, Long, Long> t1, final Trinity<Long, Long, Long> t2) {
838     if (t2.first == 0 && t2.second == 0 && t2.third == 0) return false;
839     return t1.equals(t2);
840   }
841
842   @Nullable
843   public SVNPropertyValue getPropertyWithCaching(final VirtualFile file, final String propName) throws SVNException {
844     Map<String, Pair<SVNPropertyValue, Trinity<Long, Long, Long>>> cachedMap = myPropertyCache.get(keyForVf(file));
845     final Pair<SVNPropertyValue, Trinity<Long, Long, Long>> cachedValue = cachedMap == null ? null : cachedMap.get(propName);
846
847     final File ioFile = new File(file.getPath());
848     final Trinity<Long, Long, Long> tsTrinity = getTimestampForPropertiesChange(ioFile, file.isDirectory());
849
850     if (cachedValue != null) {
851       // zero means that a file was not found
852       if (trinitiesEqual(cachedValue.getSecond(), tsTrinity)) {
853         return cachedValue.getFirst();
854       }
855     }
856
857     final SVNPropertyData value = createWCClient().doGetProperty(ioFile, propName, SVNRevision.WORKING, SVNRevision.WORKING);
858     final SVNPropertyValue propValue = value == null ? null : value.getValue();
859
860     if (cachedMap == null) {
861       cachedMap = new HashMap<String, Pair<SVNPropertyValue, Trinity<Long, Long, Long>>>();
862       myPropertyCache.put(keyForVf(file), cachedMap);
863     }
864
865     cachedMap.put(propName, new Pair<SVNPropertyValue, Trinity<Long, Long, Long>>(propValue, tsTrinity));
866
867     return propValue;
868   }
869
870   @Override
871   public boolean fileExistsInVcs(FilePath path) {
872     File file = path.getIOFile();
873     try {
874       SVNStatus status = createStatusClient().doStatus(file, false);
875       if (status != null) {
876         if (svnStatusIs(status, SVNStatusType.STATUS_ADDED)) {
877           return status.isCopied();
878         }
879         return !(svnStatusIsUnversioned(status) ||
880                  svnStatusIs(status, SVNStatusType.STATUS_IGNORED) ||
881                  svnStatusIs(status, SVNStatusType.STATUS_OBSTRUCTED));
882       }
883     }
884     catch (SVNException e) {
885       //
886     }
887     return false;
888   }
889
890   public static boolean svnStatusIsUnversioned(final SVNStatus status) {
891     return svnStatusIs(status, SVNStatusType.STATUS_UNVERSIONED);
892   }
893
894   public static boolean svnStatusIs(final SVNStatus status, @NotNull final SVNStatusType value) {
895     return value.equals(status.getNodeStatus()) || value.equals(status.getContentsStatus());
896   }
897
898   @Override
899   public boolean fileIsUnderVcs(FilePath path) {
900     final ChangeListManager clManager = ChangeListManager.getInstance(myProject);
901     final VirtualFile file = path.getVirtualFile();
902     if (file == null) {
903       return false;
904     }
905     return !SvnStatusUtil.isIgnoredInAnySense(clManager, file) && !clManager.isUnversioned(file);
906   }
907
908   private static File getEntriesFile(File file) {
909     return file.isDirectory() ? new File(file, pathToEntries) : new File(file.getParentFile(), pathToEntries);
910   }
911
912   private static File getDirPropsFile(File file) {
913     return new File(file, pathToDirProps);
914   }
915
916   @Nullable
917   public SVNInfo getInfo(final VirtualFile file) {
918     final File ioFile = new File(file.getPath());
919     return getInfo(ioFile);
920   }
921
922   public SVNInfo getInfo(File ioFile) {
923     try {
924       SVNWCClient wcClient = createWCClient();
925       SVNInfo info = wcClient.doInfo(ioFile, SVNRevision.UNDEFINED);
926       if (info == null || info.getRepositoryRootURL() == null) {
927         info = wcClient.doInfo(ioFile, SVNRevision.HEAD);
928       }
929       return info;
930     }
931     catch (SVNException e) {
932       return null;
933     }
934   }
935
936   private static class JavaSVNDebugLogger extends SVNDebugLogAdapter {
937     private final boolean myLoggingEnabled;
938     private final boolean myLogNative;
939     private final Logger myLog;
940
941     public JavaSVNDebugLogger(boolean loggingEnabled, boolean logNative, Logger log) {
942       myLoggingEnabled = loggingEnabled;
943       myLogNative = logNative;
944       myLog = log;
945     }
946
947     private boolean shouldLog(final SVNLogType logType) {
948       return myLoggingEnabled || myLogNative && SVNLogType.NATIVE_CALL.equals(logType);
949     }
950
951     @Override
952     public void log(final SVNLogType logType, final Throwable th, final Level logLevel) {
953       if (shouldLog(logType)) {
954         myLog.info(th);
955       }
956     }
957
958     @Override
959     public void log(final SVNLogType logType, final String message, final Level logLevel) {
960       if (SVNLogType.NATIVE_CALL.equals(logType)) {
961         logNative(message);
962       }
963       if (shouldLog(logType)) {
964         myLog.info(message);
965       }
966     }
967
968     private static void logNative(String message) {
969       if (message == null) return;
970       final NativeLogReader.CallInfo callInfo = SvnNativeLogParser.parse(message);
971       if (callInfo == null) return;
972       NativeLogReader.putInfo(callInfo);
973     }
974
975     @Override
976     public void log(final SVNLogType logType, final String message, final byte[] data) {
977       if (shouldLog(logType)) {
978         if (data != null) {
979           try {
980             myLog.info(message + "\n" + new String(data, "UTF-8"));
981           }
982           catch (UnsupportedEncodingException e) {
983             myLog.info(message + "\n" + new String(data));
984           }
985         }
986         else {
987           myLog.info(message);
988         }
989       }
990     }
991   }
992
993   @Override
994   public FileStatus[] getProvidedStatuses() {
995     return new FileStatus[]{SvnFileStatus.EXTERNAL,
996       SvnFileStatus.OBSTRUCTED,
997       SvnFileStatus.REPLACED};
998   }
999
1000
1001   @Override
1002   @NotNull
1003   public CommittedChangesProvider<SvnChangeList, ChangeBrowserSettings> getCommittedChangesProvider() {
1004     if (myCommittedChangesProvider == null) {
1005       myCommittedChangesProvider = new SvnCommittedChangesProvider(myProject);
1006     }
1007     return myCommittedChangesProvider;
1008   }
1009
1010   @Nullable
1011   @Override
1012   public VcsRevisionNumber parseRevisionNumber(final String revisionNumberString) {
1013     final SVNRevision revision = SVNRevision.parse(revisionNumberString);
1014     if (revision.equals(SVNRevision.UNDEFINED)) {
1015       return null;
1016     }
1017     return new SvnRevisionNumber(revision);
1018   }
1019
1020   @Override
1021   public String getRevisionPattern() {
1022     return ourIntegerPattern;
1023   }
1024
1025   @Override
1026   public boolean isVersionedDirectory(final VirtualFile dir) {
1027     return SvnUtil.seemsLikeVersionedDir(dir);
1028   }
1029
1030   @NotNull
1031   public SvnFileUrlMapping getSvnFileUrlMapping() {
1032     if (myMapping == null) {
1033       myMapping = SvnFileUrlMappingImpl.getInstance(myProject);
1034     }
1035     return myMapping;
1036   }
1037
1038   /**
1039    * Returns real working copies roots - if there is <Project Root> -> Subversion setting,
1040    * and there is one working copy, will return one root
1041    */
1042   public List<WCInfo> getAllWcInfos() {
1043     final SvnFileUrlMapping urlMapping = getSvnFileUrlMapping();
1044
1045     final List<RootUrlInfo> infoList = urlMapping.getAllWcInfos();
1046     final List<WCInfo> infos = new ArrayList<WCInfo>();
1047     for (RootUrlInfo info : infoList) {
1048       final File file = info.getIoFile();
1049       infos.add(new WCInfo(file.getAbsolutePath(), info.getAbsoluteUrlAsUrl(),
1050                            info.getFormat(), info.getRepositoryUrl(), SvnUtil.isWorkingCopyRoot(file), info.getType(),
1051                            SvnUtil.getDepth(this, file)));
1052     }
1053     return infos;
1054   }
1055
1056   @Override
1057   public RootsConvertor getCustomConvertor() {
1058     if (myProject.isDefault()) return null;
1059     return getSvnFileUrlMapping();
1060   }
1061
1062   @Override
1063   public MergeProvider getMergeProvider() {
1064     if (myMergeProvider == null) {
1065       myMergeProvider = new SvnMergeProvider(myProject);
1066     }
1067     return myMergeProvider;
1068   }
1069
1070   @Override
1071   public List<AnAction> getAdditionalActionsForLocalChange() {
1072     return Arrays.<AnAction>asList(new ShowPropertiesDiffWithLocalAction());
1073   }
1074
1075   private static String keyForVf(final VirtualFile vf) {
1076     return vf.getUrl();
1077   }
1078
1079   @Override
1080   public boolean allowsNestedRoots() {
1081     return SvnConfiguration.getInstance(myProject).DETECT_NESTED_COPIES;
1082   }
1083
1084   @Override
1085   public <S> List<S> filterUniqueRoots(final List<S> in, final Convertor<S, VirtualFile> convertor) {
1086     if (in.size() <= 1) return in;
1087
1088     final List<MyPair<S>> infos = new ArrayList<MyPair<S>>(in.size());
1089     final SvnFileUrlMappingImpl mapping = (SvnFileUrlMappingImpl)getSvnFileUrlMapping();
1090     final List<S> notMatched = new LinkedList<S>();
1091     for (S s : in) {
1092       final VirtualFile vf = convertor.convert(s);
1093       if (vf == null) continue;
1094
1095       final File ioFile = new File(vf.getPath());
1096       SVNURL url = mapping.getUrlForFile(ioFile);
1097       if (url == null) {
1098         url = SvnUtil.getUrl(ioFile);
1099         if (url == null) {
1100           notMatched.add(s);
1101           continue;
1102         }
1103       }
1104       infos.add(new MyPair<S>(vf, url.toString(), s));
1105     }
1106     final List<MyPair<S>> filtered = new ArrayList<MyPair<S>>(infos.size());
1107     ForNestedRootChecker.filterOutSuperfluousChildren(this, infos, filtered);
1108
1109     final List<S> converted = ObjectsConvertor.convert(filtered, new Convertor<MyPair<S>, S>() {
1110       @Override
1111       public S convert(final MyPair<S> o) {
1112         return o.getSrc();
1113       }
1114     });
1115     if (!notMatched.isEmpty()) {
1116       // potential bug is here: order is not kept. but seems it only occurs for cases where result is sorted after filtering so ok
1117       converted.addAll(notMatched);
1118     }
1119     return converted;
1120   }
1121
1122   private static class MyPair<T> implements RootUrlPair {
1123     private final VirtualFile myFile;
1124     private final String myUrl;
1125     private final T mySrc;
1126
1127     private MyPair(VirtualFile file, String url, T src) {
1128       myFile = file;
1129       myUrl = url;
1130       mySrc = src;
1131     }
1132
1133     public T getSrc() {
1134       return mySrc;
1135     }
1136
1137     @Override
1138     public VirtualFile getVirtualFile() {
1139       return myFile;
1140     }
1141
1142     @Override
1143     public String getUrl() {
1144       return myUrl;
1145     }
1146   }
1147
1148   private static class MyFrameStateListener implements FrameStateListener {
1149     private final ChangeListManager myClManager;
1150     private final VcsDirtyScopeManager myDirtyScopeManager;
1151
1152     private MyFrameStateListener(ChangeListManager clManager, VcsDirtyScopeManager dirtyScopeManager) {
1153       myClManager = clManager;
1154       myDirtyScopeManager = dirtyScopeManager;
1155     }
1156
1157     @Override
1158     public void onFrameDeactivated() {
1159     }
1160
1161     @Override
1162     public void onFrameActivated() {
1163       final List<VirtualFile> folders = ((ChangeListManagerImpl)myClManager).getLockedFolders();
1164       if (!folders.isEmpty()) {
1165         myDirtyScopeManager.filesDirty(null, folders);
1166       }
1167     }
1168   }
1169
1170   public static VcsKey getKey() {
1171     return ourKey;
1172   }
1173
1174   @Override
1175   public boolean isVcsBackgroundOperationsAllowed(VirtualFile root) {
1176     return ThreeState.YES.equals(myAuthNotifier.isAuthenticatedFor(root));
1177   }
1178
1179   public SvnBranchPointsCalculator getSvnBranchPointsCalculator() {
1180     return mySvnBranchPointsCalculator;
1181   }
1182
1183   @Override
1184   public boolean areDirectoriesVersionedItems() {
1185     return true;
1186   }
1187 }