/* * ==================================================================== * Copyright (c) 2004-2012 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.util; import java.io.File; import java.io.OutputStream; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.TreeMap; import org.tmatesoft.svn.core.SVNCommitInfo; import org.tmatesoft.svn.core.SVNErrorCode; import org.tmatesoft.svn.core.SVNErrorMessage; import org.tmatesoft.svn.core.SVNException; import org.tmatesoft.svn.core.SVNMergeRange; import org.tmatesoft.svn.core.SVNMergeRangeList; import org.tmatesoft.svn.core.SVNProperty; import org.tmatesoft.svn.core.SVNPropertyValue; import org.tmatesoft.svn.core.internal.wc.ISVNCommitPathHandler; import org.tmatesoft.svn.core.internal.wc.SVNCommitUtil; import org.tmatesoft.svn.core.internal.wc.SVNErrorManager; import org.tmatesoft.svn.core.internal.wc.SVNFileUtil; import org.tmatesoft.svn.core.internal.wc.SVNPropertiesManager; import org.tmatesoft.svn.core.internal.wc.admin.SVNWCAccess; import org.tmatesoft.svn.core.io.ISVNEditor; import org.tmatesoft.svn.core.io.SVNRepository; import org.tmatesoft.svn.core.io.diff.SVNDiffWindow; import org.tmatesoft.svn.core.wc.SVNRevision; import org.tmatesoft.svn.util.SVNLogType; /** * @version 1.3 * @author TMate Software Ltd. */ public class SVNMergeInfoUtil { public static void mergeCatalog(Map catalog, Map changes) throws SVNException { int i = 0; int j = 0; Object[] catalogKeys = catalog.keySet().toArray(); Object[] changesKeys = changes.keySet().toArray(); while(i < catalog.size() && j < changes.size()) { Comparable catalogKey = (Comparable) catalogKeys[i]; Comparable changeKey = (Comparable) changesKeys[j]; int compare = catalogKey.compareTo(changeKey); if (compare == 0) { Map catalogMergeinfo = (Map) catalog.get(catalogKey); Map changesMergeinfo= (Map) changes.get(changeKey); Map mergedMergeinfo = mergeMergeInfos(catalogMergeinfo, changesMergeinfo); catalog.put(catalogKey, mergedMergeinfo); i++; j++; } else if (compare < 0) { i++; } else { Map changesMergeinfo= (Map) changes.get(changeKey); changesMergeinfo = dupMergeInfo(changesMergeinfo, null); catalog.put(changeKey, changesMergeinfo); j++; } } for(; j < changes.size(); j++) { Object changeKey = changesKeys[j]; Map changesMergeinfo= (Map) changes.get(changeKey); changesMergeinfo = dupMergeInfo(changesMergeinfo, null); catalog.put(changeKey, changesMergeinfo); } } public static Map<String, Map<String,SVNMergeRangeList>> filterCatalogByRanges(Map<String, Map<String,SVNMergeRangeList>> catalog, long youngestRev, long oldestRev) { Map<String, Map<String,SVNMergeRangeList>> filteredCatalog = new TreeMap<String, Map<String,SVNMergeRangeList>>(); for (Iterator<String> catalogIter = catalog.keySet().iterator(); catalogIter.hasNext();) { String path = catalogIter.next(); Map<String, SVNMergeRangeList> mergeInfo = catalog.get(path); Map<String, SVNMergeRangeList> filteredMergeInfo = filterMergeInfoByRanges(mergeInfo, youngestRev, oldestRev); if (!filteredMergeInfo.isEmpty()) { filteredCatalog.put(path, filteredMergeInfo); } } return filteredCatalog; } public static Map<String, SVNMergeRangeList> filterMergeInfoByRanges(Map<String, SVNMergeRangeList> mergeInfo, long youngestRev, long oldestRev) { Map<String, SVNMergeRangeList> filteredMergeInfo = new TreeMap<String, SVNMergeRangeList>(); if (mergeInfo != null) { SVNMergeRange range = new SVNMergeRange(oldestRev, youngestRev, true); SVNMergeRangeList filterRangeList = new SVNMergeRangeList(range); for (Iterator<String> mergeInfoIter = mergeInfo.keySet().iterator(); mergeInfoIter.hasNext();) { String path = mergeInfoIter.next(); SVNMergeRangeList rangeList = mergeInfo.get(path); if (!rangeList.isEmpty()) { SVNMergeRangeList newRangeList = filterRangeList.intersect(rangeList, false); if (!newRangeList.isEmpty()) { filteredMergeInfo.put(path, newRangeList); } } } } return filteredMergeInfo; } public static long[] getRangeEndPoints(Map<?, SVNMergeRangeList> mergeInfo) { //long[] { youngestRange, oldestRange } long[] rangePoints = { SVNRepository.INVALID_REVISION, SVNRepository.INVALID_REVISION }; if (mergeInfo != null) { for (Iterator<?> mergeInfoIter = mergeInfo.keySet().iterator(); mergeInfoIter.hasNext();) { Object path = mergeInfoIter.next(); SVNMergeRangeList rangeList = (SVNMergeRangeList) mergeInfo.get(path); if (!rangeList.isEmpty()) { SVNMergeRange[] ranges = rangeList.getRanges(); SVNMergeRange range = ranges[ranges.length - 1]; if (!SVNRevision.isValidRevisionNumber(rangePoints[0]) || range.getEndRevision() > rangePoints[0]) { rangePoints[0] = range.getEndRevision(); } range = ranges[0]; if (!SVNRevision.isValidRevisionNumber(rangePoints[1]) || rangePoints[1] > range.getStartRevision()) { rangePoints[1] = range.getStartRevision(); } } } } return rangePoints; } public static Map<String, Map<String, SVNMergeRangeList>> elideMergeInfoCatalog(Map<String, Map<String, SVNMergeRangeList>> mergeInfoCatalog) throws SVNException { Map<String, Map<String, SVNMergeRangeList>> adjustedMergeInfoCatalog = new TreeMap<String, Map<String, SVNMergeRangeList>>(); for (Iterator<String> pathsIter = mergeInfoCatalog.keySet().iterator(); pathsIter.hasNext();) { String path = pathsIter.next(); String adjustedPath = path; if (path.startsWith("/")) { adjustedPath = path.substring(1); } adjustedMergeInfoCatalog.put(adjustedPath, mergeInfoCatalog.get(path)); } mergeInfoCatalog = adjustedMergeInfoCatalog; ElideMergeInfoCatalogHandler handler = new ElideMergeInfoCatalogHandler(mergeInfoCatalog); ElideMergeInfoEditor editor = new ElideMergeInfoEditor(mergeInfoCatalog); SVNCommitUtil.driveCommitEditor(handler, mergeInfoCatalog.keySet(), editor, -1); List elidablePaths = handler.getElidablePaths(); for (Iterator elidablePathsIter = elidablePaths.iterator(); elidablePathsIter.hasNext();) { String elidablePath = (String) elidablePathsIter.next(); mergeInfoCatalog.remove(elidablePath); } adjustedMergeInfoCatalog = new TreeMap<String, Map<String, SVNMergeRangeList>>(); for (Iterator<String> pathsIter = mergeInfoCatalog.keySet().iterator(); pathsIter.hasNext();) { String path = (String) pathsIter.next(); String adjustedPath = path; if (!path.startsWith("/")) { adjustedPath = "/" + adjustedPath; } adjustedMergeInfoCatalog.put(adjustedPath, mergeInfoCatalog.get(path)); } return adjustedMergeInfoCatalog; } public static Map<String, SVNMergeRangeList> adjustMergeInfoSourcePaths(Map<String, SVNMergeRangeList> mergeInfo, String walkPath, Map<String, SVNMergeRangeList> wcMergeInfo) { mergeInfo = mergeInfo == null ? new TreeMap<String, SVNMergeRangeList>() : mergeInfo; for (Iterator<String> paths = wcMergeInfo.keySet().iterator(); paths.hasNext();) { String srcMergePath = paths.next(); SVNMergeRangeList rangeList = (SVNMergeRangeList) wcMergeInfo.get(srcMergePath); mergeInfo.put(SVNPathUtil.getAbsolutePath(SVNPathUtil.append(srcMergePath, walkPath)), rangeList); } return mergeInfo; } public static boolean removeEmptyRangeLists(Map mergeInfo) { boolean removedSomeRanges = false; if (mergeInfo != null) { for (Iterator mergeInfoIter = mergeInfo.entrySet().iterator(); mergeInfoIter.hasNext();) { Map.Entry mergeInfoEntry = (Map.Entry) mergeInfoIter.next(); SVNMergeRangeList rangeList = (SVNMergeRangeList) mergeInfoEntry.getValue(); if (rangeList.isEmpty()) { mergeInfoIter.remove(); removedSomeRanges = true; } } } return removedSomeRanges; } public static Map<String, SVNMergeRangeList> mergeMergeInfos(Map<String, SVNMergeRangeList> originalSrcsToRangeLists, Map<String, SVNMergeRangeList> changedSrcsToRangeLists) throws SVNException { originalSrcsToRangeLists = originalSrcsToRangeLists == null ? new TreeMap() : originalSrcsToRangeLists; changedSrcsToRangeLists = changedSrcsToRangeLists == null ? Collections.EMPTY_MAP : changedSrcsToRangeLists; String[] paths1 = (String[]) originalSrcsToRangeLists.keySet().toArray(new String[originalSrcsToRangeLists.size()]); String[] paths2 = (String[]) changedSrcsToRangeLists.keySet().toArray(new String[changedSrcsToRangeLists.size()]); int i = 0; int j = 0; while (i < paths1.length && j < paths2.length) { String path1 = paths1[i]; String path2 = paths2[j]; int res = path1.compareTo(path2); if (res == 0) { SVNMergeRangeList rangeList1 = (SVNMergeRangeList) originalSrcsToRangeLists.get(path1); SVNMergeRangeList rangeList2 = (SVNMergeRangeList) changedSrcsToRangeLists.get(path2); rangeList1 = rangeList1.merge(rangeList2); originalSrcsToRangeLists.put(path1, rangeList1); i++; j++; } else if (res < 0) { i++; } else { originalSrcsToRangeLists.put(path2, changedSrcsToRangeLists.get(path2)); j++; } } for (; j < paths2.length; j++) { String path = paths2[j]; originalSrcsToRangeLists.put(path, changedSrcsToRangeLists.get(path)); } return originalSrcsToRangeLists; } public static String combineMergeInfoProperties(String propValue1, String propValue2) throws SVNException { Map srcsToRanges1 = parseMergeInfo(new StringBuffer(propValue1), null); Map srcsToRanges2 = parseMergeInfo(new StringBuffer(propValue2), null); srcsToRanges1 = mergeMergeInfos(srcsToRanges1, srcsToRanges2); return formatMergeInfoToString(srcsToRanges1, null); } public static String combineForkedMergeInfoProperties(String fromPropValue, String workingPropValue, String toPropValue) throws SVNException { Map leftDeleted = new TreeMap(); Map leftAdded = new TreeMap(); Map fromMergeInfo = parseMergeInfo(new StringBuffer(fromPropValue), null); diffMergeInfoProperties(leftDeleted, leftAdded, null, fromMergeInfo, workingPropValue, null); Map rightDeleted = new TreeMap(); Map rightAdded = new TreeMap(); diffMergeInfoProperties(rightDeleted, rightAdded, fromPropValue, null, toPropValue, null); leftDeleted = mergeMergeInfos(leftDeleted, rightDeleted); leftAdded = mergeMergeInfos(leftAdded, rightAdded); fromMergeInfo = mergeMergeInfos(fromMergeInfo, leftAdded); Map result = removeMergeInfo(leftDeleted, fromMergeInfo); return formatMergeInfoToString(result, null); } public static void diffMergeInfoProperties(Map deleted, Map added, String fromPropValue, Map fromMergeInfo, String toPropValue, Map toMergeInfo) throws SVNException { if (fromPropValue != null && fromPropValue.equals(toPropValue)) { return; } fromMergeInfo = fromMergeInfo == null ? parseMergeInfo(new StringBuffer(fromPropValue), null) : fromMergeInfo; toMergeInfo = toMergeInfo == null ? parseMergeInfo(new StringBuffer(toPropValue), null) : toMergeInfo; diffMergeInfo(deleted, added, fromMergeInfo, toMergeInfo, true); } public static void diffMergeInfo(Map deleted, Map added, Map from, Map to, boolean considerInheritance) { from = from == null ? Collections.EMPTY_MAP : from; to = to == null ? Collections.EMPTY_MAP : to; if (!from.isEmpty() && to.isEmpty()) { dupMergeInfo(from, deleted); } else if (from.isEmpty() && !to.isEmpty()) { dupMergeInfo(to, added); } else if (!from.isEmpty() && !to.isEmpty()) { walkMergeInfoHashForDiff(deleted, added, from, to, considerInheritance); } } public static Map dupCatalog(Map catalog) { Map newMergeInfoCatalog = new TreeMap(); for (Iterator catalogIter = catalog.keySet().iterator(); catalogIter.hasNext();) { String path = (String) catalogIter.next(); Map mergeInfo = (Map) catalog.get(path); Map mergeInfoCopy = dupMergeInfo(mergeInfo, null); newMergeInfoCatalog.put(path, mergeInfoCopy); } return newMergeInfoCatalog; } public static Map dupMergeInfo(Map srcsToRangeLists, Map target) { if (srcsToRangeLists == null) { return null; } target = target == null ? new TreeMap() : target; for (Iterator paths = srcsToRangeLists.keySet().iterator(); paths.hasNext();) { String path = (String) paths.next(); SVNMergeRangeList rangeList = (SVNMergeRangeList) srcsToRangeLists.get(path); target.put(path, rangeList.dup()); } return target; } public static boolean isNonInheritable(Map<String, SVNMergeRangeList> mergeInfo) { if (mergeInfo != null) { for (String path : mergeInfo.keySet()) { SVNMergeRangeList rangeList = mergeInfo.get(path); SVNMergeRange[] ranges = rangeList.getRanges(); for (int i = 0; ranges != null && i < ranges.length; i++) { if (ranges[i] != null && !ranges[i].isInheritable()) { return true; } } } } return false; } public static Map<String, SVNMergeRangeList> parseMergeInfo(StringBuffer mergeInfo, Map<String, SVNMergeRangeList> srcPathsToRangeLists) throws SVNException { srcPathsToRangeLists = srcPathsToRangeLists == null ? new TreeMap<String, SVNMergeRangeList>() : srcPathsToRangeLists; if (mergeInfo.length() == 0) { return srcPathsToRangeLists; } try { while (mergeInfo.length() > 0) { int eolInd = mergeInfo.indexOf("\n"); eolInd = eolInd < 0 ? mergeInfo.length() - 1 : eolInd; int ind = mergeInfo.lastIndexOf(":", eolInd); if (ind == -1) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.MERGE_INFO_PARSE_ERROR, "Pathname not terminated by ':'"); SVNErrorManager.error(err, SVNLogType.DEFAULT); } if (ind == 0) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.MERGE_INFO_PARSE_ERROR, "No pathname preceding ':'"); SVNErrorManager.error(err, SVNLogType.DEFAULT); } String path = null; if (mergeInfo.charAt(0) =='/') { path = mergeInfo.substring(0, ind); } else { String relativePath = mergeInfo.substring(0, ind); path = "/" + relativePath; } mergeInfo = mergeInfo.delete(0, ind + 1); SVNMergeRange[] ranges = parseRevisionList(mergeInfo, path); if (mergeInfo.length() != 0 && mergeInfo.charAt(0) != '\n') { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.MERGE_INFO_PARSE_ERROR, "Could not find end of line in range list line in ''{0}''", mergeInfo); SVNErrorManager.error(err, SVNLogType.DEFAULT); } if (mergeInfo.length() > 0) { mergeInfo = mergeInfo.deleteCharAt(0); } if (ranges.length > 1) { Arrays.sort(ranges); SVNMergeRange lastRange = ranges[0]; Collection newRanges = new ArrayList(); newRanges.add(lastRange); for (int i = 1; i < ranges.length; i++) { SVNMergeRange range = ranges[i]; if (lastRange.getStartRevision() <= range.getEndRevision() && range.getStartRevision() <= lastRange.getEndRevision()) { if (range.getStartRevision() < lastRange.getEndRevision() && range.isInheritable() != lastRange.isInheritable()) { // error. String r1 = lastRange.toString(); String r2 = range.toString(); SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.MERGE_INFO_PARSE_ERROR, "Unable to parse overlapping revision ranges ''{0}'' and ''{1}'' with different inheritance types", new Object[] {r1, r2}); SVNErrorManager.error(err, SVNLogType.WC); } if (lastRange.isInheritable() == range.isInheritable()) { lastRange.setEndRevision(Math.max(range.getEndRevision(), lastRange.getEndRevision())); continue; } } newRanges.add(ranges[i]); lastRange = ranges[i]; } ranges = (SVNMergeRange[]) newRanges.toArray(new SVNMergeRange[newRanges.size()]); } SVNMergeRangeList existingRange = (SVNMergeRangeList) srcPathsToRangeLists.get(path); if (existingRange != null) { ranges = existingRange.merge(new SVNMergeRangeList(ranges)).getRanges(); } srcPathsToRangeLists.put(path, new SVNMergeRangeList(ranges)); } } catch (SVNException svne) { if (svne.getErrorMessage().getErrorCode() != SVNErrorCode.MERGE_INFO_PARSE_ERROR) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.MERGE_INFO_PARSE_ERROR, "Could not parse mergeinfo string ''{0}''", mergeInfo.toString()); SVNErrorManager.error(err, svne, SVNLogType.DEFAULT); } throw svne; } return srcPathsToRangeLists; } /** * Note: Make sure that this method is used only for making up an error message. */ public static String formatMergeInfoCatalogToString(Map catalog, String keyPrefix, String valuePrefix) { StringBuffer buffer = null; if (catalog != null && !catalog.isEmpty()) { buffer = new StringBuffer(); for (Iterator catalogIter = catalog.keySet().iterator(); catalogIter.hasNext();) { String path1 = (String) catalogIter.next(); if (path1.startsWith("/")) { path1 = path1.substring(1); } Map mergeInfo = (Map) catalog.get(path1); if (keyPrefix != null) { buffer.append(keyPrefix); } buffer.append(path1); buffer.append('\n'); buffer.append(formatMergeInfoToString(mergeInfo, valuePrefix != null ? valuePrefix : "")); buffer.append('\n'); } } return buffer != null ? buffer.toString() : "\n"; } public static String formatMergeInfoCatalogToString2(Map<File, Map<String, SVNMergeRangeList>> catalog, String keyPrefix, String valuePrefix) { StringBuffer buffer = null; if (catalog != null && !catalog.isEmpty()) { buffer = new StringBuffer(); for (Iterator<File> catalogIter = catalog.keySet().iterator(); catalogIter.hasNext();) { File path1 = catalogIter.next(); String path1Str = SVNFileUtil.getFilePath(path1); if (path1Str.startsWith("/")) { path1Str = path1Str.substring(1); } Map<String, SVNMergeRangeList> mergeInfo = catalog.get(path1); if (keyPrefix != null) { buffer.append(keyPrefix); } buffer.append(path1Str); buffer.append('\n'); buffer.append(formatMergeInfoToString(mergeInfo, valuePrefix != null ? valuePrefix : "")); buffer.append('\n'); } } return buffer != null ? buffer.toString() : "\n"; } /** * Each element of the resultant array is formed like this: * %s:%ld-%ld,.. where the first %s is a merge src path * and %ld-%ld is startRev-endRev merge range. */ public static String[] formatMergeInfoToArray(Map srcsToRangeLists, String prefix) { srcsToRangeLists = srcsToRangeLists == null ? Collections.EMPTY_MAP : srcsToRangeLists; String[] pathRanges = new String[srcsToRangeLists.size()]; int k = 0; for (Iterator paths = srcsToRangeLists.keySet().iterator(); paths.hasNext();) { String path = (String) paths.next(); SVNMergeRangeList rangeList = (SVNMergeRangeList) srcsToRangeLists.get(path); String output = (prefix != null ? prefix : "") + (path.startsWith("/") ? "" : "/") + path + ':' + rangeList; pathRanges[k++] = output; } return pathRanges; } public static String formatMergeInfoToString(Map srcsToRangeLists, String prefix) { String[] infosArray = formatMergeInfoToArray(srcsToRangeLists, prefix); String result = ""; for (int i = 0; i < infosArray.length; i++) { result += infosArray[i]; if (i < infosArray.length - 1) { result += '\n'; } } return result; } public static boolean shouldElideMergeInfo(Map<String, SVNMergeRangeList> parentMergeInfo, Map<String, SVNMergeRangeList> childMergeInfo, String pathSuffix) { boolean elides = false; if (childMergeInfo != null) { if (childMergeInfo.isEmpty()) { if (parentMergeInfo == null || parentMergeInfo.isEmpty()) { elides = true; } } else if (!(parentMergeInfo == null || parentMergeInfo.isEmpty())) { Map pathTweakedMergeInfo = parentMergeInfo; if (pathSuffix != null) { pathTweakedMergeInfo = new TreeMap(); for (Iterator paths = parentMergeInfo.keySet().iterator(); paths.hasNext();) { String mergeSrcPath = (String) paths.next(); pathTweakedMergeInfo.put(SVNPathUtil.getAbsolutePath(SVNPathUtil.append(mergeSrcPath, pathSuffix)), parentMergeInfo.get(mergeSrcPath)); } } elides = mergeInfoEquals(pathTweakedMergeInfo, childMergeInfo, true); } } return elides; } public static void elideMergeInfo(Map parentMergeInfo, Map childMergeInfo, File path, String pathSuffix, SVNWCAccess access) throws SVNException { boolean elides = shouldElideMergeInfo(parentMergeInfo, childMergeInfo, pathSuffix); if (elides) { SVNPropertiesManager.setProperty(access, path, SVNProperty.MERGE_INFO, null, true); } } public static boolean mergeInfoEquals(Map mergeInfo1, Map mergeInfo2, boolean considerInheritance) { mergeInfo1 = mergeInfo1 == null ? Collections.EMPTY_MAP : mergeInfo1; mergeInfo2 = mergeInfo2 == null ? Collections.EMPTY_MAP : mergeInfo2; if (mergeInfo1.size() == mergeInfo2.size()) { Map deleted = new SVNHashMap(); Map added = new SVNHashMap(); diffMergeInfo(deleted, added, mergeInfo1, mergeInfo2, considerInheritance); return deleted.isEmpty() && added.isEmpty(); } return false; } public static String[] findMergeSources(long revision, Map mergeInfo) { LinkedList mergeSources = new LinkedList(); for (Iterator paths = mergeInfo.keySet().iterator(); paths.hasNext();) { String path = (String) paths.next(); SVNMergeRangeList rangeList = (SVNMergeRangeList) mergeInfo.get(path); if (rangeList.includes(revision)) { mergeSources.add(path); } } return (String[]) mergeSources.toArray(new String[mergeSources.size()]); } public static Map<String, SVNMergeRangeList> getInheritableMergeInfo(Map<String, SVNMergeRangeList> mergeInfo, String path, long startRev, long endRev) { return getInheritableMergeInfo(mergeInfo, path, startRev, endRev, true); } public static Map<String, SVNMergeRangeList> getInheritableMergeInfo(Map<String, SVNMergeRangeList> mergeInfo, String path, long startRev, long endRev, boolean inheritable) { Map<String, SVNMergeRangeList> inheritableMergeInfo = new TreeMap<String, SVNMergeRangeList>(); if (mergeInfo != null) { for (Iterator<String> paths = mergeInfo.keySet().iterator(); paths.hasNext();) { String mergeSrcPath = (String) paths.next(); SVNMergeRangeList rangeList = (SVNMergeRangeList) mergeInfo.get(mergeSrcPath); SVNMergeRangeList inheritableRangeList = null; if (path == null || path.equals(mergeSrcPath)) { inheritableRangeList = rangeList.getInheritableRangeList(startRev, endRev, inheritable); } else { inheritableRangeList = rangeList.dup(); } if (!inheritableRangeList.isEmpty()) { inheritableMergeInfo.put(mergeSrcPath, inheritableRangeList); } } } return inheritableMergeInfo; } public static Map removeMergeInfo(Map eraser, Map whiteBoard) { return removeMergeInfo(eraser, whiteBoard, true); } public static Map<String, SVNMergeRangeList> removeMergeInfo(Map<String, SVNMergeRangeList> eraser, Map<String, SVNMergeRangeList> whiteBoard, boolean considerInheritance) { Map<String, SVNMergeRangeList> mergeInfo = new TreeMap<String, SVNMergeRangeList>(); walkMergeInfoHashForDiff(mergeInfo, null, whiteBoard, eraser, considerInheritance); return mergeInfo; } public static Map intersectMergeInfo(Map mergeInfo1, Map mergeInfo2) { return intersectMergeInfo(mergeInfo1, mergeInfo2, true); } public static Map<String, SVNMergeRangeList> intersectMergeInfo(Map mergeInfo1, Map mergeInfo2, boolean considerInheritance) { Map<String, SVNMergeRangeList> mergeInfo = new TreeMap<String, SVNMergeRangeList>(); for (Iterator pathsIter = mergeInfo1.keySet().iterator(); pathsIter.hasNext();) { String path = (String) pathsIter.next(); SVNMergeRangeList rangeList1 = (SVNMergeRangeList) mergeInfo1.get(path); SVNMergeRangeList rangeList2 = (SVNMergeRangeList) mergeInfo2.get(path); if (rangeList2 != null) { rangeList2 = rangeList2.intersect(rangeList1, considerInheritance); if (!rangeList2.isEmpty()) { mergeInfo.put(path, rangeList2.dup()); } } } return mergeInfo; } public static SVNMergeRange[] parseRevisionList(StringBuffer mergeInfo, String path) throws SVNException { Collection ranges = new LinkedList(); while (mergeInfo.length() > 0 && mergeInfo.charAt(0) != '\n' && Character.isWhitespace(mergeInfo.charAt(0))) { mergeInfo = mergeInfo.deleteCharAt(0); } if (mergeInfo.length() == 0 || mergeInfo.charAt(0) == '\n') { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.MERGE_INFO_PARSE_ERROR, "Mergeinfo for ''{0}'' maps to an empty revision range", path); SVNErrorManager.error(err, SVNLogType.DEFAULT); } while (mergeInfo.length() > 0 && mergeInfo.charAt(0) != '\n') { long startRev = parseRevision(mergeInfo); if (mergeInfo.length() > 0 && mergeInfo.charAt(0) != '\n' && mergeInfo.charAt(0) != '-' && mergeInfo.charAt(0) != ',' && mergeInfo.charAt(0) != '*') { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.MERGE_INFO_PARSE_ERROR, "Invalid character ''{0}'' found in revision list", new Character(mergeInfo.charAt(0))); SVNErrorManager.error(err, SVNLogType.DEFAULT); } SVNMergeRange range = new SVNMergeRange(startRev - 1, startRev, true); if (mergeInfo.length() > 0 && mergeInfo.charAt(0) == '-') { mergeInfo = mergeInfo.deleteCharAt(0); long endRev = parseRevision(mergeInfo); if (startRev > endRev) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.MERGE_INFO_PARSE_ERROR, "Unable to parse reversed revision range ''{0}-{1}''", new Object[] { new Long(startRev), new Long(endRev) }); SVNErrorManager.error(err, SVNLogType.DEFAULT); } else if (startRev == endRev) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.MERGE_INFO_PARSE_ERROR, "Unable to parse revision range ''{0}-{1}'' with same start and end revisions", new Object[] { new Long(startRev), new Long(endRev) }); SVNErrorManager.error(err, SVNLogType.DEFAULT); } range.setEndRevision(endRev); } if (mergeInfo.length() == 0 || mergeInfo.charAt(0) == '\n') { ranges.add(range); return (SVNMergeRange[]) ranges.toArray(new SVNMergeRange[ranges.size()]); } else if (mergeInfo.length() > 0 && mergeInfo.charAt(0) == ',') { ranges.add(range); mergeInfo = mergeInfo.deleteCharAt(0); } else if (mergeInfo.length() > 0 && mergeInfo.charAt(0) == '*') { range.setInheritable(false); mergeInfo = mergeInfo.deleteCharAt(0); if (mergeInfo.length() == 0 || mergeInfo.charAt(0) == ',' || mergeInfo.charAt(0) == '\n') { ranges.add(range); if (mergeInfo.length() > 0 && mergeInfo.charAt(0) == ',') { mergeInfo = mergeInfo.deleteCharAt(0); } else { return (SVNMergeRange[]) ranges.toArray(new SVNMergeRange[ranges.size()]); } } else { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.MERGE_INFO_PARSE_ERROR, "Invalid character ''{0}'' found in range list", mergeInfo.length() > 0 ? mergeInfo.charAt(0) + "" : ""); SVNErrorManager.error(err, SVNLogType.DEFAULT); } } else { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.MERGE_INFO_PARSE_ERROR, "Invalid character ''{0}'' found in range list", mergeInfo.length() > 0 ? mergeInfo.charAt(0) + "" : ""); SVNErrorManager.error(err, SVNLogType.DEFAULT); } } if (mergeInfo.length() == 0 || mergeInfo.charAt(0) != '\n' ) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.MERGE_INFO_PARSE_ERROR, "Range list parsing ended before hitting newline"); SVNErrorManager.error(err, SVNLogType.DEFAULT); } return (SVNMergeRange[]) ranges.toArray(new SVNMergeRange[ranges.size()]); } public static Map<String, SVNMergeRangeList> appendSuffix(Map<String, SVNMergeRangeList> mergeinfo, String suffix) { Map<String, SVNMergeRangeList> result = new TreeMap<String, SVNMergeRangeList>(); for (String path : mergeinfo.keySet()) { String pathWithSuffix = SVNPathUtil.append(path, suffix); result.put(pathWithSuffix, mergeinfo.get(path)); } return result; } /** * @return [deletedList, addedList] */ public static SVNMergeRangeList[] diffMergeRangeLists(SVNMergeRangeList fromRangeList, SVNMergeRangeList toRangeList, boolean considerInheritance) { SVNMergeRangeList deletedRangeList = fromRangeList.diff(toRangeList, considerInheritance); SVNMergeRangeList addedRangeList = toRangeList.diff(fromRangeList, considerInheritance); return new SVNMergeRangeList[] { deletedRangeList, addedRangeList }; } private static long parseRevision(StringBuffer mergeInfo) throws SVNException { int ind = 0; while (ind < mergeInfo.length() && Character.isDigit(mergeInfo.charAt(ind))) { ind++; } if (ind == 0) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.REVISION_NUMBER_PARSE_ERROR, "Invalid revision number found parsing ''{0}''", mergeInfo.toString()); SVNErrorManager.error(err, SVNLogType.DEFAULT); } String numberStr = mergeInfo.substring(0, ind); long rev = -1; try { rev = Long.parseLong(numberStr); } catch (NumberFormatException e) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.REVISION_NUMBER_PARSE_ERROR, "Invalid revision number found parsing ''{0}''", mergeInfo.toString()); SVNErrorManager.error(err, SVNLogType.DEFAULT); } if (rev < 0) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.REVISION_NUMBER_PARSE_ERROR, "Negative revision number found parsing ''{0}''", mergeInfo.toString()); SVNErrorManager.error(err, SVNLogType.DEFAULT); } mergeInfo = mergeInfo.delete(0, ind); return rev; } private static void walkMergeInfoHashForDiff(Map deleted, Map added, Map from, Map to, boolean considerInheritance) { for (Iterator paths = from.keySet().iterator(); paths.hasNext();) { String path = (String) paths.next(); SVNMergeRangeList fromRangeList = (SVNMergeRangeList) from.get(path); SVNMergeRangeList toRangeList = (SVNMergeRangeList) to.get(path); if (toRangeList != null) { SVNMergeRangeList[] rangeListDiff = diffMergeRangeLists(fromRangeList, toRangeList, considerInheritance); SVNMergeRangeList deletedRangeList = rangeListDiff[0]; SVNMergeRangeList addedRangeList = rangeListDiff[1]; if (deleted != null && deletedRangeList.getSize() > 0) { deleted.put(path, deletedRangeList); } if (added != null && addedRangeList.getSize() > 0) { added.put(path, addedRangeList); } } else if (deleted != null) { deleted.put(path, fromRangeList.dup()); } } if (added == null) { return; } for (Iterator paths = to.keySet().iterator(); paths.hasNext();) { String path = (String) paths.next(); SVNMergeRangeList toRangeList = (SVNMergeRangeList) to.get(path); if (!from.containsKey(path)) { added.put(path, toRangeList.dup()); } } } private static class ElideMergeInfoCatalogHandler implements ISVNCommitPathHandler { private Map myMergeInfoCatalog; private List myElidablePaths; public ElideMergeInfoCatalogHandler(Map mergeInfoCatalog) { myMergeInfoCatalog = mergeInfoCatalog; myElidablePaths = new LinkedList(); } public boolean handleCommitPath(String path, ISVNEditor editor) throws SVNException { ElideMergeInfoEditor elideEditor = (ElideMergeInfoEditor) editor; String inheritedMergeInfoPath = elideEditor.getInheritedMergeInfoPath(); if (inheritedMergeInfoPath == null || "/".equals(path)) { return false; } String pathSuffix = SVNPathUtil.getPathAsChild(inheritedMergeInfoPath, path); if (pathSuffix == null) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.UNKNOWN, "path suffix is null"); SVNErrorManager.error(err, SVNLogType.DEFAULT); } boolean elides = shouldElideMergeInfo((Map) myMergeInfoCatalog.get(inheritedMergeInfoPath), (Map) myMergeInfoCatalog.get(path), pathSuffix); if (elides) { myElidablePaths.add(path); } return false; } public List getElidablePaths() { return myElidablePaths; } } private static class ElideMergeInfoEditor implements ISVNEditor { private Map myMergeInfoCatalog; private ElideMergeInfoCatalogDirBaton myCurrentDirBaton; public ElideMergeInfoEditor(Map mergeInfoCatalog) { myMergeInfoCatalog = mergeInfoCatalog; } public void abortEdit() throws SVNException { } public void absentDir(String path) throws SVNException { } public void absentFile(String path) throws SVNException { } public void addDir(String path, String copyFromPath, long copyFromRevision) throws SVNException { } public void addFile(String path, String copyFromPath, long copyFromRevision) throws SVNException { } public void changeDirProperty(String name, SVNPropertyValue value) throws SVNException { } public void changeFileProperty(String path, String propertyName, SVNPropertyValue propertyValue) throws SVNException { } public void closeDir() throws SVNException { } public SVNCommitInfo closeEdit() throws SVNException { return null; } public void closeFile(String path, String textChecksum) throws SVNException { } public void deleteEntry(String path, long revision) throws SVNException { } public void openDir(String path, long revision) throws SVNException { if (!path.startsWith("/")) { path = "/" + path; } ElideMergeInfoCatalogDirBaton dirBaton = new ElideMergeInfoCatalogDirBaton(); if (myMergeInfoCatalog.get(path) != null) { dirBaton.myInheritedMergeInfoPath = path; } else { dirBaton.myInheritedMergeInfoPath = myCurrentDirBaton.myInheritedMergeInfoPath; } myCurrentDirBaton = dirBaton; } public void openFile(String path, long revision) throws SVNException { } public void openRoot(long revision) throws SVNException { myCurrentDirBaton = new ElideMergeInfoCatalogDirBaton(); } public void targetRevision(long revision) throws SVNException { } public void applyTextDelta(String path, String baseChecksum) throws SVNException { } public OutputStream textDeltaChunk(String path, SVNDiffWindow diffWindow) throws SVNException { return SVNFileUtil.DUMMY_OUT; } public void textDeltaEnd(String path) throws SVNException { } public String getInheritedMergeInfoPath() { return myCurrentDirBaton.myInheritedMergeInfoPath; } private class ElideMergeInfoCatalogDirBaton { private String myInheritedMergeInfoPath; } } }