/* * Copyright 2000-2009 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.openapi.application.ApplicationManager; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.progress.ProcessCanceledException; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.Comparing; import com.intellij.openapi.util.Computable; import com.intellij.openapi.util.Ref; import com.intellij.openapi.util.io.FileUtil; import com.intellij.openapi.vcs.FilePath; import com.intellij.openapi.vcs.FilePathImpl; import com.intellij.openapi.vfs.LocalFileSystem; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.util.Processor; import com.intellij.vcsUtil.VcsUtil; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.idea.svn.api.Depth; import org.jetbrains.idea.svn.commandLine.SvnBindException; import org.jetbrains.idea.svn.status.Status; import org.jetbrains.idea.svn.status.StatusClient; import org.jetbrains.idea.svn.status.StatusConsumer; import org.jetbrains.idea.svn.status.StatusType; import org.tmatesoft.svn.core.SVNErrorCode; import org.tmatesoft.svn.core.SVNException; import org.tmatesoft.svn.core.internal.wc.SVNFileUtil; import org.tmatesoft.svn.core.wc.SVNRevision; import java.io.File; import java.util.LinkedList; public class SvnRecursiveStatusWalker { private static final Logger LOG = Logger.getInstance("#org.jetbrains.idea.svn.SvnRecursiveStatusWalker"); private final StatusWalkerPartner myPartner; private final SvnVcs myVcs; private final Project myProject; private final StatusReceiver myReceiver; private final LinkedList myQueue; private final MyHandler myHandler; public SvnRecursiveStatusWalker(final SvnVcs vcs, final StatusReceiver receiver, final StatusWalkerPartner partner) { myVcs = vcs; myProject = vcs.getProject(); myReceiver = receiver; myPartner = partner; myQueue = new LinkedList(); myHandler = new MyHandler(); } public void go(final FilePath rootPath, final Depth depth) throws SvnBindException { final MyItem root = createItem(rootPath, depth, false); myQueue.add(root); while (! myQueue.isEmpty()) { myPartner.checkCanceled(); final MyItem item = myQueue.removeFirst(); final FilePath path = item.getPath(); final File ioFile = path.getIOFile(); if (path.isDirectory()) { myHandler.setCurrentItem(item); try { final StatusClient client = item.getClient(); client.doStatus(ioFile, SVNRevision.WORKING, item.getDepth(), false, false, true, true, myHandler, null); myHandler.checkIfCopyRootWasReported(null, ioFile); } catch (SvnBindException e) { handleStatusException(item, path, e); } } else { try { final Status status = item.getClient().doStatus(ioFile, false); myReceiver.process(path, status); } catch (SvnBindException e) { handleStatusException(item, path, e); } catch (SVNException e) { handleStatusException(item, path, new SvnBindException(e)); } } } } private void handleStatusException(MyItem item, FilePath path, SvnBindException e) throws SvnBindException { if (e.contains(SVNErrorCode.WC_NOT_DIRECTORY) || e.contains(SVNErrorCode.WC_NOT_FILE)) { final VirtualFile virtualFile = path.getVirtualFile(); if (virtualFile != null) { if (! myPartner.isIgnoredByVcs(virtualFile)) { // self is unversioned myReceiver.processUnversioned(virtualFile); if (virtualFile.isDirectory()) { processRecursively(virtualFile, item.getDepth()); } } } } else { throw e; } } private static class MyItem { @NotNull private final FilePath myPath; @NotNull private final Depth myDepth; @NotNull private final StatusClient myStatusClient; private final boolean myIsInnerCopyRoot; private MyItem(@NotNull FilePath path, @NotNull Depth depth, boolean isInnerCopyRoot, @NotNull StatusClient statusClient) { myPath = path; myDepth = depth; myStatusClient = statusClient; myIsInnerCopyRoot = isInnerCopyRoot; } @NotNull public FilePath getPath() { return myPath; } @NotNull public Depth getDepth() { return myDepth; } @NotNull public StatusClient getClient() { return myStatusClient; } public boolean isIsInnerCopyRoot() { return myIsInnerCopyRoot; } } private void processRecursively(final VirtualFile vFile, final Depth prevDepth) { if (Depth.EMPTY.equals(prevDepth)) return; if (myPartner.isIgnoredIdeaLevel(vFile)) { myReceiver.processIgnored(vFile); return; } final Depth newDepth = Depth.INFINITY.equals(prevDepth) ? Depth.INFINITY : Depth.EMPTY; final File ioFile = new File(vFile.getPath()); final Processor processor; final Processor directoryFilter; final Ref lastIgnored = new Ref(); final Processor checkDirProcessor = new Processor() { @Override public boolean process(File file) { final FilePathImpl path = new FilePathImpl(file, true); path.refresh(); path.hardRefresh(); VirtualFile vf = path.getVirtualFile(); if (vf != null && myPartner.isIgnoredIdeaLevel(vf)) { lastIgnored.set(file); myReceiver.processIgnored(vf); return true; } if (file.isDirectory() && new File(file, SVNFileUtil.getAdminDirectoryName()).exists()) { final MyItem childItem = createItem(path, newDepth, true); myQueue.add(childItem); } else if (vf != null) { myReceiver.processUnversioned(vf); } return true; } }; if (Depth.EMPTY.equals(newDepth)) { // just process immediate children - so only root directory itself should satisfy filter directoryFilter = new Processor() { @Override public boolean process(File file) { return FileUtil.filesEqual(ioFile, file); } }; processor = new Processor() { @Override public boolean process(File file) { // TODO: check if we should still call checkDirProcessor() here - or we really could not check ignore settings but just call // TODO: myReceiver.processUnversioned() for all immediate children // here we deal only with immediate children - so ignored on IDEA level for children is not important return FileUtil.filesEqual(ioFile, file) || checkDirProcessor.process(file); } }; } else { directoryFilter = new Processor() { @Override public boolean process(File file) { return ! Comparing.equal(lastIgnored, file) && (myQueue.isEmpty() || ! FileUtil.filesEqual(myQueue.getLast().getPath().getIOFile(), file)); } }; processor = checkDirProcessor; } FileUtil.processFilesRecursively(ioFile, processor, directoryFilter); } @NotNull private MyItem createItem(@NotNull FilePath path, @NotNull Depth depth, boolean isInnerCopyRoot) { StatusClient statusClient = myVcs.getFactory(path.getIOFile()).createStatusClient(myPartner.getFileProvider(), myPartner.getEventHandler()); return new MyItem(path, depth, isInnerCopyRoot, statusClient); } private class MyHandler implements StatusConsumer { private MyItem myCurrentItem; private boolean myMetCurrentItem; public void setCurrentItem(MyItem currentItem) { myCurrentItem = currentItem; myMetCurrentItem = false; } public void checkIfCopyRootWasReported(@Nullable final Status ioFileStatus, final File ioFile) { File itemFile = myCurrentItem.getPath().getIOFile(); if (! myMetCurrentItem && FileUtil.filesEqual(ioFile, itemFile)) { myMetCurrentItem = true; Status statusInner; try { statusInner = ioFileStatus != null ? ioFileStatus : myCurrentItem.getClient().doStatus(itemFile, false); } catch (SvnBindException e) { LOG.info(e); statusInner = null; } if (statusInner == null) return; final StatusType status = statusInner.getNodeStatus(); final VirtualFile vf = myCurrentItem.getPath().getVirtualFile(); if (StatusType.STATUS_IGNORED.equals(status)) { if (vf != null) { myReceiver.processIgnored(vf); } return; } if (StatusType.STATUS_UNVERSIONED.equals(status) || StatusType.UNKNOWN.equals(status)) { if (vf != null) { myReceiver.processUnversioned(vf); processRecursively(vf, myCurrentItem.getDepth()); } return; } if (StatusType.OBSTRUCTED.equals(status) || StatusType.STATUS_NONE.equals(status)) { return; } if (vf != null) { if (myCurrentItem.isIsInnerCopyRoot()) { myReceiver.processCopyRoot(vf, statusInner.getURL(), myVcs.getWorkingCopyFormat(ioFile), statusInner.getRepositoryRootURL()); } else { myReceiver.bewareRoot(vf, statusInner.getURL()); } } } } @Override public void consume(final Status status) throws SVNException { myPartner.checkCanceled(); final File ioFile = status.getFile(); checkIfCopyRootWasReported(status, ioFile); final VirtualFile vFile = getVirtualFile(ioFile); if (vFile != null) { final Boolean excluded = ApplicationManager.getApplication().runReadAction(new Computable() { @Override public Boolean compute() { if (myProject.isDisposed()) return null; return myPartner.isIgnoredByVcs(vFile); } }); if (Boolean.TRUE.equals(excluded)) return; } if (myProject.isDisposed()) throw new ProcessCanceledException(); if ((vFile != null) && (status.is(StatusType.STATUS_UNVERSIONED))) { if (vFile.isDirectory()) { if (FileUtil.filesEqual(myCurrentItem.getPath().getIOFile(), ioFile)) { //myReceiver.processUnversioned(vFile); //processRecursively(vFile, myCurrentItem.getDepth()); } else { final MyItem childItem = createItem(new FilePathImpl(vFile), Depth.INFINITY, true); myQueue.add(childItem); } } else { myReceiver.processUnversioned(vFile); } } else { final FilePath path = VcsUtil.getFilePath(ioFile, status.getKind().isDirectory()); myReceiver.process(path, status); } } } private static VirtualFile getVirtualFile(File ioFile) { final LocalFileSystem lfs = LocalFileSystem.getInstance(); VirtualFile vFile = lfs.findFileByIoFile(ioFile); if (vFile == null) { vFile = lfs.refreshAndFindFileByIoFile(ioFile); } return vFile; } }