/*
* ====================================================================
* Copyright (c) 2004-2009 TMate Software Ltd. All rights reserved.
*
* This software is licensed as described in the file COPYING, which
* you should have received as part of this distribution. The terms
* are also available at http://svnkit.com/license.html.
* If newer versions of this license are posted there, you may use a
* newer version instead, at your option.
* ====================================================================
*/
package org.tmatesoft.svn.core.internal.wc;
import java.util.Iterator;
import java.util.Map;
import java.util.TreeMap;
import org.tmatesoft.svn.core.SVNErrorCode;
import org.tmatesoft.svn.core.SVNErrorMessage;
import org.tmatesoft.svn.core.SVNException;
import org.tmatesoft.svn.core.SVNMergeInfo;
import org.tmatesoft.svn.core.SVNMergeInfoInheritance;
import org.tmatesoft.svn.core.SVNMergeRangeList;
import org.tmatesoft.svn.core.SVNProperties;
import org.tmatesoft.svn.core.SVNProperty;
import org.tmatesoft.svn.core.internal.io.fs.FSEntry;
import org.tmatesoft.svn.core.internal.io.fs.FSFS;
import org.tmatesoft.svn.core.internal.io.fs.FSParentPath;
import org.tmatesoft.svn.core.internal.io.fs.FSRevisionNode;
import org.tmatesoft.svn.core.internal.io.fs.FSRevisionRoot;
import org.tmatesoft.svn.core.internal.util.SVNMergeInfoUtil;
import org.tmatesoft.svn.core.internal.util.SVNPathUtil;
import org.tmatesoft.svn.core.io.SVNRepository;
import org.tmatesoft.svn.util.SVNLogType;
/**
* @version 1.3
* @author TMate Software Ltd.
*/
public class SVNMergeInfoManager {
public Map getMergeInfo(String[] paths, FSRevisionRoot root, SVNMergeInfoInheritance inherit,
boolean includeDescendants) throws SVNException {
if (!root.getOwner().supportsMergeInfo()) {
SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.UNSUPPORTED_FEATURE,
"Querying mergeinfo requires version {0} of the FSFS filesystem schema;" +
" filesystem ''{1}'' uses only version {2}",
new Object[] { new Integer(FSFS.MIN_MERGE_INFO_FORMAT), root.getOwner().getDBRoot(),
new Integer(root.getOwner().getDBFormat()) });
SVNErrorManager.error(err, SVNLogType.FSFS);
}
Map mergeInfoAsHashes = getMergeInfoForPaths(root, paths, inherit, includeDescendants);
Map mergeInfo = new TreeMap();
for (Iterator mergeInfoIter = mergeInfoAsHashes.keySet().iterator(); mergeInfoIter.hasNext();) {
String path = (String) mergeInfoIter.next();
Map pathMergeInfo = (Map) mergeInfoAsHashes.get(path);
mergeInfo.put(path, new SVNMergeInfo(path, pathMergeInfo));
}
return mergeInfo;
}
private Map getMergeInfoForPaths(FSRevisionRoot root, String[] paths,
SVNMergeInfoInheritance inherit, boolean includeDescendants) throws SVNException {
Map result = new TreeMap();
for (int i = 0; i < paths.length; i++) {
String path = paths[i];
Map pathMergeInfo = getMergeInfoForPath(root, path, inherit);
if (pathMergeInfo != null) {
result.put(path, pathMergeInfo);
}
if (includeDescendants) {
addDescendantMergeInfo(result, root, path);
}
}
return result;
}
private void addDescendantMergeInfo(Map result, FSRevisionRoot root, String path) throws SVNException {
FSRevisionNode node = root.getRevisionNode(path);
if (node.hasDescendantsWithMergeInfo()) {
crawlDirectoryForMergeInfo(root, path, node, result);
}
}
private Map crawlDirectoryForMergeInfo(FSRevisionRoot root, String path, FSRevisionNode node,
Map result) throws SVNException {
FSFS fsfs = root.getOwner();
Map entries = node.getDirEntries(fsfs);
for (Iterator entriesIter = entries.values().iterator(); entriesIter.hasNext();) {
FSEntry entry = (FSEntry) entriesIter.next();
String kidPath = SVNPathUtil.getAbsolutePath(SVNPathUtil.append(path, entry.getName()));
FSRevisionNode kidNode = root.getRevisionNode(kidPath);
if (kidNode.hasMergeInfo()) {
SVNProperties propList = kidNode.getProperties(fsfs);
String mergeInfoString = propList.getStringValue(SVNProperty.MERGE_INFO);
if (mergeInfoString == null) {
SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.FS_CORRUPT,
"Node-revision #''{0}'' claims to have mergeinfo but doesn''t", entry.getId());
SVNErrorManager.error(err, SVNLogType.FSFS);
}
Map kidMergeInfo = SVNMergeInfoUtil.parseMergeInfo(new StringBuffer(mergeInfoString), null);
result.put(kidPath, kidMergeInfo);
}
if (kidNode.hasDescendantsWithMergeInfo()) {
crawlDirectoryForMergeInfo(root, kidPath, kidNode, result);
}
}
return result;
}
private Map getMergeInfoForPath(FSRevisionRoot revRoot, String path, SVNMergeInfoInheritance inherit) throws SVNException {
Map mergeInfo = null;
path = SVNPathUtil.canonicalizeAbsolutePath(path);
FSParentPath parentPath = revRoot.openPath(path, true, true);
if (inherit == SVNMergeInfoInheritance.NEAREST_ANCESTOR && parentPath.getParent() == null) {
return mergeInfo;
}
FSParentPath nearestAncestor = null;
if (inherit == SVNMergeInfoInheritance.NEAREST_ANCESTOR) {
nearestAncestor = parentPath.getParent();
} else {
nearestAncestor = parentPath;
}
FSFS fsfs = revRoot.getOwner();
while (true) {
boolean hasMergeInfo = nearestAncestor.getRevNode().hasMergeInfo();
if (hasMergeInfo) {
break;
}
if (inherit == SVNMergeInfoInheritance.EXPLICIT) {
return mergeInfo;
}
nearestAncestor = nearestAncestor.getParent();
if (nearestAncestor == null) {
return mergeInfo;
}
}
SVNProperties propList = nearestAncestor.getRevNode().getProperties(fsfs);
String mergeInfoString = propList.getStringValue(SVNProperty.MERGE_INFO);
if (mergeInfoString == null) {
SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.FS_CORRUPT,
"Node-revision ''{0}@{1}'' claims to have mergeinfo but doesn''t",
new Object[] { nearestAncestor.getAbsPath(), new Long(revRoot.getRevision()) });
SVNErrorManager.error(err, SVNLogType.FSFS);
}
if (nearestAncestor == parentPath) {
return SVNMergeInfoUtil.parseMergeInfo(new StringBuffer(mergeInfoString), null);
}
Map tmpMergeInfo = SVNMergeInfoUtil.parseMergeInfo(new StringBuffer(mergeInfoString), null);
tmpMergeInfo = SVNMergeInfoUtil.getInheritableMergeInfo(tmpMergeInfo, null,
SVNRepository.INVALID_REVISION, SVNRepository.INVALID_REVISION);
mergeInfo = appendToMergedFroms(tmpMergeInfo, parentPath.getRelativePath(nearestAncestor));
return mergeInfo;
}
private Map appendToMergedFroms(Map mergeInfo, String pathComponent) {
Map result = new TreeMap();
for (Iterator pathsIter = mergeInfo.keySet().iterator(); pathsIter.hasNext();) {
String path = (String) pathsIter.next();
SVNMergeRangeList rangeList = (SVNMergeRangeList) mergeInfo.get(path);
result.put(SVNPathUtil.append(path, pathComponent), rangeList.dup());
}
return result;
}
}