/* * Copyright 2000-2014 JetBrains s.r.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.jetbrains.idea.svn; import com.intellij.ide.FrameStateListener; import com.intellij.ide.FrameStateManager; import com.intellij.idea.RareLogger; import com.intellij.openapi.actionSystem.AnAction; import com.intellij.openapi.application.Application; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.application.ModalityState; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.options.Configurable; import com.intellij.openapi.progress.ProcessCanceledException; import com.intellij.openapi.project.DumbAwareRunnable; import com.intellij.openapi.project.Project; import com.intellij.openapi.startup.StartupManager; import com.intellij.openapi.util.Pair; import com.intellij.openapi.util.Trinity; import com.intellij.openapi.vcs.*; import com.intellij.openapi.vcs.annotate.AnnotationProvider; import com.intellij.openapi.vcs.changes.*; import com.intellij.openapi.vcs.checkin.CheckinEnvironment; import com.intellij.openapi.vcs.diff.DiffProvider; import com.intellij.openapi.vcs.history.VcsHistoryProvider; import com.intellij.openapi.vcs.history.VcsRevisionNumber; import com.intellij.openapi.vcs.merge.MergeProvider; import com.intellij.openapi.vcs.rollback.RollbackEnvironment; import com.intellij.openapi.vcs.update.UpdateEnvironment; import com.intellij.openapi.vcs.versionBrowser.ChangeBrowserSettings; import com.intellij.openapi.vcs.versionBrowser.CommittedChangeList; import com.intellij.openapi.vfs.LocalFileSystem; import com.intellij.openapi.vfs.VfsUtilCore; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.openapi.vfs.VirtualFileManager; import com.intellij.util.Consumer; import com.intellij.util.ThreeState; import com.intellij.util.containers.ContainerUtil; import com.intellij.util.containers.Convertor; import com.intellij.util.containers.SoftHashMap; import com.intellij.util.messages.MessageBus; import com.intellij.util.messages.MessageBusConnection; import com.intellij.util.messages.Topic; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.idea.svn.actions.CleanupWorker; import org.jetbrains.idea.svn.actions.ShowPropertiesDiffWithLocalAction; import org.jetbrains.idea.svn.actions.SvnMergeProvider; import org.jetbrains.idea.svn.annotate.SvnAnnotationProvider; import org.jetbrains.idea.svn.api.ClientFactory; import org.jetbrains.idea.svn.api.CmdClientFactory; import org.jetbrains.idea.svn.api.Depth; import org.jetbrains.idea.svn.api.SvnKitClientFactory; import org.jetbrains.idea.svn.auth.SvnAuthenticationNotifier; import org.jetbrains.idea.svn.checkin.SvnCheckinEnvironment; import org.jetbrains.idea.svn.checkout.SvnCheckoutProvider; import org.jetbrains.idea.svn.commandLine.SvnBindException; import org.jetbrains.idea.svn.commandLine.SvnExecutableChecker; import org.jetbrains.idea.svn.dialogs.SvnBranchPointsCalculator; import org.jetbrains.idea.svn.dialogs.WCInfo; import org.jetbrains.idea.svn.history.LoadedRevisionsCache; import org.jetbrains.idea.svn.history.SvnChangeList; import org.jetbrains.idea.svn.history.SvnCommittedChangesProvider; import org.jetbrains.idea.svn.history.SvnHistoryProvider; import org.jetbrains.idea.svn.info.Info; import org.jetbrains.idea.svn.info.InfoConsumer; import org.jetbrains.idea.svn.properties.PropertyClient; import org.jetbrains.idea.svn.properties.PropertyData; import org.jetbrains.idea.svn.properties.PropertyValue; import org.jetbrains.idea.svn.rollback.SvnRollbackEnvironment; import org.jetbrains.idea.svn.status.Status; import org.jetbrains.idea.svn.status.StatusType; import org.jetbrains.idea.svn.svnkit.SvnKitManager; import org.jetbrains.idea.svn.update.SvnIntegrateEnvironment; import org.jetbrains.idea.svn.update.SvnUpdateEnvironment; import org.tmatesoft.svn.core.*; import org.tmatesoft.svn.core.internal.wc.SVNAdminUtil; import org.tmatesoft.svn.core.wc.SVNRevision; import org.tmatesoft.svn.core.wc2.SvnTarget; import java.io.File; import java.util.*; @SuppressWarnings({"IOResourceOpenedButNotSafelyClosed"}) public class SvnVcs extends AbstractVcs { private static final String DO_NOT_LISTEN_TO_WC_DB = "svn.do.not.listen.to.wc.db"; private static final Logger REFRESH_LOG = Logger.getInstance("#svn_refresh"); public static boolean ourListenToWcDb = !Boolean.getBoolean(DO_NOT_LISTEN_TO_WC_DB); private static final Logger LOG = wrapLogger(Logger.getInstance("org.jetbrains.idea.svn.SvnVcs")); @NonNls public static final String VCS_NAME = "svn"; public static final String VCS_DISPLAY_NAME = "Subversion"; private static final VcsKey ourKey = createKey(VCS_NAME); public static final Topic WC_CONVERTED = new Topic("WC_CONVERTED", Runnable.class); private final Map>>> myPropertyCache = new SoftHashMap>>>(); private final SvnConfiguration myConfiguration; private final SvnEntriesFileListener myEntriesFileListener; private CheckinEnvironment myCheckinEnvironment; private RollbackEnvironment myRollbackEnvironment; private UpdateEnvironment mySvnUpdateEnvironment; private UpdateEnvironment mySvnIntegrateEnvironment; private AnnotationProvider myAnnotationProvider; private DiffProvider mySvnDiffProvider; private final VcsShowConfirmationOption myAddConfirmation; private final VcsShowConfirmationOption myDeleteConfirmation; private EditFileProvider myEditFilesProvider; private SvnCommittedChangesProvider myCommittedChangesProvider; private final VcsShowSettingOption myCheckoutOptions; private ChangeProvider myChangeProvider; private MergeProvider myMergeProvider; private final WorkingCopiesContent myWorkingCopiesContent; private final SvnChangelistListener myChangeListListener; private SvnCopiesRefreshManager myCopiesRefreshManager; private SvnFileUrlMappingImpl myMapping; private final MyFrameStateListener myFrameStateListener; //Consumer public static final Topic ROOTS_RELOADED = new Topic("ROOTS_RELOADED", Consumer.class); private VcsListener myVcsListener; private SvnBranchPointsCalculator mySvnBranchPointsCalculator; private final RootsToWorkingCopies myRootsToWorkingCopies; private final SvnAuthenticationNotifier myAuthNotifier; private final SvnLoadedBrachesStorage myLoadedBranchesStorage; private final SvnExecutableChecker myChecker; private SvnCheckoutProvider myCheckoutProvider; @NotNull private final ClientFactory cmdClientFactory; @NotNull private final ClientFactory svnKitClientFactory; @NotNull private final SvnKitManager svnKitManager; private final boolean myLogExceptions; public SvnVcs(final Project project, MessageBus bus, SvnConfiguration svnConfiguration, final SvnLoadedBrachesStorage storage) { super(project, VCS_NAME); myLoadedBranchesStorage = storage; myRootsToWorkingCopies = new RootsToWorkingCopies(this); myConfiguration = svnConfiguration; myAuthNotifier = new SvnAuthenticationNotifier(this); cmdClientFactory = new CmdClientFactory(this); svnKitClientFactory = new SvnKitClientFactory(this); svnKitManager = new SvnKitManager(this); final ProjectLevelVcsManager vcsManager = ProjectLevelVcsManager.getInstance(project); myAddConfirmation = vcsManager.getStandardConfirmation(VcsConfiguration.StandardConfirmation.ADD, this); myDeleteConfirmation = vcsManager.getStandardConfirmation(VcsConfiguration.StandardConfirmation.REMOVE, this); myCheckoutOptions = vcsManager.getStandardOption(VcsConfiguration.StandardOption.CHECKOUT, this); if (myProject.isDefault()) { myChangeListListener = null; myEntriesFileListener = null; } else { myEntriesFileListener = new SvnEntriesFileListener(project); upgradeIfNeeded(bus); myChangeListListener = new SvnChangelistListener(myProject, this); myVcsListener = new VcsListener() { @Override public void directoryMappingChanged() { invokeRefreshSvnRoots(); } }; } myFrameStateListener = project.isDefault() ? null : new MyFrameStateListener(ChangeListManager.getInstance(project), VcsDirtyScopeManager.getInstance(project)); myWorkingCopiesContent = new WorkingCopiesContent(this); myChecker = new SvnExecutableChecker(myProject); Application app = ApplicationManager.getApplication(); myLogExceptions = app != null && (app.isInternal() || app.isUnitTestMode()); } public void postStartup() { if (myProject.isDefault()) return; myCopiesRefreshManager = new SvnCopiesRefreshManager((SvnFileUrlMappingImpl)getSvnFileUrlMapping()); if (!myConfiguration.isCleanupRun()) { ApplicationManager.getApplication().invokeLater(new Runnable() { @Override public void run() { cleanup17copies(); myConfiguration.setCleanupRun(true); } }, ModalityState.NON_MODAL, myProject.getDisposed()); } else { invokeRefreshSvnRoots(); } myWorkingCopiesContent.activate(); } /** * TODO: This seems to be related to some issues when upgrading from 1.6 to 1.7. So it is not currently required for 1.8 and later * TODO: formats. And should be removed when 1.6 working copies are no longer supported by IDEA. */ private void cleanup17copies() { final Runnable callCleanupWorker = new Runnable() { public void run() { if (myProject.isDisposed()) return; new CleanupWorker(new VirtualFile[]{}, myProject, "action.Subversion.cleanup.progress.title") { @Override protected void chanceToFillRoots() { final List infos = getAllWcInfos(); final LocalFileSystem lfs = LocalFileSystem.getInstance(); final List roots = new ArrayList(infos.size()); for (WCInfo info : infos) { if (WorkingCopyFormat.ONE_DOT_SEVEN.equals(info.getFormat())) { final VirtualFile file = lfs.refreshAndFindFileByIoFile(new File(info.getPath())); if (file == null) { LOG.info("Wasn't able to find virtual file for wc root: " + info.getPath()); } else { roots.add(file); } } } myRoots = roots.toArray(new VirtualFile[roots.size()]); } }.execute(); } }; myCopiesRefreshManager.waitRefresh(new Runnable() { @Override public void run() { ApplicationManager.getApplication().invokeLater(callCleanupWorker, ModalityState.any()); } }); } public boolean checkCommandLineVersion() { boolean isValid = true; if (!isProject16() && (myConfiguration.isCommandLine() || isProject18OrGreater())) { isValid = myChecker.checkExecutableAndNotifyIfNeeded(); } return isValid; } public void invokeRefreshSvnRoots() { if (REFRESH_LOG.isDebugEnabled()) { REFRESH_LOG.debug("refresh: ", new Throwable()); } if (myCopiesRefreshManager != null) { myCopiesRefreshManager.asynchRequest(); } } @Override public boolean checkImmediateParentsBeforeCommit() { return true; } private void upgradeIfNeeded(final MessageBus bus) { final MessageBusConnection connection = bus.connect(); connection.subscribe(ChangeListManagerImpl.LISTS_LOADED, new LocalChangeListsLoadedListener() { @Override public void processLoadedLists(final List lists) { if (lists.isEmpty()) return; try { ChangeListManager.getInstance(myProject).setReadOnly(SvnChangeProvider.ourDefaultListName, true); if (!myConfiguration.changeListsSynchronized()) { processChangeLists(lists); } } catch (ProcessCanceledException e) { // } finally { myConfiguration.upgrade(); } connection.disconnect(); } }); } public void processChangeLists(final List lists) { final ProjectLevelVcsManager plVcsManager = ProjectLevelVcsManager.getInstanceChecked(myProject); plVcsManager.startBackgroundVcsOperation(); try { for (LocalChangeList list : lists) { if (!list.isDefault()) { final Collection changes = list.getChanges(); for (Change change : changes) { correctListForRevision(plVcsManager, change.getBeforeRevision(), list.getName()); correctListForRevision(plVcsManager, change.getAfterRevision(), list.getName()); } } } } finally { final Application appManager = ApplicationManager.getApplication(); if (appManager.isDispatchThread()) { appManager.executeOnPooledThread(new Runnable() { @Override public void run() { plVcsManager.stopBackgroundVcsOperation(); } }); } else { plVcsManager.stopBackgroundVcsOperation(); } } } private void correctListForRevision(@NotNull final ProjectLevelVcsManager plVcsManager, @Nullable final ContentRevision revision, @NotNull final String name) { if (revision != null) { final FilePath path = revision.getFile(); final AbstractVcs vcs = plVcsManager.getVcsFor(path); if (vcs != null && VCS_NAME.equals(vcs.getName())) { try { getFactory(path.getIOFile()).createChangeListClient().add(name, path.getIOFile(), null); } catch (VcsException e) { // left in default list } } } } @Override public void activate() { if (!myProject.isDefault()) { ChangeListManager.getInstance(myProject).addChangeListListener(myChangeListListener); myProject.getMessageBus().connect().subscribe(ProjectLevelVcsManager.VCS_CONFIGURATION_CHANGED, myVcsListener); } SvnApplicationSettings.getInstance().svnActivated(); if (myEntriesFileListener != null) { VirtualFileManager.getInstance().addVirtualFileListener(myEntriesFileListener); } // this will initialize its inner listener for committed changes upload LoadedRevisionsCache.getInstance(myProject); FrameStateManager.getInstance().addListener(myFrameStateListener); myAuthNotifier.init(); mySvnBranchPointsCalculator = new SvnBranchPointsCalculator(myProject); mySvnBranchPointsCalculator.activate(); svnKitManager.activate(); if (!ApplicationManager.getApplication().isHeadlessEnvironment()) { checkCommandLineVersion(); } // do one time after project loaded StartupManager.getInstance(myProject).runWhenProjectIsInitialized(new DumbAwareRunnable() { @Override public void run() { postStartup(); // for IDEA, it takes 2 minutes - and anyway this can be done in background, no sense... // once it could be mistaken about copies for 2 minutes on start... /*if (! myMapping.getAllWcInfos().isEmpty()) { invokeRefreshSvnRoots(); return; } ProgressManager.getInstance().runProcessWithProgressSynchronously(new Runnable() { public void run() { myCopiesRefreshManager.getCopiesRefresh().ensureInit(); } }, SvnBundle.message("refreshing.working.copies.roots.progress.text"), true, myProject);*/ } }); myProject.getMessageBus().connect().subscribe(ProjectLevelVcsManager.VCS_CONFIGURATION_CHANGED, myRootsToWorkingCopies); myLoadedBranchesStorage.activate(); } public static Logger wrapLogger(final Logger logger) { return RareLogger.wrap(logger, Boolean.getBoolean("svn.logger.fairsynch"), new SvnExceptionLogFilter()); } public RootsToWorkingCopies getRootsToWorkingCopies() { return myRootsToWorkingCopies; } public SvnAuthenticationNotifier getAuthNotifier() { return myAuthNotifier; } @Override public void deactivate() { FrameStateManager.getInstance().removeListener(myFrameStateListener); if (myEntriesFileListener != null) { VirtualFileManager.getInstance().removeVirtualFileListener(myEntriesFileListener); } SvnApplicationSettings.getInstance().svnDeactivated(); if (myCommittedChangesProvider != null) { myCommittedChangesProvider.deactivate(); } if (myChangeListListener != null && !myProject.isDefault()) { ChangeListManager.getInstance(myProject).removeChangeListListener(myChangeListListener); } myRootsToWorkingCopies.clear(); myAuthNotifier.stop(); myAuthNotifier.clear(); mySvnBranchPointsCalculator.deactivate(); mySvnBranchPointsCalculator = null; myWorkingCopiesContent.deactivate(); myLoadedBranchesStorage.deactivate(); } public VcsShowConfirmationOption getAddConfirmation() { return myAddConfirmation; } public VcsShowConfirmationOption getDeleteConfirmation() { return myDeleteConfirmation; } public VcsShowSettingOption getCheckoutOptions() { return myCheckoutOptions; } @Override public EditFileProvider getEditFileProvider() { if (myEditFilesProvider == null) { myEditFilesProvider = new SvnEditFileProvider(this); } return myEditFilesProvider; } @Override @NotNull public ChangeProvider getChangeProvider() { if (myChangeProvider == null) { myChangeProvider = new SvnChangeProvider(this); } return myChangeProvider; } @Override public UpdateEnvironment getIntegrateEnvironment() { if (mySvnIntegrateEnvironment == null) { mySvnIntegrateEnvironment = new SvnIntegrateEnvironment(this); } return mySvnIntegrateEnvironment; } @Override public UpdateEnvironment createUpdateEnvironment() { if (mySvnUpdateEnvironment == null) { mySvnUpdateEnvironment = new SvnUpdateEnvironment(this); } return mySvnUpdateEnvironment; } @Override public String getDisplayName() { return VCS_DISPLAY_NAME; } @Override public Configurable getConfigurable() { return new SvnConfigurable(myProject); } public SvnConfiguration getSvnConfiguration() { return myConfiguration; } public static SvnVcs getInstance(Project project) { return (SvnVcs)ProjectLevelVcsManager.getInstance(project).findVcsByName(VCS_NAME); } @Override @NotNull public CheckinEnvironment createCheckinEnvironment() { if (myCheckinEnvironment == null) { myCheckinEnvironment = new SvnCheckinEnvironment(this); } return myCheckinEnvironment; } @Override @NotNull public RollbackEnvironment createRollbackEnvironment() { if (myRollbackEnvironment == null) { myRollbackEnvironment = new SvnRollbackEnvironment(this); } return myRollbackEnvironment; } @Override public VcsHistoryProvider getVcsHistoryProvider() { // no heavy state, but it would be useful to have place to keep state in -> do not reuse instance return new SvnHistoryProvider(this); } @Override public VcsHistoryProvider getVcsBlockHistoryProvider() { return getVcsHistoryProvider(); } @Override public AnnotationProvider getAnnotationProvider() { if (myAnnotationProvider == null) { myAnnotationProvider = new SvnAnnotationProvider(this); } return myAnnotationProvider; } @Override public DiffProvider getDiffProvider() { if (mySvnDiffProvider == null) { mySvnDiffProvider = new SvnDiffProvider(this); } return mySvnDiffProvider; } private static Trinity getTimestampForPropertiesChange(final File ioFile, final boolean isDir) { final File dir = isDir ? ioFile : ioFile.getParentFile(); final String relPath = SVNAdminUtil.getPropPath(ioFile.getName(), isDir ? SVNNodeKind.DIR : SVNNodeKind.FILE, false); final String relPathBase = SVNAdminUtil.getPropBasePath(ioFile.getName(), isDir ? SVNNodeKind.DIR : SVNNodeKind.FILE, false); final String relPathRevert = SVNAdminUtil.getPropRevertPath(ioFile.getName(), isDir ? SVNNodeKind.DIR : SVNNodeKind.FILE, false); return new Trinity(new File(dir, relPath).lastModified(), new File(dir, relPathBase).lastModified(), new File(dir, relPathRevert).lastModified()); } private static boolean trinitiesEqual(final Trinity t1, final Trinity t2) { if (t2.first == 0 && t2.second == 0 && t2.third == 0) return false; return t1.equals(t2); } @Nullable public PropertyValue getPropertyWithCaching(final VirtualFile file, final String propName) throws VcsException { Map>> cachedMap = myPropertyCache.get(keyForVf(file)); final Pair> cachedValue = cachedMap == null ? null : cachedMap.get(propName); final File ioFile = new File(file.getPath()); final Trinity tsTrinity = getTimestampForPropertiesChange(ioFile, file.isDirectory()); if (cachedValue != null) { // zero means that a file was not found if (trinitiesEqual(cachedValue.getSecond(), tsTrinity)) { return cachedValue.getFirst(); } } PropertyClient client = getFactory(ioFile).createPropertyClient(); final PropertyValue value = client.getProperty(SvnTarget.fromFile(ioFile, SVNRevision.WORKING), propName, false, SVNRevision.WORKING); if (cachedMap == null) { cachedMap = new HashMap>>(); myPropertyCache.put(keyForVf(file), cachedMap); } cachedMap.put(propName, Pair.create(value, tsTrinity)); return value; } @Override public boolean fileExistsInVcs(FilePath path) { File file = path.getIOFile(); try { Status status = getFactory(file).createStatusClient().doStatus(file, false); if (status != null) { return status.is(StatusType.STATUS_ADDED) ? status.isCopied() : !status.is(StatusType.STATUS_UNVERSIONED, StatusType.STATUS_IGNORED, StatusType.STATUS_OBSTRUCTED); } } catch (SvnBindException e) { LOG.info(e); } return false; } @Override public boolean fileIsUnderVcs(FilePath path) { final ChangeListManager clManager = ChangeListManager.getInstance(myProject); final VirtualFile file = path.getVirtualFile(); if (file == null) { return false; } return !SvnStatusUtil.isIgnoredInAnySense(clManager, file) && !clManager.isUnversioned(file); } @Nullable public Info getInfo(@NotNull SVNURL url, SVNRevision pegRevision, SVNRevision revision) throws SvnBindException { return getFactory().createInfoClient().doInfo(url, pegRevision, revision); } @Nullable public Info getInfo(@NotNull SVNURL url, SVNRevision revision) throws SvnBindException { return getInfo(url, SVNRevision.UNDEFINED, revision); } @Nullable public Info getInfo(@NotNull final VirtualFile file) { return getInfo(new File(file.getPath())); } @Nullable public Info getInfo(@NotNull String path) { return getInfo(new File(path)); } @Nullable public Info getInfo(@NotNull File ioFile) { return getInfo(ioFile, SVNRevision.UNDEFINED); } public void collectInfo(@NotNull Collection files, @Nullable InfoConsumer handler) { File first = ContainerUtil.getFirstItem(files); if (first != null) { ClientFactory factory = getFactory(first); try { if (factory instanceof CmdClientFactory) { factory.createInfoClient().doInfo(files, handler); } else { // TODO: Generally this should be moved in SvnKit info client implementation. // TODO: Currently left here to have exception logic as in handleInfoException to be applied for each file separately. for (File file : files) { Info info = getInfo(file); if (handler != null) { handler.consume(info); } } } } catch (SVNException e) { handleInfoException(new SvnBindException(e)); } catch (SvnBindException e) { handleInfoException(e); } } } @Nullable public Info getInfo(@NotNull File ioFile, @NotNull SVNRevision revision) { Info result = null; try { result = getFactory(ioFile).createInfoClient().doInfo(ioFile, revision); } catch (SvnBindException e) { handleInfoException(e); } return result; } private void handleInfoException(@NotNull SvnBindException e) { if (!myLogExceptions || SvnUtil.isUnversionedOrNotFound(e) || // do not log working copy format vs client version inconsistencies as errors e.contains(SVNErrorCode.WC_UNSUPPORTED_FORMAT) || e.contains(SVNErrorCode.WC_UPGRADE_REQUIRED)) { LOG.debug(e); } else { LOG.error(e); } } @NotNull public WorkingCopyFormat getWorkingCopyFormat(@NotNull File ioFile) { return getWorkingCopyFormat(ioFile, true); } @NotNull public WorkingCopyFormat getWorkingCopyFormat(@NotNull File ioFile, boolean useMapping) { WorkingCopyFormat format = WorkingCopyFormat.UNKNOWN; if (useMapping) { RootUrlInfo rootInfo = getSvnFileUrlMapping().getWcRootForFilePath(ioFile); format = rootInfo != null ? rootInfo.getFormat() : WorkingCopyFormat.UNKNOWN; } return WorkingCopyFormat.UNKNOWN.equals(format) ? SvnFormatSelector.findRootAndGetFormat(ioFile) : format; } public boolean isWcRoot(FilePath filePath) { boolean isWcRoot = false; WorkingCopy wcRoot = myRootsToWorkingCopies.getWcRoot(filePath.getVirtualFile()); if (wcRoot != null) { isWcRoot = wcRoot.getFile().getAbsolutePath().equals(filePath.getIOFile().getAbsolutePath()); } return isWcRoot; } @Override public FileStatus[] getProvidedStatuses() { return new FileStatus[]{SvnFileStatus.EXTERNAL, SvnFileStatus.OBSTRUCTED, SvnFileStatus.REPLACED}; } @Override @NotNull public CommittedChangesProvider getCommittedChangesProvider() { if (myCommittedChangesProvider == null) { myCommittedChangesProvider = new SvnCommittedChangesProvider(myProject); } return myCommittedChangesProvider; } @Nullable @Override public VcsRevisionNumber parseRevisionNumber(final String revisionNumberString) { final SVNRevision revision = SVNRevision.parse(revisionNumberString); if (revision.equals(SVNRevision.UNDEFINED)) { return null; } return new SvnRevisionNumber(revision); } @Override public String getRevisionPattern() { return ourIntegerPattern; } @Override public boolean isVersionedDirectory(final VirtualFile dir) { return SvnUtil.seemsLikeVersionedDir(dir); } @NotNull public SvnFileUrlMapping getSvnFileUrlMapping() { if (myMapping == null) { myMapping = SvnFileUrlMappingImpl.getInstance(myProject); } return myMapping; } /** * Returns real working copies roots - if there is -> Subversion setting, * and there is one working copy, will return one root */ public List getAllWcInfos() { final SvnFileUrlMapping urlMapping = getSvnFileUrlMapping(); final List infoList = urlMapping.getAllWcInfos(); final List infos = new ArrayList(); for (RootUrlInfo info : infoList) { final File file = info.getIoFile(); infos.add(new WCInfo(info, SvnUtil.isWorkingCopyRoot(file), SvnUtil.getDepth(this, file))); } return infos; } public List getWcInfosWithErrors() { List result = new ArrayList(getAllWcInfos()); for (RootUrlInfo info : getSvnFileUrlMapping().getErrorRoots()) { result.add(new WCInfo(info, SvnUtil.isWorkingCopyRoot(info.getIoFile()), Depth.UNKNOWN)); } return result; } @Override public RootsConvertor getCustomConvertor() { if (myProject.isDefault()) return null; return getSvnFileUrlMapping(); } @Override public MergeProvider getMergeProvider() { if (myMergeProvider == null) { myMergeProvider = new SvnMergeProvider(myProject); } return myMergeProvider; } @Override public List getAdditionalActionsForLocalChange() { return Arrays.asList(new ShowPropertiesDiffWithLocalAction()); } private static String keyForVf(final VirtualFile vf) { return vf.getUrl(); } @Override public boolean allowsNestedRoots() { return true; } @Override public List filterUniqueRoots(final List in, final Convertor convertor) { if (in.size() <= 1) return in; final List> infos = new ArrayList>(in.size()); final SvnFileUrlMappingImpl mapping = (SvnFileUrlMappingImpl)getSvnFileUrlMapping(); final List notMatched = new LinkedList(); for (S s : in) { final VirtualFile vf = convertor.convert(s); if (vf == null) continue; final File ioFile = new File(vf.getPath()); SVNURL url = mapping.getUrlForFile(ioFile); if (url == null) { url = SvnUtil.getUrl(this, ioFile); if (url == null) { notMatched.add(s); continue; } } infos.add(new MyPair(vf, url.toString(), s)); } final List> filtered = new UniqueRootsFilter().filter(infos); final List converted = ObjectsConvertor.convert(filtered, new Convertor, S>() { @Override public S convert(final MyPair o) { return o.getSrc(); } }); if (!notMatched.isEmpty()) { // potential bug is here: order is not kept. but seems it only occurs for cases where result is sorted after filtering so ok converted.addAll(notMatched); } return converted; } private static class MyPair implements RootUrlPair { private final VirtualFile myFile; private final String myUrl; private final T mySrc; private MyPair(VirtualFile file, String url, T src) { myFile = file; myUrl = url; mySrc = src; } public T getSrc() { return mySrc; } @Override public VirtualFile getVirtualFile() { return myFile; } @Override public String getUrl() { return myUrl; } } private static class MyFrameStateListener extends FrameStateListener.Adapter { private final ChangeListManager myClManager; private final VcsDirtyScopeManager myDirtyScopeManager; private MyFrameStateListener(ChangeListManager clManager, VcsDirtyScopeManager dirtyScopeManager) { myClManager = clManager; myDirtyScopeManager = dirtyScopeManager; } @Override public void onFrameActivated() { final List folders = ((ChangeListManagerImpl)myClManager).getLockedFolders(); if (!folders.isEmpty()) { myDirtyScopeManager.filesDirty(null, folders); } } } public static VcsKey getKey() { return ourKey; } @Override public boolean isVcsBackgroundOperationsAllowed(VirtualFile root) { // TODO: Currently myAuthNotifier.isAuthenticatedFor directly uses SVNKit to check credentials - so assume for now that background // TODO: operations are always allowed for command line. As sometimes this leads to errors - for instance, incoming changes are not // TODO: displayed in "Incoming" tab - incoming changes are collected using command line but not displayed because // TODO: SvnVcs.isVcsBackgroundOperationsAllowed is false. ClientFactory factory = getFactory(VfsUtilCore.virtualToIoFile(root)); return factory == cmdClientFactory || ThreeState.YES.equals(myAuthNotifier.isAuthenticatedFor(root)); } public SvnBranchPointsCalculator getSvnBranchPointsCalculator() { return mySvnBranchPointsCalculator; } @Override public boolean areDirectoriesVersionedItems() { return true; } @Override public CheckoutProvider getCheckoutProvider() { if (myCheckoutProvider == null) { myCheckoutProvider = new SvnCheckoutProvider(); } return myCheckoutProvider; } @NotNull public SvnKitManager getSvnKitManager() { return svnKitManager; } public boolean isProject18OrGreater() { return getProjectRootFormat().isOrGreater(WorkingCopyFormat.ONE_DOT_EIGHT); } public boolean isProject16() { return WorkingCopyFormat.ONE_DOT_SIX.equals(getProjectRootFormat()); } @NotNull private WorkingCopyFormat getProjectRootFormat() { return !getProject().isDefault() ? getWorkingCopyFormat(new File(getProject().getBaseDir().getPath())) : WorkingCopyFormat.UNKNOWN; } /** * Detects appropriate client factory based on project root directory working copy format. * * Try to avoid usages of this method (for now) as it could not correctly for all cases * detect svn 1.8 working copy format to guarantee command line client. * * For instance, when working copies of several formats are presented in project * (though it seems to be rather unlikely case). * * @return */ @NotNull public ClientFactory getFactory() { return getFactory(getProjectRootFormat(), false); } @NotNull public ClientFactory getFactory(@NotNull WorkingCopyFormat format) { return getFactory(format, false); } @NotNull public ClientFactory getFactory(@NotNull File file) { return getFactory(file, true); } @NotNull public ClientFactory getFactory(@NotNull File file, boolean useMapping) { return getFactory(getWorkingCopyFormat(file, useMapping), true); } @NotNull private ClientFactory getFactory(@NotNull WorkingCopyFormat format, boolean useProjectRootForUnknown) { boolean is18OrGreater = format.isOrGreater(WorkingCopyFormat.ONE_DOT_EIGHT); boolean is16 = WorkingCopyFormat.ONE_DOT_SIX.equals(format); boolean isUnknown = WorkingCopyFormat.UNKNOWN.equals(format); return is18OrGreater ? cmdClientFactory : (is16 ? svnKitClientFactory : (useProjectRootForUnknown && isUnknown ? getFactory() : getFactoryFromSettings())); } @NotNull public ClientFactory getFactory(@NotNull SvnTarget target) { return target.isFile() ? getFactory(target.getFile()) : getFactory(); } @NotNull public ClientFactory getFactoryFromSettings() { return myConfiguration.isCommandLine() ? cmdClientFactory : svnKitClientFactory; } @NotNull public ClientFactory getOtherFactory() { return myConfiguration.isCommandLine() ? svnKitClientFactory : cmdClientFactory; } @NotNull public ClientFactory getOtherFactory(@NotNull ClientFactory factory) { return factory.equals(cmdClientFactory) ? svnKitClientFactory : cmdClientFactory; } @NotNull public ClientFactory getCommandLineFactory() { return cmdClientFactory; } @NotNull public ClientFactory getSvnKitFactory() { return svnKitClientFactory; } }