/* * ==================================================================== * 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.io.fs; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.Date; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; import java.util.TreeMap; import org.tmatesoft.svn.core.ISVNLogEntryHandler; import org.tmatesoft.svn.core.SVNErrorCode; import org.tmatesoft.svn.core.SVNException; import org.tmatesoft.svn.core.SVNLogEntry; import org.tmatesoft.svn.core.SVNMergeInfo; import org.tmatesoft.svn.core.SVNMergeInfoInheritance; import org.tmatesoft.svn.core.SVNMergeRange; import org.tmatesoft.svn.core.SVNMergeRangeList; import org.tmatesoft.svn.core.SVNProperties; import org.tmatesoft.svn.core.SVNProperty; import org.tmatesoft.svn.core.SVNPropertyValue; import org.tmatesoft.svn.core.SVNRevisionProperty; import org.tmatesoft.svn.core.internal.util.SVNDate; import org.tmatesoft.svn.core.internal.util.SVNHashMap; import org.tmatesoft.svn.core.internal.util.SVNHashSet; import org.tmatesoft.svn.core.internal.util.SVNMergeInfoUtil; import org.tmatesoft.svn.core.internal.util.SVNPathUtil; import org.tmatesoft.svn.core.internal.wc.SVNMergeDriver; import org.tmatesoft.svn.core.internal.wc.SVNMergeInfoManager; import org.tmatesoft.svn.core.io.ISVNLocationSegmentHandler; import org.tmatesoft.svn.core.io.SVNLocationEntry; import org.tmatesoft.svn.core.io.SVNLocationSegment; import org.tmatesoft.svn.core.io.SVNRepository; import org.tmatesoft.svn.core.wc.SVNRevision; /** * @version 1.3 * @author TMate Software Ltd. */ public class FSLog { private static final int MAX_OPEN_HISTORIES = 128; private FSFS myFSFS; private String[] myPaths; private boolean myIsDescending; private boolean myIsDiscoverChangedPaths; private boolean myIsStrictNode; private boolean myIsIncludeMergedRevisions; private long myStartRevision; private long myEndRevision; private long myLimit; private ISVNLogEntryHandler myHandler; private SVNMergeInfoManager myMergeInfoManager; private String[] myRevPropNames; private static final Comparator RLP_COMPARATOR = new Comparator() { public int compare(Object arg1, Object arg2) { RangeListPath rangeListPath1 = (RangeListPath) arg1; RangeListPath rangeListPath2 = (RangeListPath) arg2; SVNMergeRange[] ranges1 = rangeListPath1.myRangeList.getRanges(); SVNMergeRange[] ranges2 = rangeListPath2.myRangeList.getRanges(); SVNMergeRange range1 = ranges1[0]; SVNMergeRange range2 = ranges2[0]; if (range1.getStartRevision() < range2.getStartRevision()) { return -1; } if (range1.getStartRevision() > range2.getStartRevision()) { return 1; } if (range1.getEndRevision() < range2.getEndRevision()) { return -1; } if (range1.getEndRevision() > range2.getEndRevision()) { return 1; } return 0; } }; private static final Comparator PLR_COMPARATOR = new Comparator() { public int compare(Object o1, Object o2) { PathListRange plr1 = (PathListRange) o1; PathListRange plr2 = (PathListRange) o2; if (plr1.myRange.getStartRevision() < plr2.myRange.getStartRevision()) { return -1; } if (plr1.myRange.getStartRevision() > plr2.myRange.getStartRevision()) { return 1; } if (plr1.myRange.getEndRevision() < plr2.myRange.getEndRevision()) { return -1; } if (plr1.myRange.getEndRevision() < plr2.myRange.getEndRevision()) { return 1; } return 0; } }; public FSLog(FSFS owner, String[] paths, long limit, long start, long end, boolean descending, boolean discoverChangedPaths, boolean strictNode, boolean includeMergedRevisions, String[] revPropNames, ISVNLogEntryHandler handler) { myFSFS = owner; myPaths = paths; myStartRevision = start; myEndRevision = end; myIsDescending = descending; myIsDiscoverChangedPaths = discoverChangedPaths; myIsStrictNode = strictNode; myIsIncludeMergedRevisions = includeMergedRevisions; myRevPropNames = revPropNames; myLimit = limit; myHandler = handler; } public void reset(FSFS owner, String[] paths, long limit, long start, long end, boolean descending, boolean discoverChangedPaths, boolean strictNode, boolean includeMergedRevisions, String[] revPropNames, ISVNLogEntryHandler handler) { myFSFS = owner; myPaths = paths; myStartRevision = start; myEndRevision = end; myIsDescending = descending; myIsDiscoverChangedPaths = discoverChangedPaths; myIsStrictNode = strictNode; myIsIncludeMergedRevisions = includeMergedRevisions; myRevPropNames = revPropNames; myLimit = limit; myHandler = handler; } public long runLog() throws SVNException { long count = 0; if (!myIsIncludeMergedRevisions && myPaths.length == 1 && "/".equals(myPaths[0])) { count = myEndRevision - myStartRevision + 1; if (myLimit > 0 && count > myLimit) { count = myLimit; } for (int i = 0; i < count; i++) { long rev = myStartRevision + i; if (myIsDescending) { rev = myEndRevision - i; } sendLog(rev, null, null, false, false, false); } return count; } Map logTargetHistoryAsMergeInfo = null; if (myIsIncludeMergedRevisions) { logTargetHistoryAsMergeInfo = getPathsHistoryAsMergeInfo(myPaths, myStartRevision, myEndRevision); } return doLogs(myPaths, logTargetHistoryAsMergeInfo, null, myStartRevision, myEndRevision, myIsIncludeMergedRevisions, false, false, myIsDescending, myLimit); } private long doLogs(String[] paths, Map logTargetHistoryAsMergeinfo, Set nestedMerges, long startRevision, long endRevision, boolean includeMergedRevisions, boolean subtractiveMerge, boolean handlingMergedRevisions, boolean isDescendingOrder, long limit) throws SVNException { long sendCount = 0; PathInfo[] histories = getPathHistories(paths, startRevision, endRevision, myIsStrictNode); LinkedList revisions = null; Map revMergeInfo = null; boolean anyHistoriesLeft = true; for (long currentRev = endRevision; anyHistoriesLeft; currentRev = getNextHistoryRevision(histories)) { boolean changed = false; anyHistoriesLeft = false; for (int i = 0; i < histories.length; i++) { PathInfo info = histories[i]; changed = info.checkHistory(currentRev, myIsStrictNode, startRevision, changed); if (!info.myIsDone) { anyHistoriesLeft = true; } } if (changed) { boolean hasChildren = false; Map addedMergeInfo = null; Map deletedMergeInfo = null; Map[] mergeInfo = null; if (includeMergedRevisions) { LinkedList currentPaths = new LinkedList(); for (int i = 0; i < histories.length; i++) { PathInfo info = histories[i]; currentPaths.add(info.myPath); } mergeInfo = getCombinedMergeInfoChanges((String[]) currentPaths.toArray(new String[currentPaths.size()]), currentRev); addedMergeInfo = mergeInfo[0]; deletedMergeInfo = mergeInfo[1]; hasChildren = (addedMergeInfo.size() > 0 || deletedMergeInfo.size() > 0); } if (isDescendingOrder) { sendLog(currentRev, logTargetHistoryAsMergeinfo, nestedMerges, subtractiveMerge, handlingMergedRevisions, hasChildren); sendCount++; if (hasChildren) { if (nestedMerges == null) { nestedMerges = new SVNHashSet(); } handleMergedRevisions(addedMergeInfo, deletedMergeInfo, logTargetHistoryAsMergeinfo, nestedMerges); } if (limit > 0 && sendCount >= limit) { break; } } else { if (revisions == null) { revisions = new LinkedList(); } revisions.addLast(new Long(currentRev)); if (mergeInfo != null) { if (revMergeInfo == null) { revMergeInfo = new TreeMap(); } revMergeInfo.put(new Long(currentRev), mergeInfo); } } } } nestedMerges = null; if (revisions != null) { for (int i = 0; i < revisions.size(); i++) { boolean hasChildren = false; Map[] mergeInfo = null; long rev = ((Long) revisions.get(revisions.size() - i - 1)).longValue(); if (revMergeInfo != null) { mergeInfo = (Map[]) revMergeInfo.get(new Long(rev)); if (mergeInfo != null && mergeInfo.length == 2) { hasChildren = !mergeInfo[0].isEmpty() || !mergeInfo[1].isEmpty(); } } sendLog(rev, logTargetHistoryAsMergeinfo, nestedMerges, subtractiveMerge, handlingMergedRevisions, hasChildren); if (hasChildren) { if (nestedMerges == null) { nestedMerges = new SVNHashSet(); } handleMergedRevisions(mergeInfo[0], mergeInfo[1], logTargetHistoryAsMergeinfo, nestedMerges); } sendCount++; if (limit > 0 && sendCount >= limit) { break; } } } return sendCount; } private long getNextHistoryRevision(PathInfo[] histories) { long nextRevision = SVNRepository.INVALID_REVISION; for (int i = 0; i < histories.length; i++) { PathInfo info = histories[i]; if (info.myIsDone) { continue; } if (info.myHistoryRevision > nextRevision) { nextRevision = info.myHistoryRevision; } } return nextRevision; } private void sendLog(long revision, Map logTargetHistoryAsMergeInfo, Set nestedMerges, boolean subtractiveMerge, boolean handlingMergedRevision, boolean hasChildren) throws SVNException { if (myHandler == null) { return; } SVNLogEntry logEntry = fillLogEntry(revision, myIsDiscoverChangedPaths || handlingMergedRevision); logEntry.setHasChildren(hasChildren); logEntry.setSubtractiveMerge(subtractiveMerge); boolean revisionIsInteresting = true; if (handlingMergedRevision && !logEntry.getChangedPaths().isEmpty() && logTargetHistoryAsMergeInfo != null && !logTargetHistoryAsMergeInfo.isEmpty()) { boolean pathIsInHistory = false; revisionIsInteresting = false; for (Iterator changedPaths = logEntry.getChangedPaths().keySet().iterator(); changedPaths.hasNext();) { String changedPath = (String) changedPaths.next(); for(Iterator mergedPaths = logTargetHistoryAsMergeInfo.keySet().iterator(); mergedPaths.hasNext();) { String mergedPath = (String) mergedPaths.next(); if (SVNPathUtil.isAncestor(mergedPath, changedPath)) { SVNMergeRangeList rangeList = (SVNMergeRangeList) logTargetHistoryAsMergeInfo.get(mergedPath); SVNMergeRange[] ranges = rangeList.getRanges(); for (int i = 0; i < ranges.length; i++) { if (revision > ranges[i].getStartRevision() && revision <= ranges[i].getEndRevision()) { pathIsInHistory = true; break; } } if (pathIsInHistory) { break; } } } if (!pathIsInHistory) { revisionIsInteresting = true; break; } } } if (!myIsDiscoverChangedPaths) { logEntry.getChangedPaths().clear(); } if (revisionIsInteresting) { if (handlingMergedRevision && nestedMerges != null) { if (nestedMerges.contains(new Long(revision))) { return; } nestedMerges.add(new Long(revision)); } myHandler.handleLogEntry(logEntry); } } private SVNLogEntry fillLogEntry(long revision, boolean discoverChangedPaths) throws SVNException { Map changedPaths = null; SVNProperties entryRevProps = null; boolean getRevProps = true; boolean censorRevProps = false; if (revision > 0 && discoverChangedPaths) { FSRevisionRoot root = myFSFS.createRevisionRoot(revision); changedPaths = root.detectChanged(); } if (getRevProps) { SVNProperties revisionProps = myFSFS.getRevisionProperties(revision); if (revisionProps != null) { String author = revisionProps.getStringValue(SVNRevisionProperty.AUTHOR); String datestamp = revisionProps.getStringValue(SVNRevisionProperty.DATE); Date date = datestamp != null ? SVNDate.parseDateString(datestamp) : null; if (myRevPropNames == null || myRevPropNames.length == 0) { if (censorRevProps) { entryRevProps = new SVNProperties(); if (author != null) { entryRevProps.put(SVNRevisionProperty.AUTHOR, author); } if (date != null) { entryRevProps.put(SVNRevisionProperty.DATE, SVNDate.formatDate(date)); } } else { entryRevProps = revisionProps; if (date != null) { entryRevProps.put(SVNRevisionProperty.DATE, SVNDate.formatDate(date)); } } } else { for (int i = 0; i < myRevPropNames.length; i++) { String propName = myRevPropNames[i]; SVNPropertyValue propVal = revisionProps.getSVNPropertyValue(propName); if (censorRevProps && !SVNRevisionProperty.AUTHOR.equals(propName) && !SVNRevisionProperty.DATE.equals(propName)) { continue; } if (entryRevProps == null) { entryRevProps = new SVNProperties(); } if (SVNRevisionProperty.DATE.equals(propName) && date != null) { entryRevProps.put(propName, SVNDate.formatDate(date)); } else if (propVal != null) { entryRevProps.put(propName, propVal); } } } } } if (changedPaths == null) { changedPaths = new SVNHashMap(); } if (entryRevProps == null) { entryRevProps = new SVNProperties(); } SVNLogEntry entry = new SVNLogEntry(changedPaths, revision, entryRevProps, false); return entry; } private void handleMergedRevisions(Map addedMergeInfo, Map deletedMergeInfo, Map logTargetHistoryAsMergeInfo, Set nestedMerges) throws SVNException { if ((addedMergeInfo == null || addedMergeInfo.isEmpty()) && (deletedMergeInfo == null || deletedMergeInfo.isEmpty())) { return; } LinkedList combinedList = new LinkedList(); if (addedMergeInfo != null && addedMergeInfo.size() > 0) { combinedList = combineMergeInfoPathLists(addedMergeInfo, false); } if (deletedMergeInfo != null && deletedMergeInfo.size() > 0) { combinedList.addAll(combineMergeInfoPathLists(deletedMergeInfo, true)); } Collections.sort(combinedList, PLR_COMPARATOR); for (int i = combinedList.size() - 1; i >= 0; i--) { PathListRange pathListRange = (PathListRange) combinedList.get(i); try { doLogs(pathListRange.myPaths, logTargetHistoryAsMergeInfo, nestedMerges, pathListRange.myRange.getStartRevision(), pathListRange.myRange.getEndRevision(), true, pathListRange.reverseMerge, true, true, 0); } catch (SVNException svne) { SVNErrorCode errCode = svne.getErrorMessage().getErrorCode(); if (errCode == SVNErrorCode.FS_NOT_FOUND || errCode == SVNErrorCode.FS_NO_SUCH_REVISION) { continue; } throw svne; } } if (myHandler != null) { myHandler.handleLogEntry(SVNLogEntry.EMPTY_ENTRY); } } private Map getPathsHistoryAsMergeInfo(String[] paths, long startRevision, long endRevision) throws SVNException { if (startRevision < endRevision) { long temp = startRevision; startRevision = endRevision; endRevision = temp; } Map target = new SVNHashMap(); FSLocationsFinder locationsFinder = new FSLocationsFinder(myFSFS); final Collection locationSegments = new ArrayList(); ISVNLocationSegmentHandler locationsReceiver = new ISVNLocationSegmentHandler() { public void handleLocationSegment(SVNLocationSegment locationSegment) throws SVNException { locationSegments.add(locationSegment); } }; for (int i = 0; i < paths.length; i++) { locationsFinder.getNodeLocationSegments(paths[i], startRevision, startRevision, endRevision, locationsReceiver); Map mergeInfo = SVNMergeDriver.getMergeInfoFromSegments(locationSegments); target = SVNMergeInfoUtil.mergeMergeInfos(target, mergeInfo); } return target; } private PathInfo[] getPathHistories(String[] paths, long start, long end, boolean strictNodeHistory) throws SVNException { PathInfo[] histories = new PathInfo[paths.length]; FSRevisionRoot root = myFSFS.createRevisionRoot(end); for (int i = 0; i < paths.length; i++) { String path = paths[i]; PathInfo pathHistory = new PathInfo(); pathHistory.myPath = path; pathHistory.myHistoryRevision = end; pathHistory.myIsDone = false; pathHistory.myIsFirstTime = true; if (i < MAX_OPEN_HISTORIES) { pathHistory.myHistory = root.getNodeHistory(path); } histories[i] = pathHistory.getHistory(strictNodeHistory, start); } return histories; } private Map[] getCombinedMergeInfoChanges(String[] paths, long revision) throws SVNException { if (revision == 0) { return new Map[] {new TreeMap(), new TreeMap()}; } if (paths == null || paths.length == 0) { return new Map[] {new TreeMap(), new TreeMap()}; } Map resultAdded = new SVNHashMap(); Map resultDeleted = new SVNHashMap(); Map addedMergeInfoCatalog = new SVNHashMap(); Map deletedMergeInfoCatalog = new SVNHashMap(); FSRevisionRoot root = myFSFS.createRevisionRoot(revision); collectChangedMergeInfo(addedMergeInfoCatalog, deletedMergeInfoCatalog, revision); for (int i = 0; i < paths.length; i++) { String path = paths[i]; if (deletedMergeInfoCatalog.containsKey(path)) { continue; } long[] appearedRevision = new long[] {-1}; SVNLocationEntry prevLocation = null; try { prevLocation = myFSFS.getPreviousLocation(path, revision, appearedRevision); } catch (SVNException e) { if (e.getErrorMessage().getErrorCode() == SVNErrorCode.FS_NOT_FOUND) { continue; } throw e; } String prevPath = null; long prevRevision = -1; if (!(prevLocation != null && prevLocation.getPath() != null && prevLocation.getRevision() >=0 && appearedRevision[0] == revision)) { prevPath = path; prevRevision = revision - 1; } else if (prevLocation != null) { prevPath = prevLocation.getPath(); prevRevision = prevLocation.getRevision(); } FSRevisionRoot prevRoot = myFSFS.createRevisionRoot(prevRevision); String[] queryPaths = new String[] {prevPath}; Map catalog = null; try { catalog = getMergeInfoManager().getMergeInfo(queryPaths, prevRoot, SVNMergeInfoInheritance.INHERITED, false); } catch (SVNException e) { if (e.getErrorMessage().getErrorCode() == SVNErrorCode.FS_NOT_FOUND) { continue; } throw e; } SVNMergeInfo prevMergeinfo = (SVNMergeInfo) catalog.get(prevPath); queryPaths = new String[] {path}; catalog = getMergeInfoManager().getMergeInfo(queryPaths, root, SVNMergeInfoInheritance.INHERITED, false); SVNMergeInfo mergeInfo = (SVNMergeInfo) catalog.get(path); Map deleted = new SVNHashMap(); Map added = new SVNHashMap(); SVNMergeInfoUtil.diffMergeInfo(deleted, added, prevMergeinfo != null ? prevMergeinfo.getMergeSourcesToMergeLists() : null, mergeInfo != null ? mergeInfo.getMergeSourcesToMergeLists() : null, false); resultAdded = SVNMergeInfoUtil.mergeMergeInfos(resultAdded, added); resultDeleted = SVNMergeInfoUtil.mergeMergeInfos(resultDeleted, deleted); } for(Iterator ps = addedMergeInfoCatalog.keySet().iterator(); ps.hasNext();) { String changedPath = (String) ps.next(); Map addedMergeInfo = (Map) addedMergeInfoCatalog.get(changedPath); for (int i = 0; i < paths.length; i++) { String path = paths[i]; if (!SVNPathUtil.isAncestor(path, changedPath)) { continue; } Map deletedMergeInfo = (Map) deletedMergeInfoCatalog.get(changedPath); resultDeleted = SVNMergeInfoUtil.mergeMergeInfos(resultDeleted, deletedMergeInfo); resultAdded = SVNMergeInfoUtil.mergeMergeInfos(resultAdded, addedMergeInfo); break; } } return new Map[] {resultAdded, resultDeleted}; } private void collectChangedMergeInfo(Map addedMergeInfo, Map deletedMergeInfo, long revision) throws SVNException { if (revision == 0) { return; } FSRevisionRoot root = myFSFS.createRevisionRoot(revision); Map changedPaths = root.getChangedPaths(); if (changedPaths == null || changedPaths.isEmpty()) { return; } for (Iterator paths = changedPaths.keySet().iterator(); paths.hasNext();) { String changedPath = (String) paths.next(); FSPathChange change = (FSPathChange) changedPaths.get(changedPath); if (!change.arePropertiesModified()) { continue; } FSPathChangeKind changeKind = change.getChangeKind(); String basePath = null; long baseRevision = -1; String mergeInfoValue = null; String previousMergeInfoValue = null; if (changeKind == FSPathChangeKind.FS_PATH_CHANGE_ADD || changeKind == FSPathChangeKind.FS_PATH_CHANGE_REPLACE) { String copyFromPath = change.getCopyPath(); long copyFromRev = change.getCopyRevision(); if (copyFromPath != null && copyFromRev >= 0) { basePath = copyFromPath; baseRevision = copyFromRev; } } else if (changeKind == FSPathChangeKind.FS_PATH_CHANGE_MODIFY) { long[] appearedRevision = new long[] {-1}; SVNLocationEntry prevLocation = myFSFS.getPreviousLocation(changedPath, revision, appearedRevision); if (!(prevLocation != null && prevLocation.getPath() != null && prevLocation.getRevision() >= 0 && appearedRevision[0] == prevLocation.getRevision())) { basePath = changedPath; baseRevision = revision - 1; } else { basePath = prevLocation.getPath(); baseRevision = prevLocation.getRevision(); } } else { continue; } FSRevisionRoot baseRoot = null; if (basePath != null && baseRevision >= 0) { baseRoot = myFSFS.createRevisionRoot(baseRevision); SVNProperties props = myFSFS.getProperties(baseRoot.getRevisionNode(basePath)); previousMergeInfoValue = props.getStringValue(SVNProperty.MERGE_INFO); } SVNProperties props = myFSFS.getProperties(root.getRevisionNode(changedPath)); if (props != null) { mergeInfoValue = props.getStringValue(SVNProperty.MERGE_INFO); } if (mergeInfoValue == null && previousMergeInfoValue == null) { continue; } if (previousMergeInfoValue != null && mergeInfoValue == null) { String[] queryPaths = new String[] { changedPath }; Map tmpCatalog = getMergeInfoManager().getMergeInfo(queryPaths, root, SVNMergeInfoInheritance.INHERITED, false); SVNMergeInfo tmpMergeInfo = (SVNMergeInfo) tmpCatalog.get(changedPath); if (tmpMergeInfo != null) { mergeInfoValue = SVNMergeInfoUtil.formatMergeInfoToString(tmpMergeInfo.getMergeSourcesToMergeLists(), null); } } else if (mergeInfoValue != null && previousMergeInfoValue == null && basePath != null && SVNRevision.isValidRevisionNumber(baseRevision)) { String[] queryPaths = new String[] { basePath }; Map tmpCatalog = getMergeInfoManager().getMergeInfo(queryPaths, baseRoot, SVNMergeInfoInheritance.INHERITED, false); SVNMergeInfo tmpMergeInfo = (SVNMergeInfo) tmpCatalog.get(basePath); if (tmpMergeInfo != null) { previousMergeInfoValue = SVNMergeInfoUtil.formatMergeInfoToString(tmpMergeInfo.getMergeSourcesToMergeLists(), null); } } if ((previousMergeInfoValue != null && mergeInfoValue == null) || (previousMergeInfoValue == null && mergeInfoValue != null) || (previousMergeInfoValue != null && mergeInfoValue != null && !previousMergeInfoValue.equals(mergeInfoValue))) { Map mergeInfo = null; Map previousMergeInfo = null; if (mergeInfoValue != null) { mergeInfo = SVNMergeInfoUtil.parseMergeInfo(new StringBuffer(mergeInfoValue), null); } if (previousMergeInfoValue != null) { previousMergeInfo = SVNMergeInfoUtil.parseMergeInfo(new StringBuffer(previousMergeInfoValue), null); } Map added = new SVNHashMap(); Map deleted = new SVNHashMap(); SVNMergeInfoUtil.diffMergeInfo(deleted, added, previousMergeInfo, mergeInfo, false); addedMergeInfo.put(changedPath, added); deletedMergeInfo.put(changedPath, deleted); } } } private LinkedList combineMergeInfoPathLists(Map mergeInfo, boolean reverseMerge) { List rangeListPaths = new LinkedList(); for (Iterator pathsIter = mergeInfo.keySet().iterator(); pathsIter.hasNext();) { String path = (String) pathsIter.next(); SVNMergeRangeList changes = (SVNMergeRangeList) mergeInfo.get(path); RangeListPath rangeListPath = new RangeListPath(); rangeListPath.myPath = path; rangeListPath.myRangeList = changes.dup(); SVNMergeRange[] rangesArray = rangeListPath.myRangeList.getRanges(); for (int i = 0; i < rangesArray.length; i++) { SVNMergeRange range = rangesArray[i]; range.setStartRevision(range.getStartRevision() + 1); } rangeListPaths.add(rangeListPath); } LinkedList combinedList = new LinkedList(); while (rangeListPaths.size() > 1) { Collections.sort(rangeListPaths, RLP_COMPARATOR); RangeListPath rangeListPath = (RangeListPath) rangeListPaths.get(0); RangeListPath firstRLP = rangeListPath; long youngest = rangeListPath.myRangeList.getRanges()[0].getStartRevision(); long nextYoungest = youngest; int numRevs = 1; for (; nextYoungest == youngest; numRevs++) { if (numRevs == rangeListPaths.size()) { numRevs++; break; } rangeListPath = (RangeListPath) rangeListPaths.get(numRevs); nextYoungest = rangeListPath.myRangeList.getRanges()[0].getStartRevision(); } numRevs--; long youngestEnd = firstRLP.myRangeList.getRanges()[0].getEndRevision(); long tail = nextYoungest - 1; if (nextYoungest == youngest || youngestEnd < nextYoungest) { tail = youngestEnd; } PathListRange pathListRange = new PathListRange(); pathListRange.reverseMerge = reverseMerge; pathListRange.myRange = new SVNMergeRange(youngest, tail, false); List paths = new LinkedList(); for (int i = 0; i < numRevs; i++) { RangeListPath rp = (RangeListPath) rangeListPaths.get(i); paths.add(rp.myPath); } pathListRange.myPaths = (String[]) paths.toArray(new String[paths.size()]); combinedList.add(pathListRange); for (int i = 0; i < numRevs; i++) { RangeListPath rp = (RangeListPath) rangeListPaths.get(i); SVNMergeRange range = rp.myRangeList.getRanges()[0]; range.setStartRevision(tail + 1); if (range.getStartRevision() > range.getEndRevision()) { if (rp.myRangeList.getSize() == 1) { rangeListPaths.remove(0); i--; numRevs--; } else { SVNMergeRange[] ranges = new SVNMergeRange[rp.myRangeList.getSize() - 1]; System.arraycopy(rp.myRangeList.getRanges(), 1, ranges, 0, ranges.length); rp.myRangeList = new SVNMergeRangeList(ranges); } } } } if (!rangeListPaths.isEmpty()) { RangeListPath firstRangeListPath = (RangeListPath) rangeListPaths.get(0); while (!firstRangeListPath.myRangeList.isEmpty()) { PathListRange pathListRange = new PathListRange(); pathListRange.reverseMerge = reverseMerge; pathListRange.myPaths = new String[] { firstRangeListPath.myPath }; pathListRange.myRange = firstRangeListPath.myRangeList.getRanges()[0]; SVNMergeRange[] ranges = new SVNMergeRange[firstRangeListPath.myRangeList.getSize() - 1]; System.arraycopy(firstRangeListPath.myRangeList.getRanges(), 1, ranges, 0, ranges.length); firstRangeListPath.myRangeList = new SVNMergeRangeList(ranges); combinedList.add(pathListRange); } } return combinedList; } private SVNMergeInfoManager getMergeInfoManager() { if (myMergeInfoManager == null) { myMergeInfoManager = new SVNMergeInfoManager(); } return myMergeInfoManager; } private class RangeListPath { String myPath; SVNMergeRangeList myRangeList; } private class PathListRange { public boolean reverseMerge; String myPaths[]; SVNMergeRange myRange; } private class PathInfo { FSNodeHistory myHistory; boolean myIsDone; boolean myIsFirstTime; long myHistoryRevision; String myPath; public PathInfo getHistory(boolean strictNodeHistory, long start) throws SVNException { FSNodeHistory history = null; if (myHistory != null) { history = myHistory.getPreviousHistory(strictNodeHistory ? false : true); myHistory = history; } else { FSRevisionRoot historyRoot = myFSFS.createRevisionRoot(myHistoryRevision); history = historyRoot.getNodeHistory(myPath); history = history.getPreviousHistory(strictNodeHistory ? false : true); if (myIsFirstTime) { myIsFirstTime = false; } else if (history != null) { history = history.getPreviousHistory(strictNodeHistory ? false : true); } } if (history == null) { myIsDone = true; return this; } myPath = history.getHistoryEntry().getPath(); myHistoryRevision = history.getHistoryEntry().getRevision(); if (myHistoryRevision < start) { myIsDone = true; } return this; } public boolean checkHistory(long currentRevision, boolean strictNodeHistory, long start, boolean changed) throws SVNException { if (myIsDone) { return changed; } if (myHistoryRevision < currentRevision) { return changed; } changed = true; getHistory(strictNodeHistory, start); return changed; } } }