| /* |
| * 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.diagnostic.Logger; |
| import com.intellij.openapi.vcs.FilePath; |
| import com.intellij.openapi.vcs.VcsException; |
| import com.intellij.openapi.vcs.actions.VcsContextFactory; |
| import com.intellij.openapi.vcs.changes.ContentRevision; |
| import com.intellij.openapi.vcs.diff.DiffMixin; |
| import com.intellij.openapi.vcs.diff.DiffProvider; |
| import com.intellij.openapi.vcs.diff.DiffProviderEx; |
| import com.intellij.openapi.vcs.diff.ItemLatestState; |
| import com.intellij.openapi.vcs.history.VcsRevisionDescription; |
| import com.intellij.openapi.vcs.history.VcsRevisionDescriptionImpl; |
| import com.intellij.openapi.vcs.history.VcsRevisionNumber; |
| import com.intellij.openapi.vfs.VfsUtilCore; |
| import com.intellij.openapi.vfs.VirtualFile; |
| import com.intellij.util.containers.ContainerUtil; |
| import org.jetbrains.annotations.NotNull; |
| import org.jetbrains.annotations.Nullable; |
| import org.jetbrains.idea.svn.commandLine.SvnBindException; |
| import org.jetbrains.idea.svn.history.LatestExistentSearcher; |
| import org.jetbrains.idea.svn.info.Info; |
| import org.jetbrains.idea.svn.info.InfoConsumer; |
| import org.jetbrains.idea.svn.properties.PropertyValue; |
| import org.jetbrains.idea.svn.status.Status; |
| import org.jetbrains.idea.svn.status.StatusType; |
| import org.tmatesoft.svn.core.SVNException; |
| import org.tmatesoft.svn.core.SVNURL; |
| import org.tmatesoft.svn.core.wc.SVNRevision; |
| import org.tmatesoft.svn.core.wc2.SvnTarget; |
| |
| import java.io.File; |
| import java.util.List; |
| import java.util.Map; |
| |
| public class SvnDiffProvider extends DiffProviderEx implements DiffProvider, DiffMixin { |
| private static final Logger LOG = Logger.getInstance("#org.jetbrains.idea.svn.SvnDiffProvider"); |
| public static final String COMMIT_MESSAGE = "svn:log"; |
| private static final int BATCH_INFO_SIZE = 20; |
| |
| private final SvnVcs myVcs; |
| |
| public SvnDiffProvider(final SvnVcs vcs) { |
| myVcs = vcs; |
| } |
| |
| public VcsRevisionNumber getCurrentRevision(VirtualFile file) { |
| final Info svnInfo = myVcs.getInfo(new File(file.getPresentableUrl())); |
| |
| return getRevision(svnInfo); |
| } |
| |
| @Nullable |
| private static VcsRevisionNumber getRevision(@Nullable Info info) { |
| VcsRevisionNumber result = null; |
| |
| if (info != null) { |
| SVNRevision revision = SVNRevision.UNDEFINED.equals(info.getCommittedRevision()) && info.getCopyFromRevision() != null |
| ? info.getCopyFromRevision() |
| : info.getRevision(); |
| |
| result = new SvnRevisionNumber(revision); |
| } |
| |
| return result; |
| } |
| |
| @Override |
| public Map<VirtualFile, VcsRevisionNumber> getCurrentRevisions(Iterable<VirtualFile> files) { |
| Map<VirtualFile, VcsRevisionNumber> result = ContainerUtil.newHashMap(); |
| Map<String, VirtualFile> items = ContainerUtil.newHashMap(); |
| List<File> ioFiles = ContainerUtil.newArrayList(); |
| |
| for (VirtualFile file : files) { |
| File ioFile = VfsUtilCore.virtualToIoFile(file); |
| ioFiles.add(ioFile); |
| items.put(ioFile.getAbsolutePath(), file); |
| |
| // process in blocks of BATCH_INFO_SIZE size |
| if (items.size() == BATCH_INFO_SIZE) { |
| collectRevisionsInBatch(result, items, ioFiles); |
| items.clear(); |
| ioFiles.clear(); |
| } |
| } |
| // process left files |
| collectRevisionsInBatch(result, items, ioFiles); |
| |
| return result; |
| } |
| |
| private void collectRevisionsInBatch(@NotNull final Map<VirtualFile, VcsRevisionNumber> revisionMap, |
| @NotNull final Map<String, VirtualFile> fileMap, |
| @NotNull List<File> ioFiles) { |
| myVcs.collectInfo(ioFiles, createInfoHandler(revisionMap, fileMap)); |
| } |
| |
| @NotNull |
| private static InfoConsumer createInfoHandler(@NotNull final Map<VirtualFile, VcsRevisionNumber> revisionMap, |
| @NotNull final Map<String, VirtualFile> fileMap) { |
| return new InfoConsumer() { |
| @Override |
| public void consume(Info info) throws SVNException { |
| if (info != null) { |
| VirtualFile file = fileMap.get(info.getFile().getAbsolutePath()); |
| |
| if (file != null) { |
| revisionMap.put(file, getRevision(info)); |
| } |
| else { |
| LOG.info("Could not find virtual file for path " + info.getFile().getAbsolutePath()); |
| } |
| } |
| } |
| }; |
| } |
| |
| @Override |
| public VcsRevisionDescription getCurrentRevisionDescription(VirtualFile file) { |
| File path = new File(file.getPresentableUrl()); |
| return getCurrentRevisionDescription(path); |
| } |
| |
| private VcsRevisionDescription getCurrentRevisionDescription(File path) { |
| final Info svnInfo = myVcs.getInfo(path); |
| if (svnInfo == null) { |
| return null; |
| } |
| |
| if (svnInfo.getCommittedRevision().equals(SVNRevision.UNDEFINED) && !svnInfo.getCopyFromRevision().equals(SVNRevision.UNDEFINED) && |
| svnInfo.getCopyFromURL() != null) { |
| SVNURL copyUrl = svnInfo.getCopyFromURL(); |
| String localPath = myVcs.getSvnFileUrlMapping().getLocalPath(copyUrl.toString()); |
| if (localPath != null) { |
| return getCurrentRevisionDescription(new File(localPath)); |
| } |
| } |
| |
| try { |
| final String message = getCommitMessage(path); |
| return new VcsRevisionDescriptionImpl(new SvnRevisionNumber(svnInfo.getCommittedRevision()), svnInfo.getCommittedDate(), |
| svnInfo.getAuthor(), message); |
| } |
| catch (VcsException e) { |
| LOG.info(e); // most likely the file is unversioned |
| return null; |
| } |
| } |
| |
| @Nullable |
| private String getCommitMessage(File path) throws VcsException { |
| PropertyValue property = |
| myVcs.getFactory(path).createPropertyClient().getProperty(SvnTarget.fromFile(path), COMMIT_MESSAGE, true, SVNRevision.BASE); |
| |
| return PropertyValue.toString(property); |
| } |
| |
| private static ItemLatestState defaultResult() { |
| return createResult(SVNRevision.HEAD, true, true); |
| } |
| |
| private static ItemLatestState createResult(final SVNRevision revision, final boolean exists, boolean defaultHead) { |
| return new ItemLatestState(new SvnRevisionNumber(revision), exists, defaultHead); |
| } |
| |
| public ItemLatestState getLastRevision(VirtualFile file) { |
| return getLastRevision(new File(file.getPath())); |
| } |
| |
| public ContentRevision createFileContent(final VcsRevisionNumber revisionNumber, final VirtualFile selectedFile) { |
| FilePath filePath = VcsContextFactory.SERVICE.getInstance().createFilePathOn(selectedFile); |
| final SVNRevision svnRevision = ((SvnRevisionNumber)revisionNumber).getRevision(); |
| |
| if (! SVNRevision.HEAD.equals(svnRevision)) { |
| if (revisionNumber.equals(getCurrentRevision(selectedFile))) { |
| return SvnContentRevision.createBaseRevision(myVcs, filePath, svnRevision); |
| } |
| } |
| |
| // not clear why we need it, with remote check.. |
| Status svnStatus = getFileStatus(new File(selectedFile.getPresentableUrl()), false); |
| if (svnStatus != null && svnRevision.equals(svnStatus.getRevision())) { |
| return SvnContentRevision.createBaseRevision(myVcs, filePath, svnRevision); |
| } |
| return SvnContentRevision.createRemote(myVcs, filePath, svnRevision); |
| } |
| |
| private Status getFileStatus(File file, boolean remote) { |
| Status result = null; |
| |
| try { |
| result = myVcs.getFactory(file).createStatusClient().doStatus(file, remote); |
| } |
| catch (SvnBindException e) { |
| LOG.debug(e); |
| } |
| |
| return result; |
| } |
| |
| public ItemLatestState getLastRevision(FilePath filePath) { |
| return getLastRevision(filePath.getIOFile()); |
| } |
| |
| public VcsRevisionNumber getLatestCommittedRevision(VirtualFile vcsRoot) { |
| // todo |
| return null; |
| } |
| |
| private ItemLatestState getLastRevision(final File file) { |
| final Status svnStatus = getFileStatus(file, true); |
| if (svnStatus == null || itemExists(svnStatus) && SVNRevision.UNDEFINED.equals(svnStatus.getRemoteRevision())) { |
| // IDEADEV-21785 (no idea why this can happen) |
| final Info info = myVcs.getInfo(file, SVNRevision.HEAD); |
| if (info == null || info.getURL() == null) { |
| LOG.info("No SVN status returned for " + file.getPath()); |
| return defaultResult(); |
| } |
| return createResult(info.getCommittedRevision(), true, false); |
| } |
| final boolean exists = itemExists(svnStatus); |
| if (! exists) { |
| WorkingCopyFormat format = myVcs.getWorkingCopyFormat(file); |
| long revision = -1; |
| |
| // skipped for >= 1.8 |
| if (format.less(WorkingCopyFormat.ONE_DOT_EIGHT)) { |
| // get really latest revision |
| // TODO: Algorithm seems not to be correct in all cases - for instance, when some subtree was deleted and replaced by other |
| // TODO: with same names. pegRevision should be used somehow but this complicates the algorithm |
| if (svnStatus.getRepositoryRootURL() != null) { |
| revision = new LatestExistentSearcher(myVcs, svnStatus.getURL(), svnStatus.getRepositoryRootURL()).getDeletionRevision(); |
| } |
| else { |
| LOG.info("Could not find repository url for file " + file); |
| } |
| } |
| |
| return createResult(SVNRevision.create(revision), exists, false); |
| } |
| final SVNRevision remoteRevision = svnStatus.getRemoteRevision(); |
| if (remoteRevision != null) { |
| return createResult(remoteRevision, exists, false); |
| } |
| return createResult(svnStatus.getRevision(), exists, false); |
| } |
| |
| private boolean itemExists(Status svnStatus) { |
| return ! StatusType.STATUS_DELETED.equals(svnStatus.getRemoteContentsStatus()) && |
| ! StatusType.STATUS_DELETED.equals(svnStatus.getRemoteNodeStatus()); |
| } |
| } |