package org.tmatesoft.svn.core.internal.wc2.ng; import java.io.File; import java.util.HashMap; import java.util.Map; import java.util.TreeMap; import org.tmatesoft.svn.core.SVNDepth; import org.tmatesoft.svn.core.SVNErrorCode; 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.SVNNodeKind; import org.tmatesoft.svn.core.SVNProperties; import org.tmatesoft.svn.core.SVNProperty; import org.tmatesoft.svn.core.SVNPropertyValue; import org.tmatesoft.svn.core.SVNURL; import org.tmatesoft.svn.core.internal.util.SVNMergeInfoUtil; import org.tmatesoft.svn.core.internal.util.SVNPathUtil; import org.tmatesoft.svn.core.internal.wc.SVNEventFactory; import org.tmatesoft.svn.core.internal.wc.SVNFileUtil; import org.tmatesoft.svn.core.internal.wc17.SVNWCContext; import org.tmatesoft.svn.core.internal.wc17.SVNWCContext.CheckWCRootInfo; import org.tmatesoft.svn.core.internal.wc17.SVNWCContext.SVNWCNodeReposInfo; import org.tmatesoft.svn.core.internal.wc17.SVNWCUtils; import org.tmatesoft.svn.core.internal.wc17.db.ISVNWCDb.SVNWCDbKind; import org.tmatesoft.svn.core.internal.wc17.db.SVNWCDb; import org.tmatesoft.svn.core.internal.wc17.db.Structure; import org.tmatesoft.svn.core.internal.wc17.db.StructureFields.NodeOriginInfo; import org.tmatesoft.svn.core.internal.wc2.SvnRepositoryAccess; import org.tmatesoft.svn.core.io.SVNRepository; import org.tmatesoft.svn.core.wc.SVNEvent; import org.tmatesoft.svn.core.wc.SVNEventAction; import org.tmatesoft.svn.core.wc.SVNRevision; import org.tmatesoft.svn.core.wc.SVNStatusType; import org.tmatesoft.svn.core.wc2.ISvnObjectReceiver; import org.tmatesoft.svn.core.wc2.SvnTarget; public class SvnNgMergeinfoUtil { public static class SvnMergeInfoInfo { Map<String, SVNMergeRangeList> mergeinfo; boolean inherited; String walkRelPath; } public static class SvnMergeInfoCatalogInfo { Map<String, Map<String, SVNMergeRangeList>> catalog; boolean inherited; String walkRelPath; } private static Map<String, SVNMergeRangeList> parseMergeInfo(SVNWCContext context, File localAbsPath) throws SVNException { SVNPropertyValue propValue = context.getPropertyValue(localAbsPath, SVNProperty.MERGE_INFO); if (propValue != null && propValue.getString() != null) { return SVNMergeInfoUtil.parseMergeInfo(new StringBuffer(propValue.getString()), null); } return null; } public static void elideMergeInfo(SVNWCContext context, SVNRepository repos, File targetAbsPath, File limitAbsPath) throws SVNException { if (limitAbsPath == null || !limitAbsPath.equals(targetAbsPath)) { SvnMergeInfoInfo targetMergeinfo = null; try { targetMergeinfo = getWCMergeInfo(context, targetAbsPath, limitAbsPath, SVNMergeInfoInheritance.INHERITED, false); } catch (SVNException e) { if (e.getErrorMessage().getErrorCode() != SVNErrorCode.MERGE_INFO_PARSE_ERROR) { throw e; } return; } if (targetMergeinfo == null || targetMergeinfo.inherited || targetMergeinfo.mergeinfo == null) { return; } SvnMergeInfoInfo mergeinfo = null; try { mergeinfo = getWCMergeInfo(context, targetAbsPath, limitAbsPath, SVNMergeInfoInheritance.NEAREST_ANCESTOR, false); } catch (SVNException e) { if (e.getErrorMessage().getErrorCode() != SVNErrorCode.MERGE_INFO_PARSE_ERROR) { throw e; } return; } if ((mergeinfo == null || mergeinfo.mergeinfo == null) && limitAbsPath == null) { mergeinfo = new SvnMergeInfoInfo(); try { mergeinfo.mergeinfo = getWCOrReposMergeInfo(context, targetAbsPath, repos, true, SVNMergeInfoInheritance.NEAREST_ANCESTOR); } catch (SVNException e) { if (e.getErrorMessage().getErrorCode() != SVNErrorCode.MERGE_INFO_PARSE_ERROR) { throw e; } return; } } if (mergeinfo.mergeinfo == null && limitAbsPath != null) { return; } elideMergeInfo(context, mergeinfo.mergeinfo, targetMergeinfo.mergeinfo, targetAbsPath); } } private static void elideMergeInfo(SVNWCContext context, Map<String, SVNMergeRangeList> parent, Map<String, SVNMergeRangeList> child, File targetAbsPath) throws SVNException { if (SVNMergeInfoUtil.shouldElideMergeInfo(parent, child, null)) { SvnNgPropertiesManager.setProperty(context, targetAbsPath, SVNProperty.MERGE_INFO, null, SVNDepth.EMPTY, true, null, null); if (context.getEventHandler() != null) { SVNEvent event = SVNEventFactory.createSVNEvent(targetAbsPath, SVNNodeKind.UNKNOWN, null, -1, SVNStatusType.INAPPLICABLE, SVNStatusType.INAPPLICABLE, SVNStatusType.LOCK_INAPPLICABLE, SVNEventAction.MERGE_ELIDE_INFO, null, null, null, null); context.getEventHandler().handleEvent(event, -1); event = SVNEventFactory.createSVNEvent(targetAbsPath, SVNNodeKind.UNKNOWN, null, -1, SVNStatusType.INAPPLICABLE, SVNStatusType.CHANGED, SVNStatusType.LOCK_INAPPLICABLE, SVNEventAction.UPDATE_UPDATE, null, null, null, null); context.getEventHandler().handleEvent(event, -1); } } } public static SvnMergeInfoInfo getWCMergeInfo(SVNWCContext context, File localAbsPath, File limitAbsPath, SVNMergeInfoInheritance inheritance, boolean ignoreInvalidMergeInfo) throws SVNException { long baseRevision = context.getNodeBaseRev(localAbsPath); Map<String, SVNMergeRangeList> wcMergeInfo = null; String walkRelPath = ""; SvnMergeInfoInfo result = new SvnMergeInfoInfo(); while(true) { if (inheritance == SVNMergeInfoInheritance.NEAREST_ANCESTOR) { wcMergeInfo = null; inheritance = SVNMergeInfoInheritance.INHERITED; } else { try { wcMergeInfo = parseMergeInfo(context, localAbsPath); } catch (SVNException e) { if (e.getErrorMessage().getErrorCode() == SVNErrorCode.MERGE_INFO_PARSE_ERROR) { if (ignoreInvalidMergeInfo || !"".equals(walkRelPath)) { wcMergeInfo = new HashMap<String, SVNMergeRangeList>(); break; } } throw e; } } if (wcMergeInfo == null && inheritance != SVNMergeInfoInheritance.EXPLICIT && SVNFileUtil.getParentFile(localAbsPath) != null) { if (limitAbsPath != null && localAbsPath.equals(limitAbsPath)) { break; } CheckWCRootInfo rootInfo = context.checkWCRoot(localAbsPath, true); if (rootInfo.wcRoot || rootInfo.switched) { break; } walkRelPath = SVNPathUtil.append(SVNFileUtil.getFileName(localAbsPath), walkRelPath); localAbsPath = SVNFileUtil.getFileDir(localAbsPath); long parentBaseRev = context.getNodeBaseRev(localAbsPath); long parentChangedRev = context.getNodeChangedInfo(localAbsPath).changedRev; if (baseRevision >= 0 && (baseRevision < parentChangedRev || parentBaseRev < baseRevision)) { break; } continue; } break; } if ("".equals(walkRelPath)) { result.inherited = false; result.mergeinfo = wcMergeInfo; } else { if (wcMergeInfo != null) { result.inherited = true; result.mergeinfo = new HashMap<String, SVNMergeRangeList>(); result.mergeinfo = SVNMergeInfoUtil.adjustMergeInfoSourcePaths(result.mergeinfo, walkRelPath, wcMergeInfo); } else { result.inherited = false; result.mergeinfo = null; } } result.walkRelPath = walkRelPath; if (result.inherited && !result.mergeinfo.isEmpty()) { result.mergeinfo = SVNMergeInfoUtil.getInheritableMergeInfo(result.mergeinfo, null, -1, -1); SVNMergeInfoUtil.removeEmptyRangeLists(result.mergeinfo); } return result; } private static SvnMergeInfoCatalogInfo getWcMergeInfoCatalog(SVNWCContext context, boolean includeDescendants, SVNMergeInfoInheritance inheritance, File localAbsPath, File limitAbsPath, boolean ignoreInvalidMergeInfo) throws SVNException { SvnMergeInfoCatalogInfo result = new SvnMergeInfoCatalogInfo(); SVNWCNodeReposInfo reposInfo = context.getNodeReposInfo(localAbsPath); if (reposInfo.reposRootUrl == null) { result.walkRelPath = ""; return result; } File targetReposRelPath = context.getNodeReposRelPath(localAbsPath); SvnMergeInfoInfo mi = getWCMergeInfo(context, localAbsPath, limitAbsPath, inheritance, ignoreInvalidMergeInfo); result.walkRelPath = mi.walkRelPath; result.inherited = mi.inherited; if (mi.mergeinfo != null) { result.catalog = new TreeMap<String, Map<String,SVNMergeRangeList>>(); result.catalog.put(SVNFileUtil.getFilePath(targetReposRelPath), mi.mergeinfo); } if (context.readKind(localAbsPath, false) == SVNNodeKind.DIR && includeDescendants) { // recursive propget do. final Map<File, String> mergeInfoProperties = new TreeMap<File, String>(); ((SVNWCDb) context.getDb()).readPropertiesRecursively(localAbsPath, SVNDepth.INFINITY, false, false, null, new ISvnObjectReceiver<SVNProperties>() { public void receive(SvnTarget target, SVNProperties object) throws SVNException { if (object.getStringValue(SVNProperty.MERGE_INFO) != null) { mergeInfoProperties.put(target.getFile(), object.getStringValue(SVNProperty.MERGE_INFO)); } } }); for (File childPath : mergeInfoProperties.keySet()) { String propValue = mergeInfoProperties.get(childPath); File keyPath = context.getNodeReposRelPath(childPath); Map<String, SVNMergeRangeList> childMergeInfo = SVNMergeInfoUtil.parseMergeInfo(new StringBuffer(propValue), null); if (result.catalog == null) { result.catalog = new TreeMap<String, Map<String,SVNMergeRangeList>>(); } result.catalog.put(SVNFileUtil.getFilePath(keyPath), childMergeInfo); } } return result; } private static SvnMergeInfoCatalogInfo getReposMergeInfoCatalog(SVNRepository repository, String relativePath, long revision, SVNMergeInfoInheritance inheritance, boolean squelchIncapable, boolean includeDescendats) throws SVNException { SvnMergeInfoCatalogInfo result = new SvnMergeInfoCatalogInfo(); Map<String, SVNMergeInfo> reposMeregInfo = null; try { reposMeregInfo = repository.getMergeInfo(new String[] {relativePath}, revision, inheritance, includeDescendats); } catch (SVNException e) { if (e.getErrorMessage().getErrorCode() == SVNErrorCode.UNSUPPORTED_FEATURE && squelchIncapable) { return result; } throw e; } if (reposMeregInfo != null && !reposMeregInfo.isEmpty()) { result.catalog = new TreeMap<String, Map<String,SVNMergeRangeList>>(); for (String reposRelativePath : reposMeregInfo.keySet()) { SVNMergeInfo mi = reposMeregInfo.get(reposRelativePath); if (reposRelativePath.startsWith("/")) { reposRelativePath = reposRelativePath.substring(1); } result.catalog.put(reposRelativePath, mi.getMergeSourcesToMergeLists()); } } return result; } static SvnMergeInfoCatalogInfo getWcOrReposMergeInfoCatalog(SVNWCContext context, SVNRepository repository, File wcPath, boolean includeDescendants, boolean reposOnly, boolean ignoreInvalidMergeInfo, SVNMergeInfoInheritance inheritance) throws SVNException { SvnMergeInfoCatalogInfo result = new SvnMergeInfoCatalogInfo(); Structure<NodeOriginInfo> nodeOriginInfo = context.getNodeOrigin(wcPath, false, NodeOriginInfo.revision, NodeOriginInfo.reposRelpath, NodeOriginInfo.reposRootUrl); SVNURL url = null; if (nodeOriginInfo.get(NodeOriginInfo.reposRelpath) != null) { url = nodeOriginInfo.get(NodeOriginInfo.reposRootUrl); url = SVNWCUtils.join(url, nodeOriginInfo.<File>get(NodeOriginInfo.reposRelpath)); } long revision = nodeOriginInfo.lng(NodeOriginInfo.revision); File reposRelPath = nodeOriginInfo.<File>get(NodeOriginInfo.reposRelpath); nodeOriginInfo.release(); Map<String, Map<String, SVNMergeRangeList>> wcMergeInfoCatalog = null; Map<String, Map<String, SVNMergeRangeList>> reposMergeInfoCatalog = null; if (!reposOnly) { SvnMergeInfoCatalogInfo catalogInfo = getWcMergeInfoCatalog(context, includeDescendants, inheritance, wcPath, null, ignoreInvalidMergeInfo); result.inherited = catalogInfo.inherited; wcMergeInfoCatalog = catalogInfo.catalog; if (!(catalogInfo.inherited || (inheritance == SVNMergeInfoInheritance.EXPLICIT) || (reposRelPath != null && wcMergeInfoCatalog != null && wcMergeInfoCatalog.get(SVNFileUtil.getFilePath(reposRelPath)) != null))) { reposOnly = true; includeDescendants = false; } } if (reposOnly && url != null) { SVNProperties originalProperties = context.getPristineProps(wcPath); if (!originalProperties.containsName(SVNProperty.MERGE_INFO)) { SVNURL oldLocation = repository.getLocation(); try { repository.setLocation(url, false); SvnMergeInfoCatalogInfo catalogInfo = getReposMergeInfoCatalog(repository, "", revision, inheritance, true, includeDescendants); reposMergeInfoCatalog = catalogInfo.catalog; if (reposMergeInfoCatalog != null && reposMergeInfoCatalog.containsKey(SVNFileUtil.getFilePath(reposRelPath))) { result.inherited = true; } } finally { repository.setLocation(oldLocation, false); } } } if (wcMergeInfoCatalog != null) { result.catalog = wcMergeInfoCatalog; if (reposMergeInfoCatalog != null) { SVNMergeInfoUtil.mergeCatalog(result.catalog, reposMergeInfoCatalog); } } else if (reposMergeInfoCatalog != null) { result.catalog = reposMergeInfoCatalog; } return result; } public static Map<String, Map<String, SVNMergeRangeList>> getMergeInfo(SVNWCContext context, SvnRepositoryAccess repoAccess, SvnTarget target, boolean includeDescendants, boolean ignoreInvalidMergeInfo, SVNURL[] root) throws SVNException { Structure<SvnRepositoryAccess.RepositoryInfo> repositoryInfo = repoAccess.createRepositoryFor(target, SVNRevision.UNDEFINED, target.getPegRevision(), null); SVNURL url = repositoryInfo.get(SvnRepositoryAccess.RepositoryInfo.url); long pegRev = repositoryInfo.lng(SvnRepositoryAccess.RepositoryInfo.revision); long rev = -1; SVNRepository repository = repositoryInfo.get(SvnRepositoryAccess.RepositoryInfo.repository); repositoryInfo.release(); boolean useURL = target.isURL(); if (!useURL) { SVNURL originURL = null; Structure<NodeOriginInfo> nodeOriginInfo = context.getNodeOrigin(target.getFile(), false, NodeOriginInfo.revision, NodeOriginInfo.reposRelpath, NodeOriginInfo.reposRootUrl); File reposRelPath = nodeOriginInfo.get(NodeOriginInfo.reposRelpath); if (reposRelPath != null) { originURL = nodeOriginInfo.get(NodeOriginInfo.reposRootUrl); originURL = SVNWCUtils.join(originURL, reposRelPath); } rev = nodeOriginInfo.lng(NodeOriginInfo.revision); if (originURL == null || !originURL.equals(url) || pegRev != rev) { useURL = true; } nodeOriginInfo.release(); } if (root != null && root.length > 0) { root[0] = repository.getRepositoryRoot(true); } if (useURL) { rev = pegRev; return getReposMergeInfoCatalog(repository, "", rev, SVNMergeInfoInheritance.INHERITED, false, includeDescendants).catalog; } else { return getWcOrReposMergeInfoCatalog(context, repository, target.getFile(), includeDescendants, false, ignoreInvalidMergeInfo, SVNMergeInfoInheritance.INHERITED).catalog; } } public static Map<String, SVNMergeRangeList> getWCOrReposMergeInfo(SVNWCContext context, File wcPath, SVNRepository repository, boolean reposOnly, SVNMergeInfoInheritance inheritance) throws SVNException { SvnMergeInfoCatalogInfo catalog = getWcOrReposMergeInfoCatalog(context, repository, wcPath, false, reposOnly, false, inheritance); if (catalog != null && catalog.catalog != null) { return catalog.catalog.values().iterator().next(); } return null; } public static Map<String, Map<String, SVNMergeRangeList>> convertToCatalog(Map<String, SVNMergeInfo> catalog) { if (catalog == null) { return new TreeMap<String, Map<String,SVNMergeRangeList>>(); } Map<String, Map<String, SVNMergeRangeList>> result = new TreeMap<String, Map<String,SVNMergeRangeList>>(); for (String path : catalog.keySet()) { SVNMergeInfo mi = catalog.get(path); result.put(path, mi.getMergeSourcesToMergeLists()); } return result; } public static Map<File, Map<String, SVNMergeRangeList>> convertToCatalog2(Map<String, SVNMergeInfo> catalog) { if (catalog == null) { return new TreeMap<File, Map<String,SVNMergeRangeList>>(); } Map<File, Map<String, SVNMergeRangeList>> result = new TreeMap<File, Map<String,SVNMergeRangeList>>(); for (String path : catalog.keySet()) { SVNMergeInfo mi = catalog.get(path); if (path.startsWith("/")) { path = path.substring(1); } result.put(new File(path.replace(File.separatorChar, '/')), mi.getMergeSourcesToMergeLists()); } return result; } public static Map<String, Map<String, SVNMergeRangeList>> addPrefixToCatalog(Map<String, Map<String, SVNMergeRangeList>> catalog, File prefix) { Map<String, Map<String, SVNMergeRangeList>> result = new TreeMap<String, Map<String,SVNMergeRangeList>>(); for (String path : catalog.keySet()) { Map<String, SVNMergeRangeList> mi = catalog.get(path); if (path.startsWith("/")) { path = path.substring(1); } String prefixedPath = SVNFileUtil.getFilePath(SVNFileUtil.createFilePath(prefix, path)); result.put(prefixedPath, mi); } return result; } }