blob: 01f9a213907c063ca477c5c2bbf4cbd9c5adc35d [file] [log] [blame]
/*
* 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.
*/
/*
* Created by IntelliJ IDEA.
* User: yole
* Date: 28.11.2006
* Time: 17:20:32
*/
package org.jetbrains.idea.svn.history;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vcs.AbstractVcs;
import com.intellij.openapi.vcs.FilePath;
import com.intellij.openapi.vcs.FilePathImpl;
import com.intellij.openapi.vcs.VcsException;
import com.intellij.openapi.vcs.changes.*;
import com.intellij.openapi.vcs.versionBrowser.CommittedChangeList;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.util.ConstantFunction;
import com.intellij.util.NotNullFunction;
import com.intellij.util.UriUtil;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.vcsUtil.VcsUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.idea.svn.*;
import org.jetbrains.idea.svn.api.Depth;
import org.jetbrains.idea.svn.browse.DirectoryEntry;
import org.jetbrains.idea.svn.browse.DirectoryEntryConsumer;
import org.jetbrains.idea.svn.commandLine.SvnBindException;
import org.jetbrains.idea.svn.info.Info;
import org.tmatesoft.svn.core.*;
import org.tmatesoft.svn.core.internal.util.SVNPathUtil;
import org.tmatesoft.svn.core.wc.SVNRevision;
import org.tmatesoft.svn.core.wc2.SvnTarget;
import java.io.DataInput;
import java.io.DataOutput;
import java.io.File;
import java.io.IOException;
import java.util.*;
public class SvnChangeList implements CommittedChangeList {
private static final Logger LOG = Logger.getInstance("#org.jetbrains.idea.svn.history");
private final SvnVcs myVcs;
private final SvnRepositoryLocation myLocation;
private String myRepositoryRoot;
private long myRevision;
private String myAuthor;
private Date myDate;
private String myMessage;
private final Set<String> myChangedPaths = new HashSet<String>();
private final Set<String> myAddedPaths = new HashSet<String>();
private final Set<String> myDeletedPaths = new HashSet<String>();
private final Set<String> myReplacedPaths = new HashSet<String>();
private ChangesListCreationHelper myListsHolder;
private SVNURL myBranchUrl;
private boolean myCachedInfoLoaded;
// key: added path, value: copied-from
private final TreeMap<String, String> myCopiedAddedPaths = new TreeMap<String, String>();
private RootUrlInfo myWcRoot;
private final CommonPathSearcher myCommonPathSearcher;
private final Set<String> myKnownAsDirectories;
public SvnChangeList(@NotNull final List<CommittedChangeList> lists, @NotNull final SvnRepositoryLocation location) {
final SvnChangeList sample = (SvnChangeList) lists.get(0);
myVcs = sample.myVcs;
myLocation = location;
myRevision = sample.myRevision;
myAuthor = sample.myAuthor;
myDate = sample.myDate;
myMessage = sample.myMessage;
myRepositoryRoot = sample.myRepositoryRoot;
myCommonPathSearcher = new CommonPathSearcher();
for (CommittedChangeList list : lists) {
final SvnChangeList svnList = (SvnChangeList) list;
myChangedPaths.addAll(svnList.myChangedPaths);
myAddedPaths.addAll(svnList.myAddedPaths);
myDeletedPaths.addAll(svnList.myDeletedPaths);
myReplacedPaths.addAll(svnList.myReplacedPaths);
}
myKnownAsDirectories = new HashSet<String>(0);
}
public SvnChangeList(SvnVcs vcs, @NotNull final SvnRepositoryLocation location, final LogEntry logEntry, String repositoryRoot) {
myVcs = vcs;
myLocation = location;
myRevision = logEntry.getRevision();
myAuthor = StringUtil.notNullize(logEntry.getAuthor());
myDate = logEntry.getDate();
myMessage = StringUtil.notNullize(logEntry.getMessage());
myRepositoryRoot = UriUtil.trimTrailingSlashes(repositoryRoot);
myCommonPathSearcher = new CommonPathSearcher();
myKnownAsDirectories = new HashSet<String>(0);
for(LogEntryPath entry : logEntry.getChangedPaths().values()) {
final String path = entry.getPath();
if (entry.isDirectory()) {
myKnownAsDirectories.add(path);
}
myCommonPathSearcher.next(path);
if (entry.getType() == 'A') {
if (entry.getCopyPath() != null) {
myCopiedAddedPaths.put(path, entry.getCopyPath());
}
myAddedPaths.add(path);
}
else if (entry.getType() == 'D') {
myDeletedPaths.add(path);
}
else {
if (entry.getType() == 'R') {
myReplacedPaths.add(path);
}
myChangedPaths.add(path);
}
}
}
public SvnChangeList(SvnVcs vcs, @NotNull SvnRepositoryLocation location, DataInput stream, final boolean supportsCopyFromInfo,
final boolean supportsReplaced) throws IOException {
myVcs = vcs;
myLocation = location;
myKnownAsDirectories = new HashSet<String>();
readFromStream(stream, supportsCopyFromInfo, supportsReplaced);
myCommonPathSearcher = new CommonPathSearcher();
myCommonPathSearcher.next(myAddedPaths);
myCommonPathSearcher.next(myDeletedPaths);
myCommonPathSearcher.next(myChangedPaths);
}
public Change getByPath(final String path) {
if (myListsHolder == null) {
createLists();
}
return myListsHolder.getByPath(path);
}
public String getCommitterName() {
return myAuthor;
}
public Date getCommitDate() {
return myDate;
}
public Collection<Change> getChanges() {
if (myListsHolder == null) {
createLists();
}
return myListsHolder.getList();
}
private void createLists() {
myListsHolder = new ChangesListCreationHelper();
// key: copied-from
final Map<String, ExternallyRenamedChange> copiedAddedChanges = new HashMap<String, ExternallyRenamedChange>();
correctBeforePaths();
final List<String> copyDeleted = new ArrayList<String>(myDeletedPaths);
for(String path: myAddedPaths) {
final Change addedChange;
if (myCopiedAddedPaths.containsKey(path)) {
final String copyTarget = myCopiedAddedPaths.get(path);
if (copyDeleted.contains(copyTarget)) {
addedChange = new ExternallyRenamedChange(myListsHolder.createRevisionLazily(copyTarget, true),
myListsHolder.createRevisionLazily(path, false), copyTarget);
addedChange.getMoveRelativePath(myVcs.getProject());
((ExternallyRenamedChange) addedChange).setCopied(false);
copyDeleted.remove(copyTarget);
} else {
addedChange = new ExternallyRenamedChange(null, myListsHolder.createRevisionLazily(path, false), copyTarget);
}
copiedAddedChanges.put(copyTarget, (ExternallyRenamedChange) addedChange);
} else {
addedChange = new Change(null, myListsHolder.createRevisionLazily(path, false));
}
myListsHolder.add(path, addedChange);
}
for(String path: copyDeleted) {
final Change deletedChange;
if (copiedAddedChanges.containsKey(path)) {
// seems never occurs any more
final ExternallyRenamedChange addedChange = copiedAddedChanges.get(path);
final FilePath source = addedChange.getAfterRevision().getFile();
deletedChange = new ExternallyRenamedChange(myListsHolder.createDeletedItemRevision(path, true), null, path);
((ExternallyRenamedChange) deletedChange).setCopied(false);
//noinspection ConstantConditions
//addedChange.setRenamedOrMovedTarget(deletedChange.getBeforeRevision().getFile());
//noinspection ConstantConditions
((ExternallyRenamedChange) deletedChange).setRenamedOrMovedTarget(source);
} else {
deletedChange = new Change(myListsHolder.createDeletedItemRevision(path, true), null);
}
myListsHolder.add(path, deletedChange);
}
for(String path: myChangedPaths) {
boolean moveAndChange = false;
final boolean replaced = myReplacedPaths.contains(path);
// this piece: for copied-from (or moved) and further modified
for (String addedPath : myAddedPaths) {
String copyFromPath = myCopiedAddedPaths.get(addedPath);
if ((copyFromPath != null) && (SVNPathUtil.isAncestor(addedPath, path))) {
if (addedPath.length() < path.length()) {
final String relative = SVNPathUtil.getRelativePath(addedPath, path);
copyFromPath = SVNPathUtil.append(copyFromPath, relative);
}
final ExternallyRenamedChange renamedChange = new ExternallyRenamedChange(myListsHolder.createRevisionLazily(copyFromPath, true),
myListsHolder.createRevisionLazily(path, false), copyFromPath);
moveAndChange = true;
renamedChange.getMoveRelativePath(myVcs.getProject());
renamedChange.setIsReplaced(replaced);
final ExternallyRenamedChange addedChange = copiedAddedChanges.get(myCopiedAddedPaths.get(addedPath));
renamedChange.setCopied(addedChange != null && addedChange.isCopied());
myListsHolder.add(path, renamedChange);
break;
}
}
if (! moveAndChange) {
final ExternallyRenamedChange renamedChange =
new ExternallyRenamedChange(myListsHolder.createRevisionLazily(path, true), myListsHolder.createRevisionLazily(path, false),
null);
renamedChange.setIsReplaced(replaced);
renamedChange.setCopied(false);
myListsHolder.add(path, renamedChange);
}
}
}
private void correctBeforePaths() {
processDeletedForBeforePaths(myDeletedPaths);
processModifiedForBeforePaths(myChangedPaths);
processModifiedForBeforePaths(myReplacedPaths);
}
private void processModifiedForBeforePaths(Set<String> paths) {
final RenameHelper helper = new RenameHelper();
for (String s : paths) {
final String converted = helper.convertBeforePath(s, myCopiedAddedPaths);
if (! s.equals(converted)) {
myCopiedAddedPaths.put(s, converted);
}
}
}
private void processDeletedForBeforePaths(Set<String> paths) {
final RenameHelper helper = new RenameHelper();
final HashSet<String> copy = new HashSet<String>(paths);
paths.clear();
for (String s : copy) {
paths.add(helper.convertBeforePath(s, myCopiedAddedPaths));
}
}
@Nullable
private FilePath getLocalPath(final String path, final NotNullFunction<File, Boolean> detector) {
return SvnRepositoryLocation.getLocalPath(myRepositoryRoot + path, detector, myVcs);
}
private long getRevision(final boolean isBeforeRevision) {
return isBeforeRevision ? (myRevision - 1) : myRevision;
}
public SvnRepositoryLocation getLocation() {
return myLocation;
}
/**
* needed to track in which changes non-local files live
*/
private class ChangesListCreationHelper {
private final List<Change> myList;
private final Map<String, Change> myPathToChangeMapping;
private List<Change> myDetailedList;
private final List<Pair<Integer, Boolean>> myWithoutDirStatus;
private ChangesListCreationHelper() {
myList = new ArrayList<Change>();
myWithoutDirStatus = new ArrayList<Pair<Integer, Boolean>>();
myPathToChangeMapping = new HashMap<String, Change>();
}
public void add(final String path, final Change change) {
patchChange(change, path);
myList.add(change);
myPathToChangeMapping.put(path, change);
}
public Change getByPath(final String path) {
return myPathToChangeMapping.get(path);
}
private FilePath localDeletedPath(final String fullPath, final boolean isDir) {
final SvnFileUrlMapping urlMapping = myVcs.getSvnFileUrlMapping();
final String path = urlMapping.getLocalPath(fullPath);
if (path != null) {
File file = new File(path);
return VcsUtil.getFilePathForDeletedFile(path, isDir || file.isDirectory());
}
return null;
}
public SvnRepositoryContentRevision createDeletedItemRevision(final String path, final boolean isBeforeRevision) {
final boolean knownAsDirectory = myKnownAsDirectories.contains(path);
final String fullPath = myRepositoryRoot + path;
if (! knownAsDirectory) {
myWithoutDirStatus.add(Pair.create(myList.size(), isBeforeRevision));
}
return SvnRepositoryContentRevision.create(myVcs, myRepositoryRoot, path, localDeletedPath(fullPath, knownAsDirectory),
getRevision(isBeforeRevision));
}
public SvnRepositoryContentRevision createRevisionLazily(final String path, final boolean isBeforeRevision) {
final boolean knownAsDirectory = myKnownAsDirectories.contains(path);
final FilePath localPath = getLocalPath(path, new NotNullFunction<File, Boolean>() {
@NotNull
public Boolean fun(final File file) {
if (knownAsDirectory) return Boolean.TRUE;
// list will be next
myWithoutDirStatus.add(new Pair<Integer, Boolean>(myList.size(), isBeforeRevision));
return Boolean.FALSE;
}
});
final SvnRepositoryContentRevision contentRevision =
SvnRepositoryContentRevision.create(myVcs, myRepositoryRoot, path, localPath, getRevision(isBeforeRevision));
if (knownAsDirectory) {
((FilePathImpl) contentRevision.getFile()).setIsDirectory(true);
}
return contentRevision;
}
public List<Change> getList() {
return myList;
}
public List<Change> getDetailedList() {
if (myDetailedList == null) {
myDetailedList = new ArrayList<Change>(myList);
try {
doRemoteDetails();
uploadDeletedRenamedChildren();
ContainerUtil.removeDuplicates(myDetailedList);
}
catch (SVNException e) {
LOG.info(e);
}
catch (VcsException e) {
LOG.info(e);
}
}
return myDetailedList;
}
private void doRemoteDetails() throws SVNException, SvnBindException {
for (Pair<Integer, Boolean> idxData : myWithoutDirStatus) {
final Change sourceChange = myDetailedList.get(idxData.first.intValue());
final SvnRepositoryContentRevision revision = (SvnRepositoryContentRevision)
(idxData.second.booleanValue() ? sourceChange.getBeforeRevision() : sourceChange.getAfterRevision());
if (revision == null) {
continue;
}
// TODO: Logic with detecting "isDirectory" status is not clear enough. Why we can't just collect this info from logEntry and
// TODO: if loading from disk - use cached values? Not to invoke separate call here.
SVNRevision beforeRevision = SVNRevision.create(getRevision(idxData.second.booleanValue()));
Info info = myVcs.getInfo(SvnUtil.createUrl(revision.getFullPath()), beforeRevision, beforeRevision);
boolean isDirectory = info != null && info.isDirectory();
Change replacingChange = new Change(createRevision((SvnRepositoryContentRevision)sourceChange.getBeforeRevision(), isDirectory),
createRevision((SvnRepositoryContentRevision)sourceChange.getAfterRevision(), isDirectory));
replacingChange.setIsReplaced(sourceChange.isIsReplaced());
myDetailedList.set(idxData.first.intValue(), replacingChange);
}
myWithoutDirStatus.clear();
}
@Nullable
private SvnRepositoryContentRevision createRevision(final SvnRepositoryContentRevision previousRevision, final boolean isDir) {
return previousRevision == null ? null :
SvnRepositoryContentRevision.create(myVcs, previousRevision.getFullPath(),
new FilePathImpl(previousRevision.getFile().getIOFile(), isDir),
((SvnRevisionNumber)previousRevision.getRevisionNumber()).getRevision().getNumber());
}
private void uploadDeletedRenamedChildren() throws VcsException {
Set<Pair<Boolean, String>> duplicates = collectDuplicates();
List<Change> preprocessed = ChangesPreprocess.preprocessChangesRemoveDeletedForDuplicateMoved(myDetailedList);
myDetailedList.addAll(collectDetails(preprocessed, duplicates));
}
private List<Change> collectDetails(@NotNull List<Change> changes, @NotNull Set<Pair<Boolean, String>> duplicates)
throws VcsException {
List<Change> result = ContainerUtil.newArrayList();
for (Change change : changes) {
// directory statuses are already uploaded
if ((change.getAfterRevision() == null) && (change.getBeforeRevision().getFile().isDirectory())) {
result.addAll(getChildrenAsChanges(change.getBeforeRevision(), true, duplicates));
} else if ((change.getBeforeRevision() == null) && (change.getAfterRevision().getFile().isDirectory())) {
// look for renamed folders contents
if (myCopiedAddedPaths.containsKey(getRelativePath(change.getAfterRevision()))) {
result.addAll(getChildrenAsChanges(change.getAfterRevision(), false, duplicates));
}
} else if ((change.isIsReplaced() || change.isMoved() || change.isRenamed()) && change.getAfterRevision().getFile().isDirectory()) {
result.addAll(getChildrenAsChanges(change.getBeforeRevision(), true, duplicates));
result.addAll(getChildrenAsChanges(change.getAfterRevision(), false, duplicates));
}
}
return result;
}
private Set<Pair<Boolean, String>> collectDuplicates() {
Set<Pair<Boolean, String>> result = ContainerUtil.newHashSet();
for (Change change : myDetailedList) {
addDuplicate(result, true, change.getBeforeRevision());
addDuplicate(result, false, change.getAfterRevision());
}
return result;
}
private void addDuplicate(@NotNull Set<Pair<Boolean, String>> duplicates,
boolean isBefore,
@Nullable ContentRevision revision) {
if (revision != null) {
duplicates.add(Pair.create(isBefore, getRelativePath(revision)));
}
}
@NotNull
private String getRelativePath(@NotNull ContentRevision revision) {
return ((SvnRepositoryContentRevision)revision).getRelativePath(myRepositoryRoot);
}
@NotNull
private Collection<Change> getChildrenAsChanges(@NotNull ContentRevision contentRevision,
final boolean isBefore,
@NotNull final Set<Pair<Boolean, String>> duplicates)
throws VcsException {
final List<Change> result = new ArrayList<Change>();
final String path = getRelativePath(contentRevision);
SVNURL fullPath = SvnUtil.createUrl(((SvnRepositoryContentRevision)contentRevision).getFullPath());
SVNRevision revisionNumber = SVNRevision.create(getRevision(isBefore));
SvnTarget target = SvnTarget.fromURL(fullPath, revisionNumber);
myVcs.getFactory(target).createBrowseClient().list(target, revisionNumber, Depth.INFINITY, new DirectoryEntryConsumer() {
@Override
public void consume(final DirectoryEntry entry) throws SVNException {
final String childPath = path + '/' + entry.getRelativePath();
if (!duplicates.contains(Pair.create(isBefore, childPath))) {
final ContentRevision contentRevision = createRevision(childPath, isBefore, entry.isDirectory());
result.add(new Change(isBefore ? contentRevision : null, isBefore ? null : contentRevision));
}
}
});
return result;
}
private SvnRepositoryContentRevision createRevision(final String path, final boolean isBeforeRevision, final boolean isDir) {
return SvnRepositoryContentRevision.create(myVcs, myRepositoryRoot, path,
getLocalPath(path, new ConstantFunction<File, Boolean>(isDir)), getRevision(isBeforeRevision));
}
}
private static class RenameHelper {
public String convertBeforePath(final String path, final TreeMap<String, String> after2before) {
String current = path;
// backwards
for (String key : after2before.descendingKeySet()) {
if (SVNPathUtil.isAncestor(key, current)) {
final String relativePath = SVNPathUtil.getRelativePath(key, current);
current = SVNPathUtil.append(after2before.get(key), relativePath);
}
}
return current;
}
}
private void patchChange(Change change, final String path) {
final SVNURL becameUrl;
SVNURL wasUrl;
try {
becameUrl = SVNURL.parseURIEncoded(SVNPathUtil.append(myRepositoryRoot, path));
wasUrl = becameUrl;
if (change instanceof ExternallyRenamedChange && change.getBeforeRevision() != null) {
String originUrl = ((ExternallyRenamedChange)change).getOriginUrl();
if (originUrl != null) {
// use another url for origin
wasUrl = SVNURL.parseURIEncoded(SVNPathUtil.append(myRepositoryRoot, originUrl));
}
}
}
catch (SVNException e) {
// nothing to do
LOG.info(e);
return;
}
final FilePath filePath = ChangesUtil.getFilePath(change);
final Change additional = new Change(createPropertyRevision(filePath, change.getBeforeRevision(), wasUrl),
createPropertyRevision(filePath, change.getAfterRevision(), becameUrl));
change.addAdditionalLayerElement(SvnChangeProvider.PROPERTY_LAYER, additional);
}
@Nullable
private SvnLazyPropertyContentRevision createPropertyRevision(@NotNull FilePath filePath,
@Nullable ContentRevision revision,
@NotNull SVNURL url) {
return revision == null ? null : new SvnLazyPropertyContentRevision(filePath, revision.getRevisionNumber(), myVcs.getProject(), url);
}
@NotNull
public String getName() {
return myMessage;
}
public String getComment() {
return myMessage;
}
public long getNumber() {
return myRevision;
}
@Override
public String getBranch() {
return null;
}
public AbstractVcs getVcs() {
return myVcs;
}
public Collection<Change> getChangesWithMovedTrees() {
if (myListsHolder == null) {
createLists();
}
return myListsHolder.getDetailedList();
}
@Override
public boolean isModifiable() {
return true;
}
@Override
public void setDescription(String newMessage) {
myMessage = newMessage;
}
public boolean equals(final Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
final SvnChangeList that = (SvnChangeList)o;
if (myRevision != that.myRevision) return false;
if (myAuthor != null ? !myAuthor.equals(that.myAuthor) : that.myAuthor != null) return false;
if (myDate != null ? !myDate.equals(that.myDate) : that.myDate != null) return false;
if (myMessage != null ? !myMessage.equals(that.myMessage) : that.myMessage != null) return false;
return true;
}
public int hashCode() {
int result;
result = (int)(myRevision ^ (myRevision >>> 32));
result = 31 * result + (myAuthor != null ? myAuthor.hashCode() : 0);
result = 31 * result + (myDate != null ? myDate.hashCode() : 0);
result = 31 * result + (myMessage != null ? myMessage.hashCode() : 0);
return result;
}
public String toString() {
return myMessage;
}
public void writeToStream(final DataOutput stream) throws IOException {
stream.writeUTF(myRepositoryRoot);
stream.writeLong(myRevision);
stream.writeUTF(myAuthor);
stream.writeLong(myDate.getTime());
writeUTFTruncated(stream, myMessage);
writeFiles(stream, myChangedPaths);
writeFiles(stream, myAddedPaths);
writeFiles(stream, myDeletedPaths);
writeMap(stream, myCopiedAddedPaths);
writeFiles(stream, myReplacedPaths);
stream.writeInt(myKnownAsDirectories.size());
for (String directory : myKnownAsDirectories) {
stream.writeUTF(directory);
}
}
// to be able to update plugin only
public static void writeUTFTruncated(final DataOutput stream, final String text) throws IOException {
// we should not compare number of symbols to 65635 -> it is number of bytes what should be compared
// ? 4 bytes per symbol - rough estimation
if (text.length() > 16383) {
stream.writeUTF(text.substring(0, 16383));
}
else {
stream.writeUTF(text);
}
}
private static void writeFiles(final DataOutput stream, final Set<String> paths) throws IOException {
stream.writeInt(paths.size());
for(String s: paths) {
stream.writeUTF(s);
}
}
private void readFromStream(final DataInput stream, final boolean supportsCopyFromInfo, final boolean supportsReplaced) throws IOException {
myRepositoryRoot = stream.readUTF();
myRevision = stream.readLong();
myAuthor = stream.readUTF();
myDate = new Date(stream.readLong());
myMessage = stream.readUTF();
readFiles(stream, myChangedPaths);
readFiles(stream, myAddedPaths);
readFiles(stream, myDeletedPaths);
if (supportsCopyFromInfo) {
readMap(stream, myCopiedAddedPaths);
}
if (supportsReplaced) {
readFiles(stream, myReplacedPaths);
}
final int size = stream.readInt();
for (int i = 0; i < size; i++) {
myKnownAsDirectories.add(stream.readUTF());
}
}
private static void writeMap(final DataOutput stream, final Map<String, String> map) throws IOException {
stream.writeInt(map.size());
for (Map.Entry<String, String> entry : map.entrySet()) {
stream.writeUTF(entry.getKey());
stream.writeUTF(entry.getValue());
}
}
private static void readMap(final DataInput stream, final Map<String, String> map) throws IOException {
int count = stream.readInt();
for (int i = 0; i < count; i++) {
map.put(stream.readUTF(), stream.readUTF());
}
}
private static void readFiles(final DataInput stream, final Set<String> paths) throws IOException {
int count = stream.readInt();
for(int i=0; i<count; i++) {
paths.add(stream.readUTF());
}
}
public SVNURL getBranchUrl() {
ensureCacheUpdated();
return myBranchUrl;
}
@Nullable
public VirtualFile getVcsRoot() {
ensureCacheUpdated();
return myWcRoot == null ? null : myWcRoot.getRoot();
}
@Nullable
public VirtualFile getRoot() {
ensureCacheUpdated();
return myWcRoot == null ? null : myWcRoot.getVirtualFile();
}
public RootUrlInfo getWcRootInfo() {
ensureCacheUpdated();
return myWcRoot;
}
private void ensureCacheUpdated() {
if (!myCachedInfoLoaded) {
updateCachedInfo();
}
}
private static class CommonPathSearcher {
private String myCommon;
public void next(Iterable<String> values) {
for (String value : values) {
next(value);
}
}
public void next(final String value) {
if (value == null) {
return;
}
if (myCommon == null) {
myCommon = value;
return;
}
if (value.startsWith(myCommon)) {
return;
}
myCommon = SVNPathUtil.getCommonPathAncestor(myCommon, value);
}
public String getCommon() {
return myCommon;
}
}
private void updateCachedInfo() {
myCachedInfoLoaded = true;
final String commonPath = myCommonPathSearcher.getCommon();
if (commonPath != null) {
final SvnFileUrlMapping urlMapping = myVcs.getSvnFileUrlMapping();
if (urlMapping.isEmpty()) {
myCachedInfoLoaded = false;
return;
}
final String absoluteUrl = SVNPathUtil.append(myRepositoryRoot, commonPath);
myWcRoot = urlMapping.getWcRootForUrl(absoluteUrl);
if (myWcRoot != null) {
myBranchUrl = SvnUtil.getBranchForUrl(myVcs, myWcRoot.getVirtualFile(), absoluteUrl);
}
}
}
public void forceReloadCachedInfo(final boolean reloadRoot) {
myCachedInfoLoaded = false;
myBranchUrl = null;
if (reloadRoot) {
myWcRoot = null;
}
}
public Set<String> getChangedPaths() {
return myChangedPaths;
}
public Set<String> getAddedPaths() {
return myAddedPaths;
}
public Set<String> getDeletedPaths() {
return myDeletedPaths;
}
@Nullable
public String getWcPath() {
final RootUrlInfo rootInfo = getWcRootInfo();
return rootInfo == null ? null : rootInfo.getIoFile().getAbsolutePath();
}
public boolean allPathsUnder(final String path) {
final String commonRelative = myCommonPathSearcher.getCommon();
return commonRelative != null && SVNPathUtil.isAncestor(path, SVNPathUtil.append(myRepositoryRoot, commonRelative));
}
}